Downloads containing Ozymandius.j2as

Downloads
Name Author Game Mode Rating
JJ2+ Only: OzymandiusFeatured Download Violet CLM Single player 9.8 Download file

File preview

array<int> firespeeds = {3, 20, 3, 6, 16, 1, 0, 14, 5};
array<bool> hasAlreadyCollectedWeapon = {true, false, false, false, false, false, false, false, false};
const array<uint8> gunProfilePics =		{18, 25, 29, 34, 49, 57, 59, 61, 68};
array<string> weaponNames = {
	"Blaster",
	"Ricochet Fire",
	" Shield Cubes",
	"  Jet Missile",
	" Crimson Candy",
	"Flaming Footprint",
	"Remote Detonator",
	"Chain Launcher",
	"    Chakram"
};
array<string> weaponDescriptions = {
	"Rarity is best pony.",
	"Great balls of fire fly from your gun! The immense@destructive force of these fireballs is marred only by@their slow speeds. If a fireball hits a wall, it will@bounce, and yet not forever...",
	"Little frozen cubes spring to your defense, circling@around you as long as you hold the fire button. Let@go, and they'll fly off in search of enemies to destroy,@though they are most damaging at a shorter range...",
	"Missiles pound through the air and do great damage to@all but the fiercest. Each launch will knock you back,@however, even leading to flight. It is up to you to@turn this recoil into a blessing, or a curse...",
	"Festive green and red orbs fly madly forth, picking up@speed until they are little more than a sparkly blur@to behold. And yet, the faster they fly, the less@effective they are at harming your foes...",
	"A jet of flame hugs the ground beneath your feet,@incinerating all that dares stand in its way. If your@foe flies above you, however, your fate is sealed, for@this fire is beholden less to you than to gravity...",
	"An explosion fills the air, and your ears tingle@despite the immense distance. These bombs hurt any foe@dumb enough to stand in the way, yet despite their@great maneuverability, they inflict little harm...",
	"A tiny metal ring slips from the muzzle of your blaster,@soon joined by another, and yet another, zipping through@the air. As a melee weapon, the chain is useless,@for its damage increases with its own length...",
	"Glowing blue rings spin from your weapon, but soon lose@their appetite for battle and yearn to return to you.@Chakrams do not fly far, despite their power, but you@may be able to put them to use in your defense..."
};
bool paused = true, pausedForApproachingPlanet = true, pausedForGunStory = false, pausedForChatting = false, pausedForWatchingTheGoddess = false;
int describedWeapon = 0;
int elapsedPauseTime = 0;
enum conversations {chatEVA, chatEVA2, chatSTATUE, chatGODDESS};
int currentPauseChat = chatEVA;

bool barrierDestroyed = false, statueDefeated = false, scrolly = false, whiteWorld = false, showScore = false;
const float SCROLLSPEED = 3;

const int BOLLYHEALTH1 = 300;
const int BOLLYHEALTH2 = 200;
const int BOLLYHEALTH3 = 620;
const int BOLLYHEALTH4 = 275;

const string MUSICFILENAME = "zarathus.mo3";


void onLevelReload() {
	barrierDestroyed = statueDefeated = scrolly = whiteWorld = false;
	paused = pausedForGunStory = pausedForChatting = false; //just in case
	jjMusicLoad(MUSICFILENAME);
	jjEnableEachASFunction();
	if (jjTileGet(5, 185, 65) == 1) jjEnabledASFunctions[4] = false; //text sign
	jjLocalPlayers[0].food = 0; //whatever
	jjObjectPresets[OBJECT::BOLLY].energy = BOLLYHEALTH1 / 10;
	jjLayerHasTiles[7] = jjLayerHasTiles[5] = jjLayerHasTiles[4] = jjLayerHasTiles[3] = true;
	jjLayerHasTiles[6] = false;
	jjTileSet(4, 156, 0, 0);
	jjTileSet(4, 157, 0, 0);
	jjTileSet(4, 158, 0, 0);
	jjTileSet(4, 159, 0, 0);
	
	jjLayerYAutoSpeed[8] = 0;
	jjLayerXAutoSpeed[8] = 0;
	jjTexturedBGStyle = TEXTURE::MENU;
	jjTexturedBGStars = true;
	jjTexturedBGUsed = false;
	jjSetFadeColors(164, 164, 162);
	
	//jjTileType[7] = jjTileType[8] = jjTileType[17] = jjTileType[18] = jjTileType[424] = jjTileType[425] = 0; //normal
}
void onLevelLoad() {
	
	if (jjResolutionWidth != 640 || jjResolutionHeight != 480)
		jjAlert("||WARNING: This level is designed to be played in 640x480, not " + formatInt(jjResolutionWidth, "1") +"x" + formatInt(jjResolutionHeight, "1"));
	if (jjLocalPlayerCount > 1)
		jjAlert("||WARNING: This level is not designed to be played in splitscreen.");
	if ((jjGameMode != GAME::SP )|| (jjGameConnection != GAME::LOCAL))
		jjAlert("||WARNING: This level is only designed for local single player.");
	
	jjTexturedBGStyle = TEXTURE::MENU;
	jjTexturedBGStars = true;
	jjSetFadeColors(164, 164, 162);
	
	jjLayerHasTiles[6] = false;
	
	jjPalette.fill(0,0,0, 10, 235); //blackness blackness blackness
	jjPalette.gradient(176,92,0, 76,28,0, 24, 8); //planet pits
	jjPalette.gradient(255,0,0, 0,0,0, 32, 8); //warning text
	jjPalette.gradient(240,196,108, 72,44,4, 40, 8); //planet base
	jjPalette.gradient(255,255,255, 0,0,0, 72, 8); //stars
	jjPalette.apply();
	
	for (int i = 1; i < 10; ++i)
		jjWeapons[i].infinite = true;
	for (int i = 0; i < jjLocalPlayerCount; ++i) {
		jjLocalPlayers[i].lives = 999; //that should be enough
		jjLocalPlayers[i].powerup[WEAPON::GUN8] = true;
	}
	
	for (int x = 183; x < 197; ++x)
		jjTileSet(4, x, 0, jjTileGet(4, x, 0) | TILE::VFLIPPED); //spikes above chakram ammo
	for (int x = 173; x < 176; ++x)
		jjTileSet(5, x, 79, jjTileGet(5, x, 79) | TILE::VFLIPPED); //barrier above jet missile ammo
	for (int x = 157; x < 161; ++x)
		jjTileSet(4, x, 63, jjTileGet(4, x, 63) | TILE::VFLIPPED); //platform to the right of goddess
	
	jjObjectPresets[OBJECT::BLASTERBULLET].special = jjObjectPresets[OBJECT::BLASTERBULLET].determineCurAnim(ANIM::AMMO, 10);
	jjObjectPresets[OBJECT::BLASTERBULLET].lightType = LIGHT::NONE;
	jjObjectPresets[OBJECT::BLASTERBULLET].animSpeed = 2; //damage
	jjObjectPresets[OBJECT::BLASTERBULLET].counterEnd += 35;
	jjObjectPresets[OBJECT::BLASTERBULLETPU].animSpeed = 3; //damage
	
	jjObjectPresets[OBJECT::BOUNCERBULLET].special = jjObjectPresets[OBJECT::BOUNCERBULLET].determineCurAnim(ANIM::AMMO, 77);
	jjObjectPresets[OBJECT::BOUNCERBULLET].frameID = 4;
	jjObjectPresets[OBJECT::BOUNCERBULLET].determineCurFrame();
	jjObjectPresets[OBJECT::BOUNCERBULLET].killAnim = jjObjectPresets[OBJECT::RFBULLET].killAnim;
	jjObjectPresets[OBJECT::BOUNCERBULLET].counterEnd = 0;
	jjObjectPresets[OBJECT::BOUNCERBULLET].var[6] = 2; //firey
	jjObjectPresets[OBJECT::BOUNCERBULLET].lightType = LIGHT::NONE;
	jjObjectPresets[OBJECT::BOUNCERBULLET].behavior = BallBullet;
	jjObjectPresets[OBJECT::BOUNCERBULLET].ySpeed = 0;
	jjObjectPresets[OBJECT::BOUNCERBULLET].animSpeed = 10; //damage
	
	jjObjectPresets[OBJECT::ICEBULLET].special = jjObjectPresets[OBJECT::ICEBULLET].determineCurAnim(ANIM::AMMO, 29);
	jjObjectPresets[OBJECT::ICEBULLET].freeze = 0;
	jjObjectPresets[OBJECT::ICEBULLET].lightType = LIGHT::NONE;
	jjObjectPresets[OBJECT::ICEBULLET].counterEnd = 255;
	jjObjectPresets[OBJECT::ICEBULLET].behavior = ShieldBullet;
	jjObjectPresets[OBJECT::ICEBULLET].animSpeed = 5; //damage
	
	jjObjectPresets[OBJECT::SEEKERBULLET].special = jjObjectPresets[OBJECT::SEEKERBULLET].determineCurAnim(ANIM::SONCSHIP, 0);
	jjObjectPresets[OBJECT::SEEKERBULLET].behavior = ReboundMissile;
	jjObjectPresets[OBJECT::SEEKERBULLET].xSpeed = 4.5;
	jjObjectPresets[OBJECT::SEEKERBULLET].animSpeed = 8; //damage
	jjWeapons[WEAPON::SEEKER].style = WEAPON::NORMAL;
	
	jjObjectPresets[OBJECT::RFBULLET].special = jjObjectPresets[OBJECT::RFBULLET].determineCurAnim(ANIM::AMMO, 57);
	jjObjectPresets[OBJECT::RFBULLET].behavior = PassOnThrough;
	jjObjectPresets[OBJECT::RFBULLET].var[6] = 16; //passes through enemies
	jjObjectPresets[OBJECT::RFBULLET].killAnim = jjObjectPresets[OBJECT::BLASTERBULLET].killAnim;
	jjObjectPresets[OBJECT::RFBULLET].animSpeed = 1; //damage
	jjWeapons[WEAPON::RF].style = WEAPON::POPCORN;
	jjObjectPresets[OBJECT::RFBULLETPU].animSpeed = 11; //damage
	jjObjectPresets[OBJECT::RFBULLETPU].counterEnd = 100;
	
	jjObjectPresets[OBJECT::TOASTERBULLET].behavior = PathOfFire;
	jjObjectPresets[OBJECT::TOASTERBULLET].var[6] = 2 + 16; //passes through enemies and burns them to death
	jjObjectPresets[OBJECT::TOASTERBULLET].lightType = LIGHT::BRIGHT;
	jjObjectPresets[OBJECT::TOASTERBULLET].light = 8;
	jjWeapons[WEAPON::TOASTER].style = WEAPON::MISSILE;
	
	jjObjectPresets[OBJECT::TNT].counterEnd = 1;
	jjObjectPresets[OBJECT::TNT].behavior = RepositionTNT;
	
	jjObjectPresets[OBJECT::FIREBALLBULLETPU].determineCurAnim(ANIM::SONCSHIP, 1);
	jjObjectPresets[OBJECT::FIREBALLBULLETPU].determineCurFrame();
	jjObjectPresets[OBJECT::FIREBALLBULLETPU].behavior = ChainGang;
	jjObjectPresets[OBJECT::FIREBALLBULLETPU].xSpeed = 8;
	jjObjectPresets[OBJECT::FIREBALLBULLETPU].var[6] = 0; //nothing special
	
	jjObjectPresets[OBJECT::ELECTROBULLET].special = jjObjectPresets[OBJECT::ELECTROBULLET].determineCurAnim(ANIM::AMMO, 76);
	jjObjectPresets[OBJECT::ELECTROBULLET].behavior = Chakram;
	jjObjectPresets[OBJECT::ELECTROBULLET].var[6] = 0; //nothing special
	jjObjectPresets[OBJECT::ELECTROBULLET].killAnim = jjObjectPresets[0].determineCurAnim(ANIM::AMMO, 2, false);
	jjObjectPresets[OBJECT::ELECTROBULLET].animSpeed = 9; //damage
	
	for (int i = 0; i < 8; ++i) {
		jjObjectPresets[OBJECT::ICEAMMO3 + i].lightType = LIGHT::RING2;
		jjObjectPresets[OBJECT::ICEAMMO3 + i].light = 7;
	}
	jjObjectPresets[OBJECT::SEEKERAMMO3].behavior = RisingAmmo;
	jjObjectPresets[OBJECT::SEEKERAMMO3].isBlastable = false;
	jjObjectPresets[OBJECT::DESTRUCTSCENERY].scriptedCollisions = true;
	jjObjectPresets[OBJECT::SAVEPOST].scriptedCollisions = true;
	jjObjectPresets[OBJECT::EXTRALIFE].scriptedCollisions = true;
	jjObjectPresets[OBJECT::TRIGGERCRATE].scriptedCollisions = true;
	
	jjObjectPresets[OBJECT::FASTFIRE].curAnim = jjObjectPresets[OBJECT::CARROT].curAnim;
	jjObjectPresets[OBJECT::FASTFIRE].eventID = OBJECT::CARROT;
	
	
	jjObjectPresets[OBJECT::EVA].behavior = MyEva;
	jjObjectPresets[OBJECT::EVA].scriptedCollisions = true;
	
	jjObjectPresets[OBJECT::LEMON].behavior = Stars;
	jjObjectPresets[OBJECT::LEMON].determineCurAnim(ANIM::AMMO, 3);
	jjObjectPresets[OBJECT::LEMON].playerHandling = HANDLING::PARTICLE;
	
	jjObjectPresets[OBJECT::BIGROCK].behavior = RockBarrier;
	jjObjectPresets[OBJECT::BIGROCK].scriptedCollisions = true;
	jjObjectPresets[OBJECT::BIGROCK].energy = 100;
	
	jjObjectPresets[OBJECT::FRIES].behavior = TempleLock;
	jjObjectPresets[OBJECT::FRIES].scriptedCollisions = true;
	jjObjectPresets[OBJECT::FRIES].playerHandling = HANDLING::SPECIAL;
	
	jjObjectPresets[OBJECT::STRAWBERRY].behavior = Acolyte;
	jjObjectPresets[OBJECT::STRAWBERRY].bulletHandling = HANDLING::IGNOREBULLET;
	jjObjectPresets[OBJECT::STRAWBERRY].playerHandling = HANDLING::PARTICLE;
	jjObjectPresets[OBJECT::STRAWBERRY].determineCurAnim(ANIM::EVA, 3);
	jjObjectPresets[OBJECT::STRAWBERRY].determineCurFrame();
	jjObjectPresets[OBJECT::STRAWBERRY].light = 0;
	jjObjectPresets[OBJECT::STRAWBERRY].lightType = LIGHT::BRIGHT;
	
	
	jjObjectPresets[OBJECT::CRAB].energy = 30;
	jjObjectPresets[OBJECT::CRAB].behavior = InvisibleCrab;
	
	jjObjectPresets[OBJECT::NORMTURTLE].energy = 25;
	jjObjectPresets[OBJECT::NORMTURTLE].behavior = Turtle;
	jjObjectPresets[OBJECT::NORMTURTLE].playerHandling = HANDLING::ENEMY;
	jjObjectPresets[OBJECT::NORMTURTLE].bulletHandling = HANDLING::DESTROYBULLET;
	jjObjectPresets[OBJECT::NORMTURTLE].determineCurAnim(ANIM::TURTLE, 5);
	jjObjectPresets[OBJECT::NORMTURTLE].determineCurFrame();
	jjObjectPresets[OBJECT::NORMTURTLE].isBlastable = false;
	
	jjObjectPresets[OBJECT::PEAR].energy = 50;
	jjObjectPresets[OBJECT::PEAR].behavior = UFO;
	jjObjectPresets[OBJECT::PEAR].playerHandling = HANDLING::ENEMY;
	jjObjectPresets[OBJECT::PEAR].points = 500;
	jjObjectPresets[OBJECT::PEAR].determineCurAnim(ANIM::FRUITPLAT, 0);
	jjObjectPresets[OBJECT::PEAR].determineCurFrame();
	
	jjObjectPresets[OBJECT::STEAM].energy = 1;
	jjObjectPresets[OBJECT::STEAM].behavior = JumpyGuy;
	jjObjectPresets[OBJECT::STEAM].playerHandling = HANDLING::ENEMY;
	jjObjectPresets[OBJECT::STEAM].bulletHandling = HANDLING::HURTBYBULLET;
	jjObjectPresets[OBJECT::STEAM].points = 10;
	jjObjectPresets[OBJECT::STEAM].frameID = 2;
	jjObjectPresets[OBJECT::STEAM].determineCurFrame();
	jjObjectPresets[OBJECT::STEAM].special = 224;
	
	jjObjectPresets[OBJECT::BEEBOY].energy = 1;
	jjObjectPresets[OBJECT::BEEBOY].points = 20;
	jjObjectPresets[OBJECT::BEEBOY].behavior = KamikazBee;
	
	jjObjectPresets[OBJECT::BILSY].energy = 120;
	jjObjectPresets[OBJECT::BILSY].points = 1000;
	jjObjectPresets[OBJECT::BILSY].playerHandling = HANDLING::ENEMY;
	jjObjectPresets[OBJECT::BILSY].bulletHandling = HANDLING::HURTBYBULLET;
	jjObjectPresets[OBJECT::BILSY].behavior = MiniBilsy;
	jjObjectPresets[OBJECT::BILSY].direction = -1;
	
	jjObjectPresets[OBJECT::HELMUT].energy = 100;
	jjObjectPresets[OBJECT::HELMUT].special = 400;
	jjObjectPresets[OBJECT::HELMUT].behavior = StatueBoss;
	jjObjectPresets[OBJECT::HELMUT].state = STATE::DELAYEDSTART;
	jjObjectPresets[OBJECT::HELMUT].direction = 1;
	jjObjectPresets[OBJECT::HELMUT].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[OBJECT::HELMUT].bulletHandling = HANDLING::IGNOREBULLET;
	jjObjectPresets[OBJECT::HELMUT].scriptedCollisions = true;
	jjObjectPresets[OBJECT::HELMUT].determineCurAnim(ANIM::AMMO, 8);
	jjObjectPresets[OBJECT::HELMUT].frameID = 7;
	jjObjectPresets[OBJECT::HELMUT].determineCurFrame();
	
	jjObjectPresets[OBJECT::MILK].behavior = Goddess;
	jjObjectPresets[OBJECT::MILK].determineCurAnim(ANIM::VINE, 1);
	jjObjectPresets[OBJECT::MILK].determineCurFrame();
	jjObjectPresets[OBJECT::MILK].bulletHandling = HANDLING::IGNOREBULLET; //to start off with
	jjObjectPresets[OBJECT::MILK].playerHandling = HANDLING::PARTICLE; //to start off with
	jjObjectPresets[OBJECT::MILK].energy = 60;
	jjObjectPresets[OBJECT::MILK].special = 4800;
	jjObjectPresets[OBJECT::MILK].scriptedCollisions = true;
	
	jjObjectPresets[OBJECT::BOLLY].behavior = Bolly1;
	jjObjectPresets[OBJECT::BOLLY].determineCurAnim(ANIM::SONCSHIP, 2); //it probably already is, but can't hurt
	jjObjectPresets[OBJECT::BOLLY].determineCurFrame();
	jjObjectPresets[OBJECT::BOLLY].age = BOLLYHEALTH1;
	jjObjectPresets[OBJECT::BOLLY].energy = BOLLYHEALTH1 / 10;
	jjObjectPresets[OBJECT::BOLLY].bulletHandling = HANDLING::DESTROYBULLET;
	jjObjectPresets[OBJECT::BOLLY].deactivates = false;
	jjObjectPresets[OBJECT::BOLLY].scriptedCollisions = false;
	jjObjectPresets[OBJECT::BOLLY].isBlastable = false; //don't mess with the speeds, please
	
}

void BallBullet(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		obj.state = STATE::FLY;
		obj.xAcc = obj.yAcc = 0;
	} else if (obj.state == STATE::EXPLODE) {
		obj.counterEnd = 99;
	}
	if (jjMaskedPixel(obj.xPos + obj.xSpeed, obj.yPos)) {
		obj.xSpeed = -obj.xSpeed;
		++obj.counterEnd;
	} else if (jjMaskedPixel(obj.xPos, obj.yPos + obj.ySpeed)) {
		obj.ySpeed = -obj.ySpeed;
		++obj.counterEnd;
	}
	if (obj.counterEnd > 4) {
		obj.state = STATE::EXPLODE;
		obj.behavior = BEHAVIOR::RFBULLET;
		obj.frameID = 7;
		obj.counter = 0;
		return;
	}
	obj.xPos = obj.xPos + obj.xSpeed;
	obj.yPos = obj.yPos + obj.ySpeed;
	if (++obj.counter >= 8) {
		obj.counter = 0;
		if (++obj.frameID > 6) obj.frameID = 4;
		obj.determineCurFrame();
		uint rand = jjRandom();
		//if ((rand & 3) == 0) {
			jjPARTICLE@ fire = jjAddParticle(PARTICLE::FIRE);
			if (fire !is null) {
				fire.xPos = obj.xPos;
				fire.yPos = obj.yPos;
				fire.fire.size = (rand >> 2) & 3;
			}
		//}
	}
	jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 0, SPRITE::PALSHIFT, obj.var[0], 3);
}

void ShieldBullet(jjOBJ@ obj) {
	if (obj.state == STATE::START)
		obj.state = STATE::FLY;
	jjPLAYER@ play = jjPlayers[obj.creatorID];
	float xChange = (jjRandom() & 63) / 128.0 - 0.25;
	float yChange = (jjRandom() & 63) / 128.0 - 0.25;
	if (play.xPos < obj.xPos)
		xChange -= .25;
	else
		xChange += .25;
	if (play.yPos < obj.yPos)
		yChange -= .25;
	else
		yChange += .25;
	obj.xSpeed = obj.xSpeed + xChange;
	obj.ySpeed = obj.ySpeed + yChange;
	if (!play.keyFire) obj.behavior = BEHAVIOR::BULLET;
	obj.behave(BEHAVIOR::BULLET); //eh
}

void ReboundMissile(jjOBJ@ missile) {
	jjPLAYER@ play = jjPlayers[missile.creatorID];
	play.xSpeed = play.xSpeed - missile.xSpeed;
	play.ySpeed = play.ySpeed - missile.ySpeed;
	play.alreadyDoubleJumped = missile.ySpeed < 0; //so that Spaz can get the same height increases that Jazz and Lori do by limiting their gravity through coptering
	missile.var[0] = atan2(missile.ySpeed, missile.xSpeed) * 180;
	missile.behavior = BEHAVIOR::BOLLYBULLET; //on with the show
}

void PassOnThrough(jjOBJ@ missile) {
	if (missile.state == STATE::START) missile.state = STATE::ROCKETFLY;
	else if (missile.state == STATE::DEACTIVATE) missile.delete();
	missile.xPos = missile.xPos + missile.xSpeed + missile.xAcc;
	missile.yPos = missile.yPos + missile.ySpeed + missile.yAcc;
	if (jjMaskedPixel(missile.xPos, missile.yPos)) {
		missile.state = STATE::EXPLODE;
		missile.behavior = BEHAVIOR::EXPLOSION2;
		missile.frameID = 0;
	}
	missile.xAcc = missile.xAcc * 1.1;
	//missile.yAcc = missile.yAcc * 1.1;
	if ((jjRandom() & 7) == 0) {
		jjPARTICLE@ spark = jjAddParticle(PARTICLE::SPARK);
		if (spark !is null) {
			spark.xPos = missile.xPos;
			spark.yPos = missile.yPos;
			spark.xSpeed = -missile.xSpeed;
			spark.ySpeed = -missile.ySpeed;
		}
	}
	missile.counter += 45 + (jjRandom() & 31);
	jjDrawSprite(missile.xPos, missile.yPos + jjCos(missile.counter) * 3, ANIM::AMMO, 57, jjGameTicks % 10, missile.direction, SPRITE::PALSHIFT, 248);
}

void PathOfFire(jjOBJ@ flame) {
	if (flame.state == STATE::START) {
		jjSample(flame.xPos, flame.yPos, SOUND::AMMO_FIREGUN2A);
		if (jjMaskedPixel(flame.xPos, flame.yPos)) {
			flame.delete();
			return;
		}
		flame.state = STATE::FLY;
		if (flame.direction == 0) flame.direction = (flame.xSpeed < 0) ? -1 : 1;
		float nuY = flame.yPos + (jjRandom() & 7);
		if (!jjMaskedPixel(flame.xPos, nuY))
			flame.yPos = nuY;
		if (flame.creatorType == CREATOR::PLAYER) {// && jjRandom() & 7 == 0) {
			jjPARTICLE@ smoke = jjAddParticle(PARTICLE::SMOKE);
			if (smoke !is null) {
				smoke.xPos = flame.xPos;
				smoke.yPos = flame.yPos;
			}
		} else
			flame.animSpeed = 2;
	}
	if (++flame.counter >= 2) {
		flame.counter = 0;
		if (++flame.frameID == 2) {
			jjOBJ@ nuFlame = jjObjects[jjAddObject(OBJECT::TOASTERBULLET, flame.xPos + 10 * flame.direction, flame.yPos, flame.creatorID, flame.creatorType)];
			nuFlame.direction = flame.direction;
			nuFlame.playerHandling = flame.playerHandling;
		} else if (flame.frameID == 8)
			flame.delete();
	}
	flame.determineCurFrame();
	flame.draw();
}

void RepositionTNT(jjOBJ@ tnt) {
	jjPLAYER@ play = jjPlayers[tnt.creatorID];
	tnt.xPos = jjMouseX + play.cameraX;
	tnt.yPos = jjMouseY + play.cameraY;
	tnt.behavior = BEHAVIOR::TNT; //all done
}

void ChainGang(jjOBJ@ chain) {
	if (chain.state == STATE::START) chain.state = STATE::FLY;
	else if (chain.state == STATE::DEACTIVATE || chain.state == STATE::EXPLODE || chain.yPos < 0 || chain.xPos < 0) {
		if (--chain.counter <= 0) chain.delete();
	} else {
		chain.animSpeed = ++chain.counter >> 2;
		//if (chain.animSpeed > 15) chain.animSpeed = 15; //maximum strength
		chain.xPos = chain.xPos + chain.xSpeed;
		chain.yPos = chain.yPos + chain.ySpeed;
		if (jjMaskedPixel(chain.xPos, chain.yPos))
			chain.state = STATE::EXPLODE;
	}
	for (int i = 0; i < chain.counter - 1; ++i)
		jjDrawSpriteFromCurFrame(chain.xPos - chain.xSpeed * i, chain.yPos - chain.ySpeed * i, chain.curFrame, 0, SPRITE::TINTED, (i <= 4) ? 24 : 215);
		
}

void Chakram(jjOBJ@ ring) {
	if (ring.state == STATE::START) ring.state = STATE::FLY;
	jjPLAYER@ play = jjPlayers[ring.creatorID];
	if (play.xPos < ring.xPos) ring.xAcc = ring.xAcc - .03;
	else ring.xAcc = ring.xAcc + .03;
	if (play.yPos < ring.yPos) ring.yAcc = ring.yAcc - .03;
	else ring.yAcc = ring.yAcc + .03;
	ring.xPos = ring.xPos + ring.xSpeed + ring.xAcc;
	ring.yPos = ring.yPos + ring.ySpeed + ring.yAcc;
	if (jjMaskedPixel(ring.xPos, ring.yPos))
		ring.state = STATE::EXPLODE;
	if (ring.state == STATE::EXPLODE) {
		ring.behavior = BEHAVIOR::EXPLOSION2;
	}
	ring.frameID = (++ring.counter >> 1) % 9;
	ring.determineCurFrame();
	ring.draw();
	
}







void DisplayHealth(jjOBJ@ enemy, int yOffset = -30) {
	int health = enemy.energy;
	float xPos = enemy.xPos - (jjObjectPresets[enemy.eventID].energy/2);
	float yPos = enemy.yPos + yOffset;
	while (health > 0) {
		jjDrawSprite(xPos, yPos, ANIM::PICKUPS, 41, 0, 0, SPRITE::NORMAL, 0, 3); //heart
		xPos += 10;
		health -= 10;
	}
}


void InvisibleCrab(jjOBJ@ enemy) {
	if (enemy.state == STATE::KILL) enemy.deactivate(); //by using deactivate instead of delete, enemies will reappear when left alone, like if spawned by a generator, yet they won't regenerate while you still haven't left the spot where they died
	else {
		enemy.behave(BEHAVIOR::CRAB, (enemy.justHit > 0));
		if (enemy.justHit == 0)
		jjDrawSpriteFromCurFrame(enemy.xPos, enemy.yPos, enemy.curFrame, enemy.direction, SPRITE::NEONGLOW, 1);
		DisplayHealth(enemy);
	}
	//jjDrawTile(enemy.xPos - 32, enemy.yPos - 32, 737, TILE::TOPLEFT);
	//jjDrawTile(enemy.xPos + 16, enemy.yPos - 32, 737, TILE::TOPRIGHT);
	//jjDrawTile(enemy.xPos - 32, enemy.yPos + 16, 737, TILE::BOTTOMLEFT);
	//jjDrawTile(enemy.xPos + 16, enemy.yPos + 16, 737, TILE::BOTTOMRIGHT);
}

void Turtle(jjOBJ@ enemy) {
	switch (enemy.state) {
		case STATE::START:
			enemy.state = STATE::FALL;
			break;
		case STATE::FALL:
			if (jjMaskedPixel(enemy.xPos, enemy.yPos + enemy.ySpeed)) {
				enemy.state = STATE::FADEIN;
				enemy.yPos = enemy.yPos - 32;
				enemy.putOnGround(true);
				enemy.determineCurAnim(ANIM::TURTLE, 3);
				enemy.frameID = 0;
				enemy.counter = 0;
				if (jjLocalPlayers[0].xPos < enemy.xPos) enemy.direction = -1;
				else enemy.direction = 1;
			} else {
				enemy.yPos = enemy.yPos + enemy.ySpeed;
				enemy.ySpeed = enemy.ySpeed + .08;
			}
			break;
		case STATE::FADEIN:
			if (++enemy.counter > 5) {
				enemy.counter = 0;
				if (++enemy.frameID >= 6) {
					enemy.determineCurAnim(ANIM::TURTLE, 1);
					enemy.state = STATE::IDLE;
					enemy.bulletHandling = HANDLING::HURTBYBULLET;
				}
				enemy.determineCurFrame();
			}
			break;
		case STATE::IDLE:
			if ((jjRandom() & 31) == 0) {
				enemy.state = STATE::ATTACK;
				enemy.determineCurAnim(ANIM::TURTLE, 0);
				enemy.frameID = 0;
				enemy.counter = 0;
				jjSample(enemy.xPos, enemy.yPos, SOUND::SPAZSOUNDS_BURP);
			} else {
				if (++enemy.counter > 4) {
					enemy.counter = 0;
					if (++enemy.frameID >= 11)
						enemy.frameID = 0;
					enemy.determineCurFrame();
				}
			}
			break;
		case STATE::ATTACK:
			if (++enemy.counter > 3) {
				enemy.counter = 0;
				if (++enemy.frameID >= 11) {
					enemy.determineCurAnim(ANIM::TURTLE, 1);
					enemy.state = STATE::IDLE;
					enemy.frameID = 0;
				} else if (enemy.frameID >= 3 && enemy.frameID <= 6) {
					jjOBJ@ obj = jjObjects[jjAddObject(OBJECT::BULLET, enemy.xPos + 45 * enemy.direction, enemy.yPos - 10, enemy.objectID, CREATOR::OBJECT)];
					obj.behavior = BEHAVIOR::BULLET;
					obj.determineCurAnim(ANIM::AMMO, 14);
					obj.killAnim = jjObjectPresets[OBJECT::BLASTERBULLET].killAnim;
					obj.playerHandling = HANDLING::ENEMY;
					obj.energy = 1;
					obj.points = 5;
					obj.lightType = LIGHT::POINT;
					obj.xAcc = .2;
					obj.xSpeed = 1.2;
					int ys = (jjRandom() & 3);
					obj.ySpeed = ys - 2;
					obj.counterEnd = 25;
				}
				enemy.determineCurFrame();
			}
			break;
		case STATE::DEACTIVATE:
		case STATE::KILL:
			enemy.deactivate();
			break;
	}
	enemy.draw();
	if (enemy.bulletHandling == HANDLING::HURTBYBULLET)
		DisplayHealth(enemy);
}


void UFO(jjOBJ@ enemy) {
	if (enemy.state == STATE::KILL || enemy.state == STATE::DEACTIVATE) {
		enemy.deactivate();
	} else { //nothing complicated enough to bother disabling STATE::START
		++enemy.counter; //use counter instead of gameticks so different enemies will be in different places
		enemy.xPos = jjSin(enemy.counter*5)*120 + enemy.xOrg;
		enemy.yPos = jjSin(enemy.counter*15 + 256)*20 + enemy.yOrg;
		if (jjRandom() & 63 == 0) {
			jjOBJ@ obj = jjObjects[enemy.fireBullet(OBJECT::BULLET)];
			obj.behavior = BEHAVIOR::BULLET;
			obj.determineCurAnim(ANIM::FRUITPLAT, 1);
			obj.playerHandling = HANDLING::ENEMY;
			obj.energy = 1;
			obj.points = 5;
			obj.ySpeed = 6;
			obj.counterEnd = 100;
		}
	}
	if (enemy.justHit == 0)
		jjDrawSpriteFromCurFrame(enemy.xPos, enemy.yPos, enemy.curFrame, 0, SPRITE::PALSHIFT, 112);
	else
		enemy.draw();
	DisplayHealth(enemy);
}

array<SOUND::Sample> BoingNoises = {SOUND::AMMO_BMP1, SOUND::AMMO_BMP2, SOUND::AMMO_BMP3, SOUND::AMMO_BMP4, SOUND::AMMO_BMP5, SOUND::AMMO_BMP6};
void JumpyGuy(jjOBJ@ enemy) {
	if (enemy.state == STATE::START) {
		jjSample(enemy.xPos, enemy.yPos, SOUND::STEAM_STEAM);
		enemy.state = STATE::FALL;
		enemy.xSpeed = (int(jjRandom() & 15) - 8) / 3;
		if (enemy.creatorType == CREATOR::LEVEL) {
			enemy.special = 208;
			for (int i = (jjRandom() & 7) + 6; i > 0; --i) {
				jjOBJ@ obj = jjObjects[jjAddObject(OBJECT::STEAM, enemy.xPos + (jjRandom() & 63) - 32, enemy.yPos - (jjRandom() & 63), enemy.objectID)];
			}
		}
	} else if (enemy.state == STATE::DEACTIVATE) {
		enemy.deactivate();
	} else if (enemy.state == STATE::KILL) {
		enemy.grantPickup(jjLocalPlayers[0], 1);
		enemy.deactivate();
	} else {
		if (jjMaskedPixel(enemy.xPos + enemy.xSpeed, enemy.yPos)) {
			enemy.xSpeed = -enemy.xSpeed;
		} else if (jjMaskedPixel(enemy.xPos, enemy.yPos + enemy.ySpeed)) {
			enemy.ySpeed = -enemy.ySpeed;
			jjSample(enemy.xPos, enemy.yPos, BoingNoises[jjRandom() % BoingNoises.length()], 48);
		}
		enemy.xPos = enemy.xPos + enemy.xSpeed;
		enemy.yPos = enemy.yPos + enemy.ySpeed;
		enemy.ySpeed = enemy.ySpeed + .3;
		if (enemy.ySpeed < -7) enemy.ySpeed = -7;
		if (enemy.justHit == 0)
			jjDrawSpriteFromCurFrame(enemy.xPos, enemy.yPos, enemy.curFrame, 0, SPRITE::PALSHIFT, enemy.special);
		else
			enemy.draw();
		//DisplayHealth(enemy);
	}
}

void KamikazBee(jjOBJ@ enemy) {
	if (enemy.state == STATE::KILL || enemy.state == STATE::DEACTIVATE || jjTriggers[5]) {
		enemy.deactivate();
	} else { //nothing complicated enough to bother disabling STATE::START
		if (enemy.var[0] == 0) { //not triggered yet
			enemy.counter += 10; //use counter instead of gameticks so different enemies will be in different places
			enemy.xPos = jjSin(enemy.counter)*24 + enemy.xOrg;
			enemy.yPos = jjCos(enemy.counter)*24 + enemy.yOrg;
			int playerID = enemy.findNearestPlayer(50000);
			if (playerID > -1) {
				jjSample(enemy.xPos, enemy.yPos, SOUND::COMMON_DOWN);
				jjPLAYER@ play = jjPlayers[playerID];
				float angle = atan2(enemy.xPos - play.xPos, enemy.yPos - play.yPos);
				enemy.var[0] = 1;
				enemy.lightType = LIGHT::POINT;
				enemy.counter = 40;
				enemy.xSpeed = -8 * sin(angle);
				enemy.ySpeed = -8 * cos(angle);
				enemy.direction = enemy.xSpeed;
			} else {
				enemy.direction = (((enemy.counter + 256) & 1023) < 512) ? 1 : -1;
				/*if ((jjRandom() & 511) == 0 && enemy.var[1] == 0) {
					enemy.var[1] = 1;
					jjAddObject(OBJECT::BEEBOY, enemy.xPos, enemy.yPos, enemy.objectID);
					jjSample(enemy.xPos, enemy.yPos, SOUND::BUMBEE_BEELOOP);
				}*/
			}
		} else {
			if (enemy.counter == 0) { //slight delay before attacking
				enemy.xPos = enemy.xPos + enemy.xSpeed;
				enemy.yPos = enemy.yPos + enemy.ySpeed;
			}
			else --enemy.counter;
		}
		enemy.frameID = (jjGameTicks >> 1) & 3;
		enemy.determineCurFrame();
	}
	jjDrawSpriteFromCurFrame(enemy.xPos, enemy.yPos + jjSin(jjGameTicks << 4) * 3, enemy.curFrame, enemy.direction);
}

void MiniBilsy(jjOBJ@ enemy) {
	switch (enemy.state) {
	case STATE::START:
		enemy.state = STATE::IDLE;
		enemy.putOnGround(true);
		enemy.determineCurAnim(ANIM::BILSBOSS, 4); //idle
		break;
	case STATE::KILL:
	case STATE::DEACTIVATE:
		enemy.deactivate();
		break;
	case STATE::IDLE:
		enemy.frameID = (jjGameTicks >> 2) & 7;
		enemy.determineCurFrame();
		if (enemy.energy < 120) { //injured
			if (enemy.var[0] == 0) { jjSample(enemy.xPos, enemy.yPos, SOUND::BILSBOSS_THUNDER); enemy.var[0] = 1; }
			switch (jjRandom() & 127) {
				case 0:
					enemy.state = STATE::ATTACK;
					enemy.determineCurAnim(ANIM::BILSBOSS, 0);
					enemy.counter = 0;
					break;
				case 1: //teleport
				case 2:
				case 3:
					enemy.state = STATE::FADEOUT;
					enemy.determineCurAnim(ANIM::BILSBOSS, 2);
					enemy.counter = 0;
					if (jjDifficulty > 1) { //hard/turbo: invincible while transportalizing
						enemy.playerHandling = HANDLING::PARTICLE;
						enemy.bulletHandling = HANDLING::IGNOREBULLET;
					} {
						jjPLAYER@ play = jjLocalPlayers[0];
						enemy.xAcc = play.xPos;
						enemy.yAcc = play.yPos;
					}
					break;
			}
		}
		break;
	case STATE::FADEOUT:
		if (++enemy.counter < 68) {
			enemy.frameID = enemy.counter >> 2;
			enemy.determineCurFrame();
		} else {
			enemy.frameID = enemy.counter = 0;
			enemy.state = STATE::FADEIN;
			jjSample(enemy.xPos, enemy.yPos, SOUND::BILSBOSS_BILLAPPEAR);
			enemy.determineCurAnim(ANIM::BILSBOSS, 1);
			enemy.xPos = enemy.xAcc;
			enemy.yPos = enemy.yAcc - 32;
			enemy.direction = (jjLocalPlayers[0].xPos < enemy.xPos) ? -1 : 1;
			enemy.determineCurFrame();
			enemy.putOnGround(true);
			return;
		}
		break;
	case STATE::FADEIN:
		if (++enemy.counter < 40) {
			enemy.frameID = enemy.counter >> 1;
			enemy.determineCurFrame();
		} else {
			enemy.frameID = enemy.counter = 0;
			enemy.determineCurAnim(ANIM::BILSBOSS, 4);
			enemy.state = STATE::IDLE;
			enemy.playerHandling = HANDLING::ENEMY;
			enemy.bulletHandling = HANDLING::HURTBYBULLET;
		}
		break;
	case STATE::ATTACK:
		if (++enemy.counter < 36) {
			enemy.frameID = enemy.counter >> 1;
			enemy.determineCurFrame();
			if (enemy.counter == 32) {
				jjObjects[enemy.fireBullet(OBJECT::TOASTERBULLET)].playerHandling = HANDLING::ENEMYBULLET;
			}
		} else {
			enemy.frameID = enemy.counter = 0;
			enemy.determineCurAnim(ANIM::BILSBOSS, 4);
			enemy.state = STATE::IDLE;
		}
		break;
	}
	enemy.draw();
	DisplayHealth(enemy, -45);
}


void Stars(jjOBJ@ star) {
	if (star.state == STATE::START) {
		star.behave(BEHAVIOR::BIRDFEATHER); //get some nice random speeds
		star.frameID = 0;
		star.determineCurFrame();
		star.xSpeed = star.xSpeed * 1.3; //faster x dimension
	} else if (star.state == STATE::DEACTIVATE) {
		star.delete();
	} else {
		star.xPos = star.xPos + star.xSpeed;
		star.yPos = star.yPos + star.ySpeed;
		uint8 size = uint(abs(star.xPos - star.xOrg) + abs(star.yPos - star.yOrg)) >> 4;
		if (size < 5) size = 5;
		jjDrawSpriteFromCurFrame(star.xPos, star.yPos, star.curFrame, 0, SPRITE::RESIZED, size, 3);
	}
}

void RockBarrier(jjOBJ@ rock) {
	if (rock.energy < 100) rock.energy += 5;
	if (rock.state == STATE::DEACTIVATE) rock.deactivate();
	else if (rock.state == STATE::START) {
		rock.xPos = rock.xPos - 16;
		rock.yPos = rock.yPos + 16;
		rock.state = STATE::IDLE;
	}
	else if (rock.state == STATE::IDLE) {
		rock.beSolid();
		rock.draw();
		DisplayHealth(rock);
	}
}
void RisingAmmo(jjOBJ@ obj) {
	if (!jjMaskedPixel(obj.xPos, obj.yPos - 2))
		obj.yPos = obj.yPos - 2;
	obj.xSpeed = obj.ySpeed = 0; //just in case
	obj.behave(BEHAVIOR::PICKUP);
}
void TrackingAmmo(jjOBJ@ obj) {
	if (obj.yPos < jjLocalPlayers[0].yPos)
		obj.yPos = obj.yPos + 1;
	else
		obj.yPos = obj.yPos - 1;
	obj.behave(BEHAVIOR::PICKUP);
}


void FallingSpikes(jjOBJ@ spike) {
	if (spike.state == STATE::START) {
		spike.determineCurAnim(ANIM::AMMO, 3);
		spike.frameID = 3;
		spike.determineCurFrame();
		spike.state = STATE::FALL;
		spike.lightType = LIGHT::POINT;
		spike.energy = 1;
		spike.points = 5;
		spike.playerHandling = HANDLING::ENEMY;
	}
	else if (spike.state == STATE::KILL || spike.state == STATE::DEACTIVATE)
		spike.delete();
	spike.yPos = spike.yPos + 5;
	jjDrawTile(spike.xPos - 16, spike.yPos - 16, 752);
	//spike.draw();
}

array<float> xRockOffsets = {-40, -30, -41, 10};
array<float> yRockOffsets = {2, 57, 112, 112};
void StatueBoss(jjOBJ@ boss) {
	uint8 eyecolor = 8;
	if (boss.state == STATE::DELAYEDSTART || boss.state == STATE::EXPLODE) {
		if (++boss.counter < 140) {
			eyecolor = (boss.state == STATE::DELAYEDSTART) ? 70 : 56;
			boss.xPos = boss.xOrg + (jjRandom() & 3) - 2;
			boss.yPos = boss.yOrg + (jjRandom() & 3) - 2;
			if ((jjRandom() & 7) == 0) {
				jjObjects[
					jjAddObject(
						OBJECT::EXPLOSION,
						boss.xPos + (jjRandom() & 63) - 48,
						boss.yPos + (jjRandom() & 127) - 16,
						boss.objectID
					)
				].determineCurAnim(ANIM::AMMO, 2);
				jjSample(boss.xPos, boss.yPos, SOUND::COMMON_DAMPED1);
			}
		} else if (boss.counter >= 200) {
			if (boss.state == STATE::DELAYEDSTART) {
				boss.counter = 0;
				boss.state = STATE::WAKE;
				boss.bulletHandling = HANDLING::DETECTBULLET;
				boss.lightType = LIGHT::BRIGHT;
				boss.light = 9;
				jjMusicLoad("darkness.mod") || jjMusicLoad("boss intro.it") || jjMusicLoad("boss.j2b");
			} else {
				for (int i = 0; i < 4; ++i) {
					jjOBJ@ rock = jjObjects[boss.var[i]];
					if (i < 2) rock.particlePixelExplosion(0);
					rock.delete();
				}
				boss.delete();
			}
		}
	} else {
		float yOffset = jjSin(boss.counter += 4) * 250;
		if (yOffset > 0)
			yOffset = 0;
		else {
			if (yOffset > -2)
				jjSample(boss.xPos, boss.yPos, SOUND::COMMON_SPRING1, 63, 6000);
			if (boss.direction == 1) {
				if (boss.xPos >= boss.xOrg + 560)
					boss.direction = -1;
			} else {
				if (boss.xPos <= boss.xOrg)
					boss.direction = 1;
			}
			boss.xPos = boss.xPos + (jjRandom() & 3) * boss.direction;
			if (yOffset < -249) {
				if (jjDifficulty < 2 || (jjRandom() & 3) > 0) {
					jjAddObject(OBJECT::BULLET, (61 + (jjRandom() % 20)) * 32, 30*32, boss.objectID, CREATOR::OBJECT, FallingSpikes);
					jjSample(boss.xPos, boss.yPos, SOUND::COMMON_SPLAT2);
				} else
					jjAddObject(OBJECT::STEAM, (61 + (jjRandom() % 20)) * 32, 31*32, boss.objectID);
			}
		}
		boss.yPos = boss.yOrg + yOffset;
		for (int i = 0; i < 4; ++i) {
			jjOBJ@ rock = jjObjects[boss.var[i]];
			rock.xPos = boss.xPos + xRockOffsets[i] * boss.direction;
			rock.yPos = boss.yPos + yRockOffsets[i];
			//rock.draw();
		}
	}
	
	for (int x = 0; x < 4; ++x)
		for (int y = 0; y < 6; ++y) {
			if (boss.direction == 1)
				jjDrawTile(boss.xPos - 66 + x*32, boss.yPos - 55 + y*32, 733 + x + y*10);
			else
				jjDrawTile(boss.xPos - 62 + x*32, boss.yPos - 55 + y*32, 736 - x + y*10 + TILE::HFLIPPED);
		}
	
	if (boss.justHit > 0) eyecolor = 64;
	jjDrawSpriteFromCurFrame(boss.xPos, boss.yPos, boss.curFrame, 0, SPRITE::PALSHIFT, eyecolor);
	jjDrawSpriteFromCurFrame(boss.xPos + 20 * boss.direction, boss.yPos - 4, boss.curFrame - 7, 0, SPRITE::PALSHIFT, eyecolor);
}


void Acolyte(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		if (jjTriggers[5]) { obj.delete(); return; }
		obj.direction = (obj.xPos > 134*32) ? 1 : -1;
		obj.state = STATE::WAIT;
	} else if (obj.state == STATE::DEACTIVATE) {
		obj.deactivate();
	} else if (obj.state == STATE::WAIT) {
		if (pausedForWatchingTheGoddess) obj.state = STATE::SPRING;
		else obj.draw();
	} else if (obj.state == STATE::SPRING) {
		uint rand = jjRandom();
		if (obj.yPos == obj.yOrg && obj.xPos < 132*32) //first one, hasn't lifted up yet
			jjSamplePriority(SOUND::BILSBOSS_SCARY3);
		if (obj.counterEnd < 64) {
			if ((rand & 8) == 0) {
				obj.yPos = obj.yPos - 1;
				obj.light = (++obj.counterEnd) / 3;
				jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame + (jjRandom() & 7), obj.direction, ((rand & 16) == 0) ? SPRITE::NORMAL : SPRITE::TRANSLUCENT);
			}
		} else {
			obj.state = STATE::FLOAT;
			obj.counterEnd = 0;
		}
	} else if (obj.state == STATE::FLOAT) {
		uint rand = jjRandom();
		jjDrawSpriteFromCurFrame(obj.xPos + (rand & 7) - 4, obj.yPos + ((rand >> 3) & 7) - 4, obj.curFrame, obj.direction);
		if ((rand >> 6) & 3 == 0) {
			jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, BEHAVIOR::BIRDFEATHER)];
			spark.determineCurAnim(ANIM::AMMO, 12);
			spark.lightType = LIGHT::POINT2;
		}
		if (elapsedPauseTime >= 1080 + ((int(obj.xOrg / 32) - 130) << 2)) {
			obj.particlePixelExplosion(1);
			obj.delete();
			jjSample(obj.xPos, obj.yPos, SOUND::JAZZSOUNDS_BALANCE, 63, 16000 + obj.xOrg);
			jjSample(obj.xPos, obj.yPos, SOUND::COMMON_ELECTRIC1);
		}
	}
}

const float GODDESSSPEED = .12;
const float GODDESSMAXSPEED = 5;
const array<float> GoddessTargetsX = {137*32, 175*32, 159*32, 161*32, 189*32, 147*32};
const array<float> GoddessTargetsY = {64*32,  63*32,  70*32,  47*32,  70*32,  63*32 };
void Goddess(jjOBJ@ enemy) {
	if (enemy.state == STATE::START) {
		if (jjTriggers[5]) { enemy.delete(); return; }
		enemy.state = STATE::DELAYEDSTART;
		enemy.xPos = enemy.xOrg + 17;
		enemy.yPos = enemy.yOrg - 5;
	} else if (enemy.state == STATE::DEACTIVATE) {
		enemy.deactivate();
	} else if (enemy.state == STATE::DELAYEDSTART) {
		if (pausedForWatchingTheGoddess && elapsedPauseTime == 1120) {
			enemy.lightType = LIGHT::RING2;
			enemy.light = 80;
			jjLocalPlayers[0].activateBoss();
			jjLocalPlayers[0].boss = enemy.objectID;
			enemy.playerHandling = HANDLING::SPECIAL;
			enemy.bulletHandling = HANDLING::DETECTBULLET;
			enemy.deactivates = false;
			enemy.var[0] = 7;
		} else if (enemy.light > 0) {
			if (--enemy.light == 30)
				enemy.state = STATE::WAKE;
		}
	} else if (enemy.state == STATE::EXPLODE) {
		if (++enemy.light == -80) {
			paused = false;
			jjTriggers[5] = true;
			jjLocalPlayers[0].cameraUnfreeze();
			enemy.delete();
			jjSample(enemy.xPos, enemy.yPos, SOUND::AMMO_MISSILE, 63, 16000);
			jjAddParticlePixelExplosion(enemy.xPos, enemy.yPos + 32, jjObjectPresets[OBJECT::BILSY].curFrame, 0, 0);
			jjObjects[jjAddObject(OBJECT::BOUNCERAMMO3, enemy.xPos, enemy.yPos + 32, enemy.objectID, CREATOR::OBJECT, TrackingAmmo)].deactivates = false;
			jjMusicLoad(MUSICFILENAME);
		} else {
			jjPARTICLE@ part = jjAddParticle(PARTICLE::STRING);
			if ((jjGameTicks & 3) == 0)jjSample(enemy.xPos, enemy.yPos, SOUND::COMMON_PISTOL1);
			if (part !is null) {
				part.string.text = "|O";
				part.xPos = enemy.xPos;
				part.yPos = enemy.yPos;
				part.xSpeed = float(jjRandom() & 31) - 16;
				part.ySpeed = float(jjRandom() & 31) - 16;
			}
		}
	} else {
		if (enemy.justHit > 0) {
			--enemy.justHit;
			enemy.lightType = LIGHT::BRIGHT;
			if (enemy.special <= 0) { //dead
				paused = true;
				enemy.state = STATE::EXPLODE;
				enemy.bulletHandling = HANDLING::IGNOREBULLET; //for what it's worth
				enemy.playerHandling = HANDLING::PARTICLE;
				jjLocalPlayers[0].activateBoss(false);
				jjLocalPlayers[0].boss = 0;
				jjLocalPlayers[0].cameraFreeze(enemy.xPos, enemy.yPos, true, false);
			} else {
				jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::SHARD, enemy.xPos, enemy.yPos, enemy.objectID, CREATOR::OBJECT, BEHAVIOR::BIRDFEATHER)];
				spark.determineCurAnim(ANIM::AMMO, 7);
				spark.lightType = LIGHT::POINT;
			}
		}
		else {
			enemy.lightType = LIGHT::RING2;
			if (enemy.special < 4800) {
				enemy.special += jjDifficulty;
				enemy.energy = (enemy.special / 80);
			}
		}
		if ((jjGameTicks & 511) == 0) //new choice of target sometimes
			enemy.var[0] = (jjRandom()) & 7;
		else if (jjDifficulty > 1 && (jjRandom() & 511) == 0) {
			jjAddObject(OBJECT::BEEBOY, enemy.xPos, enemy.yPos - 32, enemy.objectID);
			jjSample(enemy.xPos, enemy.yPos, SOUND::BUMBEE_BEELOOP);
		}
		int targetID = enemy.var[0];
		float target = (targetID >= 6) ? jjLocalPlayers[0].xPos : GoddessTargetsX[targetID];
		if (enemy.xPos > target) {
			enemy.xSpeed = enemy.xSpeed - GODDESSSPEED;
			if (enemy.xSpeed < -GODDESSMAXSPEED) enemy.xSpeed = -GODDESSMAXSPEED;
		} else {
			enemy.xSpeed = enemy.xSpeed + GODDESSSPEED;
			if (enemy.xSpeed > GODDESSMAXSPEED) enemy.xSpeed = GODDESSMAXSPEED;
		}
		enemy.direction = enemy.xSpeed;
		target = (targetID >= 6) ? jjLocalPlayers[0].yPos : GoddessTargetsY[targetID];
		if (enemy.yPos > target) {
			enemy.ySpeed = enemy.ySpeed - GODDESSSPEED;
			if (enemy.ySpeed < -GODDESSMAXSPEED) enemy.ySpeed = -GODDESSMAXSPEED;
		} else {
			enemy.ySpeed = enemy.ySpeed + GODDESSSPEED;
			if (enemy.ySpeed > GODDESSMAXSPEED) enemy.ySpeed = GODDESSMAXSPEED;
		}
		enemy.xPos = enemy.xPos + enemy.xSpeed;
		enemy.yPos = enemy.yPos + enemy.ySpeed;
	}
	for (int x = 0; x < 2; ++x)
		for (int y = 0; y < 4; ++y)
			jjDrawTile(enemy.xPos - 29 + x*32, enemy.yPos - 41 + y*32, 228 + x + y*10, TILE::ALLQUADRANTS, 5);
	//enemy.draw();
}


void BollySpikeBall(jjOBJ@ obj) {
	obj.energy = 100;
	if (obj.doesHurt == 2) {
		obj.behave(BEHAVIOR::FLICKERGEM, false);
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, (obj.playerHandling == HANDLING::PARTICLE) ? SPRITE::TRANSLUCENT : SPRITE::NORMAL);
	} else {
		if (obj.state == STATE::START) {
			obj.killAnim = obj.determineCurAnim(ANIM::SONCSHIP, 4, false);
			obj.determineCurAnim(ANIM::SONCSHIP, 5);
			obj.determineCurFrame();
			obj.state = STATE::ROTATE;
			jjSample(obj.xPos, obj.yPos, SOUND::COMMON_METALHIT);
		} else if (obj.state == STATE::DEACTIVATE) {
			obj.delete();
		} else if (obj.state == STATE::KILL) {
			obj.behavior = BEHAVIOR::EXPLOSION2;
			return;
		}
		obj.counter += 4;
		jjOBJ@ bolly = jjObjects[obj.creatorID];
		if (obj.doesHurt == 1) {
			++obj.special;
			obj.xPos = obj.xOrg + jjSin(obj.counter) * obj.special;
			obj.yPos = obj.yOrg + jjCos(obj.counter) * obj.special;
		} else {
			obj.xPos = bolly.xPos - 11*bolly.direction + jjSin(obj.counter) * obj.special;
			obj.yPos = bolly.yPos + 11 + jjCos(obj.counter) * obj.special;
		obj.justHit = bolly.justHit; //not sure how I feel about this effect
		}
		//jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame);
		obj.draw();
	}
}
void BollyBird(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		obj.determineCurAnim(ANIM::SPAZ2, 2);
		obj.state = STATE::FLY;
	} else if (obj.state == STATE::DEACTIVATE || obj.state == STATE::KILL) {
		obj.delete();
	} else {
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		obj.frameID = (++obj.counter >> 2) & 7;
		obj.determineCurFrame();
		obj.draw();
		if ((jjRandom() & 31) == 0) {
			jjOBJ@ feather = jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, obj.creatorID)];
			feather.determineCurAnim(ANIM::BIRD, 6);
			feather.behavior = BEHAVIOR::BIRDFEATHER;
			feather.playerHandling = HANDLING::ENEMY;
			feather.energy = 1;
			feather.lightType = LIGHT::POINT2;
			feather.light = 1;
			jjSample(feather.xPos, feather.yPos, SOUND::COMMON_BIRDFLY);
		}
	}
}
void BollyTargetMissile(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		obj.ySpeed = 6.6;
		obj.xSpeed = jjObjects[obj.creatorID].direction;
		obj.var[0] = 768;
		obj.playerHandling = HANDLING::ENEMYBULLET;
		obj.animSpeed = 2;
		obj.determineCurAnim(ANIM::BILSBOSS, 3);
		obj.killAnim = obj.determineCurAnim(ANIM::AMMO, 7, false);
		obj.lightType = LIGHT::BRIGHT;
		obj.light = 21;
		obj.deactivates = false;
		jjSample(obj.xPos, obj.yPos, SOUND::BILSBOSS_FIRE);
	} else if (obj.doesHurt == 0) {
		if (obj.yPos < -200) {
			obj.doesHurt = 1;
			obj.xPos = jjLocalPlayers[0].xPos;
			jjSamplePriority(SOUND::SONCSHIP_TARGETLOCK);
			obj.xSpeed = 0;
			obj.var[0] = 256;
			obj.ySpeed = 0;
		} else
			obj.ySpeed = obj.ySpeed - 0.15;
	} else {
		jjOBJ@ bolly = jjObjects[obj.creatorID];
		if (abs(bolly.yPos - obj.yPos) < 10 && abs(bolly.xPos - obj.xPos) < 30) {
			obj.behavior = BEHAVIOR::EXPLOSION2;
			bolly.energy = 50; //100-50
			jjSamplePriority(SOUND::AMMO_BOEM1);
		} else {
			if (obj.ySpeed < 8.8) obj.ySpeed = obj.ySpeed + 0.15;
			jjDrawSprite(obj.xPos, 10*32, ANIM::SONCSHIP, 7, 0, 0);
		}
	}
	if (obj.playerHandling != HANDLING::EXPLOSION) { //ded
		obj.frameID = (jjGameTicks >> 2) % 3;
		obj.determineCurFrame();
	}
	obj.behave(BEHAVIOR::BOLLYBULLET, true);
}
void BollyBouncingLaser(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		obj.determineCurAnim(ANIM::AMMO, 2);
		obj.determineCurFrame();
		obj.lightType = LIGHT::POINT2;
		obj.playerHandling = (obj.scriptedCollisions) ? HANDLING::SPECIAL : HANDLING::ENEMYBULLET;
		obj.animSpeed = 2; //let's be dangerous
		obj.state = STATE::FLY;
	} else if (obj.state == STATE::FLY) { //same stuff as BallBullet, really
		if (jjMaskedPixel(obj.xPos + obj.xSpeed, obj.yPos)) {
			obj.xSpeed = -obj.xSpeed;
			++obj.counterEnd;
		} else if (obj.yPos < 0 || jjMaskedPixel(obj.xPos, obj.yPos + obj.ySpeed)) {
			obj.ySpeed = -obj.ySpeed;
			++obj.counterEnd;
		}
		if (obj.counterEnd == 4) {
			obj.delete();
			return;
		}
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		if (obj.counterEnd > 4) obj.draw();
		if (obj.special > 0) { //do this after moving
			jjOBJ@ laser = jjObjects[jjAddObject(OBJECT::BULLET, obj.xOrg, obj.yOrg, obj.creatorID)];
			laser.xSpeed = obj.xSpeed;
			laser.ySpeed = obj.ySpeed;
			laser.behavior = BollyBouncingLaser;
			laser.special = obj.special - 1;
			laser.counterEnd = obj.counterEnd;
			laser.scriptedCollisions = obj.scriptedCollisions;
			obj.special = 0;
		}
	} else
		obj.delete();
}
void BollyMovingRockPlatform(jjOBJ@ obj) {
	if (obj.state == STATE::DEACTIVATE) obj.delete();
	obj.xPos = obj.xPos + obj.xSpeed;
	if (obj.beSolid() != 0) {
		jjPARTICLE@ part = jjAddParticle(PARTICLE::ICETRAIL);
		if (part !is null) {
			part.xPos = obj.xPos;
			part.yPos = obj.yPos;
		}
	}
	obj.draw();
}


void DestroyAllBollyProjectiles(uint16 bollyID) {
	for (int i = 1; i < jjObjectCount; ++i) {
		jjOBJ@ obj = jjObjects[i];
		if (obj.isActive){
			if (obj.behavior == BollySpikeBall) {
				obj.playerHandling = HANDLING::PARTICLE;
				obj.ySpeed = 1;
				obj.doesHurt = 2;
				obj.counter = 35;
				obj.state = STATE::FLOAT;
				obj.lightType = LIGHT::NONE;
			} else if (obj.creatorID == bollyID && obj.eventID != OBJECT::BOLLY) {
				if (obj.eventID == OBJECT::BIGROCK) obj.clearPlatform();
				obj.behavior = BEHAVIOR::EXPLOSION2; //that might work
			}
		}
	}
	if (jjDifficulty <= 0) //this is only ever called between Bolly phases, so good opportunity to heal players up
		for (int i = 0; i < jjLocalPlayerCount; ++i)
			jjLocalPlayers[i].health = jjMaxHealth;
}
void BollyTop(jjOBJ@ obj) {
	//just sit there and be controlled by DrawAndManageBolly and the Bolly# behaviors. does have some code of its own, but all in onObjectHit.
}
void DrawAndManageBolly(jjOBJ@ boss) {
	jjOBJ@ top = jjObjects[boss.special];
	boss.justHit = top.justHit;
	if (top.energy < 50) { //been hurt
		boss.age -= (50 - top.energy);
		top.energy = 50;
	}
	boss.energy = (boss.age + 9) / 10;
	if (boss.age <= 0 && boss.state != STATE::DONE && boss.state != STATE::START) {
		boss.counter = 0;
		boss.state = STATE::DONE;
		boss.playerHandling = HANDLING::EXPLOSION;
		top.playerHandling = HANDLING::EXPLOSION;
	} else if (boss.behavior != Bolly1 && (jjGameTicks & 7) == 0) {	//start sparking after taking a little damage,
		jjPARTICLE@ spark = jjAddParticle(PARTICLE::SPARK);	//to provide an excuse for that big hole in the sprite
		if (spark !is null) {
			spark.xPos = boss.xPos - 11 * boss.direction;
			spark.yPos = boss.yPos + 11;
			uint rand = jjRandom();
			spark.xSpeed = (rand & 0xFFFF) / 32768.f - 1;
			spark.ySpeed = (rand >> 16) / 32768.f - 1;
		}
	}
	boss.frameID = (jjGameTicks >> 1) & 7;
	boss.determineCurFrame();
	SPRITE::Mode mode = SPRITE::NORMAL;
	if (boss.justHit > 0) mode = SPRITE::SINGLECOLOR;
	else if (boss.playerHandling == HANDLING::EXPLOSION) mode = SPRITE::TRANSLUCENT;
	jjDrawSpriteFromCurFrame(boss.xPos, boss.yPos, boss.curFrame, boss.direction, mode, 15, 5);
	//jjDrawString(boss.xPos - 10, boss.yPos - 50, "" + boss.age + ", " + boss.energy);
	top.xPos = boss.xPos;
	top.yPos = boss.yPos;
	top.direction = boss.direction;
	top.frameID = (jjGameTicks >> 3) % 6;
	top.determineCurFrame();
	jjDrawSpriteFromCurFrame(top.xPos, top.yPos, top.curFrame, top.direction, mode, 15, 5);
	if ((jjGameTicks & 63) == 0)
		jjObjects[jjAddObject(OBJECT::EXPLOSION, boss.xPos - 50 * boss.direction, boss.yPos + 12, boss.objectID)].determineCurAnim(ANIM::SONCSHIP, 4);
}
void Bolly1(jjOBJ@ boss) {
	switch (boss.state) {
		case STATE::START:
			if (boss.special == 0) {
				boss.special = jjAddObject(OBJECT::BOLLY, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollyTop);
				jjOBJ@ top = jjObjects[boss.special];
				top.determineCurAnim(ANIM::SONCSHIP, 3);
				top.playerHandling = HANDLING::SPECIAL;
				top.bulletHandling = HANDLING::DETECTBULLET;
				top.scriptedCollisions = true;
			}
			if (boss.yPos < 5*32)
				boss.yPos = boss.yPos + 1;
			else {
				jjLocalPlayers[0].activateBoss();
				boss.state = STATE::DELAYEDSTART;
				boss.yOrg = boss.yPos;
				boss.xOrg = boss.xPos;
				boss.counterEnd = 0;
				jjMusicLoad("sonicdrm.s3m") || jjMusicLoad("boss.j2b");
			}
			boss.direction = (boss.xPos < jjLocalPlayers[0].xPos) ? 1 : -1;
			break;
		case STATE::DELAYEDSTART:
			boss.yPos = jjSin(jjGameTicks << 2) * 5 + boss.yOrg;
			if ((++boss.counterEnd & 15) == 7) {
				jjOBJ@ spikeball = jjObjects[jjAddObject(OBJECT::SHARD, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollySpikeBall)];
				spikeball.playerHandling = HANDLING::ENEMY;
				spikeball.bulletHandling = HANDLING::DESTROYBULLET;
				spikeball.special = boss.counterEnd - 6;
				spikeball.counter = 512;
				spikeball.lightType = LIGHT::POINT2;
			} else if (boss.counterEnd == 200)
				boss.state = STATE::FLOAT;
			break;
		case STATE::FLOAT:
			boss.yPos = jjSin(jjGameTicks << 3) * 5 + boss.yOrg;
			if (boss.direction == -1) {
				boss.xPos = boss.xPos - 1.3;
				if (boss.xPos < 159*32) boss.direction = 1;
			} else {
				boss.xPos = boss.xPos + 1.3;
				if (boss.xPos > 171*32) boss.direction = -1;
			}
			break;
		case STATE::DONE:
			if (++boss.counter == 1) {
				DestroyAllBollyProjectiles(boss.objectID);
			} else {
				boss.xPos = boss.xPos + (jjRandom() & 7) - 3.5;
				boss.yPos = boss.yPos + (jjRandom() & 7) - 3.5;
				if ((boss.counter & 3) == 0) {
					jjOBJ@ expl = jjObjects[jjAddObject(OBJECT::EXPLOSION, (157 + (jjRandom() & 15)) * 32, (jjRandom() & 7) * 32, boss.objectID)];
					expl.determineCurAnim(ANIM::AMMO, 3);
					expl.lightType = LIGHT::BRIGHT;
					expl.light = 9;
					jjSample(expl.xPos, expl.yPos, SOUND::AMMO_BOEM1);
				}
				if (boss.counter == 350) {
					jjLayerHasTiles[5] = false;
					jjLayerHasTiles[6] = true;
					jjLayerXSpeed[7] = 0;
					jjLayerYSpeed[7] = 0;
					jjLayerYOffset[7] = 7*32;
					boss.age = 1;
					boss.state = STATE::START;
					boss.behavior = Bolly2;
					jjObjectPresets[OBJECT::BOLLY].energy = BOLLYHEALTH2 / 10;
					for (uint16 x = 156; x < 173; ++x)
						for (uint16 y = 0; y < 10; ++y)
							jjAddParticleTileExplosion(x, y, jjTileGet(5, x, y), false);
					jjSamplePriority(SOUND::COMMON_DAMPED1);
					jjTileSet(4, 156, 0, 420 | TILE::VFLIPPED);
					jjTileSet(4, 157, 0, 421 | TILE::VFLIPPED);
					jjTileSet(4, 158, 0, 420 | TILE::VFLIPPED);
					jjTileSet(4, 159, 0, 421 | TILE::VFLIPPED);
					for (int i = 0; i < jjLocalPlayerCount; ++i)
						if (jjLocalPlayers[i].yPos < 36) jjLocalPlayers[i].yPos = 36;
				}
			}
			break;
	}
	DrawAndManageBolly(boss);
}
array<uint16> ColumnsToBlowAway = {154, 155, 174, 175};
void Bolly2(jjOBJ@ boss) {
	switch (boss.state) {
		case STATE::START: {
			bool xMov = true, yMov = true;
			if (boss.xPos < boss.xOrg - 1) { boss.xPos = boss.xPos + 1; boss.direction = 1; }
			else if (boss.xPos > boss.xOrg + 1) { boss.xPos = boss.xPos - 1; boss.direction = -1; }
			else xMov = false;
			if (boss.yPos < boss.yOrg - 1) boss.yPos = boss.yPos + 1;
			else if (boss.yPos > boss.yOrg + 1) boss.yPos = boss.yPos - 1;
			else yMov = false;
			if (boss.age < BOLLYHEALTH2) {
				boss.age += 2;
				jjSample(boss.xPos, boss.yPos, SOUND::COMMON_MONITOR, boss.age & 63);
			}
			else if (!xMov && !yMov) {
				boss.state = STATE::FLOAT;
				boss.counter = 0;
				boss.playerHandling = HANDLING::ENEMY;
				jjObjects[boss.special].playerHandling = HANDLING::SPECIAL;
				jjSamplePriority(SOUND::SONCSHIP_MISSILE2);
			}
			break; }
		case STATE::FLOAT:
			boss.yPos = jjSin(jjGameTicks << 3) * 5 + boss.yOrg;
			if (boss.direction == -1) {
				boss.xPos = boss.xPos - 1.3;
				if (boss.xPos < 159*32) boss.direction = 1;
			} else {
				boss.xPos = boss.xPos + 1.3;
				if (boss.xPos > 171*32) boss.direction = -1;
			}
			if ((++boss.counter & 255) == 30) {
				uint rand = jjRandom();
				for (int i = 0; i < 1024; i += 256) if ((rand & 7) != 0) {
					jjOBJ@ spikeball = jjObjects[jjAddObject(OBJECT::SHARD, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollySpikeBall)];
					spikeball.playerHandling = HANDLING::ENEMY;
					spikeball.bulletHandling = HANDLING::DESTROYBULLET;
					spikeball.doesHurt = 1;
					spikeball.counter = i;
					spikeball.lightType = LIGHT::POINT2;
					rand >>= 3;
				}
			} else if (jjDifficulty >= 2 && (jjRandom() & 127) == 13) {
				jjOBJ@ spikeball = jjObjects[jjAddObject(OBJECT::SHARD, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollySpikeBall)];
				spikeball.playerHandling = HANDLING::ENEMY;
				spikeball.bulletHandling = HANDLING::DESTROYBULLET;
				spikeball.ySpeed = 1;
				spikeball.doesHurt = 2;
				spikeball.counter = 80;
				spikeball.state = STATE::FLOAT;
				spikeball.lightType = LIGHT::POINT2;
			}
			break;
		case STATE::DONE:
			if (++boss.counter == 1) {
				DestroyAllBollyProjectiles(boss.objectID);
			} else {
				boss.xPos = boss.xPos + 1 * boss.direction;
				boss.yPos = boss.yPos + 1.5;
				if ((boss.counter & 3) == 0) {
					jjOBJ@ expl = jjObjects[jjAddObject(OBJECT::EXPLOSION, boss.xPos + (jjRandom() & 31) - 16, boss.yPos + (jjRandom() & 31) - 16, boss.objectID)];
					expl.determineCurAnim(ANIM::AMMO, 3);
					expl.lightType = LIGHT::BRIGHT;
					expl.light = 9;
					jjSample(expl.xPos, expl.yPos, SOUND::AMMO_BOEM1);
				}
				if (boss.counter == 400) {
					jjLayerHasTiles[4] = jjLayerHasTiles[3] = false;
					jjTriggers[6] = scrolly = true;
					jjMusicLoad("GYRATION.MOD") || jjMusicLoad("neurocycle.mod") || jjMusicLoad("challenger.s3m") || jjMusicLoad("fastrack.j2b");
					jjTileSet(4, 156, 0, 0); //get rid of the spikes
					jjTileSet(4, 157, 0, 0);
					jjTileSet(4, 158, 0, 0);
					jjTileSet(4, 159, 0, 0);
					boss.age = 1;
					boss.counter = 0;
					boss.state = STATE::START;
					boss.behavior = Bolly3;
					boss.light = 1;
					boss.xOrg = boss.xAcc = 140*32;
					boss.yOrg = boss.yAcc = 7*32;
					jjObjectPresets[OBJECT::BOLLY].energy = BOLLYHEALTH3 / 10;
					jjSamplePriority(SOUND::COMMON_DAMPED1);
					for (uint16 y = 0; y < 10; ++y) {
						for (uint16 x = 0; x < 4; ++x) {
							uint16 tile = jjTileGet(3, ColumnsToBlowAway[x], y);
							for (uint8 quadrant = 0; quadrant < 4; ++quadrant) {
								jjPARTICLE@ particle = jjAddParticle(PARTICLE::TILE);
								if (particle !is null) {
									particle.xPos = (ColumnsToBlowAway[x]*32) + (quadrant & 1) * 16;
									particle.yPos = y*32 + (quadrant & 2) * 16;
									particle.tile.tileID = tile;
									particle.tile.quadrant = quadrant;
									particle.ySpeed = (jjRandom() & 7) - 3.5;
									particle.xSpeed = jjRandom() & 7;
								}
							}
						}
					}
				}
			}
			break;
	}
	DrawAndManageBolly(boss);
}
array<STATE::State> PossibleScrollyStates = {STATE::FADEIN, STATE::FADEOUT, STATE::BOUNCE, STATE::ATTACK, STATE::FLOATFALL, STATE::ROTATE};
float BollyAngle = -1;
void Bolly3(jjOBJ@ boss) {
	//pos: where sprite is drawn to and collisions happen with
	//org: current logical position, which pos only approximates
	//acc: current target, which org tries to reach
	//speed: how acc tries to reach org
	if (boss.state != STATE::DONE) {
		if (boss.state != STATE::START) {
			if (++boss.counter == 300) {
				boss.counter = 0;
				boss.xSpeed = 3;
				boss.ySpeed = 2;
				boss.xAcc = 157*32;
				boss.lightType = LIGHT::NONE;
				STATE::State nuState = PossibleScrollyStates[jjRandom() % PossibleScrollyStates.length()];
				switch (nuState) {
					case STATE::FADEOUT:
						if (boss.state == STATE::FADEOUT) boss.counter = 299; //try again
						else {
							boss.xAcc = 178*32;
							boss.xSpeed = 6;
							boss.ySpeed = 1;
						}
						break;
					case STATE::FADEIN:
						break;
					case STATE::BOUNCE:
						boss.yAcc = 140;
						BollyAngle = -1;
						break;
					case STATE::ATTACK:
						boss.lightType = LIGHT::LASER;
						break;
					case STATE::FLOATFALL:
						boss.yAcc = 40;
						boss.xSpeed = 3.5;
						break;
					case STATE::ROTATE:
						boss.xAcc = 165*32;
						boss.yAcc = 320;
						break;
				}
				boss.state = nuState;
			}
			if (abs(boss.xAcc - boss.xOrg) > 4) {
				if (boss.xAcc < boss.xOrg) boss.xOrg = boss.xOrg - boss.xSpeed;
				else boss.xOrg = boss.xOrg + boss.xSpeed;
			}
			if (abs(boss.yAcc - boss.yOrg) > 4) {
				if (boss.yAcc < boss.yOrg) boss.yOrg = boss.yOrg - boss.ySpeed;
				else boss.yOrg = boss.yOrg + boss.ySpeed;
			}
			boss.yPos = jjSin(jjGameTicks << 3) * 5 + boss.yOrg;
			boss.xPos = jjCos(jjGameTicks << 4) * 8 + boss.xOrg;
		}
		switch (boss.state) {
			case STATE::START:
				if (boss.age < BOLLYHEALTH3) {
					boss.age += 2;
					jjSample(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::COMMON_MONITOR, boss.age & 63);
				}else {
					boss.state = STATE::FADEOUT;
					boss.direction = 1;
					boss.counter = 0;
					boss.playerHandling = HANDLING::ENEMY;
					boss.xAcc = 178*32;
					boss.yAcc = jjLocalPlayers[0].yPos;
					boss.ySpeed = 2;
					boss.xSpeed = 6;
					jjObjects[boss.special].playerHandling = HANDLING::SPECIAL;
					jjSamplePriority(SOUND::SONCSHIP_MISSILE2);
				}
				break;
			case STATE::FADEOUT:
				if (boss.xOrg < boss.xAcc - 10) {
					boss.yAcc = jjLocalPlayers[0].yPos;
					if ((jjRandom() & 31) == 13) {
						jjOBJ@ spikeball = jjObjects[jjAddObject(OBJECT::SHARD, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollySpikeBall)];
						spikeball.playerHandling = HANDLING::ENEMY;
						spikeball.bulletHandling = HANDLING::DESTROYBULLET;
						spikeball.ySpeed = 1;
						spikeball.xSpeed = SCROLLSPEED;
						spikeball.doesHurt = 2;
						spikeball.counter = 20;
						spikeball.state = STATE::FLOAT;
						spikeball.lightType = LIGHT::POINT2;
					}
				}
				break;
			case STATE::BOUNCE:
				if (boss.yOrg < 150 && boss.xOrg < 158*32) {
					if (BollyAngle == -1) {
						BollyAngle = atan2(boss.xOrg - jjLocalPlayers[0].xPos, boss.yOrg - jjLocalPlayers[0].yPos);
					} else {
						if ((boss.counter & 15) == 0) {
							jjOBJ@ ball = jjObjects[boss.fireBullet(OBJECT::BOUNCERBULLET)];
							float angle = BollyAngle + (jjRandom() & 255) / 512.;
							ball.xSpeed = sin(angle) * -7;
							ball.ySpeed = cos(angle) * -7;
							ball.counterEnd = 4;
							ball.playerHandling = HANDLING::ENEMYBULLET;
							ball.animSpeed = 2;
							ball.var[0] = 8;
							jjSample(ball.xPos, ball.yPos, SOUND::COMMON_EXPSM1);
						}
					}
				}
				break;
			case STATE::ATTACK:
				if (boss.counter < 200) boss.yAcc = jjLocalPlayers[0].yPos;
				else {
					jjOBJ@ ball = jjObjects[boss.fireBullet(OBJECT::BLASTERBULLET)];
					jjSample(boss.xPos, boss.yPos, SOUND::AMMO_LASER);
					ball.xSpeed = 24;
					ball.direction = 1;
					ball.state = STATE::FLY;
					ball.playerHandling = HANDLING::ENEMYBULLET;
					ball.animSpeed = 1;
					ball.lightType = LIGHT::POINT2;
					ball.determineCurAnim(ANIM::AMMO, 9);
					ball.killAnim = 0;
				}
				break;
			case STATE::FLOATFALL:
				if ((boss.counter & 63) < 32)
					boss.xAcc = jjLocalPlayers[0].xPos;
				else {
					jjOBJ@ ball = jjObjects[boss.fireBullet(OBJECT::BLASTERBULLET)];
					jjSample(boss.xPos, boss.yPos, SOUND::AMMO_LASER);
					ball.ySpeed = 13;
					ball.xSpeed = SCROLLSPEED;
					ball.var[7] = ball.xAcc = 0;
					ball.direction = 1;
					ball.state = STATE::FLY;
					ball.playerHandling = HANDLING::ENEMYBULLET;
					ball.animSpeed = 1;
					ball.lightType = LIGHT::POINT2;
					ball.determineCurAnim(ANIM::AMMO, 9);
					ball.killAnim = 0;
				}
				break;
			case STATE::ROTATE:
				if ((boss.counter & 127) == 127) {
					for (int i = 0; i < 1024; i += 256) {
						jjOBJ@ spikeball = jjObjects[jjAddObject(OBJECT::SHARD, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollySpikeBall)];
						spikeball.playerHandling = HANDLING::ENEMY;
						spikeball.bulletHandling = HANDLING::DESTROYBULLET;
						spikeball.doesHurt = 1;
						spikeball.counter = i;
						spikeball.lightType = LIGHT::POINT2;
					}
				} 
				break;
		}
		if (boss.xPos < 155*32) {
			if (((jjGameTicks >> 4) & 1) == 0)
				jjDrawSprite(156.5 * 32, boss.yPos, ANIM::FONT, 2, 1, 0, SPRITE::PALSHIFT, 216, 2);
		} else if (boss.xPos > 175 * 32) {
			if (((jjGameTicks >> 4) & 1) == 0)
				jjDrawSprite(173.5 * 32, boss.yPos, ANIM::FONT, 2, 1, 0, SPRITE::PALSHIFT, 216, 2);
		}
	} else { //STATE::DONE
		jjOBJ@ top = jjObjects[boss.special];
		if (++boss.counter <= 2) {
			DestroyAllBollyProjectiles(boss.objectID);
			scrolly = false;
			top.justHit = 255;
			boss.particlePixelExplosion(0);
			top.particlePixelExplosion(0);
			boss.lightType = LIGHT::RING2;
			top.lightType = LIGHT::RING2;
			jjLocalPlayers[0].activateBoss(false); //no more health bar
			jjMusicStop();
		} else if (boss.counter == 200) {
			whiteWorld = true;
			boss.age = 1;
			boss.counter = 255;
			boss.state = STATE::START;
			boss.behavior = Bolly4;
			boss.xPos = 165*32;
			boss.yPos = 5*32;
			boss.lightType = LIGHT::NONE;
			top.lightType = LIGHT::NONE;
			jjObjectPresets[OBJECT::BOLLY].energy = BOLLYHEALTH4 / 10;
			jjPalette.fill(255,255,255, 10, 166);
			jjPalette.fill(255,255,255, 208, 37);
			//jjPalette.fill(0,0,0, 64, 8);
			jjPalette.gradient(232,176,88, 88,60,8);
			jjPalette.color[244].green = jjPalette.color[244].blue = 0; //red
			jjPalette.color[245].red = 0; jjPalette.color[245].green = 128; //blue
			jjPalette.apply();
			//jjTileType[7] = jjTileType[8] = jjTileType[17] = jjTileType[18] = jjTileType[424] = jjTileType[425] = 6; //frozen
			jjTexturedBGStyle = TEXTURE::TUNNEL;
			jjTexturedBGUsed = true;
			jjTexturedBGStars = false;
			jjTexturedBGTexture = TEXTURE::XARGON;
			jjSetFadeColors();
			jjLayerYAutoSpeed[8] = 3;
			jjLayerXAutoSpeed[8] = -1.2;
			jjTexturedBGFadePositionY = .35;
			jjLayerHasTiles[6] = jjLayerHasTiles[7] = false;
			jjLayerHasTiles[4] = true;
			jjSamplePriority(SOUND::BILSBOSS_THUNDER);
			return;
		}
		boss.light = (boss.counter & 31) << 2;
		top.light = ((boss.counter + 12) & 31) << 2;
		jjSamplePriority(SOUND::COMMON_RINGGUN);
		for (int i = 0; i < jjLocalPlayerCount; ++i)
			jjLocalPlayers[i].lighting = boss.counter;
	}
	DrawAndManageBolly(boss);
}
array<STATE::State> PossibleWhiteStates = {STATE::FLOATFALL, STATE::ROTATE, STATE::FLY, STATE::ROCKETFLY, STATE::FIRE, STATE::JUMP};
array<uint8> WhiteStateReds =	{255, 128, 0,   250, 0,   164};
array<uint8> WhiteStateGreens =	{0,   0,   255, 153, 210, 220};
array<uint8> WhiteStateBlues =	{0,   128, 64,  39,  250, 32};
void Bolly4(jjOBJ@ boss) {
	if (boss.state == STATE::START) {
		if (--boss.counter > 99) {
			for (int i = 0; i < jjLocalPlayerCount; ++i)
				jjLocalPlayers[i].lighting = boss.counter;
		} else {
			boss.counter = 0;
			boss.doesHurt = 0;
			boss.age = 1;
			boss.energy = 95;
			boss.xOrg = boss.xAcc = boss.xPos;
			boss.yOrg = boss.yAcc = boss.yPos;
			boss.playerHandling = HANDLING::ENEMY;
			boss.bulletHandling = HANDLING::HURTBYBULLET;
			jjObjects[boss.special].playerHandling = HANDLING::PARTICLE;
			jjObjects[boss.special].bulletHandling = HANDLING::IGNOREBULLET;
			boss.state = STATE::DELAYEDSTART;
			jjEcho = 20; //I don't know how echo works :(
			jjMusicLoad("onewingedangel.j2b");
		}
		DrawAndManageBolly(boss);
	} else {
		jjOBJ@ top = jjObjects[boss.special];
		if (boss.energy < 100) {
			boss.justHit = (100 - boss.energy);
			if ((boss.age -= boss.justHit) < 0) {
				DestroyAllBollyProjectiles(boss.objectID);
				if (++boss.doesHurt > uint8(4 + jjDifficulty)) { //start at 1, so ~four encounters
					boss.state = STATE::DONE;
					showScore = true;
					boss.playerHandling = HANDLING::EXPLOSION;
					boss.bulletHandling = HANDLING::IGNOREBULLET;
					boss.counter = 0;
					jjMusicStop();
				} else {
					boss.age = BOLLYHEALTH4;
					boss.counter = 0;
					jjSamplePriority(SOUND::SONCSHIP_MISSILE2);
					for (int i = 0; i < jjLocalPlayerCount; ++i) {
						jjLocalPlayers[i].invincibility = 150; //let's be nice
						if (jjLocalPlayers[i].health < jjMaxHealth) jjLocalPlayers[i].health = jjLocalPlayers[i].health + 1;
						switch (jjLocalPlayers[i].charCurr) {
							case CHAR::JAZZ:
								jjSamplePriority(SOUND::JAZZSOUNDS_JUMMY);
								break;
							case CHAR::SPAZ:
								jjSamplePriority(SOUND::SPAZSOUNDS_HAPPY);
								break;
							default:
								jjSamplePriority(SOUND::COMMON_EAT1);
								break;
						}
					}
					boss.xSpeed = 3;
					boss.ySpeed = 2;
					uint nuStateIndex;
					do {
						nuStateIndex = jjRandom() % PossibleWhiteStates.length();
					} while (PossibleWhiteStates[nuStateIndex] == boss.state || (PossibleWhiteStates[nuStateIndex] == STATE::FLOATFALL && boss.doesHurt == uint(4 + jjDifficulty))); //must be something DIFFERENT
					boss.state = PossibleWhiteStates[nuStateIndex];
					switch (boss.state) {
						case STATE::FLOATFALL: //drop steam puppies
							boss.yAcc = -200;
							boss.ySpeed = 2;
							break;
						case STATE::ROTATE: //four spike balls on chains
						case STATE::FIRE: //follow you around firing lasers
							boss.xSpeed = 1.5;
							boss.ySpeed = 1.3;
							break;
						case STATE::FLY: //bird swarms
							boss.xAcc = boss.xOrg;
							boss.yAcc = 4*32;
							boss.xSpeed = 2.5;
							break;
						case STATE::ROCKETFLY: //targetted missiles
							boss.yAcc = 100;
							boss.xAcc = boss.xOrg;
							boss.xSpeed = 2.5;
							break;
					}
					jjPalette.gradient(
						WhiteStateReds[nuStateIndex],
						WhiteStateGreens[nuStateIndex],
						WhiteStateBlues[nuStateIndex], 
						0,0,0
					);
					jjPalette.apply();
					jjSetFadeColors();
					jjSample(boss.xPos, boss.yPos, SOUND::COMMON_BELL_FIRE);
				}
			}
			boss.energy = 100;
		}
		
		bool xMov = false, yMov = false;
		if (abs(boss.xAcc - boss.xOrg) > 4) {
			xMov = true;
			if (boss.xAcc < boss.xOrg) { boss.xOrg = boss.xOrg - boss.xSpeed; boss.direction = -1; }
			else { boss.xOrg = boss.xOrg + boss.xSpeed; boss.direction = 1; }
		}
		if (abs(boss.yAcc - boss.yOrg) > 4) {
			yMov = true;
			if (boss.yAcc < boss.yOrg) boss.yOrg = boss.yOrg - boss.ySpeed;
			else boss.yOrg = boss.yOrg + boss.ySpeed;
		}
		boss.yPos = jjSin(jjGameTicks << 3) * 5 + boss.yOrg;
		boss.xPos = jjCos(jjGameTicks << 3) * 5 + boss.xOrg;
		
		++boss.counter;
		switch (boss.state) {
			case STATE::FLOATFALL:
				if (boss.yPos > -80) boss.counter = 0;
				else {
					if (boss.counter < 500) {
						uint rand = jjRandom();
						if ((rand & 15) == 0) {
							jjOBJ@ obj = jjObjects[jjAddObject(OBJECT::STEAM, (155*32) + ((rand >> 5) % 640), -3, boss.objectID)];
							obj.points = 0;
							obj.ySpeed = (rand & 31) / 16.;
						}
					}
					if ((boss.counter > 100) && (boss.counter & 15) == 0) { //don't need to check _constantly_
						bool remainingMushrooms = false;
						for (int i = 0; i < jjObjectCount; ++i) {
							jjOBJ@ obj = jjObjects[i];
							if (obj.isActive && obj.eventID == OBJECT::STEAM)
								remainingMushrooms = true;
						}
						if (!remainingMushrooms) {
							boss.yAcc = 30;
							boss.energy = 95;
							boss.age = 1;
						} else boss.age = BOLLYHEALTH4;
					}
				}
				break;
			case STATE::ROTATE:
				if (boss.counter < 160) {
					if ((boss.counter & 7) == 7) for (int i = 0; i < 1024; i += 256) {
						jjOBJ@ spikeball = jjObjects[jjAddObject(OBJECT::SHARD, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollySpikeBall)];
						spikeball.playerHandling = HANDLING::PARTICLE;
						spikeball.bulletHandling = HANDLING::IGNOREBULLET;
						spikeball.special = boss.counter - 6;
						spikeball.counter = i;
						spikeball.determineCurAnim(ANIM::SONCSHIP, 1); //chain
						spikeball.determineCurFrame();
						spikeball.lightType = LIGHT::POINT;
					}
				} else if (boss.counter == 160) {
					for (int i = 0; i < 1024; i += 256) {
						jjOBJ@ spikeball = jjObjects[jjAddObject(OBJECT::SHARD, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollySpikeBall)];
						spikeball.playerHandling = HANDLING::ENEMY;
						spikeball.bulletHandling = HANDLING::DESTROYBULLET;
						spikeball.special = boss.counter - 6;
						spikeball.counter = i;
						spikeball.determineCurAnim(ANIM::AMMO, 77); //spike ball-like shape
						spikeball.frameID = 7;
						spikeball.determineCurFrame();
						spikeball.lightType = LIGHT::RING2;
						spikeball.light = 7;
					}
				} else {
					if (!xMov && !yMov) {
						boss.xAcc = jjLocalPlayers[0].xPos;
						boss.yAcc = jjLocalPlayers[0].yPos;
					}
				}
				break;
			case STATE::FLY:
				if (xMov) {
					boss.counter = 0;
				} else {
					if (boss.counter == 210) {
						if (boss.xOrg > 165*32)
							boss.xAcc = 157*32;
						else
							boss.xAcc = 172*32;
					} else if (boss.counter > 40) {
						uint rand = jjRandom();
						if ((rand & 7) == 0) {
							rand >>= 3;
							jjOBJ@ bird = jjObjects[jjAddObject(OBJECT::BULLET, jjLocalPlayers[0].xPos - 640*boss.direction + (float(rand & 255) - 127.5), (float((rand >> 8) & 255) - 127.5), boss.objectID, CREATOR::OBJECT, BollyBird)];
							bird.ySpeed = 4.6;
							bird.xSpeed = 7.2 * boss.direction;
							bird.direction = -boss.direction; //bird sprite faces left
							bird.playerHandling = HANDLING::ENEMY;
							bird.energy = 1;
							bird.lightType = LIGHT::POINT2;
							bird.light = 1;
							jjSample(bird.xPos, bird.yPos, SOUND::SPAZSOUNDS_BIRDSIT);
						}
					}
				}
				break;
			case STATE::ROCKETFLY:
				if (yMov) {
					boss.counter = 0;
				} else {
					if (boss.counter == 1) {
						for (int angle = 0; angle < 1024; angle += 128) {
							jjOBJ@ spikeball = jjObjects[jjAddObject(OBJECT::SHARD, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollySpikeBall)];
							spikeball.playerHandling = HANDLING::ENEMY;
							spikeball.bulletHandling = HANDLING::DESTROYBULLET;
							spikeball.counter = angle;
							spikeball.special = 60;
							spikeball.lightType = LIGHT::POINT2;
						}
					} else {
						if (!xMov)
							boss.xAcc = jjLocalPlayers[0].xPos;
						if ((boss.counter & 127) == 0)
							jjAddObject(OBJECT::BULLET, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollyTargetMissile);
						else if ((boss.counter & 127) == 64) {
							jjOBJ@ spikeball = jjObjects[jjAddObject(OBJECT::SHARD, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BollySpikeBall)];
							spikeball.playerHandling = HANDLING::ENEMY;
							spikeball.bulletHandling = HANDLING::DESTROYBULLET;
							spikeball.ySpeed = 1;
							spikeball.doesHurt = 2;
							spikeball.counter = 5;
							spikeball.state = STATE::FLOAT;
							spikeball.lightType = LIGHT::POINT2;
						}
					}
				}
				break;
			case STATE::FIRE:
				if (!xMov && !yMov) {
					boss.xAcc = jjLocalPlayers[0].xPos;
					boss.yAcc = jjLocalPlayers[0].yPos;
				}
				if ((boss.counter & 127) == 70) {
					jjSamplePriority(SOUND::AMMO_LASER);
					jjOBJ@ laser = jjObjects[jjAddObject(OBJECT::BULLET, boss.xPos, boss.yPos, boss.objectID)];
					uint rand = jjRandom();
					laser.xSpeed = boss.xSpeed * boss.direction * 4.5 + (int(rand & 0xFFFF) - 0x8000) / 21844.;
					rand >>= 16;
					if (boss.yAcc < boss.yOrg) laser.ySpeed = boss.ySpeed * -6 + (int(rand & 0xFFFF) - 0x8000) / 21844.;
					else laser.ySpeed = boss.ySpeed * 4.5 + (int(rand & 0xFFFF) - 0x8000) / 21844.;
					laser.behavior = BollyBouncingLaser;
					laser.special = 10;
				}
				break;
			case STATE::JUMP:
				if (!xMov && !yMov) {
					boss.xAcc = jjLocalPlayers[0].xPos;
					boss.yAcc = jjLocalPlayers[0].yPos;
				}
				if (boss.counter == 1) {
					jjSamplePriority(SOUND::AMMO_LASER);
					jjOBJ@ laser = jjObjects[jjAddObject(OBJECT::BULLET, 156*32, 320, boss.objectID)];
					laser.xSpeed = 9;
					laser.ySpeed = 0;
					laser.behavior = BollyBouncingLaser;
					laser.special = 30; //thatsa one longa laser
					laser.counterEnd = 5; //will pretty much never run out
					laser.scriptedCollisions = true;
					@laser = jjObjects[jjAddObject(OBJECT::BULLET, 165*32, 32, boss.objectID)];
					laser.xSpeed = 9;
					laser.ySpeed = 0;
					laser.behavior = BollyBouncingLaser;
					laser.special = 30;
					laser.counterEnd = 5;
					laser.scriptedCollisions = true;
				} else if ((boss.counter & 15) == 3) {
					jjOBJ@ rock = jjObjects[jjAddObject(OBJECT::BIGROCK, 153*32, (jjRandom() % 620) + 20, boss.objectID)];
					rock.xSpeed = 3;
					rock.behavior = BollyMovingRockPlatform;
					rock.scriptedCollisions = false;
					rock.bulletHandling = HANDLING::IGNOREBULLET;
				}
				break;
			case STATE::DONE: {
				uint rand = jjRandom();
				if ((rand & 1) == 0) {
					jjOBJ@ expl = jjObjects[jjAddObject(OBJECT::EXPLOSION, boss.xPos + ((rand >> 1) & 31) - 16, boss.yPos + ((rand >> 6) & 31) - 16, boss.objectID)];
					expl.determineCurAnim(ANIM::AMMO, 3);
					expl.lightType = LIGHT::BRIGHT;
					expl.light = 9;
					jjSamplePriority(SOUND::COMMON_GLASS2);
				} else {
					jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::SHARD, boss.xPos, boss.yPos, boss.objectID, CREATOR::OBJECT, BEHAVIOR::BIRDFEATHER)];
					spark.determineCurAnim(ANIM::AMMO, 7);
					spark.lightType = LIGHT::POINT;
				}
				if (boss.counter > 200 && boss.counter < 300)
					for (int i = 0; i < jjLocalPlayerCount; ++i)
						jjLocalPlayers[i].lighting = -100 + boss.counter;
				else if (boss.counter == 320) {
					jjTexturedBGUsed = false; //no fade colors getting in the way of perfect whiteness
					jjPalette.fill(255,255,255, 10, 235);
					jjPalette.gradient(0,0,0, 255,255,255, 64, 8);
					jjPalette.apply();
					for (int i = 0; i < jjLocalPlayerCount; ++i) {
						jjLocalPlayers[i].lighting = 100;
						jjLocalPlayers[i].showText("@@@@@CONGRATULATIONS", STRING::LARGE);
					}
				} else if (boss.counter == 500) {
					jjNxt("ending", true, true);
					boss.delete();
					jjObjects[boss.special].delete(); //thorough
				}
				break; }
		}
		
		top.xPos = boss.xPos;
		top.yPos = boss.yPos;
		top.direction = boss.direction;
		if (boss.justHit > 0) {
			jjDrawSpriteFromCurFrame(boss.xPos - boss.justHit, boss.yPos, boss.curFrame, boss.direction, SPRITE::SINGLECOLOR, 244);
			jjDrawSpriteFromCurFrame(boss.xPos + boss.justHit, boss.yPos, boss.curFrame, boss.direction, SPRITE::SINGLECOLOR, 245);
			jjDrawSpriteFromCurFrame(top.xPos - boss.justHit, top.yPos, top.curFrame, top.direction, SPRITE::SINGLECOLOR, 244);
			jjDrawSpriteFromCurFrame(top.xPos + boss.justHit, top.yPos, top.curFrame, top.direction, SPRITE::SINGLECOLOR, 245);
		}
		jjDrawSpriteFromCurFrame(boss.xPos, boss.yPos, boss.curFrame, boss.direction);
		jjDrawSpriteFromCurFrame(top.xPos, top.yPos, top.curFrame, top.direction);
	}
}

void MyEva(jjOBJ@ eva) {
	if (eva.state == STATE::DEACTIVATE) {
		if (statueDefeated && jjLocalPlayers[0].ammo[WEAPON::RF] > 0)
			eva.eventID = OBJECT::EXTRALIFE;
		eva.deactivate();
	} else {
		int playerID = eva.findNearestPlayer(4000);
		if (playerID > -1)
			eva.direction = (jjPlayers[playerID].xPos < eva.xPos) ? 1 : -1;
		eva.draw();
	}
}


void TempleLock(jjOBJ@ lock) {
	if (lock.state == STATE::START) {
		lock.state = STATE::WAIT;
		if (statueDefeated) { lock.lightType = LIGHT::LASER; lock.light = 1; }
	} else if (lock.state == STATE::DEACTIVATE) {
		lock.deactivate();
	}
}

string passwordAttempt = "";
array<bool> letterWasJustTyped(26);
string chr(uint8 value){ //Returns a one-character string corresponding to an ASCII value. //http://www.jazz2online.com/snippets/68/operations-on-ascii-values/
  string str="\0";
  str[0]=value;
  return str;
}
void onObjectHit(jjOBJ@ obj, jjOBJ@ bull, jjPLAYER@ play, int force) {
	if (obj.eventID == OBJECT::BIGROCK) {
		if (bull !is null) {
			obj.energy -= bull.animSpeed;
			obj.justHit = 5;
			if (obj.energy <= 0) { //only reachable through RFs
				obj.clearPlatform();
				obj.particlePixelExplosion(2);
				obj.delete();
				barrierDestroyed = true;
				play.ammo[WEAPON::RF] = 0;
				play.powerup[WEAPON::RF] = false;
				play.currWeapon = WEAPON::BLASTER; //guaranteed to exist, and finding another one sounds boring
				for (int i = (jjRandom() & 15); i > 0; --i)
					jjObjects[
						jjAddObject(
							OBJECT::EXPLOSION,
							obj.xPos + (jjRandom() & 63) - 32,
							obj.yPos + (jjRandom() & 63) - 32,
							obj.objectID
						)
					].determineCurAnim(ANIM::AMMO, 5);
			}
			bull.state = STATE::EXPLODE;
		} //else if (play !is null) play.hurt();
	} else if (obj.eventID == OBJECT::HELMUT) {
		if (bull !is null) {
			obj.special -= bull.animSpeed;
			obj.energy = obj.special / 4;
			obj.justHit = 5;
			bull.state = STATE::EXPLODE;
			if (obj.energy <= 0) {
				obj.state = STATE::EXPLODE;
				obj.counter = 0;
				obj.scriptedCollisions = false;
				obj.xOrg = obj.xPos; obj.yOrg = obj.yPos;
				for (int i = 0; i < jjLocalPlayerCount; ++i)
					jjLocalPlayers[i].activateBoss(false);
				paused = pausedForChatting = true;
				elapsedPauseTime = 1;
				currentPauseChat = chatSTATUE;
			}
		}
	} else if (obj.eventID == OBJECT::EVA) {
		if (bull is null && play.ammo[WEAPON::RF] == 0) {
			if ( (!barrierDestroyed) || statueDefeated ) {
				paused = pausedForChatting = true;
				elapsedPauseTime = 1;
				play.ammo[WEAPON::RF] = 3;
				if (statueDefeated) {
					play.powerup[WEAPON::RF] = false;
					currentPauseChat = chatEVA2;
				} else {
					play.powerup[WEAPON::RF] = true;
					currentPauseChat = chatEVA;
				}
			}
		}
	} else if (obj.eventID == OBJECT::FRIES) { //temple lock
		if (bull is null) { //only interested in contact with the player
			if (obj.state == STATE::WAIT && statueDefeated) {
				obj.state = STATE::IDLE;
				jjTileSet(4, 85, 39, 795);
				jjEventSet(85, 39, AREA::TEXT); //install a reminder sign in case the player doesn't have chatlogger open and didn't write down the password
			}
			//try to type
			for (uint8 keyCode = 0; keyCode < 26; ++keyCode) { //A-Z
				if (jjKey[keyCode + 0x41]) {
					if (!letterWasJustTyped[keyCode]) {
						letterWasJustTyped[keyCode] = true;
						if (passwordAttempt.length() < 10) {
							passwordAttempt += chr(keyCode + 0x41);
							if (statueDefeated && passwordAttempt == "OPENSESAME") {
								jjTriggers[2] = true;
								for (int i = 0; i < 4; ++i) {
									jjOBJ@ expl = jjObjects[
										jjAddObject(
											OBJECT::EXPLOSION,
											obj.xPos + 64,
											obj.yPos - (i * 32),
											obj.objectID
										)
									];
									expl.determineCurAnim(ANIM::AMMO, 2);
									expl.direction = 0;
								}
								obj.delete();
							}
						}
					}
				} else letterWasJustTyped[keyCode] = false;
				if (jjKey[0x8]) { //backspace
					passwordAttempt = "";
				}
			}
			jjDrawString(obj.xPos - 80, obj.yPos - 80, "|||Enter Password:");
			jjDrawString(obj.xPos - 90, obj.yPos - 50, passwordAttempt, STRING::MEDIUM, STRING::SPIN);
		}
	} else if (obj.eventID == OBJECT::DESTRUCTSCENERY) {
		if (bull !is null) {
			bull.state = STATE::EXPLODE;
			if (bull.var[3] == obj.var[6]) //override normal behavior of TNT destroying anything
				obj.state = STATE::KILL;
		}
	} else if (obj.eventID == OBJECT::SAVEPOST) { //override normal behavior of being triggerable by bullets, e.g. TNT
		if (bull is null && obj.state == STATE::SLEEP)
			obj.state = STATE::ACTION;
			obj.special = play.playerID + CREATOR::PLAYER;
	} else if (obj.eventID == OBJECT::TRIGGERCRATE) { //using jjTileSet instead of real trigger scenery is the easiest(?) way to make triggerable tiles have the One Way event on them, if you don't mind the change being permanent, which it happens is exactly what I want here anyway...
		if (bull !is null && bull.objectID == 0) { //TNT
			obj.delete();
			jjEventSet(obj.xOrg / 32, obj.yOrg / 32, 0); //don't want this to be triggerable again after death
			jjAddParticleTileExplosion(obj.xOrg / 32, obj.yOrg / 32, 749, false);
			jjTileSet(4, 173, 36, 418);
			jjTileSet(4, 174, 36, 419);
			jjTileSet(4, 173, 37, 428);
			jjTileSet(4, 174, 37, 429);
		}
	} else if (obj.eventID == OBJECT::EXTRALIFE) {
		if (bull is null) { //player
			obj.behavior = BEHAVIOR::EXPLOSION2;
			jjChat("/smhealth " + formatInt(jjMaxHealth + 1, "1"));
			play.health = jjMaxHealth;
			play.showText("@@#Health Limit@@ Increased!", STRING::LARGE);
			jjSample(obj.xPos, obj.yPos, SOUND::COMMON_HARP1);
			jjPARTICLE@ particle = jjAddParticle(PARTICLE::STRING);
			if (particle !is null) {
				particle.xPos = play.xPos;
				particle.yPos = play.yPos;
				particle.string.text = "|1up";
				particle.ySpeed = -1;
				particle.xSpeed = 1.4;
			}
			if (jjDifficulty >= 2) //on Easy or Normal, an extra life can be collected again each time you die, for greater and greater maximum health totals...
				jjEventSet(obj.xOrg / 32, obj.yOrg / 32, 0);
		}
	} else if (obj.eventID == OBJECT::MILK) { //goddess
		if (bull !is null) {
			obj.special -= (force << 3);
			obj.energy = (obj.special / 80); //used for the boss health bar
			obj.justHit += 8 + jjDifficulty;
			if ((bull.var[6] & 16) == 0)
				bull.state = STATE::EXPLODE;
		} else if (play !is null)
			play.hurt();
	} else if (obj.eventID == OBJECT::BOLLY) { //BollyTop, specifically
		if (obj.playerHandling != HANDLING::EXPLOSION) {
			if (bull !is null) {
				jjObjects[obj.creatorID].age -= (bull.objectID > 0) ? bull.animSpeed : 1;
				obj.justHit = 5;
				if ((bull.var[6] & 16) == 0)
					bull.state = STATE::EXPLODE;
			} else if (force != 0) {
				jjObjects[obj.creatorID].age -= 3;
				obj.justHit = 5;
			} else if (play !is null)
				play.hurt(); //bahaha
		}
	} else if (obj.behavior == BollyBouncingLaser && play !is null && bull is null)
		play.hurt(2);
}



void finishChat(jjPLAYER@ play) {
		if (currentPauseChat == chatEVA || currentPauseChat == chatEVA2) {
			play.currWeapon = WEAPON::RF;
		} else if (currentPauseChat == chatSTATUE) {
			statueDefeated = true;
			jjTriggers[0] = false;
			play.cameraUnfreeze();
			jjMusicLoad(MUSICFILENAME);
		}
		play.keySelect = false;
		paused = pausedForChatting = false;
		elapsedPauseTime = 0;
}
void onPlayerInput(jjPLAYER@ play) {
	if (paused) {
		play.keyUp = play.keyDown = play.keyRight = play.keyLeft = play.keyFire = play.keyRun = play.keyJump = false;
		if (pausedForApproachingPlanet) {
			play.keyRun = true; //no idle noises
		} if (pausedForChatting && currentPauseChat == chatGODDESS) {
			if (play.xPos < 140*32) play.keyRight = true;
			else if (play.xPos > 141*32) play.keyLeft = true;
			else { play.direction = -1; play.keyRun = true; } //run, so as not to play idle animations
		} else if (elapsedPauseTime > 25 && play.keySelect) {
			if (pausedForGunStory) {
				jjTexturedBGUsed = false;
				jjMusicResume();
				play.currWeapon = describedWeapon;
				jjBackupPalette.apply();
				paused = pausedForGunStory = false;
				elapsedPauseTime = 0;
				play.cameraUnfreeze();
				if (describedWeapon == 5) {
					jjAddObject(OBJECT::NORMTURTLE, 65 * 32, 32);
					jjAddObject(OBJECT::NORMTURTLE, 73 * 32, 32);
					jjAddObject(OBJECT::NORMTURTLE, 69 * 32, 6 * 32);
				}
			} else if (pausedForChatting) {
				finishChat(play);
			}
		}
	} else if (play.keyFire && play.ammo[play.currWeapon] == 0) {
		play.currWeapon = WEAPON::BLASTER;
	}
}
bool onDrawScore(jjPLAYER@ play, jjCANVAS@ screen) {
	if (!(paused || play.bossActivated || whiteWorld) || showScore) {
		if (play.score > play.scoreDisplayed) {
			play.coins = 100; //draw the score as white instead of gray for about a second and a half
			if (play.score > play.scoreDisplayed + 100)
				play.scoreDisplayed += 50;
			else if (play.score >= play.scoreDisplayed + 10)
				play.scoreDisplayed += 10;
			else
				play.scoreDisplayed = play.score;
		} else if (play.coins > 0)
			--play.coins;
		screen.drawString(10, 10, "#" + formatInt(play.scoreDisplayed, "0", 6), STRING::MEDIUM, (play.coins <= 0 && !showScore) ? STRING::DARK : STRING::NORMAL);
	}
	return true;
}
bool onDrawHealth(jjPLAYER@ play, jjCANVAS@ screen) {
	return paused;
}
bool onDrawLives(jjPLAYER@ play, jjCANVAS@ screen) {
	return true; //where we're going, we don't need lives
}
bool onDrawAmmo(jjPLAYER@ play, jjCANVAS@ screen) {
	if (pausedForGunStory) {
		screen.drawString(100, 70, "|||" + weaponNames[describedWeapon], STRING::LARGE, STRING::BOUNCE);
		screen.drawSprite(320, 130 + jjSin(jjRenderFrame << 3) * 12, ANIM::AMMO, gunProfilePics[describedWeapon], (jjRenderFrame >> 2) % 10, 1, SPRITE::RESIZED, 80);
		array<string> description = weaponDescriptions[describedWeapon].split("@");
		for (uint i = 0; i < description.length(); ++i)
			screen.drawString(40, 210 + i * 20, description[i]);
		//screen.drawString(10, 160, weaponDescriptions[describedWeapon], STRING::SMALL, STRING::SPIN, 1);
		if (elapsedPauseTime > 25) screen.drawString(200, 340, "#Press     Select", STRING::MEDIUM, STRING::SPIN, 20);
	} else if (pausedForChatting) {
		for (int x = 0; x < 640; x += 32) {
			screen.drawTile(x, 360, 412);
			for (int y = 392; y < 480; y += 32)
				screen.drawTile(x, y, 1);
		}
		if (currentPauseChat == chatEVA || currentPauseChat == chatEVA2) 
			screen.drawSprite(630, 470, ANIM::EVA, 0, 0, 0, SPRITE::RESIZED, 120);
		else if (currentPauseChat == chatSTATUE)
			for (int x = 0; x < 4; ++x)
				for (int y = 0; y < 4; ++y)
					screen.drawTile(520 + x*32, 360 + y*32, 736 - x + y*10 + TILE::HFLIPPED);
		else //goddess
			for (int x = 0; x < 2; ++x)
				for (int y = 0; y < 4; ++y)
					screen.drawTile(570 + x*32, 390 + y*32, 228 + x + y*10);
	} else if (whiteWorld || paused) {
		//do nothing
	} else {
		int currWeapon = play.currWeapon - 1;
		uint8 animation = gunProfilePics[currWeapon];
		string weaponName = weaponNames[currWeapon];
		if (currWeapon == 0 && play.charCurr == CHAR::SPAZ) ++animation;
		else if (currWeapon == 4 && play.powerup[WEAPON::RF]) {
			weaponName = "Magic Missiles";
			animation = 48;
		}
		screen.drawSprite(15/16. * jjResolutionWidth, 14/15. * jjResolutionHeight, ANIM::AMMO, 77, 8, 0, SPRITE::TINTED, 31);
		screen.drawSprite(15/16. * jjResolutionWidth, 14/15. * jjResolutionHeight, ANIM::AMMO, animation, (jjRenderFrame >> 2) % 10, 1, SPRITE::RESIZED, 64);
		screen.drawString(jjResolutionWidth, jjResolutionHeight - 6, weaponName, STRING::SMALL, STRING::RIGHTALIGN);
	}
	return true;
}
array<OBJECT::Object> PossibleScrollingObjects = {OBJECT::CARROT, OBJECT::REDGEM, OBJECT::BILSY, OBJECT::CRAB, OBJECT::NORMTURTLE, OBJECT::BEEBOY, OBJECT::PEAR};
array<float> PossibleScrollingOffsets = {-10, -10, 0, 34, 52, 0, -64};
void onMain() {
	jjPLAYER@ play = jjLocalPlayers[0];
	play.fastfire = firespeeds[play.currWeapon - 1];
	if (paused) {
		play.invincibility = -70;
		++elapsedPauseTime;
	}
	if (!hasAlreadyCollectedWeapon[play.currWeapon - 1] && play.ammo[WEAPON::CURRENT] > 0 && (!play.powerup[WEAPON::CURRENT] || play.currWeapon == WEAPON::GUN8)) {
		describedWeapon = play.currWeapon - 1;
		hasAlreadyCollectedWeapon[describedWeapon] = true;
		jjTexturedBGUsed = true;
		paused = pausedForGunStory = true;
		jjMusicPause();
		play.cameraFreeze(22*32, 34*32, true, true);
		jjPalette.fill(0, 0, 0, 160, 16, 0.5);
		jjPalette.apply();
	} else if (pausedForApproachingPlanet) {
		jjAddObject(OBJECT::LEMON, play.xPos - 8, play.yPos - 8); //starfield
		if (elapsedPauseTime > 290) {
			uint8 size = 32;
			if (elapsedPauseTime < 350) size = (elapsedPauseTime - 286) >> 1;
			else if (elapsedPauseTime == 350) {
				jjPalette.gradient(255,255,255, 0,0,0, 64, 8); //normal text
				jjPalette.apply();
				play.showText("@@@@@NOW APPROACHING@@@@@@@@@@@@     OZYMANDIUS", STRING::LARGE);
			} else if (elapsedPauseTime > 510) {
				size = (elapsedPauseTime - 492) << 1;
				if (size < 32) { //wrapped around
					paused = pausedForApproachingPlanet = false;
					elapsedPauseTime = 0;
					jjBackupPalette.apply();
					jjMusicLoad(MUSICFILENAME);
					jjChat("/allowmouseaim on");
					jjChat("/mouseaim on");
					jjChat("/fireball on");
					jjChat("/smhealth " + (8 - jjDifficulty));
					jjSamplePriority(SOUND::COMMON_TELPORT2);
					play.showText(""); //stop showing the APPROACHING text
					play.warpToTile(2, 7, true);
					play.xOrg = play.xPos; play.yOrg = play.yPos; //in case of deaths, reset to level, not starfield
					play.blink = 5*70; //as though the level had just begun
					return;
				}
			}
			jjDrawSprite(play.xPos, play.yPos + jjSin(jjGameTicks << 3) * 3, ANIM::AMMO, 77, 5, 0, SPRITE::RESIZED, size, 3);
		}
	} else if (pausedForChatting) {
		switch (currentPauseChat) {
		case chatEVA:
			if (elapsedPauseTime == 10) jjAlert("||Eva: ||One of Devan's robots is in the East Temple!");
			else if (elapsedPauseTime == 140) jjAlert("||Eva: ||I hear the rabbit statue holds the entry password.");
			else if (elapsedPauseTime == 340) jjAlert("||Eva: ||Take these missiles to destroy its barrier.");
			else if (elapsedPauseTime == 530) finishChat(play);
			break;
		case chatEVA2:
			if (elapsedPauseTime == 10) jjAlert("||Eva: ||Congratulations!");
			else if (elapsedPauseTime == 100) jjAlert("||Eva: ||Oh, you used all the missiles? That's too bad.");
			else if (elapsedPauseTime == 260) jjAlert("||Eva: ||Here, take this Crimson Candy gun to make up for it.");
			else if (elapsedPauseTime == 410) jjAlert("||Eva: ||Come see me again later! ;)");
			else if (elapsedPauseTime == 530) finishChat(play);
			break;
		case chatSTATUE:
			if (elapsedPauseTime == 10) jjAlert("|||Statue: |||Arrrrrrgh!!!");
			else if (elapsedPauseTime == 100) jjAlert("|||Statue: |||You have bested me, tiny one!");
			else if (elapsedPauseTime == 260) jjAlert("|||Statue: |||The temple's password is \"OPENSESAME\".");
			else if (elapsedPauseTime == 430) finishChat(play);
			break;
		case chatGODDESS:
			if (elapsedPauseTime == 10) jjAlert("|Goddess: |||||Eh? What mortal is it disturbs me?");
			else if (elapsedPauseTime == 180) jjAlert("|Goddess: |||||You seek my aid to remove a robot from this temple?");
			else if (elapsedPauseTime == 440) jjAlert("|Goddess: |||||Our purposes align... I too have no fondness for robots.");
			else if (elapsedPauseTime == 550) jjAlert("|Goddess: |||||However...");
			else if (elapsedPauseTime == 780) {
				pausedForWatchingTheGoddess = true;
				play.cameraFreeze(134*32, 64*32, true, false);
				jjAlert("|Goddess: |||||Nor have I any fondness for you.");
			}
			else if (elapsedPauseTime == 1000) {
				pausedForChatting = false;
			}
			break;
		}
	} else if (pausedForWatchingTheGoddess) {
		if (elapsedPauseTime == 1123) {
			pausedForWatchingTheGoddess = paused = false;
			play.cameraUnfreeze();
			jjMusicLoad("doom.it") || jjMusicLoad("cbcd_crp.xm") || jjMusicLoad("sweetdre.xm") || jjMusicLoad("boss.j2b");
		}
	} else if (scrolly) {
		jjLayerXOffset[7] = jjLayerXOffset[7] + SCROLLSPEED * 0.29628;
		jjLayerXOffset[6] = jjLayerXOffset[6] + SCROLLSPEED;
		for (int i = 0; i < jjLocalPlayerCount; ++i) {
			if (jjLocalPlayers[0].xPos > 155.5*32)
				jjLocalPlayers[0].xPos = jjLocalPlayers[0].xPos - SCROLLSPEED;
		}
		if (jjDifficulty > 1 && (jjGameTicks & 255) == 0) {
			uint idx = jjRandom() % PossibleScrollingObjects.length();
			jjAddObject(PossibleScrollingObjects[idx], 179*32, 10*32 + PossibleScrollingOffsets[idx]);
		}
		for (int i = 1; i < jjObjectCount; ++i) {
			jjOBJ@ obj = jjObjects[i];
			if (obj.isActive && obj.creatorType != CREATOR::LEVEL && obj.eventID != OBJECT::BOLLY) {
				obj.xPos = obj.xPos - SCROLLSPEED;
				obj.xOrg = obj.xOrg - SCROLLSPEED; //for good measure
			}
		}
		for (int i = 0; i < 1024; ++i) {
			jjParticles[i].xPos = jjParticles[i].xPos - SCROLLSPEED;
		}
	} else if (whiteWorld) {
		uint rand = jjRandom();
		if ((rand & 7) == 0) {
			jjPARTICLE@ part = jjAddParticle(PARTICLE::ICETRAIL);
			if (part !is null) {
				part.xPos = (155*32) + ((rand >> 3) % 640);
				part.yPos = 10;
				part.icetrail.color = 10;
			}
		}
	}
}


void onFunction0(jjPLAYER@ player) { //introduce the turtles
	jjAddObject(OBJECT::NORMTURTLE, player.xPos - 5*32, 40);
	jjAddObject(OBJECT::NORMTURTLE, player.xPos + 7*32, 32);
	jjAddObject(OBJECT::NORMTURTLE, player.xPos + 3*32, 6);
	jjAddObject(OBJECT::NORMTURTLE, player.xPos - 4*32, 75);
	jjPARTICLE@ particle = jjAddParticle(PARTICLE::STRING);
	if (particle !is null) {
		particle.xPos = player.xPos;
		particle.yPos = player.yPos;
		particle.string.text = "|Turtles!";
		particle.ySpeed = -1;
	}
}
void onFunction1(jjPLAYER@ player) { //bolly boss
	jjTriggers[5] = false;
	player.health = jjMaxHealth; //let's be generous
	player.xSpeed = 14;
	player.cameraFreeze(155*32 + 5, 1, false, false);
	player.boss = jjAddObject(OBJECT::BOLLY, 165*32, -96);
	jjMusicStop();
	for (int i = 96; i < 245; ++i) {
		jjPalette.color[i].setHSL(
			jjPalette.color[i].getHue(),
			jjPalette.color[i].getSat() - 35,
			jjPalette.color[i].getLight() - 10
		);
	}
	jjPalette.apply();
}
void onFunction2(jjPLAYER@ player) { //statue boss
	jjTriggers[0] = jjTriggers[1] = true;
	player.cameraFreeze(62.25 * 32, 30 * 32, false, false);
	player.activateBoss();
	player.boss = jjAddObject(OBJECT::HELMUT, 63*32 + 3, 37*32 + 24);
	jjOBJ@ boss = jjObjects[player.boss];
	for (int i = 0; i < 4; ++i) {
		boss.var[i] = jjAddObject(OBJECT::BIGROCK, 85*32, 40*32, player.boss);
		jjOBJ@ rock = jjObjects[boss.var[i]];
		rock.state = STATE::HIDE;
		rock.bulletHandling = HANDLING::DESTROYBULLET;
		rock.playerHandling = HANDLING::ENEMY; //replenishes health, so needn't worry about buttstomping
	}
}
void onFunction3(jjPLAYER@ player) { //goddess boss
	if (player.health < jjMaxHealth)
		jjAlert("|Goddess: |||||Phah! Return to me when you are fuller in health.");
	else {
		jjEnabledASFunctions[3] = false;
		if (!jjTriggers[5]) { //don't do this if the goddess has already been beaten and this follows a death and a checkpoint revival
			paused = pausedForChatting = true;
			elapsedPauseTime = 1;
			currentPauseChat = chatGODDESS;
			jjMusicStop();
		}
	}
}
void onFunction4(jjPLAYER@ player) { //SSF
	if (jjDifficulty >= 2) player.showText("@@@@@@@@@@@#           Let us place@@the spirit of the 9th child@@   into the body of child 0.", STRING::MEDIUM);
	jjTileSet(5, 185, 65, 1); //remove the sign
}

void onDrawLayer5(jjPLAYER@ player, jjCANVAS@ layer) { //fill in the wall behind the OPENSESAME sign
	layer.drawTile(82*32 + 16, 38*32, 444);
	layer.drawTile(87*32 + 16, 38*32, 444);
	layer.drawTile(82*32 + 16, 39*32, 59);
	layer.drawTile(87*32 + 16, 39*32, 59);
	layer.drawTile(82*32 + 16, 40*32, 434);
	layer.drawTile(87*32 + 16, 40*32, 434);
}