Downloads containing HH24_level06.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Holiday Hare 24Featured Download PurpleJazz Single player 10 Download file

File preview

const bool MLLESetupSuccessful = MLLE::Setup(array<MLLEWeaponApply@> = {null, null, null, null, null, null, null, null, WeaponVMega::Voranj::Weapon()}); ///@MLLE-Generated
#include "MLLE-Include-1.7w.asc" ///@MLLE-Generated
#pragma require "HH24_level06-MLLE-Data-3.j2l" ///@MLLE-Generated
#pragma require "HH24_level06-MLLE-Data-2.j2l" ///@MLLE-Generated
#pragma require "HH24_level06-MLLE-Data-1.j2l" ///@MLLE-Generated
#pragma require "Waz18.j2t" ///@MLLE-Generated
#pragma require "Medivo.j2t" ///@MLLE-Generated
#pragma require "BioWinter.j2t" ///@MLLE-Generated
#pragma require "Aztec2.j2t" ///@MLLE-Generated
#pragma require "SSWorlds_SET7.j2t" ///@MLLE-Generated
#pragma require "HH24_level06.j2l" ///@MLLE-Generated
#include "WeaponVMega8.asc" ///@MLLE-Generated
#pragma require "WeaponVMega8.asc" ///@MLLE-Generated
#pragma require "hh17enemies_hh24.asc"
#include "HH18Smoke_HH24.asc"
#include "HH24IceGolem.asc"
#include "hh17enemies_HH24.asc"
#include "HH24.asc"
#include "Resize v11.asc"

#include "HH24_IceKnightEnemy.asc"
#pragma require "SExmas.j2a"

#pragma require "xmasTufTurt.j2a"
#pragma require "HH24.j2a"

#pragma require "HH24_Cold_Wind.ogg"
#pragma require "HH24_FallSound.ogg"
#pragma require "HH24_Sword.wav"

#pragma require "Sword.j2a"
#pragma require "HH17_Icicle.j2a"

#pragma require "SEhh24.j2a"
#pragma require "SEhh24dryfire.wav"
#pragma require "SEhh24launcher1.wav"
#pragma require "SEhh24launcher2.wav"
#pragma require "SEhh24launcherswitch.wav"
#pragma require "SEhh24secret.wav"
#pragma require "SEhh24spikeball1.wav"
#pragma require "SEhh24spikeball2.wav"
#pragma require "SEwaterbubble.png"

//#include "limitedoxygen.asc"
///@Event 7=H-Pole	  |+|Object|H-Pole |       |Delay:7
///@Event 48=Present  |+|Object |Xmas |Present |Event:8 |Number: 4 |Colour: 4
///@Event 107=Fire Dragon   |-|Enemy     |Fire   |Dragon  |Colour: 8
///@Event 118=Ice Knight  |-|Enemy     |Ice   |Knight
///@Event 120=Polar Bear   |-|Enemy  |Polar   |Bear
///@Event 123=Santa Ghost   |-|Enemy     |Santa   |Ghost
///@Event 125=Ice Golem     |-|Enemy     |Ice    |Golem
///@Event 142=Spike Orb     |+|Object     |Spike  |Orb
///@Event 146=Fudge         |+|Food      |Fudge
///@Event 154=Swiss Roll         |+|Food      |Swiss     |Roll
///@Event 160=Lollipop        |+|Food      |Lolli    |-Pop
///@Event 161=Candy Bar      |+|Food      |Candy    |Bar
///@Event 162=White Choc Bar       |+|Food      |White    |Choc
///@Event 163=Eggnog      |+|Food      |Egg    |Nog
///@Event 166=Apple Pie         |+|Food      |Apple     |Pie
///@Event 170=Cookie         |+|Food      |Cookie
///@Event 175=Green Candy Cane        |+|Food      |Green     |Cane
///@Event 176=Red Candy Cane         |+|Food      |Red     |Cane
///@Event 177=Caramel         |+|Food      |Cara-     |Mel
///@Event 178=Brownie        |+|Food      |Brownie
///@Event 191=Penguin  |-|Enemy     |Pen- |Guin
///@Event 232=Bubble Launcher   |+|Object   |Bubble |Launch |Angle 1:3 |Angle 2:3 |Switch: 4
///@Event 254=Delete This |-|Modifier |NOT |v

//bool showColdWaterMessage = false;
array<bool> SavedTriggers(32, false);

bool iceGolemFight, reachedCastle = false;

bool startQueenBattle = false;

bool splitscreen = jjLocalPlayerCount > 1;

const int GolemMaxHP = 60+(jjDifficulty*20);

int sample, sample2 = 0;
int swordSample = 0;

bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	if (fallDownHole) {
		canvas.drawRotatedSprite(int(jjSubscreenWidth/2), int(jjSubscreenHeight/2), fallAnimSet, 11, jjGameTicks >> 2, -fallAngle, fallScale, fallScale, SPRITE::PLAYER, player.playerID);
	}
	return MLLE::WeaponHook.drawAmmo(player, canvas);
}

bool fallDownHole, cycleOnce = false;
int fallAngle = 0;
float fallScale = 10;
ANIM::Set fallAnimSet;
int fallAnim;

namespace sound {
	const SOUND::Sample spikeballHit = SOUND::INTRO_BOEM1;
	const SOUND::Sample spikeballRecover = SOUND::INTRO_BOEM2;
	const SOUND::Sample launcherSwitch = SOUND::INTRO_BRAKE;
	const SOUND::Sample dryFire = SOUND::INTRO_END;
	const SOUND::Sample secret = SOUND::INTRO_GRAB;
	const SOUND::Sample launcherHit = SOUND::INTRO_GREN1;
	const SOUND::Sample launcherFire = SOUND::INTRO_GREN2;
}

enum CustomAnim {
	anim_bubble,
	anim_rope,
	anim_launcher,
	anim_spike_ball,
}
enum BubbleVar {
	bubble_size,
	bubble_max_size
}
enum LauncherVar {
	launcher_angle_1,
	launcher_angle_2,
	launcher_angle_target,
	launcher_angle_alt,
	launcher_angle_displayed,
	launcher_angular_velocity,
	launcher_state,
	launcher_liquid_deficit
}
enum LauncherSwitchVar {
	launcher_switch_rope_length,
	launcher_switch_rope_frame
}

class Bubble : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) override {
		obj.state = STATE::FLOAT;
		obj.xPos += obj.xSpeed;
		obj.yPos += obj.ySpeed;
		float squish = 1.f + 0.075f * jjSin(obj.counter);
		obj.counter += 9;
		if (obj.var[bubble_size] < obj.var[bubble_max_size])
			obj.var[bubble_size] = obj.var[bubble_size] + 1;
		float scale = 1.f * obj.var[bubble_size] / obj.var[bubble_max_size];
		if (isOnScreen(obj)) {
			if (jjColorDepth != 8) {
				TrueColor::DrawResizedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[anim_bubble], 0, 0, scale * squish, scale / squish, 3);
			} else {
				for (int i = 0; i < 3; i++) {
					jjDrawResizedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[anim_bubble], 0, i, scale * squish, scale / squish, SPRITE::NEONGLOW, i, 3);
				}
			}
		}
		bool pop = false;
		int width = jjLayerWidth[4] << 5;
		int height = jjLayerHeight[4] << 5;
		float outOfBounds = 2 * obj.var[1];
		pop = obj.xPos < -outOfBounds || obj.yPos < -outOfBounds || obj.xPos > width + outOfBounds || obj.yPos > height + outOfBounds;
		int radius = obj.var[0] * 3 / 4;
		for (int i = 0; !pop && i < 1024; i += 32) {
			int x = int(obj.xPos + jjSin(i) * radius);
			int y = int(obj.yPos + jjCos(i) * radius);
			pop = x >= 0 && y >= 0 && x < width && y < height && jjMaskedPixel(x, y) && jjEventAtLastMaskedPixel != AREA::STOPENEMY;
		}
		if (pop)
			destroy(obj);
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if (bullet !is null) {
			int radius = obj.var[bubble_size] * 3 / 4;
			float x = bullet.xPos - obj.xPos;
			float y = bullet.yPos - obj.yPos;
			if (x * x + y * y < radius * radius)
				destroy(obj);
		}
		return true;
	}
	void assign(jjOBJ@ obj) {
		obj.behavior = this;
		obj.counter = 0;
		obj.var[bubble_size] = 2;
		obj.var[bubble_max_size] = 64;
		obj.playerHandling = HANDLING::SPECIAL;
		obj.bulletHandling = HANDLING::DETECTBULLET;
		obj.deactivates = false;
		obj.scriptedCollisions = true;
		obj.isBlastable = false;
		obj.isFreezable = false;
		obj.isTarget = false;
		obj.causesRicochet = false;
		obj.frameID = 0;
		obj.determineCurAnim(ANIM::CUSTOM[anim_bubble], 0);
		obj.determineCurFrame();
	}
	void destroy(jjOBJ@ obj) const {
		int count = obj.var[bubble_size] * 4;
		for (int i = 0; i < count; i++) {
			auto@ part = jjAddParticle(PARTICLE::PIXEL);
			if (part !is null) {
				uint random = jjRandom();
				int angle = random & 1023;
				random >>= 10;
				float z = (random & 63) / 64.f;
				random >>= 6;
				float r = sqrt(1.f - z * z) * float(obj.var[bubble_size]);
				float xComp = jjSin(angle);
				float yComp = jjCos(angle);
				part.xPos = obj.xPos + xComp * r;
				part.yPos = obj.yPos + yComp * r;
				part.xSpeed = xComp + ((random & 15) - 7.5f) * 0.05f;
				random >>= 4;
				part.ySpeed = yComp + ((random & 15) - 7.5f) * 0.05f;
				random >>= 4;
				part.pixel.size = 1;
				for (int j = 0; j < 4; j++) {
					part.pixel.color[j] = (jjColorDepth != 8 ? 33 : 35) + (random & 3);
					random >>= 2;
				}
			}
		}
		jjSample(obj.xPos, obj.yPos, SOUND::COMMON_PLOP2);
		obj.delete();
	}
	bool isOnScreen(const jjOBJ@ obj) const {
		float margin = obj.var[bubble_size] * 2;
		float x = obj.xPos;
		float y = obj.yPos;
		for (int i = 0; i < jjLocalPlayerCount; i++) {
			const auto@ player = jjLocalPlayers[i];
			float left = player.cameraX;
			float top = player.cameraY;
			float right = left + jjSubscreenWidth;
			float bottom = top + jjSubscreenHeight;
			if (x + margin >= left && x - margin <= right && y + margin >= top && y - margin <= bottom)
				return true;
		}
		return false;
	}
}
class Launcher : jjBEHAVIORINTERFACE {
	float bubbleSpeed = 2.f;
	int fireTime = 64;
	int rechargeTime = 120;
	void onBehave(jjOBJ@ obj) override {
		float xScale = 1.f;
		float yScale = 1.f;
		if (obj.state == STATE::START) {
			obj.state = STATE::IDLE;
			int x = int(obj.xPos) >> 5;
			int y = int(obj.yPos) >> 5;
			obj.var[launcher_angle_1] = jjParameterGet(x, y, 0, 3) << 7;
			obj.var[launcher_angle_2] = jjParameterGet(x, y, 3, 3) << 7;
			if (iabs(obj.var[launcher_angle_2] - obj.var[launcher_angle_1]) > 512) {
				auto lesser = obj.var[launcher_angle_1] < obj.var[launcher_angle_2] ? launcher_angle_1 : launcher_angle_2;
				obj.var[lesser] = obj.var[lesser] + 1024;
			}
			obj.var[launcher_angle_target] = obj.var[launcher_angle_1];
			obj.var[launcher_angle_alt] = obj.var[launcher_angle_2];
			obj.var[launcher_angle_displayed] = obj.var[launcher_angle_target];
			int ropeLength = jjParameterGet(x, y, 6, 4) << 5;
			if (ropeLength != 0) {
				int id = jjAddObject(OBJECT::TRIGGERCRATE, obj.xPos, obj.yPos + ropeLength, obj.objectID, CREATOR::OBJECT, BEHAVIOR::INACTIVE);
				if (id > 0) {
					jjOBJ@ other = jjObjects[id];
					launcherSwitch.assign(other);
					other.var[launcher_switch_rope_length] = ropeLength;
				}
			}
		}
		if (obj.state == STATE::FIRE || obj.counter > 0) {
			obj.counter++;
			float squish = 1.f + 0.3f * jjSin(obj.counter * 1024 / fireTime);
			xScale *= 1.f / squish;
			yScale *= squish;
			if (obj.var[launcher_liquid_deficit] < rechargeTime) {
				obj.var[launcher_liquid_deficit] = obj.var[launcher_liquid_deficit] + obj.counter / 4;
				if (obj.var[launcher_liquid_deficit] > rechargeTime)
					obj.var[launcher_liquid_deficit] = rechargeTime;
			}
			if (obj.counter == fireTime * 3 / 4) {
				float xOff = jjCos(obj.var[launcher_angle_target]);
				float yOff = -jjSin(obj.var[launcher_angle_target]);
				int id = jjAddObject(OBJECT::BUBBLE, obj.xPos + 40.f * xOff, obj.yPos + 40.f * yOff, obj.objectID, CREATOR::OBJECT, BEHAVIOR::INACTIVE);
				if (id > 0) {
					jjOBJ@ other = jjObjects[id];
					bubble.assign(other);
					other.xSpeed = xOff * bubbleSpeed;
					other.ySpeed = yOff * bubbleSpeed;
					jjSample(other.xPos, other.yPos, sound::launcherFire);
				}
			}
			if (obj.counter >= fireTime) {
				if (obj.state == STATE::FIRE)
					obj.state = STATE::IDLE;
				obj.counter = 0;
			}
		} else if (obj.var[launcher_liquid_deficit] > 0) {
			obj.var[launcher_liquid_deficit] = obj.var[launcher_liquid_deficit] - 1;
		}
		if (obj.state == STATE::ROTATE) {
			int direction = isign(obj.var[launcher_angle_target] - obj.var[launcher_angle_displayed]);
			obj.var[launcher_angular_velocity] = obj.var[launcher_angular_velocity] + direction;
			if (obj.var[launcher_angular_velocity] < -16)
				obj.var[launcher_angular_velocity] = -16;
			else if (obj.var[launcher_angular_velocity] > 16)
				obj.var[launcher_angular_velocity] = 16;
			obj.var[launcher_angle_displayed] = obj.var[launcher_angle_displayed] + obj.var[launcher_angular_velocity];
			int newDirection = isign(obj.var[launcher_angle_target] - obj.var[launcher_angle_displayed]);
			if (newDirection != direction) {
				obj.var[launcher_angular_velocity] = -obj.var[launcher_angular_velocity] * 5 / 8;
				obj.var[launcher_angle_displayed] = obj.var[launcher_angle_target] + obj.var[launcher_angular_velocity];
				if (obj.var[launcher_angular_velocity] == 0)
					obj.state = STATE::IDLE;
			}
		}
		if (isOnScreen(obj)) {
			SPRITE::Mode mode = obj.justHit > 0 ? SPRITE::SINGLECOLOR : SPRITE::MAPPING;
			int liquidFrame = obj.var[launcher_liquid_deficit] * jjAnimations[obj.curAnim + 3].frameCount / (rechargeTime + 1);
			jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim + 2], obj.var[launcher_angle_displayed], xScale, yScale, mode, 15);
			if (mode != SPRITE::SINGLECOLOR)
				jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim + 3] + liquidFrame, 0, SPRITE::TRANSLUCENT);
			jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim + 1], obj.var[launcher_angle_displayed], xScale, yScale, mode, obj.justHit > 0? 15:10);
		}
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if (bullet !is null && obj.state == STATE::IDLE && obj.var[launcher_liquid_deficit] == 0) {
			obj.justHit = 5;
			bullet.state = STATE::EXPLODE;
			obj.state = STATE::FIRE;
			jjSample(obj.xPos, obj.yPos, sound::launcherHit);
		}
		return true;
	}
	void assign(jjOBJ@ obj) {
		obj.behavior = this;
		obj.playerHandling = HANDLING::SPECIAL;
		obj.bulletHandling = HANDLING::DETECTBULLET;
		obj.deactivates = false;
		obj.scriptedCollisions = true;
		obj.isBlastable = false;
		obj.isFreezable = false;
		obj.isTarget = false;
		obj.causesRicochet = false;
		obj.frameID = 0;
		obj.determineCurAnim(ANIM::CUSTOM[anim_launcher], 0);
		obj.determineCurFrame();
		obj.killAnim = jjAnimations[obj.determineCurAnim(ANIM::CUSTOM[anim_launcher], 1, false)];
	}
	bool isOnScreen(const jjOBJ@ obj) const {
		const float margin = 60.f;
		float x = obj.xPos;
		float y = obj.yPos;
		for (int i = 0; i < jjLocalPlayerCount; i++) {
			const auto@ player = jjLocalPlayers[i];
			float left = player.cameraX;
			float top = player.cameraY;
			float right = left + jjSubscreenWidth;
			float bottom = top + jjSubscreenHeight;
			if (x + margin >= left && x - margin <= right && y + margin >= top && y - margin <= bottom)
				return true;
		}
		return false;
	}
}
class LauncherSwitch : jjBEHAVIORINTERFACE {
	int revolutionCount = 2;
	int spinTime = 110;
	float angularVelocity = (2 * revolutionCount + 1) * 1024 / float(spinTime);
	float angularAcceleration = angularVelocity / spinTime;
	void onBehave(jjOBJ@ obj) override {
		int angle = 0;
		if (obj.state == STATE::START) {
			obj.direction = 1;
			obj.state = STATE::IDLE;
		} else if (obj.state == STATE::ROTATE) {
			int oldAngle = int(0.5f * angularAcceleration * (obj.counter * obj.counter)) & 1023;
			obj.counter--;
			if (obj.counter <= 0) {
				obj.state = STATE::IDLE;
			} else {
				angle = int(0.5f * angularAcceleration * (obj.counter * obj.counter)) & 1023;
				float product = jjSin(angle) * jjSin(oldAngle);
				if (product < 0.f || product == 0.f && jjSin(angle) != 0.f)
					jjSample(obj.xPos, obj.yPos, sound::launcherSwitch, 63, 20000 + 2000 * obj.counter);
			}
		}
		int length = obj.var[launcher_switch_rope_length];
		jjDrawSwingingVineSpriteFromCurFrame(obj.xPos, obj.yPos - length, obj.var[launcher_switch_rope_frame], length, 0, SPRITE::NORMAL, 0, 5);
		SPRITE::Mode mode = obj.justHit > 0 ? SPRITE::SINGLECOLOR : SPRITE::NORMAL;
		float xScale = abs(jjCos(angle));
		jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, xScale, 1.f, mode, 15);
		const auto@ creator = jjObjects[obj.creatorID];
		int frame = jjAnimations[obj.curAnim + 1] + (creator.var[angle < 256 || angle >= 768 ? launcher_angle_target : launcher_angle_alt] >> 7 & 7);
		jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, frame, xScale, 1.f, mode, 15);
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if (bullet !is null && obj.state == STATE::IDLE) {
			obj.justHit = 5;
			bullet.state = STATE::EXPLODE;
			obj.state = STATE::ROTATE;
			obj.counter = spinTime;
			auto@ creator = jjObjects[obj.creatorID];
			int prevAngle = creator.var[launcher_angle_target];
			creator.var[launcher_angle_target] = creator.var[launcher_angle_alt];
			creator.var[launcher_angle_alt] = prevAngle;
			creator.state = STATE::ROTATE;
		}
		return true;
	}
	void assign(jjOBJ@ obj) {
		obj.behavior = this;
		obj.playerHandling = HANDLING::SPECIAL;
		obj.bulletHandling = HANDLING::DETECTBULLET;
		obj.deactivates = false;
		obj.scriptedCollisions = true;
		obj.isBlastable = false;
		obj.isFreezable = false;
		obj.isTarget = false;
		obj.causesRicochet = false;
		obj.frameID = 0;
		obj.determineCurAnim(ANIM::CUSTOM[anim_launcher], 4);
		obj.determineCurFrame();
		obj.var[launcher_switch_rope_frame] = jjAnimations[jjAnimSets[ANIM::CUSTOM[anim_rope]]];
	}
}
class SpikeBall : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) override {
		if (obj.state == STATE::START) {
			obj.state = STATE::ATTACK;
		} else if (obj.state == STATE::HIDE) {
			const int delay = 2;
			if (obj.counter % delay == 0) {
				if (obj.frameID > obj.counter / delay)
					obj.frameID--;
				else if (obj.frameID < int(jjAnimations[obj.curAnim].frameCount) - 1)
					obj.frameID++;
			}
			obj.causesRicochet = true;
			obj.counter--;
			if (obj.counter <= 0) {
				jjSample(obj.xPos, obj.yPos, sound::spikeballRecover);
				obj.state = STATE::ATTACK;
				obj.frameID = 0;
				obj.causesRicochet = false;
			}
		}
		obj.determineCurFrame();
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::MAPPING, 10);
		if (obj.justHit == 0) {
			const auto@ anim = jjAnimations[obj.curAnim + 1];
			int frameCount = anim.frameCount;
			int timerFrame = obj.counter * frameCount / (getMaxCounter() - 5);
			if (timerFrame >= frameCount)
				timerFrame = frameCount - 1;
			jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, anim + timerFrame, obj.direction, SPRITE::MAPPING, 10);
		}
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if (bullet !is null) {
			if (obj.state == STATE::ATTACK)
				jjSample(obj.xPos, obj.yPos, sound::spikeballHit);
			if (!obj.causesRicochet)
				bullet.state = STATE::EXPLODE;
			obj.justHit = 5;
			obj.state = STATE::HIDE;
			obj.counter = getMaxCounter();
		} else if (obj.state == STATE::ATTACK) {
			player.hurt();
		}
		return true;
	}
	int getMaxCounter() const {
		return jjDifficulty < 3 ? 420 - jjDifficulty * 100 : 160;
	}
	void assign(jjOBJ@ obj) {
		obj.behavior = this;
		obj.playerHandling = HANDLING::SPECIAL;
		obj.bulletHandling = HANDLING::DETECTBULLET;
		obj.deactivates = false;
		obj.scriptedCollisions = true;
		obj.isBlastable = false;
		obj.isFreezable = false;
		obj.isTarget = false;
		obj.causesRicochet = false;
		obj.frameID = 0;
		obj.determineCurAnim(ANIM::CUSTOM[anim_spike_ball], 0);
		obj.determineCurFrame();
	}
}
Bubble bubble;
Launcher launcher;
LauncherSwitch launcherSwitch;
SpikeBall spikeBall;
se::DefaultWeaponHook weaponHook;
array<int> gunJammedTimer(jjLocalPlayerCount);
jjPAL palette8;
jjPAL palette16;
int colorDepth = 16;
bool finished = false;
int iabs(int x) {
	return x < 0 ? -x : x;
}
int isign(int x) {
	return x < 0 ? -1 : x > 0 ? 1 : 0;
}
jjPIXELMAP@ makeRopeSprite() {
	jjPIXELMAP tile(5);
	jjPIXELMAP result(5, 8);
	for (int i = 0; i < 8; i++) {
		for (int j = 0; j < 5; j++) {
			result[j, i] = tile[i, 13 + j];
		}
	}
	return result;
}

void loadResources() {
	jjAnimSets[ANIM::CUSTOM[anim_bubble]].allocate(array<uint> = {TrueColor::NumberOfFramesPerImage});
	jjAnimSets[ANIM::CUSTOM[anim_rope]].allocate(array<uint> = {1});
	jjAnimSets[ANIM::CUSTOM[anim_launcher]].load(0, "SEhh24.j2a");
	jjAnimSets[ANIM::CUSTOM[anim_spike_ball]].load(1, "SEhh24.j2a");
	auto@ ropeAnimFrame = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::CUSTOM[anim_rope]]]];
	makeRopeSprite().save(ropeAnimFrame);
	ropeAnimFrame.hotSpotX = -ropeAnimFrame.width / 2;
	TrueColor::Bitmap image("SEwaterbubble.png");
	image.saveToAnimFrames(jjAnimations[jjAnimSets[ANIM::CUSTOM[anim_bubble]]], TrueColor::Coordinates(0, 0, image.width, image.height, -image.width / 2, -image.height / 2));
	jjSampleLoad(sound::spikeballHit, "SEhh24spikeball1.wav");
	jjSampleLoad(sound::spikeballRecover, "SEhh24spikeball2.wav");
	jjSampleLoad(sound::launcherSwitch, "SEhh24launcherswitch.wav");
	jjSampleLoad(sound::dryFire, "SEhh24dryfire.wav");
	jjSampleLoad(sound::secret, "SEhh24secret.wav");
	jjSampleLoad(sound::launcherHit, "SEhh24launcher1.wav");
	jjSampleLoad(sound::launcherFire, "SEhh24launcher2.wav");
}

void setBehaviors() {
	jjObjectPresets[OBJECT::BUBBLE].behavior = BEHAVIOR::INACTIVE;
	launcher.assign(jjObjectPresets[OBJECT::BIGBOX]);
	spikeBall.assign(jjObjectPresets[OBJECT::BANANA]);
	int id = jjAddObject(0, 0.f, 0.f);
	if (id > 0) {
		auto@ obj = jjObjects[id];
		obj.deactivates = false;
		obj.behavior = function(obj) {
			jjSetWaterLevel(jjLayerHeight[4] * 32 + 128.f, true);
		};
	}
}

jjOBJ@ getBubble(const jjPLAYER@ player) {
	for (int i = 0; i < jjObjectCount; i++) {
		const auto@ obj = jjObjects[i];
		if (cast<const jjBEHAVIORINTERFACE>(obj.behavior) is bubble) {
			float x = obj.xPos - player.xPos;
			float y = obj.yPos - player.yPos;
			if (x * x + y * y < obj.var[bubble_size] * obj.var[bubble_size])
				return obj;
		}
	}
	return null;
}

void onLevelLoad() {
	jjUseLayer8Speeds = true;
	
	HH24::levelLoad();
	loadResources();
	setBehaviors();

	HH17::setEnemy(OBJECT::MONKEY);
	HH17::setEnemy(OBJECT::RAVEN);
	HH17::setEnemy(OBJECT::BAT);
	HH17::setEnemy(OBJECT::LABRAT);
	HH17::setEnemy(OBJECT::DRAGON);
	HH24Enemies::MakeEventHH24Fencer(OBJECT::FENCER);
	HH24Enemies::MakeEventHH24Hatter(OBJECT::HATTER);
	SMOKE::MECHABEAR(OBJECT::DEMON,4);
	SMOKE::PENGUINATOR(OBJECT::TUBETURTLE,2);
	SMOKE::SANTAGHOST(OBJECT::DRAGONFLY,3);
	SMOKE::CRYSTALKNIGHT(OBJECT::BUMBEE,3);
	SMOKE::ICEKNIGHTENEMY(OBJECT::HELMUT,50);
	SMOKE_HH24::ICEGOLEMEDIT(OBJECT::FATCHICK,GolemMaxHP);
	SMOKE_ICEDRAG::ICEDRAGONFIXPAL(OBJECT::SPARK,OBJECT::CRAB);
	
	jjAnimSets[ANIM::RAPIER].load();
	jjAnimSets[ANIM::MENU].load();
	
	//SMOKE::ICEKNIGHT(OBJECT::BEE,40);
	
	uint src = jjAnimSets[ANIM::CUSTOM[255]].load(0, "SExmas.j2a");
	uint dest = jjAnimSets[ANIM::PICKUPS];
	for (int i = 0; i < 95; i++) {
		const jjANIMATION@ anim = jjAnimations[src + i];
		if (anim.frameCount != 0)
			jjAnimations[dest + i] = anim;
	}
	jjAnimSets[ANIM::BRIDGE].load(1, "SExmas.j2a");
	jjAnimSets[ANIM::CUSTOM[0]].load(2, "SExmas.j2a");
	jjAnimSets[ANIM::SPIKEPLAT].load(4, "SExmas.j2a");
	jjObjectPresets[OBJECT::SPIKEPLATFORM].deactivates = false;
	
	jjObjectPresets[OBJECT::SPIKEPLATFORM].killAnim = jjObjectPresets[OBJECT::SPIKEPLATFORM].determineCurAnim(ANIM::SPIKEPLAT, 0) + 1;
	
	jjObjectPresets[OBJECT::SPIKEBOLL].bulletHandling = HANDLING::DESTROYBULLET;
	jjAnimSets[ANIM::SPIKEBOLL].load(3, "SExmas.j2a");
	jjObjectPresets[OBJECT::SPIKEBOLL].killAnim = jjObjectPresets[OBJECT::SPIKEBOLL].determineCurAnim(ANIM::SPIKEBOLL, 0) + 1;
	
	jjObjectPresets[OBJECT::ONEUPCRATE].behavior = GiftBox();
	jjObjectPresets[OBJECT::ONEUPCRATE].determineCurAnim(ANIM::CUSTOM[0], 0);
	
	jjWeapons[WEAPON::GUN8].comesFromGunCrates = true;
	jjWeapons[WEAPON::GUN9].comesFromGunCrates = true;
	
	jjObjectPresets[OBJECT::SAVEPOST].behavior = CheckpointWrapper;
	jjObjectPresets[OBJECT::SAVEPOST].deactivates = false;
	
	jjObjectPresets[OBJECT::WATERMELON].behavior = GolemHP();
	jjObjectPresets[OBJECT::WATERMELON].energy = GolemMaxHP;
	jjObjectPresets[OBJECT::WATERMELON].playerHandling = HANDLING::PARTICLE;
	jjObjectPresets[OBJECT::WATERMELON].isBlastable = false;
	
	jjObjectPresets[OBJECT::TRIGGERCRATE].behavior = ColouredCrate();
	
	src = jjAnimSets[ANIM::CUSTOM[254]].load(0, "HH24.j2a");

	jjAnimations[dest + 18] = jjAnimations[src + 6]; // Chips -> Chocolate Chip Cookie
	jjAnimations[dest + 23] = jjAnimations[src + 8]; // Cucumber -> White Chocolate
	jjAnimations[dest + 48] = jjAnimations[src + 11]; // Lemon -> Swiss Roll
	jjAnimations[dest + 49] = jjAnimations[src + 13]; // Lettuce -> Lollipop
	jjAnimations[dest + 76] = jjAnimations[src + 12]; // Pie -> Apple Pie
	jjAnimations[dest + 26] = jjAnimations[src + 9]; // Eggplant -> Candy Bar
	jjAnimations[dest + 20] = jjAnimations[src + 7]; // Soft Drink -> Eggnog
	jjAnimations[dest + 17] = jjAnimations[src + 5]; // Chicken Leg -> Caramel
	jjAnimations[dest + 80] = jjAnimations[src + 16]; // Sandwich -> Brownie
	jjAnimations[dest + 79] = jjAnimations[src + 15]; // Pretzel -> Fudge
	
	// Christmas Tuf Turt
	
	jjAnimSets[ANIM::TUFTUR].load(1, "HH24.j2a");
	jjObjectPresets[OBJECT::TUFTURT].determineCurAnim(ANIM::TUFTUR, 0);
	
	deleteUnwantedEvents();
	
	jjObjectPresets[OBJECT::QUEEN].behavior = SwordQueen();
	jjObjectPresets[OBJECT::QUEEN].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[OBJECT::QUEEN].bulletHandling = HANDLING::DETECTBULLET;
	jjObjectPresets[OBJECT::QUEEN].scriptedCollisions = true;
	jjObjectPresets[OBJECT::QUEEN].isBlastable = false;
	jjObjectPresets[OBJECT::QUEEN].energy = 100;
	//jjObjectPresets[OBJECT::QUEEN].age = 150 + (50*jjDifficulty);
	jjObjectPresets[OBJECT::QUEEN].points = 20000;
	jjObjectPresets[OBJECT::QUEEN].deactivates = false;
	
	jjAnimSets[ANIM::CUSTOM[69]].load(0, "Sword.j2a");
	jjAnimSets[ANIM::CUSTOM[70]].load(0, "HH17_Icicle.j2a");
	
	Resize::Resize(
		jjAnimations[jjAnimSets[ANIM::MENU] + 1],
		3.5,
		Resize::Method::Scale2x
	);
	
	jjAnimSets[ANIM::CUSTOM[anim_bubble]].allocate(array<uint> = {TrueColor::NumberOfFramesPerImage});
	jjWaterLayer = 99;
	jjObjectPresets[OBJECT::BUBBLE].behavior = BEHAVIOR::INACTIVE;
	TrueColor::ProcessPalette();
	
	jjAnimSets[ANIM::CUSTOM[anim_bubble]].allocate(array<uint> = {TrueColor::NumberOfFramesPerImage});
	jjAnimSets[ANIM::CUSTOM[anim_launcher]].load(0, "SEhh24.j2a");
	
	TrueColor::Bitmap image("SEwaterbubble.png");
	image.saveToAnimFrames(jjAnimations[jjAnimSets[ANIM::CUSTOM[anim_bubble]]], TrueColor::Coordinates(0, 0, image.width, image.height, -image.width / 2, -image.height / 2));
	launcher.assign(jjObjectPresets[232]);
	
	jjWaterLighting = WATERLIGHT::GLOBAL;
}

void onLevelReload() {
	MLLE::SpawnOffgridsLocal();

	HH24::levelReload();
	
	HH17::processEnemyColors();
	jjWaterLighting = WATERLIGHT::GLOBAL;
	
	for (uint i = 0; i < 32; ++i)
		jjTriggers[i] = SavedTriggers[i];
		
	jjMusicLoad(reachedCastle? "jj2_symphoni_dexentrique.mo3" : "falling_flakes.xm");
	iceGolemFight = false;
	if (!reachedCastle && SMOKE_HH24::golemDeathCount > 0) {
		SMOKE_HH24::golemDeathCount = 0;
	}
	
	if (jjDifficulty > 0) {
		currPhase = First;
	}
	if (currState < Exit) {
		currState = Intro;	
		startQueenBattle = false;
	}
}

void onLevelBegin() {
	MLLE::SpawnOffgrids();
	
	jjSampleLoad(SOUND::WIND_WIND2A, "HH24_Cold_Wind.ogg");
	jjSampleLoad(SOUND::BONUS_BONUSBLUB, "HH17_Glass1.wav");
	jjSampleLoad(SOUND::BONUS_BONUS1, "HH24_FallSound.ogg");
	jjSampleLoad(SOUND::SCIENCE_PLOPKAOS, "HH24_Sword.wav");
	
	jjObjectPresets[OBJECT::ELECTROBULLET].animSpeed = 1;
	jjObjectPresets[OBJECT::ELECTROBULLETPU].animSpeed = 2;
}

void deleteUnwantedEvents() {
    int width = jjLayerWidth[4];
    int height = jjLayerHeight[4] - 1;
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            int event = jjEventGet(j, i);
            if (event == 254)
                jjParameterSet(j, i + 1, -12, 32, 0);
        }
    }
}


enum QueenPhases {First, Second};
uint currPhase = First;

enum QueenStates {Intro, Run, Slash, Spin, Charge, Bunnyhop, SpinJump, Stomp, Downward, Bounce, Defeat, Transform, Icicles, PrepareThrow, Throw, MegaSlash, Ultimate, DefeatTwo, Exit};
uint currState = Intro;

const array<QueenStates> longRangeAttacks = {Charge, SpinJump, Stomp};
const array<QueenStates> shortRangeAttacks = {Slash, Bunnyhop, Stomp};

const array<QueenStates> longRangeAttacks_PhaseTwo = {Charge, SpinJump, Stomp, Icicles, PrepareThrow};
const array<QueenStates> shortRangeAttacks_PhaseTwo = {Slash, Bunnyhop, Stomp, Icicles, MegaSlash};

array<QueenStates> longRangeQueue;
array<QueenStates> shortRangeQueue;

array<QueenStates> longRangeQueue_PhaseTwo;
array<QueenStates> shortRangeQueue_PhaseTwo;

void shuffle(array<QueenStates>& input) {
    for (int i = input.length(); i != 0;) {
        int index = jjRandom() % i;
        i--;
        QueenStates temp = input[index];
        input[index] = input[i];
        input[i] = temp;
    }
}

QueenStates pop(array<QueenStates>& input) {
    QueenStates result = input[input.length() - 1];
    input.removeLast();
    return result;
}

QueenStates getNextAttack(array<QueenStates>& queue, const array<QueenStates>& reserve) {
    if (queue.isEmpty()) {
        queue = reserve;
        shuffle(queue);
    }
    return pop(queue);
}

bool passive = false;

class SwordQueen : jjBEHAVIORINTERFACE {
	int currAnim, sample, armor, armorCounter;
	bool lowHP, jump, floating, immune, shield;
	
	float jumpApex;
	float bossHP = 150 + (25*jjDifficulty);
	float bossHPMax = 150 + (25*jjDifficulty);
	
	bool reverseAnim = false;
	bool deflect = true;
	
	int swordTime = (25*70) + ((5*70)*jjDifficulty);
	
	void onBehave(jjOBJ@ obj) {
		obj.determineCurAnim(ANIM::QUEEN, currAnim);
		obj.determineCurFrame();
		
		if (reverseAnim) {
			if (jjGameTicks % 7 == 0) {
				obj.frameID++;
			}
		} else {
			if (jjGameTicks % 7 == 0) {
				obj.frameID--;
			}
		}
		
		if (startQueenBattle) obj.counter++;
		
		if (obj.freeze > 0) {
			obj.unfreeze(1);
		}
		
		if (obj.justHit != 0) armorCounter = 0;
		else armorCounter++;
	  
		if (armorCounter > 0) {
			if (armorCounter < 35) {
				if (armorCounter % 7 == 0) {
					armor--;
				}
			}
		}
		if (armorCounter >= 70) {
			if (armorCounter % 4 == 0) {
				armor--;
			}
		}
	  
	  if (armor > 20) armor = 20;
	  if (armor < 0) armor = 0;
	  
	  obj.energy = int(bossHP / (1.5+(0.25*jjDifficulty)));
		
		if (obj.isActive) {
			for (int i = 0; i <= jjLocalPlayerCount; i++) {
				if (jjLocalPlayers[i].xPos >= 467*32) {
					jjLocalPlayers[i].boss = obj.objectID;
					startQueenBattle = true;
				}
			}
			if (startQueenBattle && jjLocalPlayerCount == 1 && jjResolutionWidth >= 640 && currState < Exit) {
				jjPLAYER@ play = jjLocalPlayers[0];
			
				float left = 449*32;
				float top = 135*32;
				float right = jjResolutionWidth < 800? 466*32 : 462*32;
				float bottom = jjResolutionHeight < 600? 138*32 : 135*32;
				if (right - left <= 32.f)
					left = right = (left + right) / 2.f;
				if (bottom - top <= 32.f)
					top = bottom = (top + bottom) / 2.f;
				float xCam = play.xPos - (jjSubscreenWidth >> 1);
				float yCam = play.yPos - (jjSubscreenHeight >> 1);
				if (xCam < left)
					xCam = left;
				if (xCam > right)
					xCam = right;
				if (yCam < top)
					yCam = top;
				if (yCam > bottom)
					yCam = bottom;
				play.cameraFreeze(xCam, yCam, false, true);
			}
		}
		
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		
		/*if (jjMaskedHLine(int(obj.xPos*32), int(obj.xSpeed), int(obj.yPos*32))) {
			obj.direction *= -1;
		}*/
		
		if (obj.xPos > 482*32) {
			obj.direction *= -1;
			obj.xSpeed = 0;
			obj.xPos = 481.75*32;
		}
		
		if (obj.xPos < 453*32) {
			obj.direction *= -1;
			obj.xSpeed = 0;
			obj.xPos = 453.25*32;
		}
		
		if (currPhase == Second && obj.energy <= 25 && !lowHP && currState == Run && jjDifficulty > 0) {
			setNewState(obj, Ultimate);
			lowHP = true;
		}
		
		int playerID = obj.findNearestPlayer(1000000);
		jjPLAYER@ play = jjPlayers[playerID];
		
		if (isFacingPlayer(obj, play.playerID) && play.xPos >= 467*32) startQueenBattle = true;
		
		int attackAngle = int(atan2(obj.xPos - play.xPos - (play.xSpeed * 16), obj.yPos - play.yPos - (play.ySpeed * 16)) * (512 / PI));
		
		if (currPhase == Second) {
			if (obj.energy <= 0 && currState < DefeatTwo && currState > Intro) {
				setNewState(obj, DefeatTwo);
				jjSetModPosition(20,0,false);
			}
		} else {
			if (obj.energy <= 0 && currState < Defeat && currState > Intro) {
				setNewState(obj, Defeat);
				bossHP = 0;
				jjSetModPosition(22,0,false);
			}		
		}
		
		if (onGround(obj) && !jump && !floating && currState < Exit) {
			obj.putOnGround(true);
		}
		
		if (jump) {
			obj.ySpeed = -4;
		}
		
		if (obj.yPos <= jumpApex) {
			if (obj.ySpeed < 4) obj.ySpeed += 0.35;
			jump = false;
		}
		
		switch (currState) {
			case Intro:
				currAnim = 3;
				reverseAnim = false;
				deflect = true;
				immune = true;
				passive = true;
				jump = false;
				floating = false;
				shield = false;
				bossHP = bossHPMax;
				lookAtPlayer(obj, play);
				lowHP = false;
				
				if (obj.counter == 1) {
					jjMusicLoad("jj2_Claw6.xm");
					jjLocalPlayers[0].showText("@@@@@@Your journey ends here.@@You will not uncover the secret of the castle!");
					for (int i = 0; i <= jjLocalPlayerCount; i++) {
						jjLocalPlayers[i].bossActivated = true;
					}
					int swrd = jjAddObject(OBJECT::SPRINGCORD, int(obj.xPos - (10 * obj.direction)), int(obj.yPos + 3));
					jjOBJ@ sword = jjObjects[swrd];
					sword.behavior = QueenSwordWeapon();
					sword.playerHandling = HANDLING::ENEMYBULLET;
					sword.bulletHandling = HANDLING::DESTROYBULLET;
					sword.isBlastable = false;
					sword.animSpeed = 1;
					sword.energy = obj.energy;
				}
				if (obj.counter >= 210) {
					jjLocalPlayers[0].showText("@@@@@@Well, not much of a secret now, is it...");
					immune = passive = false;
					setNewState(obj, Run);
				}
			break;
			case Run:
				currAnim = 7;
				reverseAnim = true;
				deflect = jump? false:true;
				obj.xSpeed = 3 * obj.direction;
				lookAtPlayer(obj, play);
				
				if (obj.counter >= 55) {
					randomAttack(obj, play);
				}
			break;
			
			case Slash:
				currAnim = 6;
				lookAtPlayer(obj, play);
				deflect = false;
				obj.xSpeed = (jump? 3:2) * obj.direction;
				
				if (play.yPos < (obj.yPos - 32) && !jump && onGround(obj)) {
					obj.special++;
					if (obj.special == 14) {
						jump = true;
						if (play.yPos < (obj.yPos - 96)) {
							jumpApex = 146.5*32;
						} else {
							jumpApex = 147.5*32;
						}
						jjSample(play.xPos, play.yPos, SOUND::BUBBA_BUBBABOUNCE2);
						obj.special = 0;
					}
				}
				
				if (currPhase == Second) {
					if (obj.counter % 6 == 0) {
						jjOBJ@ iceball = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos + jjSin(obj.counter*20)), int(obj.yPos + jjCos(obj.counter*20)), obj.objectID, CREATOR::OBJECT)];     
						jjSample(iceball.xPos, iceball.yPos, SOUND::AMMO_ICEGUN, 0, 0);
						iceball.behavior = IceCloud;
						iceball.curAnim = iceball.determineCurAnim(ANIM::AMMO, 82, true);
						iceball.frameID=5;
						iceball.determineCurFrame();
						iceball.counterEnd=20;
						iceball.freeze = 210;
						iceball.state = STATE::FLY;
						iceball.playerHandling = HANDLING::PARTICLE;
						iceball.lightType = LIGHT::NONE;
						iceball.xSpeed = ((jjSin(obj.counter*20)*6) * obj.direction) + obj.xSpeed;
						iceball.ySpeed = (jjCos(obj.counter*20)*6);
						iceball.killAnim = jjObjectPresets[OBJECT::ICEBULLET].killAnim;
					}
				}
				
				if (obj.counter % 35 == 0) {
					if (longRangeCriteria(obj, play) && onGround(obj)) {
						randomAttack(obj, play);
					} else {
						setNewState(obj, Run);
					}
					jjSample(play.xPos, play.yPos, SOUND::COMMON_FOEW3, 63, 12000);
				}
				
				if (obj.counter >= 35 && jump) {
					setNewState(obj, Spin);
				}
				
				if (obj.counter >= 105 && !jump) {
					setNewState(obj, Run);
				}
				
			break;
			
			case Spin:
				currAnim = 5;
				lookAtPlayer(obj, play);
				deflect = false;
				reverseAnim = false;
				obj.xSpeed = 3 * obj.direction;
				obj.yPos -= 0.1;

				if (currPhase == Second) {
					if (obj.counter % 6 == 0) {
						jjOBJ@ iceball = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos + jjSin(obj.counter*20)), int(obj.yPos + jjCos(obj.counter*20)), obj.objectID, CREATOR::OBJECT)];     
						jjSample(iceball.xPos, iceball.yPos, SOUND::AMMO_ICEGUN, 0, 0);
						iceball.behavior = IceCloud;
						iceball.curAnim = iceball.determineCurAnim(ANIM::AMMO, 82, true);
						iceball.frameID=5;
						iceball.determineCurFrame();
						iceball.counterEnd=20;
						iceball.freeze = 210;
						iceball.state = STATE::FLY;
						iceball.playerHandling = HANDLING::PARTICLE;
						iceball.lightType = LIGHT::NONE;
						iceball.xSpeed = ((jjSin(obj.counter*20)*6) * obj.direction) + obj.xSpeed;
						iceball.ySpeed = (jjCos(obj.counter*20)*6);
						iceball.killAnim = jjObjectPresets[OBJECT::ICEBULLET].killAnim;
					}
				}
				
				if (onGround(obj)) {
					setNewState(obj, Run);
				}
			break;
			
			case Charge:
				if (obj.counter == 1) jjSample(play.xPos, play.yPos, SOUND::INTRO_MONSTER2);
				if (obj.counter < 35) {
					obj.xSpeed = 0;
					currAnim = 6;
					reverseAnim = false;
					lookAtPlayer(obj, play);
					deflect = false;
				} else {
					if (obj.xPos >= 481.75*32 || obj.xPos <= 453.25*32) {
						obj.xSpeed = 0;
						obj.direction *= -1;
						obj.var[0] = 0;
						if (longRangeCriteria(obj, play) && onGround(obj)) {
							randomAttack(obj, play);
						} else {
							setNewState(obj, Run);
						}
					} else {
						currAnim = 7;
						reverseAnim = true;
						deflect = true;
						obj.xSpeed = 7 * obj.direction;
						
						if (currPhase == Second && obj.counter % 5 == 0) {
							int bulletID3 = jjAddObject(OBJECT::TOASTERBULLET, obj.xPos - obj.direction * 32, obj.yPos, obj.objectID, CREATOR::OBJECT);
							jjOBJ @ o = jjObjects[bulletID3];
							o.behavior = IceCloud;
							o.direction = obj.direction;
							o.xSpeed = 0;
							o.ySpeed = 0;

							o.curAnim = o.determineCurAnim(ANIM::AMMO, 82, true);
							o.frameID=5;
							o.determineCurFrame();
							o.counterEnd=20;
							o.freeze = 210;
							o.playerHandling = HANDLING::ENEMYBULLET;

						}
						
						if (play.yPos < (obj.yPos - 32) && !jump && onGround(obj) && obj.var[0] == 0 && obj.counter >= 35) {
							obj.special++;
							if (obj.special == 7) {
								jump = true;
								jumpApex = 147.5*32;
								jjSample(play.xPos, play.yPos, SOUND::BUBBA_BUBBABOUNCE2);
								obj.special = 0;
								if (jjRandom()%2 == 0) {
									if (play.yPos < (obj.yPos - 96)) {
										jumpApex = 145.5*32;
									} else {
										jumpApex = 147.5*32;
									}
									setNewState(obj, Slash);
								}
							}
						}
					}
				}
			break;
			
			case Bunnyhop:
				if (obj.counter == 1) {
					jump = true;
					jumpApex = 148.25*32;
					jjSample(play.xPos, play.yPos, SOUND::BUBBA_BUBBABOUNCE1);
				} else {
					if (onGround(obj) && !jump) {
						if (shortRangeCriteria(obj, play)) {
							setNewState(obj, Slash);
						} else {
							randomAttack(obj, play);
						}
					}
				}
				obj.xSpeed = 4 * obj.direction;
			break;
			
			case SpinJump:
				currAnim = 5;
				lookAtPlayer(obj, play);
				deflect = false;
				reverseAnim = false;
				
				if (obj.counter < 28) obj.xSpeed = 0;
				
				if (obj.counter == 28) {
					jump = true;
					jumpApex = play.yPos < 144*32? 143*32 : 145.5*32;
					jjSample(play.xPos, play.yPos, SOUND::BUBBA_BUBBABOUNCE2);
					jjSample(play.xPos, play.yPos, SOUND::COMMON_FOEW3, 63, 12000);
					obj.xSpeed = -5.5 * jjSin(attackAngle);
				} else {
					if (obj.counter > 28) {
					
						if (currPhase == Second) {
							if (obj.counter % 9 == 0) {
								jjOBJ@ iceball = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos + jjSin(obj.counter*20)), int(obj.yPos + jjCos(obj.counter*20)), obj.objectID, CREATOR::OBJECT)];     
								jjSample(iceball.xPos, iceball.yPos, SOUND::AMMO_ICEGUN, 0, 0);
								iceball.behavior = IceCloud;
								iceball.curAnim = iceball.determineCurAnim(ANIM::AMMO, 82, true);
								iceball.frameID=5;
								iceball.determineCurFrame();
								iceball.counterEnd=20;
								iceball.freeze = 210;
								iceball.state = STATE::FLY;
								iceball.playerHandling = HANDLING::PARTICLE;
								iceball.lightType = LIGHT::NONE;
								iceball.xSpeed = ((jjSin(obj.counter*20)*6) * obj.direction) + obj.xSpeed;
								iceball.ySpeed = (jjCos(obj.counter*20)*6);
								iceball.killAnim = jjObjectPresets[OBJECT::ICEBULLET].killAnim;
							}
						}
					
						if (!jump) {
							if (directlyBelow(obj, play)) {
								setNewState(obj, Downward);
							}
							obj.yPos -= 0.1;
							if (onGround(obj)) {
								obj.xSpeed = 0;
								if (shortRangeCriteria(obj, play)) {
									setNewState(obj, Slash);
								} else {
									setNewState(obj, Run);
								}
							}
						}
					}
				}
			break;
			
			case Stomp:
				currAnim = 6;
				lookAtPlayer(obj, play);
				deflect = false;
				obj.xSpeed = 0;
				
				if (currPhase == First) {
					if (obj.counter % (32-(jjDifficulty*4)) == 0 && onGround(obj)) {
						jjSample(play.xPos, play.yPos, SOUND::COMMON_LAND2);
						for (uint i = 0; i < (jjRandom()%3)+1; i++) {
							float brickXPos = ((obj.xPos - 64) + (((jjRandom()%(abs(xDistance(obj,play))+6))*32)*obj.direction) <= 453*32)? 453*32 : ((obj.xPos - 64) + (((jjRandom()%(abs(xDistance(obj,play))+6))*32)*obj.direction) >= 482*32)? 482*32 : ((obj.xPos - 64) + (((jjRandom()%(abs(xDistance(obj,play))+6))*32)*obj.direction));
							jjOBJ@ brick = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, brickXPos, 138.5*32 + jjRandom()%24, obj.objectID, CREATOR::OBJECT)];
							brick.xSpeed = 0;
							brick.xAcc = 0;
							brick.behavior = QueenBricks();
							brick.ySpeed = 2;
							brick.yAcc = 0.1;
							brick.direction = 1;
							brick.counterEnd = 250;
							brick.state = STATE::FLY;
							brick.playerHandling = HANDLING::ENEMYBULLET;
							brick.lightType = LIGHT::NONE;
						}
					}
				} else {
					if (obj.counter % 42 == 0) {
						jjSample(play.xPos, play.yPos, SOUND::BONUS_BONUSBLUB);
					}
				
					if (obj.counter % (10-(jjDifficulty*2)) == 0 && onGround(obj)) {
						jjOBJ@ icicles = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, ((452.5*32) + jjRandom()%944), 138.5*32, obj.objectID, CREATOR::OBJECT)];
						icicles.xSpeed = 0;
						icicles.xAcc = 0;
						icicles.behavior = QueenIcicles();
						icicles.ySpeed = 2;
						icicles.yAcc = 0.1;
						icicles.direction = 1;
						icicles.counterEnd = 250;
						icicles.state = STATE::FLY;
						icicles.playerHandling = HANDLING::ENEMYBULLET;
						icicles.lightType = LIGHT::NONE;
					}
				}
				
				if (obj.counter >= 105) {
					if (longRangeCriteria(obj, play) && onGround(obj)) {
						randomAttack(obj, play);
					} else {
						setNewState(obj, Run);
					}
				}
				
			break;
			
			case Downward:
				lookAtPlayer(obj, play);
				deflect = false;
				obj.xSpeed = 0;
				
				if (obj.counter < 35) {
					currAnim = 6;
					obj.ySpeed = 0.1;
				} else {
					if (obj.counter == 35) jjSample(play.xPos, play.yPos, SOUND::COMMON_DOWN);
					currAnim = 5;
					obj.ySpeed = 8;
					if (onGround(obj)) {
						jjSample(play.xPos, play.yPos, SOUND::ROBOT_POLE);
						jjSample(play.xPos, play.yPos, SOUND::COMMON_LAND1);
						setNewState(obj, Bounce);
					}
				}
				
			break;
			
			case Bounce: 
				deflect = false;
				obj.xSpeed = 0;
				currAnim = 5;
				
				if (currPhase == Second && obj.counter % 7 == 0 && obj.counter <= 21) {
					for (int i = 0; i < 2; i++) {
						jjOBJ@ blast = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos), int(obj.yPos+16), obj.objectID, CREATOR::OBJECT)];
						blast.xSpeed = i == 0? -6:6;
						blast.behavior = QueenBlast();
						blast.ySpeed = 0;
						blast.xAcc = blast.yAcc = 0;
						blast.direction = -obj.direction;
						blast.counterEnd = 90;
						blast.state = STATE::FLY;
						blast.playerHandling = HANDLING::ENEMYBULLET;
						blast.lightType = LIGHT::NONE;
					}
				}
				
				if (obj.counter <= 7) {
					jump = true;
					jumpApex = 149.5*32;
				} else {
					if (onGround(obj) && !jump) {
						setNewState(obj, Run);
					}
				}
			break;
			
			case Defeat:
				/*for (int i = 0; i <= jjLocalPlayerCount; i++) {
					jjLocalPlayers[i].bossActivated = false;
				}*/
				obj.xSpeed = 0;
				reverseAnim = false;
				immune = true;
				passive = true;
				if (obj.counter == 1) {
					//jjMusicStop();
					currAnim = 1;
					jjLocalPlayers[0].showText("@@@@@@@You're a lot tougher than I expected...");
					
					/*if (jjDifficulty < 1) {
						jjOBJ@ carrot = jjObjects[jjAddObject(OBJECT::FULLENERGY, 467*32, 144*32, 0, CREATOR::LEVEL)];
						carrot.state = STATE::FLOATFALL;
					}*/
				}
				
				if (obj.counter == 210) {
					jjLocalPlayers[0].showText("@@@@@@@But you haven't won yet.");
					currAnim = 3;
					obj.var[1] = 1;
				}
				
				if (obj.counter == 350) {
					setNewState(obj, Transform);
				}

			break;
			
			case Transform:
				currAnim = 5;
				floating = true;
				if (obj.yPos > 144*32) obj.ySpeed = -1;
				else obj.ySpeed = 0;

				if (bossHP < bossHPMax) {
					if (obj.counter % (6-jjDifficulty) == 0) bossHP += 2;
					immune = passive = true;
				} else {
					currPhase = Second;
					floating = jump = false;
					obj.ySpeed = 4;
					if (onGround(obj)) {
						jjLocalPlayers[0].showText("@@@@@@@We are one!");
						immune = passive = false;
						setNewState(obj, Run);
					}
				}
				
				if (obj.counter == 14) {
					jjMusicLoad("jj2_ckbattle.it");
					jjLocalPlayers[0].showText("@@@@@@@Sister, lend me your power!");
					jjSamplePriority(SOUND::ORANGE_SWEEP0R);
					jjOBJ@ iceQueen = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos), int(obj.yPos-320), obj.objectID, CREATOR::OBJECT)];
					iceQueen.xSpeed = 0;
					iceQueen.ySpeed = 1;
					iceQueen.behavior = IceQueenSpirit();
					iceQueen.direction = obj.direction;
					iceQueen.state = STATE::FLY;
					iceQueen.playerHandling = HANDLING::PARTICLE;
					iceQueen.lightType = LIGHT::LASER;
					iceQueen.light = 8;
				}
				
				if (obj.counter > 14 && obj.counter % 14 == 0) {
					jjOBJ@ cloud = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos+(128*jjSin(obj.counter*8))), int(obj.yPos+(128*jjCos(obj.counter*8))), obj.objectID, CREATOR::OBJECT)];
					cloud.xSpeed = -3 * jjSin(objReturnAngle(obj, cloud));
					cloud.ySpeed = -3 * jjCos(objReturnAngle(obj, cloud));
					cloud.behavior = IceTrail();
					cloud.xAcc = cloud.yAcc = 0;
					cloud.var[1] = 1;
					cloud.direction = -obj.direction;
					cloud.counterEnd = 200;
					cloud.state = STATE::FLY;
					cloud.playerHandling = HANDLING::PARTICLE;
					cloud.lightType = LIGHT::NONE;
				}
			
			break;
						
			case Icicles:
				currAnim = 0;
				lookAtPlayer(obj, play);
				deflect = false;
				reverseAnim = false;
				obj.xSpeed = 0;
				
				if (obj.counter == 28) {
					jjSample(play.xPos, play.yPos, SOUND::QUEEN_SCREAM);
					jjSample(play.xPos, play.yPos, SOUND::QUEEN_SCREAM, 15, 0);
				}
				
				if (obj.counter % (18 - (jjDifficulty*3)) == 0 && obj.counter >= 28) {
					jjOBJ@ icicle = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos), int(obj.yPos), obj.objectID, CREATOR::OBJECT)];
					icicle.xSpeed = 7 * obj.direction;
					icicle.xAcc = 0;
					icicle.yAcc = 0;
					icicle.behavior = QueenIcicles();
					icicle.ySpeed = -(2.5 + jjRandom()%4);
					icicle.direction = -obj.direction;
					icicle.counterEnd = 250;
					icicle.var[4] = 1;
					icicle.state = STATE::FLY;
					icicle.playerHandling = HANDLING::ENEMYBULLET;
					icicle.lightType = LIGHT::NONE;
				}
				
				if (obj.counter >= 84) {
					setNewState(obj, Run);
				}
			break;
			
			case PrepareThrow:
				currAnim = 0;
				lookAtPlayer(obj, play);
				deflect = false;
				reverseAnim = false;
				obj.xSpeed = 0;
				obj.special = 0;
				
				if (obj.counter >= 50) {
					throwSword = true;
					jjSample(play.xPos, play.yPos, SOUND::TUFBOSS_CATCH);
					setNewState(obj, Throw);
				}
			break;
				
			case Throw:
				currAnim = 3;
				lookAtPlayer(obj, play);
				deflect = true;
				reverseAnim = false;
				obj.xSpeed = 0;
				
				if (obj.special == 1) {
					setNewState(obj, Run);
				}
			break;
			
			case MegaSlash:
				if (obj.counter == 49) {
					deflect = false;
					obj.xSpeed = 8 * obj.direction;
					jjSample(play.xPos, play.yPos, SOUND::COMMON_FOEW3, 63, 12000);
					currAnim = 6;
					reverseAnim = true;
					deflect = false;
				} else if (obj.counter < 49) {
					obj.xSpeed = 0;
					currAnim = 3;
					reverseAnim = false;
					lookAtPlayer(obj, play);
					deflect = true;
				}
				
				if (obj.counter > 49 && obj.counter % 5 == 0) {
					jjOBJ@ iceball = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos + jjSin(obj.counter*20)), int(obj.yPos + jjCos(obj.counter*20)), obj.objectID, CREATOR::OBJECT)];     
					jjSample(iceball.xPos, iceball.yPos, SOUND::AMMO_ICEGUN, 0, 0);
					iceball.behavior = IceCloud;
					iceball.curAnim = iceball.determineCurAnim(ANIM::AMMO, 82, true);
					iceball.frameID=5;
					iceball.determineCurFrame();
					iceball.counterEnd=20;
					iceball.freeze = 210;
					iceball.state = STATE::FLY;
					iceball.playerHandling = HANDLING::PARTICLE;
					iceball.lightType = LIGHT::NONE;
					iceball.xSpeed = ((jjSin(obj.counter*20)*6) * obj.direction) + obj.xSpeed;
					iceball.ySpeed = (jjCos(obj.counter*20)*6);
					iceball.killAnim = jjObjectPresets[OBJECT::ICEBULLET].killAnim;
				}
				
				if (obj.counter == 84 || (obj.xPos >= 481.75*32 || obj.xPos <= 453.25*32)) {
					setNewState(obj, Run);
				}
				
			break;
			
			case Ultimate:
				currAnim = 5;
				if (floating) {
					immune = true;
					if (obj.yPos > 144*32) {
						obj.ySpeed = -2;
					} else {
						obj.ySpeed = 0;
					}
					
					if (obj.special == 0) {
						if (obj.xPos < 466.5*32) {
							obj.xSpeed = 4;
						} else if (obj.xPos > 466.5*32) {
							obj.xSpeed = -4;
						}
					}
					
					if (obj.xPos > 465.5*32 && obj.xPos < 467.5*32 && obj.yPos <= 144*32 && obj.special == 0) {
						obj.xSpeed = obj.ySpeed = 0;
						obj.special = 1;
						obj.counter = 0;
					}
					
					if (obj.special == 1 && obj.counter % (40 - (jjDifficulty*6)) == 0) {
						if (jjRandom()%4 == 0) {
							jjOBJ@ sword = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, obj.xPos-496, jjRandom()%2 == 0? play.yPos : (obj.yPos+jjRandom()%212*(jjRandom()%2 == 0? 1:-1)), obj.objectID, CREATOR::OBJECT)];   
							sword.behavior = SwordProjectiles();
							sword.state = STATE::START;
							sword.var[0] = 0;
							sword.deactivates = false;
						}
						if (jjRandom()%4 == 1) {
							jjOBJ@ sword = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, obj.xPos+536, jjRandom()%2 == 0? play.yPos : (obj.yPos+jjRandom()%212*(jjRandom()%2 == 0? 1:-1)), obj.objectID, CREATOR::OBJECT)];   
							sword.behavior = SwordProjectiles();
							sword.state = STATE::START;
							sword.var[0] = 1;
							sword.deactivates = false;
						}
						if (jjRandom()%4 == 2) {
							jjOBJ@ sword = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, jjRandom()%2 == 0? play.xPos : (obj.xPos+jjRandom()%460*(jjRandom()%2 == 0? 1:-1)), 138*32, obj.objectID, CREATOR::OBJECT)];   
							sword.behavior = SwordProjectiles();
							sword.state = STATE::START;
							sword.var[0] = 2;
							sword.deactivates = false;
						}
						if (jjRandom()%4 == 3) {
							jjOBJ@ sword = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, jjRandom()%2 == 0? play.xPos : (obj.xPos+jjRandom()%460*(jjRandom()%2 == 0? 1:-1)), 153*32, obj.objectID, CREATOR::OBJECT)];   
							sword.behavior = SwordProjectiles();
							sword.state = STATE::START;
							sword.var[0] = 3;
							sword.deactivates = false;
						}								
					}
				}
				
				if (obj.counter >= swordTime) {
					floating = false;
					immune = false;
					shield = false;
					obj.ySpeed = 4;
					
					if (onGround(obj)) {
						jjLocalPlayers[0].showText("@@@@@@Wait, you're still alive?!@@Maybe you are worthy after all...");
						setNewState(obj, Run);
					}
				}
				
				if (obj.counter == 1 && !floating) {
					jjLocalPlayers[0].showText("@@@@@@You've fought well.@@But no way are you going to survive this...");
					floating = true;
					shield = true;
				}
			break;
			
			case DefeatTwo:
				for (int i = 0; i <= jjLocalPlayerCount; i++) {
					jjLocalPlayers[i].bossActivated = false;
				}
				obj.xSpeed = 0;
				reverseAnim = false;
				immune = true;
				passive = true;
				shield = false;
				//jjMusicStop();
				if (obj.counter == 1) {
					currAnim = 1;
					jjLocalPlayers[0].showText("@@@@@@@Okay fine, you win for real this time!");
				}
				
				if (obj.counter == 210) {
					jjLocalPlayers[0].showText("@@@@@@@I will let you pass.@@But you have no chance against@the dangers that lie ahead...");
					currAnim = 3;
					obj.var[1] = 1;
				}
				
				if (obj.counter == 500) {
					setNewState(obj, Exit);
				}
			break;
			
			case Exit:
				currAnim = 5;
				obj.ySpeed = -3;
				jjMusicStop();
				if (obj.counter == 1) {
					jjLocalPlayers[0].showText("@@@@@@@Good luck.@@You will need it...");
					play.cameraUnfreeze();
					jjOBJ@ crate = jjObjects[jjAddObject(OBJECT::TRIGGERCRATE, 482*32, 149*32, 0, CREATOR::LEVEL)];
					jjParameterSet(int(crate.xPos/32), int(crate.yPos/32), 0, 5, 6);
				}
				
				if (obj.xPos <= 457*32) {
					obj.special++;
				}
				
				if (obj.yPos <= 130*32) {
					obj.delete();
				}
			break;
			
		}
	}
	
	void setNewState(jjOBJ@ obj, int state) {
		obj.counter = 0;
		obj.special = 0;
		obj.frameID = 0;
		currState = state;
	}
	
	void randomAttack(jjOBJ@ obj, jjPLAYER@ play) {
		if (currPhase == First) {
			if (shortRangeCriteria(obj, play)) {
				setNewState(obj, getNextAttack(shortRangeQueue, shortRangeAttacks));
			}
			if (longRangeCriteria(obj, play)) {
				setNewState(obj, getNextAttack(longRangeQueue, longRangeAttacks));
			}
		} else if (currPhase == Second) {
			if (shortRangeCriteria(obj, play)) {
				setNewState(obj, getNextAttack(shortRangeQueue_PhaseTwo, shortRangeAttacks_PhaseTwo));
			}
			if (longRangeCriteria(obj, play)) {
				setNewState(obj, getNextAttack(longRangeQueue_PhaseTwo, longRangeAttacks_PhaseTwo));
			}
		}
	}
	
	bool shortRangeCriteria(jjOBJ@ obj, jjPLAYER@ play) {
		return play.xPos > (obj.xPos - 160) && play.xPos < (obj.xPos + 160) && isFacingPlayer(obj, play.playerID) && onGround(obj);
	}
	
	bool longRangeCriteria(jjOBJ@ obj, jjPLAYER@ play) {
		return (play.xPos > (obj.xPos + 160) || play.xPos < (obj.xPos - 160)) && isFacingPlayer(obj, play.playerID) && onGround(obj);
	}
	
	int xDistance(jjOBJ@ obj, jjPLAYER@ play) {
		return int((obj.xPos - play.xPos) / 32);
	}
	
	void moveIntoXPosition(jjOBJ@ obj, jjPLAYER@ play, float speed, int angle) {
		if (obj.xPos < int(play.xPos - 140) || obj.xPos > int(play.xPos + 140)) {
			obj.xSpeed = -speed * jjSin(angle);
		} else {
			obj.xSpeed = 0;
		}
	}
	void moveIntoYPosition(jjOBJ@ obj, jjPLAYER@ play, float speed, int angle) {
		if (obj.yPos < int(play.yPos - 88) || obj.yPos > int(play.yPos - 32)) {
			obj.ySpeed = -speed * jjCos(angle);
		} else {
			obj.ySpeed = 0;
		}
	}
	
	void onDraw(jjOBJ@ obj) {
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, -obj.direction, obj.justHit > 0? SPRITE::SINGLECOLOR : SPRITE::NORMAL, jjLocalPlayers[0].blink == 0? 15:32);
		if (shield) {
			jjDrawSprite(obj.xPos, obj.yPos, ANIM::MENU, 1, 8, obj.direction, SPRITE::ALPHAMAP, 33);
		}
	}
	
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bull, jjPLAYER@ play, int force) {
		if (bull !is null) {
			float multiplier = (bull.var[3] == 4 || bull.var[3] == 5 || bull.var[3] == 6)? 0.4f : 0.6f;
			if (bull.playerHandling == HANDLING::PLAYERBULLET && bull.creatorType == CREATOR::PLAYER) {
				
				if (deflect && onGround(obj)) {
					if (bull.xSpeed > 0) {
						if (obj.direction == -1) {
							bull.ricochet();
						} else {
							if (!immune) {
								obj.justHit = 5;
		
								if (bull.var[3] != 3) {
									bossHP -= (damageCalc(obj, bull, armor, play) > (bull.animSpeed * multiplier))? damageCalc(obj, bull, armor, play) : (bull.animSpeed * multiplier);
									armor += bull.animSpeed;
								}
			
								jjPARTICLE@ text = jjAddParticle(PARTICLE::STRING);
								/*if (text !is null) {
									text.xPos = obj.xPos;
									text.yPos = obj.yPos;
									text.ySpeed = -0.25;
									text.string.text = "" + (damageCalc(obj,bull,armor,play) > (bull.animSpeed * multiplier)? damageCalc(obj,bull,armor, play) : (bull.animSpeed * multiplier));
								}*/
							} 
							if ((bull.var[6] & 16) == 0 && bull.creatorType == CREATOR::PLAYER) {
								bull.state = STATE::EXPLODE;
							}
						}
					} else if (bull.xSpeed < 0) {
						if (obj.direction == 1) {
							bull.ricochet();
						} else {
							if (!immune) {
								obj.justHit = 5;
		
								if (bull.var[3] != 3) {
									bossHP -= (damageCalc(obj, bull, armor, play) > (bull.animSpeed * multiplier))? damageCalc(obj, bull, armor, play) : (bull.animSpeed * multiplier);
									armor += bull.animSpeed;
								}
			
								jjPARTICLE@ text = jjAddParticle(PARTICLE::STRING);
								/*if (text !is null) {
									text.xPos = obj.xPos;
									text.yPos = obj.yPos;
									text.ySpeed = -0.25;
									text.string.text = "" + (damageCalc(obj,bull,armor,play) > (bull.animSpeed * multiplier)? damageCalc(obj,bull,armor, play) : (bull.animSpeed * multiplier));
								}*/
							} 
							if ((bull.var[6] & 16) == 0 && bull.creatorType == CREATOR::PLAYER) {
								bull.state = STATE::EXPLODE;
							}
						}
					}
				} else {
					if (!immune) {
						obj.justHit = 5;
								
						if (bull.var[3] != 3) {
							bossHP -= (damageCalc(obj, bull, armor, play) > (bull.animSpeed * multiplier))? damageCalc(obj, bull, armor, play) : (bull.animSpeed * multiplier);
							armor += bull.animSpeed;
						}
			
						jjPARTICLE@ text = jjAddParticle(PARTICLE::STRING);
						/*if (text !is null) {
							text.xPos = obj.xPos;
							text.yPos = obj.yPos;
							text.ySpeed = -0.25;
							text.string.text = "" + (damageCalc(obj,bull,armor,play) > (bull.animSpeed * multiplier)? damageCalc(obj,bull,armor, play) : (bull.animSpeed * multiplier));
						}*/
					} 
					if ((bull.var[6] & 16) == 0 && bull.creatorType == CREATOR::PLAYER) {
						bull.state = STATE::EXPLODE;
					}
				}
			}
		} else if (play !is null && !passive) {
			play.hurt(1, false);
		}
		return true;
	}
}

float damageCalc(jjOBJ@ obj, jjOBJ@ bull, int armor, jjPLAYER@ play) {
	if (armor <= bull.animSpeed) {
		return bull.animSpeed * (play.blink == 0? 1:0.125);
	} else {
		return (bull.animSpeed - ((0.0125f * (bull.animSpeed*3)) * armor)) * (play.blink == 0? 1:0.125);
	}
}
	
bool isFacingPlayer(jjOBJ@ obj, int playerID) {
	if (((obj.xPos < (jjPlayers[playerID].xPos - 32) && obj.direction == 1) || (obj.xPos > (jjPlayers[playerID].xPos + 32) && obj.direction == -1)) && obj.yPos <= int(jjPlayers[playerID].yPos + 256) && obj.yPos >= int(jjPlayers[playerID].yPos - 256)) return true;
	return false;
}


bool onGround(jjOBJ@ obj) {
	return obj.yPos >= 149.25*32;
}

void lookAtPlayer(jjOBJ@ obj, jjPLAYER@ play) {
	if (play.xPos > int(obj.xPos+96)) obj.direction = 1;
	else if (play.xPos < int(obj.xPos-96)) obj.direction = -1;
}

bool directlyBelow(jjOBJ@ obj, jjPLAYER@ play) {
	return ((play.xPos >= (obj.xPos - 16)) && (play.xPos <= (obj.xPos + 16))) && play.yPos > obj.yPos;
}

bool throwSword = false;

int halfScreenX() {
	return int(jjSubscreenWidth / 2);
}

int halfScreenY() {
	return int(jjSubscreenHeight / 2);
}

class QueenIcicles : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::BULLET, false);
		obj.var[0] = int(atan2(-obj.ySpeed, obj.xSpeed) * (512.f * 0.318309886142228f));
		if (obj.state == STATE::EXPLODE) {
			obj.unfreeze(1);
			obj.delete();
		} else {
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[70], 0, 0, obj.var[0], 1, 1, SPRITE::NORMAL);
			
			if (obj.var[4] == 1) {
				obj.ySpeed += 0.15;
			}
			
			if (obj.var[4] == 2) {
				if (obj.ySpeed < 0) obj.ySpeed *= -1;
			}
		}
	}
}

class QueenBricks : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::BULLET);
		
		obj.determineCurAnim(ANIM::QUEEN, 4);
		obj.determineCurFrame();
		
		obj.xSpeed = obj.xAcc = 0;
		
		if (obj.state == STATE::EXPLODE) {
			jjSample(obj.xPos, obj.yPos, SOUND::COMMON_COLLAPS);
			obj.particlePixelExplosion(0);
			obj.delete();
		}
	}
}

class QueenBlast : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.determineCurAnim(ANIM::CUSTOM[69], 2);
		obj.determineCurFrame();
		
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.counter++;
		
		obj.yPos = 150*32;
		
		if (obj.counter % 7 == 0) {
			obj.frameID++;
		}
		
		if (uint(obj.counter) == obj.counterEnd || obj.xPos <= 451.5*32 || obj.xPos >= 482.5*32) {
			obj.delete();
		}
	}
	
	void onDraw(jjOBJ@ obj) {
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::SINGLEHUE, 32);
	}
}

class IceQueenSpirit : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.determineCurAnim(ANIM::QUEEN, 5);
		obj.determineCurFrame();
		
		obj.yPos = obj.yPos + obj.ySpeed;
		obj.counter++;
		
		if (obj.counter % 7 == 0) {
			obj.frameID++;
		}
		
		for (int i = 1; i < jjObjectCount; i++) {
			if (jjObjects[i].eventID == OBJECT::QUEEN && obj.yPos <= jjObjects[i].yPos + 4 && obj.yPos >= jjObjects[i].yPos - 4) {
				obj.delete();
			}
		}
	}
	
	void onDraw(jjOBJ@ obj) {
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, -obj.direction, SPRITE::TRANSLUCENTMAPPING, 0);
	}
}

class IceTrail : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.counter++;
		
		for (int i = 1; i < jjObjectCount; i++) {
			if (jjObjects[i].eventID == OBJECT::QUEEN && obj.doesCollide(jjObjects[i], true) && obj.var[1] == 1) {
				obj.delete();
			}
		}
		
		int playerID = obj.findNearestPlayer(1000);
		if (playerID > -1) {
			if (obj.doesCollide(jjPlayers[playerID], true)) jjPlayers[playerID].freeze(true);
		}
	
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		
		if (obj.counter == int(obj.counterEnd)) obj.delete();
		
		obj.determineCurAnim(ANIM::AMMO, 82);
		obj.determineCurFrame();
		if (jjGameTicks % 7 == 0) obj.frameID--;
		
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::FROZEN);
	}
}

class SwordProjectiles : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.determineCurAnim(ANIM::CUSTOM[69], 0);
		obj.determineCurFrame();
		
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		
		obj.counter++;
		
		if (obj.state == STATE::START) {
			
			if (obj.counter == 1) jjSample(obj.xPos, obj.yPos, SOUND::COMMON_ITEMTRE);
			
			obj.xSpeed = obj.ySpeed = 0;
			
			obj.playerHandling = HANDLING::PARTICLE;
			
			if (obj.counter >= 70) {
				obj.state = STATE::FLY;
				jjSample(obj.xPos, obj.yPos, SOUND::SCIENCE_PLOPKAOS);
			}
		}
		
		if (obj.state == STATE::FLY) {
			obj.playerHandling = HANDLING::ENEMYBULLET;
			
			switch (obj.var[0]) {
				case 0: obj.xSpeed = 8; obj.ySpeed = 0; break;
				case 1: obj.xSpeed = -8; obj.ySpeed = 0; break;
				case 2: obj.xSpeed = 0; obj.ySpeed = 8; break;
				case 3: obj.xSpeed = 0; obj.ySpeed = -8; break;
				default: obj.xSpeed = 8; obj.ySpeed = 0; break;
			}
			
			if (obj.counter >= 420) obj.delete();
		}
		
		switch (obj.var[0]) {
			case 0: obj.frameID = 14; break;
			case 1: obj.frameID = 38; break;
			case 2: obj.frameID = 26; break;
			case 3: obj.frameID = 50; break;
			default: obj.frameID = 14; break;
		}
	}
	void onDraw(jjOBJ@ obj) {
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, obj.state == STATE::START? SPRITE::NEONGLOW : SPRITE::NORMAL, 5);
	}
}


int objReturnAngle(jjOBJ@ obj, jjOBJ@ obj2) {
	return int(atan2(obj2.xPos - obj.xPos, obj2.yPos - obj.yPos) * (512 / PI));
}

class QueenSwordWeapon : jjBEHAVIORINTERFACE {
	int currAnim;
	bool freezeAnim;
	
	void onBehave(jjOBJ@ obj) {
		obj.determineCurAnim(ANIM::CUSTOM[69], currAnim);
		obj.determineCurFrame();
		
		if (jjGameTicks % 2 == 0 && !freezeAnim) {
			obj.frameID++;
		}
		
		int playerID = obj.findNearestPlayer(1000000);
		jjPLAYER@ play = jjPlayers[playerID];
		
		int attackAngle = int(atan2(obj.xPos - play.xPos - (play.xSpeed * 16), obj.yPos - play.yPos - (play.ySpeed * 16)) * (512 / PI));
		
		if (passive) obj.playerHandling == HANDLING::PARTICLE;
		else obj.playerHandling == HANDLING::ENEMYBULLET;
		
		if (currState >= DefeatTwo) {
			obj.particlePixelExplosion(31);
			obj.delete();
		}
		
		switch (currState) {
			case Intro:
				currAnim = 0;
				freezeAnim = true;
				obj.frameID = 0;
			break;
			case Run:
				currAnim = 1;
				freezeAnim = true;
				obj.frameID = 0;
			break;
			case Slash:
				currAnim = 0;
				freezeAnim = false;
				if (obj.counter % 35 == 0) obj.frameID = 0;
			break;
			case Spin:
				currAnim = 0;
				freezeAnim = false;
			break;
			case Charge:
				currAnim = 1;
				freezeAnim = true;
				obj.frameID = 0;
			break;
			case Bunnyhop:
				currAnim = 1;
				freezeAnim = true;
				obj.frameID = 0;
			break;
			case SpinJump:
				currAnim = 0;
				freezeAnim = false;
			break;
			case Stomp:
				currAnim = 0;
				freezeAnim = true;
				obj.frameID = 0;
			break;
			case Downward:
				currAnim = 0;
				freezeAnim = true;
				obj.frameID = 26;
			break;
			case Bounce:
				currAnim = 0;
				freezeAnim = true;
				obj.frameID = 26;
			break;
			case Defeat:
				currAnim = 1;
				freezeAnim = true;
				obj.frameID = 0;
			break;
			case Transform:
				currAnim = 0;
				freezeAnim = true;
				obj.frameID = 0;
			break;
			case Icicles:
				currAnim = 0;
				freezeAnim = true;
				obj.frameID = 0;
			break;
			case PrepareThrow:
				currAnim = 0;
				freezeAnim = false;
			break;
			case Throw:
				currAnim = 0;
				freezeAnim = false;
				if (obj.counter % 9 == 0) {
					jjOBJ@ iceball = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos + jjSin(obj.counter*20)), int(obj.yPos + jjCos(obj.counter*20)), obj.objectID, CREATOR::OBJECT)];     
					jjSample(iceball.xPos, iceball.yPos, SOUND::AMMO_ICEGUN, 0, 0);
					iceball.behavior = IceCloud;
					iceball.curAnim = iceball.determineCurAnim(ANIM::AMMO, 82, true);
					iceball.frameID=5;
					iceball.determineCurFrame();
					iceball.counterEnd=20;
					iceball.freeze = 210;
					iceball.state = STATE::FLY;
					iceball.playerHandling = HANDLING::PARTICLE;
					iceball.lightType = LIGHT::NONE;
					iceball.xSpeed = ((jjSin(obj.counter*20)*6) * obj.direction) + obj.xSpeed;
					iceball.ySpeed = (jjCos(obj.counter*20)*6);
					iceball.killAnim = jjObjectPresets[OBJECT::ICEBULLET].killAnim;
				}
			break;
			case MegaSlash:
				currAnim = 0;
				if (obj.counter < 49) {
					obj.frameID = 37;
					freezeAnim = true;
				} else {
					freezeAnim = false;
				}
			break;
			case Ultimate:
				currAnim = 0;
				freezeAnim = false;
			break;
			case DefeatTwo:
				currAnim = 1;
				freezeAnim = true;
				obj.frameID = 0;
			break;
			case Exit:
				currAnim = 1;
				freezeAnim = true;
				obj.frameID = 0;
			break;
		}
		
		if (currState == SpinJump) {
			if (obj.var[2] == 0) {
				obj.frameID = 10;
				obj.var[2] = 1;
			}
		} else {
			obj.var[2] = 0;
		}
		
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		if (!throwSword) obj.xSpeed = obj.ySpeed = 0;
		
		for (int i = 1; i < jjObjectCount; i++) {
			jjOBJ@ o = jjObjects[i];
			if (o.eventID == OBJECT::QUEEN) {
				if (currState != Icicles && currState != PrepareThrow && currState != Throw) {
					if (currState == Defeat || currState == DefeatTwo) {
						if (obj.var[1] == 0) {
							if (obj.yPos < 150*32) obj.yPos += 1;
							else if (obj.var[0] == 0) {
								jjSample(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::ROBOT_POLE);
								obj.var[0] = 1;
							}
						}
					} else {
						obj.xPos = o.xPos - (10 * o.direction);
						obj.yPos = o.yPos + 3;
					}
				} else {
					if (throwSword) {
						swordSample = jjSampleLooped(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::TUFBOSS_SWING, swordSample, 0, 0);
						if (obj.var[1] == 0) {
							obj.xSpeed = -7 * jjSin(attackAngle);
							obj.ySpeed = -7 * jjCos(attackAngle);
							obj.var[1] = 1;
						}
						if (obj.counter >= 90 && obj.var[1] == 1) {
							obj.xSpeed = -7 * jjSin(objReturnAngle(o, obj));
							obj.ySpeed = -7 * jjCos(objReturnAngle(o, obj));
							if (obj.doesCollide(o, true)) {
								throwSword = false;
								obj.var[1] = 0;
								o.special = 1;
							}
						}
					} else {
						obj.xPos = o.xPos - (27 * o.direction);
						obj.yPos = o.yPos + 4;
					}
				}
				obj.justHit = o.justHit;
				obj.direction = o.direction;
				obj.counter = o.counter;
				obj.energy = o.energy;
				obj.playerHandling = currState == Defeat? HANDLING::PARTICLE : HANDLING::ENEMYBULLET;
			}
		}
	}
	
	void onDraw(jjOBJ@ obj) {
		jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 0, 1*obj.direction, 1, obj.justHit > 0? SPRITE::SINGLECOLOR : currPhase == Second? SPRITE::MAPPING : SPRITE::NORMAL, obj.justHit > 0? 15 : 1, 3, 3);
		if (!freezeAnim) jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[69], currAnim, obj.frameID > 0? obj.frameID : obj.frameID-1, 0, 1*obj.direction, 1, currPhase == Second? SPRITE::TRANSLUCENTMAPPING : SPRITE::TRANSLUCENT, 1, 3, 3);
	}
}

void onPlayer(jjPLAYER@ play) {
	play.lightType = LIGHT::NONE;
	
	HH24::player(play);
	
	if (SMOKE_HH24::golemDeathCount == 2) {
		jjAlert("||ARRRGH!",false,STRING::MEDIUM);
		play.bossActivated = false;
		jjTriggers[2] = false;
		play.cameraUnfreeze(false);
		SMOKE_HH24::golemDeathCount = -1;
		jjMusicStop();
	}
	
	if (play.xPos > 231*32) reachedCastle = true;
	if (reachedCastle && jjGameTicks == 7) jjMusicLoad("jj2_symphoni_dexentrique.mo3");
	
	const int bounds = 24;
	
	if (play.xPos > ((jjLayerWidth[4]*32) - bounds) || play.xPos < bounds) {
		play.xPos = play.xPos < (bounds+1)? bounds : (jjLayerWidth[4]*32) - bounds;
		play.xSpeed = 0;
		play.specialMove = 0;
	}
	
	if (play.yPos < 8) play.yPos = 8;
	
	if (play.yPos >= 164*32 && play.xPos >= 497*32) {
		fallDownHole = true;
		play.xPos = 394*32;
		play.yPos = 164*32;
	}
	
	if (fallDownHole) {
		play.keyLeft = play.keyRight = play.keyUp = play.keyDown = play.keyFire = play.keyJump = play.keyRun = play.keySelect = false;
		play.idle = 0;
		play.invisibility = true;
		if (fallScale == 10) {
			jjSample(play.xPos, play.yPos, SOUND::BONUS_BONUS1, 42, 0);
			fallScale -= 0.05;
		} else {
			fallScale -= 0.05;
			fallAngle += 5;
			if (fallAngle > 1024) fallAngle = 0;
		}
	}
	
	switch (play.charCurr) {
		case CHAR::JAZZ: fallAnimSet = ANIM::JAZZ; break;
		case CHAR::SPAZ: fallAnimSet = ANIM::SPAZ; break;
		case CHAR::LORI: fallAnimSet = ANIM::LORI; break;
		default: fallAnimSet = ANIM::JAZZ; break;
	}
	
	if (fallScale <= 0) {
		fallScale = 0;
		HH24::endLevel();
	}
	bool found = false;
	for (int i = 0; i < jjObjectCount; i++) {
		const auto@ obj = jjObjects[i];
		if (cast<const jjBEHAVIORINTERFACE>(obj.behavior) is bubble) {
			float x = obj.xPos - play.xPos;
			float y = obj.yPos - play.yPos;
			if (x * x + y * y < obj.var[0] * obj.var[0]) {
				found = true;
				break;
			}
		}
	}
	jjSetWaterLevel(getBubble(play) !is null ? 0.f : jjLayerHeight[4] * 32 + 128.f, true);
}

void onPlayerInput(jjPLAYER@ player) {
	if (!player.keyFire) {
		gunJammedTimer[player.localPlayerID] = 0;
		return;
	}
	if (getBubble(player) !is null) {
		if (gunJammedTimer[player.localPlayerID] == 0) {
			jjSamplePriority(sound::dryFire);
			const auto@ frame = jjAnimFrames[player.curFrame];
			float x = player.xPos + (player.direction < 0 ? -1 : 1) * (frame.hotSpotX - frame.gunSpotX);
			float y = player.yPos + frame.hotSpotY - frame.gunSpotY;
			int id = jjAddObject(OBJECT::EXPLOSION, x, y);
			if (id > 0)
				jjObjects[id].determineCurAnim(ANIM::AMMO, 6);
		}
		player.keyFire = false;
		gunJammedTimer[player.localPlayerID] = 30;
		return;
	}
	if (gunJammedTimer[player.localPlayerID] > 0) {
		player.keyFire = false;
		gunJammedTimer[player.localPlayerID]--;
	}
}


int triggerCount = 0;

void onMain() {
	HH24::main();
	
	HH17::handleEnemyProjectiles();
	
	array<jjLAYER@> layers = jjLayerOrderGet();
	layers[0].hasTiles = layers[1].hasTiles = layers[2].hasTiles = layers[3].hasTiles = jjColorDepth == 16 && !jjLowDetail? true:false;
	
	jjLayers[8].spriteParam = fallDownHole? 3:9;
	jjLayers[8].textureStyle = fallDownHole? TEXTURE::TUNNEL : TEXTURE::WARPHORIZON;
	jjLayers[8].texture = fallDownHole? TEXTURE::CORRUPTEDSANCTUARY : TEXTURE::DIAMONDUSBETA;
	jjLayers[8].xAutoSpeed = fallDownHole? 0 : 0.15;
	
	if (fallDownHole) {
		for (uint l = 0; l < layers.length() - 1; l++) {
			layers[l].hasTiles = false;
		}
	}
	
	sample = jjSampleLooped(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::WIND_WIND2A, sample, jjLocalPlayers[0].lighting < 100? 12:40, 0);
	
	jjIsSnowing = !jjLowDetail && !fallDownHole;
	
	if (jjIsSnowing) {
		for (int i = 0; i < 1024; i++) {
			jjPARTICLE@ particle = jjParticles[i];
			if (particle.type == PARTICLE::FLOWER && particle !is null && !particle.isActive) {
				if (jjTileGet(7, int(particle.xPos/32), int(particle.yPos/32)) != 0 && particle.flower.size >= 63) {
					particle.type = PARTICLE::INACTIVE;
				}
			}
		}
	}
	
	array<jjOBJ@> bubbles;
	array<jjOBJ@> spikeBalls;
	for (int i = 0; i < jjObjectCount; i++) {
		auto@ obj = jjObjects[i];
		if (obj.isActive) {
			const auto@ behavior = cast<const jjBEHAVIORINTERFACE>(obj.behavior);
			if (behavior is bubble)
				bubbles.insertLast(@obj);
			else if (behavior is spikeBall && obj.state == STATE::ATTACK)
				spikeBalls.insertLast(@obj);
		}
	}
	for (uint i = 0; i < bubbles.length(); i++) {
		auto@ obj = bubbles[i];
		int radius = obj.var[bubble_size];
		for (uint j = 0; j < spikeBalls.length(); j++) {
			auto@ ball = spikeBalls[j];
			float x = ball.xPos - obj.xPos;
			float y = ball.yPos - obj.yPos;
			if (x * x + y * y < radius * radius)
				bubble.destroy(obj);
		}
	}
}

void CheckpointWrapper(jjOBJ@ obj) {
	if (obj.state == STATE::STOP) { //don't do anything anymore
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame);
	} else if (obj.state == STATE::DEACTIVATE) { //due to death
		obj.deactivate();
	} else {
		obj.behave(BEHAVIOR::CHECKPOINT);
		if (obj.state == STATE::DONE) { //triggered by the player hitting it
			obj.state = STATE::STOP;
			//save the current state of some properties
			for (uint i = 0; i < 32; ++i)
				SavedTriggers[i] = jjTriggers[i];
			//OPTIONAL: this loop makes checkpoints reusable, so only the most recent checkpoint you touched is ever active
			for (int i = jjObjectCount; --i > 0;) {
				jjOBJ@ obj2 = jjObjects[i];
				if (obj2.eventID == OBJECT::CHECKPOINT && i != obj.objectID && obj2.isActive) {
					obj2.state = STATE::SLEEP;
					obj2.var[0] = 0;
				}
			}
		}
	}
}

class GolemHP : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::WALKINGENEMY, false);
		for (int i = 0; i < jjLocalPlayerCount; i++) {
			if (jjLocalPlayers[i].bossActivated) {
				jjLocalPlayers[i].boss = obj.objectID;
			}
		}
		obj.energy = int(SMOKE_HH24::golemDuoHP / 2);
	}
}

class ColouredCrate : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::CRATE, false);
		int colour = jjParameterGet(int(obj.xPos/32), int(obj.yPos/32), 0, 5);
		int palShift = colour == 1? -32 : colour == 3? -48 : colour == 4? -24 : colour == 5? 24 : colour == 6? -40 : 0;
		
		if (obj.state == STATE::KILL) {
			for (uint i = 0; i < 32; ++i) {
				SavedTriggers[i] = jjTriggers[i];
			}
			jjEventSet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0);
		}
		
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::PALSHIFT, palShift);
	}
}

class Imitation : jjBEHAVIORINTERFACE {
	private uint8 eventID;
	private jjBEHAVIOR behavior;
	Imitation(uint8 realEventID, uint8 fakeEventID) {
		jjOBJ@ obj = jjObjectPresets[realEventID];
		eventID = obj.eventID;
		behavior = obj.behavior;
		obj.eventID = fakeEventID;
		obj.behavior = this;
	}
	void onBehave(jjOBJ@ obj) override {
		if (obj.state == STATE::DEACTIVATE)
			obj.eventID = eventID;
		obj.behave(behavior);
	}
}
class GiftBox : jjBEHAVIORINTERFACE {
	void destroy(jjOBJ@ obj) {
		jjSample(obj.xPos, obj.yPos, SOUND::COMMON_WOOD1);
		{
			int id = jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos);
			if (id != 0) {
				jjOBJ@ other = jjObjects[id];
				other.determineCurAnim(ANIM::PICKUPS, 4);
			}
		}
		for (int i = jjRandom() & 7 | 8; i-- != 0;) {
			int id = jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos);
			if (id != 0) {
				jjOBJ@ other = jjObjects[id];
				other.determineCurAnim(ANIM::PICKUPS, 93 + (jjRandom() & 1));
			}
		}
		obj.yPos -= 8.f;
		for (int i = obj.var[1]; i-- != 0;) {
			int id = jjAddObject(obj.var[0], obj.xPos, obj.yPos);
			if (id != 0) {
				jjOBJ@ other = jjObjects[id];
				if (other.playerHandling == HANDLING::PICKUP) {
					int angle = (jjRandom() & 255) + 128;
					other.xSpeed = jjCos(angle) * 5.f;
					other.ySpeed = jjSin(angle) * -3.f;
				} else if (other.playerHandling == HANDLING::SPECIAL) {
					other.deactivates = false;
				}
			}
		}
		obj.clearPlatform();
		obj.delete();
	}
	void onBehave(jjOBJ@ obj) override {
		switch (obj.state) {
			case STATE::START:
				{
					uint16 x = int(obj.xOrg) >>> 5;
					uint16 y = int(obj.yOrg) >>> 5;
					obj.var[0] = jjParameterGet(x, y, 0, 8);
					obj.var[1] = jjParameterGet(x, y, 8, 4);
					obj.curAnim += jjParameterGet(x, y, 12, 2);
					obj.determineCurFrame();
					obj.bulletHandling = HANDLING::DESTROYBULLET;
					obj.scriptedCollisions = true;
				}
				break;
			case STATE::FALL:
				obj.var[2] = 1;
				break;
			case STATE::FREEZE:
			case STATE::SLEEP:
				if (obj.var[2] != 0) {
					destroy(obj);
					return;
				}
		}
		obj.behave(BEHAVIOR::MONITOR);
	}
	bool onObjectHit(jjOBJ@, jjOBJ@, jjPLAYER@, int) {
		return true;
	}
	bool onIsSolid(jjOBJ@) {
		return true;
	}
}

void onFunction0(jjPLAYER@ play, bool offset) {
	if (SMOKE_HH24::golemDeathCount == 0 && !iceGolemFight) {
		iceGolemFight = true;
		jjMusicLoad("Battle05.s3m");
		if (!splitscreen) play.cameraFreeze(198.5*32,85*32,true,false);
		jjTriggers[2] = true;
		play.bossActivated = true;
		jjAlert("||WHO DARES ENTER OUR LAIR?", false, STRING::MEDIUM);
		jjAlert("||YOU SHALL PERISH!", false, STRING::MEDIUM);
		jjSamplePriority(SOUND::INTRO_MONSTER2);
		jjSamplePriority(SOUND::INTRO_MONSTER2);
		iceGolemFight = true;
	}
}

void onFunction1(jjPLAYER@ play, bool offset) {
	if (!reachedCastle) {
		reachedCastle = true;
		jjMusicLoad("jj2_symphoni_dexentrique.mo3");
	}
}

namespace HH24Enemies {
	namespace Private {
		void applyGenericEnemySettingsToPreset(jjOBJ@ preset) {
			preset.causesRicochet = false;
			preset.isBlastable = false;
			preset.triggersTNT = true;
			preset.isFreezable = true;
			preset.isTarget = true;
			preset.direction = 1;
			preset.freeze = 0;
			preset.state = STATE::START;
		}
	}

	jjOBJ@ MakeEventHH24Fencer(uint8 eventID) {
		jjAnimSets[ANIM::FENCER].load();
		for (uint i = 0; i < 2; i++) {
			jjANIMATION@ animFencer = jjAnimations[jjAnimSets[ANIM::FENCER] + i];
			for (uint j = 0; j < animFencer.frameCount; j++) {
				jjANIMFRAME@ frame = jjAnimFrames[animFencer + j];
				jjPIXELMAP sprite(frame);
				for (uint x = 0; x < sprite.width; ++x) {
					for (uint y = 0; y < sprite.height; ++y) {
						if (sprite[x,y] == 24 || sprite[x,y] == 25) sprite[x,y] = 77;
						if (sprite[x,y] == 26 || sprite[x,y] == 27) sprite[x,y] = 78;
						if (sprite[x,y] == 28 || sprite[x,y] == 29) sprite[x,y] = 79;
						if (sprite[x,y] == 30 || sprite[x,y] == 31) sprite[x,y] = 95;
						if (sprite[x,y] >= 48 && sprite[x,y] <= 55) sprite[x,y] += 40;
						if (sprite[x,y] >= 80 && sprite[x,y] <= 87) sprite[x,y] -= 24;
					}
				}
				sprite.save(frame);
			}
		}
		
		jjOBJ@ preset = jjObjectPresets[eventID];
		HH24Enemies::Private::applyGenericEnemySettingsToPreset(preset);

[preview ends here]