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, alter or totally replace individual ammo types, load new graphics or sounds from external files, transmit arbitrary sequences of data among players in online servers, and so much more.

(Note, however, that JJ2's AngelScript implementation is primarily local in its domain, and if you are writing code for a multiplayer level, you will need to be careful to find ways to sync gameplay experiences among clients. A few properties are naturally server-global, such as the jjTeamScore properties or the current game mode or various properties for configuring how your local players are drawn, but most other code—individual items, bullets, graphical effects, etc.—will need to be configured more carefully, in particular using the jjSTREAM network communication class. (Of course, none of this is not a concern for Single Player levels.) Fortunately, everyone in the server will be running all 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, math, and dictionary 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 such as Notepad++ extended with Sir Ementaler's XML files) 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, plusPalettes.j2as for plusPalettes.j2l, etc. This new .j2as file is where all the code for this particular level will live, unless you load additional files using the #include statement or various mutators.

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@ player) {
	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), and information about errors and warnings will then be displayed in the chatlogger window. It is also possible to send your own debugging information using the global function jjDebug(string text, bool timestamp = false), 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; onObjectHit is described in the description for jjBEHAVIORINTERFACE::onObjectHit; onReceive in the description for jjSendPacket; and onGetPublicInterface in the description for jjPUBLICINTERFACE).

void onChat(int clientID, string &in stringReceived, CHAT::Type chatType)
bool onLocalChat(string &in stringReceived, CHAT::Type chatType)
onChat is called whenever a chat message pops up in game. onLocalChat is called only when chat is received from players on the same machine the script is executing on. A return value of true indicates that the chat message should be suppressed, whereas a return value of false will cause the message to be handled normally. clientID is a unique ID of the game client that sent the chat message. stringReceived is the text of the chat message that was received. chatType can take one of the following values: NORMAL, TEAMCHAT, WHISPER amd ME.
Any message beginning with "/" is interpreted as a command, not as chat, and so will not be passed to either of these hooks. Messages beginning with "!" will, though, as will arguments of commands /whisper (and its aliases, /w and @) and /me.
bool onCheat(string &in cheat)
This function is called in single player mode when a player attempts to enter a cheat code. It will be called once for each letter appended to the cheat code after jj. For example, in typing jjgod, onCheat will be called three times, with cheat equal to "jjg", "jjgo" and "jjgod". A return value of true indicates the cheat should be suppressed, whereas a return value of false will cause the cheat to be handled normally. If you want to compare cheat to the full list of standard cheat codes, use jjIsValidCheat, which will be updated if JJ2+ adds or changes any cheat codes in future releases.
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.
void onRoast(jjPLAYER@ victim, jjPLAYER@ killer)
This function is called every time a player is roasted, including non-local players, including self-inflicted deaths from pits and other environmental hazards (in which case victim is killer will be true). Note however that not every death counts as a roast, e.g. deaths from the /frustration command or from unspectating.

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 a series of appendices containing instructions for specific topics, followed by a fourth 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 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. Thankfully, 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. When you encounter a need to change properties of other players, this is possible to achieve with use of custom packets; for that you will need the jjSTREAM class and the jjSendPacket function.)

For the most part, properties and methods of jjPLAYER objects are familiar from regular JJ2, or at least can be easily understood. What merits discussion is a new feature known as the Player Timer. Put loosely, it 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 impossible for the player to double jump, assuming they're Spaz and currently in the air. To allow compatibility with multiple air jumps, this was replaced with doubleJumpCount.
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, or else set their allowed properties to true in the jjWeapons array.
bool antiGrav
Whether the player falls up (true) or down (false). Also causes the player to be drawn upside-down.
(For the record, this mode is still a work-in-progress. A handful of objects—bridges, pinball flippers, and rotating poles—don't work very well with it yet. However, future revisions will be 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.
int buttstomp
Buttstomp phase. Values below 41 mean the player is initializing a buttstomp, 41 means the player is currently buttstomping, values between 41 and 121 mean the player is landing on the ground and values of 121 and higher mean the player is not currently buttstomping.
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, CHAR::FROG or CHAR::BIRD2). 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.
const int clientID
ID of the game instance controlling the player. This is particularly useful for packet exchange with use of jjSendPacket but can also be used to determine whether players are splitscreeners.
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 uint16 curAnim
The current animation the player takes its frames from, serving as an index to the jjAnimations array. Each animation has a distinct value; for example, Jazz standing in place might be 81, but Spaz standing in place might be 185.
const uint curFrame
The overall current frame displayed to the screen to represent this player, serving as an index to the jjAnimFrames array. For any given jjPLAYER@ p, p.curFrame will equal jjAnimations[p.curAnim].firstFrame + p.frameID.
const int currTile
A shortcut value, always equalling int(xPos)/32 + int(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).
const int deaths
In competitive game modes, the number of deaths the player experienced or a negative value if unknown (this is currently the case when the property is checked by clients in XLRS mode).
int8 direction
Which direction the player is facing. Negative values mean left and non-negative ones mean right.
int doubleJumpCount
How many times the player used double jump in the air since their last regular jump.
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.
const uint8 frameID
The index of the current frame displayed to the screen to represent this player within its animation. For example, Jazz's standing animation is only one frame long, so his frameID will always equal 0 while playing that animation, while Spaz's will range from 0 to 4. For any given jjPLAYER@ p, p.curFrame will equal jjAnimations[p.curAnim].firstFrame + p.frameID.
int8 frozen
0 if unfrozen; otherwise, constantly counts down towards 0.
uint32 fur
Palette indexes of the player's fur in the form of 4 colors, 8 bits each. For potentially more intuitive ways of fur color manipulation refer to jjPLAYER methods furGet and furSet. Changes to this setting are automatically shared between all clients in the server. Clients are not allowed to change the fur colors of non-local players, but servers or local games can change the colors of any player (even if their jjPLAYER::isActive is false), which can be useful for drawing sprites using SPRITE::PLAYER.
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 helicopter
The amount of time in which player's copter ears will run out or 0 if the player is currently not using copter ears.
int helicopterElapsed
The amount of time player spent using copter ears since their last jump. If this reaches or exceeds their respective jjCHARACTER::helicopterDurationMax, the player will be unable to use helicopter ears again until they have landed, analogous to using up a double jump.
int idle
The amount of time since the last change of idle animation or 0 if the player is not idle.
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.
bool invisibility
Whether the player is invisible. Invisible players don't have their sprites or names drawn. Changes to this setting are automatically shared between all clients in the server.
const bool isActive
Does this jjPLAYER object correspond to an actual player in the server, local or otherwise? If not, few if any of this jjPLAYER's other properties will be reliable. This is a fairly weak check and equals true for several types of players who may be present in the server but not actually playing—isInGame may often be more useful.
const bool isAdmin
Whether the player is logged in as a Remote Admin in the current online server. Because admin privileges can vastly differ depending on server and admin group, a more precise tool hasPrivilege exists.
const bool isConnecting
Whether the player is a client who has not finished joining the current online server yet, as represented by a "C" or "D" (if downloading) next to their name on the player list.
const bool isIdle
Whether the player is idle and does not appear in the level or player list. Currently this can only ever be true of the server.
const bool isInGame
Equals true if isActive is true but isConnecting, isIdle, isOut, and isSpectating are all false. If more such properties need to be added in future revisions of JJ2+, isInGame will be updated to reflect them.
const bool isJailed
In Jailbreak game mode, whether the player is currently in jail. False in all other game modes.
const bool isLocal
Is this jjPLAYER object controlled by this instance of JJ2?
const bool isOut
Equals true if the player has lost all their lives (or joined too late) in an LRS-based gamemode.
const bool isSpectating
Equals true if the player is spectating normally, i.e. not forced into spectating by being out or an idle server.
const bool isZombie
In Pestilence game mode, whether the player is currently a zombie. False in all other game modes.
float jumpStrength
Vertical speed gained by the player on jump. Defaults to -10.
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.
int8 light
The intensity of light emitted by the player. Changes to this setting are automatically shared between all clients in the server.
uint8 lighting
The player's current level of ambient lighting, as affected by the Set Light and Reset Light events.
LIGHT::Type lightType
The type of light emitted by the player. Possible values are NONE, NORMAL, POINT, POINT2, FLICKER, BRIGHT, LASERBEAM, LASER, RING, RING2, and PLAYER (default). Changes to this setting are automatically shared between all clients in the server.
int lives
In single player or cooperative mode, how many lives the player has remaining. For LRS-based game modes use lrsLives instead.
const int localPlayerID
Which local player the player is, in case of splitscreen. 0-3.
const int lrsLives
In LRS-based game modes (LRS, TLRS, XLRS), how many lives the player has remaining, a negative value for other modes. For single player and cooperative modes use lives instead.
const string name
The player's name. This is a constant property; to change it, you can use the setName method.
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 default 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 int8 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, or else set their allowedPowerup properties to true in the jjWeapons array.
const int roasts
In competitive game modes, how many kills the player made or a negative value if unknown (this is currently the case when the property is checked by clients in RT, LRS, Pestilence and TLRS modes). If the game mode doesn't allow players to kill other players, as is the case in Single Player, Cooperative, Treasure Hunt and Race, this property will always be equal to 0.
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
In single player and cooperative modes, 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
In single player and cooperative modes, 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.
const ANIM::Set setID
Which set of animations the player uses, serving as an index to the jjAnimSets array. This is nearly a 1:1 mapping with charCurr, except that the two birds (CHAR::BIRD and CHAR::BIRD2) share a setID.
The RABBIT::Anim enum is a version-independent list of all possible animations a rabbit player might use, expressed as indices of animations within the player's anim set. To see if a rabbit player p is currently standing still, for example, you can check if (p.curAnim - jjAnimSets[p.setID].firstAnim == RABBIT::STAND).
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. Using values from outside of the range of existing shields may be a reason of incompatibility with future versions of JJ2+ in case more shields are added.
int specialMove
How long the player has been performing their special move or 0 if the player is currently not using a special move.
SPRITE::Mode spriteMode
Sprite mode the player's sprite is drawn in. Possible constants appear in the appendix below. Changes to this setting are automatically shared between all clients in the server.
uint8 spriteParam
A sprite mode parameter further specifying how the player should be drawn. Changes to this setting are automatically shared between all clients in the server.
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 jjResolutionHeight. Note that the "subscreen" includes any black borders (as measured by jjBorderHeight and jjBorderWidth), so these properties are not necessarily the top left corner of a visible section of the level.
const TEAM::Color team
The player's team in team-based game modes. Possible values are TEAM::BLUE, TEAM::RED, TEAM::GREEN, and TEAM::YELLOW.
const bool teamRed
Incorrectly equals true whenever the player is not on the blue team, i.e. including if they're on green or yellow. Use the property above instead.
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 per game tick squared, 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 per game tick, positive or negative.
float yAcc
Vertical acceleration in pixels per game tick squared, 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 per game tick, 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)
void cameraFreeze(bool xUnfreeze, float yPixel, bool centered, bool instant)
void cameraFreeze(float xPixel, bool yUnfreeze, bool centered, bool instant)
void cameraFreeze(bool xUnfreeze, bool yUnfreeze, 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. In overloads containing xUnfreeze or yUnfreeze, their values determine whether the corresponding axis should be unfrozen by the function, allowing its free movement (true), or left unaffected (false). In particular, player.cameraFreeze(true, true) is equivalent to player.cameraUnfreeze().
void cameraUnfreeze(bool instant = true)
If cameraFreeze has been called, undoes the effect and lets the camera freely follow the player once again. If instant is left false, the camera will take roughly half a second to scroll to its target.
bool doesCollide(const jjOBJ@ object, bool always = false) const
Returns true if the player's sprite collides with that of the specified object, otherwise false. This is a convenient wrapper for jjANIMFRAME::doesCollide, using the positions, directions and curFrame properties of the player and object involved. No other variables—including playerHandling, bulletHandling, or the scale and rotation their sprites are drawn in—are taken into account.
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.
void furGet(uint8 &out, uint8 &out, uint8 &out, uint8 &out) const
Sets provided parameters' values to palette indexes of the player's fur colors.
void furSet(uint8, uint8, uint8, uint8)
Sets player's fur colors to those represented by provided palette indexes. Changes made by this method are automatically shared between all clients in the server. Clients are not allowed to change the fur colors of non-local players, but servers or local games can change the colors of any player (even if their jjPLAYER::isActive is false), which can be useful for drawing sprites using SPRITE::PLAYER.
int getObjectHitForce(const jjOBJ@ target = null) const
Returns a value expressing whether the player is currently in a state to harm objects or other players, equivalent to the "force" argument in objectHit or the onObjectHit hook/method. Specifically, this method returns -1 if the player is performing a special move and the canHurt property of their respective jjCHARACTER is true, 1 if the player is buttstomping or has a sugar rush, or 0 if none of the above are true. Doesn't work especially well when called on non-local players.
If a non-null jjOBJ@ is specified as target, two additional checks will be performed, returning -101 if the object is frozen and the player is running into it really fast, or -1 if the player is a frog (with a true canHurt) hitting the object with its tongue.
bool hasPrivilege(const string &in privilege, uint moduleID = jjScriptModuleID) const
Checks whether the player has the specified privilege within the script module moduleID. Privileges are specified in the admin configuration file (typically admin.ini) and serve as a way to determine what actions specific admin groups are allowed to take. You can read more about them in the Remote Admin section of the JJ2+ readme.
Because privileges are only known to the host, this function will always return false if called client-side. The function will always return false for players who are not logged in, and will always return true if the player is the host or the game is local. If the privilege string contains invalid characters, i.e. anything besides letters, digits, underscores ('_'), full stops ('.'), and hyphens ('-'), or if moduleID is not valid, the function will also return false. In all other cases, i.e. if called server-side for a logged in client and with valid parameters, privileges obtained from the admin configuration file will be tested for a match, and if one is found, the function will return true, otherwise false.
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 and the method is not called by the host, 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.
bool isEnemy(const jjPLAYER &in victim) const
Returns true iff victim is a player who can be hurt by this player's bullets or other attacks, according to the rules of the current gamemode. Potentially useful if you are writing a custom weapon behavior and don't want to use HANDLING::PLAYERBULLET.
The following conditions will cause this method to return false: the two jjPLAYER objects are the same player; the gamemode is Single Player or Cooperative; in a team-based game, the two players are on the same team and jjFriendlyFire is false; in Roast Tag, Eva's Ring has been captured, yet neither player is jjTokenOwner or jjBottomFeeder; in Pestilence, either both players are zombies or neither are zombies but jjDoZombiesAlreadyExist is false; or in Jailbreak, victim is in jail.
Again, this method checks only gamemode-related conditions. (And if more gamemodes are added in the future, this method will be updated to reflect them.) It does not check jjPLAYER::isInGame, jjGameState, or anything that might cause jjPLAYER::hurt to return false if its forceHurt parameter is left false.
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-Bird2. Or if rabbitsOnly is true, skips birds and frog and acts like a morph monitor instead, restricting available morph targets to those whose jjCHARACTER::morphBoxCycle properties equal true. Returns the player's new character: CHAR::JAZZ, CHAR::SPAZ, CHAR::LORI, CHAR::BIRD, CHAR::FROG or CHAR::BIRD2.
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, CHAR::FROG or CHAR::BIRD2.
bool objectHit(jjOBJ@ target, int force, HANDLING::Player playerHandling)
Causes the game to think the player is colliding with object target, even if they are not in the same area, and returns true if (generally speaking) the object is affected somehow. Calling this method on a non-local player will return false unless called by the host of a server.
The force parameter hints at whether the player should be able to damage the object, if the object can be damaged to begin with. A value of 0 means that the player is simply bumping into the object at random; 1 means a buttstomp or sugar rush; -1 means a special move; and -101 means the object is frozen and the player runs into it really fast. If you want this parameter to reflect reality, get the value from jjPLAYER's getObjectHitForce method.
The playerHandling parameter specifies how the player should interact with the object—for example, passing HANDLING::PICKUP will force the player to treat the object as a pickup, even if its actual playerHandling property is set to something else. If the value is not HANDLING::ENEMY, HANDLING::SPECIAL, HANDLING::PICKUP, HANDLING::ENEMYBULLET, or HANDLING::PLAYERBULLET, nothing will happen. If the value is one of the first three and the object has scriptedCollisions set to true, some version of onObjectHit will be called with a null bullet argument, this jjPLAYER as player, and whatever force you call this method with as force.
The results of this collision will be broadcast to other players in the server exactly as if it had happened naturally through the sprites colliding. For example, collecting a pickup object half the level away will cause other players to see the pickup disappear.
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 and any companion bird the player may have. The best way of creating seamlessly looping levels.
uint8 resetLight()
Resets player's ambient lighting value to the level's default.
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. If morphEffect is true, displays the default visual effect. Returns the new character (equal to the player's charOrig).
bool setName(const string &in name)
Sets the player's name. This method will only succeed if called server-side or in a local game, and it will then return true to indicate success. Otherwise no change will occur and the method will return false.
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. To access text in help strings with specific ID, use jjHelpStrings instead.
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. Otherwise returns true.
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 and returns false. 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 and returns false. 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. To allow the script to have this kind of impact on game state of other clients it's necessary to use the jjSTREAM class and the jjSendPacket function. 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 jjObjectPresets[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
Intended to determine how fast the object animates. However, only gem rings, speed destruct scenery, and collapse scenery actually use this property for that purpose, and all other non-bullet objects leave it untouched.
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 jjBEHAVIORINTERFACE.
In order to avoid common bugs, it's strongly discouraged to modify behavior of objects of eventID OBJECT::GENERATOR, and especially of jjObjectPresets[OBJECT::GENERATOR]. Currently a great portion of game code tests it against BEHAVIOR::GENERATOR and if it doesn't succeed, it may take unexpected actions, typically involving kicking and banning clients for invalid weapons. Similarly, replacing BEHAVIOR::DIAMONDSAREFOREVER is liable to break Treasure Hunt or Head Hunters games.
Replacing BEHAVIOR::DESTRUCTSCENERY, BEHAVIOR::TRIGGERSCENERY, or BEHAVIOR::SUPERGEM will normally prevent the states of those objects from being shared with newly joining clients in online servers, but this can be circumvented by replacing them with jjBEHAVIORINTERFACE-implementing classes that define onGetActive/onSetActive methods appropriately.
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; an index for the jjAnimations array. You can obtain useful values for this from the determineCurAnim method or else any jjANIMSET::firstAnim.
uint curFrame
The overall current frame displayed to the screen to represent this object, taking into account both curAnim and frameID; an index for the jjAnimFrames array. You can obtain useful values for this from the determineCurFrame method or else any jjANIMATION::firstFrame.
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. Some objects may also correctly interpret SPRITE::Direction constants: SPRITE::FLIPNONE, SPRITE::FLIPH, SPRITE::FLIPV and SPRITE::FLIPHV but the ones involving vertical flip will not be perfectly reliable for objects without custom behavior, except for box objects (crates, barrels, monitors), which will take vertically flipped direction values as a cue to fall upwards instead of downwards.
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 for most behaviors, though you'll need to implement that manually if writing your own.
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, in the same format as curAnim.
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, LASERBEAM, LASER, RING, RING2, and PLAYER.
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 < jjObjectMax, jjObjects[n].objectID == n.
uint8 objType
Instead, use the properties playerHandling, isTarget, triggersTNT, deactivates, and scriptedCollisions.
bool scriptedCollisions
When true, JJ2 will call some version of onObjectHit to determine what to do if a bullet or player is detected as having collided with this object. See jjBEHAVIORINTERFACE. 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 by the game's native behavior functions 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 per game tick squared, positive or negative.
float xOrg
Original horizontal location in pixels.
float xPos
Current horizontal location in pixels.
float xSpeed
Horizontal speed in pixels per game tick, positive or negative.
float yAcc
Vertical acceleration in pixels per game tick squared, positive or negative.
float yPos
Current vertical location in pixels.
float yOrg
Original vertical location in pixels.
float ySpeed
Vertical speed in pixels per game tick, 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 jjBEHAVIORINTERFACE.
void bePlatform(float xOld, float yOld, int width = 0, int height = 0)
Makes the object act as a platform for players. Unlike beSolid, this method will only cause interactions with players on top of the object and not on its sides. Additionally, this method better accounts for the object's self-induced movement. The parameters xOld and yOld should be set to horizontal and vertical position of the object in the previous tick, whereas the jjOBJ properties xPos and yPos will be used for its current position. The remaining two parameters, width and height, should indicate the platform's dimensions in pixels. The default value of 0 will result in dimensions chosen automatically based on the size of the object's curFrame.
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 blast(int maxDistance, bool blastObjects)
Sends all players within maxDistance of the object flying away, like RFs or Bombs do when they explode. If creatorType equals CREATOR::PLAYER and players can currently hurt each other, the blast will hurt nearby players unless their playerID is equal to this object's creatorID. If blastObjects is true, then other jjOBJs within maxDistance whose isBlastable property equals true will be damaged and/or sent flying, just like when a TNT object explodes.
void clearPlatform()
Causes all local players that are currently standing on top of this object or pushing it to no longer be standing on top of it or pushing it. Should be called when deleting an object that calls bePlatform or 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.
Internally, this method runs the following code:
if (jjAnimSets[setID].firstAnim == 0) //not yet loaded
	jjAnimSets[setID].load(); //load from anims.j2a or plus.j2a, depending on setID
const int16 newCurAnimValue = jjAnimSets[setID].firstAnim + animation;
if (change)
	this.curAnim = newCurAnimValue;
return newCurAnimValue;
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.
Internally, this method runs the following code:
const jjANIMATION@ animation = jjAnimations[this.curAnim];
const uint newCurFrameValue = (animation.frameCount == 0) ? 0 : (animation.firstFrame + (this.frameID % animation.frameCount));
if (change)
	this.curFrame = newCurFrameValue;
return newCurFrameValue;
bool doesCollide(const jjOBJ@ object, bool always = false) const
bool doesCollide(const jjPLAYER@ player, bool always = false) const
Returns true if the object's sprite collides with that of the specified object or player, otherwise false. This is a convenient wrapper for jjANIMFRAME::doesCollide, using the positions, directions and curFrame properties of the player and object involved. No other variables—including playerHandling, bulletHandling, or the scale and rotation their sprites are drawn in—are taken into account.
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. Both maxDistance and foundDistance are actually squares of the distance expressed in pixels. 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 objectHit(jjOBJ@ target, HANDLING::Player playerHandling)
Assumes that this object is a player bullet colliding with object target, even if they are not in the same area or if this object is not actually HANDLING::PLAYERBULLET. You are allowed to set what HANDLING::Player value the target object will be treated as having—HANDLING::ENEMY, HANDLING::SPECIAL, or HANDLING::PICKUP, with all other values having no effect—but otherwise the code will run exactly as if the two objects really did collide. Effects may or may not be broadcast to other players in the server, and some version of onObjectHit will be called (with force as this object's animSpeed property) iff target has scriptedCollisions set to true and HANDLING::SPECIAL is passed as playerHandling.
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)
Moves 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 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 PARTICLE::INACTIVE or not, but it does exist and does get set sometimes.
PARTICLE::Type type
Any of the standard type options allowed by jjAddParticle: INACTIVE, FIRE, FLOWER, ICETRAIL, LEAF, PIXEL, RAIN, SMOKE, SNOW, SPARK, STAR, 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 before they disappear, 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 colorStop (default 48, pink), at which point the particle will disappear. The default initial value is 40 (yellow).
int8 fire.colorDelta is the rate at which the color property is going to change. The most commonly used values for this are 1 and -1 meaning that the color value will respectively increase or decrease by 1 every time the game decides to modify it. The default value is 1.
uint8 fire.colorStop is the color value which, when reached, will cause the particle to disappear. The default value is 48 (pink).
uint8 fire.size decides how large the oval will be, ranging from 0-3. The default value is 3.
flower
Corresponds to PARTICLE::FLOWER. Flower particles are drawn as single color, partially transparent, rotationally symmetric flowers, and drift for a while while getting progressively smaller. They are traditionally created by setting Type=1 on a Snow event.
uint8 flower.angle is the current angle of rotation the flower is drawn at.
int8 flower.angularSpeed is how much the angle changes every tick, positive or negative. By default they do not rotate at all.
uint8 flower.color is the color which a flower particle will be drawn as, and does not change during the particle's lifetime. The default value is 16 (green)
uint8 flower.size is how large the flower should be drawn, though this is not equal to its size in actual pixels. Decreases by 1 every tick until it reaches 0, at which point the particle disappears. The default starting value is 64.
uint8 flower.petals is the number of petals the flower has. The default value is 5.
icetrail
Corresponds to PARTICLE::ICETRAIL. Ice trail particles are drawn as single pixels. 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 colorStop (default 40, yellow), at which point the particle will disappear. The default initial value is 32 (light blue).
int8 icetrail.colorDelta is the rate at which the color property is going to change. The most commonly used values for this are 1 and -1 meaning that the color value will respectively increase or decrease by 1 every time the game decides to modify it. The default value is 1.
uint8 icetrail.colorStop is the color value which, when reached, will cause the particle to disappear. The default value is 40 (yellow).
leaf
Corresponds to PARTICLE::LEAF. Leaf particles are drawn as frames of the ANIM::PLUS_SCENERY animation set, and float along based on their speed properties and some random jiggling, until they pass offscreen or hit a wall and fall to the ground. They are traditionally created by setting Type=3 on a Snow event. Note that you will need to load ANIM::PLUS_SCENERY manually in order for leaf particles to display properly.
uint8 leaf.countup is set to 1 when the leaf hits a wall, then increments every tick until it hits 140 and the particle is deleted.
uint8 leaf.frame specifies which frame of the animation will be drawn. This ranges from 0-31 when the leaf is in motion and 0-2 once the deathcounter property is non-zero. The value of this property does not exactly correspond to frames in the animation in plus.j2a, however, since the leaf animation repeats many frames that are only included once in the file.
uint16 leaf.frameBase is ID of the frame the particle will use as a base sprite (curFrame-style), and has the default value of jjAnimations[jjAnimSets[ANIM::PLUS_SCENERY].firstAnim].firstFrame.
uint8 leaf.height is the distance from the ground in pixels that the particle must be in order to stop falling after hitting a wall. The default value is 2.
bool leaf.noclip, if set to true, makes the leaf pass through walls and floors, making its countup and height properties irrelevant.
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.
rain
Corresponds to PARTICLE::RAIN. Rain particles are drawn as transparent, resized sprites, and move around according to their xSpeed/ySpeed properties until they hit a masked pixel, at which point they are either deleted (if the pixel is to their side) or begin a splashing animation (if the pixel is above or below). They are traditionally created by setting Type=2 on a Snow event.
uint8 rain.frame ranges from 0-7 while the particle is in motion, switches to 8 upon hitting a floor or ceiling, and then increases until it hits 18 and the particle is deleted.
uint16 rain.frameBase is the first frame of the animation used by the particle, and has the default value of jjAnimations[jjAnimSets[ANIM::COMMON].firstAnim + 2].firstFrame. The displayed frame will be equal to this property plus the current value of the frame property.
smoke
Corresponds to PARTICLE::SMOKE. Smoke particles are drawn as small gray rectangles and always move 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 initial value is 0.
uint16 snow.frameBase is ID of the frame the particle will use as a base sprite (curFrame-style), and has the default value of jjAnimations[jjAnimSets[ANIM::SNOW].firstAnim].firstFrame. The displayed frame will be equal to this property plus the current value of the frame property.
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 colorStop (default 46, maroon), at which point the particle will disappear. The default initial value is 40 (yellow).
int8 spark.colorDelta is the rate at which the color property is going to change. The most commonly used values for this are 1 and -1 meaning that the color value will respectively increase or decrease by 1 every time the game decides to modify it. The default value is 1.
uint8 spark.colorStop is the color value which, when reached, will cause the particle to disappear. The default value is 46 (maroon).
star
Corresponds to PARTICLE::STAR. Star particles were added to the game to indicate sugar rush. They are drawn as a rotated star sprite. Besides linear speed they also have angular speed and change colors.
uint8 star.angle is the current angle of rotation the star is drawn at.
int8 star.angularSpeed is how much the angle changes every tick, positive or negative. By default they do not rotate at all.
uint8 star.color is the color the star is drawn as. The default value is 40 (yellow).
uint8 star.colorChangeCounter counts down to 0, at which point it resets to the value of the colorChangeInterval property and changes the color property to one of the sprite colors at random.
uint8 star.colorChangeInterval is how many ticks the particle goes before changing colors again. If it is set to 0 (the default value), however, the color will never change.
uint8 star.frame determines whether the star is filled (0) or only an outline (1). The default value is 0.
uint8 star.size is how large the star is. It decreases over time until it hits 0, at which point the particle is deleted. The default value is 16.
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. Possible values of TILE::Quadrant are TOPLEFT, TOPRIGHT, BOTTOMLEFT, BOTTOMRIGHT, and (default) ALLQUADRANTS.
uint16 tile.tileID specifies which tile the particle draws in the first place. The default value is 0, so remember to change it.

class jjCONTROLPOINT

This is a simple class representing a Domination game mode control point. It contains a set of read-only properties that let you obtain information about control points present in the level. Instances of the class can be accessed via the global jjControlPoints[16] property.

const TEAM::Color controlTeam
The team that this point is presently under control of. Possible values are TEAM::NEUTRAL, TEAM::BLUE, TEAM::RED, TEAM::GREEN, and TEAM::YELLOW.
const int direction
Direction of the gem sprite displayed by the control point. The value has purely visual influence on the game.
const string name
The control point's name.
const float xPos
Horizontal position in pixels.
const int xTile
Horizontal position in tiles.
const float yPos
Vertical position in pixels.
const int yTile
Vertical position in tiles.

class jjCHARACTER

This class contains a character profile, that is, a number of character-specific properties that determine various aspects of behavior followed by players who use the characters. Character profiles can be accessed using the jjCharacters[] array with CHAR::Char constants as its indexes.

AIR::Jump airJump
Determines the character's reaction to pressing jump in the air. Possible values are AIR::NONE, AIR::HELICOPTER and AIR::DOUBLEJUMP. No effect for birds or frogs.
bool canHurt
Whether the character can cause damage by using their special moves. This property also applies to Chuck's beak attack and Frog's tongue attack. Defaults true for all rabbits and false for birds and frogs.
bool canRun
Whether the character is capable of fast movement. Defaults true for all rabbits and false for birds and frogs.
int doubleJumpCountMax
The maximum number of jumps the character can perform in the air if their airJump property allows it. Defaults 1 for Spaz.
float doubleJumpXSpeed
float doubleJumpYSpeed
What horizontal and vertical speed the character will gain by using double jump. doubleJumpYSpeed defaults -8 for Spaz. doubleJumpXSpeed defaults 0 and is relative to the player's current direction, so for example a negative doubleJumpXSpeed will make the character move backwards.
int helicopterDurationMax
The maximum amount of time the character can keep using helicopter ears if their airJump property allows it.
float helicopterXSpeed
float helicopterYSpeed
What horizontal and vertical speed the character will gain by using helicopter ears. helicopterYSpeed defaults 1 for all rabbits; its effects when <= 0 are currently undefined. helicopterXSpeed defaults 0 and is relative to the player's current direction, so for example a negative helicopterXSpeed will make the character move backwards.
GROUND::Jump groundJump
Determines the character's reaction to pressing jump while crouching. Possible values are GROUND::JAZZ, GROUND::SPAZ, GROUND::LORI (available even in 1.23), GROUND::CROUCH (character remains crouching and does not jump), and GROUND::JUMP (character jumps but does not immediately buttstomp). No effect for birds or frogs. Note that changing this property to any the first three values may look pretty silly if the animations are not edited accordingly.
bool morphBoxCycle
Whether the character can be morphed to using the "Jazz<->Spaz" morph box. Also affects the jjPLAYER function morph when its rabbitsOnly argument is set to true.

class jjWEAPON

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

bool allowed
bool allowedPowerup
Whether the weapon and/or its powerup can be legally used in the level. This value is used by the server to detect attempts of cheating and kick offenders, and by all players to determine which weapons they get upon use of the /ready command.
Between onLevelLoad and onLevelBegin, JJ2+ runs a function to determine the values for these properties for each weapon. jjWeapons[WEAPON::BLASTER].allowed will always be set to true; for the rest, JJ2+ looks at the current contents of jjObjects and checks each active object's jjOBJ::eventID. Any +3 ammo pickup or +15 ammo crate will set allowed to true for that weapon, and any powerup will set both allowed and allowedPowerup to true. Additionally, OBJECT::GUNCRATE and OBJECT::GUNBARREL will both set jjWeapons[WEAPON::BOUNCER].allowed to true, and OBJECT::GENERATOR and object-spawning crates will behave as if they were the objects they will spawn, e.g. a generator that creates +3 Toaster pickups will set jjWeapons[WEAPON::TOASTER].allowed to true. (Anything not set to true will be set to false, so there is no point in editing these properties in onLevelLoad.)
This system covers most levels, but it is absolutely possible to set up your level to include ammo that JJ2+ was unable to guess based on a single loop through jjObjects, e.g. MCEs, or the "Weapon" parameter on +15 Bouncer crates, or any number of scripting changes. If you're at all unsure whether JJ2+ will make the right predictions for your particular level, there is zero harm in explicitly setting as many allowed and allowedPowerup values as you want, to true or to false, although make sure to set them in onLevelBegin at the earliest.
bool comesFromBirds
bool comesFromBirdsPowerup
Determines whether or not the red companion bird is allowed to shoot bullets of the specified weapon (comesFromBirds), and, if so, whether they may be powered-up (comesFromBirdsPowerup) if the player currently has a powerup for that weapon. Otherwise the bird will shoot ordinary blaster bullets regardless of the player's currWeapon.
Bird bullets do not affect the player's ammo count, regardless of the weapon's infinite property, and will not be replaced by shield bullets (because the bird does not have its own shield), regardless of the weapon's replacedByShield property.
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 defaultSample
When false, the default sample of the bullet will not be played.
int gemsLost
int gemsLostPowerup
How many gems a player shot by this weapon will lose in Treasure Hunt. (If the player has fewer gems than the weapon should cause them to lose, they will lose all their gems.) Defaults to 10 for WEAPON::SEEKER, WEAPON::RF, WEAPON::TNT, WEAPON::GUN8, and WEAPON::GUN9, and 3 for the others, with powerup status making no difference by default. Doesn't have any effect when changed by clients, only by the server (or in local games).
bool gradualAim
Whether it takes a few shots for bullets to adjust their direction to the direction the player is aiming, as is the case with pepper spray. If this weapon's spread property is SPREAD::GUN8 and jjAllowsFireball is true, this property will be treated as false.
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 or if it goes underwater subsequent to being shot. Defaults to true for WEAPON::TOASTER (and therefore also for fire shield bullets).
bool replacedByShield
When true and a shield is active, shield ammo will replace default weapon ammo. Defaults to true for WEAPON::BLASTER.
Under certain circumstances this setting is used to determine validity of network packets, so keep in mind that if its value is not the same for the host as it is for clients, they may be kicked for cheating.
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.
SPREAD::Spread spread
How many bullets are spawned by a single use of the weapon and in what manner. Possible values are:
  • NORMAL: Fires one bullet.
  • ICEPU: Fires two bullets; one in the direction you face, one in the direction you aim.
  • ICE: Same as NORMAL when not powered up, and ICEPU when powered up.
  • RFNORMAL: Fires two bullets.
  • RFPU: Fires three bullets.
  • RF: Same as RFNORMAL when not powered up, and RFPU when powered up.
  • TOASTER: Fires one bullet with its speed partially determined by how much fastfire the player has.
  • PEPPERSPRAY: Fires two pepper spray bullets.
  • GUN8: Same as NORMAL if jjAllowsFireball is true (but ignoring this weapon's gradualAim property), and PEPPERSPRAY when it's off.
Any assignments of GUN8 or PEPPERSPRAY should be shared between clients and server to prevent kicking for invalid gun use.
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), WEAPON::POPCORN (fires continously and respects the player's fastfire property, but at a capped minimum rate of fire), and WEAPON::CAPPED (like NORMAL, but prevents players from shooting faster than allowed by their fastfire property).

class jjSTREAM

The jjSTREAM class is used to save/transfer specific data. Its main functions are push and pop. You can create a jjSTREAM variable, and push any values onto it. Then, when you need to read from it, you pop them. jjSTREAM is a container working on FIFO basis, i.e. the variables are popped in the same order they were pushed. For example:

jjSTREAM myStream; //create an empty stream
int a = 4;
string b = "TexT";
bool c = false;
myStream.push(a); //push the int "a" onto the stream
myStream.push(b); //push the string "b" onto the stream
myStream.push(c); //push the bool "c" onto the stream
int d;
string e;
bool f;
myStream.pop(d); //the int "d" will have the same value as pushed "a"
myStream.pop(e); //the string "e" will have the same value as pushed "b"
myStream.pop(f); //the bool "f" will have the same value as pushed "c"
//now the stream is empty again.

It's very important to keep the order of removing variables from jjSTREAM the same as the order of inserting them into it, as well as keep consistency in use of variable types. Attempts to pop a variable of different type than the pushed type will not be blocked but will generally result in obtaining a wrong section of data.

As you can imagine, the potential for usage of this class as a container is fairly limited and most of the time you'd prefer to use the built-in array class instead. The real strengths of jjSTREAM show when you implement networking or file manipulation in your script.

To demonstrate file manipulation capabilities of jjSTREAM, let's assume your single player campaign requires that various items be carried between levels. JJ2+ doesn't currently offer means of transfering custom data between separate levels, but it allows you to save a temporary file in one level that you can then load in another. The following code shows possible implementation of functions that would do so.

array<string> items; //In this example we assume items are stored as text strings, representing their names. However, any other basic type, such as int identifiers, would work as well.
bool saveItems() {
	jjSTREAM file;
	file.push(items.length()); //The first uint in the file will represent the number of items.
	for (uint i = 0; i < items.length(); i++) {
		file.push(items[i]); //The rest of the file will consist of elements of the items array.
	}
	return file.save("items.asdat"); //Return whether successful.
}
bool loadItems() {
	jjSTREAM file("items.asdat");
	uint length;
	if (file.pop(length)) { //Pop the first uint, assumed to be the number of items. If anything fails, abort the entire operation.
		items.resize(length);
		bool success = true;
		for (int i = 0; i < items.length() && success; i++) {
			success = file.pop(items[i]); //Retrieve consecutive elements of the items array from the file in the same order they were saved.
		}
		return success;
	}
	return false;
}

The exact way you write and read files will of course vary depending on what information you need to save, but you can expect it to be generally based around this scheme.

Networking works in a similar way as file manipulation but has its own quirks, especially due to the distinction between a server and clients. We'll demonstrate it using a fragment of a hypothetical script that allows players to press buttons and pull levers, each time informing all players in the server about the performed action. We'll think of a button as of something that doesn't have a state, and each time it's pressed it performs the same action. On the other hand, a lever will be something that at any given time can be either on or off. The fragment assumes that all buttons and levers in the level already have unique IDs assigned to them elsewhere, that are the same for the server and all clients.

enum packet_type {packet_button, packet_lever, packet_all_levers}; //We enumerate possible packet types. You will probably want to do this in almost every script that uses streams, as it simplifies the process of giving them more than one purpose. In our case, we want packets that inform about pressing buttons, pulling levers, and a special packet that informs newly joining clients about states of all levers in the level.
array<bool> leverStates;
void performButtonAction(jjPLAYER@ player, int buttonID) {
   //...
}
void sendButtonPacket(int8 playerID, int buttonID, int skippedClientID) {
   jjSTREAM packet;
   packet.push(uint8(packet_button));
   packet.push(playerID);
   packet.push(buttonID);
   jjSendPacket(packet, -skippedClientID);
}
void pressButton(int8 playerID, int buttonID) {
   performButtonAction(jjPlayers[playerID], buttonID);
   sendButtonPacket(playerID, buttonID, 0);
}
void sendLeverPacket(int8 playerID, int leverID, int skippedClientID) {
   jjSTREAM packet;
   packet.push(uint8(packet_lever));
   packet.push(playerID);
   packet.push(leverID);
   packet.push(leverStates[leverID]);
   jjSendPacket(packet, -skippedClientID);
}
void pullLever(int8 playerID, int leverID) {
   leverStates[leverID] = !leverStates[leverID];
   sendLeverPacket(playerID, leverID, 0);
}
void onLevelLoad() {
   if (!jjIsServer) {
      jjSTREAM packet;
      packet.push(uint8(packet_all_levers));
      jjSendPacket(packet); //When the level loads for clients, they request status of all levers from the server by sending a packet_all_levers packet.
   }
}
void onReceive(jjSTREAM &in packet, int clientID) {
   uint8 type;
   packet.pop(type);
   if (jjIsServer) {
      switch (type) {
         case packet_button: //A client reports having pressed a button.
            {
               int8 playerID;
               packet.pop(playerID);
               if (jjPlayers[playerID].clientID == clientID) { //Check if the received player ID really belongs to the client who sent the packet. Otherwise we might have to do with a hacking attempt.
                  int buttonID;
                  packet.pop(buttonID);
                  performButtonAction(jjPlayers[playerID], buttonID);
                  sendButtonPacket(playerID, buttonID, clientID);
               }
            }
            break;
         case packet_lever: //A client reports having pulled a lever.
            {
               int8 playerID;
               packet.pop(playerID);
               if (jjPlayers[playerID].clientID == clientID) { //Check if the received player ID really belongs to the client who sent the packet. Otherwise we might have to do with a hacking attempt.
                  int leverID;
                  packet.pop(leverID);
                  packet.pop(leverStates[leverID]);
                  sendLeverPacket(playerID, leverID, clientID);
               }
            }
            break;
         case packet_all_levers: //A client requests states of all levers.
            {
               jjSTREAM response;
               response.push(uint8(packet_all_levers));
               for (uint i = 0; i < leverStates.length(); i++) {
                  response.push(leverStates[i]);
               }
               jjSendPacket(response, clientID);
            }
            break;
         //A default case might also be in place to react to hacking attempts. To simplify the example it was not included.
      }
   } else {
      switch (type) {
         case packet_button: //The server informs about a button having been pressed.
            {
               int8 playerID;
               int buttonID;
               packet.pop(playerID);
               packet.pop(buttonID);
               performButtonAction(jjPlayers[playerID], buttonID);
            }
            break;
         case packet_lever: //The server informs about a lever having been pulled.
            {
               int8 playerID;
               int leverID;
               packet.pop(playerID);
               packet.pop(leverID);
               packet.pop(leverStates[leverID]);
            }
            break;
         case packet_all_levers: //The server responds to request for all lever states.
            for (uint i = 0; i < leverStates.length(); i++) {
               packet.pop(leverStates[i]);
            }
            break;
      }
   }
}

The example may seem arbitrary, but it contains all types of packets you want to send most of the time. Generally all network actions you will want to take are: report an event occurred (analogous to buttons), report a state of something changed (analogous to levers), or request state of the entire level when you join it (analogous to packet_all_levers in the above example).

jjSTREAM()
jjSTREAM(const string &in filename)
Constructs an instance of the class. The default constructor creates an empty stream. If filename is provided, the stream will be built from the file's contents.
jjSTREAM& operator = (const jjSTREAM &in stream)
Assigns contents of one stream to another and returns a reference to self.
void clear()
Clears content of the stream leaving it empty.
bool discard(uint count)
Discards count bytes from the front of the stream. Returns whether successful, i.e. whether the stream contained sufficiently many bytes to perform the operation. If the operation fails, the stream is left in an undefined state.
bool get(const string &out value, uint count = 1)
bool get(const jjSTREAM &out value, uint count = 1)
Reads count bytes from the front of the stream, removes them and places their contents into value. Returns whether successful, i.e. whether the stream contained sufficiently many bytes to perform the operation. If the operation fails, the stream is left in an undefined state.
bool getLine(string &out value, const string &in delim = '\n')
Reads bytes from the stream, interpreting them as text characters, until it finds the character sequence delim. The read bytes are placed into value. All read characters are removed from the stream, including delim; however, the resulting value will not contain delim. Returns true on success and false if delim was not found anywhere in the stream. In the latter case, the behavior is still well-defined and all contents of the stream are placed into value, making the stream empty.
uint getSize() const
Returns size of the stream in bytes.
bool isEmpty() const
Returns whether the stream is empty, i.e. whether getSize() == 0.
bool pop(T &out value)
Pops value from the stream into value, causing value to be filled and reducing the size of the stream. Returns true on success and false on failure, i.e. if the stream didn't have sufficiently many bytes to perform the operation. Supported types are all primitive types (int and its size variations, uint and its size variations, bool, float and double) as well as strings and other jjSTREAMs. The method is designed to allow obtaining data previously inserted using push (read its documentation for details).
bool push(T value)
bool push(const string &in value)
bool push(const jjSTREAM &in value)
Pushes value onto the stream. Returns whether successful, which is always true. Supported types are all primitive types (int and its size variations, uint and its size variations, bool, float and double) as well as strings and other jjSTREAMs. The method is designed to allow easily obtaining data in the future by calling pop. Different data types are assigned different amount of space of the stream. The following rules apply:
  • 1 byte for bool, int8 and uint8
  • 2 bytes for int16 and uint16
  • 4 bytes for int, uint and float
  • 8 bytes for int64, uint64 and double
  • 4 + X bytes for strings and streams, where X is the size of the string or stream
In that last case, the initial 4 bytes are a uint containing the size, whereas the rest are the contents of the string or stream. This is necessary in order for pop to be able to recognize how many bytes to read, however it may be undesirable for other purposes, which is why additional functions exist for strings and streams: write and get.
bool save(const string &in filename) const
Saves contents of the stream to file filename and returns whether successful.
There are several limits to this method. The file cannot be saved in any directory other than the default one, which is the directory containing the executable (for local games and servers) or cache (for clients). File extension has to be ".asdat" and if any other or no extension is provided in the string, it will be replaced. Additionally, scripts downloaded from a server can only save up to 16 files on the computer of a client; they are, however, allowed to overwrite files they saved previously.
bool write(const string &in value)
bool write(const jjSTREAM &in value)
Appends bytes of value at the end of the stream. In contrast to push, this method doesn't store the size of the provided string or stream, thus making it impossible to pop, but it can still be obtained with get if the size is known or getLine if it ends with a defined delimiter. Returns whether successful, which is always true.

class jjBEHAVIOR

jjBEHAVIOR is a very simple class that exposes no properties or methods (other than operator overloads) to users. Its sole purpose is to be used as the behavior property of jjOBJ instances and occasionally an argument of their behave method. At its core jjBEHAVIOR contains only one value, which is either a BEHAVIOR::Behavior constant or an AngelScript behavior function handle. In terms of C-based programming languages, it can be considered a union.

As it's based around implicit conversions, you rarely have to keep existence of this class in mind.

jjBEHAVIOR(const BEHAVIOR::Behavior &in behavior = BEHAVIOR::INACTIVE)
Constructs an instance of the class and assigns behavior as its value.
jjBEHAVIOR& operator = (const jjBEHAVIOR &in behavior)
jjBEHAVIOR& operator = (BEHAVIOR::Behavior behavior)
jjBEHAVIOR& operator = (jjVOIDFUNCOBJ@ behavior)
jjBEHAVIOR& operator = (jjBEHAVIORINTERFACE@ behavior)
Assigns behavior to the instance and returns a reference to self.
bool operator == (const jjBEHAVIOR &in behavior) const
bool operator == (BEHAVIOR::Behavior behavior) const
bool operator == (const jjVOIDFUNCOBJ@ behavior) const
Compares the instance with behavior and returns whether they are the same.
explicit operator BEHAVIOR::Behavior () const
If the object represents a valid BEHAVIOR::Behavior constant, returns that constant, otherwise returns BEHAVIOR::INACTIVE. Usage for a sample jjBEHAVIOR instance behavior:
BEHAVIOR::Behavior result = BEHAVIOR::Behavior(behavior);
explicit operator jjVOIDFUNCOBJ@ () const
If the object represents a behavior function handle, returns that handle. Otherwise, if the object represents a handle to an object implementing jjBEHAVIORINTERFACE, returns a delegate of that object's onBehave method. If neither of those is the case, i.e. if the object represents a BEHAVIOR::Behavior constant, returns null. Usage for a sample jjBEHAVIOR instance behavior:
jjVOIDFUNCOBJ@ result = cast<jjVOIDFUNCOBJ>(behavior);
explicit operator jjBEHAVIORINTERFACE@ () const
If the object represents a handle to an object implementing jjBEHAVIORINTERFACE, returns a handle to that object. Otherwise returns null. Usage for a sample jjBEHAVIOR instance behavior:
jjBEHAVIORINTERFACE@ result = cast<jjBEHAVIORINTERFACE>(behavior);

class jjRNG

Every instance of this class represents a pseudo-random number generator. Usually pseudo-random number generation can be handled by the simple function jjRandom, however, it may be found insufficient for some applications. In particular, because jjRandom may be called by any running script and internally by the game itself, it cannot be relied on to always provide the same sequence of numbers on consecutive calls for a given seed. In constrast, each instance of this class contains an independent internal state, allowing the sequence of call results to remain the same for the same seed and uninfluenced by external factors. In particular, if two instances of jjRNG initially compare equal, i.e. have the same internal state, and the call operator of each of them is invoked exactly once, then it is guaranteed that the returned values of both invocations also compare equal, and both instances still compare equal afterwards.

jjRNG(uint64 seed = 5489)
Constructs an instance of the class with the provided seed.
uint64 operator () ()
Advances the generator's state and returns the generated value.
jjRNG& operator = (const jjRNG &in other)
Copies the internal state of another instance to this instance.
bool operator == (const jjRNG &in other) const
Tests equality of two instances. Two instances compare equal if their internal states are identical.
void seed(uint64 value = 5489)
Sets the current state of the generator based on value.
void discard(uint64 count = 1)
Advances the generator's state by count. The effect is equivalent to invoking the call operator count times and discarding the returned values, but calculated more efficiently.

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.

jjPAL()
Constructs a default instance of the class. The resulting palette has all colors set to black.
jjPAL& operator = (const jjPAL &in palette)
Copies contents of one palette to another. Returns a reference to self.
bool operator == (const jjPAL &in palette) const
Compares two palettes and returns true if all pairs of fields on corresponding indexes are identical or false otherwise.
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)
void fill(jjPALCOLOR color, uint8 start, uint8 length, float opacity = 1.0)
void fill(jjPALCOLOR color, float opacity = 1.0)
Replaces a series of length colors beginning at start with the color red,green,blue or, in the later 2 overloads, color. 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.
uint8 findNearestColor(jjPALCOLOR color) const
Returns index of the palette entry whose components differ the least from those of the provided color. Ensures the result has no special treatment by never returning values from the range of 0-15.
void gradient(uint8 red1, uint8 green1, uint8 blue1, uint8 red2, uint8 green2, uint8 blue2, uint8 start = 176, uint8 length = 32, float opacity = 1.0)
void gradient(jjPALCOLOR color1, jjPALCOLOR color2, 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 (or color1) and ending with red2,green2,blue2 (or color2). 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 Palette Suite, 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

jjPALCOLOR()
jjPALCOLOR(uint8 red, uint8 green, uint8 blue)
Constructs an instance of the class. The default constructor results in black color, i.e. one having all component values set to 0. The other constructor builds an instance with the provided RGB values.
jjPALCOLOR& operator = (const jjPALCOLOR &in color)
Does a component-wise assignment of one instance to another. Returns a reference to self.
bool operator == (const jjPALCOLOR &in color) const
Performs a component-wise comparison and returns true if all components of both instances have the same values or false otherwise.
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.
void swizzle(COLOR::Component red, COLOR::Component green, COLOR::Component blue)
Swaps values of selected components of the color. The value of the red component will be replaced with that specified by parameter red, the value of the green component with that specified by green and the value of blue with that specified by blue. Values accepted by the parameters are COLOR::RED, COLOR::GREEN and COLOR::BLUE.

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 jjLAYER::hasTiles 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.

Most of the methods below take a pair of parameters SPRITE::Mode mode and uint8 param. A full list of options and their explanations can be found in the appendix, but briefly, the sprite modes control how the basic individual pixel colors of the image (sprite, rectangle, whatever) you're drawing will turn out as colors on the screen. For example, consider drawing the first frame of the powered-up toaster animation:

bool onDrawHealth(jjPLAYER@ play, jjCANVAS@ screen) {
	screen.drawSprite(50, 50, ANIM::AMMO, 0, 0, 1, SPRITE::NORMAL, 123); //if using SPRITE::NORMAL, param's value doesn't actually matter; 123 is as good as any
	return false;
}

The method as called above uses SPRITE::NORMAL (the default value), which draws each pixel of the sprite exactly as it appears in anims.j2a, but you have plenty of other options. SPRITE::PALSHIFT, 16 will draw the sprite as pink instead of blue (16 colors farther down in the palette). SPRITE::BRIGHTNESS, 64 will draw the sprite as dark blue (64 is half of 128, which for SPRITE::BRIGHTNESS is the normal brightness value) instead of bright blue. SPRITE::BLEND_LUMINANCE, 255 (the BLEND_ sprite modes use 255 to signify complete opacity) will draw each pixel of the sprite using the colors (but not the brightness) of the pixel that was already there on the screen. JJ2's commonest sprite modes are NORMAL, TRANSLUCENT, FROZEN, PLAYER, and (for coloring text characters) PALSHIFT, but JJ2+ provides you with many more options, so try them all! Note also that unlike in previous releases of JJ2+, your choice of SPRITE::Mode does not determine whether sprites may be flipped horizontally or not.

Alphabetical order is defied slightly while listing the methods of jjCANVAS, in order to present the most fundamental method first and then its variants afterwards.

void drawSprite(int xPixel, int yPixel, int setID, uint8 animation, uint8 frame, int8 direction = 0, SPRITE::Mode mode = SPRITE::NORMAL, uint8 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 integer 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. For an example, to draw a broken bird cage (9-5-1 in Jazz Sprite Dynamite), you would write ANIM::BIRD, 4, 0.
The direction parameter determines which direction the sprite will be facing; values 0 to 63 will not flip the sprite, -64 to -1 will flip it horizontally, 64 to 127 will flip it vertically, and -128 to -65 will flip it both horizontally and vertically. Alternatively to integer values, you can use constants made for this very purpose that belong to the SPRITE::Direction enum: SPRITE::FLIPNONE, SPRITE::FLIPH, SPRITE::FLIPV, SPRITE::FLIPHV.
Note again that unlike in past releases of JJ2+, the value of mode has no effect on whether the sprite may be flipped.
void drawSpriteFromCurFrame(int xPixel, int yPixel, uint sprite, int8 direction = 0, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)
The same as drawSprite, but takes a single sprite parameter instead of one each for Set, Animation, and Frame. The name references the curFrame property of jjOBJ and jjPLAYER, whose real significance is as an index to jjAnimFrames[].
Internally any draw*Sprite method is actually a call to the corresponding draw*SpriteFromCurFrame method, with the following formula applied to find the curFrame value: (jjAnimations[jjAnimSets[setID].firstAnim + animation].firstFrame + frame) % jjAnimations[jjAnimSets[setID].firstAnim + animation].frameCount
void drawResizedSprite(int xPixel, int yPixel, int setID, uint8 animation, uint8 frame, float xScale, float yScale, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)
void drawResizedSpriteFromCurFrame(int xPixel, int yPixel, uint sprite, float xScale, float yScale, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)
Draws a single resized sprite frame at xPixel,yPixel. This swaps out drawSprite's direction argument with a pair of float arguments, xScale and yScale, which determine how much the sprite is enlarged (or shrunken) on each axis. For example, a 32x32 sprite drawn with xScale 3 and yScale 1.5 would be drawn across a 96×48 area. Making one or both scale values negative will cause the sprite to be flipped horizontally and/or vertically. No anti-aliasing will be performed while resizing the sprite.
void drawRotatedSprite(int xPixel, int yPixel, int setID, uint8 animation, uint8 frame, int angle, float xScale = 1, float yScale = 1, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)
void drawRotatedSpriteFromCurFrame(int xPixel, int yPixel, uint sprite, int angle, float xScale = 1, float yScale = 1, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)
Draws a single resized and rotated sprite frame at xPixel,yPixel. This carries over the xScale and yScale arguments from drawResizedSprite, and adds a new angle parameter with the same 0-1023 domain as jjSin and jjCos. (Numbers outside the domain will be seemlessly moduloed.) An angle of 0 will not rotate the sprite at all. Without getting into matrix math, you can think of the sprite as being resized before it is rotated, which in most cases is exactly what you'd want. For example, a 32×32 sprite drawn with angle 256, xScale 2, and yScale 1 would be drawn across an area of 32×64, not 64×32.
Unlike in previous releases of JJ2+, any sprite at all may be rotated. However, rotated sprites should be no larger (before transformations are applied) than 128 pixels wide or 256 pixels high. The results of drawing larger sprites are, for now, undefined while we decide whether this restriction should or can be removed.
Any sprite that drawSprite can draw, drawResizedSprite can draw in the same way by setting xScale and yScale each to 1. And any sprite that drawResizedSprite can draw, drawRotatedSprite can draw unless it's greater than 128×256, by setting angle to 0. As you might imagine, though, the simpler methods (besides being quicker to write) run faster than the more complicated methods, so you might as well use them when you can get away with it.
void drawSwingingVineSpriteFromCurFrame(int xPixel, int yPixel, int sprite, int length, int curvature, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)
Draws a single sprite frame at xPixel,yPixel in the style of a Swinging Vine object. The parameters named length and curvature correspond respectively to properties var[1] (normally always 128) and var[2] of swinging vines; if the sprite is shorter than the length value, its pixels will be repeated again from the top as necessary.
For boring internal code reasons, this method expects that sprites will have no transparent pixels. Instead, to tell this method not to draw an individual pixel from the frame, its value must be 128 instead of 0. Using normal transparent pixels instead will cause the sprite to be drawn as a jumbled mess.
void drawPixel(int xPixel, int yPixel, uint8 color, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)
Changes the color of the pixel at xPixel,yPixel into the palette color corresponding to index color. If you are drawing anything more than a handful of pixels at a time, you should consider creating a new sprite of your own using jjPIXELMAP, and then drawing that sprite as a single unit with some other drawing method.
void drawRectangle(int xPixel, int yPixel, int width, int height, uint8 color, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)
Draws a rectangle with one of its vertices at xPixel,yPixel of specified width and height in the palette color corresponding to index color.
void drawString(int xPixel, int yPixel, const string &in text, STRING::Size size = STRING::SMALL,
STRING::Mode mode = STRING::NORMAL, uint8 param = 0)
void drawString(int xPixel, int yPixel, const string &in text, const jjANIMATION &in animation, STRING::Mode mode = STRING::NORMAL, uint8 param = 0)
void drawString(int xPixel, int yPixel, const string &in text, STRING::Size size, const jjTEXTAPPEARANCE &in appearance, uint8 param1 = 0, SPRITE::Mode spriteMode = SPRITE::PALSHIFT, uint8 param2 = 0)
void drawString(int xPixel, int yPixel, const string &in text, const jjANIMATION &in animation, const jjTEXTAPPEARANCE &in appearance, uint8 param1 = 0, SPRITE::Mode spriteMode = SPRITE::PALSHIFT, uint8 param2 = 0)
Draws a string of characters of your choice at position (xPixel, yPixel) on the screen. Unique size values are SMALL, MEDIUM, and LARGE, or you can also specify a different jjANIMATION to grab character sprites from, e.g. one from a custom .j2a file. Whichever animation is chosen should use the same order (and, ideally, number) of frames/characters as the standard font animations, so frame 16 is “0”, frame 33 is “A”, and so on. If it helps, you may think of the overloads with size arguments as shorthand for jjAnimations[jjAnimSets[ANIM::FONT].firstAnim + size].
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.
Most of these modes use alignment of STRING::DEFAULT, meaning that values of xPixel higher than 0x4000 have a special meaning in drawing strings, most noticably changing their alignment on the screen. View documentation for jjTEXTAPPEARANCE::align for more detail.
In the second prototype of the function, arguments mode and param are split into four arguments appearance, param1, spriteMode and param2 for more customizability: appearance controls several different aspects of text discussed in the jjTEXTAPPEARANCE section, param1 is a multiplier applied to amplitude of text determined by appearance, spriteMode is the sprite mode the characters will be drawn in, and param2 is the sprite mode parameter. Notice that the choice of spriteMode will also determine the effect of using special characters such as # and | in text (if appearance allows them to take effect).
void 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.

class jjLAYER

As suggested by the name, jjLAYER is the class JJ2+ uses to represent layers of tiles. Normally any given level contains exactly eight jjLAYER objects, numbered 1–8 in JCS. (JCS calls these, in order from front to back, "Foreground layer #2," "Foreground layer #1," "Sprite foreground layer," "Sprite layer," "Background layer #1," "Background layer #2," "Backgound layer #3," and "Background layer." However, common parlance and therefore also this readme call them simply "Layer 1" through "Layer 8.")

In any level where you do not plan on changing which (or in what order) tile layers are drawn to the screen, you rarely need to worry about the jjLAYER class, because all its major properties have indexed global properties serving as shortcuts to them. For example, to get the width in tiles of Layer 4 (256 by default in JCS), you can use jjLayerWidth and write jjLayerWidth[4] instead of jjLayers[4].width. (Said shortcuts may even be somewhat faster to execute, although you shouldn't need to worry about that.) But if you make any calls to jjLayerOrderSet, particularly in conjunction with one or more jjLAYER constructors or else jjLayersFromLevel, knowing the class's properties and methods should be helpful.

jjLAYER(const jjLAYER &in filename)
Copies all properties (including tile layout) of another jjLAYER.
jjLAYER(uint layerWidth, uint layerHeight)
Creates a new jjLAYER and sets height to layerHeight and both width and widthReal to layerWidth. The tile map will be empty, which to say, entirely composed of word 0 (0,0,0,0), but hasTileMap and hasTiles will be set to true anyway in preparation for your filling in part of the tile map with tiles. Unlike JCS, JJ2+ will not do the work of guaranteeing that layers can tile horizontally for you, so if you plan to set to true your constructed layer's tileWidth property, you should make sure to use a layerWidth divisible by four.
If either layerWidth or layerHeight is an invalid value (i.e. less than 1 or greater than 1023), a default layer size of 8×8 will be used instead.
const bool hasTileMap
Layers that contain no tiles at all (other than tile 0) in JCS are saved as defective layers that define size, speed, etc. properties but do not include actual tile maps, meaning that they can never be successfully drawn to the screen. This property is therefore false for those defective layers and true for all other layers.
bool hasTiles
Simply, whether JJ2 should draw the layer or not. (Defaults to the same value as hasTileMap.) In addition to the layer's tiles, this also controls whether the layer's onDrawLayer# hook, if any, can be called (see jjCANVAS section), but does not impact sprites drawn by the jjDrawSprite family no matter the value of their layerZ arguments. If hasTileMap is false but hasTiles is true, the onDrawLayer# hook will still work but no tiles will be drawn because there will be no tiles to draw.
As an alternative to setting hasTiles to false, you can also exclude a layer from the drawing order altogether using jjLayerOrderSet.
const int height
The height of the layer in tiles.
bool limitVisibleRegion
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.
SPRITE::Mode spriteMode
uint8 spriteParam
The sprite mode and sprite mode parameter used to draw tiles in this layer. The default values for all layers are SPRITE::NORMAL and 0, but other possible SPRITE::Mode constants appear with explanations in the appendix below. Specifically, all tiles in this layer whose jjTileType values are 0 (default), 4 (caption), or 5 (heat effect) will be drawn using this sprite mode and parameter, while tile types 1, 2, 3, and 6 override this with SPRITE::TRANSLUCENTTILE, SPRITE::NORMAL, SPRITE::INVISIBLE, and SPRITE::FROZEN respectively.
These properties affect only the tiles naturally present in this layer's tile map, not any calls made to any jjCANVAS methods on an onDrawLayer hook attached to this layer. As with tile types, textured background layers ignore sprite modes altogether.
int rotationAngle
Not settable in JCS: the offset of the angle at which this layer rotates while a player is stoned, with a domain of 0–1023 to be passed to jjSin and jjCos.
Layers 1–8 have values -512, -256, 0, 0, 0, 256, 512, and 768, respectively. A script-created jjLAYER will default to 0.
int rotationRadiusMultiplier
Not settable in JCS: the multipler of the distance this layer is offset from its center while rotating while a player is stoned. A value of 0 means the layer will not rotate at all; jjLayers[5].rotationRadiusMultiplier = 0; might therefore be a useful line of code in levels that give Layer 5 speed values to match Layer 4's.
Layers 1–8 have values 4, 3, 0, 0, 2, 2, 1, and 1, respectively. A script-created jjLAYER will default to 0.
bool tileHeight
Whether the layer should be vertically tiled, as seen in the JCS Layer Properties window.
bool tileWidth
Whether the 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 and widthReal are not equal. 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 width
const int widthReal
The width of the layer in tiles. Normally these two values will be equal, but they may differ on layers that were saved with the "Tile Width" checkbox checked in their level editors. The reason for this is that layers are not stored as arrays of tile IDs but rather as arrays of "word" IDs, where each word is defined in the "tile cache" as a row of four adjacent tile IDs. In order to properly tile, therefore, a layer whose width is not a multiple of four is expanded by the level editor to a widthReal that is the lowest common multiple of 4 and width, e.g. 8 for 8, 36 for 9, 20 for 10, 44 for 11, and 12 for 12.
const int widthRounded
The width of the layer in "words," always equal to (jjLAYER::widthReal + 3) / 4.
float xAutoSpeed
float yAutoSpeed
The auto speed of the layer, as seen in the JCS Layer Properties window.
float xOffset
float yOffset
Not settable in JCS: constant pixel values added to the position of the layer, regardless of its speed.
float xSpeed
float ySpeed
The speed of the layer, as seen in the JCS Layer Properties window.
void generateSettableTileArea()
void generateSettableTileArea(int xTile, int yTile, int width, int height)
Makes all tiles in the range of the rectangle with its top left corner at xTile,yTile and specified width and height possible to use tileSet on without the potential consequence of changing other occurrences of the tile in the level due to tile cache. The zero-argument version of the method applies to the entire layer, or to be precise, passes 0,0 to xTile,yTile, widthReal to width, and height to height. Does nothing if hasTileMap is false.
float getXPosition(const jjPLAYER &in play) const
float getYPosition(const jjPLAYER &in play) const
Gets the last position of this layer for the specified jjPLAYER. (The results of calling this method for players for whom isLocal is false are undefined.) For example, given a jjPLAYER@ object play, you could attach water to the top of layer 7 by continually calling jjSetWaterLevel(play.cameraY - jjLayers[7].getYPosition(play), true); This takes into account everything that could possibly reposition a layer, including its speeds and offsets, the resolution and max resolution, the player's camera position, and whether the player is stoned.
Note that these values are not updated for layers while they are not drawn to the screen on account of having been left out in the last call to jjLayerOrderSet.
bool maskedHLine(int xPixel, int lineLength, int yPixel) const
Returns true if any pixel from xPixel,yPixel to xPixel+lineLength,yPixel is masked. Returns false if hasTileMap is false.
bool maskedPixel(int xPixel, int yPixel) const
Returns true if pixel xPixel,yPixel is masked. Returns false if hasTileMap is false.
int maskedTopVLine(int xPixel, int yPixel, int lineLength) const
If any pixel from xPixel,yPixel to xPixel,yPixel+lineLength is masked, 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. Returns 0 if hasTileMap is false.
bool maskedVLine(int xPixel, int yPixel, int lineLength) const
Returns true if any pixel from xPixel,yPixel to xPixel,yPixel+lineLength is masked. Returns false if hasTileMap is false.
void setXSpeed(float newspeed, bool newSpeedIsAnAutoSpeed)
void setYSpeed(float newspeed, bool newSpeedIsAnAutoSpeed)
Changes the X or Y speed. Unlike the basic properties like xSpeed and yAutoSpeed, these functions will ensure that the layer remains in the same position it was before its speeds were changed, and can therefore be much more useful.
uint16 tileGet(int xTile, int yTile) const
Returns the current tile at location xTile,yTile. If the tile is an animated tile, this function will return the tile ID for that animated tile instead of the current frame. Returns 0 if hasTileMap is false.
uint16 tileSet(int xTile, int yTile, uint16 newTile)
Sets the current tile at location xTile,yTile to be newTile. The same change will be applied to all instances of the same four-tile word that appear elsewhere in the level unless the tiles had had generateSettableTileArea used on them prior to the change. Returns newTile, or 0 (and does nothing) if hasTileMap is false.

class jjTEXTAPPEARANCE

jjTEXTAPPEARANCE is a structure used as an argument in functions that draw or otherwise operate on text, determining its traits such as alignment, treatment of special symbols, and amplitude of movement. Functions that currently rely on this class are jjCANVAS::drawString, jjDrawString, and jjGetStringWidth.

The majority of jjTEXTAPPEARANCE properties are of type STRING::SignTreatment and determine whether a symbol should be hidden, displayed normally, or have special behavior. These three treatments correspond respectively to constants HIDESIGN, DISPLAYSIGN and SPECIALSIGN.

jjTEXTAPPEARANCE()
jjTEXTAPPEARANCE(STRING::Mode mode)
Constructs an instance of the class. The default constructor creates an instance with 0 horizontal and vertical amplitude, spacing equal to 1, default alignment, and no special treatment for any symbols. The constructor from STRING::Mode uses values specific for that mode instead, according to the following table, where "H" and "D" and "S" stand for HIDESIGN, DISPLAYSIGN, and SPECIALSIGN respectively:
align@^#\n|§~skipInitialHashspacingxAmpyAmp
NORMAL DEFAULTDSDHSSDtrue 100
DARK DEFAULTDSDHHSDtrue 100
RIGHTALIGN RIGHT DSDHHHHfalse100
BOUNCE DEFAULTDSDHHHHfalse001
SPIN DEFAULTSSSHHSSfalse111
PALSHIFT DEFAULTDSDHHHDfalse100
No matter the constructor, monospace will always be false unless set manually.
jjTEXTAPPEARANCE& operator = (STRING::Mode mode)
Assigns text display traits specific to mode to the instance. Returns a reference to self.
STRING::Alignment align
Horizontal alignment of text. This can take values of DEFAULT, LEFT, CENTER or RIGHT.
Whereas LEFT, CENTER and RIGHT should be self-explanatory, DEFAULT is the value JJ2 used for all strings originally, defined as follows: if xPixel is less than 0x4000, the string will have its left side aligned to the left side of the screen or layer. If in the range of 0x4000 to 0xFFFF, the string will be center-aligned relative to 0x8000, so for instance 0x8020 would align the center of the string to 0x20 pixels right of the center of the screen. Finally, if xPixel is greater than or equal to 0x10000, it will be drawn with its right side aligned to the right side of the screen instead, with higher values moving farther left. Note that these special cases currently apply only for drawing to the screen; the use of values of 0x4000 or higher for onDrawLayer# hooks is currently undefined.
STRING::SignTreatment at
Treatment of the at ('@') character. If it's SPECIALSIGN, at will begin a new line of text 20 pixels below the previous one.
STRING::SignTreatment caret
Treatment of the caret ('^') character. If it's SPECIALSIGN, caret will be displayed as infinity signs.
STRING::SignTreatment hash
Treatment of the hash ('#') character. If it's SPECIALSIGN, characters following hash will use a pattern of colors (or other parameters depending on sprite mode of the string).
bool monospace
Usually strings achieve naturally looking spacing between characters by placing the next character at character width + spacing relative to the previous one. When this property is set to true, character width is disregarded, and placement is determined only by spacing, i.e. is constant throughout the string (unless modified by special function of section sign). Because, by default, spacing is a low value, you may want to modify it if you use this property. This property is compatible with section, although the result of changing the spacing partway through a string may look somewhat bizarre.
STRING::SignTreatment newline
Treatment of the line feed character (created by the escape sequence '\n' in AngelScript). If it's either DISPLAYSIGN or SPECIALSIGN, line feed will begin a new line of text below the previous one, with vertical spacing varying depending on font size.
STRING::SignTreatment pipe
Treatment of the pipe ('|') character. If it's SPECIALSIGN, pipe will change text color (or other parameter depending on sprite mode of the string) of the following characters.
STRING::SignTreatment section
Treatment of the section sign ('§') character. If it's SPECIALSIGN, section sign will use ASCII code of the directly following character to determine spacing used for further text.
bool skipInitialHash
Whether or not to ignore the first character of a string if it's a hash ('#'). The ignored hash will not be displayed or otherwise affect the string. If the string is preceded by more than one hash, all but the first one will be displayed.
This property is provided mainly for compatibility with several STRING::Mode values, as it's used by JJ2 wherever strings are meant to be conditionally colored, such as menu items.
int spacing
Default spacing between characters, in pixels. If section is set to SPECIALSIGN, this value only applies until the first section sign character is found, as the effect of the section sign is absolute rather than relative to this property.
STRING::SignTreatment tilde
Treatment of the tilde ('~') character. If it's SPECIALSIGN, tilde will cancel the special effect of hash or, if it's already inactive, it will change the color (or other parameter depending on sprite mode) to default.
int xAmp
int yAmp
Horizontal and vertical amplitudes of text movement.

class jjANIMSET

JJ2's thousands of sprites and animations are accessible through AngelScript as a three-tiered system of classes and matching global arrays. If you're familiar with the contents of anims.j2a from Jazz Sprite Dynamite, that should make it easier to understand how this works; most simply, the three tiers of classes/arrays (jjANIMSET, jjANIMATION, jjANIMFRAME) correspond to the three scrollbars (Set ID, Animation, Frame #) in JSD, although JSD uses 1-indexed numbers for everything and JJ2+ does not. To take a concrete example, the sprite frame used by the Cake pickup is Set ID=68, Animation=13, and Frame #=1 in JSD. (Or Set ID=72 in 1.24.) In AngelScript terms, this is jjAnimFrames[jjAnimations[jjAnimSets[ANIM::PICKUPS].firstAnim + 12].firstFrame].

An image may help to explain the three arrays. The lowest level, jjAnimFrames, is a long series of individual animation frames (sprites) with some basic properties for setting the hotspots, default transparency, and the like. (To edit the actual sprite image, you'll need the jjPIXELMAP class described below.) Individual frames have no idea where they fit in any given animation; that is handled by the jjAnimations array, each entry in which defines a single animation by its starting frame (firstFrame) and number of frames (frameCount). Finally the jjAnimSets array points you to the first animations of each anim set, and additionally carries a few methods for creating new anim sets either from scratch or from specific .j2a files.

A few examples should make clearer the three arrays' (and classes') uses and relationships. As seen above, the twenty animations and many corresponding frames of ANIM::BIRD are always loaded immediately before the ninety-five animations and many frames of ANIM::PICKUPS. An animation is essentially nothing more than a first frame and a frame count. So if you wanted Extra Life pickups to use the dead bird animation for some reason, there are (at least) three ways to do that:

	#1
jjObjectPresets[OBJECT::EXTRALIFE].curAnim = jjAnimSets[ANIM::BIRD].firstAnim + 19;
	#2
jjANIMATION@ extraLifeAnimation = jjAnimations[jjAnimSets[ANIM::PICKUPS].firstAnim];
extraLifeAnimation.frameCount = 1;
extraLifeAnimation.firstFrame -= 1;
	#3
jjAnimations[jjAnimSets[ANIM::PICKUPS].firstAnim] = jjAnimations[jjAnimSets[ANIM::BIRD].firstAnim + 19];

Crucially, none of those options would have any effect on what actual birds looked like after being roasted; any frame may be used in any number of animations. Moreover, the boundary lines between animations are completely arbitrary. Consider the following code:

jjAnimations[jjAnimSets[ANIM::PICKUPS].firstAnim].firstFrame -= 5;

This would cause Extra Lives (and any other objects using the same animation) to display an animation ten frames long: four frames of a bird sitting idly in place, one frame of a roasted bird, and five frames of green "1UP" text. The reason for this is that jjObjectPresets[OBJECT::EXTRALIFE].curAnim is, on level load, exactly equal to jjAnimSets[ANIM::PICKUPS].firstAnim. (Likewise, jjObjectPresets[OBJECT::APPLE].curAnim is equal to jjAnimSets[ANIM::PICKUPS].firstAnim+1, and so on.) Anytime you see a property or argument called curFrame, that means it is an index for jjAnimFrames[]. Likewise, anything called curAnim is an index for jjAnimations[], and anything called setID is an index for jjAnimSets[].

At the top level is the jjAnimSets array, which loads new series of animations and frames from various .j2a files and then points you to where they can be found in the lower two arrays. At the beginning of a level, all three animation arrays are nearly empty (jjAnimFrames and jjAnimations each contain an empty entry at index [0] for various internal purposes). Then JJ2+ runs the following line of code internally:

jjAnimSets[ANIM::FONT].load(ANIM::PLUS_FONT, "plus.j2a");

This opens up the plus.j2a file and discovers that ANIM::PLUS_FONT is exactly four animations long. It loads those animations (basically their firstFrame and frameCount properties) into jjAnimations[1–4]. Between them, those four animations include 224+224+224+2=674 frames, so those (images, hotspots, etc.) are loaded into jjAnimFrames[1–674]. And lastly, jjAnimSets[ANIM::FONT].firstAnim (and jjAnimations[jjAnimSets[ANIM::FONT].firstAnim].firstFrame) are set to 1. Next:

jjAnimSets[ANIM::JAZZ].load(); //in full: .load(ANIM::JAZZ, "anims.j2a");

ANIM::JAZZ has a massive 104 different animations (or 78 in TSF). But jjAnimations[1–4] are already in use, so JJ2+ loads the first ANIM::JAZZ animation into jjAnimations[5], and carries on the rest from there. Likewise the hundreds of individual frames are loaded into jjAnimFrames starting at jjAnimFrames[675], where the last jjANIMSET::load call left off. And jjAnimations[jjAnimSets[ANIM::JAZZ].firstAnim].firstFrame is set to 675, and the firstFrame properties of the remaining animations from ANIM::JAZZ are also adjusted accordingly.

So the process continues, loading first all the animations that every level uses, then a few level-specific ones (mostly enemies), then a few more that JJ2+ thinks might be useful. After that, you are free to mess up the existing order of frames and animations in any way you like, or simply to load or otherwise create all-new ones. Any call to jjANIMSET::load or jjANIMSET::allocate appends new entries to the ends of jjAnimFrames and jjAnimations, adjusting the animations' firstFrame properties to match their new positions, and then sets jjANIMSET::firstAnim to match. If an anim set has not yet been loaded from any .j2a file (or allocated), its firstAnim will equal 0.

And now, the list of jjANIMSET's methods and properties:

operator uint () const
Implicit conversion to uint; returns firstAnim.
uint firstAnim
ID of the first animation in the set, to be used with the jjAnimations array.
jjANIMSET@ allocate(const array<uint> &in frameCounts)
Creates space for an animation set with the number of animations equal to length of frameCounts, each with a number of frames determined by the corresponding frameCounts array element.
jjANIMSET@ load(uint fileSetID = 2048, const string &in filename = "")
Loads animation set specified by fileSetID from a j2a file specified by filename. Using the default value of fileSetID results in loading the set appropriate for the jjANIMSET it's called on. If filename is left empty, it deduces the file name from fileSetID, using either Anims.j2a or plus.j2a.

class jjANIMATION

jjANIMATION& operator = (const jjANIMATION &in animation)
Copies all properties of another jjANIMATION and returns a reference to self. This does not copy any jjANIMFRAME objects refered to by the animation; however, after the assignment, both jjANIMATION objects will have their firstFrame refer to a common jjANIMFRAME.
operator uint () const
Implicit conversion to uint; returns firstFrame.
uint firstFrame
ID of the first frame of the animation, to be used with the jjAnimFrames array.
int16 fps
This property corresponds to the "FPS" number for animations in Jazz Sprite Dynamite, but that name is only a guess, because its value is never referenced by JJ2 at all. Most commonly equals 10.
uint16 frameCount
The number of frames used by the animation.

class jjANIMFRAME

jjANIMFRAME& operator = (const jjANIMFRAME &in animFrame)
Copies all properties of another jjANIMFRAME including the picture it uses and returns a reference to self.
int16 coldSpotX
int16 coldSpotY
Horizontal and vertical position of the frame's cold spot, relative to its hot spot. It is considered to be the point at which the frame would touch the ground. It's mostly used by objects such as walking enemies and pickup crates, as well as players themselves. In particular, the jjOBJ method putOnGround, if its argument is set to true, uses coldSpotY to determine the outcome.
int16 gunSpotX
int16 gunSpotY
Horizontal and vertical position of the frame's gun spot, i.e. the point that's used to determine initial position of projectiles, relative to its hot spot. Originally this is only defined for player animations and sprites of enemies that produce projectiles.
const uint16 height
Frame height in pixels.
int16 hotSpotX
int16 hotSpotY
Horizontal and vertical position of the frame's hot spot, added to the sprite position by all drawing operations and used by collision detection, etc. These properties are analogous to the hot spot coordinates shown by Jazz Sprite Dynamite, however it should be noted that JSD inverts their values for user convenience, while JJ2 doesn't. This means that you will usually want to use negative values for these properties.
bool transparent
Whether the frame is to be drawn as translucent by default. The only AngelScript function this directly affects is the jjOBJ method draw but JJ2 objects often use this property, e.g. to draw freezer ammo pickups as translucent even though they use the same behavior as any other pickup.
const uint16 width
Frame width in pixels.
bool doesCollide(int xPos, int yPos, int direction, const jjANIMFRAME@ frame2, int xPos2, int yPos2, int direction2, bool always = false) const
Returns true if this sprite (at position xPos,yPos and direction direction) would collide with another sprite with its own specified coordinates and direction, taking both sprites' hotspot positions into account; otherwise false. If always is true, collisions will be registered even if there is only one overlapping pixel (used by bullets); the default value of false will require at least about eight common pixels (used by players), which is probably a better test in most cases. Both direction parameters will be tested for the sprites being horizontally flipped, vertically flipped, or both, and the collision detection will be carried out accordingly.

class jjTILE

The primary focus of this class is to provide an interface for interaction with animated tiles, such as obtaining or modifying their animation frames and speed. However, instances of jjTILE in fact exist for every valid tile ID - instances that correspond to static tiles are immutable, i.e. cannot be modified, while instances corresponding to animations are mutable. This distinction is reflected in the existence of two arrays that offer access to jjTILE instances: jjTiles and jjAnimatedTiles, the former of which offers read-only access to both static and animated tiles, while the latter allows read-write access, but only to animated tiles.

Because there can be always only one instance of this class corresponding to a specific tile ID, jjTILE objects cannot be constructed inside a script and may be only accessed via handles, akin to jjOBJ, jjPLAYER, etc.

uint8 fps
Animation speed in frames per second. For static tiles this is always 0.
This property is safe to modify, but it is currently undefined which animation frame will be visible immediately after the change.
const uint16 tileID
Tile ID corresponding to the instance, such that for every non-null jjTILE handle tile, tile is jjTiles[tile.tileID].
array<uint16>@ getFrames() const
Constructs and returns an array of tile IDs of animation frames of the instance. For static tiles this is an array of size 1 containing this tile's own ID.
The returned array represents animation frames after all transformations selected in animating tile properties, such as ping-pong animation or frames to wait between animation cycles. This means that the result may contain more entries than the animation as viewed in JCS.
Keep in mind that in levels edited by other means than JCS, entries of the array may themselves be animations.

class jjPIXELMAP

A straightforward, yet very powerful class that lets you manipulate most graphics in the game. A pixel map is a simple two-dimensional array of pixels, represented as uint8s, each an index for jjPAL::color. A value of 0 is transparent, 24 is (in most tilesets) red, 92 is dark purple, etc. Any pixel in the map may be edited at any time, so the only part that's complicated at all is how to get images from or into various parts of the game, which is accomplished through a handful of constructors and methods. For example, the following code renders a single tile in the tileset completely black, except for the transparent sections:

void onLevelLoad() {
	jjPIXELMAP flowerTile(692); //generate a 32×32 pixel map from tile 692 (the first flower in carrot1.j2t)
	for (uint x = 0; x < flowerTile.width; ++x)
		for (uint y = 0; y < flowerTile.height; ++y)
			if (flowerTile[x,y] != 0) //non-transparent; also note the special two-dimensional array syntax, [x,y] instead of [x][y] or something
				flowerTile[x,y] = 23; //very dark color
	flowerTile.save(692, true); //save to tile 692, whether horizontally flipped or not
}

That's pretty much all there is to it! To turn carrot1.j2t's entire flower animation black, you would wrap the above code in a for (uint tileID = 692; tileID <= 699; ++tileID) loop and replace instances of 692 with tileID. Another simple idea would be to recolor one of the shootable pole sprites (Jungle Pole, Psych Pole, etc.) to use palette indices more in line with the palette of whatever particular tileset you're using. You're also allowed to construct a jjPIXELMAP instance from a tile but save it to a jjANIMFRAME, or vice versa, or construct a jjPIXELMAP from scratch and make it into a textured background... the important thing when saving a pixel map (except to a jjANIMFRAME) is its dimensions, not how it was constructed. If a tileset has some tiles that look like enemies or pickups (many tileset conversions from other games do), by all means create pixel maps from one or more of those tiles and save them as new sprites to liven up your level. Absolutely any 2D 8-bit image may be stored and manipulated in a pixel map and, dimensions allowing, saved to most of JJ2's major drawing components.

(What jjPIXELMAP currently lacks is any way (post-constructor) to manipulate pixel data more than one byte at a time; e.g. cropping or rotating or copying sections from one pixel map to another. Since users can recreate any such operation on their own, going pixel by pixel, this was not made a priority, but if there's any particular operation you think would really save you some time, let us know and it might get added later.)

jjPIXELMAP(uint width, uint height)
Creates a totally transparent pixel map with dimensions width×height.
jjPIXELMAP(const jjANIMFRAME@ animFrame)
Creates a pixel map with the same dimensions and image as the specified jjANIMFRAME.
jjPIXELMAP(TEXTURE::Texture texture)
Creates a 256×256 pixel map using the graphics of the specified textured background texture. Consequently, all pixels will range in color from 176–207 unless you use TEXTURE::LAYER8
jjPIXELMAP(uint16 tileID = 0)
Creates a 32x32 pixel map using the graphics of the specified tile, with color 0 standing in for transparent pixels. If (tileID & TILE::VFLIPPED) != 0, the tile image will be vertically flipped while creating the pixel map.
TILE::HFLIPPED is somewhat more complicated, because JJ2 stores horizontally flipped tiles separately from their non-flipped versions. Trying to construct a jjPIXELMAP from a tileID when (tileID & TILE::HFLIPPED) != 0 will only give you the expected results if the tile's flipped version has been generated, which is typically done by flipping the tile in some layer in JCS. Otherwise you will just get a totally transparent 32x32 map.
jjPIXELMAP(uint left, uint top, uint width, uint height, uint layer = 4)
jjPIXELMAP(uint left, uint top, uint width, uint height, const jjLAYER &in layer)
Creates a pixel map with dimensions width×height, based on a rectangular area with origin left,top and size width×height in layer layer. This allows you put any number of arbitrary tiles next to each other in JCS and then create a single image out of them for use as a jjANIMFRAME or something else. See plusPixelMapEx.j2l/.j2as for several examples of this form.
uint8& operator [] (uint x, uint y)
const uint8& operator [] (uint x, uint y) const
Gets and (if non-const) sets any pixel anywhere in the pixel map, provided x is less than width and y is less than height. These are uint8s, so acceptable values are 0–255, with 0 being transparent. Caution: colors 1–9 and 246–254 may appear different in 8 bit vs. 16 bit color modes and so should be used rarely at best.
const uint height
const uint width
Dimensions of the map in pixels, set by the map's constructor
bool makeTexture()
Changes the level's current textured background to contents of the pixel map. The map has to be exactly 256×256 to allow this action and if that is not the case, the function will report failure by returning false. On success returns true.
bool save(uint16 tileID, bool hFlip = false) const
Overwrites tileset tile selected by tileID with contents of the pixel map. The map has to be exactly 32×32 to allow this action and if that is not the case, the function will report failure by returning false. On success returns true.
If (tileID & TILE::VFLIPPED) != 0, the pixel map image will be vertically flipped while saving the tile. TILE::HFLIPPED is somewhat more complicated, because JJ2 stores horizontally flipped tiles separately from their non-flipped versions. By default, and to avoid unnecessary memory usage, jjPIXELMAP::save will only save to either the regular version of the tile or the horizontally flipped version, depending on whether (tileID & TILE::HFLIPPED) != 0. By setting hFlip to true, though, you can ensure that both the regular and the horizontally flipped version of the tile will be overwritten.
The results of saving when (tileID & TILE::ANIMATED) != 0 || (tileID & TILE::RAWRANGE) == 0 are undefined.
bool save(jjANIMFRAME@ frame) const
Overwrites the image used by frame with contents of the pixel map. Returns true on success and false on failure.

class jjMASKMAP

The simpler cousin of jjPIXELMAP, used only for editing the clipping masks of tiles in the tileset. As such, it uses bool instead of uint8, is always 32×32, and can only be constructed from or saved to tiles.

jjMASKMAP(bool filled = false)
Creates a mask map where every pixel's value matches filled, either masked (true) or unmasked (false).
jjMASKMAP(uint16 tileID)
Creates a mask map using on the mask of the specified tile. All notes mentioned for jjPIXELMAP::jjPIXELMAP(uint16) apply in exactly the same way here.
bool& operator [] (uint x, uint y)
const bool& operator [] (uint x, uint y) const
Gets and (if non-const) sets any pixel anywhere in the mask map, masked pixels equalling true and unmasked pixels false.
bool save(uint16 tileID, bool hFlip = false) const
Overwrites mask of tileset tile selected by tileID with contents of the mask map. All notes mentioned for jjPIXELMAP::save apply in exactly the same way here, although you don't have to worry about dimensions, since mask maps are always 32×32.

Interfaces

JJ2+ uses interfaces as a way for you, the scriptwriter, to define your own script objects that perform partially in ways that JJ2+ expects and partially in ways that are completely up to you. In this, they can be thought of as microcosms of JJ2+'s AngelScript API in general, and in fact they follow similar naming conventions: any properties or methods of an interface that are directly accessed by internal JJ2+ code (optional or not) will begin with the letters "jj" or "on"—any other name is available for you to do as you please with.

jjBEHAVIORINTERFACE

An instance of a class implementing jjBEHAVIORINTERFACE lets you redefine nearly every aspect of how an individual jjOBJ, or set of them, behaves and interacts with the game around it. You could almost think of them as extending the jjOBJ class directly, in fact, but the API isn't quite set up right to make that possible. Still, they're very closely linked.

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. (And 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. How about a Norm Turtle that walks back and forth and changes direction every second, regardless of whether it's about to hit a wall? For this, instead of setting behavior to a BEHAVIOR::Behavior constant, we use an instance of a script-defined class that implements jjBEHAVIORINTERFACE:

void onLevelLoad() {
	jjObjectPresets[OBJECT::NORMTURTLE].behavior = TurnAround(); //jjOBJ::behavior must be set to an instance of a class, not to a class itself. This means that instead of BEHAVIOR::WALKINGENEMY or BEHAVIOR::NORMTURTLE, Norm Turtles will call the method named "onBehave" on this class instance.
	jjObjectPresets[OBJECT::NORMTURTLE].playerHandling = HANDLING::ENEMY;
}

class TurnAround : jjBEHAVIORINTERFACE { //The full list of methods for jjBEHAVIORINTERFACE is given below, but the only required one is "void onBehave(jjOBJ@)"
	void onBehave(jjOBJ@ obj) { //called once per tick, like any other behavior
		if ((jjGameTicks % 70) == 0) //a second is 70 ticks long, so this means "once per second"
			obj.xSpeed *= -1; //reverses xSpeed and therefore direction
		obj.behave(BEHAVIOR::WALKINGENEMY); //jjOBJ::behave tells the object to spend a tick as if its jjOBJ::behavior equalled the method's first argument, in this case, BEHAVIOR::WALKINGENEMY. Without the xSpeed code, therefore, this would be functionally identical to the previous ".behavior = BEHAVIOR::WALKINGENEMY;" example.
	}
}

Another fun trick is that behave takes 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::NORMTURTLE].behavior = TurnAroundTintedRed();
	jjObjectPresets[OBJECT::NORMTURTLE].playerHandling = HANDLING::ENEMY;
}

class TurnAroundTintedRed : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		if ((jjGameTicks % 70) == 0)
			obj.xSpeed *= -1;
		obj.behave(BEHAVIOR::WALKINGENEMY, false);
	}
	void onDraw(jjOBJ@ obj) { //usually also called once per tick
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::TINTED, 24);
	}
}

In the above examples, we've been using the default argumentless constructor for our jjBEHAVIORINTERFACE classes, but that's only scratching the surface. You have the power to define your own classes, and with that comes the power to define any number of properties or methods not included in the fairly limited jjOBJ class. A jjOBJ has no string properties, for example, but you can add one to a jjBEHAVIORINTERFACE:

void onLevelLoad() {
	jjObjectPresets[OBJECT::NORMTURTLE].behavior = DescribedWalker("turtle");
	jjObjectPresets[OBJECT::NORMTURTLE].playerHandling = HANDLING::ENEMY;
	jjObjectPresets[OBJECT::LIZARD].behavior = DescribedWalker("lizard");//note that both turtles and lizards use the exact same class, DescribedWalker, but pass different strings to its constructor
}

class DescribedWalker : jjBEHAVIORINTERFACE {
	private string enemyType; //jjOBJs don't have string properties, but we can put one in here
	DescribedWalker(const string &in et) {
		enemyType = et;
	}
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::WALKINGENEMY, true);
		jjDrawString(obj.xPos, obj.yPos - 40, "I'm a " + enemyType + "!");
	}
}

Or, taking the power of constructors to their extreme, you can give each object its own individual class instance. The most convenient way to do this is often using anonymous functions. The following example once again uses a string property on the jjBEHAVIORINTERFACE class, but you can use any other types too, from uints and floats all the way to arrays or dictionaries.

uint NumberOfTurtles = 0;
void onLevelLoad() {
	jjObjectPresets[OBJECT::NORMTURTLE].behavior = function(obj) { obj.behavior = CountTurtles(); };
}

class CountTurtles : jjBEHAVIORINTERFACE {
	string description;
	CountTurtles() {
		description = "Turtle #" + (++NumberOfTurtles);
	}
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::NORMTURTLE, true);
		jjDrawString(obj.xPos, obj.yPos - 40, description);
	}
}

(In addition to constructors, you may also give your class a destructor; it should however be noted that the AngelScript library does not guarantee that an object's destructor will be called the exact moment there are no more references to it, only that it will be called eventually. Therefore, you should not rely on a destructor to notify you instantly when a jjOBJ's behavior changes due to e.g. jjOBJ::delete.)

At some point, though, you'll want to break away from the crutch that is jjOBJ::behave and write your own object behavior 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 deactivates property is set to false, 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 jjOBJ::delete, you can usually just get away with calling jjOBJ::deactivate, 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 onLevelLoad() {
	jjOBJ@ presetObject = jjObjectPresets[OBJECT::NORMTURTLE]; //at this point, it doesn't matter so much which jjObjectPresets you choose, since you'll be editing most or all of its relevant properties
	presetObject.behavior = StationaryEnemy();
	presetObject.determineCurAnim(ANIM::SUCKER, 4);
	presetObject.playerHandling = HANDLING::ENEMY;
	presetObject.bulletHandling = HANDLING::HURTBYBULLET; //some of these values will already be used by whichever jjObjectPresets slot you choose, but it can't hurt to make sure
	presetObject.isTarget = true;
	presetObject.isFreezable = true;
	presetObject.triggersTNT = true;
	presetObject.deactivates = true;
	presetObject.energy = 1;
	presetObject.points = 300;
}
class StationaryEnemy : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START: //always used
				obj.state = STATE::IDLE;
			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
				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
				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;
		}
	}
	void onDraw(jjOBJ@ obj) {
		obj.draw();
	}
}

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
}
class DullBullet : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		if (obj.state == STATE::START) {
			obj.state = STATE::FLY;
			if (obj.creatorType == CREATOR::PLAYER) 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.xAcc;
			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.xSpeed;
				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.xSpeed;
				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 jjBEHAVIORINTERFACE method of them all: onObjectHit.

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

class FastFeet : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::PICKUP);
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@, jjPLAYER@ player, int) {
		player.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, using jjSample. pick one! it'll be an adventure!
		return true; //for details, see discussion in the onObjectHit item in the method list below, but basically you should almost always return "true" here
	}
}

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 third argument points to the jjPLAYER who collided with the pickup object, and the second and fourth arguments may be totally ignored. For the details of working with HANDLING::SPECIAL, which is rather more complicated, refer to the onObjectHit method description below.

And now, the list of jjBEHAVIORINTERFACE's methods:

void onBehave(jjOBJ@ obj)
For every jjOBJ whose behavior is an instance of the class defining this method, this method will be called once per gametick with that jjOBJ passed as its argument. (See above discussion for several examples.) This is the only method that must absolutely be defined in any class implementing jjBEHAVIORINTERFACE or else the script will fail to compile.
There are also two other ways to give a method or function this role for a jjOBJ without implementing jjBEHAVIORINTERFACE or naming it "onBehave". The following three code snippets all accomplish much the same effect, but with varying syntactic complexity and other options:
/******************************************
	Version 1: jjBEHAVIORINTERFACE
	The approach described above: define a class that implements jjBEHAVIORINTERFACE, assign an instance of that class to jjOBJ::behavior, and write the main behavior code in jjBEHAVIORINTERFACE::onBehave.
	(It is also possible to define a class that inherits from a different class which in turn implements jjBEHAVIORINTERFACE, which lets you write some standard object-handling code (e.g. a function to unfreeze frozen objects) without polluting the global namespace; one possible gotcha here is that any standard jjBEHAVIORINTERFACE method name (e.g. onDraw) must be registered directly on the class that implements jjBEHAVIORINTERFACE, even though the method JJ2+ will actually call is its final override. So if you have a generic "abstract class GenericObjectClass : jjBEHAVIORINTERFACE", and then a specific "class SolidBox : GenericObjectClass" which you want to define the onIsSolid method for, you will need to define that method for both of them, even if GenericObjectClass::onIsSolid is never directly called.)
/******************************************/
void onLevelLoad() {
	jjObjectPresets[OBJECT::NORMTURTLE].behavior = ClassName();
}
class ClassName : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::NORMTURTLE);
	}
}
/******************************************
	Version 2: Global Function
	Using a global function is faster both to write and to execute than the jjBEHAVIORINTERFACE setup, and requires only that the function assigned to jjOBJ::behavior is of funcdef jjVOIDFUNCOBJ: a void-returning function with a sole jjOBJ@ argument. The only drawback of its speed is that you lose access to all the benefits of defining your own class, and must instead use global properties/functions or jjOBJ properties for everything.
	(Prior to JJ2+ version 5.2, this was the _only_ way to define a behavior function, so you will see a lot of it in older scripts. The above examples all used the full jjBEHAVIORINTERFACE syntax for the purpose of making it easy to remember and understand.)
/******************************************/
void onLevelLoad() {
	jjObjectPresets[OBJECT::NORMTURTLE].behavior = BehaviorFunction;
}
void BehaviorFunction(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::NORMTURTLE);
}
/******************************************
	Version 3: Delegate Method
	You can also create a jjVOIDFUNCOBJ as a delegate of an object method with the proper signature, even if that object method is not named "onBehave". This gains you back the ability to define your own class with its own properties, but you lose access to every standard jjBEHAVIORINTERFACE method. The most obvious reason you might want to use this odd in-between style is that you can define multiple jjVOIDFUNCOBJ-patterned methods on a single class and switch an object's behavior from one to another at will while retaining access to all the class instance's properties.
/******************************************/
void onLevelLoad() {
	jjObjectPresets[OBJECT::NORMTURTLE].behavior = jjVOIDFUNCOBJ(ClassName().behaviorMethod);
}
class ClassName {
	void behaviorMethod(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::NORMTURTLE);
	}
}
void onDraw(jjOBJ@ obj)
An optional addition to onBehave where you can put code that runs nearly every gametick but is strictly related to drawing the jjOBJ to the screen. You can simply put all this code in onBehave with no noticeable difference in results, but you may find this division to be more tidy.
In particular, onDraw will not be called under circumstances when sprites are never drawn, e.g. if the game is minimized in a multiplayer game, or if a jjBEHAVIOR that is set to an instance of the class defining this method is passed as an argument to jjOBJ::behave and the draw argument is set to false.
bool onGetActive(jjOBJ@ obj)
void onSetActive(jjOBJ@ obj, bool setTo)
A pair of methods, one returning a bool and one taking one, for saying/learning whether an object is "active" for the server when the client joins. This is the area of code that specifies the starting status of pickups, generators, trigger scenery, destruct scenery, etc. for newly joining clients, but extended to the AS world.
By default, i.e. if they are not defined in your class, the onGetActive equivalent simply checks jjOBJ::isActive, and the onSetActive equivalent calls jjOBJ::delete if passed false, or does nothing if passed true.
Only applies to objects placed in JCS, i.e. jjOBJ::creator == CREATOR::LEVEL.
bool onIsSolid(jjOBJ@ obj)
When jjGameConnection == GAME::LOCAL, all box objects (objects with behavior BEHAVIOR::CRATE, BEHAVIOR::AMMO15, BEHAVIOR::MONITOR, or BEHAVIOR::BIGOBJECT) can sit on top of other box objects and cannot be pushed through other box objects. Return true from this method to add objects with this class (instance) as their behavior onto that list. (This has no impact on whether players treat the object as solid, only whether crates and such do.)
By default, i.e. if onIsSolid is not defined in your class, it is assumed to return false.
This method can be simulated on global jjVOIDFUNCOBJ behavior functions by use of the [SOLID] metadata string (the equivalent of onIsSolid always returning true), e.g.
[SOLID]
void Crate(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::CRATE);
}
bool onIsRFBullet(jjOBJ@ obj)
Most standard bullet behaviors handle speed and acceleration pretty much the same way, but RFs need a lot of special treatment, and messing with them at all tends to make them not fly fast enough and stuff like that. If you write a behavior class or function that's supposed to serve as a wrapper around BEHAVIOR::RFBULLET (i.e. you set it as the behavior for one or more bullets in jjObjectPresets and it calls jjOBJ::behave(BEHAVIOR::RFBULLET)), it's important for onIsRFBullet to return true in order to receive that same special treatment and thus get the right speeds assigned at firing time.
By default, i.e. if onIsRFBullet is not defined in your class, it is assumed to return false.
This method can be simulated on global jjVOIDFUNCOBJ behavior functions by use of the [RFBULLET] metadata string (the equivalent of onIsRFBullet always returning true), e.g.
[RFBULLET]
void Missile(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::RFBULLET);
}
Here's a sample snippet highlighting the use of class constructors to reuse the same method for multiple underlying behavior functions:
void onLevelLoad() {
    for (int eventID = OBJECT::BLASTERBULLET; eventID <= OBJECT::ELECTROBULLETPU; ++eventID) {
        jjObjectPresets[eventID].behavior = BulletWrapper(jjObjectPresets[eventID].behavior);
    }
}

class BulletWrapper : jjBEHAVIORINTERFACE {
    private jjBEHAVIOR nativeBehavior;
    BulletWrapper(const jjBEHAVIOR &in nb) { nativeBehavior = nb; }

    void onBehave(jjOBJ@ obj) {
        obj.behave(nativeBehavior);
    }
    bool onIsRFBullet(jjOBJ@ obj) {
        return nativeBehavior == BEHAVIOR::RFBULLET;
    }
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force)
JJ2 contains a lot of code for figuring out how each object should behave when collided with by either a player or a bullet, most of which it bases on jjOBJ::eventID. For instance, Silver Coin and Banana objects use the same jjOBJ::behavior (BEHAVIOR::PICKUP), but react differently to being touched by a player based on their eventID properties (OBJECT::SILVERCOIN and OBJECT::BANANA). This works perfectly well for vanilla JJ2 but is not enough for scripted levels, where you might want objects to react in ways that the original game never intended. For this you need onObjectHit.
In order for onObjectHit to called at all, two requirements must be met by the object: it must have jjOBJ::scriptedCollisions set to true, and jjOBJ::playerHandling to either HANDLING::PICKUP or HANDLING::SPECIAL. In either case, assuming that the onObjectHit method is properly defined in the class, that method will be called whenever the object needs to react to being hit.
In the first case, HANDLING::PICKUP, onObjectHit will only be called when a player object comes into contact with the pickup. The first argument will be the pickup itself, the third argument the player object, and the second and fourth may be totally ignored. If the result of the collision is that the pickup is collected, you should set the pickup's behavior to BEHAVIOR::EXPLOSION2; this is traditional in local single player, and mandatory in online multiplayer in order for the results of the collision (the pickup getting collected) to be broadcast to other players in the server.
HANDLING::SPECIAL is unfortunately a lot more complicated, because it has to handle not only a player bumping into the object, but also a player attacking the object with a physical special move, a bullet hitting the object, and a handful of special cases besides. The following table indicates what the values of the second through fourth arguments of the method will be under those circumstances, so that when writing an onObjectHit method of your own, you'll be able to figure out why it was called:
  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.
If you are scripting a multiplayer level and wish to ensure that the results of the HANDLING::SPECIAL collision are received by other players in the server, changing its state to STATE::KILL, STATE::EXPLODE, or STATE::ACTION will be announced to other players; any other state change will remain local.
Notice that onObjectHit has a bool return value. This follows the pattern established by the various global bool-returning hook functions: return true to signify that the hook has dealt with the situation, or false to let JJ2+ carry out the default behavior. In this particular case, the default behavior is to look for a global hook function with nearly the same signature, differing only in return value:
void onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force)
The onObjectHit global function is called for any object with scriptedCollisions true and playerHandling PICKUP or SPECIAL if that object's behavior class does not define its own onObjectHit method, or if it does define such a method but it happens to return false. (More accurately, every such global function across every script module is called.) The global hook option remains open primarily for backwards compatibility, but defining a class-specific method is almost invariably the better idea, because it greatly reduces the chances of one object accidentally using some code meant for another one.

jjPUBLICINTERFACE

Typically, different script modules, i.e. mutators and level scripts, are designed as separate entities that don't directly interact with each other and instead only interact with the game. This is usually desirable, but in other circumstances limits functionality, leads to repetition in code, or causes conflicts between scripts. jjPUBLICINTERFACE is a tool designed to allow communication and interaction between simultaneously running scripts.

Communication using jjPUBLICINTERFACE is asymmetric: when two modules interact, one of them is an exporter and the other is an importer. The exporter is a module that exposes some of its functionality to other modules. It doesn't have to know their names or be in any way aware of their existence. All it has to do is register a class that implements jjPUBLICINTERFACE and a special hook function named onGetPublicInterface. It's far more common for the exporter to be a mutator than a level script, but in theory it may be either. The importer is a module that is aware the exporter exists and refers to it by name to gain access to its functionality. It does so by calling the jjGetPublicInterface function. The importer may equally well be a mutator as a level script.

jjPUBLICINTERFACE only defines one method that serves the purpose of obtaining version of the exporter, thus, on its own, it cannot perform meaningful communication. In order to expose other methods, the interface handle obtained from jjGetPublicInterface has to be cast to an underlying type that is known to both script modules. This generates certain restrictions: the underlying type needs to be defined identically in both modules, and it needs to be defined as shared. The most convenient way to meet those requirements is to use a so-called header file to contain the type. Below you can see an example of a structure of three files: a header, an exporter, and an importer, that demonstrates basic module interaction:

//header.asc

//This file will be shared between the exporter and the importer so that PublicInterface is a common type for both of them.
//The shared type is an interface, but could also be a class or an abstract class.
shared interface PublicInterface : jjPUBLICINTERFACE {
    int getProperty();
    void setProperty(int);
    //getVersion inherited from jjPUBLICINTERFACE, may be omitted.
    string getVersion() const;
}
//exporter.mut

#pragma require "header.asc"
#include "header.asc"
int property = 0;
//Create a type that implements PublicInterface. This class doesn't have to be shared, so it can refer to module-local entities, such as property in this example.
class PublicClass : PublicInterface {
    int getProperty() {
        return property;
    }
    void setProperty(int value) {
        property = value;
    }
    string getVersion() const {
        return "1.0";
    }
}
//Create an instance of the class to return from onGetPublicInterface.
PublicClass publicInstance;
//The hook. Notice that the return type doesn't have to be jjPUBLICINTERFACE@. This is discussed below.
PublicClass@ onGetPublicInterface() {
    return publicInstance;
}
void onMain() {
    property++;
}
//importer.mut

#pragma require "header.asc"
#include "header.asc"
PublicInterface@ publicInstance;
void onLevelLoad() {
    //Calls onGetPublicInterface of exporter.mut, if that mutator is running.
    jjPUBLICINTERFACE@ pi = jjGetPublicInterface("exporter.mut");
    //If pi is null, exporter.mut is not running or doesn't register onGetPublicInterface, so no interaction will be possible.
    if (pi !is null) {
        //Attempt a cast to the underlying type.
        @publicInstance = cast<PublicInterface>(pi);
        //If pi is not null but the cast fails, the underlying type is not what we expected. Perhaps exporter.mut changed the underlying type in a newer version. Here's an example of how getVersion may be used even if the underlying type is unknown:
        if (publicInstance is null)
            jjAlert("Incompatible exporter.mut version: " + pi.getVersion());
    }
}
void onMain() {
    if (publicInstance !is null) {
        //Through the exposed functions, importer.mut can now access and modify variables of exporter.mut.
        int exporterProperty = publicInstance.getProperty();
        publicInstance.setProperty(exporterProperty + 1);
    }
}

A few things are worth paying attention to about this example. In particular, as pointed out, the onGetPublicInterface hook returns a type that is local to the script module. Indeed, although the default signature of this hook is

jjPUBLICINTERFACE@ onGetPublicInterface()

other signatures are also accepted, as long as the hook is registered to take no arguments and return a handle of a type that implements jjPUBLICINTERFACE. Another thing worth bringing up is that although this example may seem complicated to a beginner, most of the burden of implementation is on the side of the person creating the exporting module; creating an importing module is relatively easy. In fact, a well-designed header file may provide convenience functions that immensely reduce nuances of importing functionality from a module.

Below is the sole method of jjPUBLICINTERFACE:

string getVersion() const
Should be defined to return a constant string denoting the module version, or the version of its public interface. The format of this string is unspecified but it is recommended that it be descriptive, e.g. the first released version of the module may define this to return "1.0".

Global Properties

const uint jjActiveGameTicks
How long the game has been actively running, at a rate of 70 ticks per second. Unlike jjGameTicks, this value is not incremented when the game is paused, stopped, or in pregame. This is a local value that counts up from 0 except in online Race games, where it is used to track lap times and is therefore synced between server and clients.
const bool jjAllowsFireball
Whether weapon 8 is fireball instead pepper spray, as set by the /fireball command.
const bool jjAllowsMouseAim
Whether the server (or the SP level) allows mouse aim, as set by the /allowmouseaim command.
const bool jjAllowsReady
Whether the server allows players to use the /ready command, as set by the /allowready command.
const bool jjAlwaysRunning
Whether always running is enabled, as set by the /run command.
jjTILE@ jjAnimatedTiles[0x10000]
Animated tiles defined by the level. For every tileID,
jjAnimatedTiles[tileID] is jjTiles[tileID | TILE::ANIMATED].
See also jjTiles.
jjANIMATION@ jjAnimations[1500]
Loaded animations. See the jjANIMATION class description for more information.
jjANIMFRAME@ jjAnimFrames[15000]
Loaded animation frames. See the jjANIMFRAME class description for more information.
jjANIMSET@ jjAnimSets[ANIM::Set]
Loaded animation sets. See the jjANIMSET class description for more information.
const bool jjAutoWeaponChange
Whether automatic weapon change is locally enabled, as set by the /weaponchange command.
const jjPAL jjBackupPalette
The tileset's original palette. See the jjPAL documentation above for further details.
const int jjBorderHeight
const int jjBorderWidth
The size of the black borders that appear at the edges of each local player's subscreen when a subscreen is larger than the level/server's maximum resolution, when a subscreen is larger than Layer 4 (and Layer 4 does not have Tile Width/Tile Height checked), and/or when the F3 key has been used. Useful for deciding where to draw HUD/UI elements. Note that these values refer to the size of each border, not the overall size of the black space, so for instance if jjBorderWidth is 80, there will be 80 columns of black pixels on the left side of the subscreen and an additional 80 columns on the right side.
jjPLAYER@ jjBottomFeeder
In Roast Tag game mode, the player who is currently the bottom feeder or null if none. For the other special role in Roast Tag, see jjTokenOwner.
jjCHARACTER@ jjCharacters[CHAR::Char]
Character profiles. Use either CHAR::JAZZ, CHAR::SPAZ, CHAR::LORI, CHAR::BIRD, CHAR::FROG or CHAR::BIRD2 as an index. Refer to the jjCHARACTER section for more information.
const int jjColorDepth
Color depth in bits per pixel. Either 8 or 16.
const jjCONTROLPOINT@ jjControlPoints[16]
An array containing all Domination control points in the level. See the jjCONTROLPOINT section 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, box objects (trigger crates, all wooden crates, bird morph monitors, and also bird cages) spawned from Generator objects 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.
const bool jjDoZombiesAlreadyExist
In Pestilence game mode, whether any player is already a zombie.
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.
const bool jjEnabledTeams[TEAM::Color]
const bool jjEnabledTeams[4]
Currently enabled teams. Possible indices are TEAM::BLUE, TEAM::RED, TEAM::GREEN, and TEAM::YELLOW.
LIGHT::Enforce jjEnforceLighting
This setting defines the minimal ambient lighting options required from the game. It will not change game settings if they don't fulfill the requirements but it will display lights as if the settings were changed. Allowed values are:
  • OPTIONAL: The default value; ambient lighting can be freely disabled and enabled with no limits.
  • BASIC: Ambient lighting can be disabled but basic lights, such as those emitted by objects, players and laser shields, have to be drawn. This only affects the game if ambient lighting is disabled and low detail is enabled, because that's when basic lights stop being drawn.
  • COMPLETE: Ambient lighting cannot be disabled, all lights have to be drawn.
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 bool jjFriendlyFire
Whether friendly fire is enabled, as set by the /friendlyfire command.
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, DOM, and HEAD.
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 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 int jjGameTicks
How long the game has been actively running, at a rate of 70 ticks per second.
string jjHelpStrings[16]
Help strings as set in level properties and used by Text events and end bosses. These can be modified but are limited to 511 characters each, so longer strings will be truncated. For the standard function to display these strings, see showText.
const bool jjIsAdmin
Whether the current game executable is logged in as a Remote Admin in the current online server. To check this property for any client in the server, use jjPLAYER property isAdmin instead.
const bool jjIsServer
Whether the current game executable is hosting an online server.
bool jjIsSnowing
Whether there's any active weather effect. The type of the effect is determined by jjSnowingType.
bool jjIsSnowingOutdoorsOnly
Whether the current weather effect is specified to only take effect on transparent tiles, i.e. appear to be limited to outdoors areas.
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.
uint8 jjKeyChat
The current virtual key used to open the chat prompt in a multiplayer game, default value 0x54 ('T'). No matter the key (or mouse button), pressing the Shift key at the same time will open the chat prompt in Team Chat mode (in CTF games). Note that as a workaround to allow players to cycle and whatnot, pressing Ctrl+T will always open the chat prompt, even if jjKeyChat is set to 0 or something similarly inaccessible.
bool jjLayerHasTiles[8]
const int jjLayerHeight[8]
bool jjLayerLimitVisibleRegion[8]
bool jjLayerTileHeight[8]
bool jjLayerTileWidth[8]
const int jjLayerWidth[8]
const int jjLayerWidthReal[8]
const int jjLayerWidthRounded[8]
float jjLayerXAutoSpeed[8]
float jjLayerYAutoSpeed[8]
float jjLayerXOffset[8]
float jjLayerYOffset[8]
float jjLayerXSpeed[8]
float jjLayerYSpeed[8]
Shortcut global properties for the same-named jjLAYER properties on the same-indexed jjLayers objects.
jjLAYER@ jjLayers[8]
The original eight layers placed in this level (i.e. jjLevelFileName) in JCS or some other level editor, 1-indexed to match the JCS numbers, e.g. jjLayers[4] for the main sprite layer or jjLayers[8] for the final background layer, regardless of what other code may have done to create new layers or alter their order. See the jjLAYER documentation above for further details.
const string jjLevelFileName
File name of the current level, e.g. Castle1.j2l. The file extension (.j2l) will be included, but not the folder structure. Currently the capitalization of this string is undefined, so please make sure to use case-insensitive comparisons.
string jjLevelName
Title of the current level, e.g. Dungeon Dilemma.
const int jjLocalPlayerCount
The number of local players.
const jjPLAYER@ jjLocalPlayers[4]
The local players.
const bool jjLowDetail
Whether the Low Detail video setting is enabled.
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 jjMaxScore
In competitive game modes, the score required to win, as set by the /maxscore command.
const bool jjMouseAim
Whether mouse aim is locally enabled, as set by the /mouseaim command.
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.
const bool jjMusicActive
Mute Music, as seen in the Sound & Music Properties window.
const string jjMusicFileName
File name of the music file currently playing, e.g. 3ddemo.mod. The file extension will be included (even if not included by the level or user, e.g. this string will be "castle.j2b" if "castle" was written in Level Properties), but not the folder structure. Currently the capitalization of this string is undefined, so please make sure to use case-insensitive comparisons.
This is a const property. To change it, use jjMusicLoad.
const int jjMusicVolume
Music Volume, as seen in the Sound & Music Properties window.
const bool jjNoBlink
Whether the no blink mode is enabled, as set by the /noblink command.
const bool jjNoMovement
Whether the game blocks movement during stopped games, as set by the /nomovement command.
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 2048 in local Single Player/Coop, or 4096 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
Doesn't work! Check back later.
const jjPLAYER@ jjPlayers[32]
All the players in the game, local or otherwise.
const bool jjQuirks
Whether the quirks mode is enabled, as set by the /quirks command.
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 uint jjScriptModuleID
Each script module (including mutators) will see this property as equalling a different value: 0 for the .j2as script (if any), and 1 or higher for all mutators (loaded in alphabetical order). For use only as parameters of function jjSendPacket and jjPLAYER method hasPrivilege.
const bool jjShowMaxHealth
Whether the show max health option from the Plus menu is enabled.
uint8 jjSnowingIntensity
Intensity of the current weather effect. Note that that this setting only influences the game if jjIsSnowing is true.
SNOWING::Type jjSnowingType
Type of the current weather effect. Note that that this setting only influences the game if jjIsSnowing is true. Possible values are SNOW, FLOWER, RAIN, and LEAF, each spawning particles of the corresponding PARTICLE::Type.
const bool jjSoundEnabled
Whether JJ2 should produce any form of audio at all.
const bool jjSoundFXActive
Mute Sound, as seen in the Sound & Music Properties window.
const 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 bool jjStrongPowerups
Whether strong powerups is enabled, as set by the /strongpowerups command.
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. The subscreen size includes, and is therefore not changed by the values of, jjBorderHeight and jjBorderWidth.
const int jjTeamScore[TEAM::Color]
Each team's current score in team-based game modes and undefined value in other modes. Available indexes are BLUE, RED, GREEN and YELLOW.
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).
bool jjTexturedBGStars
The layer 8 checkbox titled "Parallaxing stars background (Star Wars)" in JCS.
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 jjTexturedBGUsed
Whether this level displays a textured background in place of layer 8 at all.
const uint jjTileCount
The number of (non-animated, non-flipped) tiles currently defined in the level, usually a multiple of 10. Can be increased using jjTilesFromTileset.
const jjTILE@ jjTiles[0x10000]
Static and animated tiles corresponding to tile IDs.
See also jjAnimatedTiles.
const string jjTilesetFileName
File name of the tileset used by the current level, e.g. Castle1.j2t. The file extension (.j2t) will be included, but not the folder structure. Currently the capitalization of this string is undefined, so please make sure to use case-insensitive comparisons.
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.
jjPLAYER@ jjTokenOwner
In Roast Tag game mode, the player who is currently "it" or null if none. For the other special role in Roast Tag, see jjBottomFeeder.
bool jjUseLayer8Speeds
Whether jjLayers[8] should use its specified layer speeds. Works regardless of whether the layer is textured or not. False by default.
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.
WATERINTERACTION::WaterInteraction jjWaterInteraction
How local players react to being underwater. If this property is set to SWIM, they will swim; if LOWGRAVITY, they will use regular physics but will fall more slowly than usual. If this property is set to POSITIONBASED (the default), the game will choose between the effects of SWIM or LOWGRAVITY depending on whether jjWaterLevel is lower or greater than 32*128. This property has no effects on other objects or on sound effects, which always move more slowly/sound different underwater.
int jjWaterLayer
Which layer, 1-8, water is drawn in front of when visible. Defaults to 1. Set to any non-existing layer number to make water invisible. Note that this is a purely visual setting, and putting water behind the sprite layer will not prevent players from swimming in it.
If the order of layers has been changed, this property's distance from 4 is its distance from the sprite layer, e.g. leaving it at 1 means that it will be drawn in front of the third layer in front of the sprite layer. (And therefore, if the sprite layer is the first, second, or third layer in the drawing order, water will not be drawn at all.)
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 jjBEHAVIORINTERFACE.
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.
void jjAddParticlePixelExplosion(float xPixel, float yPixel, int curFrame, int direction, int mode)
Creates an explosion of particles based on the shape and possibly colors of the specified curFrame. Use a mode value of 0 for a normal explosion, 1 for a fire explosion caused by a toaster or fire shield bullet, or 2 for an explosion caused by a special move. Values in the range of 15-255 will create fire explosions whose particles will use palette index equal to mode - this effect is currently used by weapons such as powered-up toaster and laser shield to create blue explosions. Value 14 creates a fire explosion whose particles use individually random colors. Values in the range of 3-13 and higher than 255 are undefined and - to preserve backward compatibility - shouldn't be used.
void 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(const string &in text, bool sendToAll = false, STRING::Size size = STRING::SMALL)
Writes text to the chatlogger window, and also displays it ingame for the local player. Uses size to determine size and positioning of text. If sendToAll is true and the function is called by the server, text will be sent to all clients as well.
void jjChat(const string &in 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.
void jjConsole(const string &in text, bool sendToAll = false)
Writes text as a console message to the chatlogger window, and also displays it ingame for the local player. If sendToAll is true and the function is called by the server, text will be sent to all clients as well.
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.
uint jjCRC32(const jjSTREAM &in input, uint crc = 0)
Computes cyclic redundancy check of input with optional initial value crc. The checksum is computed according to the CRC-32 standard (as used in the zlib library and all JJ2 data files that use CRC).
void jjDebug(const string &in text, bool timestamp = false)
Writes text to the chatlogger window (but not ingame), but only if [General]AngelscriptDebug equals True in plus.ini. If timestamp is true, adds a timestamp before the text.
void jjDeleteObject(int objectID)
Permanently deletes an object. Like jjAddObject, this function is purely local in its scope.
void jjDrawPixel(float xPixel, float yPixel, uint8 color, SPRITE::Mode mode = SPRITE::NORMAL, int param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawRectangle(float xPixel, float yPixel, int width, int height, uint8 color, SPRITE::Mode mode = SPRITE::NORMAL, int param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawResizedSprite(float xPixel, float yPixel, uint8 setID, uint8 animation, uint8 frame, float xScale, float yScale, SPRITE::Mode mode = SPRITE::NORMAL, int param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawResizedSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, float xScale, float yScale, SPRITE::Mode mode = SPRITE::NORMAL, int param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawRotatedSprite(float xPixel, float yPixel, uint8 setID, uint8 animation, uint8 frame, int angle, float xScale = 1, float yScale = 1, SPRITE::Mode mode = SPRITE::NORMAL, int param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawRotatedSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, int angle, float xScale = 1, float yScale = 1, SPRITE::Mode mode = SPRITE::NORMAL, int param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawSprite(float xPixel, float yPixel, uint8 setID, uint8 animation, uint8 frame, int direction = 0, SPRITE::Mode mode = SPRITE::NORMAL, int param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, int direction = 0, SPRITE::Mode mode = SPRITE::NORMAL, int param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawString(float xPixel, float yPixel, const string &in text, STRING::SIZE size = STRING::SMALL, STRING::Mode mode = STRING::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawString(float xPixel, float yPixel, const string &in text, const jjANIMATION &in animation, STRING::Mode mode = STRING::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawString(float xPixel, float yPixel, const string &in text, STRING::SIZE size, const jjTEXTAPPEARANCE &in appearance, uint8 param1 = 0, SPRITE::Mode spriteMode = SPRITE::PALSHIFT, uint8 param2 = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawString(float xPixel, float yPixel, const string &in text, const jjANIMATION &in animation, const jjTEXTAPPEARANCE &in appearance, uint8 param1 = 0, SPRITE::Mode spriteMode = SPRITE::PALSHIFT, uint8 param2 = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawSwingingVineSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, int length, int curvature, SPRITE::Mode mode = SPRITE::NORMAL, int param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
void jjDrawTile(float xPixel, float yPixel, uint16 tile, TILE::Quadrant tileQuadrant = TILE::ALLQUADRANTS, int8 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.
If the order of layers has been changed, then layerZ and layerXY's distance from 4 are their distance from the sprite layer, e.g. 3 means not necessarily jjLayers[3], but rather 4-1, the first layer in front of the sprite layer, whichever jjLAYER that happens to be.
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 jjGenerateSettableTileArea(uint8 layer, int xTile, int yTile, int width, int height)
Shortcut global function for jjLAYER::generateSettableTileArea on the same-indexed jjLayers objects.
jjPALCOLOR jjGetFadeColors()
Returns the fade colors of the level's textured background, as seen in the Layer properties window for layer 8 in JCS.
int jjGetModOrder()
Returns the current order in the currently playing module music, or -1 if the currently playing music is not a module, a module not handled by BASS or no music is playing.
int jjGetModRow()
Returns the current row in the currently playing module music, or -1 if the currently playing music is not a module, a module not handled by BASS or no music is playing.
int jjGetModSpeed()
Returns the "speed" parameter (ticks per row) of the currently playing module music, or -1 if the currently playing music is not a module, a module not handled by BASS or no music is playing.
int jjGetModTempo()
Returns the tempo of the currently playing module music, or -1 if the currently playing music is not a module, a module not handled by BASS or no music is playing.
jjPUBLICINTERFACE@ jjGetPublicInterface(const string &in moduleName)
If moduleName is a name of a currently running script module that registers the onGetPublicInterface hook, calls that hook and returns its result, otherwise returns null. Module names are the same as names of files that contain the modules, including the file extension ".j2as" or ".mut", and the comparison is not case sensitive. In the target module, onGetPublicInterface must be a global function with the following signature:
jjPUBLICINTERFACE@ onGetPublicInterface()
The exact return type may differ as long as it is a handle to a class or interface that implements jjPUBLICINTERFACE. This function should not be called earlier than in onLevelLoad, i.e. it should not be used to initialize a global variable, as the target module may not be available at that point yet. More details about usage of this function may be found in the dedicated section of this document.
uint16 jjGetStaticTile(uint16 tileID)
If tileID is animated, i.e. (tileID & TILE::ANIMATED) != 0, returns tile ID of the current animation frame of the tile corresponding to tileID. Otherwise returns tileID.
Like animated tiles themselves, this function relies on system time rather than game ticks, which means that subsequent calls during the same frame may return different results, and that results may insignificantly differ compared to the effective state of the animation. This behavior may change in the future.
This function never returns tile ID of animated tiles. If its result would be animated, which may happen in levels edited by other means than JCS, the function is called recursively on the result until a static tile is obtained.
int jjGetStringWidth(const string &in text, STRING::Size size, const jjTEXTAPPEARANCE &in style)
int jjGetStringWidth(const string &in text, const jjANIMATION &in animation, const jjTEXTAPPEARANCE &in style)
Returns width, in pixels, that text would have if it was drawn in specified size (or animation) and style. If style allows multi-line text and text is multi-line, width of the longest line is returned.
bool jjIsValidCheat(const string &in text)
Returns true if text is interpreted by the game as a cheat code.
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.
array<jjLAYER@>@ jjLayerOrderGet()
bool jjLayerOrderSet(const array<jjLAYER@>& in order)
A pair of functions for accessing or changing the ordered list of layers drawn to the screen. (In most cases it should be simpler to keep the jjLAYER@ array around in your script as a variable, rather than retrieving it using jjLayerOrderGet, but that function is there just in case you need it.) The arrays are ordered so that the first jjLAYER is in front and the last is in back, meaning that writing jjLayerOrderSet(array<jjLAYER@> = {jjLayers[1], jjLayers[2], jjLayers[3], jjLayers[4], jjLayers[5], jjLayers[6], jjLayers[7], jjLayers[8]}); should have no visible effect in a previously unaltered level.
The array passed to jjLayerOrderSet must include jjLayers[4] (the gameplay/sprite layer) as one of its entries and must not include any null handles, or else the function will return false and cause a debug error. Including multiple handles to the same jjLAYER is allowed but not very useful.
Following a call to jjLayerOrderSet, it takes some extra thought and understanding to figure out how to refer to individual layers in other sections of the code. Specifically there are two different patterns (which happen to come to the same thing if the layer order is never changed at all):
All global properties or functions with a 1-indexed layer argument—e.g. jjLayers, jjLayerYSpeed, or jjTileGet— refer exclusively to the level's original eight layers as defined in the level editor. jjLayers[1] will always refer to the same layer no matter which layer ends up being drawn foremost in the foreground. To read or write the size, speeds, etc. of any layer besides those eight, use the properties and methods of its jjLAYER instance. Similarly, the jjCANVAS onDrawLayer# hooks are (currently) only available for those original eight layers, and jjLayers[1] will always call onDrawLayer1 (if defined) no matter its position in the drawing order.
When calling jjDrawSprite or any of its related functions with their layerZ and layerXY arguments, those arguments refer to the list of layers from jjLayerOrderGet, relative to Layer 4's position in that list. The default layerZ value is 4 and will always draw sprites to jjLayers[4]. If layerZ equals 3, 4-1, the sprite will be drawn to whichever jjLAYER is ordered one in front of jjLayers[4]. If 6, 4+2, the layer two behind jjLayers[4]. And so on. The same principle applies to jjWaterLayer.
array<jjLAYER@>@ jjLayersFromLevel(const string &in filename, const array<uint> &in layerIDs, int tileIDAdjustmentFactor = 0)
Returns an array containing a number of handles of new jjLAYER instances built from the layers defined in the level filename, specifically those requested in the 1-indexed layerIDs. (Passing numbers not in the range of 1–8 in layerIDs is undefined behavior.) For example, jjLayersFromLevel("castle1.j2l", array<uint> = {5,6}); returns an array with two layers, one of width 23 and height 25 and the other of width 20 and height 11, containing different sets of pillars as defined in Layer 5 and Layer 6 of castle1.j2l. These new layer objects may then be inserted into the set of layers drawn to the screen by use of jjLayerOrderSet.
By default, the tile IDs of the tiles in these layers will be unchanged, meaning that unless the tileset used by the level filename is the same as is used by this level, the results will likely look very peculiar. (Similarly, any animated tiles used in those layers should probably match those used in this level.) In fact, you may want to make a special unplayable level just for the sake of taking its miscellaneous background or foreground layers into this one (and if you do, remember to tick "Hide level in Home Cooked Levels list" in its level properties). Alternatively, the optional tileIDAdjustmentFactor parameter is a value added to the tile IDs of any non-zero, non-animated tiles in the layer/s imported from filename, so for instance, if tileIDAdjustmentFactor equals 10, then a series of tiles 5,6,0,0,3 would become 15,16,0,0,13. (This option is intended for use in conjunction with jjTilesFromTileset, though you may find other uses for it as well.)
Like in other sections of JJ2+ code, levels saved in either the TSF or regular JCSes work equally well. It does not matter whether the level is passworded. If the file does not exist or does not have the file extension ".j2l" then the returned array will have zero length. If the file is not a valid level then the game may or may not crash. As with other dependent files, it is recommended to use #pragma require if the level being mined for layers is not a default level.
bool jjMaskedHLine(int xPixel, int lineLength, int yPixel)
Returns true if any pixel from xPixel,yPixel to xPixel+lineLength,yPixel is masked.
bool jjMaskedPixel(int xPixel, int yPixel)
Returns true if pixel xPixel,yPixel is masked.
int jjMaskedTopVLine(int xPixel, int yPixel, int lineLength)
If any pixel from xPixel,yPixel to xPixel,yPixel+lineLength is masked, 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.
bool jjMaskedVLine(int xPixel, int yPixel, int lineLength)
Returns true if any pixel from xPixel,yPixel to xPixel,yPixel+lineLength is masked.
bool jjMaskedHLine(int xPixel, int lineLength, int yPixel, uint8 layer)
bool jjMaskedPixel(int xPixel, int yPixel, uint8 layer)
int jjMaskedTopVLine(int xPixel, int yPixel, int lineLength, uint8 layer)
bool jjMaskedVLine(int xPixel, int yPixel, int lineLength, uint8 layer)
Shortcut global functions for the same-named jjLAYER methods on the same-indexed jjLayers objects.
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(bool warp = false, bool fast = false)
void jjNxt(const string& filename = "", bool warp = false, bool fast = false)
Ends the level and skips to the next one, or to filename if specified and of length>0. 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.
array<jjPLAYER@>@ jjPlayersWithClientID(int clientID)
Convenience function; creates and returns an array containing handles of all active players with a given clientID. The result is an empty array if the client is not connected or if clientID is not a valid ID. The result is a null handle if the function is called by a client.
void jjPrint(const string &in text, bool timestamp = false)
Writes text to the chatlogger window but does not display it ingame. If timestamp is true, adds a timestamp before the text.
uint jjRandom()
Provides a random number.
bool jjRegexIsValid(const string &in expression)
Returns true if expression is a valid regular expression and false otherwise. This is the only regex function that doesn't cause debug errors when the input is an invalid expression and as such it should be always used before calling other regex functions when expression comes from an untrusted source (such as from user input rather than from a constant string in the script). Expressions will be parsed according to modified ECMAScript regular expression grammar.
bool jjRegexMatch(const string &in text, const string &in expression, bool ignoreCase = false)
bool jjRegexMatch(const string &in text, const string &in expression, array<string> &out results, bool ignoreCase = false)
Returns true if expression is a valid regular expression that matches text entirely and false if no match is found. Where the second overload is used, results will contain match results in a standard order. If ignoreCase is true, the matching will be case insensitive. If expression is not a valid regular expression, a debug message will be printed to the chatlogger and the return value will be undefined. In future versions of JJ2+ this might have further consequences including complete script shutdown. For this reason, jjRegexIsValid should always be used to validate untrusted input. Note that this function only returns true if expression matches the entire string, whereas jjRegexSearch accepts substring matches.
string jjRegexReplace(const string &in text, const string &in expression, const string &in replacement, bool ignoreCase = false)
Returns a string that is the result of replacement of all substrings of text that are matched by expression with replacement. If ignoreCase is true, the matching will be case insensitive. Capture results ($1, $2, etc., $0 always being the entire matched substring) can be successfully used in the replacement string. If expression is not a valid regular expression, a debug message will be printed to the chatlogger and the return value will be undefined. In future versions of JJ2+ this might have further consequences including complete script shutdown. For this reason, jjRegexIsValid should always be used to validate untrusted input.
bool jjRegexSearch(const string &in text, const string &in expression, bool ignoreCase = false)
bool jjRegexSearch(const string &in text, const string &in expression, array<string> &out results, bool ignoreCase = false)
Returns true if expression is a valid regular expression that matches any substring of text and false if no match is found. Where the second overload is used, results will contain match results in a standard order. If ignoreCase is true, the matching will be case insensitive. If expression is not a valid regular expression, a debug message will be printed to the chatlogger and the return value will be undefined. In future versions of JJ2+ this might have further consequences including complete script shutdown. For this reason, jjRegexIsValid should always be used to validate untrusted input. Note that this function returns true if expression matches any character subsequence of text, whereas jjRegexMatch will only look for matches with the entire string.
void 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.
bool jjSampleIsLoaded(SOUND::Sample sample)
Returns whether sample is loaded or not.
bool jjSampleLoad(SOUND::Sample sample, string &in filename)
Attempts to load sample from a .wav file filename and returns true on success or false otherwise. If there is already a loaded sample corresponding to this SOUND::Sample constant, it will be overwritten.
int jjSampleLooped(float xPixel, float yPixel, SOUND::Sample sample, int channel, int volume = 63, int frequency = 0)
Plays a looped sound from anims.j2a at pixel xPixel, yPixel. For every source of sound (e.g. a jjOBJ instance), channel should be 0 on the first call and in later calls should be replaced with the value returned from the previous call of this function.
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.
bool jjSendPacket(jjSTREAM &in packet, int toClientID = 0, uint toScriptModuleID = jjScriptModuleID)
Sends packet from client to server (in case if you're a client) or from server to client (in case if you're a server). Returns true on success and false on failure. If toClientID is a positive value, the packet will be sent only to the client with the appropriate jjPLAYER::clientID. If it's negative, it will be sent to all clients with the exception of the one indicated by toClientID. Using the default value of 0 results in sending the packet to all clients. For a script to receive packet, you will need to declare an onReceive hook, which has the following signature:
void onReceive(jjSTREAM &in packet, int fromClientID)
Notice that packets can't be sent between two clients but only between the server and a client; if the function is called client-side, the toClientID argument is completely ignored. If a client is meant to send a packet to another client, they have to send the packet to the server first and the server should resend it to the other client from onReceive.
If there are multiple distinct script modules running (two or more mutators, or one mutator and a level's primary script), the packet will only be received by the module whose jjScriptModuleID global value matches the toScriptModuleID parameter. By leaving this parameter as the default value, you can ensure that a packet sent from foo.j2mut will always be read by (the onReceive hook defined in) foo.j2mut, rather than another, simultaneously running module that wouldn't know what to do with the data. Passing 0 instead will send the packet to the level's primary script (if any), and is rarely a good idea. Other values are even more rarely a good idea.
void jjSetDarknessColor(jjPALCOLOR color = jjPALCOLOR(0, 0, 0))
Sets the color of darkness used with ambient lighting.
void jjSetFadeColors(uint8 red, uint8 green, uint8 blue)
void jjSetFadeColors(jjPALCOLOR color)
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)
Shortcut global functions for setXSpeed and setYSpeed on the same-indexed jjLayers objects.
void jjSetModPosition(int order, int row, bool reset)
Jumps to a specific row of a specific order in the currently playing module file. If reset is true, also stops all notes and resets the module's global volume, tempo, etc. to their original values.
Calling this function with an invalid order or row number, or while BASS is not playing a module file, will have no effect.
void jjSetModSpeed(uint8 speed)
Sets the "speed" of the currently playing module music file. Does nothing if BASS is not currently playing a module file. Note that the module may change its own speed, overwriting your change.
void jjSetModTempo(uint8 tempo)
Sets the tempo of the currently playing module music file. Does nothing if BASS is not currently playing a module file. Note that the module may change its own tempo, overwriting your change.
void jjSetWaterGradient(uint8 red1, uint8 green1, uint8 blue1, uint8 red2, uint8 green2, uint8 blue2)
void jjSetWaterGradient(jjPALCOLOR color1, jjPALCOLOR color2)
void 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).
void 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.
void jjSlideModChannelVolume(int channel, float volume, int milliseconds)
In currently playing module music, slide the volume of channel to volume over a chosen number of milliseconds. Does nothing if BASS is not currently playing a module file or channel does not exist. volume is 1.0 for the module's channel volume, 0.0 for silent. If you give a higher or lower value, the slide will stop when it reaches one of these boundaries. Volume slides continue while the music is paused. If you begin sliding the volume of a channel that is already sliding, the old slide will immediately stop.
void jjSpy(const string &in text)
Prints text to the game's built-in spy window activated by running it with the -spy command line parameter, and also writes it to your jazz2.log file.
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)
uint16 jjTileSet(uint8 layer, int xTile, int yTile, uint16 newTile)
Shortcut global functions for the same-named jjLAYER methods on the same-indexed jjLayers objects.
bool jjTilesFromTileset(const string &in filename, uint firstTileID, uint tileCount, const array<uint8>@ paletteColorMapping = null)
Opens the tileset filename, extracts tileCount tiles from it starting at tile ID firstTileID, and appends them to the end of the currently loaded tileset, increasing jjTileCount by tileCount. If paletteColorMapping is not null and is of length 256 or greater, it will be used to recolor the imported tiles in a way that better fits the palette you are currently using, e.g. pixels of color 10 will be changed to paletteColorMapping[10] instead. Returns false upon various reasons for failure.
jjTilesFromTileset is primarily intended to be combined with jjLayersFromLevel and jjLayerOrderSet, and the three should ideally be called in that order, so that jjLayersFromLevel has the right tiles to use in its new layers. Here is a sample script that imports the background pillars from Dungeon Dilemma (into any level using any tileset) and recolors them to use colors from the textured background:
void onLevelLoad() {
	const int oldTileCount = jjTileCount; //the number of tiles in the level's tileset before jjTilesFromTileset increases the number
	array<uint8> pillarColorMapping(256);
	for (int i = 0; i < 16; ++i)
		pillarColorMapping[i + 128] = i + 192; //map pillars' colors (stored in palette indices 128 through 143) to the (second row of) textured background colors
	jjTilesFromTileset( //appends tiles to the end of the internal copy of the tileset used by the current level
		"Castle1.j2t", //filename to take tiles from
		420, //first tile ID in tileset to take
		60, //number of tiles to take
		pillarColorMapping //an array<uint8>(256) that maps colors in the source tileset to colors in the destination tileset, here only working on a single 16-color gradient because that's all that is used in those particular two layers
	);
	array<jjLAYER@> castleLayers = jjLayersFromLevel( //builds new jjLAYER instances from the layers defined in this level
		"Castle1.j2l", //filename to take layers from
		array<uint> = {5,6}, //which layers to grab
		oldTileCount - 420 //a number to offset the non-zero tileIDs by: the pillars started at tile 420 in castle1.j2t, but here start at the end of the old tileset, aka oldTileCount
	);
	jjLayerOrderSet(array<jjLAYER@> = {jjLayers[1], jjLayers[2], jjLayers[3], jjLayers[4], castleLayers[0], castleLayers[1], jjLayers[5], jjLayers[6], jjLayers[7], jjLayers[8]}); //insert the two layers from castle1.j2l between Layer 4 and Layer 5
}
void jjTriggerRock(uint8 id)
Activates all Rotating Rock events with the "RockID" parameter set to id, exactly like the Trigger Rock event.
uint64 jjUnixTimeMs()
Returns unix time in milliseconds, i.e. the number of milliseconds that have elapsed since 00:00:00 UTC, Thursday, 1 January 1970, not counting leap seconds.
uint64 jjUnixTimeSec()
Returns unix time in seconds, i.e. the number of seconds that have elapsed since 00:00:00 UTC, Thursday, 1 January 1970, not counting leap seconds.
void jjUpdateTexturedBG()
Forces JJ2+ to reconstruct the textured background from its relevant properties. This should be handled automatically now.
bool jjZlibCompress(const jjSTREAM &in input, jjSTREAM &out output)
Compresses data in input using the zlib library. The compression result is placed into output. Returns whether successful, which should almost always be true.
bool jjZlibUncompress(const jjSTREAM &in input, jjSTREAM &out output, uint size)
Attempts to uncompresses data in input using the zlib library. The decompression result is placed into output. For successful decompression, size must be at least the same value as the amount of bytes of the predicted output. Returns whether successful. The function can fail if input is not a valid compressed stream or size is too small to accomodate the output or too large to allocate memory required for performing the operation, as well as, rarely, for other reasons.

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: Preprocessor Directives

A preprocessor directive is a line included in an Angelscript file that does not obey standard AS syntax rules and is in fact not processed in the course of ordinary code running but is instead preprocessed—evaluated before anything else in the script runs, even onLevelLoad. They each begin with a pound sign (#) and should take up an entire line of the file; AngelScript's handling of preprocessor directives is pretty flexible, but it's best to put them all at the top of the file, outside of any code blocks, each given its own line. Though all the below examples use double quotes (") to surround directive arguments, single quotes (') may be used instead if so desired, so long as the same symbol is used on both sides.

#include "<filename>"
Similar to include directives/functions in other programming languages, #include loads another AngelScript file and adds all the code inside of it to the same module that the #include directive was written in. JJ2+ will treat everything in the included script exactly as if it were written directly in the including script, meaning that e.g. functions from one script may call functions from the other, but the same function name/return type may not be defined in both. A simple example use case might be a convenient single function to add a particle of a specific type at a specific location with specific initial speeds. Such a function would be useful enough that you might want to use it in multiple scripts, and this could be accomplished by placing it in its own file (addparticle.asc) and having those other scripts #include the file.
It's worth emphasizing that #include adds all the code of the included file to the global scope, no matter where the directive appeared in the including file. Writing an #include directive inside of a namespace, for example, will not cause everything in the included file to be defined within that namespace.
An included file may have any number of preprocessor directives of its own, including more #include directives. Any given file will only be #included at most once per script module, however, and JJ2+ will simply ignore attempts to the contrary; for example, if foo.j2as #includes both bar.asc and baz.asc but baz.asc also #includes bar.asc, then bar.asc will only be included the once. No error message is produced in this scenario.
#pragma require "<filename>"
Unlike #include, the require directive is not interpreted by the AngelScript section of JJ2+ at all but instead is used in netcode. (As such, it is unnecessary in levels made exclusively for local play.) When an online server cycles to a new level or gets a new joiner, clients are forced to download the .j2l, .j2t, and (if any) .j2as file the server is hosting, unless they already have an identical version of that same file conveniently located on their harddrive. The same treatment is also applied to any files that are #pragma required, unless the server's host doesn't have the file to begin with.
You should #pragma require any external file that your script uses unless you have a good reason not to. This includes tilesets or palettes used by jjPAL::load, custom sounds or animation files used by jjSampleLoad or jjANIMSET::load, and of course script files that you #include.
#pragma offer "<filename>"
Offering files is the less forceful version of requiring them: the server will still attempt to send all clients any files that have been #pragma offered, but the clients may decline to accept those files by pressing Esc at the downloading screen. This is the same treatment that is given to music files (unless the server has turned /uploadmusic off), and music files (as loaded by jjMusicLoad) are basically the archetypal kind of file that should be offered (at best), not required, due to their comparatively large filesizes.
(The overall order of files the client may attempt to download is, in case you're curious: the .j2l itself; the .j2t; any .j2as or .mut files; any #pragma required files; any #pragma offered files; the level's music.)
If the same file appears in both a #pragma require and a #pragma offer, across any number of different script files, it will be required instead of offered.
#pragma name "<mutator name>"
The #pragma name directive is only currently used by mutators, and must be included in the first ten lines of the file in order to be noticed. It serves no other purpose than to provide a human-readable name for JJ2+ to display in place of the mutator's filename in certain contexts, e.g. while listing active mutators.

Appendix: Mutators

Mutators are special AngelScript files that differ from the ones described in the introduction in three key ways:

  1. Instead of being connected to individual .j2l files, any mutator can be run in any level at all. (This is not always a good idea—for example, a mutator script designed solely for Treasure Hunt games wouldn't be much help run in a Race level—but it is allowed.) Mutators are enabled or disabled using the /mutators command in chat messages, and clients joining a server with one or more mutators running will download those scripts and run them just like they would an ordinary script file.
  2. To signify this and to make for easier organization, mutators use the file extension .mut instead of .j2as.
  3. Mutators may not define the onFunction# or onPlayerTimerEnd hook functions, because they are too closely tied to specific level designs. Mutators may define functions that happen to use those names, but they will not be called by JJ2 under normal circumstances. (Mutators can however use the jjPLAYER method timerFunction to make a player use a timer end function defined inside a mutator script, as well as access the global array jjEnabledASFunctions or the global function jjEnableEachASFunction.)

Otherwise, mutators have all the same options available to them that a level's primary script does, are run concurrently by both clients and servers, and may be written for any number of purposes. You could write a mutator that acts like an IRC bot and reacts to certain chat phrases, or a mutator to replace JJ2's normal health system with a new one of your devising, or a mutator to draw a minimap of players to some corner of the screen, or plenty of other things. Since any number of different mutators may be loaded at once, you should try to make each one do as few distinct tasks as possible.

To be specific about how exactly mutators work: each one is loaded by JJ2+'s AngelScript engine as a script module, meaning each has access to the same set of JJ2+-defined global variables, functions, classes, etc., but cannot access the other modules' locally defined variables or functions. Two mutators may each define a function void foo(), but since they live in separate modules, there will be no naming conflict. (The primary .j2as script of a level is also loaded as a distinct script module.)

Because mutators do not need to worry about naming conflicts with other mutators (or the primary script), individual hooks may be multiply defined across different modules. Three different mutators may all define a void onMain() function, for example, and JJ2+ will run all three of them every tick. Specifically, for any multiply defined hook, the version (if any) in the primary script will be run first, followed by the versions (if any) in each active mutator, in alphabetical order. It becomes the job of the server host (or remote admins) to pick a list of mutators that do not run code at cross purposes with one another.

For example, suppose that foo.j2l has a primary script foo.j2as with the function void onPlayer(jjPLAYER@ p) { p.coins = 5; }. The server is running bar.mut, containing void onPlayer(jjPLAYER@ p) { ++p.coins; }, and baz.mut, containing void onPlayer(jjPLAYER@ p) { jjDrawString(p.xPos, p.yPos - 40, "" + p.coins); }. The net result of the three script modules will be that the number 6 (5+1) will always be drawn above each local player's head.

Certain hooks—e.g. onLocalChat and onDrawHealth—have bool return values, where "true" means roughly "this script has performed all necessary actions relevant to this event," e.g. if onDrawHealth returns true then JJ2 will not run its normal health-drawing code but instead assumes the script is somehow doing the job of presenting that information to the player. If two different mutators each define onDrawHealth, and the first mutator's version returns true, the second mutator's version will still be run, but JJ2 will not draw the normal hearts no matter what value the second mutator's version returns. The return values are ORed together and it only matters that at least one of them (no matter which one) returns true.

Besides onFunction# and onPlayerTimerEnd, the only exception to the above rule is onReceive, which is only run at most once per jjSTREAM packet received. The global function jjSendPacket has an argument toScriptModuleID specifying which script module should receive the packet, defaulting to jjScriptModuleID, which is the index of the script module calling the function. The values of jjScriptModuleID match up with the order of modules that a multiply defined hook will be called in, so for example, in a server running foo.j2as and baz.mut and bar.mut, jjScriptModuleID will equal 0 within foo (primary script is always 0), 1 within bar, and 2 within baz (alphabetically later than bar). Therefore if jjScriptModuleID is passed as the value for toScriptModuleID, the packet will be received by the onReceive hook defined in the same module as jjSendPacket was called in—but if toScriptModuleID equals 0, the packet will be received by onReceive defined in the primary script instead. This is rarely a good idea but could be used by mutators designed to extend specific scripts.

Mutators can use all the same preprocessor directives that a primary script can, including or requiring or offering any supplementary files relevant to their execution. In fact, they are (currently) the only kind of script that can use #pragma name.

Even if you are not writing a mutator, it is important to be aware that they exist and might be run in parallel with your level's primary script (assuming you are creating a multiplayer level). For example, it might be tempting to repurpose jjPLAYER::coins to keep track of some other player-related property in a level with no coins in it, but it's better to declare your own non-member variable for that, in order to avoid potential conflicts with a mutator using jjPLAYER::coins for something else. Certainly there will always be scripts that simply cannot functionally coexist with each other (a primary script where everyone has airboards; a mutator that disables all flight), but you might as well do your best to restrict such cases to times when the basic purposes of the scripts are in conflict, instead of just when one of them isn't coded carefully enough.

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, for when an object gets destroyed; DEACTIVATE, for when an object passes out of memory in single player mode; EXPLODE, for dying bullets; ACTION, for collisions between players/bullets and some more complicated objects; 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).

NORMAL
TEXTURE::NORMAL is the default JJ2 background texture, drawn by Nick Stadler.
BLADE
TEXTURE::BLADE, drawn by Blade.
CORRUPTEDSANCTUARY
TEXTURE::CORRUPTEDSANCTUARY, drawn by Disguise.
DESOLATION
TEXTURE::DESOLATION, drawn by BlurredD.
DIAMONDUSBETA
TEXTURE::DIAMONDUSBETA, drawn by Pyromanus.
ICTUBELECTRIC
TEXTURE::ICTUBELECTRIC, drawn by P4ul.
MEDIVO
TEXTURE::MEDIVO, drawn by Nick Stadler.
MEZ02
TEXTURE::MEZ02, drawn by Mez.
MUCKAMOKNIGHT
TEXTURE::MUCKAMOKNIGHT, drawn by Haze.
PSYCH
TEXTURE::PSYCH, drawn by Nick Stadler.
RANEFORUSV
TEXTURE::RANEFORUSV, drawn by Violet CLM.
WINDSTORMFORTRESS
TEXTURE::WINDSTORMFORTRESS, drawn by BlurredD.
WISETYNESS
TEXTURE::WISETYNESS, drawn by Toxic Bunny.
WTF
TEXTURE::WTF, drawn by cooba.
XARGON
TEXTURE::XARGON, drawn by Spaztic.

SPRITE::Mode

Sprite modes dictate the result of the combination of three factors: the basic pixel being drawn (e.g. an individual color in a sprite, or the overall color of a rectangle); the background pixel it is being drawn onto, and a uint8 param value that dicates opacity, amount of palette shifting, or other such effects depending on the sprite mode in question. The simplest cases are modes like SPRITE::NORMAL, which draws the basic pixel without any modification, or SPRITE::TRANSLUCENT, which partially blends the basic pixel with the background pixel, but there are many others, and you are encouraged to try them all and decide for yourself which is best for your situation. (Hint: Usually SPRITE::NORMAL.)

ANIM::Set

Values used as indices to the jjAnimSets array, plus the setID parameters of the jjOBJ method determineCurAnim, the jjCANVAS method drawSprite, and the global function jjDrawSprite, as well as the jjPLAYER setID property. 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.

The above constants are all used for accessing animations in the anims.j2a file. JJ2+ comes with its own plus.j2a file, however, with its own set of constants listed below. Because plus.j2a, unlike anims.j2a, has the potential to change in future releases of JJ2+, you should be aware of the following guarantees:

While the order of anim sets within plus.j2a will not change, their indices might. For example, currently ANIM::PLUS_BETA immediately follows ANIM::PLUS_AMMO, but in some future release of JJ2+, another anim set ANIM::PLUS_BANHAMMER might be added between the two. Treat the ANIM::Set constants as magic numbers and do not subtract them from each other or perform any other such operations that might break in future releases.

Within an anim set in plus.j2a, the order of animations will not change, but their number might. If new animations are added to an existing anim set, they will be appended to the end, so jjAnimations[jjAnimSets[ANIM::PLUS_SCENERY].firstAnim] will always be a leaf, but jjAnimations[jjAnimSets[ANIM::PLUS_WARP].firstAnim + 2] does not currently exist but might someday later.

Within an animation in plus.j2a, the number of frames may change. For example, jjAnimations[jjAnimSets[ANIM::PLUS_COMMON].firstAnim + 6] contains the stars used by sugar rushes, and its frameCount property equals 2. However, a future release of JJ2+ might add a third star and up frameCount to 3.

PLUS_AMMO
Animations used by bullets and related code; loaded in every level. The first five animations deal with unfinished code and should be ignored for now; firstAnim+5 is Lori's fastfire pickup/blaster icon, and firstAnim+6 is the same but powered up.
PLUS_BETA
Animations from beta versions of JJ2 that were replaced for the final release, as accessible through the JJ2+ menu item Plus->Beta Sprites. You can load this set if you want to use some or all of the animations regardless of whether the user has checked that menu item. The order of animations is 0: super gem; 1: gem; 2: rect gem; 3: sodas (two frames, 0=Coke and 1=Pepsi).
PLUS_COMMON
All sorts of generally useful animations, loaded in every level. In order they are 0: Gun8 ammo+15 crate; 1: Gun9 ammo+15 crate; 2: Laser Shield monitor; 3: unfinished; 4: unfinished; 5: Lori blaster powerup monitor; 6: sugar rush stars; 7: Carrotade logo (pickup-sized).
PLUS_CONTINUE
Continue animations for Lori, who was missing them in the regular 1.24 release. Same sorts/order of animations as in ANIM::CONTINUE, probably not too useful for general purposes
PLUS_FONT
This is a special case animation that replaces ANIM::FONT to the point that JJ2+ will refuse to load ANIM::FONT from anims.j2a and will load ANIM::PLUS_FONT from plus.j2a instead. It's loaded in every level as ANIM::FONT, so there's not much point in you doing anything with it separately.
PLUS_REPLACEMENTS
Another special case animation: PLUS_REPLACEMENTS is never loaded as a whole set, but instead individual animations are loaded from it to replace individual animations from anims.j2a under specific circumstances. You shouldn't need to access this one, because its contents will already be in game when they're needed; just in case, though, the first animation is the morph box animation with a yellow arrow (used in 1.24 but not 1.23), and the second and third animations are different sizes of the Jazz 2 logo using the menu palette.
PLUS_RETICLES
Images used by mouse aim mode, specifically 1=WEAPON::NORMAL, 2=WEAPON::MISSILE, 3=WEAPON::POPCORN, 4=WEAPON::CAPPED, and 0 is the smaller target used for displaying fire angle as separate from cursor position. Although each of these animations is only one frame long, JJ2+ can play longer animations in their stead if you mess with the relevant jjANIMATION properties.
PLUS_SCENERY
Useful animations for various graphical effects, not loaded by default. In order they are 0=Leaf (used by the Snow event if given the right parameters), 1=Fire, 2=Lava, 3=Bee hive. 4=Various destructible blocks (0: Blank; 1-2: Trigger; 3: Speed destruct; 4: Buttstomp; 5: Unknown; 6-9: Timed destruct; 10: Destruct; 11-19: Weapon-specific destruct; 20-23: Shield-specific destruct).
PLUS_WARP
A pair of animations used by the Warp event when its ShowAnim parameter is set to 1.
CUSTOM[256]
Finally, the CUSTOM array provides you with 256 slots in the jjAnimSets array that are guaranteed not to be used by any non-AngelScript code. You should generally use these when loading or creating your own animations unless you're specifically replacing a specific existing animation, e.g. replacing the Tuf Turtle enemy (who has only one animation and is therefore very easy to work with) with a slime creature or something. Unlike the other constants, the ANIM::CUSTOM values do not correspond to any animations in any .j2a file and should never be used in jjANIMSET::load.

RABBIT::Anim

For the most part, the anims.j2a files in versions 1.23 and 1.24 of Jazz 2 are just the same, differing primarily in that 1.24's has a few extra anim sets, in particular ANIM::LORI and all the X* and Z* enemies. The other difference is that 1.24's removes a number of animations from the Jazz and Spaz anim sets that were not used in the final game, such as the animations for climbing onto a ledge. In practice this means that the animation for Jazz or Spaz on an airboard, for example, is the first animation in the set in 1.24, but the second in 1.23. Since you should try to make your code as version-independent as possible, this is kind of annoying.

The RABBIT::Anim enum solves this problem for you by providing a series of constants corresponding to the indices of each animation appearing in both 1.23 and 1.24, with their values adjusted internally depending on which version of JJ2 you're running. Thus RABBIT::AIRBOARD equals 0 in 1.24 and 1 in 1.23; RABBIT::HURT equals 29 in 1.24 and 32 in 1.23; and so on. The names are our best guesses for how they were originally named, working from some internal variable names as well as the fact that every animation in anims.j2a is ordered alphabetically.

To get the animation index of an actual rabbit player, suitable for comparing to RABBIT::Anim values, subtract the first animation of their anim set from their current animation. To see if a rabbit player p is currently standing still, for example, you can check if (p.curAnim - jjAnimSets[p.setID].firstAnim == RABBIT::STAND), and that will work no matter whether the player is running 1.23 or 1.24, or playing as Jazz, Spaz, or Lori.

Thanks

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