Downloads containing ab23btl19.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Anniversary Bash 23 levels Jazz2Online Multiple N/A Download file

File preview

const bool MLLESetupSuccessful = MLLE::Setup(); ///@MLLE-Generated
#include "MLLE-Include-1.5.asc" ///@MLLE-Generated
#pragma require "ab23btl19-MLLE-Data-1.j2l" ///@MLLE-Generated
#pragma require "Keen 4 Paradise 2.j2t" ///@MLLE-Generated
#pragma require "lwt13a.j2t" ///@MLLE-Generated
#pragma require "Monster Bash Graveyard.j2t" ///@MLLE-Generated
#pragma require "Xargon Diurnal.j2t" ///@MLLE-Generated
#pragma require "MEZ02.j2t" ///@MLLE-Generated
#pragma require "junglebg.j2t" ///@MLLE-Generated
#pragma require "ab23btl19.j2l" ///@MLLE-Generated
#pragma require "Meteor.j2a"
#pragma require "expmine.wav"
#pragma require "FTURROCK.wav"
#pragma require "EXPSTD3.wav"
#pragma require "redeemer_flight.wav"

bool controllingRedeemer, warning = false;
int xPos = 0, yPos = 0, cameraX = 0, cameraY = 0, redeemerAngle = 0, CTFArrowTimer = 0;
uint delay = 0;

int redeemerColor(jjPLAYER@ creator) {
	//switch(creator.team) {
		//case TEAM::BLUE: return 0;
		//case TEAM::RED: return 1;
		//case TEAM::GREEN: return 2;
		//case TEAM::YELLOW: return 3;
	//}
	return 0;
}

bool gameIsActive() {
	return jjGameState == GAME::STARTED || jjGameState == GAME::OVERTIME;
}

bool CTFMode() {
	return (jjGameMode == GAME::CTF && jjGameCustom == GAME::NOCUSTOM) || jjGameCustom == GAME::DCTF;
}

bool otherMode() {
	return jjGameMode != GAME::CTF || jjGameCustom == GAME::TB || jjGameCustom == GAME::TLRS || jjGameCustom == GAME::DOM;
}

bool isDestructibleItem(jjOBJ@ target) {
	return target.eventID == OBJECT::TNT || target.behavior == BEHAVIOR::MONITOR;
}

void onLevelLoad() {       
	jjAnimSets[ANIM::CUSTOM[22]].load(0, "Meteor.j2a");
	jjAnimations[jjAnimSets[ANIM::AMMO] + 24] = jjAnimations[jjAnimSets[ANIM::CUSTOM[22]] + 1];
	jjAnimations[jjAnimSets[ANIM::AMMO] + 25] = jjAnimations[jjAnimSets[ANIM::CUSTOM[22]] + 2];
	
	jjObjectPresets[OBJECT::BOUNCERBULLET].behavior = jjObjectPresets[OBJECT::BOUNCERBULLETPU].behavior = Meteor();
	jjObjectPresets[OBJECT::BOUNCERBULLET].special = jjObjectPresets[OBJECT::BOUNCERBULLET].determineCurAnim(ANIM::CUSTOM[22], 1);
	jjObjectPresets[OBJECT::BOUNCERBULLETPU].special = jjObjectPresets[OBJECT::BOUNCERBULLETPU].determineCurAnim(ANIM::CUSTOM[22], 0);
	jjObjectPresets[OBJECT::BOUNCERBULLET].ySpeed = jjObjectPresets[OBJECT::BOUNCERBULLETPU].ySpeed = jjObjectPresets[OBJECT::BLASTERBULLET].ySpeed;
	jjObjectPresets[OBJECT::BOUNCERBULLETPU].killAnim = jjObjectPresets[OBJECT::SEEKERBULLET].killAnim;
	jjObjectPresets[OBJECT::BOUNCERBULLET].lightType = LIGHT::POINT;
	jjObjectPresets[OBJECT::BOUNCERBULLETPU].lightType = LIGHT::BRIGHT;
	jjObjectPresets[OBJECT::BOUNCERBULLET].light = jjObjectPresets[OBJECT::BOUNCERBULLETPU].light = 10;
       	
	jjObjectPresets[OBJECT::BOUNCERAMMO15].determineCurAnim(ANIM::CUSTOM[22], 3);
	jjObjectPresets[OBJECT::BOUNCERAMMO15].determineCurFrame();
	
	jjObjectPresets[OBJECT::BOUNCERPOWERUP].determineCurAnim(ANIM::CUSTOM[22], 4);
	jjObjectPresets[OBJECT::BOUNCERPOWERUP].determineCurFrame();
	
	jjWeapons[WEAPON::BOUNCER].defaultSample = false;
	jjWeapons[WEAPON::BOUNCER].style = WEAPON::MISSILE;
	
	jjSampleLoad(SOUND::ORANGE_BOEMR, "expmine.wav");

	jjObjectPresets[OBJECT::TNT].determineCurFrame();
	jjObjectPresets[OBJECT::TNT].behavior = Redeemer();
	jjObjectPresets[OBJECT::TNT].xSpeed = jjObjectPresets[OBJECT::BLASTERBULLET].xSpeed * 1.33;
	jjObjectPresets[OBJECT::TNT].counterEnd = 255;
	jjObjectPresets[OBJECT::TNT].killAnim = jjObjectPresets[OBJECT::SEEKERBULLET].killAnim;
	
	jjObjectPresets[OBJECT::TNTAMMO3].behavior = RedeemerPickup();
	jjObjectPresets[OBJECT::TNTAMMO3].determineCurFrame();
	jjObjectPresets[OBJECT::TNTAMMO3].scriptedCollisions = true;
	
	jjWeapons[WEAPON::TNT].maximum = 1;
	
	jjAnimSets[ANIM::SONCSHIP].load();
	jjAnimSets[ANIM::VINE].load();
	jjAnimations[jjAnimSets[ANIM::AMMO] + 59] = jjAnimations[jjAnimSets[ANIM::SONCSHIP] + 0];
	
	generateRedeemerPickupSprite(jjAnimSets[ANIM::CUSTOM[1]], array<uint> = {0});
	
	jjSampleLoad(SOUND::P2_POEP, "FTURROCK.wav");
	jjSampleLoad(SOUND::P2_PTOEI, "EXPSTD3.wav");
	jjSampleLoad(SOUND::P2_SPLOUT, "redeemer_flight.wav");

        jjObjectPresets[OBJECT::BOLLPLATFORM].direction = SPRITE::FLIPH;
        jjObjectPresets[OBJECT::FRUITPLATFORM].direction = SPRITE::FLIPH;	
	jjObjectPresets[OBJECT::PINKPLATFORM].direction = SPRITE::FLIPHV;
	jjObjectPresets[OBJECT::PINKPLATFORM].bulletHandling = HANDLING::DESTROYBULLET;
	jjANIMFRAME@ frame = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::PINKPLAT].firstAnim + 0].firstFrame];
	frame.hotSpotY = -12;
}


class Meteor : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::BULLET, obj.state == STATE::EXPLODE? true:false);
		jjPLAYER@ creator = jjPlayers[obj.creatorID];
		
		if (obj.state != STATE::EXPLODE) {
			if (obj.counter == 1 && creator.isLocal) {
				jjSample(creator.xPos, creator.yPos, SOUND::ORANGE_BOEMR, 42, obj.eventID == OBJECT::BOUNCERBULLET? 22000 : 20000);
				obj.var[2] = 0;
				
			}
			obj.age += obj.direction == 0? 10 : 10 * obj.direction;
			
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[22], obj.eventID == OBJECT::BOUNCERBULLET? 1:0, 0, -obj.age, 1, 1, obj.eventID == OBJECT::BOUNCERBULLET || obj.var[4] == 1? SPRITE::SINGLEHUE : SPRITE::NORMAL, 72);
			
			jjPARTICLE@ smoke = jjAddParticle(PARTICLE::SMOKE);
			if (smoke !is null) {
				smoke.xPos = smoke.xPos;
				smoke.yPos = smoke.yPos;
			}
			
			if (obj.eventID == OBJECT::BOUNCERBULLETPU && obj.var[4] == 0) {
				jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[22], 0, 0, -obj.age, 1, 1, SPRITE::TRANSLUCENTSINGLEHUE, 40);
				jjPARTICLE@ cinders = jjAddParticle(PARTICLE::FIRE);
				if (cinders !is null) {
					cinders.xPos = int(obj.xPos - 8) + jjRandom()%17;
					cinders.yPos = int(obj.yPos - 8) + jjRandom()%17;
				}
			}
			
			if (obj.yPos > jjWaterLevel) {
				obj.var[4] = 1;
				obj.xSpeed = obj.xSpeed * 0.875;
				obj.ySpeed = obj.ySpeed * 0.875;
			}
		
			switch (obj.direction) {
				case 1: obj.xSpeed -= obj.eventID == OBJECT::BOUNCERBULLET? 0.1:0.15; obj.ySpeed += obj.eventID == OBJECT::BOUNCERBULLET? 0.15:0.2; break;
				case -1: obj.xSpeed += obj.eventID == OBJECT::BOUNCERBULLET? 0.1:0.15; obj.ySpeed += obj.eventID == OBJECT::BOUNCERBULLET? 0.15:0.2; break;
			}
			
			if (obj.xSpeed == 0) obj.ySpeed += 0.4;
			if (obj.ySpeed > 8) obj.ySpeed = 8;
			
		} else {
			obj.age = 0;
			if (obj.var[2] == 0) {
				jjSample(obj.xPos, obj.yPos, SOUND::COMMON_BENZIN1, 0, 0);
				obj.var[2] = 1;
				
				for (int i = -1; i <= 1; i+= 2) {
					Rock temp;
						jjOBJ@ rock = jjObjects[jjAddObject(OBJECT::SHARD, int(obj.xPos + (i * 12)), int(obj.yPos - 8), obj.creatorID, CREATOR::PLAYER, jjVOIDFUNCOBJ(temp.onBehave))];
						rock.determineCurAnim(obj.eventID == OBJECT::BOUNCERBULLETPU? ANIM::CUSTOM[22] : ANIM::FONT, obj.eventID == OBJECT::BOUNCERBULLETPU? 0:14);
						rock.playerHandling = HANDLING::PLAYERBULLET;
						rock.var[3] = 2;
						rock.var[4] = obj.var[4];
						rock.var[5] = obj.eventID == OBJECT::BOUNCERBULLETPU? 0:1;
						rock.var[6] = obj.eventID == OBJECT::BOUNCERBULLETPU? 8:0;
						rock.special = obj.eventID == OBJECT::BOUNCERBULLETPU? 40:72;
						rock.animSpeed = 2;
						rock.direction = i;
						rock.xSpeed = 6 * i;
						rock.ySpeed = -3;
						rock.state = STATE::FLY;
						rock.lightType = LIGHT::POINT;
						rock.light = 10;
						rock.counterEnd = jjObjectPresets[OBJECT::BOUNCERBULLET].counterEnd;
						rock.killAnim = jjObjectPresets[OBJECT::BOUNCERBULLET].killAnim;
				}
			}
		}
	}
}

class Rock : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::BULLET, obj.state == STATE::EXPLODE? true:false);
		
		if (obj.state == STATE::FLY) {
			obj.age += obj.direction == 0? 10 : 10 * obj.direction;
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[22], obj.var[5] == 0? 0:1, 0, -obj.age, 0.5, 0.5, SPRITE::SINGLEHUE, obj.special);
			
			switch (obj.direction) {
				case 1: obj.xSpeed -= 0.05; obj.ySpeed += 0.1; break;
				case -1: obj.xSpeed += 0.05; obj.ySpeed += 0.1; break;
			}
			
			if (obj.yPos > jjWaterLevel) {
				obj.var[4] = 1;
				obj.xSpeed = obj.xSpeed * 0.875;
				obj.ySpeed = obj.ySpeed * 0.875;
			}
			
			jjPARTICLE@ smoke = jjAddParticle(PARTICLE::SMOKE);
			if (smoke !is null && obj.var[5] == 2) {
				smoke.xPos = obj.xPos;
				smoke.yPos = obj.yPos;
			}
			
		}
		
	}
}

class Redeemer : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		//var[0] - angle
		//var[1] - angular speed
		//var[2] - has the redeemer exploded
		//special - value of jjGameTicks at the time the last packet was sent/received	
		
		int angle;
		float speed = jjObjectPresets[OBJECT::TNT].xSpeed;
		
		if (obj.creatorType != CREATOR::PLAYER)
			obj.delete();
		jjPLAYER@ creator = jjPlayers[obj.creatorID];
		
		switch (obj.state) {
			case STATE::START:
				obj.curAnim = jjObjectPresets[obj.eventID].curAnim;
				jjSamplePriority(SOUND::P2_POEP);
				obj.var[0] = int(atan2(-obj.ySpeed, obj.xSpeed) * (512.f * 0.318309886142228f));
				obj.ySpeed = speed * -jjSin(obj.var[0]);
				obj.xSpeed = speed * jjCos(obj.var[0]);
				obj.direction = obj.xSpeed < 0.f ? -1 : 1;
				obj.var[2] = 0;
				if (creator.isLocal) controllingRedeemer = true;
				obj.state = STATE::FLY;
			break;
				
			case STATE::FLY:
				redeemerCamera(creator, obj);
				jjDrawRotatedSprite(obj.xPos, obj.yPos, (creator.isLocal || jjGameMode != GAME::CTF)? ANIM::SONCSHIP : ANIM::SONCSHIP, (creator.isLocal || jjGameMode != GAME::CTF)? 0 : redeemerColor(creator), obj.curFrame, obj.var[0], 2, 2, SPRITE::NORMAL);
				if (creator.isLocal) {
					redeemerAngle = obj.var[0];
					if (!jjLowDetail && (obj.counter % 131 == 36 || obj.counter == 1) && controllingRedeemer) jjSamplePriority(SOUND::P2_SPLOUT);
					float dx = creator.xPos - obj.xPos, dy = creator.yPos - obj.yPos;
						if (dx * dx + dy * dy < 420 * 420) warning = true;
						else warning = false;
					if (creator.health == 0) {
						creator.cameraUnfreeze(true);
						controllingRedeemer = false;
					} else {
						if (controllingRedeemer) creator.xSpeed = 0;
						if (obj.counter >= 21) {
							if (controllingRedeemer) {
								if (creator.keyRight || (obj.direction < 0 ? creator.keyUp : creator.keyDown))
									obj.var[1] = -16;
								else if (creator.keyLeft || (obj.direction < 0 ? creator.keyDown : creator.keyUp))
									obj.var[1] = 16;
								else
									obj.var[1] = 0;
							}
							else obj.var[1] = 0;
						}
						if (obj.counter >= 127)
							obj.counter = 35;
						else if (obj.counter >= 35 && creator.keyFire && controllingRedeemer)
							obj.state = STATE::EXPLODE;
					}
					xPos = int(obj.xPos);
					yPos = int(obj.yPos);
				}
				if (obj.yPos >= jjLayerHeight[4]*32) obj.state = STATE::EXPLODE;
				obj.var[0] = obj.var[0] + obj.var[1];
				obj.ySpeed = speed * -jjSin(obj.var[0]);
				obj.xSpeed = speed * jjCos(obj.var[0]);
				if (obj.counter % 3 == 0 && !jjLowDetail) spawnFireTrail(obj);
			break;
			
			case STATE::EXPLODE:
				if (obj.var[2] == 0) {
					RedeemerExplosion temp;
					jjOBJ@ explosion = jjObjects[jjAddObject(OBJECT::BULLET, obj.xPos, obj.yPos, obj.creatorID, CREATOR::PLAYER, jjVOIDFUNCOBJ(temp.onBehave))];
					jjSamplePriority(SOUND::P2_PTOEI);
					obj.var[2] = 1;
					explosion.var[2] = 1;
				} else {
					jjDrawResizedSprite(obj.xPos, obj.yPos, ANIM::AMMO, 5, obj.curFrame + 5, 8, 8, SPRITE::NORMAL, 0, 2, 2);
				}
				creator.cameraUnfreeze(true);
				if (creator.isLocal) controllingRedeemer = false;
			break;
		}
		
		int previousState = obj.state;
		obj.behave(BEHAVIOR::BULLET, false);
		if (!creator.isLocal) {
			if (obj.special + 128 > jjGameTicks)
				obj.state = previousState;
		} else if (obj.special + 4 <= jjGameTicks) {
			jjSTREAM packet;
			packet.push(int8(obj.creatorID));
			packet.push(obj.state == STATE::EXPLODE);
			packet.push(obj.xPos);
			packet.push(obj.yPos);
			if (obj.state != STATE::EXPLODE) {
				packet.push(int16(obj.var[0]));
				packet.push(int16(obj.var[1]));
			}
			jjSendPacket(packet);
			obj.special = jjGameTicks;
		}
	}
}

void spawnFireTrail(jjOBJ@ obj) {
	jjOBJ@ trail = jjObjects[jjAddObject(OBJECT::EXPLOSION, int(obj.xPos - jjCos(obj.var[0])), int(obj.yPos - jjSin(obj.var[0])))];
	trail.determineCurAnim(ANIM::AMMO, 3);
	trail.lightType = LIGHT::POINT;
	trail.playerHandling = HANDLING::PARTICLE;
	trail.bulletHandling = HANDLING::IGNOREBULLET;
	trail.isBlastable = false;
}

void redeemerCamera(jjPLAYER@ creator, jjOBJ@ obj) {
	float dx = jjLayerWidth[4] - obj.xPos, dy = jjLayerHeight[4] - obj.yPos;
	
	if (jjLayerWidth[4] - dx <= 400) cameraX = 400;
	else cameraX = int(obj.xPos);
	
	if (jjLayerHeight[4] - dy <= 400) cameraY = 400;
	else cameraY = int(obj.yPos);
	
	if (controllingRedeemer) {
		creator.cameraFreeze(int(obj.xPos) >= 8528? 8528 : cameraX, int(obj.yPos) >= 4080? 4080 : cameraY, true, true);
	
		for (int i = 0; i < 32; i++) {
			jjPLAYER@ play = jjPlayers[i];
			float reticleScale = jjSin(jjGameTicks*5);
			float reticleColor = jjSin(jjGameTicks*10)*2;
			float pdx = play.xPos - obj.xPos, pdy = play.yPos - obj.yPos;
			if ((pdx * pdx + pdy * pdy < 320 * 320) && (jjGameMode != GAME::CTF || jjFriendlyFire || play.team != creator.team) && !play.isLocal)
				jjDrawResizedSprite(play.xPos, play.yPos, ANIM::SONCSHIP, 7, 0, reticleScale + 2, reticleScale + 2, SPRITE::SINGLECOLOR, int(reticleColor + 18), 1);
							
		}
	}
}

class RedeemerPickup : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::PICKUP, false);
		obj.direction = obj.xPos > 4464? -1:1;
		if (obj.isActive) jjDrawResizedSprite(int(obj.xPos) - (6 * obj.direction), int(obj.yPos) + jjSin(jjGameTicks*10)*6, ANIM::CUSTOM[1], 0, obj.curFrame, 2 * obj.direction, 2, ((obj.xPos < 4464 && jjLocalPlayers[0].team == TEAM::BLUE) || (obj.xPos > 4464 && jjLocalPlayers[0].team == TEAM::RED))? SPRITE::NORMAL : SPRITE::NORMAL);
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ play, int force) {
		if (play.ammo[WEAPON::TNT] < jjWeapons[WEAPON::TNT].maximum) {
			if ((obj.xPos < 4464 && play.team == TEAM::BLUE) || (obj.xPos > 4464 && play.team == TEAM::RED) || otherMode()) {
				if (play.isLocal) {
					play.ammo[WEAPON::TNT] = play.ammo[WEAPON::TNT] + 1;
					play.currWeapon = WEAPON::TNT;
					jjSample(obj.xPos, obj.yPos, SOUND::COMMON_LOADSPAZ, 0, 0);
					if (play.charCurr == CHAR::JAZZ) {
						jjSample(play.xPos, play.yPos, SOUND::JAZZSOUNDS_JUMMY, 0, 0);
					}
					if (play.charCurr == CHAR::SPAZ) {
						jjSample(play.xPos, play.yPos, SOUND::SPAZSOUNDS_HAPPY, 0, 0);
					}
					if (play.charCurr == CHAR::LORI) {
						jjSample(play.xPos, play.yPos, SOUND::LORISOUNDS_WEHOO, 0, 0);
					}
				}
				obj.behavior = BEHAVIOR::EXPLOSION2;
				obj.scriptedCollisions = false;
				obj.frameID = 0;
			}
		}
		return true;
	}
}

class RedeemerExplosion : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		//var[0] - obj.counter
		//var[2] - has the redeemer exploded
		//var[4] - blast radius
		//var[5] - damage
		//var[8] - has the player been hit by the explosion
	
		obj.playerHandling = HANDLING::PARTICLE;
		obj.bulletHandling = HANDLING::IGNOREBULLET;
		
		obj.lightType = obj.var[2] == 1? LIGHT::RING2 : LIGHT::NONE;
		
		jjPLAYER@ creator = jjPlayers[obj.creatorID];
		jjPLAYER@ play = jjLocalPlayers[0];
		
		if (obj.var[2] == 1) {
			obj.var[0] = obj.var[0] + 1;
			obj.light += 2;
			obj.var[4] = obj.light * 5;
			
			if (obj.var[4] >= 460) obj.var[5] = 1;
			else if (obj.var[4] >= 360 && obj.var[4] < 460) obj.var[5] = 2;
			else if (obj.var[4] < 360) obj.var[5] = 7;
			
			for (int i = 1; i < jjObjectCount; i++) {
				jjOBJ@ target = jjObjects[i];
				float dx = target.xPos - obj.xPos, dy = target.yPos - obj.yPos;
				if (isDestructibleItem(target) && target.var[4] == 0) {
					if (dx * dx + dy * dy < obj.var[4] * obj.var[4]) {
						if (target.eventID != OBJECT::TNT) creator.objectHit(target, -1, HANDLING::SPECIAL);
						else target.state = STATE::EXPLODE;
						target.var[4] = 1;
						if (target.eventID == OBJECT::WATERSHIELD) play.shieldTime = 40*70;
					}
					else target.var[4] = 0;
				}
				else if (target.behavior == BEHAVIOR::PICKUP && target.state == STATE::FLOAT) {
					if (dx * dx + dy * dy < obj.var[4] * obj.var[4])
						target.state = STATE::FLOATFALL;
				}
			}
			
			float pdx = play.xPos - obj.xPos, pdy = play.yPos - obj.yPos;
			if (pdx * pdx + pdy * pdy < 1600 * 1600) {
				uint random = jjRandom();
				int magnitude = (2 << (random & 3)) - 1;
				int halfMagnitude = magnitude >> 1;
				
				if ((jjGameTicks & 1 == 0 && !jjTriggers[19]) || (jjGameTicks & 1 == 0 && jjTriggers[19] && jjMaskedHLine(int(play.xPos) - 12, 24, int(play.yPos) + 21)) && !controllingRedeemer)
					play.cameraFreeze(play.cameraX + (random & magnitude) - halfMagnitude, play.cameraY + (random >> 8 & magnitude) - halfMagnitude, false, true);
				else
					play.cameraUnfreeze();
			}
		}
		
		if (jjIsServer && gameIsActive()) {
			for (int i = 0; i < 32; i++) {
				jjPLAYER@ player = jjPlayers[i];
				if (
					player.isActive && player.isInGame && player.health > 0 && gameIsActive()
						&& (jjGameMode != GAME::CTF || jjFriendlyFire || player.team != creator.team || player is creator)
				) {
					float dx = player.xPos - obj.xPos, dy = player.yPos - obj.yPos;
					if (dx * dx + dy * dy < obj.var[4] * obj.var[4]) {
						if (obj.var[8] & 1 << i == 0) {
							player.hurt(obj.var[5], true, creator);
							obj.var[8] = obj.var[8] | 1 << i;
						}
					}
				}
			}
		}
		
		if (obj.var[0] == 1) {
			jjSample(obj.xPos, obj.yPos, SOUND::BILSBOSS_FIRE, 0, 0);
			for (int i = -8; i <= 8; i+=8) {
				for (int j = -8; j <= 8; j+=8) {
					if (i != 0 || j != 0) {
						Fireworks temp;
						int id = jjAddObject(OBJECT::ELECTROBULLET, obj.xPos, obj.yPos, obj.creatorID, CREATOR::PLAYER, jjVOIDFUNCOBJ(temp.onBehave));
						if (id != 0) {
							jjOBJ@ flares = jjObjects[id];
							flares.xSpeed = j*2;
							flares.ySpeed = i*2;
						}
					}
				}
			}
		}
		else if (obj.var[0] == 70) {
			obj.var[0] = 0;
			obj.var[2] = 0;
			obj.var[4] = 0;
			if (!controllingRedeemer) play.cameraUnfreeze();
			obj.delete();
		}
	}
}

class Fireworks : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::ELECTROBULLET, false);
		obj.counterEnd = 210;
		if (obj.ySpeed < 10 && obj.xSpeed != 0) obj.ySpeed += 0.5;
		obj.playerHandling = HANDLING::PARTICLE;
		obj.bulletHandling = HANDLING::IGNOREBULLET;
		if (obj.state == STATE::FLY) obj.particlePixelExplosion(1);
	}
}

void onReceive(jjSTREAM &in packet, int clientID) {
	int8 playerID;
	bool explosion;
	float xPos, yPos;
	int16 angle, angleSpeed;
	jjSTREAM packetBackup;
	if (jjIsServer)
		packetBackup = packet;
	if (packet.pop(playerID) && playerID >= 0 && playerID < 32 &&
		packet.pop(explosion) && packet.pop(xPos) && packet.pop(yPos) &&
		(explosion || packet.pop(angle) && packet.pop(angleSpeed))
	) {
		const jjPLAYER@ player = jjPlayers[playerID];
		if (!jjIsServer || player.isActive && player.isInGame && player.clientID == clientID ) {
			jjOBJ@ redeemer;
			for (int i = 0; i < jjObjectCount; i++) {
				jjOBJ@ obj = jjObjects[i];
				if (obj.isActive && obj.eventID == OBJECT::TNT && obj.creatorType == CREATOR::PLAYER && obj.creatorID == uint(playerID)) {
					@redeemer = obj;
					break;
				}
			}
			if (redeemer is null && jjGameTicks < 140) {
				int id = jjAddObject(OBJECT::TNT, xPos, yPos, playerID, CREATOR::PLAYER);
				if (id > 0)
					@redeemer = jjObjects[id];
			}
			if (redeemer !is null) {
				if (jjIsServer)
					jjSendPacket(packetBackup, -clientID);
				if (explosion)
					redeemer.state = STATE::EXPLODE;
				redeemer.xPos = xPos;
				redeemer.yPos = yPos;
				redeemer.var[0] = angle;
				redeemer.var[1] = angleSpeed;
				redeemer.special = jjGameTicks;
			}
		}
	}
}

bool onDrawAmmo(jjPLAYER@ play, jjCANVAS@ canvas) {
	if (controllingRedeemer) {
		canvas.drawString(
			jjSubscreenWidth - 480,
			jjSubscreenHeight - 450,
			"Redeemer!",
			STRING::MEDIUM,
			STRING::NORMAL
		);
		//canvas.drawString(
			//jjSubscreenWidth - 496,
			//jjSubscreenHeight - 422,
			//"Redeemer pos " + int(xPos / 32) + "," + int(yPos / 32),
			//STRING::SMALL,
			//STRING::NORMAL
		//);
		canvas.drawString(
			jjSubscreenWidth - 624,
			jjSubscreenHeight - 406,
			"|Use the |||movement keys |||||to rotate the missile",
			STRING::SMALL,
			STRING::NORMAL
		);
		canvas.drawString(
			jjSubscreenWidth - 625,
			jjSubscreenHeight - 390,
			"|Press |||FIRE |||||to detonate the missile in mid-air",
			STRING::SMALL,
			STRING::NORMAL
		);
		canvas.drawRectangle(
			jjSubscreenWidth - 750,
			jjSubscreenHeight - 240,
			140,
			140,
			87,
			SPRITE::TRANSLUCENT
		);
		canvas.drawString(
			jjSubscreenWidth - 708,
			jjSubscreenHeight - 108,
			"Radar",
			STRING::SMALL,
			STRING::PALSHIFT,
			16
		);
		canvas.drawRotatedSprite(
			jjSubscreenWidth - 680, 
			jjSubscreenHeight - 170, 
			ANIM::CUSTOM[1],
			0,
			jjGameTicks >> 2,
			redeemerAngle, 
			1, 
			1, 
			SPRITE::SINGLEHUE, 
			80
		);
		
		for (int i = 0; i < 32; i++) {
			jjPLAYER@ player = jjPlayers[i];
			
			uint8 teamColor;
			switch (player.team) {
				case TEAM::BLUE: teamColor = 34; break;
				case TEAM::RED: teamColor = 24; break;
				case TEAM::GREEN: teamColor = 18; break;
				case TEAM::YELLOW: teamColor = 40; break;
				default: teamColor = 24; break;
			}
			
			int radarOffsetX = int(xPos - player.xPos) / 35;
			int radarOffsetY = int(yPos - player.yPos) / 35;
			
			if (radarOffsetX < 70 && radarOffsetX > -63 && radarOffsetY < 70 && radarOffsetY > -63 && player.xPos > 0 && player.yPos > 0 && !play.isSpectating && !play.isOut && !play.isConnecting) {
				if (player.flag != 0 && !player.isLocal) {
					canvas.drawResizedSprite(
						((jjSubscreenWidth - 678) - radarOffsetX),
						((jjSubscreenHeight - 168) - radarOffsetY),
						ANIM::FLAG,
						3,
						jjGameTicks >> 2,
						radarOffsetX > 0? 0.4:-0.4,
						0.4,
						SPRITE::SINGLECOLOR,
						player.team == TEAM::BLUE? 24 : 34
					);
					canvas.drawRectangle(
						((jjSubscreenWidth - 680) - radarOffsetX),
						((jjSubscreenHeight - 170) - radarOffsetY),
						6,
						6,
						teamColor,
						SPRITE::NORMAL
					);
				}
				else if (player.flag == 0) {
					canvas.drawRectangle(
						((jjSubscreenWidth - 680) - radarOffsetX),
						((jjSubscreenHeight - 170) - radarOffsetY),
						player.isLocal? 8:6,
						player.isLocal? 8:6,
						player.isLocal? 64 : jjGameMode == GAME::CTF? teamColor : 24,
						SPRITE::NORMAL
					);
				}
			}
			
			for (int j = 1; j < jjObjectCount; j++) {
				if (jjObjects[j].eventID == OBJECT::TNT) {
					
					uint8 redeemerColor;
					switch (jjPlayers[jjObjects[j].creatorID].team) {
						case TEAM::BLUE: redeemerColor = 34; break;
						case TEAM::RED: redeemerColor = 24; break;
						case TEAM::GREEN: redeemerColor = 18; break;
						case TEAM::YELLOW: redeemerColor = 40; break;
						default: redeemerColor = 88; break;
					}
					
					int radarMissileOffsetX = int(xPos - jjObjects[j].xPos) / 35;
					int radarMissileOffsetY = int(yPos - jjObjects[j].yPos) / 35;
					
					if (!jjPlayers[jjObjects[j].creatorID].isLocal && radarMissileOffsetX < 70 && radarMissileOffsetX > -63 && radarMissileOffsetY < 70 && radarMissileOffsetY > -63) {
						canvas.drawRotatedSprite(
							((jjSubscreenWidth - 680) - radarMissileOffsetX),
							((jjSubscreenHeight - 170) - radarMissileOffsetY),
							ANIM::CUSTOM[1],
							0,
							jjGameTicks >> 2,
							jjObjects[j].var[0],
							0.5,
							0.5,
							SPRITE::SINGLECOLOR,
							jjGameMode == GAME::CTF? redeemerColor : 90
						);
					}
				}
			}
		}
		
		if (warning) {
			canvas.drawString(
				jjSubscreenWidth - 550,
				jjSubscreenHeight - 374,
				"||WARNING: YOU ARE IN RANGE!",
				STRING::SMALL,
				STRING::NORMAL
			);
		}
		
		if (!jjLowDetail && jjGameTicks % 140 >= 7) {
			canvas.drawString(
				jjSubscreenWidth - 132,
				jjSubscreenHeight - 46,
				"REC",
				STRING::MEDIUM,
				STRING::NORMAL
			);
			canvas.drawSprite(
				jjSubscreenWidth - 146,
				jjSubscreenHeight - 45,
				ANIM::VINE,
				0,
				0,
				0,
				SPRITE::NORMAL
			);
		}
	}
	else if (!controllingRedeemer && play.currWeapon == WEAPON::TNT) {
		canvas.drawString(
			jjSubscreenWidth - 80,
			jjSubscreenHeight - 14,
			"x" + play.ammo[WEAPON::TNT],
			STRING::MEDIUM,
			STRING::NORMAL
		);
		canvas.drawResizedSprite(
			jjSubscreenWidth - 110,
			jjSubscreenHeight - 14,
			ANIM::CUSTOM[1],
			0,
			jjGameTicks >> 2,
			2,
			2,
			SPRITE::NORMAL
		);
	}
	
	return controllingRedeemer || play.currWeapon == WEAPON::TNT;
}

jjANIMSET@ redeemerPickupSprite;
bool generateRedeemerPickupSprite(jjANIMSET@ anim, const array<uint> &in colors) {
	int length = colors.length();
	bool success = (@redeemerPickupSprite = anim).allocate(array<uint>(length, 8)) !is null;
	if (success) {
		uint srcSet = jjAnimSets[ANIM::SONCSHIP];
		for (int i = 0; i < length; i++) {
			uint color = colors[i];
			uint destAnimOffset = anim + i;
			uint srcAnim = jjAnimations[srcSet + 0];
			uint destAnim = jjAnimations[destAnimOffset + 0];
			for (int k = 0; k < 8; k++) {
				jjPIXELMAP image(jjAnimFrames[destAnim + k] = jjAnimFrames[srcAnim + k]);
				int width = image.width;
				int height = image.height;
				for (int l = 0; l < height; l++) {
					for (int m = 0; m < width; m++) {
						int pixel = image[m, l];
						if (pixel >= 40 && pixel < 48)
							image[m, l] = color;
					}
				}
			if (!image.save(jjAnimFrames[destAnim + k]))
				return false;
			}
		}
	}
	return success;
}

void onMain() {
	array<jjLAYER@> layers = jjLayerOrderGet();
	
	layers[8].xOffset -=1;
	if (layers[8].xOffset % (layers[8].widthReal*32) == 0) layers[8].xOffset = 0;
	layers[8].yOffset += jjSin(jjGameTicks*10)/5;
	
	for (int i = 1; i < jjObjectCount; i++) {
		jjOBJ@ obj = jjObjects[i];
		if (obj.isActive && obj.eventID == OBJECT::COPTER && obj.state == STATE::FLY) {
			obj.counter = 0;
			if (obj.var[4] == 0)
				obj.state = STATE::DONE;
		}
	}
}

void onLevelReload() {
	MLLE::Palette.apply();
}