JJ2+ AngelScript readme

Introduction

Arguably one of the biggest advances JJ2+ brings to JJ2 is the introduction of AngelScript. The AngelScript website describes it as "an extremely flexible cross-platform scripting library designed to allow applications to extend their functionality through external scripts." What this means for you as a level designer is that you can write code in a C/C++-like scripting language—anywhere from two or three to potentially hundreds or thousands of lines—that will directly alter the level, characters, and other objects within JJ2. AngelScript lets you change tiles without setting up trigger scenery, change the palette without cycling to a new level with a different tileset, force players to be Spaz without setting up elaborate systems of springs and sucker tubes, create enemies from nowhere, and so much more.

(Note, however, that JJ2's AngelScript implementation is all but exclusively local in its domain. If a level is being run in an online server, essentially the only AngelScript code that will be passed on to the rest of the server is changing a local player's health/character, firing bullets, and chatting. Pretty much everything else is local to the instance of JJ2 calling the AngelScript code in the first place, and levels should be designed accordingly. (Of course, this is not a concern for Single Player levels.) Fortunately, everyone in the server will be running the same AngelScript code, making for somewhat more equal experiences than one might otherwise expect.)

This document is not intended teach you the syntax of AngelScript, which has a dedicated online manual for this very purpose. It should however be noted that JJ2's AngelScript implementation includes the string, array, and math modules.

First, though, you need to know a) how to call AngelScript functions within JJ2 and b) how to change things within JJ2 once a function has been called. To begin with, open an old level or begin a new level that you would like to add some AngelScript functionality to, and take note of the filename (e.g. myLevel.j2l). Next create a plain text file (in Notepad or some similar editor, or a code syntax highlighter program that supports AngelScript if you have one) and save it to your JJ2 directory, giving it the same filename as the level but with the extension .j2as instead of .j2l. Thus: myLevel.j2as for myLevel.j2l, castle1.j2as for castle1.j2l, plus-gemtest.j2as for plus-gemtest.j2l, etc. This new .j2as file is where all the code for this particular level will live. Chunks of code may be shared across multiple levels using the #include directive, but this should not be used in multiplayer levels, and is more complicated than this introduction need go into.

Next you will need to define one or more functions in the .j2as file for JJ2+ to call. Let's say that you're making a Single Player level and want to change the level's music, but not to either boss.j2b nor boss2.j2b, which are the only filenames the Activate Boss event would allow you. With AngelScript, this couldn't be simpler. Write the following in myLevel.j2as:

void onFunction0(jjPLAYER@ playerWhoTouchedTheTextEvent) {
  jjMusicLoad("tubelec.j2b");
}

Finally you need to set up to the level to call onFunction0. For this, you will need to edit your JCS.ini to include the following entry for 207 in the [Events] group:

207=Text |+|Trigger|Text| |TextID:8|Vanish:1|AngelScript:1|Offset:8

The boolean parameter "AngelScript," when set to 1, will call a hook function from within the level's corresponding .j2as file. Which function it calls is determined by the "TextID" parameter, which ranges from 0–255. TextID=3,AngelScript=1 will call onFunction3; TextID=150,AngelScript=1 will call onFunction150; and so on. In this case, each time a local player touches a Text event with TextID=0,AngelScript=1, JJ2+ will try to load and play the music file "tubelec.j2b."

The jjMusicLoad function is actually something of a special case, because it will not load a new music file if the file requested is already the currently playing music file. Thus hitting a Text event with TextID=0,AngelScript=1 multiple times will not have any undesirable effect like restarting the music. However, plenty of other functions do have cumulative effects, and it is not always desirable for a player to call the same function more than once, e.g. if there's a whole column of identical Text events to ensure that the player hits at least one of them while passing through an area. This is where the boolean "Vanish" parameter comes in handy. Unlike when AngelScript=0, when AngelScript=1, Vanish=1 disables the entire onFunction# function from being called by any Text events anywhere in the level. You can reenable, for example, onFunction5 by writing jjEnabledASFunctions[5] = true; in another AngelScript function or by calling the global function jjEnableEachASFunction, but unless you do one of those things, the function will remain disabled for the rest of the level. (It is still possible to call a disabled function directly from another function in the .j2as file, but not from another Text event.)

Finally the "Offset" parameter of Text events provides an optional one-byte parameter to the AngelScript function. The function for a given TextID, e.g. 5, can be defined one of four different ways, and in each case the "Offset" parameter is parsed accordingly. Note that if the AngelScript parameter is of type int8—a signed 8-bit integer—then Offset will be interpreted as a JCS parameter of type -8, instead of type 8. Note also that if you define multiple AngelScript functions with the same name, JJ2+ will not know which one to call. This is undefined behavior and the result may vary across JJ2+ releases, so don't do it.

void onFunction5(jjPLAYER@ player) {}
void onFunction5(jjPLAYER@ player, bool paramName) {}
void onFunction5(jjPLAYER@ player, uint8 paramName) {}
void onFunction5(jjPLAYER@ player, int8 paramName) {}

The contents of your .j2as file must all be syntactically correct AngelScript. Any error in or outside of any function will prevent any and all AngelScript from running in the level. To get information about such errors, you will need to add a line AngelscriptDebug=True to the [General] group of plus.ini. (AngelscriptDebug defaults to false.) Information about errors and warnings will then be displayed in the chatlogger window, assuming the window has fully loaded by the time the error occurs. It is also possible to send your own debugging information using the global function jjDebug(string text), though, as with any other AngelScript function, it will only work if everything is formatted correctly.

Besides this, there are a number of hook functions which, if defined in a .j2as file, will be called at various points. More may well be added in subsequent releases, but here are the basic ones available so far. (A few more are described in the jjCANVAS section below.)

void onLevelLoad()
This function is called once, at the beginning of the level, and never again. If your level's tileset has a red textured background but you want a blue one, this is the place to change its colors. If you want the level to start with water at a certain height without placing Water Level events all around every start position, this is the place for that. And so on.
void onLevelBegin()
Also called only at the beginning of the level, but a little bit later, once things have had time to be initialized. For example, onLevelLoad is fired before any jjOBJs have been created and before certain jjPLAYER properties have been initialized, so onLevelBegin allows you to modify those things as well.
void onGameStart()
void onGameStart(bool firstTime)
void onGameStop()
void onGameStop(bool firstTime)
In multiplayer games, these functions are called whenever the game gets started or stopped. The firstTime parameter, if defined, is intended to capture starts/stops resulting from the /autostart command, so it should be true for the game being started or stopped at the beginning of the level, and false thereafter. However, it will also be true for clients who join the level partway through.
void onLevelReload()
This function is only ever called in Single Player, where it is called every time the player dies, after everything in the level has been reinitialized. Since a great number of things get reset in Single Player when the player dies, this is your chance to make sure some things stay the same.
void onMain()
Unlike the above functions, which are very rare, this function is called absolutely constantly. It is important to understand that JJ2 measures time in "ticks," which number 70 to a second. (Correspondingly, when setting the duration for a sugar rush or somesuch, you will need to multiply by 70 to get the desired number of seconds.) The global property jjGameTicks will tell you how many ticks have elapsed at any given point. onMain is fired once per tick, and allows you to check constantly whether a given property has changed, move an object in a circle, or whatever else you find desirable.
void onPlayer(jjPLAYER@ play)
This function can be called even more often than onMain: it is called once per tick per local player. If you're playing splitscreen with two players, for instance, it will be called two times per tick. This is a useful distinction because of the jjPLAYER@ parameter, which points to the local player every time onPlayer is called. If you want to prevent blaster from ever being used in a level, for instance, void onPlayer(jjPLAYER@ play) { play.noFire = (play.currWeapon == WEAPON::BLASTER); } is one way of doing that, whereas trying to do the same in onMain would at minimum require manually looping through the jjLocalPlayers array. To check which jjPLAYER is being invoked during any given onPlayer, use the jjPLAYER.localPlayerID property.
(JJ2+ used to encourage use of a global property p (or occasionally jjP), which performed the same function as the jjPLAYER@ argument. This coding style still works in certain cases, to ensure backwards compatibility with older scripts, but can be unclear and is deprecated/stylistically discouraged.)
void onPlayerInput(jjPLAYER@ play)
This function works essentially the same way as onPlayer, and is called very slightly earlier in the tick, but with a caveat: it is only called when the player's input properties, keyRight and keyJump and so on, have had the potential to change. In particular, it is not called while chatting in a multiplayer game. Because of this unpredictability, it should only be used for situations specifically relating to input, for instance swapping the effects of a player's right and left keys.
void onPlayerTimerEnd(jjPLAYER@ play)
This function is something of a special case. By default, when a Player Timer runs out (see the jjPLAYER section below), this function will be called for the player whose timer just ran out. However, a single level may have multiple timer sections with different purposes -- one where you die for not doing something quickly enough, one where you warp after a certain delay, and so on -- so it is also possible to change the function called using the jjPLAYER method timerFunction. Thus onPlayerTimerEnd is simply the default function name, not the only one you are allowed to use for this purpose.

The rest of this document is divided into three sections. First there are sections for all the available classes that you may create and manipulate, and also full lists of their properties and methods. Second are two lists, one of global properties and one of global functions. Like the classes, all global properties and functions begin with the letters "jj," with the sole exceptions of the deprecated p and the hook functions, all of which begin with the letters "on." This is a guarantee of backwards compatibility from future versions of JJ2+. While new global properties and functions will undoubtedly be added to AngelScript in future releases of JJ2+, as long as you don't begin any of your own properties and functions with "jj" or "on," there will be no naming conflicts and your scripts will continue to function correctly.

Finally there are two appendices containing instructions for specific topics, followed by a third which contains the various enums used by certain properties, methods, and functions. Each enum is nested within a namespace, often unique to that enum, and both the name of the namespace and all the values of the enum are in all caps. Such enums may be the sole options for the properties and parameters that use them. For example, to configure the level so that water and ambient lighting can coexist, you will need to set the global property jjWaterLighting to WATERLIGHT::GLOBAL. You do not set it to 1 (a number), or GLOBAL (a variable that doesn't exist in the main namespace), or "GLOBAL" (a string), or CHAR::JAZZ (another enum value), etc. As well, certain arrays are indexed by enums rather than (or as well as) by integers; for example, you access a player's number of red gems through jjPLAYER.gems[GEM::RED], not jjPLAYER.gems[1], although jjPLAYER.ammo[WEAPON::BOUNCER] and jjPLAYER.ammo[2] are both acceptable. Depending on the commonality of an enum and the number of different values it has, either its values will be listed whenever it comes up in a property/method/function or they will be listed in full in the appendix at the bottom of this file. Like any other data type, enums may be used as parameters in your own defined functions, so long as you remember to attach the namespace to the enum name.

It is perfectly understandable if much of this seems overwhelming at first. You are advised to look through the example levels provided in your JJ2+ download, many of which include examples of AngelScript in action. Even if the specific properties being affected in a given level do not interest you, that level can still serve as an example as how AngelScript is written generally. The Jazz 2 Online website additionally hosts an AngelScript Snippets section, where people share pieces of generally useful (and hopefully well-commented) code. And if all else fails, head to the JazzJackrabbit Community Forums to ask for help. Good luck!

Classes

class jjPAL

This is the class used by each level's ingame palette, used for drawing pretty much everything besides 16-bit water and textured background fade colors, which can be changed by the global function groups jjSetWaterGradient and jjSetFadeColors respectively. As such, changes made here can be very far-reaching and dramatic. Palette-editing in AngelScript follows essentially a two step process: first, make all the changes you desire to a (non-const) jjPAL object, and second, call that object's apply() function to make it be the current palette in use by the game. Here's a basic example:

void onLevelLoad() {
  jjPAL newPal;
  newPal.load("Castle1.j2t");
  newPal.gradient(190,0,255, 0,0,147, 112, 8); //change the colors of the knight statues
  newPal.apply(); //the game will now use a slightly-modified version of the Castle1 palette
}

In the simple example above, it would not actually be needed to declare a new jjPAL object. Instead, you could use jjPalette, which contains the current palette actually in use by the game. When you call apply() on any other jjPAL object, you copy its contents to jjPalette. Editing it directly is good for cumulative changes, but if you'd like to revert back to a previous state, you'll need a second jjPAL object. Alternatively, you could save the current state by copying it to a second jjPAL object using the assignment operator, e.g. jjPAL myPal = jjPalette;.

There is also a const jjPAL, jjBackupPalette, which maintains the palette that came with the tileset and will never change.

jjPALCOLOR color[256]
At its heart, a palette is a collection of 256 colors, and you can access them individually through the color array. In fact, you could if you chose reproduce nearly every jjPAL method here by directly altering the colors, but the methods are here to save you time, so don't do that. The details of jjPALCOLOR objects are listed below this section, though most basically you just use their properties red, green, and blue.
void apply() const
Causes the contents of this jjPAL object to be the current colors in use by the game, and by extension jjPalette.
Note that this function does a fair bit of housekeeping behind the scenes to make sure everything is propertly converted to the new palette—there's a difference between myPal.apply(); and jjPalette = myPal;—and so it should always be called after doing any edits, even if you've been making the edits directly to jjPalette. Failure to do so can have unpredictable consequences.
void copyFrom(uint8 start, uint8 length, uint8 start2, const jjPAL &in source, float opacity)
Overlays length colors from source onto the current palette, beginning at start on this palette and start2 on source. If opacity is below 1.0, the new colors will only partially replace the old.
void fill(uint8 red, uint8 green, uint8 blue, uint8 start, uint8 length, float opacity = 1.0)
void fill(uint8 red, uint8 green, uint8 blue, float opacity = 1.0)
Replaces a series of length colors beginning at start with the color red,green,blue. If opacity is specified and below 1.0, the new color will only partially replace the old ones, making for a tinting effect. Leave out the start and length arguments to fill (or tint) the entire palette.
void gradient(uint8 red1, uint8 green1, uint8 blue1, uint8 red2, uint8 green2, uint8 blue2, uint8 start = 176, uint8 length = 32, float opacity = 1.0)
Replaces a series of length colors beginning at start with a gradient beginning with red1,green1,blue1 and ending with red2,green2,blue2. If opacity is specified and below 1.0, the new colors will only partially replace the old, making for a tinting effect.
The default values for start and length will set a gradient for the colors used by water and (in most tilesets) textured backgrounds.
bool load(string& filename)
Loads a palette from the specified file. If the file extension is ".j2t", will treat it as a tileset file and will try to access the palette stored in the file. Otherwise, tries to treat it as a palette file saved in "Color Map" format in Palsuite, and if that fails, simply reads the first 1024 bytes of the file. Returns false if the file cannot be found in either the main game folder or the cache subfolder, or if the file is fewer than 1024 bytes long; otherwise true.
void reset()
Loads the original colors used by the tileset. There is no non-stylistic difference between myPal.reset(); and myPal = jjBackupPalette;.

class jjPALCOLOR

The basic units of jjPAL objects. At their heart, they're just tiny collections of RGB values, but methods for playing with the HSL values are also provided in case you don't mind using up a little bit more processing power. You can define and initialize your own jjPALCOLOR instances in two ways: either by using the default constructor and setting each property manually or by using a provided jjPALCOLOR(uint8 red, uint8 green, uint8 blue) constructor. Alphabetical order is ignored in favor of traditional order in the list below

uint8 red
How red this palette color is.
uint8 green
How green this palette color is.
uint8 blue
How blue this palette color is.
uint8 getHue() const
The hue of this palette color.
uint8 getSat() const
The saturation of this palette color, where 0 is grayscale and 255 is most saturated.
uint8 getLight() const
The lighting of this palette color, where 0 is black and 255 is white.
void setHSL(int hue, uint8 sat, uint8 light)
Changes the entire color to a brand new one derived from the given HSL values. Due to the complexity of the calculations involved, you can only change all three values at a time, so if you want to leave, for instance, saturation constant, you'll need to read the old value through getSat() and then use that as the sat parameter.
hue is an int instead of a uint8 because it's a loop instead of a scale. 2, 258, 514, -254, etc., are all equally valid and all mean the same hue.

class jjPLAYER

If you're writing some AngelScript for Jazz Jackrabbit 2, one of the most natural things to want to affect is Jazz Jackrabbit himself! Or Spaz, or Lori, or so on, as the case may be. Many hook functions—all the onFunction# functions, onPlayer and onPlayerInput, all the onDraw# functions, and so on—include jjPLAYER@ parameters so that you know which (local) player you should be affecting within a given chunk of code.

In some cases you'll want access to some player other than the one who triggered a function. For this, there are two global arrays: jjPlayers[32] and jjLocalPlayers[4]. While JJ2/AngelScript shouldn't actually crash if you try to access a player that doesn't exist in the game, it may not give you the most useful results, so the global properties jjPlayerCount and jjLocalPlayerCount should also be kept in mind. Additionally, jjPLAYER objects have boolean isActive and isLocal read-only properties to help you figure out which jjPLAYER objects to affect with a given section of code.

(Of course, since AngelScript is predominantly local in its domain, the isLocal==false jjPLAYER objects are mostly only useful for reading values, not writing, and even then only a few of those values—xPos, yPos, currWeapon, and so forth—will actually be locally accurate. Trying to set, say, jumpStrength for a non-local jjPLAYER won't do you any good.)

For the most part, properties and methods of jjPLAYER objects are familiar from regular JJ2. The antiGrav, noclipMode, and noFire booleans are all new, but are only single properties and thus not too complicated. What merits discussion is the Player Timer, a new feature available in all game modes but Race (where it would interfere with the lap times). Put loosely, a Player Timer is a player-specific on-screen countdown which, once its time runs out, calls a certain AngelScript function (usually onPlayerTimerEnd), passing as an argument the player that it belonged to. The most important jjPLAYER methods are timerStart() and timerFunction(), which respectively begin the Player Timer (and set how long it will last) and set the effect of the Player Timer running out. Explaining the Player Timer in detail is not the function of this document, however, so see plusTimerEx.j2l/.j2as for a fuller explanation and examples.

bool alreadyDoubleJumped
Whether it is currently possible for the player to double jump, assuming they're Spaz and currently in the air.
int ammo[WEAPON::Weapon]
int ammo[9]
How much ammo the player has of each ammo type. Possible constants appear in the appendix below, or you may use simple 1-indexed numbers instead (all values besides 1-9 will evaluate to WEAPON::CURRENT).
Note that JJ2+ prevents the use of weapons without corresponding +3/+15/powerup events in the level while in online servers. To remedy this, hide some such ammo-providing event somewhere in the level.
bool antiGrav
Whether the player falls up (true) or down (false).
(For the record, this mode is something of a work-in-progress. Plenty of events—notably objects that you stand on, like crates and swinging platforms—don't work very well with it yet. However, future revisions should be at least backwards compatible with what works already, so don't hold off on using it for that reason unless it truly can't do yet what you need it do.)
int ballTime
If greater than 0, how much longer (in ticks) the player will be tucked into a ball.
int blink
If greater than 0, how much longer (in ticks) the player will be blinking and invincible and unable to collide with other players, as if recently hurt. Works online.
int boss
The object ID of the jjOBJ whose energy is shown in the boss health meter, or 0 if inapplicable.
More specifically, the boss health meter will show the energy of the jjOBJ as a percentage of its initial health as described in its entry in jjObjectPresets. If you create a Tuf Turtle enemy, give it 100 health, and assign a jjPLAYER's boss its object ID, JJ2 will still assume the Tuf Turtle started out at 4 health, not 100. On the other hand, if you write jjObjectPresets[OBJECT::TUFTURT].energy = 100; first, then the boss health meter will work as you might want it to.
bool bossActivated
The bool set by the Activate Boss event or the activateBoss method. In general, you should make sure this property is true for at least one local player when writing behaviors for custom bosses.
const float cameraX
const float cameraY
The top left corner of the player's current view of the level, as measured from the top left corner of layer 4.
const CHAR::Char charCurr
The player's current character (CHAR::JAZZ, CHAR::SPAZ, CHAR::LORI, CHAR::BIRD, or CHAR::FROG). This is a read-only value, because you should use morphTo or another related method to change it instead.
CHAR::Char charOrig
Which character the player began the level as (CHAR::JAZZ, CHAR::SPAZ, or CHAR::LORI), aka the character that the Revert Morph event switches them to.
int coins
How many coins the player has.
If you want to require the player to have a certain number of coins to do something, like with coin warps, consider using the more elaborate testForCoins method instead.
const int currTile
A shortcut value, always equalling xPos/32 + yPos/32*65536. Since both xPos and yPos are easily accessible properties, it really only makes sense to use currTile to compare against previous values of currTile, i.e. to see if the player has moved or not.
uint8 currWeapon
Which ammo type the player currently has selected. Possible constants appear in the appendix below, or you may use simple 1-indexed numbers instead (all values besides 1-9 will evaluate to WEAPON::CURRENT).
int direction
Which direction the player is facing.
int fastfire
The waiting time between shots, as decreased by Fastfire events or the JJFIRE cheat code. Starts out at 35—half a second—and decreases to a minimum of 6 (from Fastfire events) or 1 (JJFIRE).
const int flag
The object ID of the flag the player is carrying, or 0 if the player is not carrying a flag.
int fly
Possible special constant values are FLIGHT::NONE, FLIGHT::FLYCARROT, or FLIGHT::AIRBOARD, from the FLIGHT::Mode enum.
If the player is currently using a copter or Cheshire2 object, fly will equal the object ID of that object plus one, which is to say, the jjOBJ the player is holding will be jjObjects[p.fly - 1].
int food
How much food the player has eaten.
Setting this to 100 will not cause a sugar rush. Use the startSugarRush method instead.
int8 frozen
0 if unfrozen; otherwise, constantly counts down towards 0.
int gems[GEM::Color]
How many gems the player has collected. Possible values of GEM::Color are GEM::RED, GEM::GREEN, GEM::BLUE, and GEM::PURPLE.
uint8 health
How many hearts the player has remaining. If you set this in an online server, all other players will be notified of the change.
int invincibility
How much longer the player will be invincible. (Does not work in servers.) Specifically, the absolute value is the remaining duration of the invincibility; a positive number will display the invincibility effect around the player, but a negative number (or zero) will not.
const bool isActive
Does this jjPLAYER object correspond to an actual player in the game, local or otherwise? Note that spectating players are in fact counted as active.
const bool isLocal
Is this jjPLAYER object controlled by this instance of JJ2?
bool keyDown
bool keyFire
bool keyJump
bool keyLeft
bool keyRight
bool keyRun
bool keySelect
bool keyUp
A series of bools controlling whether the player believes its various control keys are being pressed. Note that always setting keyFire to true is not the same as making the player constantly fire, unless their fastfire property equals 1.
float jumpStrength
How high the player jumps. Defaults to -10.
uint8 lighting
The player's current level of ambient lighting, as affected by the Set Light and Reset Light events.
int lives
In single player or cooperative mode, how many lives the player has remaining.
const int localPlayerID
Which local player the player is, in case of splitscreen. 0-3.
const string name
The player's name.
bool noclipMode
Whether the player is currently in Noclip Mode, as caused by Sucker Tube events with the "BecomeNoclip" parameter set to 1. Setting this to true could be dangerous if the level design is not prepared for it.
bool noFire
Whether the player is currently allowed to shoot bullets. Hides the current weapon/ammunition display while true.
int platform
The object ID of the object the player is currently standing on, or 0 if inapplicable.
const int playerID
In online play, which number the player is in the server's list of players. 0-31.
bool powerup[WEAPON::Weapon]
bool powerup[9]
Whether each ammo type is powered-up or not. Possible constants appear in the appendix below, or you may use simple 1-indexed numbers instead (all values besides 1-9 will evaluate to WEAPON::CURRENT).
Note that JJ2+ prevents the use of powered-up weapons without corresponding powerup events in the level while in online servers. To remedy this, hide powerup events somewhere in the level.
bool running
Is the player currently running? Detects the run key, capslock (if there is only one local player), and the /run <on|off> command. Apparently running is set based on keyRun, and then other parts of code query running exclusively.
int score
The player's current score. JJ2 only increments this in multiples of 50 (or 10 if you count the unpatched Butterfly enemy), but that's up to you.
int scoreDisplayed
The number currently displayed for the player's score. Whenever score increases, scoreDisplayed takes a few moments to catch up to the new value. Unless the level defines an onDrawScore function, in which case this property could mean or do anything.
int shieldTime
How much longer (in ticks) the player's shield will last, or 0 if the player doesn't have a shield.
int shieldType
Which shield the player currently has, assuming the player has a shield at all. In place of numbers, you may also use the dedicated SHIELD::Shield enum, options being NONE, FIRE, BUBBLE/WATER, LIGHTNING/PLASMA, and LASER.
int stoned
How much longer will the player be stoned, like after touching a smoke ring.
const int subscreenX
const int subscreenY
Where the player's subscreen begins in the window. These will usually equal 0, but playing with more than one local player, in one of the two 3D modes, or both, may produce other numbers. For example, in a level with horizontal splitscreen while playing with Top-And-Bottom 3D, player 2's second subscreen will be drawn with subscreenY equal to 75% of jjSubscreenHeight.
const bool teamRed
True if the player's on the red team, false if the player's on the blue team (or not in a team game).
bool timerPersists
Should dying disable the Player Timer (false) or have no effect on it (true)?
const TIMER::State timerState
The current state of the Player Timer (TIMER::STOPPED, TIMER::STARTED, or TIMER::PAUSED), for comparisons only. Use the corresponding timerStart, timerStop, timerPause, and timerResume methods to set this instead.
int timerTime
How many ticks are left on the Player Timer.
const int warpID
If this number is higher than 0, the player is currently warping, and it will equal the ID of the Warp event plus one, a range of 1-256.
It is often wise to make sure this property equals 0 before calling a warp method, lest the player be locked into a constant loop of beginning to warp but never finishing it. Something like if (conditionsForWarping && player.warpID == 0) player.warpToID(25);. Performing this check is however unnecessary for fast warps, or warps triggered by Text events, since that code is only called when the player first enters the tile.
float xAcc
Horizontal acceleration in pixels, positive or negative.
float xOrg
If this or yOrg are non-zero, where the player should respawn after death.
float xPos
Horizontal location in pixels.
float xSpeed
Horizontal speed in pixels, positive or negative.
float yAcc
Vertical acceleration in pixels, positive or negative.
float yOrg
If this or xOrg are non-zero, where the player should respawn after death.
float yPos
Vertical location in pixels.
float ySpeed
Vertical speed in pixels, positive or negative.
void activateBoss(bool activate = true)
Activates all bosses and disables the player's sugar rush if applicable. Unlike the Activate Boss event, does not change the music track. (Use jjMusicLoad for that instead.)
Setting activate to false will attempt to deactivate bosses, but this mostly only results in the boss health meter going away. The jjPLAYER object's boss is left unchanged unless you change it manually, and bosses do not stop moving around.
void cameraFreeze(float xPixel, float yPixel, bool centered, bool instant)
Fixes the camera in place, like when encountering a Robot Boss, until the corresponding cameraUnfreeze method is called. If instant is left false, the camera will take roughly half a second to scroll to its target. If centered is left false, the camera will position itself so that xPixel,yPixel is in the top left corner of the screen; otherwise, that position will be in the very center.
void cameraUnfreeze()
If cameraFreeze has been called, undoes the effect and lets the camera freely follow the player once again.
int extendInvincibility(int duration)
A convenience method to extend the absolute value of the player's invincibility property by the absolute value of the duration parameter, which also makes invincibility positive (visible) if duration is visible. For example, collecting a full energy carrot extends invincibility by +350, whereas buttstomping most enemies extends invincibility by -70.
int fireBullet(uint8 gun = WEAPON::CURRENT, bool depleteAmmo = true,
bool requireAmmo = true, DIRECTION::Dir direction = DIRECTION::CURRENT)
int fireBullet(uint8 gun, bool depleteAmmo, bool requireAmmo, float angle)
Causes the player to fire, using either the specified ammo type or the current one. The return value will be the object ID of the new bullet, unless the weapon fires more than one bullet at a time. This action is visible for all players in an online server.
Possible gun constants may be found in the appendix at the bottom of the page, or you may use simple 1-indexed numbers instead (all values besides 1-9 will evaluate to WEAPON::CURRENT).
If depleteAmmo is false, the method will not affect how much of the ammo type the player has remaining.
If requireAmmo is false, the player can fire a bullet of that type even if they don't have any ammo of that type.
Possible values for direction are DIRECTION::RIGHT, DIRECTION::LEFT, DIRECTION::UP, and DIRECTION::CURRENT. If mouse aiming is enabled, DIRECTION::CURRENT will evaluate to whatever angle the mouse cursor is at, rather than whichever direction the player is physically facing. Alternatively you may pass a float angle argument instead of an orthogonal direction argument, in which case 0 is up, .5 * pi is right, pi is down, 1.5 * pi is left, and 2 * pi is up again.
void freeze(bool frozen = true)
Freezes the player for the same length of time as the Freeze Enemies pickup, or unfreezes the player if frozen is set to false. Helper method.
bool hurt(int8 damage = 1, bool forceHurt = false, jjPLAYER@ attacker = null)
Attempts to hurt the player damage hearts, or at least strip the player of their bird or reduce their shield time. If attacker is left null, or if it's the same player as the one getting hurt, the injury will be counted as coming from the level, and if a death results, it will be marked online with the "ate it/you killed yourself" text. This is what you should do for injuries from enemies and other level-based factors. On the other hand, if the player dies from the hurt call and attacker is another player in the server, that player will get credit for the roast. Returns false if neither the hurtee nor the hurter are local players, or if forceHurt (which bypasses traditional safety sources like the invincibility and blink properties and buttstomping) is false and something or other prevents the hurting from happening.
void kill()
Kills the player instantly. If you want the player to be roasted by some other player in an online server, use hurt with a high damage value instead.
void limitXScroll(uint16 left, uint16 width)
Works like a Limit X Scroll event with the corresponding Left and Width parameters. Remember that these are measured in tiles, not pixels.
CHAR::Char morph(bool rabbitsOnly = false, bool morphEffect = true)
Cycles the player's character to the next on the list, just like the JJMORPH cheat: Jazz-Spaz-(Lori-)Bird-Frog. Or if rabbitsOnly is true, skips bird and frog and acts like a morph monitor instead. Returns the player's new character: CHAR::JAZZ, CHAR::SPAZ, CHAR::LORI, CHAR::BIRD, or CHAR::FROG.
CHAR::Char morphTo(CHAR::Char charNew, bool morphEffect = true)
Sets the player's character to charNew, possible values CHAR::JAZZ, CHAR::SPAZ, CHAR::LORI (in TSF), CHAR::BIRD, or CHAR::FROG.
bool offsetPosition(int xPixels, int yPixels)
Instantly moves the player xPixels pixels to the right and yPixels pixels down. The camera instantly readjusts itself to follow, as does the glowing trace following the player while running. The best way of creating seemlessly looping levels.
CHAR::Char revertMorph(bool morphEffect = true)
Reverts the player to the character they were when they began the level, just like the Revert Morph event.
int setScore(int score)
Sets the player's score. While setting the score through the score property is slightly delayed as scoreDisplayed increases/decreases to catch up to the new value, this function sets both properties at once. Probably most helpful as a function for quickly showing you debug information.
void showText(uint8 textID, uint8 offset, STRING::Size size = STRING::SMALL)
void showText(string text, STRING::Size size = STRING::SMALL)
Displays text on the player's screen either like a Text event, with the corresponding textID and offset parameters, or simply a specified text. Unique size values are SMALL, MEDIUM, and LARGE. Note that not all glyphs that appear in one size character set may appear in another; for instance, the underscore character is unique to SIZE::SMALL.
bool startSugarRush(int time = 1400)
Gives the player a sugar rush lasting time ticks, unless their bossActivated property equals true, in which case the method returns false.
bool testForCoins(int numberOfCoins)
If the player has at least numberOfCoins coins, depletes their coins by numberOfCoins and returns true. Otherwise displays a warning onscreen that they need more coins to continue. Basically the same as a coin warp event, but you get to choose the result.
bool testForGems(int numberOfGems, GEM::Color type)
If the player has at least numberOfGems type-colored gems, depletes their type gems by numberOfGems and returns true. Otherwise displays a warning onscreen that they need more gems to continue. Basically the same as a coin warp event, but you get to choose the result, and it's for gems instead of coins.
Possible values of type are GEM::RED, GEM::GREEN, GEM::BLUE, and GEM::PURPLE.
void timerFunction(string functionName)
void timerFunction(jjVOIDFUNC function)
void timerFunction(jjVOIDFUNCPLAYER function)
When the Player Timer hits zero without being stopped artifically, AngelScript will call the function named by this method (a string is acceptable, but pointing directly to the function is advised instead), setting the (technically optional) jjPLAYER@ property to point to the player whose Player Timer just expired. This defaults to onPlayerTimerEnd, aka void onPlayerTimerEnd(jjPLAYER@). It is up to you to define this function and decide what should happen to the player.
TIMER::State timerPause()
Pauses the Player Timer and returns TIMER::PAUSED.
TIMER::State timerResume()
Resumes the Player Timer and returns TIMER::STARTED.
TIMER::State timerStart(int ticks, bool startPaused = false)
Begins the Player Timer (and optionally pauses it) with ticks ticks remaining on the clock. Returns TIMER::STARTED or TIMER::PAUSED, depending.
TIMER::State timerStop()
Stops the Player Timer and returns TIMER::STOPPED.
bool warpToID(uint8 warpID, bool fast = false)
Warps the player to a Warp Target event with the specified Warp ID, instantly if fast is true or using the standard warp effect if fast is false.
bool warpToTile(int int xTile, int yTile, bool fast = false)
Warps the player to the specified tile, instantly if fast is true or using the standard warp effect if fast is false.

class jjOBJ

Objects are the enemies, pickups, platforms, light sources, and so forth.

The most important global property is jjObjects[], a list of all the potential objects in the game. To obtain a jjOBJ to play with, you'll need to specify one of the entries in jjObjects[], e.g. jjOBJ@ o = jjObjects[1]. The global property jjObjectCount is useful for looping through the contents of jjObjects[]. When performing such a loop, it is important to test that a given jjOBJ has a true isActive property, since deleted objects may leave traces of themselves in memory and these traces can only be distinguished from currently-extant objects using isActive. To narrow your results, comparing the eventID class to specific OBJECT::Object constants is also useful, e.g.

for (int i = 1; i < jjObjectCount; i++) { //jjObjects[0] is never active, so we start the index at 1
  jjOBJ@ o = jjObjects[i];
  if (o.isActive && o.eventID == OBJECT::NORMTURTLE) {
    o.state = STATE::KILL;
  }
}

Besides a loop invoking jjObjectCount, object IDs can also be obtained through the jjPLAYER property platform, which gives the ID of the object the player is standing on (if any), the jjPLAYER property boss, which gives the ID of the object whose health is displayed in the boss health meter on that player's screen (if any), and the jjOBJ properties creatorID and creatorType, since if the latter equals CREATOR::OBJECT, the former will refer back to another object which created that one. Usually creatorID equals 0, but occasionally—for example, enemy bullets or objects created by generators—it will point to an object, though you should still remember to check isActive to make sure the object's creator hasn't already been destroyed.

Another reliable way to obtain an object ID is the global function jjAddObject, which returns the ID of the object it adds so that you can then look it up in jjObjects and set some of its properties. The last three parameters can be—and usually are—left out, but you're free to do as you like. Both jjPLAYER and jjOBJ additionally have fireBullet methods which will also usually produce object IDs.

As ever, note that AngelScript's scope is all but exclusively the JJ2 copy on your computer, even if you're in an online server, and jjAddObject and the various jjOBJ properties are no exception to this. If one player fires a slice of AngelScript that creates a morph monitor, the other players will not see any morph monitor until and unless they too fire the same slice of code. Likewise, changing the xPos and yPos of a pinball bumper will only be recognized by the local players, not by anyone else in the server. These details are of course of no concern for single player levels, but should be kept in mind for multiplayer design.

Certain of these properties—curFrame in particular—will constantly be set by the object itself, so trying to change them manually will have little effect unless you also redefine their behavior. Others, such as state or lightType, will have immediate and possibly enduring effects. There are also a significant number of properties whose function (if any) varies from object (i.e. eventID) to object; these should be better explained in future iterations of JJ2+, but you're welcome to experiment in the meantime.

Finally: there are also nearly 256 proto-objects stored in the global array jjOBJ objectPresets[256]. These do not correspond to objects currently active in the game, but are instead the value-collection prototypes from which all in-game objects are initially derived. Whenever jjAddObject is called using, say, OBJECT::GREENGEM, the created object will get its initial values for points, curAnim, var[0], and more from jjObjectPresets[OBJECT::GREENGEM]. The contents of jjObjectPresets are reset every level, so it is totally safe for you to decide that, e.g., you'd really rather if all Skeleton enemies were lightning-fast and took nineteen hits to destroy instead of three (jjObjectPresets[OBJECT::SKELETON].energy = 19; jjObjectPresets[OBJECT::SKELETON].xSpeed = 5;).

int age
A variable totally unused by JJ2. The name comes from its original intended purpose as indicating how long had elapsed since the object was created.
int animSpeed
How fast the object animates. Not always used for this purpose (if... ever?), since animations have their own internal framerate values.
BULLETS: This property stores the amount of damage a bullet does to enemies, e.g. 1 for normal blaster, 2 for normal seekers, or 3 for certain shield bullets.
jjBEHAVIOR behavior
Which function is called for this object's behavior. See the Custom Objects section.
HANDLING::Bullet bulletHandling
What happens when a bullet (or turtle shell, or TNT blast, or attacking bird) comes into contact with this object, assuming that playerHandling is either ENEMY or SPECIAL and state is anything other than KILL?
  • HURTBYBULLET: If this object has a non-zero energy property, then hitting it will decrease its energy by the force of the bullet. If the object is frozen, it will be unfrozen. If the object's energy sinks to 0 or less, its state will change to STATE::KILL and the player behind the bullet will receive the object's points. Otherwise, its justHit property will be set to 5. The bullet will be destroyed unless it has bit 16 set for its var[6], like fireball bullets do, or if the object has causesRicochet as true. If the object's playerHandling is SPECIAL, other things may happen.
  • IGNOREBULLET: The object will be unaffected in every way by the collision, and the bullet will not be destroyed.
  • DESTROYBULLET: The object will be unaffected in every way by the collision, and the bullet will be destroyed no matter what its properties are.
  • DETECTBULLET: The object is by default unaffected, but the bullet will be destroyed unless it has bit 16 set for its var[6], like fireball bullets do, or if the object has causesRicochet as true. If the object's playerHandling is SPECIAL, other things may happen.
bool causesRicochet
When true, colliding bullets will ricochet off of this object unless bulletHandling is set to DESTROYBULLET.
int counter
A general purpose property, usually used for counting up or down to some future event.
uint8 counterEnd
A general purpose property, usually used for counting up or down to some future event. Only goes up to 255, so not as versatile as counter.
BULLETS: how long a bullet will exist before exploding. Used by pretty much every bullet behavior but BEHAVIOR::BOLLYBULLET, although no behavior-external code seems to reference it specifically.
int creator
The sum of the creatorID and creatorType properties, which is less useful than you might hope.
int creatorID
The object ID or player ID of the object or player that created this object, as usable as the index for jjObjects or jjPlayers. 0 if no creator is actually known.
CREATOR::Type creatorType
CREATOR::OBJECT if the object was created by another object, CREATOR::PLAYER if it was created by a player, or CREATOR::LEVEL if it was added directly from the event map.
If the object was created by a Generator object specifically, then creatorType will equal CREATOR::LEVEL, but creatorID will equal the object ID of that Generator object rather than 0.
int16 curAnim
The current animation the object takes its frames from. While it is possible to set this property manually, in practice you should usually use the determineCurAnim method instead.
uint curFrame
The overall current frame displayed to the screen to represent this object, taking into account both curAnim and frameID. While it is possible to set this property manually, in practice you should use the determineCurFrame method instead.
bool deactivates
When true, this object will be deleted from memory if the player wanders too far away from it in local single player mode, and all its properties will be reset next time it gets loaded. This property has absolutely no effect in multiplayer, and setting it to false cannot force an object to remain in memory in single player when the player dies.
int8 direction
Which way the object is facing. Generally, direction >= 0 is right and < 0 is left.
uint8 doesHurt
A variable totally unused by JJ2. The name comes from its original intended purpose as specifying whether boss objects could hurt the player.
int8 energy
If this object's playerHandling value is HANDLING::ENEMY, this number is how many more hits the object can take before it is destroyed, unless it equals 0, in which case the object is invincible to bullets (but not to special attacks). This property is also used to determine the fullness of the boss bar.
uint8 eventID
e.g. 158 for a peach, 43 for a bomb, 243 for an airboard, 1 for a blaster bullet, and so on. Is a uint8 for maximum flexibility, but you should probably set/compare it to OBJECT::Object constants instead most of the time, if for no other reason than readability.
While this value is not strictly constant/read-only, changing it can lead to unpredictable and undesirable effects, since this is the only truly reliable way of knowing what kind of object a given jjOBJ really is, and JJ2 queries it very frequently. The effects can vary from a food pickup playing the wrong sound effect when collected to, say, a red spring simply not working at all when touched. You have been warned.
int8 frameID
The object's current frame within a single animation set, e.g. which direction the Tube Turtle faces while it rotates in place.
uint8 freeze
0 if the object is unfrozen, otherwise counts down to 0.
const bool isActive
Does this jjOBJ correspond to a real object, or is it just the abandoned memory of one? For any jjOBJ o, (o.isActive) is the same as (o.behavior != BEHAVIOR::INACTIVE), but one is obviously much shorter than the other.
bool isBlastable
When true, nearby TNT explosions will set this object's xSpeed and ySpeed properties.
bool isFreezable
When false, this object will treat ice bullets just the same as any other bullet with the same properties. This is what Caterpillar and Queen do, for example.
bool isTarget
When true, this object will be attacked by birds and seeker missiles.
uint8 justHit
When this property has a non-zero value, most objects will be drawn as pure white until the property counts back down to 0 one tick at a time. JJ2 deincrements this property for all objects, so you needn't worry about it when defining your own behavior functions.
int16 killAnim
Which animation the object uses while being destroyed.
int8 light
The intensity of the light produced by the object.
LIGHT::Type lightType
The type of light produced by the object. Possible values are NONE, NORMAL, POINT, POINT2, FLICKER, BRIGHT, LASER, RING, and RING2.
STATE::State oldState
If the object is frozen, what state it was in before it was frozen. Possible constants are listed in the appendix at the end of this file.
HANDLING::Player playerHandling
How does this object interact with the rest of the game, most specifically coming into contact with players, although also bullets?
  • ENEMY: If a player touches this object, they will be hurt (barring invincibility and such), unless they are using a special attack, in which case the object's energy property will decrease by 4. If energy reaches 0 or lower, the object's state will be set to KILL and the player will receive its points. Objects of playerHandling PLAYERBULLET that come into contact with this object have the potential to collide with it, depending on its bulletHandling setting.
  • PLAYERBULLET: When the object's state is anything but START or EXPLODE, this object will constantly be checked for collision with players other than its creatorID, as well as objects with playerHandling ENEMY, PICKUP, or SPECIAL.
  • ENEMYBULLET: When the object's state is anything but START or EXPLODE, if a player touches this object, they will be hurt (barring invincibility and such).
  • PARTICLE: Effect unknown (semantic only?)
  • EXPLOSION: Effect unknown (semantic only?)
  • PICKUP: If a player touches this object, something special will happen, exactly what depending on its eventID value. Usually if nothing else the player will receive its points, and its behavior will be set to BEHAVIOR::EXPLOSION2, though sometimes (e.g. carrots when touched by a player with full health) nothing will happen at all. If the object has scriptedCollisions set to true, that will override the effect of the eventID value. Should it have 0 xSpeed/ySpeed properties and be overlapped by an object of playerHandling PLAYERBULLET whose state is not START or EXPLODE, the pickup object will partially inherit that bullet object's speed and direction.
  • DELAYEDPICKUP: Effect unknown (semantic only?)
  • HURT: Effect unknown (semantic only?)
  • SPECIAL: Whenever this object is overlapped by a player or an object of playerHandling PLAYERBULLET (depending in the latter case on this object's bulletHandling value), something special will happen, exactly what depending on its eventID value. If the object has scriptedCollisions set to true, that will override the effect of the eventID value.
  • DYING: Effect unknown (semantic only?)
  • SPECIALDONE: Effect unknown (semantic only?)
  • SELFCOLLISION: Effect unknown (semantic only?)
uint16 points
How many points a player will gain for destroying the object.
int8 noHit
Instead, use the properties bulletHandling, causesRicochet, isFreezable, and isBlastable.
const uint16 objectID
For all values of n such that n < jjMaxObjects, jjObjects[n].objectID == n.
uint8 objType
Instead, use the properties playerHandling, isTarget, triggersTNT, deactivates, and scriptedCollisions.
bool scriptedCollisions
When true, JJ2 will call the onObjectHit hook function to determine what to do if a bullet or player is detected as having collided with this object. See the Custom Objects section. This property's effect if playerHandling is anything other than ENEMY, PICKUP, or SPECIAL is presently undefined.
int special
A general-purpose variable, means different things for different objects.
BULLETS: This property stores the animation (curAnim-style) that is used if the bullet is shot upwards, not horizontally. If this property equals 0, it will not be possible to shoot the bullet upwards except by mouse aiming; this is how shields work.
STATE::State state
The current state of the state machine that is the object. Possible constants are listed in the appendix at the end of this file.
bool triggersTNT
When true, TNT will explode if this object is nearby.
int var[11]
A series of general-purpose variables, used for different things by different objects. In general, earlier values are more likely to be used than later ones.
BULLETS:
  • var[3] represents what ammo type the bullet is, 1-9, which is primarily used for destroying destruct scenery with a non-zero "Weapon" parameter.
  • var[6] is used as a series of boolean flags that specify how the bullet interacts with various objects: bit 2 for fire-based bullets which can melt springs and burn enemies into fire/smoke particles; bit 4 for the laser shield's laser; bit 8 for bullets that do two damage in multiplayer; and bit 16 for bullets that pass through enemies, like the fireball, rather than explode on impact.
  • var[7] is the xSpeed that the player who fired this bullet was moving at when the bullet was fired, reduced to a range of -8–8... multiplied by 65536. Sorry about that.
  • var[9] is a counter for how many times the bullet has ricocheted in its lifetime, beginning at 0.
  • var[10] is a counter for how long it's been since the bullet has last ricocheted; each ricochet resets it to 0, but traditional bullet behaviors constantly increment it, and a bullet cannot ricochet if the value is less than 8.
float xAcc
Horizontal acceleration in pixels, positive or negative.
float xOrg
Original horizontal location in pixels.
float xPos
Current horizontal location in pixels.
float xSpeed
Horizontal speed in pixels, positive or negative.
float yAcc
Vertical acceleration in pixels, positive or negative.
float yPos
Current vertical location in pixels.
float yOrg
Original vertical location in pixels.
float ySpeed
Vertical speed in pixels, positive or negative.
void behave(jjBEHAVIOR behavior = BEHAVIOR::DEFAULT, bool draw = true)
Causes this jjOBJ to perform the specified jjBEHAVIOR function, or its own behavior property if behavior is set to BEHAVIOR::DEFAULT. If draw is false, it will not draw anything to the screen. See the Custom Objects section.
int beSolid()
Causes this jjOBJ to serve as a solid block for players trying to move into it. Used by crates, monitors, etc. Returns -1 if a player is trying to push the object to the left, 1 if a player is trying to push it to the right, or otherwise 0, in case you wish to write some code to make the object pushable.
void clearPlatform()
Causes all players that are currently standing on top of this object to no longer be standing on top of it. Should be called when deleting an object that calls beSolid.
void deactivate()
A wrapper method, called by most objects when their state property equals DEACTIVATE:
obj.delete();
if(obj.creatorType == CREATOR::LEVEL) {
  jjEventSet(obj.xOrg/32, obj.yOrg/32, obj.eventID);
  jjParameterSet(obj.xOrg/32, obj.yOrg/32, -1, 1, 0);
}
void delete()
Permanently deletes the object. Like jjAddObject, this method is purely local in its scope.
int16 determineCurAnim(uint8 setID, uint8 animation, bool change = true)
int16 determineCurAnim(ANIM::Set setID, uint8 animation, bool change = true)
Determines the value of the curAnim corresponding to Set ID setID and Animation animation as seen in Jazz Sprite Dynamite. (0-indexed.) If change is specified as false, this serves as essentially a static method, calculating the proper curAnim value but not actually setting this particular jjOBJ's curAnim to that value.
You are allowed to use a simple uint8 to specify the setID, but an ANIM::Set constant is strongly recommended, since the values for certain sets differ between 1.23 and 1.24. The full list of constants can be found in the appendix at the bottom of this file.
uint determineCurFrame(bool change = true)
Determines the value of the curFrame corresponding to this jjOBJ's current curAnim and frameID values. If change is specified as false, this calculates the proper value but does not actually set this particular jjOBJ's curFrame property to that value.
int draw()
Essentially a wrapper for jjDrawSpriteFromCurFrame; uses the jjOBJ's xPos, yPos, direction, freeze, justHit, and curFrame properties to determine where to draw the sprite and what mode to use. This isn't specific enough for all objects, but it does the job in a high percentage of cases.
int findNearestPlayer(int maxDistance) const
int findNearestPlayer(int maxDistance, int &out foundDistance) const
Returns the playerID property of the nearest jjPLAYER object within maxDistance, or a negative number if none exist. If the foundDistance parameter is included, it will be set to the distance of the found jjPLAYER. Used by numerous enemies and other objects in order to react to nearby players.
int fireBullet(OBJECT::Object eventID) const
A much-simplified version of jjAddObject. This method creates a new object of type eventID, directly at the "gunspot" position of the jjOBJ's current curFrame sprite, and sets its direction, xSpeed, and xAcc based on the jjOBJ's direction. The return value is the object ID of the new bullet object, or 0 if the method was unsuccessful. Used by dragons, hatters, Bilsy, and so on.
void grantPickup(jjPLAYER@ player, int frequency) const
Potentially creates a random pickup (red gem, green gem, blue gem, or carrot) in front of the jjOBJ, the likelihood depending on frequency (higher values are less likely). Traditionally, this method is called when an enemy or crate is destroyed by a bullet, and frequency equals 5 if the bullet was an unpowered-up blaster bullet or otherwise 10.
The player parameter is necessary because one in every eight pickups a player is granted is a fastfire instead, so JJ2 needs to be able to keep track of when each individual player should next receive a fastfire.
Only works if creatorType equals CREATOR::LEVEL.
void particlePixelExplosion(int style) const
A fast wrapper for jjAddParticlePixelExplosion, using the jjOBJ's own xPos, yPos, direction, and curFrame properties.
void pathMovement()
Makes the object use the same waypoint-based path movement as the Butterfly and Rocket Turtle objects. The method potentially sets the xAcc, yAcc, xPos, yPos, xSpeed, ySpeed, counter, direction, var[6] and var[7] properties in the process.
void putOnGround(bool precise = false)
These methods instantly move the object downward until it's on top of the nearest available masked tile below it, or else the bottom of the level. If precise is left false, the resulting yPos may be off by as much as three pixels either up or down, which is still fine for most objects.
bool ricochet()
To be used on bullet objects. Reverses the bullet's xSpeed/xAcc/direction, gives it a randomized ySpeed, plays one of the SOUND::AMMO_BUL* samples, and calls jjAddParticle(PARTICLE::SPARK) several times. Returns false if the bullet last ricocheted too recently.
int unfreeze(int style)
Sets freeze to 0, plays SOUND::COMMON_ICECRUSH, and creates an explosion of ice fragments radiating outward from the object. Unique values for style are 0, 1, or any other number.

class jjWEAPON

This is a fairly simple class, containing one array and several properties allowing for more control over weapons and their ammunition. The class exists solely in the jjWeapons[9] array.

bool comesFromGunCrates
Determines whether or not the ammo for the specified weapon can drop from Gun Crates and Gun Barrels. Defaults to true for WEAPON::BOUNCER, WEAPON::ICE, WEAPON::SEEKER, WEAPON::RF, WEAPON::TOASTER, and false for WEAPON::TNT, WEAPON::GUN8 and WEAPON::GUN9. Has no effect on WEAPON::BLASTER. In an online server, the pickups dropped will be according to the comesFromGunCrates settings for the player who destroyed the crate/barrel.
bool infinite
When true, ammo displays an infinity symbol for its quantity and can never be deplenished. Defaults to true for WEAPON::BLASTER. When spectating another player, the ammo count will appear according to your infinite setting for their currently chosen weapon, not their own.
int maximum
Determines how much of each ammo type a player can hold at a time. Defaults to -1, which is interpreted as "99 in single player or cooperative, otherwise 50," but you may want to change some numbers individually (e.g. limit the number of seekers but not bouncers).
int multiplier
The factor by which ammo pickups/powerups increase a weapon's ammo count, and by which that count is divided to be displayed onscreen. Defaults to 32 for WEAPON::TOASTER and 1 for everything else. When spectating another player, the ammo count will appear according to your multiplier setting for their currently chosen weapon, not their own.
bool replacedByBubbles
When true, the bullet will be replaced by an air bubble when shot underwater. Defaults to true for WEAPON::TOASTER.
bool replenishes
When true, ammo jumps back up to 50 or 99 on level (re)load. Should be set in onLevelLoad, rather than onLevelBegin, since it actually takes effect between the two. Defaults to true for WEAPON::BLASTER.
WEAPON::Style style
Determines how often will the weapon fire a bullet when the player holds the fire button. Possible values are WEAPON::NORMAL (fires continously, respecting the player's fastfire property), WEAPON::MISSILE (fires once per press of the button), and WEAPON::POPCORN (fires continously and respects the player's fastfire property, but at a capped minimum rate of fire).

class jjPARTICLE

JJ2 doesn't use the jjObjects jjOBJ list for everything: in particular, a number of visual effects are much too simple to require that many values, let alone all the specialized handling for light types, special states, multiplayer packets, and so on. For these, JJ2 employs a series of particle objects whose function is merely to make the screen look prettier without actually interacting with any other object, and disappear upon going offscreen (if not before that). These particles are in fact so very simple that AngelScript is able to give you absolute control over their movements and population.

Like full objects, particles are listed in a global array, jjParticles[1024]. Since particles are totally capable of taking care of themselves once they've been created, though, this is of only so much use. Most of your time will probably be spent around the jjAddParticle function, which takes a sole PARTICLE::Type parameter and returns a newly created jjPARTICLE, or a null pointer if the function was unsuccessful. Sample code:

jjPARTICLE@ particle = jjAddParticle(PARTICLE::SNOW);
if (particle !is null) {
	particle.xPos = p.xPos;
	particle.yPos = p.yPos + 12;
	particle.xSpeed = -1.66;
	particle.snow.frame = jjRandom()&7;
}

Note the peculiar particle.snow.frame property. Each particle type has its own unique property or properties in addition to the common properties, which will be listed at the end of this section. Since all particles are affected by gravity, if nothing else, setting the xSpeed/ySpeed properties is only so important, but xPos and yPos are of course essential.

bool isActive
The point of this property is somewhat unclear, since JJ2 prefers to check if a given particle is active by testing whether type equals 0 or not, but it does exist and does get set sometimes.
PARTICLE::Type type
Any of the standard type options allowed by jjAddParticle: FIRE, ICETRAIL, PIXEL, SMOKE, SNOW, SPARK, STRING, or TILE.
float xPos
Horizontal location in pixels.
float xSpeed
Horizontal speed in pixels, positive or negative.
float yPos
Vertical location in pixels.
float ySpeed
Vertical speed in pixels, positive or negative.
fire
Corresponds to PARTICLE::FIRE. Fire particles are drawn as small horizontal ovals, and unfortunately ignore their xSpeed values. Before they disappear, they may at any time create a smoke particle. Fire particles are usually created when an enemy or other destructible object is destroyed using a fire-based weapon.
uint8 fire.color is the color (palette entry) which a fire particle will be drawn as. This property gradually increases as the particle remains active, until it reaches 45 (dark orange), at which point the particle will disappear. The default value is 40 (yellow).
uint8 fire.size decides how large the oval will be, ranging from 0-3. The default value is 3.
icetrail
Corresponds to PARTICLE::ICETRAIL. Ice trail particles are drawn as single pixels, and unfortunately ignore their xSpeed values. In regular JJ2, ice bullets leave them in their wake as they fly.
uint8 icetrail.color is the color (palette entry) which an ice trail particle will be drawn as. This property gradually increases as the particle remains active, until it reaches 40 (yellow), at which point the particle will disappear. The default value is 32 (light blue).
pixel
Corresponds to PARTICLE::PIXEL. Depending on their size value, pixel particles will be drawn as 1x1, 2x2, or 3x3 rectangles of pixels. They move both horizontally and vertically, and will also bounce off of masks. These are the particles created by destroying most enemies.
uint8 pixel.color[9] specifies which colors will be drawn at each pixel in the particle's rectangle. Note that if the rectangle is smaller than 3x3, not every number in the array will be used. Values of 0 represent transparent pixels and will therefore not be drawn.
uint8 pixel.size specifies the size of the rectangle drawn to the screen. 0 for 1x1, 1 for 2x2, or any other number for 3x3. The default value is 0.
smoke
Corresponds to PARTICLE::SMOKE. Smoke particles are drawn as small gray rectangles, and ignore their xSpeed and ySpeed values both, instead always moving erratically upwards. Traditionally they are created from fire particles or from the BEHAVIOR::BURNING objects created by powered-up toaster or a frozen Bily.
uint8 smoke.countdown gradually decreases until it reaches 64, at which point the particle will disappear. The default value is 71.
snow
Corresponds to PARTICLE::SNOW. Snow particles are drawn as frames of the ANIM::SNOW animation set, and fly slowly around based on their speed properties and a general wind force until they hit a masked tile and slowly fade away. They are, of course, traditionally created by the Snow event. Note that you will need to load ANIM::SNOW manually in order for snow particles to display properly.
uint8 snow.countdown specifies how long (in ticks) the particle may pass through masked tiles after first being created before masked tiles cause it to fade away and disappear. The default value is 35.
uint8 snow.countup has something unknown to do with the particle disappearing once it hits a masked tile. The default value is 0.
uint8 snow.frame specifies which frame of the animation will be drawn. This remains constant until the particle hits a wall, at which point it will increase to 7 before disappearing. The default value is 0.
spark
Corresponds to PARTICLE::SPARK. Spark particles move around according to their xSpeed/ySpeed properties and also gravity, and are drawn as short trails left behind as they move. They are traditionally created by bullets ricocheting off of turtle shells or metallic surfaces, or by electro-blaster bullets in flight.
uint8 spark.color is the color (palette entry) which a spark particle's trail will be drawn as. This property gradually increases as the particle remains active, until it reaches 45 (dark orange), at which point the particle will disappear. The default value is 40 (yellow).
string
Corresponds to PARTICLE::STRING. String particles move at ever-increasing speeds until they leave the screen, drawing up to eight consecutive characters as they go. They are traditionally used to show many points a player gained for destroying an object.
string string.text is the series of characters that will be drawn. Strings longer than eight characters will be truncated.
tile
Corresponds to PARTICLE::TILE. Tile particles move around according to their xSpeed/ySpeed properties and also gravity, and are drawn as single tiles (or quarters of single tiles) from the tileset used by the level. They are traditionally created from the destruction of destruct or collapse scenery blocks.
TILE::Quadrant tile.quadrant specifies how much of the tile will be drawn to the screen. If 4, the entire tile. Possible values of TILE::Quadrant are TOPLEFT, TOPRIGHT, BOTTOMLEFT, BOTTOMRIGHT, and (default) ALLQUADRANTS. Note that in the current release, if only a single quadrant is drawn, it will be drawn as though it had tile type NORMAL (not translucent or anything else) and vertical flipping will not be understood. This is a known issue and may change in some future revision.
uint tile.tileID specifies which tile the particle draws in the first place. The default value is 0, so remember to change it.

class jjCANVAS

The jjOBJ.draw() method is okay, but sometimes you just need to draw things to the screen that aren't jjOBJs. The upper right corner of the player's screen shows how much health they have, for example... how can you get in on that action? The answer is the jjCANVAS class, which presents the JJ2 window as a canvas for you to draw stuff on.

Probably the most straightforward use of jjCANVAS is for drawing your own HUD elements, for which the following hooks are presently available. Note that these hooks have bool return values: if you return true, JJ2 will not draw the standard visuals for that subject. So if you want to show a player's health in the upper left instead of the upper right, for example, not only will you need to call jjCANVAS methods to draw the health where you want it to go, you'll also need to have onDrawHealth return true.

bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {}
bool onDrawHealth(jjPLAYER@ player, jjCANVAS@ canvas) {}
bool onDrawLives(jjPLAYER@ player, jjCANVAS@ canvas) {}
bool onDrawPlayerTimer(jjPLAYER@ player, jjCANVAS@ canvas) {}
bool onDrawScore(jjPLAYER@ player, jjCANVAS@ canvas) {}

Each of the above hooks presents you with an entire player screen to draw HUD-style elements upon. If you're curious, the origin of the screen may be determined through the subscreenX and subscreenY properties of the player, and its size by the jjSubscreenWidth and jjSubscreenHeight global properties. Any (portion of any) graphic to be drawn outside of that rectangle will simply be ignored, so don't worry too much about fitting into the boundaries.

There are additionally eight hooks which draw to the level instead of the HUD, as determined by the positions of each of the eight layers, in the format void onDrawLayer#(jjPLAYER@ player, jjCANVAS@ canvas) where # is any number from 1 to 8. These hooks are only called for those layers for which jjLayerHasTiles[] is true, and let you draw tiles not fixed to the 32x32 grid, text in front of sign tiles, or whatever else you may please. Just note that jjCANVAS's drawing methods are just that—drawing methods—and do not actually add tiles or enemies or anything to the level to be interacted with. Additionally, the layer hooks don't care if a layer has 'Tile Width' or 'Tile Height' checked, and will instead only perform each drawing operation once per player subscreen.

int drawSprite(int xPixel, int yPixel, uint8 setID, uint8 animation, uint8 frame, int direction = 0, SPRITE::MODE mode = SPRITE::NORMAL, int param = 0)
Draws a single sprite frame at xPixel,yPixel. The setID, animation, and frame parameters correspond to the "Set ID," "Animation," and "Frame #" sliders respectively in Jazz Sprite Dynamite, but are 0-indexed. You are allowed to use a simple uint8 to specify the setID, but an ANIM::Set constant is strongly recommended, since the values for certain sets differ between 1.23 and 1.24. The full list of constants can be found in the appendix at the bottom of this file.
The following options are available for the mode parameter:
  • NORMAL: Simply draws a sprite to the screen, pointing left if direction < 0, or right if >= 0.
  • TRANSLUCENT: The same as NORMAL, but the sprite will be semi-translucent.
  • TINTED: The same as NORMAL, but the sprite will be tinted to the color of palette entry param.
  • GEM: Draws a sprite frame with colors adjusted as if the sprite is a gem sprite. Naturally this works best with sprites that actually are gem sprites. The value of param determines the color: 0 for red, 1 for green, 2 for blue, and 3 for purple, with other values having other (possibly palette-determined) effects. 28 is perhaps the best value for black gems.
  • SINGLECOLOR: Same as NORMAL, but every non-transparent pixel of the frame will be the color of palette entry param. Usually JJ2 uses this to draw injured enemies as all-white (15).
  • RESIZED: Draws a resized sprite, with param deciding the size: 32 for the sprite's normal size, 64 for twice the normal size, and so on. Does not respect the direction parameter; in case this changes in a future revision, leave direction a positive number for backwards compatability.
  • NEONGLOW: Draws the sprite as a sort of neon glow, the color of which is determined by the value of param: 0 for red, 1 for green, or 2 or any other number for blue. Only works in 16-bit color.
  • FROZEN: Draws a frozen sprite.
  • PLAYER: Draws a sprite with its colors adjusted to match the fur colors of player param. Mostly used for drawing actual Jazz/Spaz/Lori sprites, of course, but you're welcome to experiment.
  • PALSHIFT: Draws a sprite, but shifts each color in the sprite by param entries. For example, drawing a skeleton sprite with param=16 would result in a purple skeleton (assuming an ordinary palette). Does not respect the direction parameter; in case this changes in a future revision, leave direction a positive number for backwards compatability.
int drawSpriteFromCurFrame(int xPixel, int yPixel, uint sprite, int direction = 0, SPRITE::MODE mode = SPRITE::NORMAL, int param = 0)
The same as jjDrawSprite, but takes a single sprite parameter instead of one each for Set, Animation, and Frame. Get the value for sprite from a curFrame jjOBJ property.
int drawString(int xPixel, int yPixel, const string& text, STRING::Size size = STRING::SMALL, STRING::Mode mode = STRING::NORMAL, uint8 param = 0)
Draws a string of characters of your choice at some position on the screen. Unique size values are SMALL, MEDIUM, and LARGE. The following options are available for the mode parameter:
  • NORMAL: Draws a left-aligned string as if drawing chat or player names; | changes the text color after itself, and @ and # are ordinary symbols.
  • DARK: Draws a left-aligned string with darkened letters, as if unselectable. |, @, and # have no effect.
  • RIGHTALIGN: Draws a right-aligned string with normal letters. |, @, and # have no effect.
  • SPIN: Draws a left-aligned string whose letters spin around in place. | has no effect; @ introduces a newline; # makes every letter after it a new color. The param value determines how close together the letters are: 0 for a fairly ordinary looking string, or 255 for complete chaos, with other numbers falling in-between.
  • BOUNCE: Draws a left-aligned string whose letters bounce up and down in place. |, @, and # have no effect. The param value determines how close together the letters are: 0 for a fairly ordinary looking string, or 255 for complete chaos, with other numbers falling in-between.
  • PALSHIFT: Draws a left-aligned string with normal letters whose colors are all shifted param palette entries. A value of 24, for instance, would produce purple text, or 32 would enter into the non-sprite-color portion of the palette. |, @, and # have no effect.
int drawTile(int xPixel, int yPixel, uint16 tile, TILE::Quadrant tileQuadrant = TILE::ALLQUADRANTS)
Draws a tile of your choice at some position on the screen. This is purely a drawing operation; using it to draw masked tiles to the screen does not create masked areas for players or other objects to interact with. Possible values of tileQuadrant are TOPLEFT, TOPRIGHT, BOTTOMLEFT, BOTTOMRIGHT, and (default) ALLQUADRANTS. Note that in the current release, if only a single quadrant is drawn, it will be drawn as though it had tile type NORMAL (not translucent or anything else) and vertical flipping will not be understood. This is a known issue and may change in some future revision.

Global Properties

Note: to be convenient, the arrays of layer properties are all 1-indexed. For example, the X speed of layer 4 (the sprite layer) is jjLayerXSpeed[4] and not jjLayerXSpeed[3].

const jjPAL jjBackupPalette
The tileset's original palette. See the jjPAL documentation above for further details.
const bool jjDeactivatingBecauseOfDeath
When the player dies in Single Player mode, this property is set to true before all jjOBJs have their state property set to DEACTIVATE. Since DEACTIVATE is also used for when an object goes too far off-screen, this property is how to discover the reason for the state change. In practice, is probably only ever consulted by destruct scenery and trigger scenery.
bool jjDelayGeneratedCrateOrigins
If set to true, crates (trigger crates and all wooden crates) spawned from Generator objets will derive their parameters from the tile they begin at, not the tile they are created at. If the Generator object is in the air, the crate will appear on top of the nearest solid tile below the Generator, and will get its parameters from the tile there.
int jjDifficulty
The current difficulty level. 1 for Normal difficulty; 0 and below for Easy; 2 for Hard; 3 and above for Turbo. Numerous enemies base their speeds at least partially on the difficulty, so numbers outside of the well-tested 0-3 range may have unexpected or undesirous effects with certain enemies; still, it's worth a try! This property cannot be used to determine whether to load events specified in JCS as Easy or Hard, since that has already been checked by the time AngelScript starts running in a level.
int jjEcho
The current degree of echo, as set by the "Echo" event.
bool jjEnabledASFunctions[256]
Usually all true. When a Text event is touched with AngelScript=1,Vanish=1, the jjEnabledASFunctions[#] bool for that Text event's TextID value will be set to false and the corresponding onFunction# will be uncallable by other Text events until the bool is set to true again.
uint8 jjEventAtLastMaskedPixel
Whenever one of the mask-detection functions, e.g. jjMaskedHLine, finds a masked pixel in layer 4, this property will be set to the event at the tile containing that pixel. This allows you to write code for object like seeker missiles, which ignore masked pixels on tiles with the AREA::ONEWAY, AREA::HOOK, or AREA::VINE events. There's not much reason to edit it manually, since JJ2 changes its value all but constantly, but you can if you want.
const int jjFPS
The current frames per second rate, as viewable by pressing F9 twice.
const GAME::Connection jjGameConnection
Is this game joinable by players from other computers, and if so, must they be connected to the same network or just the internet? Options are LOCAL, ONLINE, and LAN.
const GAME::Custom jjGameCustom
If using a custom gamemode, what is it? Options are NOCUSTOM, RT, LRS, XLRS, PEST, TB, JB, DCTF, FR, TLRS, and DOM.
const GAME::Mode jjGameMode
What is the current base gamemode, irrespective of whether there is a custom gamemode or not? Options are SP, COOP, BATTLE, CTF, TREASURE, and RACE.
const int jjGameTicks
How long the game has been actively running, at a rate of 70 ticks per second.
const GAME::State jjGameState
In an online/network server, is the game started, stopped, or some other variation? Options are. STOPPED, STARTED, PAUSED (only possible if there is a time limit), PREGAME, and OVERTIME.
const bool jjIsAdmin
Whether the current game executable is logged in as a Remote Admin in the current online server.
const bool jjIsServer
Whether the current game executable is hosting an online server.
const bool jjIsTSF
Whether the current game executable is 1.23+ or 1.24+. Useful for Lori, XMas enemies, etc.
const bool jjKey[256]
Whether any given key on the keyboard is currently pressed, assuming JJ2 is able to check it, including the left and right mouse buttons. Uses virtual key codes for indexation. Note that jjKey[1] and jjKey[2] refer to the primary and secondary mouse buttons respectively, rather than left and right.
bool jjLayerHasTiles[8]
Simply, whether JJ2 should draw the layer or not. Setting this to true for a layer that does not actually have any tiles may have unpredictable consequences; it is best to use this solely to hide layers that really do have tiles.
const int jjLayerHeight[8]
The height of each layer, in tiles.
bool jjLayerLimitVisibleRegion[8]
Assuming the layer is not vertically tiled, whether the layer should be vertically offset some pixels downward, the exact value depending on the current resolution.
bool jjLayerTileHeight[8]
Whether each layer should be vertically tiled, as seen in the JCS Layer Properties window.
bool jjLayerTileWidth[8]
Whether each layer should be horizontally tiled, as seen in the JCS Layer Properties window.
Setting this to true for a layer that is not saved with Tile Width checked in JCS may lead to unpredictable effects if the layer's width is not a multiple of 4. If you wish to turn it on partway through the level, it is best to check Tile Width in JCS and then disable it in onLevelLoad.
const int jjLayerWidth[8]
The width of each layer, in tiles.
const int jjLayerWidthReal[8]
If a layer has Tile Width checked, the lowest common multiple of its width in tiles and 4. Otherwise, its width in tiles.
const int jjLayerWidthRounded[8]
The lowest multiple of four greater or equal to each layer's width in tiles.
float jjLayerXAutoSpeed[8]
The X auto speed of each layer, as seen in the JCS Layer Properties window.
float jjLayerXOffset[8]
Constant pixel values added to the X position of each layer, regardless of their speeds.
float jjLayerXSpeed[8]
The X speed of each layer, as seen in the JCS Layer Properties window.
float jjLayerYAutoSpeed[8]
The Y auto speed of each layer, as seen in the JCS Layer Properties window.
float jjLayerYOffset[8]
Constant pixel values added to the Y position of each layer, regardless of their speeds.
float jjLayerYSpeed[8]
The Y speed of each layer, as seen in the JCS Layer Properties window.
const jjPLAYER jjLocalPlayers[4]
The local players.
const int jjLocalPlayerCount
The number of local players.
const int jjMaxHealth
The most health a player can ever have, as set by the /maxhealth command. Defaults to 5 in Single Player/Cooperative/Battle, or 3 in Capture The Flag.
const int jjMouseX
const int jjMouseY
The current position of the mouse cursor relative to the top left corner of the game window. To convert these coordinates to coordinates within layer 4, you'll need to use the jjPLAYER cameraX and cameraY properties.
bool jjMusicActive
Mute Music, as seen in the Sound & Music Properties window.
int jjMusicVolume
Music Volume, as seen in the Sound & Music Properties window.
const int jjObjectCount
When looping through jjObjects, this is the endpoint; there should never exist a jjOBJ with an object ID higher than jjObjectCount. It is not however the number of distinct jjOBJs in existence at any given time, since for instance jjObjects[1] and jjObjects[3] may both be active but jjObjects[2] inactive, but jjObjectCount would still equal 4.
const int jjObjectMax
The most jjOBJs that can ever exist at the same time. This equals 512 in Single Player, or 2560 otherwise.
jjOBJ jjObjectPresets[256]
The templates from which each object is built. Tends to contain default xSpeed, ySpeed, points, curAnim, and so on. Make changes here in onLevelLoad() for maximum efficiency.
jjOBJ jjObjects[jjObjectMax]
All the objects currently in memory.
jjPLAYER jjP
The current player.
jjPAL jjPalette
The current palette. See the jjPAL documentation above for further details.
jjPARTICLE jjParticles[1024]
All the particles currently in memory. See the jjPARTICLE documentation above for further details.
const int jjPlayerCount
The total number of players in the game. Potentially distinct from jjLocalPlayerCount when in online servers. Note that using this as the maximum value for a for loop may be impractical, since player numbers need not be consecutive.
const jjPLAYER jjPlayers[32]
All the players in the game, local or otherwise.
const int jjRenderFrame
How long the game has been running. Unlike jjGameTicks, jjRenderFrame updates when the game is paused. This is the value used for drawing layers with automatic x/y speeds.
const int jjResolutionHeight
const int jjResolutionWidth
The size of the current game window in pixels, usually 640 by 480.
const int jjResolutionMaxHeight
const int jjResolutionMaxWidth
The maximum size the game window is allowed to be in the current level/server.
const bool jjSoundEnabled
Whether JJ2 should produce any form of audio at all.
bool jjSoundFXActive
Mute Sound, as seen in the Sound & Music Properties window.
int jjSoundFXVolume
Sound Volume, as seen in the Sound & Music Properties window.
const int jjStartHealth
How much health a player starts with, as set by the /starthealth command. Defaults to 5 in Single Player/Cooperative/Battle, or 3 in Capture The Flag.
const int jjSubscreenHeight
const int jjSubscreenWidth
The size of a player's subscreen in pixels. If there is only one local player and the game is not being viewed in 3D, these will be equal to jjResolutionHeight and jjResolutionWidth -- otherwise, either or both may be cut in half.
float jjTexturedBGFadePositionX
float jjTexturedBGFadePositionY
Where on the screen, with 0 as top/left and 1 as bottom/right, the textured background should fade to, if applicable (only Warp Horizon and Tunnel use Y, and only Tunnel uses X).
TEXTURE::Style jjTexturedBGStyle
How the level's textured background should display itself. Options are WARPHORIZON, TUNNEL, MENU, and TILEMENU, defaulting to whichever is specified in layer 8's layer properties in JCS.
TEXTURE::Texture jjTexturedBGTexture
Which 256x256 pixel (aka 8x8 tile) texture is used by the level. Defaults to LAYER8, meaning whatever the first 64(=8*8) tiles in layer 8 are. (If layer 8 has fewer than 64 tiles, this may cause JJ2 to crash.) The other options are listed in the appendix at the bottom of this file.
bool jjTexturedBGStars
The layer 8 checkbox titled "Parallaxing stars background (Star Wars)" in JCS.
bool jjTexturedBGUsed
Whether this level displays a textured background in place of layer 8 at all.
uint8 jjTileType[4096]
Each tile's tile type: 0 for normal, 1 for translucent, 3 for invisible, and so on. Refer to your JCS.ini for the full list.
bool jjTriggers[32]
The triggers, as set by the Trigger Zone and Trigger Crate events.
bool jjVerticalSplitscreen
If there are exactly two local players, how the window is divided into their two subscreens.
bool jjWarpsTransmuteCoins
If set to false, using a coin warp in Single Player mode will not turn all remaining coins into red and green gems.
float jjWaterChangeSpeed
How fast water moves up or down when the water level is set (by event or function) with the "Instant" parameter set to false. Defaults to 1.
const float jjWaterLevel
How high the water currently is, in pixels.
This is a constant value; use the jjSetWaterLevel helper function instead for changing it.
WATERLIGHT::wl jjWaterLighting
The current way that water and ambient lighting interact in the level. (Ambient lighting varies by local player and as such is a jjPLAYER property.) The following constants are permissible values:
  • WATERLIGHT::NONE: The default. When water is activated, the level will display at lighting 100, regardless of the current settings.
  • WATERLIGHT::GLOBAL: The entire level will be lit according to the current ambient lighting settings, both above and below the water line.
  • WATERLIGHT::LAGUNICUS: The current ambient lighting setting is ignored. Above the water, the level will display at lighting 100. Below the water, the level will display darker and darker depending on how far below the water line the player is.
const float jjWaterTarget
The height the water is moving towards, in pixels. If the water level is set (by event or function) with the "Instant" parameter set to false, there will be a period in which jjWaterLevel and jjWaterTarget are two distinct values.
This is a constant value; use the jjSetWaterLevel helper function instead for changing it.
jjWEAPON jjWeapons[WEAPON::Weapon]
jjWEAPON jjWeapons[9]
Various properties of the nine different weapons available to a player; see the jjWEAPON section. Possible constants appear in the appendix below, or you may use simple 1-indexed numbers instead.
jjPLAYER p
The current player; an alias of jjP, and the only property not to begin with the jj prefix, provided solely for convenience value.

Global Functions

int jjAddObject(uint8 eventID, float xPixel, float yPixel, uint16 creatorID = 0, CREATOR::Type
creatorType = CREATOR::OBJECT, jjBEHAVIOR behavior = BEHAVIOR::DEFAULT)
Adds and initiates an object of type eventID at xOrg xPixel and yOrg yPixel. Possible values for creatorType are CREATOR::OBJECT, CREATOR::LEVEL, and CREATOR::PLAYER. Useful values for eventID can be found in the appendix at the bottom of the page. Returns the object ID of the new object, or 0 if the function fails for whatever reason.
The difference between jjAddObject(1, 0, 0, CREATOR::OBJECT, 0, BEHAVIOR::BOUNCERBULLET); and jjObjects[jjAddObject(1, 0, 0)].behavior = BEHAVIOR::BOUNCERBULLET; is that jjAddObject calls the object's behavior function as part of creating it. The first version will call BEHAVIOR::BOUNCERBULLET while the object's state is still STATE::START; the second version will call jjObjectPresets[1].behavior and only switch the object's behavior to BEHAVIOR::BOUNCERBULLET after it has already been initialized and its state likely changed to something else. The same distinction applies to setting the object's xOrg/yOrg, creatorType, and creatorID properties as parameters to the function or later on. See the Custom Objects section.
jjPARTICLE@ jjAddParticle(PARTICLE::Type type)
Creates and returns a new particle object, or a null pointer if unsuccessful. See the jjPARTICLE documentation above for full details.
bool jjAddParticlePixelExplosion(float xPixel, float yPixel, int curFrame, int direction, int mode)
Creates an explosion of particle objects based on the shape and possibly colors of the specified curFrame. Use a mode value of 0 for a normal explosion, 1 for an explosion caused by a toaster/electro-blaster/fire shield/laser shield bullet, or 2 for an explosion caused by a special move.
bool jjAddParticleTileExplosion(uint16 xTile, uint16 yTile, uint16 tile, bool collapseSceneryStyle)
Creates four fragments of a tile falling from a specified location, like when destroying a destructable scenery block. Does not produce a sound effect; use jjSample or jjSamplePriority for that instead. The fragments will continue to be drawn until they fall off the screen. If you want more control over the fragments' positions, speeds, etc., use jjAddParticle instead.
void jjAlert(string text)
Writes text to the chatlogger window, and also displays it ingame for the local player.
void jjChat(string text, bool teamchat = false)
In online play, sends text to the server as a line of chat. If text is a command (e.g. "/spectate on" or "/ready"), it will be interpreted as such to the extent that the local player is allowed to use that command in the server.
In offline play, JJ2+ will try to parse text as a command but will not display it as chat because there is no chat in offline mode. If you want to simulate chatting in a local game, use jjAlert instead.
float jjCos(uint in)
Returns the cosine of in with a range of 0.0-1.0 and a domain of 0-1023. Numbers outside the domain will be seemlessly moduloed. You may prefer AngelScript's native cos function.
void jjDebug(string text)
Writes text to the chatlogger window (but not ingame), but only if [General]AngelscriptDebug equals True in plus.ini.
void jjDeleteObject(int objectID)
Permanently deletes an object. Like jjAddObject, this function is purely local in its scope.
int jjDrawSprite(float xPixel, float yPixel, uint8 setID, uint8 animation, uint8 frame, int direction = 0, SPRITE::MODE mode = SPRITE::NORMAL, int param = 0, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
int jjDrawSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, int direction = 0, SPRITE::MODE mode = SPRITE::NORMAL, int param = 0, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
int jjDrawString(float xPixel, float yPixel, const string& text, uint8 STRING::SIZE size = STRING::SMALL, STRING::Mode mode = STRING::NORMAL, uint8 param = 0, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
int jjDrawTile(float xPixel, float yPixel, uint16 tile, TILE::Quadrant tileQuadrant = TILE::ALLQUADRANTS, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
Global function versions of the jjCANVAS methods as applied to onDrawLayer# hooks, differing in that the jjCANVAS methods are executed instantly, whereas these functions create instructions for JJ2 to perform the drawing operations later on, at the proper time. For example, a swinging platform will call jjDrawSpriteFromCurFrame many times over, once for each of its chain links, in the middle of its behavior function, but the links won't actually get drawn to the screen until later in the game cycle. Native JJ2 code uses this method for everything but HUD graphics.
The layerZ parameter specifies which layer, 1-8, the graphic should be drawn in front of as its Z-index. Unlike the jjCANVAS hooks, this can be used even for layers that don't have any tiles. JJ2 draws sprites exclusively(?) in front of layers 3, 4, and 5, but you're welcome to experiment.
The layerXY parameter specifies which layer, 1-8, the graphic should be positioned relative to the top left corner of. JJ2 always, always does layer 4, but you can vary it up a bit. Unfortunately the game cycle is ordered so that the layers besides layer 4 may actually move around a little after the instruction is registered but before the graphic is drawn, so these drawing instructions will always be one frame behind. Here the jjCANVAS methods have a clear advantage.
The playerID parameter specifies which player should see the drawn graphic, 0-31, or -1 for all of them (restricted only to players with true isLocal). Drawing for one player a time is used by JJ2+ to, for example, draw fastfire pickups as green/blue or normal/powered-up depending on the charCurr and powerup[1] values of each jjPLAYER viewing them. When spectating, sprites are drawn for the player ID of the spectator, not the spectatee.
void jjEnableEachASFunction()
Resets all 256 bools in jjEnabledASFunctions to true.
int jjEventGet(uint16 xTile, uint16 yTile)
Gets the Event ID at tile xTile,yTile, as seen in JCS.ini. This number can also be compared to the OBJECT or AREA constants listed in the appendix at the bottom of this file.
void jjEventSet(uint16 xTile, uint16 yTile, uint8 newEventID)
void jjEventSet(uint16 xTile, uint16 yTile, OBJECT::Object newEventID)
void jjEventSet(uint16 xTile, uint16 yTile, AREA::Area newEventID)
Sets the event at tile xTile,yTile to newEventID. Possible OBJECT or AREA constants are listed in the appendix at the bottom of this file.
Caution: this is a permanent change and will subsist even after death in offline play.
void jjKillObject(int objectID)
Permanently deletes an object, but first calls its native STATE::KILL code (if any). Probably functionally identical to jjDeleteObject in most cases, but might work a little better sometimes.
bool jjMaskedHLine(int xPixel, int lineLength, int yPixel, uint8 layer = 4)
Returns true if any pixel from xPixel,yPixel to xPixel+lineLength,yPixel is masked in layer layer. Leaving the layer parameter out should be faster than setting it to 4.
bool jjMaskedPixel(int xPixel, int yPixel, uint8 layer = 4)
Returns true if pixel xPixel,yPixel is masked in layer layer. Leaving the layer parameter out should be faster than setting it to 4.
int jjMaskedTopVLine(int xPixel, int yPixel, int lineLength, uint8 layer = 4)
If any pixel from xPixel,yPixel to xPixel,yPixel+lineLength is masked in layer layer, returns the height of the topmost masked pixel relative to yPixel. (For example, if xPixel,yPixel+2 is masked but +1 and +0 weren't, the function returns 2.) If none of the pixels are masked, returns lineLength+1. Used for detecting inclines and the like. Leaving the layer parameter out should be faster than setting it to 4.
bool jjMaskedVLine(int xPixel, int yPixel, int lineLength, uint8 layer = 4)
Returns true if any pixel from xPixel,yPixel to xPixel,yPixel+lineLength is masked in layer layer. Leaving the layer parameter out should be faster than setting it to 4.
bool jjMusicLoad(string filename, bool forceReload = false)
Loads and starts playing a new music file, of any type supported by JJ2+. Returns false if the file cannot be found in either the main game folder or the cache subfolder, or if the specified music file is already playing and forceReload is false.
void jjMusicPause()
Pauses the current music track. May not work with .mp3 files.
void jjMusicPlay()
(Re)starts the current music track.
void jjMusicResume()
Resumes the current music track, once paused. May not work with .mp3 files.
void jjMusicStop()
Stops the current music track.
void jjNxt(string& filename = "", bool warp = false, bool fast = false)
Ends the level and skips to the next one, or to filename if specified. Only works in Single Player and Cooperative.
int jjParameterGet(uint16 xTile, uint16 yTile, int8 offset, int8 length)
Gets one of the parameters at tile xTile,yTile. Follow JCS.ini's lead in figuring out how to write the offset and length parameters.
length is the simplest: use the exact same formatting JCS.ini does. To get the speed of a belt event, for instance, length should be -8. To get the number of blue gems in a gem crate, length should be 4. And so on.
offset is calculated by adding the absolute values of every parameter on the tile prior to the one you want. The first (bottommost) parameter will always have offset 0. To get the parameter "Blue" in Gem Crate, offset should be 8 (4+4). To get the Y-Speed of a Rotating Rock, offset should be 12 (8+abs(-4)). And so on.
Set length to 2 and offset to -4 to get the difficulty of an event (normal, easy, hard, multiplayer-only).
void jjParameterSet(uint16 xTile, uint16 yTile, int8 offset, int8 length, int newValue)
Sets one of the parameters at tile xTile,yTile. length and offset work exactly as they do for jjParameterGet; the only change is newValue, which should be a valid number for the length setting. Trying to assign a negative number to an unsigned length parameter doesn't really make sense, for example, nor can you reasonably assign a newValue of 100 to a length of 3.
Note that this is not quite as powerful as it may seem, since some objects read and process their parameters into memory when they are first created, rather than continually reading them again and again as the game continues. The function will however work fine for zones that affect the player, such as Warp or Sucker Tube or Wind, and it will also successfully set parameters for any such new objects created after the function is called.
Caution: this is a permanent change and will subsist even after death in offline play.
void jjPrint(string text)
Writes text to the chatlogger window but does not display it ingame.
uint jjRandom()
Provides a random number.
float jjResetWaterGradient()
Restores 16-bit water to its natural colors.
void jjSample(float xPixel, float yPixel, SOUND::Sample sample, int volume = 63, int frequency = 0)
Plays a sound from anims.j2a at pixel xPixel, yPixel. Possible values for sample are listed in the appendix at the bottom of this file.
volume ranges from 1-63, and 0 will default to 63. Higher values of frequency result in higher frequencies, or leaving it at 0 will use the sample's unique default frequency.
void jjSamplePriority(SOUND::Sample sample)
Plays a sound from anims.j2a, no matter what any local players' positions are. This is the function used to play the sugar rush jingle. Possible values for sample are listed in the appendix at the bottom of this file.
void jjSetFadeColors(uint8 red, uint8 green, uint8 blue)
void jjSetFadeColors(uint8 paletteColorID = 207)
Sets the fade colors of the level's textured background, as seen in the Layer properties window for layer 8 in JCS. Has no effect if there is no textured background.
A simpler one (or zero!) parameter version of the function also exists to set the fade colors to the same RGB values as used by one of the entries in jjPalette. This defaults to 207, which is the last color of the most common textured background gradient and thus, not infrequently, the fade color used in 8-bit color.
void jjSetLayerXSpeed(uint8 layerID, float newspeed, bool newSpeedIsAnAutoSpeed)
void jjSetLayerYSpeed(uint8 layerID, float newspeed, bool newSpeedIsAnAutoSpeed)
Changes the X or Y speed of a given layer. Unlike the basic arrays like jjLayerXSpeed and jjLayerYAutoSpeed, these functions will ensure that the layers remain in the same positions they were before their speeds were changed. Much more useful.
float jjSetWaterGradient(uint8 red1, uint8 green1, uint8 blue1, uint8 red2, uint8 green2, uint8 blue2)
float jjSetWaterGradient()
Changes the colors used by water in 16-bit color. If no parameters are included, the gradient will be generated from palette entries 176 and 207 instead, the most typical textured background colors (and most of the colors used by 8-bit water).
float jjSetWaterLevel(float yPixel, bool instant)
Sets jjWaterTarget to yPixel. If instant is true, jjWaterLevel will also be set to yPixel; otherwise, it will move slowly up or down from its current height until it reaches its new target.
Caution: this function is not identical to the Water Level event in JCS. The event measures in tiles, but this function measures in pixels. Multiply by thirty-two to get the same effect.
float jjSin(uint in)
Returns the sine of in with a range of 0.0-1.0 and a domain of 0-1023. Numbers outside the domain will be seemlessly moduloed. This is the sine function used by JJ2 for spinning platforms and the like, though you may prefer AngelScript's native sin function.
bool jjSwitchTrigger(uint8 id)
Toggles jjTriggers[id] from true to false, or vice versa, like the "switch" parameter on the Trigger Zone and Trigger Crate events.
uint16 jjTileGet(uint8 layer, int xTile, int yTile)
Returns the current tile at tile xTile,yTile in layer layer. If the tile is an animated tile, this function will return the tile ID for that animated tile instead of the current frame.
uint16 jjTileSet(uint8 layer, int xTile, int yTile, uint16 newTile)
Sets the current tile at tile xTile,yTile in layer layer to be newTile. The same change will be applied to all instances of the same four-tile word that appear elsewhere in the level.
void jjTriggerRock(uint8 id)
Activates all Rotating Rock events with the "RockID" parameter set to id, exactly like the Trigger Rock event.
void jjUpdateTexturedBG()
Forces JJ2+ to reconstruct the textured background from its relevant properties. This should be handled automatically now.

Appendix: Tile IDS

Tile IDs are stored in uint16 variables, and while it's perfectly all right for you to figure them out on your own, JJ2+ does provide you with a few helpful constants.

There are a maximum of 4095 unique tiles in any given level, and you can get any ordinary tile's ID in JCS. The "1*" destruct block in the Tubelectric tileset, for example, is at position 5,31 within the tileset. Since those numbers are 1-indexed, you need to subtract 1 from each of them. Then multiply the Y position by 10, add it to the X position, and you've got the tile ID: 304. One row below it, the "2*" block is tile ID 314, and so on. If you've gotten a tile ID from jjTileGet and want to know which basic tile it is, you can bitwise AND the number with the constant TILE::RAWRANGE (=0xFFF): if uint16 foo is a vertically-flipped version of tile 715, then foo & TILE::RAWRANGE will equal 715 exactly.

Horizontally and vertically flipped tiles are marked by the 0x1000 and 0x2000 bits respectively, or more helpfully, TILE::HFLIPPED and TILE::VFLIPPED. Thus to get the tile ID for the horizontally flipped version of tile 443, type 443 + TILE::HFLIPPED. (Or | instead of +, to be safe.) Note that JJ2 may crash if asked to horizontally flip a tile that was not placed horizontally flipped somewhere in the level in JCS; the same issue does not apply to vertically flipped tiles.

Similarly, to place or otherwise use animated tiles, add the TILE::ANIMATED constant (=0x8000). 0 + TILE::ANIMATED will be the topmost animated tile in the list, 1 + TILE::ANIMATED will be the second from the top, and so on. You can add TILE::HFLIPPED and/or TILE::VFLIPPED at the same time as TILE::ANIMATED—1 + TILE::VFLIPPED + TILE::ANIMATED is the second animated tile from the top, flipped vertically—and ANDing by TILE::RAWRANGE will get rid of the TILE::ANIMATED flag just like it does the other two.

(These constants all belong to the TILE::Flags enum, in case you need to know that for any arcane reason.)

Appendix: Custom Objects

Among AngelScript's most powerful features is certainly the ability to define your own objects. behaviors. To begin with, it is important to have some understanding of how JJ2 handles its objects. Every active object contains a pointer to a function that defines its behavior. To take a simple example: the Pulze Light object sits in place, constantly adjusting its light property, and removes itself from memory when it goes too far offscreen or when the player dies in single player. Most objects have significantly more complicated behaviors than that, but they all come down to one thing: a function that is called by the object, every single tick. What AngelScript does is allow you to write your own object behaviors, either based on JJ2's native ones or else totally from scratch.

The starting point for any object customization is the jjOBJ property behavior. JJ2 (and by extension AngelScript) has a massive inventory of possible values, all grouped together for you in the BEHAVIOR namespace. Most of the behaviors correspond to individual JJ2 objects—BEHAVIOR::QUEEN for OBJECT::QUEEN, BEHAVIOR::CHESHIRE1 for OBJECT::CHESHIRE1, and so on—but there are also a lot of more generic behaviors that get recycled for multiple objects, such as BEHAVIOR::PICKUP (food, gems, ammo, coins, and so on), BEHAVIOR::WALKINGENEMY (lizards, hatters, doggy doggs, and several more), and BEHAVIOR::SHARD (various particle effects). To make the Norm Turtle enemy behave like its JJ1 counterpart, i.e. walk back and forth and never do anything else, we need only change its behavior from BEHAVIOR::NORMTURTLE to BEHAVIOR::WALKINGENEMY. By giving it a generic enemy-type playerHandling value, we can also remove its behavior of creating turtle shells when defeated.

void onLevelLoad() {
	jjObjectPresets[OBJECT::NORMTURTLE].behavior = BEHAVIOR::WALKINGENEMY;
	jjObjectPresets[OBJECT::NORMTURTLE].playerHandling = HANDLING::ENEMY;
}

Still, that's not very exciting. The generic WALKINGENEMY behavior is really easy to use because it only ever uses one animation, but what if you want something a little more complicated, for example, a Labrat? There are two steps involved here. First, define a behavior function, which is any function that fits the pattern void func(jjOBJ@). This jjOBJ, naturally, is the object doing the behaving... if you have three objects in your level with the same behavior function, that function will be called three times per tick, pointing to each of the three objects in turn. Second, in onLevelLoad, set behavior to that function of yours instead of a BEHAVIOR::Behavior constant. For example:

void onLevelLoad() {
	jjObjectPresets[OBJECT::LABRAT].behavior = MyRat;
}
void MyRat(jjOBJ@ rat) {
	rat.behave(BEHAVIOR::LABRAT);
}

Those two steps alone are pretty pointless, though, since the end result will be that the object behaves exactly as it always did. Instead of JJ2 seeing that behavior is BEHAVIOR::LABRAT and calling the labrat behavior function, JJ2 will see that behavior is MyRat and then call that function, which will in turn tell JJ2 to call the labrat behavior function. A player would notice no difference whatsoever. However, behave does take an optional boolean parameter (defaults true), to specify whether JJ2 should actually follow any instructions within the native behavior function to draw the object. By setting this parameter to false, we gain the opportunity to draw the object however we like using AngelScript's various drawing functions, for example, tinted red:

void onLevelLoad() {
	jjObjectPresets[OBJECT::LABRAT].behavior = MyRat;
}
void MyRat(jjOBJ@ rat) {
	rat.behave(BEHAVIOR::LABRAT, false);
	jjDrawSpriteFromCurFrame(rat.xPos, rat.yPos, rat.curFrame, rat.direction, SPRITE::TINTED, 24);
}

But AngelScript lets you do much more than just call the behave method and play around with sprites. Instead you can define an object's behavior completely from start (literally) to finish. The most important jjOBJ property to consider when defining a custom object behavior is state, since JJ2 objects are basically state machines. When an object is first created—barring bizarre jjObjectPresets fiddling—its state will equal STATE::START. Traditionally, objects take this opportunity to initialize a few properties not already set in jjObjectPresets, perhaps read some parameters from the event map, and then change their state to something else, e.g. STATE::IDLE or STATE::STILL or STATE::DELAYEDSTART. A red spring, for instance, learns during STATE::START whether it's supposed to be a ceiling spring or a floor spring, and never bothers checking to find out ever again. If you're defining a bullet object, changing state to something else (usually STATE::FLY) is mandatory, since bullets of STATE::START are not checked for collision with other objects/players; otherwise there's nothing in the game code that forces you to do it, but it certainly seems like it should be a good idea.

On the opposite side of an object's lifespan is STATE::KILL. Not all objects need to have this state, but anything that uses JJ2's normal shootable-object code will be set to STATE::KILL when the energy property reaches 0. In most cases, STATE::KILL is a sign to call jjOBJ::delete(), although you may want to do other things as well, e.g. add an explosion. Bullets are a bit different, using STATE::EXPLODE instead, but the outcome is pretty much the same.

Somewhat related is STATE::DEACTIVATE, the only state besides STATE::START that is essentially guaranteed to apply to every single object, albeit only in Single Player. (In multiplayer, it can of course be invoked manually, but will never be triggered from the game itself.) STATE::DEACTIVATE occurs under one of two circumstances: the player dies, causing every object to deactivate, or the object was active but is now more than about thirty tiles distant from the player and thus no longer belongs in active memory. Objects whose objType property's seventh bit is set to 1, e.g. Rotating Rock, are immune from the latter case, but all objects get STATE::DEACTIVATE when the player dies in SP. Like STATE::KILL and jjObjectDelete, you can usually just get away with calling jjObjectDeactivate, which deletes the object and (if it was created directly from the level map) makes a note that the object is no longer active and can be recreated later.

Finally, many objects will need some code for STATE::FREEZE. The usual pattern is to decrease the freeze property by one every tick, and once it hits 0, restore state to oldState. This is an also an opportunity to use the SPRITE::FROZEN mode for drawing sprites, although jjOBJ::draw() will take care of that for you automatically, along with flashing the object white if it's recently been shot. Here, then, is some sample code for a very basic enemy that sits in place and does absolutely nothing but animate:

void StationaryEnemy(jjOBJ@ obj) {
	switch (obj.state) {
		case STATE::START: //always used
			obj.determineCurAnim(ANIM::SUCKER, 4);
			obj.state = STATE::IDLE;
			obj.playerHandling = HANDLING::ENEMY;
			obj.bulletHandling = HANDLING::HURTBYBULLET;
			obj.isTarget = true;
			obj.triggersTNT = true;
			obj.energy = 1;
			obj.points = 300;
		case STATE::IDLE: //arbitarily chosen state
			obj.frameID = (jjGameTicks/5) & 7;
			obj.determineCurFrame(); //remember to do this after changing frameID, since by the time you're writing your own behavior, JJ2 won't do it for you anymore
			obj.draw();
			break;
                case STATE::FREEZE: //can be left out if object can't be shot, or if isFreezable equals false, or if there's no ice in the level, or if you don't mind object never unfreezing
                        if (--obj.freeze == 0) obj.state = obj.oldState;
			//consider calling jjOBJ::unfreeze() here
			obj.draw();
			break;
		case STATE::DEACTIVATE: //can be left out if level is MP-only
			obj.deactivate();
                        break;
		case STATE::KILL: //can be left out if not using normal object energy handling
			obj.delete();
			break;
	}
}

Naturally there's a lot more that an enemy can do—move around, change animations, fire bullets—but that's the basic structure right there. Draw the sprite to the screen somehow, remember to delete the object when it gets killed or deactivated, and pretty much everything else is optional or bonus. You don't even need to worry about its energy, since the HANDLING::ENEMY and HANDLING::HURTBYBULLET settings make JJ2 take care of all that stuff for you. Then there's the basic form of a bullet, which might look something like this:

void onLevelLoad() {
	jjObjectPresets[OBJECT::BLASTERBULLET].behavior = DullBullet; //for the sake of example, let's just use blaster's existing values for curAnim and xSpeed and so on
}
void DullBullet(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		obj.state = STATE::FLY;
		if (obj.creatorType == CREATOR::PLAYER) obj.xSpeed = obj.xSpeed + obj.var[7] / 65536.0; //xSpeed of the player when firing the bullet
	} else if (obj.state == STATE::DEACTIVATE) {
		obj.delete();
	} else if (obj.state == STATE::EXPLODE) {
		obj.behavior = BEHAVIOR::EXPLOSION2;
		obj.frameID = 0; //display the full .killAnim animation
	} else {
		obj.xSpeed = obj.xSpeed + obj.xAcc;
		obj.ySpeed = obj.ySpeed + obj.yAcc;
		if ((--obj.counterEnd == 0) || (jjMaskedPixel(obj.xPos + obj.xSpeed, obj.yPos + obj.ySpeed))) {
			obj.state = STATE::EXPLODE;
		} else {
			obj.xPos = obj.xPos + obj.xSpeed;
			obj.yPos = obj.yPos + obj.ySpeed;
			obj.draw();
		}
	}
}
    

The most important things about defining a bullet are a) changing the state from STATE::START and b) changing the state to STATE::EXPLODE, because those states are referenced by various bits of external code. Bullets with either of those two states will not be checked for collision with other objects or players. Moreover, setting the state to STATE::EXPLODE in an online server may potentially tell other clients that their own copies of that bullet object need to be destroyed.

To illustrate one way in which bullet behaviors may be made more complicated, using the following code instead will cause the bullet to recognize ricochet events:

		if (--obj.counterEnd == 0) {
			obj.state = STATE::EXPLODE;
		} else if (
			jjMaskedPixel(obj.xPos + obj.xSpeed, obj.yPos + obj.ySpeed) && ((jjEventAtLastMaskedPixel != AREA::RICOCHET) || !obj.ricochet())) {
			obj.state = STATE::EXPLODE;
		} else {
			obj.xPos = obj.xPos + obj.xSpeed;
			obj.yPos = obj.yPos + obj.ySpeed;
			obj.draw();
		}		
    

Not all objects are enemies and bullets, of course, but you can get by for quite a while by pretending they are while making ever more inventive use of the various sprite-drawing functions. Still, what if you want to add a new pickup instead? For example, say you want the Fast Feet pickup to increase how high a player can jump. For that you'll need the most complicated hook function of them all: onObjectHit.

void onLevelLoad() {
	jjObjectPresets[OBJECT::FASTFEET].points = 100;
	jjObjectPresets[OBJECT::FASTFEET].scriptedCollisions = true;
}

void onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
	if (obj.eventID == OBJECT::FASTFEET) { //always a good idea to make sure you've got the right object
		play.jumpStrength = play.jumpStrength - 1;
		obj.behavior = BEHAVIOR::EXPLOSION2; //this is ESSENTIAL. just like enemies die by getting their states set to STATE::KILL, and bullets die by getting their states set to STATE::EXPLODE, pickups die by getting their behavior set to BEHAVIOR::EXPLOSION2. yes, sometimes a little consistency is in fact too much to ask for.
		obj.scriptedCollisions = false; //or obj.playerHandling = HANDLING::EXPLOSION; or something like that
		obj.frameID = 0;
		//you should probably play a sound here too. pick one! it'll be an adventure!
	}
}

The short version of the story is that onObjectHit is what gets called for objects for which scriptedCollisions equals true, if their playerHandling value is HANDLING::PICKUP (called for collisions with players only), or HANDLING::SPECIAL (called for collisions with either players or bullets). The above case, HANDLING::PICKUP, is pretty straightforward: the player argument points to the jjPLAYER who collided with the pickup object, and the bullet and force arguments may be totally ignored. HANDLING::SPECIAL is unfortunately a lot more complicated.

  jjOBJ@ bullet jjPLAYER@ player int force
Collision null non-null 0
Sugar rush null non-null 1
Special move null non-null -1
Frozen+running null non-null -101
Bullet non-null maybe null variable
Turtle shell non-null, but objectID=0 maybe null 1
Blue+orange bird non-null, but objectID=0 maybe null 4
TNT explosion non-null, but objectID=0 maybe null variable

As you can see, the various collisions that may call onObjectHit for HANDLING::SPECIAL objects may be broken into two main types, or three if you want to consider bullets on their own. The first four categories result from a player rabbit directly colliding with the object, and the force parameter hinting at whether the object should consider taking damage from the collision or not. Of course, something like the Cheshire2 object doesn't pay any attention to force here; it's pretty much just for destructable objects like enemies or crates.

Bullets are the main alternate cause, and force should correspond to their animSpeed property. In this case, player may or may not be null, depending on whether the bullet was fired by a player (not null) or by a generator object/crate object/whatever (null). Or to put it in AngelScript terms, whether the bullet's creatorType property equals CREATOR::PLAYER.

The remaining three categories—turtle shells, impacts with the blue and orange bird's beak, and being caught in a TNT object's blast radius— all employ the curious strategy of setting bullet to jjObjects[0]. To distinguish them from true bullets, thus all you need to do is compare the jjOBJ's objectID property to 0, but since JJ2 never really reads any of object 0's properties, it's generally safe to treat such collisions exactly as if they were truly bullets: for example, setting the bullet jjOBJ's state to STATE::EXPLODE when that jjOBJ is object 0 is an action totally without consequences. Like bullets, turtle shell collisions may or may not set the player parameter, depending on whether they've been shot by a player or not prior to calling onObjectHit.

A fairly comprehensive onObjectHit definition for enemy-like objects can be found in plusEnscripted.j2as, which makes note of most of the edge cases that it's leaving out. Otherwise... good luck! Remember to look at all the options for the jjOBJ playerHandling and bulletHandling properties while figuring out exactly how you want your object to work, and as with everything else, don't be afraid to ask for help.

Appendix: Constants

WEAPON::Weapon

Weapons are named pretty intuitively, using the common names for each one instead of necessarily the JCS names or official manual names. The exceptions are Pepper Spray and Electro-Blaster, which are simply GUN8 and GUN9 (a la JCS) to allow for situations in which those constants refer to the Fireball and Blade Guns instead.

STATE::State

These are the different modes, or states, that a jjOBJ object can be in at any given time. The important ones are START, which is the initial state of an object that's just been created; KILL or sometimes DONE, for when an object gets destroyed; DEACTIVATE, for when an object passes out of memory in single player mode; EXPLODE, for dying bullets; and FREEZE, for when an object gets shot with ice. The rest are largely arbitrary.

AREA::Area

Indented constants are alternate aliases for the non-indented constants directly above them. They evaluate to exactly the same results—the only difference between, say, AREA::JAZZLEVELSTART and AREA::JAZZSTART is which you'd rather type.

OBJECT::Object

Indented constants are alternate aliases for the non-indented constants directly above them. They evaluate to exactly the same results—the only difference between, say, OBJECT::SAVEPOST and OBJECT::CHECKPOINT is which you'd rather type.

BEHAVIOR::Behavior

Most of these constants share the same names as their corresponding OBJECT::Object constants—BEHAVIOR::SHARD for OBJECT::SHARD, BEHAVIOR::UTERUS for OBJECT::UTERUS, and so on—but there are a few exceptions. To save space, only the BEHAVIOR::Behavior values that do not align perfectly with OBJECT::Object values will be listed.

SOUND::Sample

Preview samples at the invaluable JJ2 Soundboard. All LORISOUNDS_* samples are naturally only available in TSF, but the X* and Z* samples (for HH98 and TSF enemies) will evaluate in 1.23 to their ordinary equivalents, e.g. BILSBOSS_THUNDER for XBILSY_THUNDER or DOG_WAF1 for ZDOG_WAF1.

TEXTURE::Texture

With the exception of TEXTURE::LAYER8, all textures listed below were drawn from their respective tilesets (without asking permission from their artists). For preview images, refer to the online version of this document.

ANIM::Set

Values used by the setID parameters of the jjOBJ method determineCurAnim, the jjCANVAS method drawSprite, and the global function jjDrawSprite. These are in the same order as they appear in Jazz Sprite Dynamite, so it should be totally straightforward to match the names to the numbers. Sets for HH98 or TSF-exclusive enemies, when used in 1.23, will evaluate to the values of their 1.23 equivalents, e.g. LIZARD for XLIZARD.

Thanks

AngelScript is written and maintained by Andreas Jönsson of AngelCode.com. The JJ2+ developers owe him a great deal of thanks.