Downloads containing HH17_Level06.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Holiday Hare '17Featured Download ShadowGPW Single player 8.8 Download file

File preview

#include "MLLE-Include-1.6.asc" ///@MLLE-Generated
const bool MLLESetupSuccessful = MLLE::Setup();
#pragma require "HH17_Level06-MLLE-Data-2.j2l"
#pragma require "HH17_Level06-MLLE-Data-1.j2l"
#pragma require "HH17_Level06.j2l"
#include "HH17Enemies.asc"
#pragma require "Jazz1Nippius.j2t"
#pragma require "HolidaiusN.j2t"
#pragma require "CrysilisV3.j2t"
#pragma require "BioWinter.j2t"
#pragma require "HH17_lowind.wav"
#pragma require "HH17_caveamb.wav"
#pragma require "HH17_Glass1.wav"
#pragma require "HH17_Glass2.wav"
#pragma require "HH17_Glass3.wav"
#pragma require "HH17_Glass4.wav"
#pragma require "HH17_Null.wav"
#include "Jazz1Enemies v03.asc"
#pragma require "SExmas.j2a"
#pragma require "HH17_Icicle.j2a"
#include "kangaroo.asc"
#pragma require "kangaroo.asc"
#pragma offer "Boss.s3m"
#pragma offer "ckbattle.mo3"
#pragma offer "Xmas 1.s3m"

bool blizzard = true;

uint R = 210, G = 200, B = 220;
jjPAL pal;

bool refresh = false;
int bossActivateTime = 0;
int elapsed = 0;

enum queenPhases {None, PhaseOne, Transition, PhaseTwo, Dying, Complete};
uint currPhase = None;
int transitionTime = 0;

enum queenStates {Fall, Move, Stand, Scream, Stomp, Float}; 
uint currState = Fall;
int rand = 3;
int randcar = 20;

bool cutscene = false;
bool cutsceneComplete = false;
int cutsceneTime = 0;

class vector2i {
	int x, y;
}

array<vector2i> oneWays;
                                        
void onLevelLoad() {
	Jazz1::MakeEnemy(OBJECT::TUBETURTLE, Jazz1::Enemies::Nippius_SkatePen);
	Kangaroo::MakeEventJoey(OBJECT::FATCHICK, 0, 5, 4, 16, 14, 256);
	jjObjectPresets[OBJECT::FATCHICK].behavior = function(obj) {
		obj.behavior = JoeyWithTrail(64, 5);
		obj.behave();
	};
	
	HH17::replaceAllEnemies();

	jjTexturedBGTexture = TEXTURE::DIAMONDUSBETA;
	jjUseLayer8Speeds = true;
	
	jjObjectPresets[OBJECT::STEADYLIGHT].behavior = SnowEffect();
	jjObjectPresets[OBJECT::AMBIENTSOUND].behavior = AmbientSound();
	jjObjectPresets[OBJECT::AMBIENTSOUND].deactivates = false;
	
	jjObjectPresets[OBJECT::GREENSPRING].deactivates = false;
	jjObjectPresets[OBJECT::BLUESPRING].deactivates = false;
	
	jjObjectPresets[OBJECT::AIRBOARD].deactivates = false;
		
	jjObjectPresets[OBJECT::TUBETURTLE].energy = 1;
	jjObjectPresets[OBJECT::TUBETURTLE].points = 200;
	
	jjObjectPresets[OBJECT::FISH].behavior = FrozenFish();
	
	jjObjectPresets[OBJECT::QUEEN].behavior = IceQueen();
	jjObjectPresets[OBJECT::LIZARD].behavior = IceQueenPhaseTwo();
	jjObjectPresets[OBJECT::LIZARD].energy = 100;
	jjObjectPresets[OBJECT::LIZARD].age = 150;
	jjObjectPresets[OBJECT::LIZARD].points = 10000;
	jjObjectPresets[OBJECT::LIZARD].scriptedCollisions = true;
	jjObjectPresets[OBJECT::LIZARD].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[OBJECT::LIZARD].bulletHandling = HANDLING::DETECTBULLET;
	jjObjectPresets[OBJECT::LIZARD].isBlastable = false;
	jjObjectPresets[OBJECT::LIZARD].isTarget = true;
	
	jjObjectPresets[OBJECT::BUBBA].behavior = Bubba();
	jjObjectPresets[OBJECT::BUBBA].energy = 20;
	jjObjectPresets[OBJECT::TUFBOSS].behavior = TuffBoss();
	jjObjectPresets[OBJECT::TUFBOSS].energy = 30;
	
	jjObjectPresets[OBJECT::BUTTERFLY].behavior = HandleMovingPlatforms();
	jjObjectPresets[OBJECT::BUTTERFLY].playerHandling = HANDLING::PARTICLE;
	jjObjectPresets[OBJECT::BUTTERFLY].bulletHandling = HANDLING::IGNOREBULLET;
	jjObjectPresets[OBJECT::BUTTERFLY].isBlastable =
	jjObjectPresets[OBJECT::BUTTERFLY].triggersTNT =
	jjObjectPresets[OBJECT::BUTTERFLY].isTarget = false;
	
	jjObjectPresets[OBJECT::PINKPLATFORM].behavior = MovingPlatform();
	
	jjObjectPresets[OBJECT::STRAWBERRY].behavior = PurpleJazz();
	jjObjectPresets[OBJECT::STRAWBERRY].playerHandling = HANDLING::PARTICLE;
	jjObjectPresets[OBJECT::STRAWBERRY].bulletHandling = HANDLING::IGNOREBULLET;
	
	jjSampleLoad(SOUND::WIND_WIND2A, "HH17_lowind.wav");
	jjSampleLoad(SOUND::SCIENCE_PLOPKAOS, "HH17_caveamb.wav");
	jjSampleLoad(SOUND::COMMON_DAMPED1, "HH17_Glass1.wav");
	jjSampleLoad(SOUND::COMMON_COLLAPS, "HH17_Null.wav");
	jjSampleLoad(SOUND::SKELETON_BONE1, "HH17_Glass2.wav");
	jjSampleLoad(SOUND::SKELETON_BONE2, "HH17_Glass3.wav");
	jjSampleLoad(SOUND::SKELETON_BONE3, "HH17_Glass4.wav");
	
	jjObjectPresets[OBJECT::COLLAPSESCENERY].behavior = CollapsingIce;
	
	Imitation(OBJECT::TACO, OBJECT::MILK);
	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::CUSTOM[2]].load(0, "HH17_Icicle.j2a");
	jjAnimSets[ANIM::BRIDGE].load(1, "SExmas.j2a");
	
	jjWeapons[WEAPON::TNT].comesFromGunCrates = jjWeapons[WEAPON::GUN8].comesFromGunCrates = jjWeapons[WEAPON::GUN9].comesFromGunCrates = true;
	
	jjANIMATION@ animBrick = jjAnimations[jjAnimSets[ANIM::QUEEN] + 4];
	for (uint j = 0; j < animBrick.frameCount; j++) {
		jjANIMFRAME@ frame = jjAnimFrames[animBrick + j];
		jjPIXELMAP sprite(frame);
		for (uint x = 0; x < sprite.width; ++x) {
			for (uint y = 0; y < sprite.height; ++y) {
				if (sprite[x,y] != 254) sprite[x,y] = 0;
			}
		}
		sprite.save(frame);
	}
	
	jjAnimSets[ANIM::CUSTOM[0]].load(2, "SExmas.j2a");
	jjObjectPresets[OBJECT::ONEUPCRATE].behavior = GiftBox();
	jjObjectPresets[OBJECT::ONEUPCRATE].determineCurAnim(ANIM::CUSTOM[0], 0);
	
	jjObjectPresets[OBJECT::BIGBOX].behavior = LayerFiveBox();
	
	jjAnimSets[ANIM::SPIKEPLAT].load(4, "SExmas.j2a");
	jjObjectPresets[OBJECT::SPIKEPLATFORM].killAnim = jjObjectPresets[OBJECT::SPIKEPLATFORM].determineCurAnim(ANIM::SPIKEPLAT, 0) + 1;
}

void onLevelBegin() {
	for (int i = 0; i < jjLocalPlayerCount; i++) {
		jjLocalPlayers[i].activateBoss(true);
		jjLocalPlayers[i].bossActivated = true;
		bossActivateTime = 0;
	}
	
	jjANIMATION@ animKangaroo = jjAnimations[jjAnimSets[ANIM::CUSTOM[1]] + 0];
	for (uint j = 0; j < animKangaroo.frameCount; j++) {
		jjANIMFRAME@ frame = jjAnimFrames[animKangaroo + j];
		jjPIXELMAP sprite(frame);
		for (uint x = 0; x < sprite.width; ++x) {
			for (uint y = 0; y < sprite.height; ++y) {
				if (sprite[x,y] >= 40 && sprite[x,y] <= 47) sprite[x,y] -= 2;
				if (sprite[x,y] >= 48 && sprite[x,y] <= 55) sprite[x,y] += 16;
			}
		}
		sprite.save(frame);
	}
	
	for (uint i = 0; i < 3; i++) {
		jjANIMATION@ animDragon = jjAnimations[jjAnimSets[ANIM::DRAGON] + i];
		for (uint j = 0; j < animDragon.frameCount; j++) {
			jjANIMFRAME@ frame = jjAnimFrames[animDragon + j];
			jjPIXELMAP sprite(frame);
			for (uint x = 0; x < sprite.width; ++x) {
				for (uint y = 0; y < sprite.height; ++y) {
					if (sprite[x,y] >= 72 && sprite[x,y] <= 77) sprite[x,y] += 9;
					if (sprite[x,y] == 78) sprite[x,y] = 86;
					if (sprite[x,y] == 79) sprite[x,y] = 87;
				}
			}
			sprite.save(frame);
		}
	}
	
	if (jjGameConnection != GAME::LOCAL) jjAlert("|Warning: This level is not designed to be played online!");
}

void onLevelReload() {
	MLLE::Palette.apply();
	HH17::processEnemyColors();
	refresh = true;
	currState = Fall;
	
	for (int i = 0; i < jjLocalPlayerCount; i++) {
		jjLocalPlayers[i].activateBoss(true);
		jjLocalPlayers[i].bossActivated = true;
		bossActivateTime = 0;
	}
	
	array<jjLAYER@> layers = jjLayerOrderGet();
	for (int l = 0; l < 20; l++) {
		layers[l].hasTiles = true;
	}
	if (currPhase < Complete) {
		currPhase = None;
		jjTexturedBGTexture = TEXTURE::DIAMONDUSBETA;
		jjTexturedBGStyle = TEXTURE::WARPHORIZON;
		jjSetFadeColors(118,108,138);
		jjLayerXAutoSpeed[8] = 0.25;
		jjMusicLoad("lemonade-snowmelt.mo3", false);
	} else {
		jjMusicLoad("Xmas 1.s3m", false);
		jjPalette.gradient(0,125,255, 255,255,255);
		jjPalette.apply();
	}
}

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;
	}
}

const array<SOUND::Sample> CollapsingIceSounds = {
	SOUND::SKELETON_BONE1,
	SOUND::SKELETON_BONE2,
	SOUND::SKELETON_BONE3
};
void CollapsingIce(jjOBJ@ obj) {
	if (obj.state == STATE::EXTRA) {
		jjSample(obj.xPos, obj.yPos, CollapsingIceSounds[jjRandom()%3]);
		
		int SpikeX = int(obj.xPos) >>> 5;
		int SpikeY = (int(obj.yPos) >>> 5) - 1;
		if (jjEventGet(SpikeX, SpikeY) == AREA::HURT) {
			jjTileSet(4, SpikeX, SpikeY, 160);
			jjAddParticleTileExplosion(SpikeX, SpikeY, 515, true);
			jjSample(obj.xPos, obj.yPos - 32, CollapsingIceSounds[jjRandom()%3]);
		}
	}
	obj.behave(BEHAVIOR::DESTRUCTSCENERY);
}

bool inView(const jjOBJ@ obj) {
	for (int i = 0; i < jjLocalPlayerCount; i++) {
		const jjPLAYER@ player = jjLocalPlayers[i];
		if (obj.xPos > player.cameraX - 64 && obj.yPos > player.cameraY - 64 && obj.xPos < player.cameraX + jjSubscreenWidth + 64 && obj.yPos < player.cameraY + jjSubscreenHeight + 64)
			return true;
	}
	return false;
}

class SnowEffect : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::STEADYLIGHT);
		obj.deactivates = false;
		obj.light = blizzard && jjLocalPlayerCount == 1 && (jjLocalPlayers[0].lighting > 5 && jjLocalPlayers[0].lighting < 101)? 127:-127;
		obj.xPos = jjLocalPlayers[0].cameraX + 400;
		obj.yPos = jjLocalPlayers[0].cameraY + 300;
	}
}

class AmbientSound : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ sound) {
		sound.deactivates = false;
		sound.behave((jjLowDetail && jjGameTicks >= 2) || currPhase >= PhaseTwo? BEHAVIOR::INACTIVE : BEHAVIOR::AMBIENTSOUND, false);
		sound.deactivates = false;
		
		if (blizzard) { 
			if (sound.yOrg > 4480) loopSound(sound);
			else sound.yPos = jjLocalPlayers[0].yPos + 160;
		} else if (!blizzard) {
			if (sound.yOrg < 4480) loopSound(sound);
			else sound.yPos = jjLocalPlayers[0].yPos + 160;
		}
	}
}

void loopSound(jjOBJ@ sound) {
	sound.xPos = jjLocalPlayers[0].xPos;
	sound.yPos = jjLocalPlayers[0].yPos;
}

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 JoeyWithTrail : jjBEHAVIORINTERFACE {
	private int trailLength;
	private array<float> xPrev;
	private array<float> yPrev;
	private int index = 0;
	private uint8 color;
	JoeyWithTrail(int trailLength, uint8 color) {
		this.trailLength = trailLength;
		for (int i = 0; i < trailLength; i++) {
			xPrev.insertLast(-1e3f);
			yPrev.insertLast(-1e3f);
		}
		this.color = color;
	}
	void onBehave(jjOBJ@ obj) override {
		float xPos = obj.xPos;
		float yPos = obj.yPos;
		obj.behave(Kangaroo::Joey, false);
		for (int i = 0; i < trailLength; i++) {
			int j = (index + i) % trailLength;
			if (!closeTo(xPrev[j], obj.xPos) || !closeTo(yPrev[j], obj.yPos)) {
				float scale = 0.5f * i / trailLength;
				jjDrawResizedSprite(xPrev[j], yPrev[j], ANIM::AMMO, 10, 0, scale, scale, SPRITE::SINGLECOLOR, color);
			}
		}
		obj.draw();
		float xInc = (obj.xPos - xPos) / 4;
		float yInc = (obj.yPos - yPos) / 4;
		for (int i = 0; i < 4; i++) {
			xPrev[index] = xPos += xInc;
			yPrev[index] = yPos += yInc;
			index = (index + 1) % trailLength;
		}
	}
}

bool givePlayerPointsForObject(jjPLAYER@ player, jjOBJ@ obj) { //This will probably be made a jjOBJ method as part of the real JJ2+ API eventually, because it shows up all the time in the native code, but that hasn't happened yet, so here you go. Increases the player's jjPLAYER::score to match the object's jjOBJ::points, and creates a string particle with that number which flies up to the top left corner of the screen.
	if (player is null)
		return false;
	if (obj.points != 0 && (jjGameMode == GAME::SP || jjGameMode == GAME::COOP)) {
		player.score += obj.points;
		jjPARTICLE@ particle = jjAddParticle(PARTICLE::STRING);
		if (particle !is null) {
			particle.xPos = obj.xPos;
			particle.yPos = obj.yPos;
			particle.xSpeed = (-32768 - int(jjRandom() & 0x3FFF)) / 65536.f;
			particle.ySpeed = (-65536 - int(jjRandom() & 0x7FFF)) / 65536.f;
			particle.string.text = formatInt(obj.points);
		}
		obj.points = 0; //Ensures that JJ2 won't end up increasing the player's score TWICE, since a call to (the native version of) this function is actually part of the standard pickup-handling code and would otherwise therefore get you in trouble with the FiveUp onObjectHit code.
		return true;
	}
	return false;
}

void onPlayer(jjPLAYER@ play) {
	for (uint i = 0; i < oneWays.length(); i++) {
		jjEventSet(oneWays[i].x, oneWays[i].y, AREA::ONEWAY);
	}
	oneWays.resize(0);
	int px = int(play.xPos), py = int(play.yPos);
	bool masked;
	for (int i = -11 + int(play.ySpeed); i <= 14; i++) {
		if (masked = jjMaskedHLine(px - 14, 28, py + i))
			break;
	}
	if (!masked) {
		for (int i = 8; i <= 16; i += 8) {
			for (int j = 12; j <= 20; j += 8) {
				int x = (px + play.direction * j) >>> 5, y = (py + i) >>> 5;
				if (x >= 0 && y >= 0 && x < jjLayerWidth[4] && y < jjLayerHeight[4] && jjEventGet(x, y) == AREA::ONEWAY) {
					vector2i point;
					jjEventSet(point.x = x, point.y = y, 0);
					oneWays.insertLast(point);
				}
			}
		}
	}

	if (currPhase != PhaseTwo) play.boss = 0;
	if (play.bossActivated) bossActivateTime++;
	if (bossActivateTime >= 35 && currPhase != PhaseTwo) play.bossActivated = false;
	else play.bossActivated = true;

	if (currPhase <= PhaseOne) {
		play.lighting = blizzard && jjLocalPlayerCount == 1? (50 + int(jjSin(jjGameTicks)*5)) : jjLocalPlayerCount > 1? 100 : 60;
	} else {
		if (currPhase == Transition) {
			transitionTime++;
			if (jjGameTicks % 14 == 0 && transitionTime >= 70) {
				if (play.lighting > 0) play.lighting = play.lighting - 1;
			}
			
			if (transitionTime == 1 && jjDifficulty <= 1) {
				jjOBJ@ carrot = jjObjects[jjAddObject(OBJECT::FULLENERGY, 578*32, 16*32, 0, CREATOR::LEVEL)];
				carrot.state = STATE::FLOATFALL;
			}
			
			if (transitionTime == 70) {
				play.showText("@@@@@@@@@You feel yourself being swallowed@@by the raging blizzard...", STRING::MEDIUM);
			}
			
			if (transitionTime >= 490) {
				currPhase = PhaseTwo;
				jjMusicLoad("ckbattle.mo3");
				jjMusicPlay();
				blizzard = true;
				play.warpToID(250, true);
				play.cameraFreeze(642.5*32, 89*32, true, true);
			}
		} else {
			if (currPhase == PhaseTwo) {
				play.lighting = 30;
				transitionTime = 0;
			}
		}
		if (currPhase == Dying) {
			transitionTime++;
			if (play.lighting > 0) play.lighting = play.lighting - 1;
			if (transitionTime >= 350) {
				play.showText("@@@@@@@@@The blizzard subsided.", STRING::MEDIUM);
				currPhase = Complete;
				jjMusicLoad("Xmas 1.s3m", false);
				jjMusicPlay();
				jjTexturedBGTexture = TEXTURE::DIAMONDUSBETA;
				jjTexturedBGStyle = TEXTURE::WARPHORIZON;
				jjSetFadeColors(118,108,138);
				jjLayerXAutoSpeed[8] = 0.25;
				play.warpToID(251, true);
				jjPalette.gradient(0,125,255, 255,255,255);
				jjPalette.apply();
				jjTriggers[28] = true;
			}
		}
		if (currPhase == Complete) {
			if (blizzard) play.lighting = 110;
			else play.lighting = 60;
		}
	}
	play.lightType = LIGHT::NONE;
	jjEnforceLighting = currPhase == Transition || currPhase == Dying? LIGHT::COMPLETE : LIGHT::OPTIONAL;
	
	if (currPhase != Transition) {
		if (blizzard) {
			if (R < 210) R+=2;
			else if (R > 210) R-=2;
			if (G < 200) G+=2;
			else if (G > 200) G-=2;
			if (B < 220) B+=2;
			else if (B > 220) B-=2;
		} else {
			if (R < 20) R+=2;
			else if (R > 20) R-=2;
			if (G < 10) G+=2;
			else if (G > 10) G-=2;
			if (B < 60) B+=2;
			else if (B > 60) B-=2;
		}
	} else {
		if (R < 250) R++;
		else if (R > 250) R--;
		if (G < 250) G++;
		else if (G > 250) G--;
		if (B < 250) B++;
		else if (B > 250) B--;	
	}
	
	if (currPhase == Complete) {
		play.cameraUnfreeze(true);
	}
	
	jjSetDarknessColor(jjPALCOLOR(R,G,B));
	
	if (blizzard && jjLocalPlayerCount == 1 && currPhase != Complete) jjIsSnowing = true;
	else jjIsSnowing = false;
	
	if (refresh) {
		play.cameraFreeze(play.xOrg, play.yOrg, true, true);
		play.xPos = 6*32;
		play.yPos = 145*32;
		elapsed++;
		if (elapsed == 2) {
			play.cameraUnfreeze();
			play.xPos = int(play.xOrg + 16);
			play.yPos = play.yOrg;
			refresh = false;
			elapsed = 0;
		}
	}
	
	if (cutscene) {
		cutsceneTime++;
		
		play.keyLeft = play.keyRight = play.keyUp = play.keyDown = play.keyFire = play.keyJump = play.keyRun = false;
		play.idle = 0;
		play.xSpeed = 0;
		
		switch (cutsceneTime) {
			case 5:
			jjAlert("|||||Thank you for rescuing me!");
			break;
			case 215:
			jjAlert("|||||Slaying the Ice Queen seems to have ended this dreadful blizzard.");
			break;
			case 425:
			jjAlert("|||||I'm so glad you got here when you did, I probably would've frozen to death!");
			break;
			case 635:
			jjAlert("|||||You're probably looking for the stolen presents, right?");
			break;
			case 845:
			jjAlert("|||||Well, I hate to be the bearer of bad news, but they're not here.");
			break;
			case 1005:
			jjAlert("|||||I overheard that the Ice Queen gave them to her pet dragon to protect.");
			break;
			case 1265:
			jjAlert("|||||The dragon's lair is about 60 miles to the north of here, in the icy caverns");
			jjAlert("|||||beneath this high mountain range.");
			break;
			case 1475:
			jjAlert("|||||So... yeah. You'd better pay him a visit if you want them back...");
			break;
			case 1685:
			jjAlert("|||||I'd better go now, my tea's probably stone cold!");
			break;
			case 1895:
			jjAlert("|||||Oh, by the way, you can have this. I managed to smuggle it into my cell.");
			jjAlert("|||||Merry Christmas!  Now I'd better skiddadle...");
			break;
			case 2245:
			cutscene = false;
			cutsceneComplete = true;
			break;
			default: break;
		}
	}
}

void onMain() {
	for (int i = 0; i < 1024; i++) {
		jjPARTICLE@ particle = jjParticles[i];
		if (particle.type == PARTICLE::SNOW) {
			if (currPhase <=  PhaseOne) {
				if (particle.xSpeed < -1) particle.xSpeed = -8 + (jjSin(jjGameTicks)*4);
				else particle.xSpeed = -1;
				particle.ySpeed = 2 - jjSin(jjGameTicks);
			} else {
				particle.xSpeed = currPhase == Transition?  -16 : -8;
				particle.ySpeed = 2;
			}
		}
	}
	
	for (int i = 0; i < jjObjectCount; i++) {
		jjOBJ@ obj = jjObjects[i];
		if (obj.behavior == BEHAVIOR::BOUNCEONCE && obj.xSpeed == 0 && obj.ySpeed > 0) {
			QueenBoulder temp;
			jjOBJ@ boulder = jjObjects[jjAddObject(OBJECT::ROTATINGROCK, obj.xPos < 583*32? int(obj.xPos - (jjRandom()%150)) : 583*32, int(obj.yPos - 300), obj.objectID, CREATOR::OBJECT, jjVOIDFUNCOBJ(temp.onBehave))];
			boulder.state = STATE::FLY;
			boulder.ySpeed = 4;
			boulder.xSpeed = 4;
			obj.delete();
		}
		
		if (obj.eventID == OBJECT::TUBETURTLE) {
			obj.xSpeed = 3;
		}
		
		if (obj.eventID == OBJECT::TUFTURT) {
			if (obj.xOrg > 189*32 && obj.xOrg < 205*32) {
				obj.yPos = obj.yOrg;
				if ((obj.xPos > obj.xOrg + 80) || (obj.xPos < obj.xOrg - 80)) {
					obj.direction *= -1;
				}
			}
		}
	}
	
	jjPlayers[31].furSet(88, 32, 16, 16);
	
	HH17::handleEnemyProjectiles();
	
	if (jjGameConnection != GAME::LOCAL) jjTriggers[28] = true;
	
	array<jjLAYER@> layers = jjLayerOrderGet();
	
	if (currPhase == PhaseTwo) {
		for (int l = 0; l < 20; l++) {
			layers[l].hasTiles = false;
		}
		jjTexturedBGTexture = TEXTURE::XARGON;
		jjSetFadeColors(200,225,255);
		jjTexturedBGStyle = TEXTURE::TUNNEL;
		jjLayerXAutoSpeed[8] = 1;
	} else if (currPhase == Complete) {
		for (int l = 0; l < 20; l++) {
			layers[l].hasTiles = true;
		}
	}
	
	if (currPhase >= Transition && currPhase < Complete) jjTriggers[30] = true;
	else jjTriggers[30] = false;

	
	if (jjLocalPlayerCount < 2) {
		if (currPhase >= PhaseOne && currPhase < Complete) jjTriggers[31] = true;
		else jjTriggers[31] = false;
	}
}

class FrozenFish : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::FISH, false);
		jjDrawSprite(obj.xPos, obj.yPos, ANIM::FISH, 1, 0, obj.direction, SPRITE::FROZEN, 5, 5, 5);
	}
}

class LayerFiveBox : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::BIGOBJECT, false);
		jjDrawSpriteFromCurFrame(obj.xPos, int(obj.yPos + 2), obj.curFrame, obj.direction, obj.freeze == 0? SPRITE::NORMAL : SPRITE::FROZEN, 5, 5, 5);
	}
}

class IceQueen : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::QUEEN);
		switch (jjDifficulty) {
			case 0: obj.var[4] = 34; break;
			case 1: obj.var[4] = 24; break;
			case 2: obj.var[4] = 24; break;
			case 3: obj.var[4] = 14; break;
		}
		if (obj.state == STATE::ATTACK) {
			int playerID = obj.findNearestPlayer(50000);
			if (playerID > -1) {
				if (jjPlayers[playerID].ySpeed > -1) jjPlayers[playerID].xSpeed -= 1;
			} else {
				if (jjGameTicks % obj.var[4] == 0) {
					Icicle temp;
					jjOBJ@ icicle = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos + 320), int((obj.yPos + 16) - (jjRandom()%16 * 5)), obj.objectID, CREATOR::OBJECT, jjVOIDFUNCOBJ(temp.onBehave))];
					icicle.xSpeed = -8;
					icicle.direction = -1;
					icicle.counterEnd = 200;
					icicle.state = STATE::FLY;
					icicle.playerHandling = HANDLING::ENEMYBULLET;
					icicle.lightType = LIGHT::NONE;
				}
			}
			
			for (int i = 0; i < jjObjectCount; i++) {
				if (jjObjects[i].eventID == OBJECT::ROTATINGROCK) {
					jjObjects[i].state = STATE::FLY;
					jjObjects[i].xSpeed -= 1;
				}
			}

		}
		if (obj.yPos > 34*32) { 
			obj.particlePixelExplosion(2);
			obj.delete();
			if (jjLocalPlayerCount < 2) {
				jjMusicStop();
				for (int i = 0; i < jjLocalPlayerCount; i++) {
					givePlayerPointsForObject(jjLocalPlayers[i], obj);
					currPhase = Transition;
				}
			} else {
				currPhase = Complete;
				jjMusicLoad("Xmas 1.s3m", false);
				jjPalette.gradient(0,125,255, 255,255,255);
				jjPalette.apply();
			}
		}
	
		if (currPhase == Complete || jjGameConnection != GAME::LOCAL) obj.delete();
	}	
}

class IceQueenPhaseTwo : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.determineCurAnim(ANIM::QUEEN, obj.var[0]);
		obj.determineCurFrame();
		obj.draw();
		
		if (currState != Fall && obj.freeze == 0) obj.special++;
		
		if (currState != Move) {
			if (jjGameTicks % 7 == 0 && obj.freeze == 0) obj.frameID++;
		} else {
			if (jjGameTicks % 3 == 0 && obj.freeze == 0) obj.frameID--;
		}
		
		rand = obj.energy < 50? 4:3;
		switch (jjDifficulty) {
			case 0: randcar = 10; break;
			case 1: randcar = 25; break;
			case 2: randcar = 100; break;
			default: randcar = 100; break;
		}
		
		switch (currState) {
			case Fall:
				obj.var[0] = 5;
				facePlayer(obj);
		 		if (!jjMaskedHLine(int(obj.xPos - 12) , 24, int(obj.yPos + 24))) {
					obj.yPos += 2;
				} else {
					jjSample(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::COMMON_SPLUT, 48, 0);
					currState = Move;
				}
			break;
			case Move:
				obj.var[0] = 7;
				facePlayer(obj);
				if (!jjMaskedPixel(int(obj.xPos - (32 * obj.direction)), int(obj.yPos), 4) && obj.freeze == 0) {
					obj.xPos -= (3 * obj.direction);
				}
				if (obj.special >= 70) {
					if (jjRandom()%rand > 0 && jjRandom()%rand < 3) {
						currState = Stand;
						obj.special = 0;
						obj.frameID = 0;
					} else if (jjRandom()%rand == 0) {
						currState = Move;
						obj.special = 0;
						obj.frameID = 0;
					} else if (jjRandom()%rand == 3) {
						currState = Float;
						obj.special = 0;
						obj.frameID = 0;
					}
				}
			break;
			case Stand:
				obj.var[0] = 3;
				facePlayer(obj);
				if (obj.special >= 35) {
					switch(jjRandom()%2) {
						case 0: 
							currState = Scream; 
							obj.special = 0;
							obj.frameID = 0;
						break;
						case 1: 
							currState = Stomp;
							obj.special = 0;
							obj.frameID = 0;
						break;
					}
					facePlayer(obj);
				}
			break;
			case Scream:
				obj.var[0] = 0;
				if (obj.special == 14) jjSample(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::QUEEN_SCREAM, 48, 0);
				
				if (obj.special % 21 == 0 && obj.special >= 14) {
					Icicle temp;
					jjOBJ@ icicle = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos), int(obj.yPos), obj.objectID, CREATOR::OBJECT, jjVOIDFUNCOBJ(temp.onBehave))];
					icicle.xSpeed = -8 * obj.direction;
					icicle.ySpeed = -(2 + jjRandom()%4);
					icicle.direction = -obj.direction;
					icicle.counterEnd = 200;
					icicle.var[4] = 1;
					icicle.state = STATE::FLY;
					icicle.playerHandling = HANDLING::ENEMYBULLET;
					icicle.lightType = LIGHT::NONE;
				}
				
				if (obj.special >= 70) {
					randomQueenMove(obj);
					facePlayer(obj);
				}
			break;
			case Stomp:
				obj.var[0] = 6;
				facePlayer(obj);
				if (obj.special == 35) jjSample(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::COMMON_LAND1, 48, 0);
				if (obj.special == 50) jjSample(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, CollapsingIceSounds[jjRandom()%3]);
				if (jjRandom()%randcar < (randcar - 1)) {
					if (obj.special % 4 == 0 && obj.special >= 42) {
						Icicle temp;
						jjOBJ@ icicle = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, (20160 + jjRandom()%768), 80*32, obj.objectID, CREATOR::OBJECT, jjVOIDFUNCOBJ(temp.onBehave))];
						icicle.xSpeed = 0;
						icicle.ySpeed = 6;
						icicle.direction = 1;
						icicle.counterEnd = 200;
						icicle.state = STATE::FLY;
						icicle.playerHandling = HANDLING::ENEMYBULLET;
						icicle.lightType = LIGHT::NONE;
					}
				} else {
					if (obj.special % 4 == 0 && obj.special >= 42) {
						jjOBJ@ carrot = jjObjects[jjAddObject(OBJECT::CARROT, (20160 + jjRandom()%768), 80*32, obj.objectID, CREATOR::OBJECT)];
						carrot.state = STATE::FLOATFALL;
					}
				}
				if (obj.special >= 70) {
					randomQueenMove(obj);
				}
			break;
			case Float:
				obj.var[0] = 5;
				facePlayer(obj);
				if (obj.freeze == 0) {
					if (obj.xPos < 642.5*32) obj.xPos += 2;
					else if (obj.xPos > 642.5*32) obj.xPos -= 2;
					
					if (obj.yPos > 89*32) {
						obj.yPos -= 2;
						obj.special = 0;
					} else {
						if (obj.special % 7 == 0 && obj.special < 350 && (obj.xPos > 641*32 && obj.yPos < 643*32)) {
							Iceball temp;
							jjOBJ@ iceball = jjObjects[jjAddObject(OBJECT::BLASTERBULLET, int(obj.xPos + jjSin(jjGameTicks*20)), int(obj.yPos + jjCos(jjGameTicks*20)), obj.objectID, CREATOR::OBJECT, jjVOIDFUNCOBJ(temp.onBehave))];     
							jjSample(iceball.xPos, iceball.yPos, SOUND::AMMO_ICEGUN, 0, 0);
							iceball.counterEnd = 250;
							iceball.state = STATE::FLY;
							iceball.playerHandling = HANDLING::ENEMYBULLET;
							iceball.lightType = LIGHT::NONE;
							iceball.xSpeed = (jjSin(jjGameTicks*20)*6) * (jjRandom()%2 > 0? 1:-1);
							iceball.ySpeed = (jjCos(jjGameTicks*20)*6) * (jjRandom()%2 > 0? 1:-1);
							iceball.killAnim = jjObjectPresets[OBJECT::ICEBULLET].killAnim;
						}
						if (obj.special >= 350) {
							currState = Fall;
							obj.special = 0;
							obj.frameID = 0;
						}
					}
				}
			break;
		}
		
		if (jjLocalPlayers[0].bossActivated) {
			jjLocalPlayers[0].boss = obj.objectID;
		}
		
		if (obj.state == STATE::FREEZE) {
			obj.var[1] = obj.var[1] - 1;
			if (obj.var[1] == 0) {
				obj.unfreeze(1);
				obj.state = obj.oldState;
			}
		} else {
			obj.var[1] = 70;
		}
		
		obj.energy = int(obj.age / 1.5);
		
		if (obj.age < 1) {
			obj.particlePixelExplosion(0);
			obj.delete();
			givePlayerPointsForObject(jjLocalPlayers[0], obj);
			currPhase = Dying;
			jjMusicStop();
			jjLocalPlayers[0].bossActivated = false;
		}
		
		if (jjGameConnection != GAME::LOCAL || jjLocalPlayerCount > 1) obj.delete();
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bull, jjPLAYER@ play, int force) {
		if (bull !is null) {
			if (bull.playerHandling == HANDLING::PLAYERBULLET) {
				if ((currState == Move || currState == Stand) && bull.var[3] != 9) {
					if (bull.xSpeed > 0) {
						if (obj.direction == 1) bull.ricochet();
						else hurtByBullet(obj, bull);
					} else if (bull.xSpeed < 0) {
						if (obj.direction == -1) bull.ricochet();
						else hurtByBullet(obj, bull);
					}
				} else {
					hurtByBullet(obj, bull);
				}
			}
			if ((bull.var[6] & 16) == 0) {
				bull.state = STATE::EXPLODE;
			}
		} else if (play !is null) {
			play.hurt();
		}
		return true;
	}
}

void randomQueenMove(jjOBJ@ obj) {
	switch (jjRandom()%rand) {
		case 0: currState = Move; break;
		case 1: currState = Scream; break;
		case 2: currState = Stomp; break;
		case 3: currState = Float; break;
	}
	obj.special = 0;
	obj.frameID = 0;
}

void facePlayer(jjOBJ@ obj) {
	if (obj.freeze == 0) {
		if (jjLocalPlayers[0].xPos > int(obj.xPos + 48)) obj.direction = -1;
		else if (jjLocalPlayers[0].xPos < int(obj.xPos - 48)) obj.direction = 1;
	}
}

void hurtByBullet(jjOBJ@ obj, jjOBJ@ bull) {
	obj.age -= bull.animSpeed;
	obj.justHit = 5;
	if (obj.freeze > 0) {
		obj.unfreeze(1);
		obj.age -= bull.animSpeed;
	}
}

class QueenBoulder : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::ROTATINGROCK);
		obj.age++;
		
		for (int i = 0; i < jjObjectCount; i++) {
			if (jjObjects[i].eventID == OBJECT::QUEEN && obj.doesCollide(jjObjects[i], true)) {
				obj.xSpeed = -4;
				obj.ySpeed = -2;
			}
		}
		
		if (obj.age == 420) {
			obj.particlePixelExplosion(0);
			obj.clearPlatform();
			obj.delete();
		}
	}	
}

class Icicle : 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 {
			obj.xAcc = 0;
			obj.yAcc = 0;
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[2], 0, 0, obj.var[0], 1, 1, SPRITE::SINGLEHUE, 128);
			
			if (obj.var[4] == 1) {
				obj.ySpeed += 0.15;
			}
		}
	}
}

class Iceball : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::BULLET, obj.state == STATE::EXPLODE? true:false);
		obj.var[0] = int(atan2(-obj.ySpeed, obj.xSpeed) * (512.f * 0.318309886142228f));
		if (obj.state == STATE::FLY) {
			obj.xAcc = 0;
			obj.yAcc = 0;
			if (obj.ySpeed < 0) obj.ySpeed *= -1;
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::AMMO, 77, 6, obj.var[0], 0.8, 0.8, SPRITE::SINGLEHUE, 128);
		}
	}
}

class Bubba : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(!obj.isActive? BEHAVIOR::EXPLOSION2 : inView(obj)? BEHAVIOR::BUBBA : BEHAVIOR::BEE);
		if (obj.energy < 1) {
			obj.particlePixelExplosion(0);
			jjOBJ@ coin = jjObjects[jjAddObject(OBJECT::SILVERCOIN, obj.xPos, obj.yPos)];
			coin.state = STATE::FLOATFALL;
			for (int i = 0; i < jjLocalPlayerCount; i++) {
				givePlayerPointsForObject(jjLocalPlayers[i], obj);
			}
			obj.delete();
		}
	}	
}

class TuffBoss : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(!obj.isActive? BEHAVIOR::EXPLOSION2 : inView(obj)? BEHAVIOR::TUFBOSS : BEHAVIOR::WALKINGENEMY);
		if (obj.energy < 1) {
			obj.particlePixelExplosion(0);
			jjOBJ@ coin = jjObjects[jjAddObject(OBJECT::SILVERCOIN, obj.xPos, obj.yPos)];
			coin.state = STATE::FLOATFALL;
			for (int i = 0; i < jjLocalPlayerCount; i++) {
				givePlayerPointsForObject(jjLocalPlayers[i], obj);
			}
			obj.delete();
		}
	}	
}

class HandleMovingPlatforms : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(obj.freeze == 0? BEHAVIOR::BUTTERFLY : BEHAVIOR::WALKINGENEMY, false);
		obj.deactivates = false;
	}
}

class MovingPlatform : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::PLATFORM);
		obj.deactivates = false;
		if (jjLocalPlayerCount == 1 && jjLocalPlayers[0].health == 0) obj.delete();
		for (int i = 0; i < jjObjectCount; i++) {
			if (jjObjects[i].eventID == OBJECT::BUTTERFLY) {
				float dx = jjObjects[i].xPos - obj.xPos, dy = jjObjects[i].yPos - obj.yPos;
				if (dx * dx + dy * dy < 64 * 64) {
					obj.xOrg = jjObjects[i].xPos;
					obj.yOrg = jjObjects[i].yPos;
					obj.xPos = jjObjects[i].xPos;
					obj.yPos = jjObjects[i].yPos;
					jjObjects[i].freeze = obj.freeze;
				}
			}
		}
	}	
}

class PurpleJazz : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ pj) {
		pj.behave(BEHAVIOR::PICKUP, false);
		pj.direction = -1;
		pj.var[1] = jjIsTSF? 30:33;
		pj.var[2] = jjIsTSF? 55:70;
		jjDrawSprite(pj.xPos, pj.yPos, ANIM::JAZZ, pj.var[0] == 1? pj.var[2] : pj.var[1], pj.var[0] == 1? jjGameTicks >> 2 : 0, pj.direction, SPRITE::PLAYER, 31);
		if (cutsceneTime >= 2017) {
			pj.xPos -= 6;
			pj.var[0] = 1;
			if (pj.xPos < jjLocalPlayers[0].xPos) jjLocalPlayers[0].direction = -1;
		}
		if (cutsceneTime == 2060) {
			for (int i = 0; i < jjObjectCount; i++) {
				if (jjObjects[i].eventID == OBJECT::ONEUPCRATE && jjObjects[i].xOrg > 654*32) {
					jjObjects[i].xPos = pj.xPos;
					jjObjects[i].yPos = 28.75*32;
				}
			}
		}
		if (pj.xPos < 636*32) pj.delete();
	}
}

void onFunction0() {
	blizzard = true;
}

void onFunction1() {
	blizzard = false;
}

void onFunction2(jjPLAYER@ play) {
	if (jjGameConnection == GAME::LOCAL) {
		for (int i = 0; i < jjObjectCount; i++) {
			if (jjObjects[i].eventID == OBJECT::QUEEN) {
				if (currPhase == None) {
					play.showText("@@@@Who dares enter the Ice Queen's lair?@You shall perish, foolish rabbit!");
					jjMusicLoad("Boss.s3m", false);
					currPhase = PhaseOne;
				}
				play.cameraFreeze(577.5*32, jjLocalPlayerCount > 1? 30.5*32 : 27.5*32, true, false);
			}
		}
	} else {
		jjAlert("|No boss in online coop, sorry :(");
		jjNxt("HH17_Level07.j2l", true, false);
	}
}

void onFunction3() {
	if (!cutsceneComplete) cutscene = true;
}

void onFunction4() {
	if (cutsceneComplete) jjTriggers[29] = true;
}