#pragma name "Bunny Must Die! guns"
/*
	Gives all weapons enhanced graphics based on sprites ripped from Bunny Must Die!, without altering gameplay.
	If you like this mutator, please support Rockin' Android by visiting http://bunnymustdie.com/ and buying the
	game or at least downloading the free demo!
*/
#pragma require "weaponBunny.j2a"

const uint8 DARKBLUE = 39; //This very dark blue color from the sprite palette will be used as the hue for drawing a trail behind running players:
class Trace{
	int xPos, yPos;
	int direction;
	uint curFrame;
	Trace(){}
	Trace(jjPLAYER@ play) {
		xPos = int(play.xPos); //Since each Trace instance will be drawn more than once, it's marginally faster to do the math of converting floats to ints upon construction instead of upon drawing.
		yPos = int(play.yPos);
		direction = play.direction;
		if (play.antiGrav) direction ^= 0x40; //This is the easiest way to reverse a sprite's vertical orientation. SPRITE::FLIPNONE ^ 0x40 = SPRITE::FLIPV, SPRITE::FLIPH ^ 0x40 = SPRITE::FLIPHV, and so on. Because jjPLAYER objects, unlike jjOBJs, store their reverse gravity in jjPLAYER::antiGrav instead of in jjPLAYER::direction, this operation must be applied here, while setting up the player's trail.
		curFrame = play.curFrame;
	}
};
//A 32-long array of arrays of Traces, each initially empty. (32 is the maximum of players in a single game of JJ2, and so the length of the jjPlayers[] array.)
array<array<Trace>> PlayerTraces(32, array<Trace>(0));

//When setting up a custom .j2a file, keeping track of its various animations (and/or anim sets) in an enum is convenient, since it lets you rearrange them freely while you develop the .j2a and script in tandem. Writing "anSYLPHPU" is also much easier to understand than "1", of course.
enum CustomAnims {anSYLPH, anSYLPHPU, anFAIRYSHOT, anBLACKWING, anEXPL, anMISSILE, anFIRERED, anFIREBLUE, anENEMY, anSPIKEHAMMER, anSPIKECHAIN, anROCKET, anCROSSHAIR, anSHIELD, anPUMPKIN};

//weaponBunny.mut is designed to interact with other scripts as well as possible, and uses only a single anim set for maximum convenience.
//However, simply loading all the animations into ANIM::CUSTOM[0] would interact poorly with any other script that also used ANIM::CUSTOM[0],
//so this quick function finds the first empty slot in the CUSTOM array and loads weaponBunny's animations into that.
uint customAnimID = 0;
void findCustomAnimID() {
	while (jjAnimSets[ANIM::CUSTOM[customAnimID]].firstAnim != 0) //A loaded anim set will have a firstAnim value of 1 or higher.
		++customAnimID; //Keep searching...
	//If all 256 entries in ANIM::CUSTOM are already taken, this loop will crash JJ2+. Oh well! The mutator couldn't run properly then anyway.
	
	jjAnimSets[ANIM::CUSTOM[customAnimID]].load(0, "weaponBunny.j2a");
}

//Now that there's code in place above for loading the animations from weaponBunny.j2a, there'll need to be functions to call them. However,
//because this mutator is designed for maximum coexistence, we're only going to replace the jjOBJ::behavior values of bullets that haven't
//already had them replaced by other scripts. If you're running bubblegun.mut to replace fireball bullets, for instance, weaponBunny will
//let you keep your bubble gun and only replace all the other bullet behaviors that haven't been touched.
bool replaceBulletPreset(OBJECT::Object eventID, BEHAVIOR::Behavior behaviorOld, jjVOIDFUNCOBJ@ behaviorNew) {
	if (jjObjectPresets[eventID].behavior == behaviorOld) {
		jjObjectPresets[eventID].behavior = behaviorNew;
		return true;
	}
	jjDebug("Could not replace object " + eventID + "'s behavior!"); //You should really use AngelScript's built-in formatInt function, but this is quicker for simple jjDebug strings.
	return false;
}

void onLevelLoad() {
	jjPalette.color[DARKBLUE].green = jjPalette.color[DARKBLUE].red = 0; //dark blue should be blue only
	jjPalette.apply(); //even the tiniest change requires an entire jjPAL::apply call
	
	findCustomAnimID();
	
	//Replace as many bullet behaviors as possible.
	replaceBulletPreset(OBJECT::BLASTERBULLET, BEHAVIOR::BULLET, SylphShooter);
	replaceBulletPreset(OBJECT::BLASTERBULLETPU, BEHAVIOR::BULLET, SylphShooterPU);
	replaceBulletPreset(OBJECT::BOUNCERBULLET, BEHAVIOR::BOUNCERBULLET, Bouncer);
	replaceBulletPreset(OBJECT::BOUNCERBULLETPU, BEHAVIOR::BOUNCERBULLETPU, SpikeHammerPU);
	replaceBulletPreset(OBJECT::ICEBULLET, BEHAVIOR::BULLET, Ice);
	replaceBulletPreset(OBJECT::ICEBULLETPU, BEHAVIOR::ICEBULLETPU, IcePU);
	replaceBulletPreset(OBJECT::SEEKERBULLET, BEHAVIOR::SEEKERBULLET, RoppongiMissile);
	replaceBulletPreset(OBJECT::SEEKERBULLETPU, BEHAVIOR::SEEKERBULLET, RoppongiMissile);
	replaceBulletPreset(OBJECT::RFBULLET, BEHAVIOR::RFBULLET, YellowRocket);
	replaceBulletPreset(OBJECT::RFBULLETPU, BEHAVIOR::RFBULLET, BlueLaser);
	replaceBulletPreset(OBJECT::TOASTERBULLET, BEHAVIOR::TOASTERBULLET, Toaster);
	replaceBulletPreset(OBJECT::TOASTERBULLETPU, BEHAVIOR::TOASTERBULLET, ToasterPU);
	replaceBulletPreset(OBJECT::TNT, BEHAVIOR::TNT, Crosshair);
	replaceBulletPreset(OBJECT::FIREBALLBULLET, BEHAVIOR::BULLET, FairyShot);
	replaceBulletPreset(OBJECT::FIREBALLBULLETPU, BEHAVIOR::BULLET, FairyShot);
	replaceBulletPreset(OBJECT::ELECTROBULLET, BEHAVIOR::ELECTROBULLET, BlackWing);
	replaceBulletPreset(OBJECT::ELECTROBULLETPU, BEHAVIOR::ELECTROBULLET, BlackWing);
}

void onMain() {
	if (jjGameTicks & 1 == 1) { //This line isn't really needed, but then the contents of this loop don't need to run _every_ single tick, so we might as well speed things up slightly.
		for (int i = 0; i < 32; ++i) {
			jjPLAYER@ play = jjPlayers[i];
			if (!play.isInGame || play.curFrame == 0) //Since the Trail class specifically uses jjPLAYER::curFrame, it's best to make absolutely sure it doesn't equal 0, since trying to draw curFrame 0 goes badly.
				continue;
			
			array<Trace>@ traces = @PlayerTraces[i];
			if (traces.length > 4) traces.removeAt(0); //The trail should never be more than a few traces long, so keep deleting the oldest Trace elements
			traces.insertLast(Trace(play)); //...and then inserting new ones.
			
			if (play.blink != 0) //If the player can't be hurt, display a shield sprite over them.
				//This is the first drawing operation in the file, and we're using jjDrawSprite instead of jjDrawSpriteFromCurFrame because this
				//version is easier to read. The first two arguments specify _where_ in the level the sprite should be drawn, in this case, directly
				//on top of the invincible (blinking) player. The next three specify _which_ sprite to draw: the second (1) frame of the first (0)
				//animation of ANIM::CUSTOM[customAnimID]. Next we specify _how_ to draw the sprite: using the Dodge color blend mode, set to
				//full opacity (255). Finally the "3" argument says _when_ to draw the sprite: in layer 3, after the player sprite is already drawn.
				jjDrawSprite(play.xPos, play.yPos, ANIM::CUSTOM[customAnimID], anSHIELD, 0, 1, SPRITE::BLEND_DODGE, 255, 3);
		}
		
		//As the last part of the onMain loop, we're going to look for objects with behavior BEHAVIOR::PEPPERBULLET. This is basically
		//the same sort of code as in replaceBulletPreset above, but applied to individual objects instead of object presets, because
		//pepper spray doesn't have a jjObjectPresets entry of its own.
		for (int i = jjObjectCount - 1; i >= 1; --i) { //jjObjects[0] is never useful; doing a reverse search means we only have to check jjObjectCount once and can rely on constant numbers for every loop. This is a meaningless degree of optimization and you should feel free to search from 1 to jjObjectCount yourself instead.
			jjOBJ@ obj = jjObjects[i];
			if (obj.behavior == BEHAVIOR::PEPPERBULLET)
				obj.behavior = Pumpkin; //Both pepper spray replacements share the same behavior; we'll test if the bullet is powered-up or not later.
		}
	}
}

//The onDrawLayer# hooks can be useful under a bizarre array of circumstances; here we're using this one to draw sprites before player
//sprites are drawn but after tiles are drawn, which is the ideal position for a trail that appears behind a player.
void onDrawLayer4(jjPLAYER@ viewingPlayer, jjCANVAS@ screen) {
	for (int i = 0; i < 32; ++i) {
		jjPLAYER@ play = jjPlayers[i];
		if (!play.keyRun || !play.isInGame || play.curFrame == 0) //same conditions as in onMain above, but only drawing trails for players who are currently running.
			continue;
		array<Trace>@ traces = @PlayerTraces[i];
		for (int j = 0; j < int(traces.length) - 1; ++j) {
			Trace@ trace = @traces[j];
			//This is a more complex draw, since we're drawing each frame of the trace _two_ times: first as a single color, using the DARKBLUE
			//constant from the top of the file, then using BLEND_LUMINANCE, which preserves the brightness of the curFrame but uses the color
			//of the underlying pixels--which just happen to have been primed to all be SINGLECOLOR DARKBLUE. The varying opacity (j<<6) in
			//the second draw call makes trace frames closer to the player be drawn more visibly than those farther away.
			screen.drawSpriteFromCurFrame(trace.xPos, trace.yPos, trace.curFrame, trace.direction, SPRITE::SINGLECOLOR, DARKBLUE);
			screen.drawSpriteFromCurFrame(trace.xPos, trace.yPos, trace.curFrame, trace.direction, SPRITE::BLEND_LUMINANCE, j<<6);
			//(Also note that in any hook with a jjCANVAS@ object, jjCANVAS::draw* methods are used instead of global jjDraw* functions.)
		}
	}
}

int getBulletAngle(jjOBJ@ obj) { //Maths! This is pretty much the same code JJ2+ uses to draw blaster bullets (and some others) as rotated sprites.
	return int(atan2( //This example script cannot and will not teach you trigonometry.
            (obj.xSpeed >= 0) ? obj.ySpeed : -obj.ySpeed,
            abs(obj.xSpeed)
	) * -512.0 * 0.318309886142228); //convert from 0-1 to 0-1023
}
int getBulletDirection(jjOBJ@ obj) { //JJ2 accepts direction 0 to have sprites face right, i.e. not be flipped horizontally. That's all well and good, but there are some functions (jjDrawResizedSprite; jjDrawRotatedSprite) that expect a float for the sprite's width multiplier, and 1 is much more helpful than 0 under those circumstances.
	if (obj.xSpeed >= 0)
		return 1;
	return -1;
}

/*
	The rest of this file is a long list of custom bullet behaviors, all following basically
	the same pattern: check if the bullet has exploded yet. If not, make it use its normal
	(internal to JJ2+) bullet behavior, then draw some fancy sprites on top of it. If so,
	either delete the bullet right away or else do some fancy explosion drawing for a while.
	Commentary will grow sparse after the first handful, since they're really all pretty much
	variations on the same few concepts.
*/
void SylphShooter(jjOBJ@ obj) { //Replaces non-powered-up blaster with a glowing sword that goes flying away when it hits a wall.
	bool naturalCauses = obj.counter >= int(obj.counterEnd); //BEHAVIOR::BULLET will explode if the bullet is alive too long, not just if it hits a wall. Making this check before calling jjOBJ::behave lets us see if that's what's going to kill the bullet this tick.
	obj.behave(BEHAVIOR::BULLET, false); //The core of most of these functions is a jjOBJ::behave call to the bullet's original behavior. The second argument, false, means that BEHAVIOR::BULLET will skip all its internal drawing operations and only move the bullet around, leaving the drawing to this script.
	
	if (obj.counter >= 5) //For the first few ticks of the bullet's life, don't draw anything at all. This mimics many native bullet behaviors, which don't like to draw themselves until they've moved out of the rabbit sprite.
		jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anSYLPH, (obj.counter >> 1) & 3, getBulletAngle(obj), getBulletDirection(obj)); //This is what we defined getBulletAngle and getBulletDirection above for. The last explicit argument here is xScale, so if it's -1, the sprite gets flipped horizontally before it is rotated. Since the anSYLPH animation was created in weaponBunny.j2a to have only four frames, (obj.counter >> 1) & 3 is sufficient for picking a frame number.
	if (obj.state == STATE::EXPLODE || obj.state == STATE::KILL || obj.state == STATE::DEACTIVATE) { //Various reasons it could be time to destroy a bullet object. EXPLODE is the most common; KILL can be triggered by exiting the level area; DEACTIVATE happens in local SP games when the bullet moves too far away from the rabbit who fired it.
		if (!naturalCauses) { //The bullet hit a wall.
			jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, SylphShard); //Instead of carrying out a death animation routine inside SylphShooter, another object with another behavior will be created instead. This full use of jjAddObject copies most of the new object's properties from jjObjectPresets[OBJECT::SHARD] (e.g. eventID, bulletHandling), but gives it the behavior SylphShard instead of the default BEHAVIOR::SHARD.
			obj.delete();
		} else //The bullet died of old age.
			obj.behavior = BEHAVIOR::BULLET; //Revert to ordinary bullet behavior in order to show ordinary blaster bullet death animation.
	}
}
void SylphShard(jjOBJ@ obj) { //A fairly simple behavior that lasts until jjOBJ::counterEnd, initialized at 0 and decreased by 4 every tick, hits 0, at which point the object will be deleted. Along the way, it's drawn as a rotating sprite (with counterEnd as the angle, multiplied by sixteen) with decreasing opacity (again, counterEnd), meaning by the time it's deleted it will have become practically invisible.
	if (obj.state == STATE::START) {
		obj.state = STATE::ROTATE;
		obj.playerHandling = HANDLING::PARTICLE; //A generic playerHandling value that means it won't have any effect if the player runs into it.
		obj.counterEnd = 252;
		jjOBJ@ creator = jjObjects[obj.creator]; //In general it would be good form to make sure that the object's creatorType is CREATOR::OBJECT, but since this behavior is only ever used on objects created from objects with the SylphShooter behavior above, that's not needed here.
		obj.direction = -getBulletDirection(creator);
		obj.xSpeed = (1 + (jjRandom() & 3)) * obj.direction;
		obj.ySpeed = -5;
	}
	obj.xPos += obj.xSpeed;
	obj.yPos += (obj.ySpeed += .25); //That .25 value is what jjOBJ::yAcc (acceleration) is for, but that's not necessary when you're writing a single-use behavior like this.
	jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anSYLPH, 0, obj.counterEnd << 4, obj.direction, 1, SPRITE::BLEND_NORMAL, obj.counterEnd);
	if ((obj.counterEnd -= 4) == 0)
		obj.delete();
}

//Before reaching the next bullet behavior function, we're going to define a helper function,
//objectInView, and then a much more generic behavior function Explosion, which will handle
//most of the fire effects used by the various bullets in this mutator.
bool objectInView(jjOBJ@ obj) { //JJ2 does a lot of this sort of thing on its own--in particular, it won't draw sprites if they're fully offscreen--but sometimes it can be handy to check in the script itself to see if some code really needs to run or if it'd be totally invisible to the local player/s. Since sprites drawn by the Explosion class can lag the game a bit, and jjDrawRotatedSprite (which it uses) is the most expensive drawing function, it seems like a reasonable concern to draw as few of them as possible.
	for (int i = 0; i < jjLocalPlayerCount; ++i) {
		jjPLAYER@ play = jjLocalPlayers[i];
		if (obj.xPos > play.cameraX - 64 && obj.xPos < play.cameraX + jjSubscreenWidth + 64 && obj.yPos > play.cameraY - 64 && obj.yPos < play.cameraY + jjSubscreenHeight + 64) //This is most of the camera-related properties. There's also jjPLAYER::subscreenX and jjPLAYER::subscreenY, which are useful in splitscreen (or 3D mode) for determining where in the game window the current screen you're drawing to is.
			return true;
	}
	return false;
}
void Explosion(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		if (!objectInView(obj)) {
			obj.delete();
			return;
		}
		obj.state = STATE::EXPLODE;
		obj.frameID = 0;
		obj.counter = 0;
		obj.lightType = LIGHT::NONE;
		obj.age = jjRandom() & 1023; //age here = angle. The age property is never used by JJ2+, so even if this weren't a 100% custom behavior, it'd be safe to use for this purpose.
		obj.doesHurt = 4; //doesHurt = layer to draw sprite in front of. Like age, doesHurt is never used by JJ2+. This will be a default value, though other behaviors creating objects with the Explosion behavior may want to change it once the Explosion object has been created.
		return;
	}
	obj.xPos += obj.xSpeed += obj.xAcc; //Unlike SylphShooter above, Explosion objects may be created with a range of xAcc and yAcc
	obj.yPos += obj.ySpeed += obj.yAcc; //properties, so we're using very generic movemenet code here.
	if (++obj.counter >= obj.animSpeed) {
		obj.counter = 0;
		if (++obj.frameID == 8) { //All explosions are eight frames long, so when this hits frameID 8, it's time for it to be deleted.
			obj.delete();
			return; //Don't draw deleted objects.
		}
	}
	obj.determineCurFrame();
	jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.age += 32, obj.xOrg, obj.xOrg, SPRITE::BLEND_SCREEN, 255, obj.doesHurt); //BLEND_SCREEN is used by all explosion effects in this mutator and is a lightening operation, like BLEND_LIGHTEN but a bit more complicated. The more BLEND_SCREEN sprites are drawn close together, the brighter the resulting pixels will be.
}

void SylphShooterPU(jjOBJ@ obj) { //Another bullet behavior. This one's main features are the "length" variable, which determines how large the sprite should be drawn (it's very large at full 1x1 size), and the fact that upon explosion, it creates an object with an Explosion behavior (defined above) instead of SylphShard this time.
	float length = ++obj.age * 0.05;
	if (obj.state != STATE::EXPLODE && obj.state != STATE::KILL && obj.state != STATE::DEACTIVATE) {
		obj.behave(BEHAVIOR::BULLET, false);
		if (length > 1)
			length = 1;
		jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anSYLPHPU, 0, getBulletAngle(obj), length * getBulletDirection(obj), length);
	} else {
		jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, Explosion)];
		if (spark.isActive) {
			spark.determineCurAnim(ANIM::CUSTOM[customAnimID], ((jjRandom() & 1) == 1) ? anFIREBLUE : anFIRERED);
			spark.animSpeed = 3; //Genuinely, the Explosion's speed of animation.
			spark.xSpeed = spark.ySpeed = 0; //A very basic Explosion instance that doesn't move at all but merely animates until it runs out of frames and is deleted.
			spark.xOrg = 3; //Explosion uses "xOrg" for the scale the sprites are drawn at, since it has no use for the actual meaning of the property.
		}
		obj.delete();
	}
}
void FairyShot(jjOBJ@ obj) { //It doesn't get much simpler than this. FairyShot replaces fireball bullets and has almost the same effect as calling jjOBJ::determineCurAnim on the fireball bullets' jjObjectPresets entries, except that that would affect pixel collision.
	obj.behave(BEHAVIOR::BULLET, false);
	if (obj.counter >= 3) 
		jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anFAIRYSHOT, (obj.var[6]&8 != 0) ? 1 : 0, getBulletAngle(obj), getBulletDirection(obj));
	if (obj.state == STATE::EXPLODE) {
		obj.delete();
	}
}
void BlackWing(jjOBJ@ obj) { //BlackWing replaces electro-blaster bullets. Unlike the above behaviors, this does not call jjOBJ::behave, because electro-blaster bullets produce a lot of sparks that can't be suppressed by passing false to jjOBJ::behave (they're jjPARTICLE instances). So here we rewrite the entirety of electro-blaster's movement code instead, which fortunately is very simple, in addition to some basic rotating sprite drawing and Explosion creation.
	if (obj.state == STATE::START) {
		obj.state = STATE::FLY;
		obj.var[0] = getBulletAngle(obj) + 128*getBulletDirection(obj);
	} else if (obj.state == STATE::EXPLODE || obj.state == STATE::KILL || obj.state == STATE::DEACTIVATE)
		obj.delete();
	else {
		if (++obj.counter > 54) {
			obj.state = STATE::EXPLODE;
			return;
		}
		obj.xSpeed += obj.xAcc;
		obj.xPos += obj.xSpeed + obj.var[7]/65536.0;
		obj.ySpeed += obj.yAcc;
		obj.yPos += obj.ySpeed;
		
		if (obj.counter < 8)
			return;

		if (jjStrongPowerups && obj.eventID == OBJECT::ELECTROBULLETPU) { //In JJ2+, pepper spray and electro-blaster only use their special powerup graphics if jjStrongPowerups is on, so we might as well emulate that here.
			uint rand = jjRandom();
			jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, Explosion)];
			if (spark.isActive) {
				spark.determineCurAnim(ANIM::CUSTOM[customAnimID], anEXPL);
				spark.animSpeed = 4;
				spark.xSpeed = -obj.xSpeed + (rand & 3) - 1.5;
				spark.ySpeed = -obj.ySpeed + ((rand >> 2) & 3) - 1.5;
				spark.xAcc = -obj.xAcc;
				spark.yAcc = -obj.yAcc;
				spark.xOrg = 1;
			}
		}
		
		obj.var[0] = obj.var[0] + 32 * getBulletDirection(obj); //AngelScript doesn't yet support compound assignment operators (e.g. +=) on indexed properties like this
		jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anBLACKWING, (++obj.age >> 1) % 3, obj.var[0], getBulletDirection(obj), 1, SPRITE::NORMAL, 0, 3);
	}
}

void RoppongiMissile(jjOBJ@ obj) {
	if (obj.state == STATE::EXPLODE || obj.state == STATE::KILL || obj.state == STATE::DEACTIVATE) {
		for (int i = 0; i < 9; ++i) { //Create nine large explosions using any of the different explosion animations.
			uint rand = jjRandom();
			jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos + (rand & 31) - 15.5, obj.yPos + ((rand >> 5) & 31) - 15.5, obj.objectID, CREATOR::OBJECT, Explosion)];
			if (spark.isActive) {
				spark.determineCurAnim(ANIM::CUSTOM[customAnimID], ((rand & 2) == 0) ? anEXPL : (obj.var[6]&8 != 0) ? anFIREBLUE : anFIRERED);
				spark.animSpeed = 3;
				spark.xSpeed = spark.ySpeed = 0;
				spark.xOrg = (obj.var[6]&8 != 0) ? float(4) : float(2.5);
			}
		}
		obj.delete();
	} else {
		if (obj.counter >= 10) //don't draw while inside bunny
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anMISSILE, (obj.counter >> 1) & 3, getBulletAngle(obj), getBulletDirection(obj), 1, SPRITE::NORMAL, 0, 3);
		jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, Explosion)];
		if (spark.isActive) {
			spark.determineCurAnim(ANIM::CUSTOM[customAnimID], (obj.var[6]&8 != 0) ? anFIREBLUE : anFIRERED);
			spark.animSpeed = 4;
			spark.xSpeed = -obj.xAcc; spark.ySpeed = -obj.yAcc;
			spark.xOrg = 1;
		}
	}
	obj.behave(BEHAVIOR::SEEKERBULLET, false);
}

void Ice(jjOBJ@ obj) {
	if (obj.state != STATE::EXPLODE && obj.state != STATE::KILL && obj.state != STATE::DEACTIVATE) {
		obj.behave(BEHAVIOR::BULLET, false);
		if (obj.counter > 4)
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anENEMY, 0, getBulletAngle(obj), getBulletDirection(obj), 1, SPRITE::TRANSLUCENT);
	} else {
		obj.behavior = BEHAVIOR::BULLET;
	}
}
void IcePU(jjOBJ@ obj) {
	if (obj.ySpeed != 0)
		Ice(obj);
	else {
		if (obj.state == STATE::START)
			obj.age = 248;
		if (obj.state != STATE::EXPLODE && obj.state != STATE::KILL && obj.state != STATE::DEACTIVATE)
			obj.behave(BEHAVIOR::BULLET, false);
		else if ((obj.age -= 8) == 0)
			obj.delete();
		const float size = 1 + (248-obj.age)*0.025;
		jjDrawResizedSprite(obj.xPos, obj.yPos + 21, ANIM::CUSTOM[customAnimID], anENEMY, 1, size*getBulletDirection(obj), size, SPRITE::BLEND_SCREEN, obj.age);
	}
}

void Bouncer(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::BOUNCERBULLET, false);
	if (obj.state == STATE::EXPLODE || obj.state == STATE::KILL || obj.state == STATE::DEACTIVATE) {
		obj.delete();
	} else {
		jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, Explosion)];
		if (spark.isActive) {
			spark.determineCurAnim(ANIM::CUSTOM[customAnimID], anFIRERED);
			spark.animSpeed = 4;
			spark.xSpeed = -obj.xSpeed / 4; spark.ySpeed = -obj.ySpeed / 4;
			spark.xOrg = 0.6667;
		}
	}
}
void SpikeHammerPU(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::BOUNCERBULLETPU, false);
	const jjPLAYER@ creator = jjPlayers[obj.creator];
	const float deltaX = creator.xPos - obj.xPos;
	const float deltaY = creator.yPos - obj.yPos;
	const float distance = sqrt(pow(deltaX, 2) + pow(deltaY, 2));
	const uint chainNumber = int(distance / 16);
	const float chainLength = distance / float(chainNumber);
	const int angle = int(atan2(
            deltaY,
            deltaX
	) * -512.0 * 0.318309886142228);
	const float stepX = -jjCos(angle) * chainLength;
	const float stepY = jjSin(angle) * chainLength;
	if (obj.state != STATE::EXPLODE && obj.state != STATE::KILL && obj.state != STATE::DEACTIVATE) {
		const int frameID = (obj.objectID + (jjGameTicks >> 1)) & 3;
		for (uint i = 0; i < chainNumber; ++i) {
			const float xPos = creator.xPos + stepX * i;
			const float yPos = creator.yPos + stepY * i;
			if (frameID > 0)
				jjDrawRotatedSprite(xPos, yPos, ANIM::CUSTOM[customAnimID], anSPIKECHAIN, frameID, angle);
			jjDrawRotatedSprite(xPos, yPos, ANIM::CUSTOM[customAnimID], anSPIKECHAIN, 0, angle);
		}
		jjDrawSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anSPIKEHAMMER, frameID, 1);
	} else {
		jjAddParticlePixelExplosion(obj.xPos, obj.yPos, jjAnimations[jjAnimSets[ANIM::CUSTOM[customAnimID]] + anSPIKEHAMMER] + 2, 1, 2);
		for (uint i = 0; i < chainNumber; ++i) if ((jjRandom() & 3) == 0) {
			const float xPos = creator.xPos + stepX * i;
			const float yPos = creator.yPos + stepY * i;
			jjAddParticlePixelExplosion(xPos, yPos, jjAnimations[jjAnimSets[ANIM::CUSTOM[customAnimID]] + anSPIKECHAIN] + (jjRandom()&3), 1, 0);
		}
		obj.delete();
	}
}

//Behaviors that replace BEHAVIOR::RFBULLET but are intended as wrappers for it--in particular, directly
//calling behave(BEHAVIOR::RFBULLET) within themselves, like this--should use the [RFBULLET] metadata marker
//above them. This makes JJ2+ assign them the proper speeds at creation time, since RF bullets need a lot
//of special treatment while being fired and otherwise JJ2+ wouldn't know that this behavior warrants it
//while any number of other behaviors do not.
[RFBULLET]
void YellowRocket(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::RFBULLET, false);
	if (obj.state != STATE::EXPLODE && obj.state != STATE::KILL && obj.state != STATE::DEACTIVATE) {
		if (obj.counter > 4)
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anROCKET, 0, getBulletAngle(obj), getBulletDirection(obj), 1, SPRITE::NORMAL, 0, 3);
		jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, Explosion)];
		if (spark.isActive) {
			spark.determineCurAnim(ANIM::CUSTOM[customAnimID], anFIRERED);
			spark.animSpeed = 1;
			spark.xOrg = 1;
		}
	} else {
		if (obj.counter == 1) for (int i = 0; i < 4; ++i) {
			uint rand = jjRandom();
			jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos + (rand & 31) - 15.5, obj.yPos + ((rand >> 5) & 31) - 15.5, obj.objectID, CREATOR::OBJECT, Explosion)];
			if (spark.isActive) {
				spark.determineCurAnim(ANIM::CUSTOM[customAnimID], anFIRERED);
				spark.animSpeed = 3;
				spark.xSpeed = spark.ySpeed = 0;
				spark.xOrg = 5.5;
			}
		}
	}
}
//Powered-up RFs need to draw a whole lot of sprites to make up their chain of colorful explosions back to
//the rabbit who fired them. Rather giving every single chain link its own jjOBJ, these will be stored in
//a custom ChainPosition class, and then each powered-up RF object will get its own array of ChainPositions
//to call drawing code on.
class ChainPosition{
	float xPos, yPos;
	int angle;
	ChainPosition(){}
	ChainPosition(float x, float y, int a) { xPos = x; yPos = y; angle = a; }
}
array<array<ChainPosition>> ChainPositions(jjObjectMax, array<ChainPosition>(0));
[RFBULLET]
void BlueLaser(jjOBJ@ obj) {
	const int angle = jjGameTicks << 5;
	array<ChainPosition>@ positions = @ChainPositions[obj.objectID];
	for (uint i = positions.length; i > 0; --i) {
		ChainPosition@ position = positions[i - 1];
		jjDrawRotatedSprite(position.xPos, position.yPos, ANIM::CUSTOM[customAnimID], anFIRERED + (i&1), 1 + (jjRandom()&3), position.angle + angle, 2, 2, SPRITE::BLEND_SCREEN, 255);
	}
	obj.behave(BEHAVIOR::RFBULLET, false);
	if (obj.state != STATE::EXPLODE && obj.state != STATE::KILL && obj.state != STATE::DEACTIVATE) {
		if (objectInView(obj))
			positions.insertLast(ChainPosition(obj.xPos, obj.yPos, jjGameTicks << 5));
		if (obj.counter > 4)
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anROCKET, 0, getBulletAngle(obj), getBulletDirection(obj), 1, SPRITE::SINGLEHUE, 24);
	} else {
		positions.removeAt(0);
		if (positions.length == 0) //trail empty
			obj.delete();
		else if (!obj.isActive) //deleted
			positions.resize(0);
	}
}

void Crosshair(jjOBJ@ obj) {
	const int angle = int(pow(abs(int(obj.counterEnd) - obj.counter), 2)) >> 4;
	jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anCROSSHAIR, 0, angle, 1, 1, SPRITE::NORMAL, 0, 1);
	jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anCROSSHAIR, 0, 256+angle/2, 1, 1, SPRITE::NORMAL, 0, 1);
	obj.behave(BEHAVIOR::TNT, false);
	if (obj.state == STATE::EXPLODE) {
		obj.counter = 20;			//These two lines are enough to make the TNT perform its blast explosion, destroying nearby objects and potentially hurting nearby players.
		obj.behave(BEHAVIOR::TNT, false);	//jjOBJ::blast would do pretty much the same work, but this way makes sure to stick to the original code as closely as possible.
		obj.delete();
		
		for (int i = 0; i < 16; ++i) {
			jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, Explosion)];
			if (spark.isActive) {
				spark.determineCurAnim(ANIM::CUSTOM[customAnimID], anEXPL);
				spark.animSpeed = 9;
				spark.xSpeed = jjSin(i << 6) * 7;
				spark.ySpeed = jjCos(i << 6) * 7;
				//spark.yAcc = 0.3;
				spark.xOrg = 2;
				spark.frameID = 2;
				spark.isBlastable = false; //in case there are multiple TNT explosions in the same area
			}
		}
		
		uint rand = jjRandom();
		jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos + (rand & 31) - 15.5, obj.yPos + ((rand >> 5) & 31) - 15.5, obj.objectID, CREATOR::OBJECT, Explosion)];
		if (spark.isActive) {
			spark.determineCurAnim(ANIM::CUSTOM[customAnimID], anFIRERED);
			spark.animSpeed = 4;
			spark.xSpeed = spark.ySpeed = 0;
			spark.xOrg = 9;
		}
	}	
}

void Toaster(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::TOASTERBULLET, false);
	if (obj.state == STATE::EXPLODE || obj.state == STATE::KILL || obj.state == STATE::DEACTIVATE) {
		obj.delete();
	} else if (obj.counter >= 4) {
		jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, Explosion)];
		if (spark.isActive) {
			spark.determineCurAnim(ANIM::CUSTOM[customAnimID], anFIRERED);
			spark.animSpeed = 0;
			//spark.xSpeed = -obj.xSpeed; spark.ySpeed = -obj.ySpeed;
			spark.xOrg = 2.5;
			spark.doesHurt = 3;
		}
	}
}
void ToasterPU(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::TOASTERBULLET, false);
	if (obj.state == STATE::EXPLODE || obj.state == STATE::KILL || obj.state == STATE::DEACTIVATE) {
		obj.delete();
	} else if ((obj.counter & 3) == 3) {
		jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, Explosion)];
		if (spark.isActive) {
			spark.determineCurAnim(ANIM::CUSTOM[customAnimID], anFIREBLUE);
			spark.animSpeed = 3;
			spark.xSpeed = (jjRandom() & 7) - 3.5;
			spark.ySpeed = -4;
			spark.xOrg = 2;
		}
	}
	jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[customAnimID], anFIREBLUE, 3, obj.age += 32, 3, 3, SPRITE::BLEND_SCREEN, 255, 3);
}

void Pumpkin(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::PEPPERBULLET, false);
	const bool powerup = jjStrongPowerups && obj.var[6]&8 != 0; //Since this is Pepper Spray, we again check jjStrongPowerups. Bullets signify their powerup status through obj.var[6]&8, and since pepper spray bullets don't have their own jjObjectPresets entries, this is about the only value we can test to distinguish between the two.
	const int direction = getBulletDirection(obj);
	const uint curFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[customAnimID]] + anPUMPKIN] + (powerup ? 0 : 5);
	if (obj.state == STATE::EXPLODE || obj.state == STATE::KILL || obj.state == STATE::DEACTIVATE) {
		jjAddParticlePixelExplosion(obj.xPos, obj.yPos, curFrame, direction, 0);
		obj.delete();
	} else {
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, curFrame, direction);
		if (powerup) {
			if (obj.age == 0)
				obj.age = (jjRandom() & 3) + 1;
			jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos - 25, curFrame + obj.age, direction);
		}
	}
}