Downloads containing AGenSP10Year.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: A Generic Single Player...Featured Download Blackraptor Single player 9.4 Download file

File preview

#pragma offer "1405.mp3"
#pragma offer "BogglyWoods.ogg"
#pragma offer "09 Dying City.ogg"
#pragma offer "AARmx-iSWM.ogg"
#pragma offer "Terrible Fate.ogg"
#pragma offer "112-le_gran_luxe.mp3"
#pragma require "Xargon Gloaming.j2t"

bool foundCheese = false;
bool metSal = false;

/*
To view the active object count for testing purposes only 

bool onDrawScore(jjPLAYER@ play, jjCANVAS@ screen) {
screen.drawString(0, 30, "" + jjObjectCount);
return false;
}
*/

void onPlayer(jjPLAYER@ play) {
if (play.fly > 1) {
jjOBJ@ obj = jjObjects[play.fly - 1];
if (obj.eventID == OBJECT::CHESHIRE2) {
if (play.keyLeft) obj.xPos -= 4;
else if (play.keyRight) obj.xPos += 4;
}
}
}

/* Thanks to Violet for the Chesire Cat pilot mode code above 
and Sal for the suggestion to use text events instead of triggers 
for the changes in the background music tracks */


void onLevelLoad() {
	jjAnimSets[ANIM::CUSTOM[0]].allocate(array<uint> = {1,1,1});
	
	jjANIMFRAME@ newFrame = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::CUSTOM[0]].firstAnim].firstFrame];
	jjANIMFRAME@ firstCheese = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::PICKUPS].firstAnim + 15].firstFrame];
	copySprite(firstCheese,newFrame,8);
	
	jjAnimSets[ANIM::GLOVE].load();
	jjANIMFRAME@ newFrame2 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::CUSTOM[0]].firstAnim+1].firstFrame];
	jjANIMFRAME@ glove = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::GLOVE].firstAnim].firstFrame];
	copySprite(glove,newFrame2,1);
	newFrame2.hotSpotY = newFrame2.hotSpotY-27;
	
	jjANIMFRAME@ newFrame3 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::CUSTOM[0]].firstAnim+2].firstFrame];
	copySprite(firstCheese,newFrame3,2);
	
	jjObjectPresets[OBJECT::XMASLIZARD].eventID = OBJECT::ROBOT;
	jjObjectPresets[OBJECT::XMASLIZARD].determineCurAnim(ANIM::ROBOT,15);
	jjObjectPresets[OBJECT::XMASLIZARD].determineCurFrame();
	jjObjectPresets[OBJECT::XMASLIZARD].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[OBJECT::XMASLIZARD].energy = 40;
	jjObjectPresets[OBJECT::XMASLIZARD].behavior = RobotWrapper;
	jjObjectPresets[OBJECT::FATCHICK].determineCurAnim(ANIM::TUFBOSS,5);
	jjObjectPresets[OBJECT::FATCHICK].determineCurFrame();
	jjObjectPresets[OBJECT::FATCHICK].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[OBJECT::FATCHICK].bulletHandling = HANDLING::DETECTBULLET;
	jjObjectPresets[OBJECT::FATCHICK].scriptedCollisions = true;
	jjObjectPresets[OBJECT::FATCHICK].energy = 30;
	jjObjectPresets[OBJECT::FATCHICK].points = 300;
	jjObjectPresets[OBJECT::XMASLIZARD].points = 450;
	jjObjectPresets[OBJECT::FATCHICK].behavior = TufferTurt();
	jjObjectPresets[OBJECT::XMASNORMTURTLE].determineCurAnim(ANIM::TUFBOSS,5);
	jjObjectPresets[OBJECT::XMASNORMTURTLE].determineCurFrame();
	jjObjectPresets[OBJECT::XMASNORMTURTLE].behavior = TuffestTurt;
	jjObjectPresets[OBJECT::XMASNORMTURTLE].energy = 50;
	jjObjectPresets[OBJECT::XMASNORMTURTLE].xSpeed = 1.1;
	jjObjectPresets[OBJECT::XMASNORMTURTLE].points = 500;
	jjObjectPresets[OBJECT::PINKPLATFORM].determineCurAnim(ANIM::JAZZ,3);
	jjObjectPresets[OBJECT::BEES].determineCurAnim(ANIM::GLOVE,0);
	jjObjectPresets[OBJECT::BEES].energy = 2;
	jjObjectPresets[OBJECT::BEES].points = 25;
	jjObjectPresets[OBJECT::BEES].behavior = TysonPunch;
	jjObjectPresets[OBJECT::FASTFIRE].behavior = FastFire;
	jjMusicLoad("neve.s3m");
	}
	
	
// I had to put this here because of a bug that wouldn't play the music after player death until it loaded in
// a different music file from the one that was last active. This way the level would always load "neve.s3m"
// moments before the text strings surrounding the save points would cause a switch to the desired area music.	
// "Neve.s3m" itself isn't affected since it's the default music in the level options so it always loads. Weird. 
void onLevelReload() {
	jjMusicLoad("neve.s3m");
	while(updatables.length > 0) {
		updatables.removeAt(0);
	}
	activated = false;
	
	foundCheese = false;
	metSal = false;
}
	

// Deletes every fast fire pickup in the map, including what's dropped by enemies. This part is a modification
// of a suggestion by Violet, I simplified it to the most basic form possible.   	
void FastFire(jjOBJ@ obj) {
		 obj.delete();
		}
	
	
void TysonPunch (jjOBJ@ obj) {
		obj.behave(BEHAVIOR::TUBETURTLE, false);
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::NORMAL,1);
		}
	
	/*
void TufferTurt(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::WALKINGENEMY, false);
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::FROZEN, 1);
}
*/

//Thanks to Violet for this Ice Turtle script, it's a lot more complex than what I originally wrote
class TufferTurt : jjBEHAVIORINTERFACE {
 void onBehave(jjOBJ@ obj) {
  obj.behave(BEHAVIOR::WALKINGENEMY, false);
  jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::FROZEN, 1);
 }
 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bull, jjPLAYER@ play, int force) {
if (bull is null)
   play.objectHit(obj, 0, HANDLING::ENEMY);
  else if (bull.var[3] == WEAPON::TOASTER) {
   obj.playerHandling = HANDLING::ENEMY;
   obj.bulletHandling = HANDLING::HURTBYBULLET;
   bull.objectHit(obj, HANDLING::ENEMY);
   obj.playerHandling = jjObjectPresets[obj.eventID].playerHandling;
   obj.bulletHandling = jjObjectPresets[obj.eventID].bulletHandling;
   obj.unfreeze( 0); //spray ice shards around
  } else {
   bull.state = STATE::EXPLODE;
  }
  return true;
 }
}

void TuffestTurt(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::WALKINGENEMY, false);
	jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::NORMAL,1);
}

	
void onFunction24(jjPLAYER@ player) {
	player.showText("@@@....Simon, are you there?");
}

void onFunction19(jjPLAYER@ player) {
	jjMusicLoad("1405.mp3");
	}
	
void onFunction20(jjPLAYER@ player) {
	jjMusicLoad("BogglyWoods.ogg");
	}

void onFunction21(jjPLAYER@ player) {
	jjMusicLoad("AARmx-iSWM.ogg");
	}
	
void onFunction22(jjPLAYER@ player) {
	jjMusicLoad("neve.s3m");
	}
void onFunction23(jjPLAYER@ player) {
	jjMusicLoad("09 Dying City.ogg");
	}

void onFunction25(jjPLAYER@ player) {
	player.showText("@@@@<- White Hell");
}

void onFunction36(jjPLAYER@ player) {
	jjMusicLoad("Terrible Fate.ogg");
	jjSetFadeColors (158, 0, 180); 
	jjTexturedBGTexture = TEXTURE::NORMAL;
	}
	
void onFunction26(jjPLAYER@ player) {
	player.showText("@@@@||A|||||||r|||||||e||||||| y|||||||o|||||||u||||||| l|||||||o|||||||s|||||||t||||||| i|||||||n||||||| t|||||||h|||||||e||||||| c|||||||o|||||||l|||||||d|||||||?@||||||| C|||||||a|||||||n|||||||'|||||||t||||||| f|||||||i|||||||n|||||||d||||||| yo|||||||u|||||||r||||||| w|||||||a|||||||y||||||| @B|||||||A|||||||C|||||||K|||||||?");
}
	

void onFunction27(jjPLAYER@ player) {
	player.showText("@@@@WARP to Starting Zone");		
	}
		
void onFunction28(jjPLAYER@ player) {
	player.showText("@@@@Welcome to the Easy Access Warp Room (tm)@Please select a destination and step on the nearby warp pad@All Warp Room pads in nearby zones now unlocked!@#(I should use these warps to find a key to unlock the exit path!)");		
	}
void onFunction29(jjPLAYER@ player) {
	player.showText("@@@@WARP to First Checkpoint");
}

void onFunction30(jjPLAYER@ player) {
	player.showText("@@@@WARP to Grand Reef Entrance");
	
}	

void onFunction31(jjPLAYER@ player) {
	player.showText("@@@@WARP to Deep Quartz Cave");
}

void onFunction32(jjPLAYER@ player) {
	player.showText("@@@@WARP to Overgrown Temple Bulwark");
}

void onFunction33(jjPLAYER@ player) {
	jjSetFadeColors (158, 0, 180); 
	}
void onFunction34(jjPLAYER@ player) {
	player.showText("#@@@@Finding the map too difficult?@Or seeking a greater challenge?@Try EASY or HARD mode for a different experience!@(Currently out of service for coop...)");
}	

void onFunction35(jjPLAYER@ player) {
	jjSetFadeColors (35, 0, 120); 
	jjPAL nightPal;
	nightPal.load("Xargon Gloaming.j2t");
	nightPal.apply();
	jjTexturedBGTexture = TEXTURE::DESOLATION;
	//jjTexturedBGUsed = true;
	jjObjectPresets[OBJECT::RAVEN].behavior=RavenBehavior;
	jjObjectPresets[OBJECT::RAVEN].energy = 5;
	jjTexturedBGStars = true;
	}

void onFunction37(jjPLAYER@ player) {
	player.showText("#@@@@You've met with a terrible fate, haven't you?");
}	

void onFunction38(jjPLAYER@ player) {
	player.showText("#@@@@Time to enjoy your cup of tea ;)@@@@@@@@@@@@@@@@@@@@@@     (Don't move!)");
}	
		
void RavenBehavior(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::RAVEN,false);
	jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::SINGLECOLOR, obj.justHit > 0? 24 : 6);	
	jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, 
SPRITE::NEONGLOW, 6);

}

void onFunction39(jjPLAYER@ player) {
	player.showText("#@@@@You found a SUPER SECRET!@Some solid tiles like the key locks and these clouds@can be affected by breaking certain trigger crates...");
}	

void onFunction40(jjPLAYER@ player) {
	player.showText("#@@@@You can reach some tricky places only with certain characters@or by using tricks like RF jumping and stomping on enemies@When jumping don't forget to tap or hold SHIFT for extra hops!");
}

void onFunction41(jjPLAYER@ player) {
	player.showText("@@@@I beheld through eyes not mine@And I felt with fear beyond reason@@This wondrous joy of the divine@Witnessing our abhorrent acts of treason@@Beneath its solemn shroud shifted myriad faces weeping@This I know from knowledge gained while I was sleeping");
}	

void onFunction42(jjPLAYER@ player) {
	player.showText("@@@@It's getting kinda chilly down here...");
}	

void onFunction43(jjPLAYER@ player) {
	player.showText("@@@@Hey, haven't seen another traveller in a while@I can't find a way to leave this place@I saw someone else here once who said they knew a way out@But they wouldn't tell me...");
}	

void onFunction44(jjPLAYER@ player) {
	if(!metSal) {
		player.showText("@@@@You there! Happen to be looking for the exit?@Maybe I can help you...@I'm looking for the World's Tastiest Cheese@Find it around here somewhere and then we'll talk.");
		metSal = true;
		createMagicCheese();
	} else {
		if(!foundCheese) {
			player.showText("@@@@This area is scary and dark, but I was safe here@At least until you triggered all my traps@So hurry up and find the cheese and we can both leave.");
		} else {
			player.showText("@@@@The ripe pungent odour, the multicoloured glow...@That's the World's Tastiest Cheese!@Finally I can unleash the monster that guards the Way Out@Let's meet again in the cave to the south and I'll show you");
			jjTriggers[8] = true;
		}
	}
}

void createMagicCheese() {
	//Array of possible spawn locations
	array<Point@> locations = {@Point(900,63),
							   //@Point(1018,97),
							   @Point(960,148),
							   @Point(834,148),
							   //@Point(834,109),
							   @Point(904,62),
							   @Point(1017,130),
							   @Point(1000,101),
							   @Point(847,170)};
							  
					
	int index = jjRandom() % locations.length;
	Point@ chosenPoint = locations[index];
	MagicCheese(chosenPoint.getXPos()*32,chosenPoint.getYPos()*32);
}

void onFunction45(jjPLAYER@ player) {
	
}	

void onFunction46(jjPLAYER@ player) {
	player.showText("@@@@Holy Cow! That must be the World's Tastiest Cheese!@I suppose you can exit now@");
}	

void onFunction47(jjPLAYER@ player) {
	player.showText("@@@@The shadows come out to play...@The Beast watches from the trees@Here you will soon stay@Beneath the fallen leaves");
}	

void onFunction48(jjPLAYER@ player) {
	player.showText("@@@@WARP to White Hell@#(I think I came from here, but it's broken...)");
}	


void onFunction49(jjPLAYER@ player) {
	player.showText("@@@@In these dank depths of little visibility@Walls and halls of ice as far as I could see@Haunts of spirits and ice monstrosities@I show no fear as FIRE walks with me");
}	


//Thanks to Violet for the code to the Robot Boss behaviour, it's much less boring now :D
void RobotWrapper(jjOBJ@ obj) {
 if (obj.state == STATE::START) {
  obj.state = 5;
  obj.var[2] = 2;
  obj.var[1] = 131072;
  obj.putOnGround(true);
 } else if (obj.state == STATE::KILL) {
  obj.delete();
 } else if (obj.state == STATE::DEACTIVATE) {
  obj.eventID = OBJECT::XMASLIZARD;
  obj.deactivate();
 } else {
  jjPLAYER@ play = jjLocalPlayers[0];
  const float playerRealPosition = play.xPos;
  if ((obj.direction < 0) != (play.xPos < obj.xPos)) //player is behind the robot
   play.xPos = obj.xPos + obj.direction * 300; //pretend the robot is in front of the robot instead, but too distant to attack
  obj.behave(BEHAVIOR::ROBOT);
  play.xPos = playerRealPosition;
 }
}


//Sal's insanity starts here

//Globals
array<Object@> updatables();
bool activated = false;

//Hooks
void onMain() {
	for(uint i = 0; i < updatables.length; i++) {
		updatables[i].getBehavior().onBehave(null);
		updatables[i].getBehavior().onDraw(null);
	}
	if(activated == false && p.xPos > 986*32 && p.yPos > 161*32) {
		activated = true;
		jjMusicLoad("112-le_gran_luxe.mp3");
		updatables.insertLast(@CheeseMonsterEncounter());
	}
}

interface Behavior : jjBEHAVIORINTERFACE {
	void onDraw(jjOBJ@ obj);
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force);
	bool onGetActive(jjOBJ@ obj);
	void onSetActive(jjOBJ@ obj, bool setTo);
	bool onIsSolid(jjOBJ@ obj);
	bool onIsRFBullet(jjOBJ@ obj);
}

mixin class BaseBehavior : Behavior {
	void onBehave(jjOBJ@ obj) {}
	void onDraw(jjOBJ@ obj) {}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		return false;
	}
	bool onGetActive(jjOBJ@ obj) {
		if(obj !is null) 
			return obj.isActive; 
		return false;
	}
	void onSetActive(jjOBJ@ obj, bool setTo) {
		if(obj !is null && !setTo) 
			obj.delete();
	}
	bool onIsSolid(jjOBJ@ obj) {
		return false;
	}
	bool onIsRFBullet(jjOBJ@ obj) {
		return false;
	}
}

class Object {
	private jjOBJ@ object;
	private Behavior@ behavior;
	
	Object(jjOBJ@ object, Behavior@ behavior) {
		@this.object = @object;
		@this.behavior = @behavior;
		if(object !is null)
			object.behavior = behavior;
	}
	
	jjOBJ@ getObject() const { return object; }
	Behavior@ getBehavior() const { return behavior; }
}

//64=webs
//80,96
enum CheeseTentacleState {
	RISE,
	WAVE,
	STRAIGHTEN,
	PIVOT,
	FIRE,
	MELT
}

class CheeseTentacleBehavior : BaseBehavior {
	protected float xPos, yPos;
	protected array<CheeseComponent@> cheeses();
	private int health;
	private CheeseTentacleState state = STATE::RISE;
	private uint startTime = 0;
	private float angle = 0;
	private float deltaAngle = 0.05;
	private bool deleted = false;
	private int CHEESES_BELOW_GROUND = 6;
	private float pivotX, pivotY;
	
	private int CHEESE_DIST = 20;

	CheeseTentacleBehavior(float xPos, float yPos, uint length) {
		this.xPos = xPos;
		this.yPos = yPos + CHEESE_DIST*length;
		this.health = 5*length;
		for(uint i = 0; i < length; i++) {
			CheeseComponent@ comp = @CheeseComponent(this);
			cheeses.insertLast(comp);
			comp.getComponentBehavior().spriteMode = SPRITE::SINGLEHUE;
			comp.getComponentBehavior().spriteParam = 56;
		}
		for(uint i = 0; i < cheeses.length; i++)
			waveUpdateCheese(i);
		startTime = 0;
	}
	
	private void waveUpdateCheese(int index) {
		cheeses[index].getObject().xPos = this.xPos + (sin((jjGameTicks/15.0)+index)*15);
		cheeses[index].getObject().yPos = this.yPos - (CHEESE_DIST * index);
	}
	
	private void pivotUpdateCheese(int index) {
		cheeses[index].getObject().xPos = pivotX + (CHEESE_DIST * (index-CHEESES_BELOW_GROUND)) * sin(angle);
		cheeses[index].getObject().yPos = pivotY - (CHEESE_DIST * (index-CHEESES_BELOW_GROUND)) * cos(angle);
	}
	
	void onBehave(jjOBJ@ obj) {
		if(deleted) return;
		if(state == RISE) {
			yPos -= 2;
			startTime++;
			for(uint i = 0; i < cheeses.length; i++)
				waveUpdateCheese(i);
			if(startTime > CHEESE_DIST*(cheeses.length-(CHEESES_BELOW_GROUND-.5))/2) {
				startTime = 0;
				state = STATE::STRAIGHTEN;
			}
		} else if(state == WAVE) {
			for(uint i = 0; i < cheeses.length; i++)
				waveUpdateCheese(i);
			if(jjGameTicks - startTime > 4*70)
				state = STRAIGHTEN;
		} else if(state == STRAIGHTEN) {
			bool straight = true;
			for(uint i = 0; i < cheeses.length; i++) {
				if(cheeses[i].getObject().xPos > this.xPos - 1 &&
				   cheeses[i].getObject().xPos < this.xPos + 1)
					continue;
				waveUpdateCheese(i);
				straight = false;
			}
			if(straight) {
				state = PIVOT;
				startTime = jjGameTicks;
				pivotX = cheeses[CHEESES_BELOW_GROUND].getObject().xPos;
				pivotY = cheeses[CHEESES_BELOW_GROUND].getObject().yPos;
			}
		} else if(state == PIVOT) {
			angle += deltaAngle;
			if(angle > 3.14*.5)
				deltaAngle = -0.05;
			if(angle < -3.13*.5)
				deltaAngle = 0.05;
			if(angle > -0.1 && angle < 0 && deltaAngle > 0) {
				state = FIRE;
				startTime = jjGameTicks;
				angle = 0;
			}
			for(uint i = CHEESES_BELOW_GROUND; i < cheeses.length; i++)
				pivotUpdateCheese(i);
		} else if(state == FIRE) {
			if(cheeses.length == 0)
				deleted = true;
			if((jjGameTicks - startTime) % 35 == 0) {
				uint lastCheeseIndex = cheeses.length() - 1;
				CheeseComponent@ lastCheese = cheeses[lastCheeseIndex];
				float xSpeed = (p.xPos - lastCheese.getObject().xPos)/90;
				float ySpeed = (p.yPos - lastCheese.getObject().yPos)/90;
				float magnitude = sqrt(xSpeed*xSpeed + ySpeed*ySpeed);
				xSpeed = xSpeed/magnitude*4;
				ySpeed = ySpeed/magnitude*4;
				lastCheese.getObject().xSpeed = xSpeed;
				lastCheese.getObject().ySpeed = ySpeed;
				lastCheese.getComponentBehavior().spriteMode = SPRITE::SINGLEHUE;
				lastCheese.getComponentBehavior().spriteParam = 24;
				lastCheese.getComponentBehavior().deleteAfter(500);
				lastCheese.getComponentBehavior().deleteIfOutOfBounds();
				lastCheese.getObject().causesRicochet = true;
				cheeses.removeAt(lastCheeseIndex);
			}
		}
	}

	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if(player !is null)
			if(bullet !is null) {
				bullet.state = STATE::EXPLODE;
				handleHit();
			} else
				player.hurt(1);
		return true;
	}
	
	private void handleHit() {
		if(health-- % 4 == 0) {
			uint lastCheeseIndex = cheeses.length() - 1;
			Object@ lastCheese = cheeses[lastCheeseIndex];
			ShrinkingCheese(lastCheese.getObject().xPos, lastCheese.getObject().yPos);
			lastCheese.getObject().delete();
			cheeses.removeAt(lastCheeseIndex);
		}
		for(uint i = 0; i < cheeses.length; i++) {
			cheeses[i].getComponentBehavior().turnWhite();
		}
	}
}

class CheeseTentacle : Object {
	CheeseTentacle(float xPos, float yPos, uint length) {
		super(null, CheeseTentacleBehavior(xPos,yPos,length));
		updatables.insertLast(this);
	}
	CheeseTentacleBehavior@ getTentacleBehavior() {return cast<CheeseTentacleBehavior@>(getBehavior());}
}

class CheeseBehavior : BaseBehavior {
	private int white = 0;
	private bool deleteOutOfBounds = false;
	private int deleteAge = -1;
	private int age = -1;
	
	private float ySpeed;
	private float xSpeed;
	private bool xSpeedSet;
	private bool ySpeedSet;
	
	private float xPos;
	private float yPos;
	private bool xPosSet;
	private bool yPosSet;
	
	SPRITE::Mode spriteMode;
	int spriteParam;

	CheeseBehavior() {}
	
	void onBehave(jjOBJ@ obj) {
		obj.xPos += obj.xSpeed;
		obj.yPos += obj.ySpeed;
		obj.xSpeed += obj.xAcc;
		obj.ySpeed += obj.yAcc;
	
		if(xSpeedSet) {
			obj.xSpeed = xSpeed;
			xSpeedSet = false;
		}
		if(ySpeedSet) {
			obj.ySpeed = ySpeed;
			ySpeedSet = false;
		}
		if(xPosSet) {
			obj.xPos = xPos;
			xPosSet = false;
		}
		if(yPosSet) {
			obj.yPos = yPos;
			yPosSet = false;
		}
		if(deleteOutOfBounds) {
			if(obj.xPos > p.xPos + 50*32 || obj.xPos < p.xPos - 50*32) {
				obj.delete();
			}
		}
		if(age >= 0) {
			if(age++ > deleteAge) {
				obj.delete();
			}
		}
	}
	
	void onDraw(jjOBJ@ obj) {
		if(white > 0) {
			jjDrawSprite(obj.xPos,obj.yPos,ANIM::PICKUPS,15,0,obj.direction,SPRITE::SINGLECOLOR,15);
			white--;
		} else {
			jjDrawSprite(obj.xPos,obj.yPos,ANIM::PICKUPS,15,0,obj.direction,spriteMode,spriteParam);
		}
	}
	
	void turnWhite() {
		white = 5;
	}
	
	void setXSpeed(float xSpeed) {
		this.xSpeed = xSpeed;
		this.xSpeedSet = true;
	}
	
	void setYSpeed(float ySpeed) {
		this.ySpeed = ySpeed;
		this.ySpeedSet = true;
	}
	
	void setXPos(float xPos) {
		this.xPos = xPos;
		this.xPosSet = true;
	}
	
	void setYPos(float yPos) {
		this.yPos = yPos;
		this.yPosSet = true;
	}
	
	void deleteAfter(int numTicks) {
		deleteAge = numTicks;
		age = 0;
	}
	
	void deleteIfOutOfBounds(bool delete = true) {
		deleteOutOfBounds = delete;
	}
}

class CheeseComponentBehavior : CheeseBehavior {
	private Behavior@ parent;
	bool hittable;

	CheeseComponentBehavior(Behavior@ parent, bool hittable = true) {
		@this.parent = @parent;
		this.hittable = hittable;
	}
	
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if(!hittable) return true;
		if(player !is null && bullet is null) {
			player.hurt(1);
			return true;
		}
		return parent.onObjectHit(obj, bullet, player, force);
	}
}

class CheeseComponent : Object {
	CheeseComponent(Behavior@ parentBehavior, bool hittable = true) {
		super(jjObjects[jjAddObject(OBJECT::CHEESE, 0, 0, 0, CREATOR::OBJECT)],
			  CheeseComponentBehavior(parentBehavior, hittable));
		getObject().playerHandling = HANDLING::SPECIAL;
		getObject().scriptedCollisions = true;
	}
	
	CheeseComponentBehavior@ getComponentBehavior() {
		return cast<CheeseComponentBehavior@>(getBehavior());
	}
}

class ShrinkingCheeseBehavior : BaseBehavior {
	private float scale = 1.5;
	
	void onBehave(jjOBJ@ obj) {
		scale -= 0.05;
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		jjDrawResizedSprite(obj.xPos, obj.yPos, ANIM::PICKUPS, 15, 0, scale, scale,SPRITE::SINGLEHUE,56);
		if(scale <= 0) {
			obj.delete();
		}
	}
}

class ShrinkingCheese : Object {
	ShrinkingCheese(float xPos, float yPos) {
		super(@jjObjects[jjAddObject(OBJECT::CHEESE, 0, 0, 0, CREATOR::OBJECT)], ShrinkingCheeseBehavior());
		getObject().xPos = xPos;
		getObject().yPos = yPos;
	}
}

class Point {
	private float x;
	private float y;
	Point(float x, float y) {
		this.x = x;
		this.y = y;
	}
	float getXPos() const { return x; }
	float getYPos() const { return y; }
}

void nullBehavior(jjOBJ@ behave) {}
jjOBJ@ makeComponent() {
	jjOBJ@ obj = jjObjects[jjAddObject(OBJECT::BEES, 0, 0, 0, CREATOR::OBJECT)];
	obj.behavior = nullBehavior;
	return obj;
}

class CheeseMonsterBodyBehavior : BaseBehavior {
	array<Point@> outlinePoints = {
		Point(9,252),
		Point(12,68),
		Point(16,97),
		Point(33,278),
		Point(35,236),
		Point(38,37),
		Point(51,117),
		Point(66,300),
		Point(70,17),
		Point(70,220),
		Point(88,143),
		Point(99,205),
		Point(103,318),
		Point(110,5),
		Point(114,161),
		Point(137,178),
		Point(144,327),
		Point(148,2),
		Point(184,326),
		Point(196,6),
		Point(218,313),
		Point(244,297),
		Point(255,31),
		Point(275,278),
		Point(294,64),
		Point(306,247),
		Point(317,117),
		Point(321,167),
		Point(323,213)
	};
	
	array<jjOBJ@> cheeses();
	jjOBJ@ eyeCheese;
	array<jjOBJ@> tongueCheeses();
	int yBounce = 0;
	float angle = 0;
	float xPos, yPos;
	float xSpeed, ySpeed;
	int dissolve = 255;
	
	CheeseMonsterBodyBehavior(float xPos, float yPos) {
		this.xPos = xPos;
		this.yPos = yPos;
		for(uint i = 0; i < outlinePoints.length; i++) {
			jjOBJ@ cheese = makeComponent();
			cheese.playerHandling = HANDLING::ENEMY;
			cheeses.insertLast(cheese);
		}
		@eyeCheese = makeComponent();
		for(uint i = 0; i < 4; i++) {
			tongueCheeses.insertLast(makeComponent());
		}
	}
	
	void onBehave(jjOBJ@ obj) {
		int eyeOffsetX = 100;
		int eyeOffsetY = 40;
		int tongueOffsetX = 70;
		int tongueOffsetY = 105;
		int centerAdjustX = 100;
		int centerAdjustY = 100;
		xPos = xPos + xSpeed;
		yPos = yPos + ySpeed;
		yBounce = int(sin(jjGameTicks/20.0)*5);
		float xOrigin = xPos - centerAdjustX;
		float yOrigin = yPos - centerAdjustY;
		
		for(uint i = 0; i < outlinePoints.length; i++) {
			float xCoord = outlinePoints[i].getXPos() * 0.6;
			float yCoord = outlinePoints[i].getYPos() * 0.6;
			cheeses[i].xPos = xOrigin + (xCoord * cos(angle) + yCoord * sin(angle));
			cheeses[i].yPos = yOrigin + (yCoord * cos(angle) + xCoord * sin(angle)) + yBounce;
		}
		eyeCheese.xPos = xOrigin + eyeOffsetX * cos(angle) + eyeOffsetY * sin(angle);
		eyeCheese.yPos = yOrigin + eyeOffsetY * cos(angle) + eyeOffsetX * sin(angle) + yBounce;
		for(uint i = 0; i < tongueCheeses.length; i++) {
			float tongueX = tongueOffsetX - 15*i;
			float tongueY = tongueOffsetY;
			tongueCheeses[i].xPos = xOrigin + tongueX * cos(angle) + tongueY * sin(angle);
			tongueCheeses[i].yPos = yOrigin + tongueY * cos(angle) + tongueX * sin(angle) + yBounce;
		}
	}
	
	void cheeseBreath() {
		BreathCheese(tongueCheeses[0].xPos, tongueCheeses[0].yPos,20);
	}
	
	void onDraw(jjOBJ@ obj) {
		SPRITE::Mode mode;
		int param = 0;
		if(dissolve >= 255) {
			mode = SPRITE::NORMAL;
			param = 0;
		} else {
			mode = SPRITE::BLEND_DISSOLVE;
			param = 0;
		}
		for(uint i = 0; i < cheeses.length; i++) 
			jjDrawSprite(cheeses[i].xPos,cheeses[i].yPos,ANIM::PICKUPS,15,0,0,mode,dissolve);
		if(dissolve >= 255) {
			jjDrawSprite(eyeCheese.xPos,eyeCheese.yPos,ANIM::PICKUPS,15,0,0,SPRITE::SINGLEHUE,18);
			for(uint i = 0; i < tongueCheeses.length; i++) {
				jjDrawSprite(tongueCheeses[i].xPos,tongueCheeses[i].yPos+sin(jjGameTicks/10.0+i)*7,ANIM::PICKUPS,15,0,0,SPRITE::SINGLEHUE,24);
			}
		}
	}
	
	void setAngle(float angle) {
		this.angle = angle;
	}
}


class CheeseMonsterBody : Object {
	CheeseMonsterBody(float xPos, float yPos) {
		super(null, CheeseMonsterBodyBehavior(xPos,yPos));
		updatables.insertLast(this);
	}
	CheeseMonsterBodyBehavior@ getBodyBehavior() {return cast<CheeseMonsterBodyBehavior@>(getBehavior());}
}

class BreathCheese : Object {
	BreathCheese(float xPos, float yPos, float ySpeed) {
		super(@jjObjects[jjAddObject(OBJECT::CHEESE, 0, 0, 0, CREATOR::OBJECT)], BreathCheeseBehavior());
		getObject().xPos = xPos;
		getObject().yPos = yPos;
		getObject().ySpeed = ySpeed;
		getObject().playerHandling = HANDLING::ENEMYBULLET;
	}
}

class BreathCheeseBehavior : BaseBehavior {
	int age = 0;

	BreathCheeseBehavior() {}
	
	void onBehave(jjOBJ@ obj) {
		age++;
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		obj.xSpeed = obj.xSpeed + jjRandom() % 10 - 5;
		if(age > 32)
			obj.delete();
	}
	
	void onDraw(jjOBJ@ obj) {
		jjDrawSprite(obj.xPos,obj.yPos,ANIM::PICKUPS,15,0,0,SPRITE::SINGLEHUE,15);
	}
}

class CollapsingCheeseTentacleBehavior : BaseBehavior {
	protected float xPos, yPos;
	protected array<CheeseComponent@> cheeses();
	protected array<float> cheeseAngles();
	private int health;
	CheeseTentacleState state = STATE::RISE;
	private uint startTime = 0;
	private float deltaAngle = 0.05;
	private bool deleted = false;
	private int CHEESE_DIST = 20;
	private int direction;
	private int CHEESES_BELOW_GROUND = 6;
	private float pivotX;
	private float pivotY;

	CollapsingCheeseTentacleBehavior(float xPos, float yPos, uint length) {
		this.xPos = xPos;
		this.yPos = yPos + CHEESE_DIST*length;
		this.health = 5*length;
		this.direction = direction;
		for(uint i = 0; i < length; i++) {
			CheeseComponent@ comp = @CheeseComponent(this);
			comp.getObject().causesRicochet = true;
			comp.getComponentBehavior().spriteMode = SPRITE::SINGLEHUE;
			comp.getComponentBehavior().spriteParam = 40;
			cheeses.insertLast(comp);
			cheeseAngles.insertLast(0);
		}
		for(uint i = 0; i < cheeses.length; i++)
			waveUpdateCheese(i);
		startTime = 0;
	}
	
	private void waveUpdateCheese(int index) {
		cheeses[index].getObject().xPos = this.xPos + (sin((jjGameTicks/15.0)+index)*15);
		cheeses[index].getObject().yPos = this.yPos - (CHEESE_DIST * index);
	}
	
	private void pivotUpdateCheese(int index) {
		cheeses[index].getObject().xPos = pivotX + (CHEESE_DIST * (index-CHEESES_BELOW_GROUND)) * sin(cheeseAngles[index]);
		cheeses[index].getObject().yPos = pivotY - (CHEESE_DIST * (index-CHEESES_BELOW_GROUND)) * cos(cheeseAngles[index]);
	}
	
	void onBehave(jjOBJ@ obj) {
		if(deleted) return;
		if(state == RISE) {
			yPos -= 2;
			startTime++;
			for(uint i = 0; i < cheeses.length; i++)
				waveUpdateCheese(i);
			if(startTime > CHEESE_DIST*(cheeses.length-(CHEESES_BELOW_GROUND-.5))/2) {
				startTime = 0;
				state = STATE::STRAIGHTEN;
			}
		} else if(state == STRAIGHTEN) {
			bool straight = true;
			for(uint i = 0; i < cheeses.length; i++) {
				if(cheeses[i].getObject().xPos > this.xPos - 1 &&
				   cheeses[i].getObject().xPos < this.xPos + 1)
					continue;
				waveUpdateCheese(i);
				straight = false;
			}
			if(straight) {
				direction = xPos > p.xPos ? -1 : 1;
				state = PIVOT;
				startTime = jjGameTicks;
				pivotX = cheeses[CHEESES_BELOW_GROUND].getObject().xPos;
				pivotY = cheeses[CHEESES_BELOW_GROUND].getObject().yPos;
			}
		} else if(state == PIVOT) {
			bool stillMoving=false;
			int destIndex=-1;
			for(int index = CHEESES_BELOW_GROUND; index < cheeses.length; index++) {
				if(jjGameTicks - startTime > 5*(index-CHEESES_BELOW_GROUND)) {
					if(direction < 0 && cheeseAngles[index] > -3.1415/2) {
						cheeseAngles[index] -= 0.05;
						if(cheeseAngles[index] < -3.1415/2)
							destIndex=index;
						stillMoving=true;
					} else if(direction > 0 && cheeseAngles[index] < 3.1415/2) {
						cheeseAngles[index] += 0.05;
						if(cheeseAngles[index] > 3.1415/2)
							destIndex=index;
						stillMoving=true;
					}
				}
				
				pivotUpdateCheese(index);
				if(destIndex > -1 && destIndex % 3 == 0) {
					int xTile = cheeses[destIndex].getObject().xPos/32;
					int yTile = cheeses[destIndex].getObject().yPos/32 + 1;
					int tile = jjTileGet(4,xTile,yTile);
					jjAddParticleTileExplosion(xTile,yTile,tile,false);
				}
			}
			if(!stillMoving) {
				state = MELT;
				startTime = jjGameTicks;
			}			
		} else if(state == MELT) {
			for(int index = 0; index < cheeses.length; index++) {
				if(jjGameTicks - startTime == 5*index+1) {
					cheeses[index].getComponentBehavior().setYSpeed(5);
					cheeses[index].getComponentBehavior().deleteAfter(35);
					cheeses[index].getComponentBehavior().deleteIfOutOfBounds();
					cheeses[index].getComponentBehavior().spriteMode = SPRITE::SINGLEHUE;
					cheeses[index].getComponentBehavior().spriteParam = 24;
				}
			}
		}
	}

	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		return true;
	}
}

class CollapsingCheeseTentacle : Object {
	CollapsingCheeseTentacle(float xPos, float yPos, uint length) {
		super(null, CollapsingCheeseTentacleBehavior(xPos,yPos,length));
		updatables.insertLast(this);
	}
	CollapsingCheeseTentacleBehavior@ getTentacleBehavior() {return cast<CollapsingCheeseTentacleBehavior@>(getBehavior());}
}

enum CheeseMonsterEncounterState {
	DELAY,
	MELTED_CHEESE,
	FALLING_HEAD,
	ROTATE,
	TRANSLATE,
	HOME,
	DISSOLVE,
	TEN_TICKLES,
	WORMS,
	FINAL
}

class CheeseMonsterEncounterBehavior : BaseBehavior {
	int startTime;
	CheeseMonsterEncounterState state;
	CheeseMonsterEncounterState transitionState;
	CheeseMonsterBody@ body;
	int duration;
	FinalCheese@ cheese;

	CheeseMonsterEncounterBehavior() {
		delay(140, MELTED_CHEESE);
	}
	
	void delay(int duration, CheeseMonsterEncounterState nextState) {
		resetTimer();
		state = DELAY;
		this.duration = duration;
		this.transitionState = nextState;
	}
	
	void resetTimer() {startTime = jjGameTicks;}
	
	void onBehave(jjOBJ@ obj) {
		int timePassed = jjGameTicks - startTime;
		if(state == DELAY) {
			if(timePassed > duration) {
				resetTimer();
				state = transitionState;
			}
		}
		if(state == MELTED_CHEESE) {
			for(uint i = 0; i < 4; i++) {
				if((timePassed-1)/(4*70.0) == i) {
					createMeltingTentacle();
				}
			}
			if((timePassed-1)/(4*70.0) == 4) {
				createTentacleValidPosition(p.xPos + 8*32);
				createTentacleValidPosition(p.xPos - 8*32);
			}
			if((timePassed-1)/(4*70.0) == 5) {
				delay(70,FALLING_HEAD);
			}
		} else if(state == FALLING_HEAD) {
			if(timePassed == 1) {
				@body = @CheeseMonsterBody(998*32+7*32, 160*32);
				body.getBodyBehavior().ySpeed = 8;
				body.getBodyBehavior().setAngle(3*3.14/2);
			}
			if(body.getBodyBehavior().yPos > 170*32) {
				delay(70,ROTATE);
				body.getBodyBehavior().ySpeed = 0;
				resetTimer();
			}
		} else if(state == ROTATE) {
			body.getBodyBehavior().ySpeed = 0;
			body.getBodyBehavior().angle = body.getBodyBehavior().angle + 0.05;
			if(body.getBodyBehavior().angle > 3*3.14/2 + 2*3.14) {
				state = TRANSLATE;
				resetTimer();
			}
		} else if(state == TRANSLATE) {
			float xSpeed = body.getBodyBehavior().xPos > p.xPos+7*32 ? -2 : 2;
			body.getBodyBehavior().xSpeed = xSpeed;
			body.getBodyBehavior().cheeseBreath();
			if(timePassed > 120) { 
			   resetTimer();
			   body.getBodyBehavior().xSpeed = -xSpeed;
			   state = HOME;
			}
		} else if(state == HOME) {
			body.getBodyBehavior().cheeseBreath();
			if(body.getBodyBehavior().xSpeed > 0 && body.getBodyBehavior().xPos > 998*32+7*32 || 
			   body.getBodyBehavior().xSpeed < 0 && body.getBodyBehavior().xPos < 998*32+7*32) {
				body.getBodyBehavior().xSpeed = 0;
				delay(100,DISSOLVE);
			}
		} else if(state == DISSOLVE) {
			if(body.getBodyBehavior().dissolve > 0)
				body.getBodyBehavior().dissolve = body.getBodyBehavior().dissolve - 5;
			else {
				delay(5, TEN_TICKLES);
			}
		} else if(state == TEN_TICKLES) {
			if(timePassed == 1) {
				float xTentacle;
				CheeseTentacle(998*32, 171*32,19);
			} else if(timePassed == 30*32) {
				CheeseTentacle(1005*32, 171*32,12);
				CheeseTentacle(992*32, 171*32,12);
			} else if(timePassed == 50*32) {
				state = WORMS;
				CheeseWorm(992*32,170*32+16);
				resetTimer();
			}
		} else if(state == WORMS) {
			if(timePassed >= 2000) {
				state = FINAL;
				resetTimer();
				@cheese = @FinalCheese(1020*32, 168*32);
			}
		} else if(state == FINAL) {
			
		}
	}
	
	void createMeltingTentacle(float xPos=-1.0) {
		if(xPos < 0) xPos = p.xPos;
		int dist = jjRandom() % 4 + 3;
		if(jjRandom() % 2 == 0) dist *= -1;
		int tentX = xPos + dist*32;
		createTentacleValidPosition(tentX);
	}
	
	void createTentacleValidPosition(float xPos=-1.0) {
		if(xPos < 987*32) xPos = 987*32;
		if(xPos > 1010*32) xPos = 1010*32;
		CollapsingCheeseTentacle(xPos, 171*32,19);
	}
}

class PickupBehavior : BaseBehavior {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::PICKUP,false);
	}
}

enum CheeseWormState {
	FALL,
	SQUISH,
	DEATH,
	DEAD
}

class CheeseMonsterEncounter : Object {
	CheeseMonsterEncounter() {super(null, CheeseMonsterEncounterBehavior());}
	CheeseMonsterEncounterBehavior@ getBehavior() {return cast<CheeseMonsterEncounterBehavior@>(getBehavior());}
}

class CheeseWormBehavior : BaseBehavior {
	float xPos;
	float yPos;
	float xSpeed;
	float ySpeed;
	array<jjOBJ@> cheeses;
	int midLength = 9;
	float squishFactor = 0;
	CheeseWormState state;
	int squishDirection = 1;
	int moveDirection = 1;
	int fallIndex = 0;
	int white = 0;
	int health = 60;

	CheeseWormBehavior(float xPos, float yPos) {
		this.xPos = xPos;
		this.yPos = yPos;
		for(uint i = 0; i < midLength + 2; i++) {
			jjOBJ@ obj = jjObjects[jjAddObject(OBJECT::CHEESE, 998*32, 160*32, 0, CREATOR::OBJECT)];
			obj.playerHandling = HANDLING::ENEMY;
			obj.behavior = PickupBehavior();
			obj.causesRicochet = true;
			cheeses.insertLast(obj);
		}
		state = FALL;
		moveDirection = p.xPos > xPos ? 1 : -1;
	}
	
	void onBehave(jjOBJ@ obj) {
		if(state == FALL) {
			if(jjGameTicks % 20 == 0) {
				if(fallIndex < cheeses.length) {
					int x = fallIndex - midLength/2-1;
					cheeses[fallIndex].xPos = xPos + (32-5*squishFactor)*x;
					cheeses[fallIndex].yPos = yPos - 32*8;
					cheeses[fallIndex].ySpeed = 10;
					fallIndex++;
				}
			}
			bool done = true;
			for(uint i = 0; i < cheeses.length; i++) {
				if(cheeses[i].yPos < yPos) {
					cheeses[i].yPos = cheeses[i].yPos + cheeses[i].ySpeed;
					done = false;
				}
				jjDrawSprite(cheeses[i].xPos,cheeses[i].yPos,ANIM::PICKUPS,15,0,0,SPRITE::SINGLEHUE,40);
			}
			if(done) {
				state = SQUISH;
				for(uint i = 0; i < cheeses.length; i++) {
					cheeses[i].behavior = CheeseWormComponentBehavior(this);
					cheeses[i].playerHandling = HANDLING::SPECIAL;
					cheeses[i].scriptedCollisions = true;
					cheeses[i].causesRicochet = false;
				}
			}
		} if(state == SQUISH) {
			squishFactor += squishDirection * 0.05;
			if(squishFactor < 0 || squishFactor > 5) {
				squishDirection *= -1;
			}
			if(squishFactor < 0) {
				moveDirection = p.xPos > xPos ? 1 : -1;
			}
			updateCheeses();
			if(health <= 0) {
				state = DEATH;
			}
		} else if(state == DEATH) {
			if(jjGameTicks % 3 == 0) {
				jjOBJ@ obj = jjObjects[jjAddObject(OBJECT::CHEESE, 0, 0, 0, CREATOR::OBJECT)];
				obj.xPos = xPos;
				obj.yPos = yPos;
				obj.ySpeed = - 10;
				int xSpeed = (jjRandom() % 10) - 5;
				obj.xSpeed = xSpeed;
				obj.playerHandling = HANDLING::ENEMYBULLET;
				obj.behavior = CheeseSlimeBehavior();
			}
			if(jjGameTicks % 15 == 0) {
				if(fallIndex >= 0) {
					fallIndex--;
					cheeses[fallIndex].ySpeed = 10;
				}
			}
			for(uint i = 0; i < cheeses.length; i++) {
				if(i >= fallIndex)
					cheeses[i].ySpeed = 15;
				cheeses[i].yPos = cheeses[i].yPos + cheeses[i].ySpeed;
				jjDrawSprite(cheeses[i].xPos,cheeses[i].yPos,ANIM::PICKUPS,15,0,1,SPRITE::SINGLEHUE,24);
			}
			if(fallIndex == 0) {
				state = DEAD;
				for(uint i = 0; i < cheeses.length(); i++) {
					cheeses[i].delete();
				}
			}
		}
	}
	
	void updateCheeses() {
		xPos += xSpeed;
		yPos += ySpeed;
		for(uint i = 0; i < cheeses.length; i++) {
			int x = i - midLength/2-1;
			cheeses[i].xPos = xPos + (32-5*squishFactor)*x;
			if(i > 0 && i < cheeses.length-1)
				cheeses[i].yPos = yPos + squishFactor*x*x-squishFactor*16;
			else
				cheeses[i].yPos = yPos;
		}
		if(squishDirection > 0)
			xSpeed = squishFactor/2.0 * moveDirection;
		else
			xSpeed = 0;
		for(uint i = 0; i < cheeses.length; i++) {
			if(white <= 0)
				jjDrawSprite(cheeses[i].xPos,cheeses[i].yPos,ANIM::PICKUPS,15,0,0,SPRITE::SINGLEHUE,40);
			else {
				jjDrawSprite(cheeses[i].xPos,cheeses[i].yPos,ANIM::PICKUPS,15,0,1,SPRITE::SINGLECOLOR,15);
				white--;
			}
		}
	}
	
	void hit() {
		white = 30;
		health--;
	}
}

class CheeseWorm : Object {
	CheeseWorm(float xPos, float yPos) {
		super(null, CheeseWormBehavior(xPos, yPos));
		updatables.insertLast(this);
	}
	CheeseWormBehavior@ getBehavior() {return cast<CheeseWormBehavior@>(getBehavior());}
}

class CheeseWormComponentBehavior : BaseBehavior {
	CheeseWormBehavior@ parent;

	CheeseWormComponentBehavior(CheeseWormBehavior@ parent) {
		@this.parent = @parent;
	}
		
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if(player !is null)
			if(bullet !is null) {
				bullet.state = STATE::EXPLODE;
				parent.hit();
			} else
				player.hurt(1);
		return true;
	}
}

class CheeseSlimeBehavior : BaseBehavior {
	CheeseSlimeBehavior() {}
	
	void onBehave(jjOBJ@ obj) {
		obj.ySpeed = obj.ySpeed + 0.1;
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		obj.age = obj.age + 1;
		if(obj.age > 600)
			obj.delete();
	}
	
	void onDraw(jjOBJ@ obj) {
		jjDrawSprite(obj.xPos,obj.yPos,ANIM::PICKUPS,15,0,0,SPRITE::SINGLEHUE,15);
	}
}

class CheeseFlyBehavior : BaseBehavior {
	private float xPos, yPos;
	private float xSpeed, ySpeed;
	private int health = 12;

	private array<CheeseComponent@> cheeses;
	private bool wingState = true;

	CheeseFlyBehavior(float xPos, float yPos) {
		this.xPos = xPos;
		this.yPos = yPos;
		for(uint i = 0; i < 4; i++) {
			CheeseComponent@ comp = @CheeseComponent(this);
			comp.getComponentBehavior().setXPos(xPos);
			comp.getComponentBehavior().setYPos(yPos);
			comp.getComponentBehavior().spriteMode = SPRITE::SINGLEHUE;
			comp.getComponentBehavior().spriteParam = 56;
			comp.getComponentBehavior().deleteIfOutOfBounds(true);
			cheeses.insertLast(comp);
		}
	}
	
	void onBehave(jjOBJ@ obj) {
		if(jjGameTicks % 5 == 0 && health > 0)
			wingState = !wingState;
			
		float xDiff = p.xPos - xPos;
		int xDir = xDiff/abs(xDiff);
		
		float yDiff = p.yPos - yPos;
		int yDir = yDiff/abs(yDiff);
		
		xSpeed = xSpeed + xDir * 0.1;
		ySpeed = ySpeed + yDir * 0.1;
		
		float maxSpeed = 2.5;
		
		if(xSpeed > maxSpeed) xSpeed = maxSpeed;
		if(xSpeed < -maxSpeed) xSpeed = -maxSpeed;
		if(ySpeed > maxSpeed) ySpeed = maxSpeed;
		if(ySpeed < -maxSpeed) ySpeed = -maxSpeed;
		
		xPos = xPos + xSpeed;
		yPos = yPos + ySpeed;
		
		cheeses[0].getComponentBehavior().setXPos(xPos);
		cheeses[0].getComponentBehavior().setYPos(yPos);
		cheeses[3].getComponentBehavior().setXPos(xPos);
		cheeses[3].getComponentBehavior().setYPos(yPos+16);
		
		float adjust = health;
		if(wingState) {
			cheeses[1].getComponentBehavior().setXPos(xPos+16+adjust);
			cheeses[1].getComponentBehavior().setYPos(yPos-16-adjust);
			cheeses[2].getComponentBehavior().setXPos(xPos-16-adjust);
			cheeses[2].getComponentBehavior().setYPos(yPos-16-adjust);
		} else {
			cheeses[1].getComponentBehavior().setXPos(xPos+16+adjust);
			cheeses[1].getComponentBehavior().setYPos(yPos-8-adjust);
			cheeses[2].getComponentBehavior().setXPos(xPos-16-adjust);
			cheeses[2].getComponentBehavior().setYPos(yPos-8-adjust);
		}
		if(health <= 0) {
			xSpeed = 0;
			ySpeed = 5;
			for(uint i = 0; i < 4; i++) {
				cheeses[i].getComponentBehavior().spriteMode = SPRITE::SINGLEHUE;
				cheeses[i].getComponentBehavior().spriteParam = 24;
			}
		}
	}
	
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if(bullet != null) {
			for(uint i = 0; i < cheeses.length; i++){
				cheeses[i].getComponentBehavior().turnWhite();
			}
			health--;
			bullet.delete();
		}
		return true;
	}
}

class CheeseFly : Object {
	CheeseFly(float xPos, float yPos) {
		super(null, CheeseFlyBehavior(xPos, yPos));
		updatables.insertLast(this);
	}
	CheeseFlyBehavior@ getBehavior() {return cast<CheeseFlyBehavior@>(getBehavior());}
}

class MagicCheeseBehavior : CheeseBehavior {
	private int increment = 1;

	MagicCheeseBehavior() {
		spriteMode = SPRITE::SINGLEHUE;
		spriteParam = 5;
	}
	
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::PICKUP,false);
		if(jjGameTicks % 5 == 0) {
			spriteParam = (spriteParam + 1) % 100;
			obj.light=obj.light+increment;
			if(obj.light > 30 || obj.light < 10)
				increment *= -1;
		}
	}
	
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if(bullet is null && player !is null) {
			foundCheese = true;
			obj.delete();
		}
		return true;
	}
}

class MagicCheese : Object {
	MagicCheese(float xPos, float yPos) {
		super(jjObjects[jjAddObject(OBJECT::CHEESE, xPos, yPos, 0, CREATOR::OBJECT)], @MagicCheeseBehavior());
		getObject().deactivates = false;
		getObject().lightType = 11;
		getObject().light = 25;
		getObject().scriptedCollisions = true;
		getObject().playerHandling = HANDLING::SPECIAL;
	}
	MagicCheeseBehavior@ getBehavior() {return cast<MagicCheeseBehavior@>(getBehavior());}
}

class FinalCheeseBodyBehavior : BaseBehavior {
	private int white = 0;
	private FinalCheeseBehavior@ parent;
	
	FinalCheeseBodyBehavior(FinalCheeseBehavior@ parent) {
		@this.parent = @parent;
	}

	void onBehave(jjOBJ@ obj) {
		obj.curFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[0]].firstAnim].firstFrame;
		obj.draw();
	}

	void onDraw(jjOBJ@ obj) {
		if(white > 0) {
			white--;
			jjDrawResizedSprite(parent.xPos, parent.yPos, ANIM::PICKUPS, 15, 0, obj.direction*8, 8,SPRITE::SINGLECOLOR,15);
		}
	}
	
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if(bullet !is null && player !is null) {
			white = 6;
			bullet.delete();
			return parent.onObjectHit(obj, bullet, player, force);
		}
		if(player !is null && bullet is null) {
			player.hurt(1);
			return true;
		}
		return true;
	}
}

enum FinalCheeseState {
	ENTER,
	TURN,
	MOVE_TO_HAT,
	LOWER_HAND,
	LIFT_HAT,
	FLY_HAT,
	FALL_HAT,
	SPAWN_HAT,
	WAIT,
	SPROING,
	RABBIT
}

class FinalCheeseBehavior : BaseBehavior {
	float xPos, yPos;
	float xSpeed, ySpeed;
	int health = 30;
	jjOBJ@ body;
	bool armState = false;
	int direction = -1;
	bool rotateGlove = false;
	bool manageGlove = true;
	FinalCheeseState state;
	bool manageHat = true;
	int gloveLowerY = yPos;
	float hatPosX, hatPosY;
	float hatSpeedX, hatSpeedY;
	float hatAngle = 0;
	int stacheAngle = 500;
	int stacheIncrement = 1;
	float gloveAngle = 0;
	int time;

	FinalCheeseBehavior(float xPos, float yPos) {
		@body = jjObjects[jjAddObject(OBJECT::CHEESE, xPos, yPos, 0, CREATOR::OBJECT)];
		body.direction = 1;
		body.scriptedCollisions = true;
		body.playerHandling = HANDLING::SPECIAL;
		body.behavior = @FinalCheeseBodyBehavior(this);
		body.deactivates = false;
		this.xPos = xPos;
		this.yPos = yPos;
		jjAnimSets[ANIM::GRASSPLAT].load();
		jjAnimSets[ANIM::JUNGLEPOLE].load();
		jjAnimSets[ANIM::DIAMPOLE].load();
		
		state = ENTER;
		setXSpeed(-4);
		rotateGlove = true;
	}
	
	void setXSpeed(float xSpeed) {
		this.xSpeed = xSpeed;
		this.body.xSpeed = xSpeed;
		if(xSpeed != 0) {
			this.direction = xSpeed / abs(xSpeed);
			this.body.direction = xSpeed / -abs(xSpeed);
		}
	}
	
	void onBehave(jjOBJ@ obj) {
		xPos = xPos + xSpeed;
		yPos = yPos + ySpeed;
		body.xPos = body.xPos + xSpeed;
		body.yPos = body.yPos + ySpeed;
		
		if(state == ENTER) {
			if(xPos < 983*32) {
				setXSpeed(2);
				state = TURN;
			}
		} else if(state == TURN) {
			if(xPos > 995*32) {
				setXSpeed(0);
				rotateGlove = false;
				manageGlove = false;
				state = MOVE_TO_HAT;
			}
		} else if(state == MOVE_TO_HAT) {
			if(gloveAngle < 0) {
				state = LOWER_HAND;
			}
		} else if(state == LOWER_HAND) {
			if(gloveLowerY > yPos + 40) {
				state = LIFT_HAT;
				manageHat = false;
			}
		} else if(state == LIFT_HAT) {
			if(gloveLowerY < yPos) {
				state = FLY_HAT;
				time = jjGameTicks;
			}
		} else if(state == FLY_HAT) {
			if(jjGameTicks > time + 800) {
				state = FALL_HAT;
			}
		}
	}
	
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if(bullet !is null && player !is null) {
			health--;
		}
		return true;
	}
	
	void onDraw(jjOBJ@ obj) {
		int feetAdjust = jjSin(jjGameTicks*xSpeed*10)*10;
		jjDrawResizedSprite(xPos+30+feetAdjust,yPos+80,ANIM::PICKUPS,33,0,direction*2,2);
		jjDrawResizedSprite(xPos-20-feetAdjust,yPos+80,ANIM::PICKUPS,33,0,direction*2,2);
		
		if(jjGameTicks % 2 == 0) {
			if(stacheAngle < 490 || stacheAngle > 510)
				stacheIncrement *= -1;
			stacheAngle += stacheIncrement;
		}
		
		if(manageHat) {
			if(direction < 0) {
				hatPosX = xPos;
				hatPosY = yPos-80;
			} else {
				hatPosX = xPos-15;
				hatPosY = yPos-80;
			}
			hatAngle = 0;
			hatSpeedX = 0;
			hatSpeedY = 0;
		}
		
		if(direction < 0) {
			jjDrawResizedSprite(xPos-10,yPos-30,ANIM::PINBALL,0,0,0.7,0.7,SPRITE::SINGLECOLOR,15);
			jjDrawResizedSprite(xPos-15,yPos-22,ANIM::PINBALL,0,0,0.4,0.4,SPRITE::SINGLECOLOR,0);
			jjDrawRotatedSprite(hatPosX,hatPosY,ANIM::HATTER,1,7,hatAngle,-direction*5,5);
			jjDrawRotatedSprite(xPos-25,yPos+40,ANIM::GRASSPLAT,0,0,stacheAngle,2,2,SPRITE::SINGLECOLOR,0);
			if(rotateGlove) {
				gloveAngle = gloveAngle+20;
				jjDrawRotatedSprite(xPos+40,yPos,ANIM::CUSTOM[0],1,0,gloveAngle,2,2);
			} else if(manageGlove) {
				float gloveIncrement = jjSin(jjGameTicks*10)*15;
				jjDrawRotatedSprite(xPos+80+gloveIncrement,yPos-30-gloveIncrement,ANIM::GLOVE,0,0,875,2,2);
			}
		} else {
			jjDrawResizedSprite(xPos+10,yPos-30,ANIM::PINBALL,0,0,0.7,0.7,SPRITE::SINGLECOLOR,15);
			jjDrawResizedSprite(xPos+15,yPos-22,ANIM::PINBALL,0,0,0.4,0.4,SPRITE::SINGLECOLOR,0);
			jjDrawRotatedSprite(hatPosX,hatPosY,ANIM::HATTER,1,7,hatAngle,-direction*5,5);
			jjDrawRotatedSprite(xPos+15,yPos+40,ANIM::GRASSPLAT,0,0,stacheAngle,-2,2,SPRITE::SINGLECOLOR,0);
			if(rotateGlove) {
				gloveAngle = gloveAngle+20;
				if(gloveAngle > 1024) gloveAngle -= 1024;
				jjDrawRotatedSprite(xPos-40,yPos,ANIM::CUSTOM[0],1,0,gloveAngle,2,2);
			} else if(manageGlove) {
				float gloveIncrement = -jjSin(jjGameTicks*10)*15;
				if(gloveAngle < -1024) gloveAngle += 1024;
				jjDrawRotatedSprite(xPos-80-gloveIncrement,yPos-30-gloveIncrement,ANIM::GLOVE,0,0,150,2,2);
			}
		}
		
		if(state == MOVE_TO_HAT) {
			if(gloveAngle > 0) {
				gloveAngle = gloveAngle-6;
			}
			jjDrawRotatedSprite(xPos-40,yPos,ANIM::CUSTOM[0],1,0,gloveAngle,2,2);
		} else if(state == LOWER_HAND) {
			jjDrawRotatedSprite(xPos-40,gloveLowerY++,ANIM::CUSTOM[0],1,0,gloveAngle,2,2);
		} else if(state == LIFT_HAT) {
			jjDrawRotatedSprite(xPos-40,gloveLowerY--,ANIM::CUSTOM[0],1,0,gloveAngle,2,2);
			jjDrawResizedSprite(hatPosX,hatPosY--,ANIM::HATTER,1,7,-direction*5,5);
		} else if(state == FLY_HAT) {
			if(!manageGlove ) {
				if(gloveAngle < 150) {
					gloveAngle = gloveAngle+6;
					jjDrawRotatedSprite(xPos-40,gloveLowerY,ANIM::CUSTOM[0],1,0,gloveAngle,2,2);
				} else {
					manageGlove = true;
				}
			}
			hatPosX = hatPosX + hatSpeedX;
			hatPosY = hatPosY + hatSpeedY;
			int diff;
			if(jjGameTicks < time + 650) {
				diff = p.xPos - hatPosX;
			} else {
				diff = 1000*32 - hatPosX;
			}
			if(diff != 0) {
				int dir = diff/abs(diff);
				hatSpeedX = dir*3;
			} else {
				hatSpeedX = 0;
			}
			if(jjGameTicks % 100 == 0) {
				CheeseBomb(hatPosX, hatPosY);
			}
		} else if(state == FALL_HAT) {
			hatAngle = hatAngle + 19;
			if(hatPosY++ > 170*32) {
				state = SPAWN_HAT;
				hatAngle = 512;
				time = jjGameTicks;
			}
		} else if(state == SPAWN_HAT) {
			if(time + 200 > jjGameTicks) {
				if(jjGameTicks % 3 == 0) {
					jjOBJ@ obj = jjObjects[jjAddObject(OBJECT::CHEESE, 0, 0, 0, CREATOR::OBJECT)];
					obj.xPos = hatPosX;
					obj.yPos = hatPosY;
					obj.ySpeed = - 10;
					int xSpeed = (jjRandom() % 10) - 5;
					obj.xSpeed = xSpeed;
					obj.playerHandling = HANDLING::ENEMYBULLET;
					obj.behavior = CheeseSlimeBehavior();
				}
			} else if(time + 400 < jjGameTicks) {
				Sal(hatPosX, hatPosY-10);
				state = RABBIT;
				time = jjGameTicks;
			} 
		} else if(state == RABBIT) {
			
		}
	}
}

class FinalCheese : Object {
	FinalCheese(float xPos, float yPos) {
		super(null, @FinalCheeseBehavior(xPos, yPos));
		updatables.insertLast(this);
	}
	FinalCheeseBehavior@ getFinalCheeseBehavior() {return cast<FinalCheeseBehavior@>(getBehavior());}
}

class CheeseBomb : Object {
	CheeseBomb(float xPos, float yPos) {
		super(jjObjects[jjAddObject(OBJECT::CHEESE, xPos, yPos, 0, CREATOR::OBJECT)], @CheeseBombBehavior());
		getObject().playerHandling = HANDLING::ENEMYBULLET;
		getObject().ySpeed = 3;
	}
	CheeseBombBehavior@ getCheeseBombBehavior() {return cast<CheeseBombBehavior@>(getBehavior());}
}

class CheeseBombBehavior : BaseBehavior {
	void onBehave(jjOBJ@ obj) {
		obj.curFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[0]].firstAnim+2].firstFrame;
		obj.draw();
		obj.yPos = obj.yPos + obj.ySpeed;
		if(obj.yPos > 170*32) {
			makeSlime(-2, obj);
			makeSlime(2, obj);
			obj.particlePixelExplosion(0);
			obj.delete();
		}		
	}
	
	void makeSlime(float xSpeed, jjOBJ@ obj) {
		jjOBJ@ slime = jjObjects[jjAddObject(OBJECT::CHEESE, 0, 0, 0, CREATOR::OBJECT)];
		slime.xPos = obj.xPos;
		slime.yPos = obj.yPos;
		slime.ySpeed = -5;
		slime.xSpeed = xSpeed;
		slime.playerHandling = HANDLING::ENEMYBULLET;
		slime.behavior = CheeseSlimeBehavior();
	}
}

class Sal : Object {
	Sal(float xPos, float yPos) {
		super(jjObjects[jjAddObject(OBJECT::CHEESE, xPos, yPos, 0, CREATOR::OBJECT)], @SalBehavior());
		getObject().playerHandling = HANDLING::SPECIAL;
		getObject().scriptedCollisions = true;
		getObject().ySpeed = -10;
		getObject().xSpeed = 0.8;
	}
	SalBehavior@ getSalBehavior() {return cast<SalBehavior@>(getBehavior());}
}

class SalBehavior : BaseBehavior {
	bool end = false;
	bool end2 = false;
	int time = -1;

	void onBehave(jjOBJ@ obj) {
		if(end == false)
			obj.ySpeed = obj.ySpeed + 0.2;
		if(obj.yPos > 170.3*32 && end == false) {
			obj.ySpeed = 0;
			obj.xSpeed = 0.5;
		}
		if(obj.xPos > 1004*32 && end == false) {
			obj.xSpeed = 0;
			end = true;
			p.showText("@@@My cheese monster is completed! @Thank you for your service. @Have some cheese on the house.");
			time = jjGameTicks;
		}
		obj.yPos = obj.yPos + obj.ySpeed;
		obj.xPos = obj.xPos + obj.xSpeed;
		jjPlayers[15].furSet(69,16,40,32);
		jjDrawSprite(obj.xPos,obj.yPos,ANIM::SPAZ,48,0,0,SPRITE::PLAYER,15);
		if(time > 0 && time + 370 < jjGameTicks && end2 == false) {
			end2 = true;
			jjNxt();
		}
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		return true;
	}
}

void copySprite(jjANIMFRAME@ frame, jjANIMFRAME@ save, int scale) {
	jjPIXELMAP oldFrame(frame);
	jjPIXELMAP newFrame(frame.width*scale,frame.height*scale);
	for(uint x = 0; x < frame.width; x++) {
		for(uint y = 0; y < frame.height; y++) {
			for(uint i = 0; i < scale; i++) {
				for(uint j = 0; j < scale; j++) {
					newFrame[x*scale+i,y*scale+j] = oldFrame[x,y];
				}
			}
		}
	}
	newFrame.save(save);
	save.hotSpotX = frame.hotSpotX*scale;
	save.hotSpotY = frame.hotSpotY*scale;
	save.coldSpotX = frame.coldSpotX*scale;
	save.coldSpotY = frame.coldSpotY*scale;
	save.gunSpotX = frame.gunSpotX*scale;
	save.gunSpotY = frame.gunSpotY*scale;
}