Downloads containing Jazz-otJ.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Jazz of the JungleFeatured Download Violet CLM Single player 8.9 Download file

File preview

const bool MLLESetupSuccessful = MLLE::Setup(); ///@MLLE-Generated
#include "MLLE-Include-1.5.asc" ///@MLLE-Generated
#pragma require "Aztec2.j2t" ///@MLLE-Generated
#pragma require "Amazont.j2t" ///@MLLE-Generated
#pragma require "Jazz-otJ.j2as" ///@MLLE-Generated

#include "Jazz1Enemies v05.asc"
#pragma require "FishFace.j2a"
#pragma require "JillYEAH.wav"
#include "ArtificialArrow.asc"

class PlayerX {
	PlayerX(){}
	bool Fish = false;
	bool Immobile = false;
	int NoJumping = 0;
	float WaterLevel = jjLayerHeight[4] * 32;
	int Apples = 2;
}
array<PlayerX> PlayersX(jjLocalPlayerCount);

const int KeyCurFrame = jjObjectPresets[147].curFrame;
void onLevelLoad() {
	///@Event 142=Bouncing Ball                          |-|Enemy      |Bounce|Ball
	Jazz1::MakeEnemy(142, Jazz1::Enemies::Battleships_BounceSpike).SetUsesJJ2StyleDeathAnimation(true).SetPointsFormula(Jazz1::PointsFormula::MultiplyByFive).SetDeathSound(SOUND::COMMON_METALHIT);
	///@Event 143=Flying Devil                        |-|Enemy      |Devil|Fly
	Recolor(Jazz1::MakeEnemy(143, Jazz1::Enemies::Holidaius_Devil).SetUsesJJ2StyleDeathAnimation(true).SetPointsFormula(Jazz1::PointsFormula::MultiplyByFive).SetDeathSound(SOUND::BUBBA_BUBBAEXPLO).GetFirstFrame(), 2, array<uint8> = {40,24, 56,24});
	///@Event 144=Snake                        |-|Enemy      |Snake
	Recolor(Jazz1::MakeEnemy(144, Jazz1::Enemies::Jungrock_JetSnake).SetUsesJJ2StyleDeathAnimation(true).SetPointsFormula(Jazz1::PointsFormula::MultiplyByFive).GetFirstFrame(), 3, array<uint8> = {56,79});
	///@Event 145=Slug                        |-|Enemy      |Slug
	Recolor(Jazz1::MakeEnemy(145, Jazz1::Enemies::Sluggion_Sluggi, false, 2, Resize::Method::Scale2x).SetUsesJJ2StyleDeathAnimation(true).SetPointsFormula(Jazz1::PointsFormula::MultiplyByFive).SetWalkingEnemyCliffReaction(Jazz1::CliffReaction::TurnAround).SetSpeed(0.4).SetDeathSound(SOUND::HATTER_SPLIN).GetFirstFrame(), 3, array<uint8> = {16,72, 56,72, 40,75, 24,32});
	
	DeactivateEnemyWrappers(array<uint8>={142,143,144,145});
	
	//if (jjAnimSets[ANIM::PLUS_SCENERY] == 0)
	//	jjAnimSets[ANIM::PLUS_SCENERY].load();
	///@Event 237=Beehive                        |-|Enemy      |Hive
	jjObjectPresets[OBJECT::BEEBOY].behavior = OhBeehive;
	
	///@Event 253=Pacman Ghost                        |-|Enemy      |Pacman|Ghost|Bird:c1
	jjObjectPresets[OBJECT::PACMANGHOST].behavior = PacmanGhost();
	jjObjectPresets[OBJECT::PACMANGHOST].scriptedCollisions = true;
	jjObjectPresets[OBJECT::PACMANGHOST].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[OBJECT::PACMANGHOST].bulletHandling = HANDLING::DETECTBULLET;
	
	jjObjectPresets[OBJECT::BLASTERBULLET].determineCurAnim(ANIM::AMMO, 49);
	jjObjectPresets[OBJECT::BLASTERBULLET].special = 0;
	jjObjectPresets[OBJECT::BLASTERBULLET].behavior = WeaponOne;
	jjWeapons[WEAPON::BLASTER].defaultSample = false;
	jjWeapons[WEAPON::BLASTER].replenishes = false;
	jjObjectPresets[OBJECT::BOUNCERBULLET].behavior = SpikeBall;
	jjObjectPresets[OBJECT::BOUNCERBULLET].determineCurAnim(ANIM::ROBOT, 0);
	jjObjectPresets[OBJECT::BOUNCERBULLET].special = 0;
	jjObjectPresets[OBJECT::BOUNCERBULLET].determineCurFrame();
	jjObjectPresets[OBJECT::BOUNCERBULLET].var[0] = 26;
	jjObjectPresets[OBJECT::BOUNCERBULLET].counterEnd /= 2;
	jjObjectPresets[OBJECT::BOUNCERBULLET].animSpeed = 3;
	jjWeapons[WEAPON::BOUNCER].defaultSample = false;
	jjWeapons[WEAPON::BLASTER].style = WEAPON::CAPPED;
	jjWeapons[WEAPON::BOUNCER].style = WEAPON::CAPPED;
	
	Recolor(jjAnimations[jjAnimSets[ANIM::AMMO] + 66], 9, array<uint8>={64, 40});
	Recolor(jjAnimations[jjAnimSets[ANIM::AMMO] + 49], 10, array<uint8>={16,24, 24,88});
	
	if (jjAnimSets[ANIM::FISH] == 0)
		jjAnimSets[ANIM::FISH].load();
	jjAnimSets[ANIM::CUSTOM[0]].load(0, "FishFace.j2a");
	for (uint i = 0; i < 2; ++i) {
		jjANIMATION@ fishAnim = jjAnimations[jjAnimSets[ANIM::DOG] + i] = jjAnimations[jjAnimSets[ANIM::FISH] + i];
		for (uint j = 0; j < fishAnim.frameCount; ++j) {
			jjANIMFRAME@ fishFrame = jjAnimFrames[fishAnim + j];
			fishFrame.coldSpotY = -fishFrame.height;
		}
	}
	jjObjectPresets[OBJECT::DOGGYDOGG].determineCurFrame();
	jjObjectPresets[OBJECT::DOGGYDOGG].behavior = function(o) { if (o.state == STATE::KILL) o.deactivate(); o.behave(BEHAVIOR::DOGGYDOGG); };
	
	///@Event 146=Morph Head                        |-|Morph      |Morph|Head|Character:{Rabbit,Frog,Bird,Fish}2|notcheckpoint:c1
	jjObjectPresets[146].behavior = MorphHead();
	jjObjectPresets[146].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[146].scriptedCollisions = true;
	
	for (int i = 0; i < 3; ++i) {
		jjPIXELMAP key(611 - i * 90);
		jjANIMFRAME@ frame = jjAnimFrames[KeyCurFrame - i];
		key.save(frame);
		frame.hotSpotX = frame.hotSpotY = -16;
	}
	///@Event 147=Key|_|Trigger      |Key||Type:{Key,Gem}1
	jjObjectPresets[147].behavior = Key();
	jjObjectPresets[147].scriptedCollisions = true;
	
	if (jjAnimSets[ANIM::DESTSCEN] == 0)
		jjAnimSets[ANIM::DESTSCEN].load();
	///@Event 154=Lock|_|Trigger      |Lock||Type:{Key,Gem,Right,Left}2|Height-1:2
	jjObjectPresets[154].behavior = Lock();
	jjObjectPresets[154].scriptedCollisions = true;
	
	jjAnimSets[ANIM::FENCER].allocate(array<uint>={0,0});;
	jjAnimations[jjAnimSets[ANIM::FENCER] + 0] = jjAnimations[jjAnimSets[ANIM::FROG] + 0];
	jjAnimations[jjAnimSets[ANIM::FENCER] + 1] = jjAnimations[jjAnimSets[ANIM::FROG] + 2];
	///@Event 155=Frog|-|Enemy      |Frog
	jjObjectPresets[155].behavior = BEHAVIOR::FENCER;
	jjObjectPresets[155].energy = 1;
	jjObjectPresets[155].playerHandling = HANDLING::ENEMY;
	jjObjectPresets[155].bulletHandling = HANDLING::HURTBYBULLET;
	jjObjectPresets[155].points = jjObjectPresets[OBJECT::FENCER].points;
	jjObjectPresets[155].determineCurAnim(ANIM::FENCER, 1);
	jjObjectPresets[155].determineCurFrame();
	jjObjectPresets[155].behavior = FrogFencer;
	
	jjObjectPresets[OBJECT::APPLE].behavior = Apple();
	jjObjectPresets[OBJECT::APPLE].scriptedCollisions = true;
	
	jjObjectPresets[OBJECT::CARROT].behavior =
	jjObjectPresets[OBJECT::FASTFIRE].behavior =
	jjObjectPresets[OBJECT::REDGEM].behavior =
	jjObjectPresets[OBJECT::GREENGEM].behavior =
	jjObjectPresets[OBJECT::BLUEGEM].behavior =
	BEHAVIOR::INACTIVE;
	
	jjCharacters[CHAR::JAZZ].groundJump = GROUND::CROUCH;
	jjCharacters[CHAR::SPAZ].groundJump = GROUND::CROUCH;
	if (jjIsTSF)
		jjCharacters[CHAR::LORI].groundJump = GROUND::CROUCH;
	jjCharacters[CHAR::BIRD2].canRun = true;
	jjCharacters[CHAR::SPAZ].airJump = AIR::HELICOPTER;
	
	GenerateWaterLayer();
	jjUseLayer8Speeds = true;
	jjTexturedBGStyle = TEXTURE::WARPHORIZON;
	jjTexturedBGUsed = true;
	jjTexturedBGTexture = TEXTURE::RANEFORUSV;
	jjSetFadeColors(166);
	
	{
		jjPIXELMAP vine(0,0,32,136);
		for (uint x = 0; x < 32; ++x)
			for (uint y = 0; y < 136; ++y)
				if (vine[x,y] == 0)
					vine[x,y] = 128;
		jjANIMFRAME@ vineFrame = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::VINE]] + 1];
		vine.save(vineFrame);
		vineFrame.hotSpotX = -16;
	}
	
	///@Event 34=Ammo                        |-|Ammo      |Ammo||Spike:c1
	jjObjectPresets[OBJECT::BOUNCERAMMO3].behavior = Ammo();
	jjObjectPresets[OBJECT::BOUNCERAMMO3].scriptedCollisions = true;
	
	jjSampleLoad(SOUND::COMMON_COIN, "JillYEAH.wav");
	
	onLevelReload();
	
	for (int x = jjLayerWidth[3]; --x >= 0;)
		for (int y = jjLayerHeight[3]; --y >= 0;) {
			const uint8 ev = jjEventGet(x,y);
			if (ev == AREA::WARPTARGET) {
				const uint targetID = jjParameterGet(x,y,0,8);
				if (targetID >= Targets.length)
					Targets.resize(targetID + 1);
				Targets[targetID] = array<int> = {x * 32 + 16, y * 32 + 16};
				//jjEventSet(x,y, 0);
			}
	}
	
	if (jjDifficulty > 0)
		jjHelpStrings[5] = jjHelpStrings[5] + "@@You can hold TAB at any time@for a hint to your next goal.";
}
//enemies:
///@Event 100=
///@Event 102=
///@Event 103=
///@Event 104=
///@Event 105=
///@Event 106=
///@Event 107=
///@Event 108=
///@Event 109=
///@Event 110=
///@Event 113=
///@Event 115=
///@Event 117=
///@Event 118=
///@Event 120=
///@Event 123=
///@Event 124=
///@Event 125=
///@Event 126=
///@Event 127=
///@Event 152=
///@Event 183=
///@Event 184=
///@Event 190=
///@Event 191=
///@Event 197=
///@Event 225=
///@Event 236=
///@Event 237=
///@Event 248=
///@Event 249=
///@Event 250=
///@Event 252=

void Recolor(uint frameID, uint frameCount, array<uint8> gradients) {
	while (frameCount-- != 0) {
		jjANIMFRAME@ frame = jjAnimFrames[frameID++];
		jjPIXELMAP image(frame);
		
		for (uint x = 0; x < frame.width; ++x)
			for (uint y = 0; y < frame.height; ++y)
				for (uint g = 0; g < gradients.length; g += 2)
					if (image[x,y] & ~7 == gradients[g]) {
						const uint8 replacement = gradients[g+1];
						//if (replacement < 112)
							image[x,y] = (image[x,y] & 7) + replacement;
						//else
						//	image[x,y] = ((image[x,y] & 7) << 1) + replacement;
						break;
					}
		
		image.save(frame);
	}
}

class DeactivateEnemyWrapper : jjBEHAVIORINTERFACE {
	Jazz1::Enemy@ enemy;
	DeactivateEnemyWrapper(Jazz1::Enemy@ e) { @enemy = @e; }
	void onBehave(jjOBJ@ obj) {
		enemy.onBehave(obj);
		if (obj.state == STATE::KILL && obj.creatorType == CREATOR::LEVEL) {
			//jjEventSet(int(obj.xOrg)/32, int(obj.yOrg)/32, obj.eventID); //shouldn't be needed
			jjParameterSet(int(obj.xOrg)/32, int(obj.yOrg)/32, -1, 1, 0); //deactivate
		}
	}
	void onDraw(jjOBJ@ obj) { enemy.onDraw(obj); }
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) { return enemy.onObjectHit(obj, bullet, player, force); }
}
void DeactivateEnemyWrappers(array<uint8> objectIDs) {
	for (uint i = 0; i < objectIDs.length; ++i)
		jjObjectPresets[objectIDs[i]].behavior = DeactivateEnemyWrapper(cast<Jazz1::Enemy@>(cast<jjBEHAVIORINTERFACE>(jjObjectPresets[objectIDs[i]].behavior)));
}

void OhBeehive(jjOBJ@ obj) {
	if (obj.creatorType == CREATOR::LEVEL) {
		if (obj.state == STATE::START) {
			obj.determineCurAnim(ANIM::PLUS_SCENERY, 3);
			obj.state = STATE::STILL;
			obj.counterEnd = 0;
			obj.energy = 5;
		} else {
			if (obj.state == STATE::KILL)
				jjParameterSet(int(obj.xOrg)/32, int(obj.yOrg)/32, -1, 1, 0); //deactivate
			obj.behave(BEHAVIOR::FISH);
			if (obj.counterEnd != 0) {
				if (obj.counterEnd == 255) {
					jjOBJ@ lastBee = jjObjects[obj.special];
					if (lastBee.behavior != OhBeehive || lastBee.creatorID != uint(obj.objectID) || lastBee.state != STATE::IDLE) {
						obj.counterEnd = 0; //will be changed to 1 momentarily
						obj.special = jjAddObject(obj.eventID, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT);
					} else { //already got one buzzing around
						obj.counterEnd = 220; //check again soon
					}
				}
				++obj.counterEnd;
			} else if (obj.findNearestPlayer(200*200) >= 0)
				obj.counterEnd = 192;
		}
	} else {
		if (obj.state == STATE::ATTACK) {
			jjDifficulty -= 1;
			obj.counter = 70; //never stop
			obj.behave(BEHAVIOR::BEE);
			jjDifficulty += 1;
		} else
			obj.behave(BEHAVIOR::BEE);
	}
}

bool everyOtherBirdShot = false;
void WeaponOne(jjOBJ@ obj) {
	const jjPLAYER@ player = jjPlayers[obj.creatorID];
	if (player.charCurr == CHAR::BIRD2) {
		everyOtherBirdShot = !everyOtherBirdShot;
		if (everyOtherBirdShot) {
			obj.delete();
			return;
		}
		obj.special = obj.determineCurAnim(ANIM::AMMO, 66);
		obj.behavior = BirdShot;
		obj.animSpeed = 2;
		obj.var[3] = 5; //not blaster
		jjSample(obj.xPos, obj.yPos, SOUND::COMMON_FLAP);
	} else if (PlayersX[player.localPlayerID].Fish) {
		obj.determineCurAnim(ANIM::AMMO, 30);
		obj.behavior = BEHAVIOR::BULLET;
		jjSample(obj.xPos, obj.yPos, SOUND::COMMON_SPLUT);
	} else {
		obj.var[6] = 16; //fireball
		obj.state = STATE::FLY;
		obj.determineCurFrame();
		obj.behavior = Knife;
	}
	obj.behave();
}

void BirdShot(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::BULLET);
}
bool KnifeMask(float x, float y) {
	return jjMaskedPixel(int(x), int(y)) && (jjEventAtLastMaskedPixel == 0 || jjEventAtLastMaskedPixel > AREA::HOOK);
}
void Knife(jjOBJ@ obj) {
	if (obj.counter++ < 7) {
		obj.xSpeed += obj.xAcc;
		const float newX = obj.xPos + obj.xSpeed + obj.var[7]/65536.0;
		obj.ySpeed += obj.yAcc;
		const float newY = obj.yPos + obj.ySpeed;
		if (KnifeMask(newX, newY)) {
			obj.counter = 50; //return
		} else {
			obj.xPos = newX;
			obj.yPos = newY;
		}
	} else {
		jjPLAYER@ play = jjPlayers[obj.creatorID];
		const float dx = play.xPos - obj.xPos;
		const float dy = play.yPos - obj.yPos;
		if ((abs(dx) < 18 && abs(dy) < 18) || (obj.state == STATE::DEACTIVATE && !jjDeactivatingBecauseOfDeath)) {
			jjSamplePriority(SOUND::COMMON_PICKUPW1);
			obj.delete();
			if (play.ammo[WEAPON::BLASTER] <= AMMOBUFFER)
				play.currWeapon = WEAPON::BLASTER;
			play.ammo[WEAPON::BLASTER] = play.ammo[WEAPON::BLASTER] + 1;
			return;
		} else {
			const float targetX = (dx > 0) ? 3 : -3;
			const float targetY = (dy > 0) ? 2 : -2;
			if (obj.xSpeed < targetX) obj.xSpeed += 0.1;
			else if (obj.xSpeed > targetX) obj.xSpeed -= 0.1;
			if (obj.ySpeed < targetY) obj.ySpeed += 0.1;
			else if (obj.ySpeed > targetY) obj.ySpeed -= 0.1;
			if (!KnifeMask(obj.xPos + obj.xSpeed, obj.yPos))
				obj.xPos += obj.xSpeed;
			else
				obj.xSpeed = 0;
			if (!KnifeMask(obj.xPos, obj.yPos + obj.ySpeed))
				obj.yPos += obj.ySpeed;
			else
				obj.ySpeed = 0;
		}
	}
	if (obj.counter > 3) {
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame);
		if (++obj.counterEnd >= 5) {
			obj.counterEnd = 0;
			obj.frameID += 1;
			obj.determineCurFrame();
		}
	}
}

array<jjMASKMAP@> FullMasks = {jjMASKMAP(false), jjMASKMAP(true)};
void SetFishMask(bool setTo) {
	FullMasks[setTo ? 1 : 0].save(1040, false);
}

void NotSwimming(jjOBJ@) {
	jjSetWaterLevel(jjLayerHeight[4] * 32, true);
	SetFishMask(false);
}
void onLevelReload() {
	MLLE::Palette.apply();
	jjAddObject(OBJECT::BEES,0,0,0,CREATOR::OBJECT, NotSwimming);
	for (int x = jjLayerWidth[3]; --x >= 0;)
		for (int y = jjLayerHeight[3]; --y >= 0;) {
			const uint8 ev = jjEventGet(x,y);
			if (ev == 34) //ammo
				jjParameterSet(x,y,1,1,0);
	}
}

array<array<int>> Targets(0);
uint CurrentTargetID = 0;
bool SetTarget(uint nu) {
	if (nu > CurrentTargetID) {
		CurrentTargetID = nu;
		return true;
	}
	return false;
}
void onFunction5(jjPLAYER@, uint8 nu) {
	SetTarget(nu);
}

const int AMMOBUFFER = 10;
void onPlayer(jjPLAYER@ play) {
	play.lives = 5;
	if (play.health <= 0)
		PlayersX[play.localPlayerID].Apples = 2;
	play.fastfire = 8;
	if (PlayersX[play.localPlayerID].Fish) {
		play.spriteMode = SPRITE::INVISIBLE;
		play.currWeapon = WEAPON::BLASTER;
		jjWeapons[WEAPON::BLASTER].infinite = true;
		play.keySelect = false;
		play.jumpStrength = -5;
		SetFishMask(true);
		jjSetWaterLevel(PlayersX[play.localPlayerID].WaterLevel, true);
		if (play.yPos <= jjWaterLevel) {
			play.keyDown = play.keyFire = false;
			if (play.ySpeed > 0)
				play.keyJump = false;
		}
		float ySpeed = play.ySpeed;
		if (ySpeed < -2.f) ySpeed = -2.f;
		else if (ySpeed > 2.f) ySpeed = 2.f;
		jjDrawRotatedSprite(play.xPos, play.yPos, ANIM::FISH, 1, jjGameTicks / 6, int(ySpeed * -64) * play.direction, play.direction, 1, SPRITE::PLAYER, play.playerID);
	} else {
		play.spriteMode = SPRITE::PLAYER;
		NotSwimming(null);
		if (play.charCurr == CHAR::FROG) {
			play.jumpStrength = play.keyJump ? -18 : -7;
			play.keyJump = true;
			play.keyFire = false;
			if (PlayersX[play.localPlayerID].Immobile)
				play.keyLeft = play.keyRight = false;
		} else if (play.charCurr == CHAR::BIRD2) {
			play.currWeapon = WEAPON::BLASTER;
			play.keySelect = false;
			jjWeapons[WEAPON::BLASTER].infinite = true;
			if (PlayersX[play.localPlayerID].Immobile) {
				play.keyUp = play.keyDown = play.keyJump = play.keyFire = play.keyRun = false; //ONLY left/right allowed
				play.ySpeed = -1.5;
				play.invincibility = -200;
			}
		} else {
			if (play.keySelect || play.ammo[WEAPON::CURRENT] <= AMMOBUFFER)
				play.keyFire = false;
			if (play.keyDown && !jjMaskedPixel(int(play.xPos), int(play.yPos) + 21) && (jjEventGet(int(play.xPos) >> 5, int(play.yPos) >> 5) != AREA::VINE))
				play.keyDown = false;
			if (PlayersX[play.localPlayerID].NoJumping > jjGameTicks)
				play.keyJump = false;
			jjWeapons[WEAPON::BLASTER].infinite = false;
			play.jumpStrength = -10;
		}
	}
	
	if (CurrentTargetID < Targets.length)
		if (jjDifficulty <= 0 || jjKey[9])
			ArtificialArrow::DrawArrow(Targets[CurrentTargetID], play, SPRITE::SINGLEHUE, 72);
}


class PacmanGhost : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		if (obj.state == STATE::START) {
			obj.state = STATE::FLY;
			obj.yPos -= 32;
			if (jjParameterGet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 0, 1) == 1) {
				obj.scriptedCollisions = false;
				obj.playerHandling = HANDLING::ENEMY;
				obj.bulletHandling = HANDLING::HURTBYBULLET;
				obj.determineCurAnim(ANIM::BIRD, 9);
			}
		} else if (obj.state == STATE::FLY) {
			obj.pathMovement();
			if (++obj.counterEnd >= 6) {
				obj.counterEnd = 0;
				obj.frameID += 1;
				obj.determineCurFrame();
			}
			jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.scriptedCollisions ? obj.direction * -2 - 1 : obj.direction, obj.justHit == 0 ? SPRITE::NORMAL : SPRITE::SINGLECOLOR, 15);
		} else if (obj.state == STATE::KILL) {
			obj.deactivate();
		} else { //freeze, deactivate
			obj.behave(BEHAVIOR::SPARK);
		}
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ play, int) {
		if (bullet !is null) {
			if (bullet.behavior == BirdShot) {
				obj.bulletHandling = HANDLING::HURTBYBULLET;
				obj.scriptedCollisions = false;
				bullet.objectHit(obj, HANDLING::ENEMY);
			}
		} else
			play.objectHit(obj, 0, HANDLING::ENEMY);
		return true;
	}
}

void SpikeBall(jjOBJ@ obj) {
	if (obj.state == STATE::DEACTIVATE || obj.state == STATE::EXPLODE || obj.state == STATE::KILL) {
		jjPLAYER@ owner = jjPlayers[obj.creatorID];
		owner.ammo[WEAPON::BOUNCER] = owner.ammo[WEAPON::BOUNCER] + 1;
		obj.behavior = BEHAVIOR::BOUNCERBULLETPU;
	}
	obj.behave(BEHAVIOR::BOUNCERBULLETPU);
}

int getLivesAnimID(int character, bool swimming) {
	if (!swimming) switch (character) {
		case CHAR::JAZZ:
			return jjAnimSets[ANIM::FACES] + 3;
		case CHAR::SPAZ:
			return jjAnimSets[ANIM::FACES] + (jjIsTSF ? 5 : 4);
		case CHAR::LORI:
			return jjAnimSets[ANIM::FACES] + 4;
		case CHAR::FROG:
			return jjAnimSets[ANIM::FACES] + 2;
		case CHAR::BIRD:
		case CHAR::BIRD2:
			return jjAnimSets[ANIM::FACES] + 0;
	}
	return jjAnimSets[ANIM::CUSTOM[0]];
}

bool onDrawLives(jjPLAYER@ player, jjCANVAS@ canvas) {
	const jjANIMATION@ anim = jjAnimations[getLivesAnimID(player.charCurr, PlayersX[player.localPlayerID].Fish)];
	canvas.drawSpriteFromCurFrame(0, jjSubscreenHeight, anim.firstFrame + (jjGameTicks / 6) % anim.frameCount, 1, SPRITE::PLAYER, player.playerID);
	int x = 20;
	for (int keyType = 0; keyType < 2; ++keyType) {
		for (uint i = 0; i < Keys[keyType]; ++i)
			canvas.drawSpriteFromCurFrame(x += 30, jjSubscreenHeight - 20, KeyCurFrame - keyType);
	}
	jjLayerXOffset[2] = 
	jjLayerXOffset[2] + 2.5;
	return true;
}

void onFunction0(jjPLAYER@ player, uint8 level) {
	if (PlayersX[player.localPlayerID].Fish) {
		int target;
		if (level == 0) target = int(player.yPos) & ~31;
		else target = level * 32;
		PlayersX[player.localPlayerID].WaterLevel = target;
	} else if (player.charCurr != CHAR::FROG) {
		if (player.health > 0)
			player.hurt(7, true);
	}
}

void onFunction1(jjPLAYER@ player, bool canMove) {
	if (player.charCurr == CHAR::FROG)
		PlayersX[player.localPlayerID].Immobile = !canMove;
}

const array<CHAR::Char> paramToChar = {jjLocalPlayers[0].charOrig, CHAR::FROG, CHAR::BIRD2, CHAR::JAZZ};
class MorphHead : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		if (obj.state == STATE::START) {
			obj.var[0] = jjParameterGet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 0, 2); //{Rabbit,Frog,Bird,Fish}
			obj.var[1] = paramToChar[obj.var[0]];
			obj.var[2] = jjParameterGet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 2, 1);
			obj.curAnim = getLivesAnimID(obj.var[1], obj.var[0] == 3);
			obj.state = STATE::FLOAT;
			obj.special = 0;
			obj.curFrame = jjAnimations[jjAnimSets[ANIM::AMMO] + 5] + 6;
		} else if (obj.state == STATE::DEACTIVATE) {
			obj.deactivate();
		} else {
			{
				const int playerID = obj.findNearestPlayer(150 * 150);
				if (playerID >= 0)
					obj.direction = (jjPlayers[playerID].xPos >= obj.xPos) ? 1 : -1;
			}
			const jjANIMATION@ anim = jjAnimations[obj.curAnim];
			jjDrawSpriteFromCurFrame(obj.xPos - 14 * obj.direction, obj.yPos + 16, anim.firstFrame + (jjGameTicks / 6) % anim.frameCount, obj.direction);
			if (obj.special != 0 && (jjGameTicks & 7) == 0) {
				obj.frameID = 100;
				obj.state = STATE::ACTION;
				obj.behave(BEHAVIOR::CHECKPOINT, false);
				for (int i = 0; i < jjLocalPlayerCount; ++i) {
					jjPLAYER@ player = jjLocalPlayers[i];
					player.xOrg = obj.xPos; //don't use checkpoints' curframe hotspot code, because lives icons have weird hotspots
					player.yOrg = obj.yPos;
				}
				obj.curFrame = jjObjectPresets[obj.eventID].curFrame;
				obj.special = 0;
				obj.var[0] = jjParameterGet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 0, 2);
				
				for (int x = jjLayerWidth[3]; --x >= 0;)
					for (int y = jjLayerHeight[3]; --y >= 0;) {
						const uint8 ev = jjEventGet(x,y);
						if (ev == 34 && jjParameterGet(x,y,1,1) == 1) //ammo
							jjEventSet(x,y, 0);
				}
			}
		}
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int) {
		if (bullet is null) {
			if (obj.var[0] != 3) {
				if (player.charCurr != obj.var[1] || PlayersX[player.localPlayerID].Fish) {
					player.morphTo(CHAR::Char(obj.var[1]), true);
					PlayersX[player.localPlayerID].Fish = false;
					PlayersX[player.localPlayerID].NoJumping = jjGameTicks + 3;
					if (player.charCurr == CHAR::BIRD2)
						SetTarget(6);
					else if (player.charCurr == CHAR::FROG)
						SetTarget(11);
					else if (player.charCurr == jjLocalPlayers[0].charOrig && player.ammo[WEAPON::BLASTER] > 0)
						SetTarget(8);
				} else return true;
			} else {
				if (!PlayersX[player.localPlayerID].Fish) {
					player.morphTo(CHAR::JAZZ, true); //I guess
					PlayersX[player.localPlayerID].Fish = true;
					player.xPos = obj.xPos;
					SetTarget(14);
				} else return true;
			}
			jjSample(obj.xPos, obj.yPos, SOUND::COMMON_HOLYFLUT);
			obj.special = player.playerID + CREATOR::PLAYER; //to trigger checkpoint code
			for (uint objectID = jjObjectCount; --objectID != 0;) { //delete bullets from this player, for better checkpoint oldAmmo numbers
				jjOBJ@ bb = jjObjects[objectID];
				if (bb.behavior == Knife && bb.creator == obj.special) {
					player.ammo[WEAPON::BLASTER] =
					player.ammo[WEAPON::BLASTER] + 1;
					bb.delete();
				} else if (bb.behavior == BEHAVIOR::BOUNCERBULLETPU && bb.creator == obj.special) {
					player.ammo[WEAPON::BOUNCER] =
					player.ammo[WEAPON::BOUNCER] + 1;
					bb.delete();
				}
			}
			if (obj.var[2] == 1) //nocheckpoint
				obj.special = 0;
		}
		return true;
	}
}

array<uint> Keys(2, 0);
class Key : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		if (obj.state == STATE::START) {
			obj.special = jjParameterGet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 0, 1);
			obj.curAnim -= obj.special;
		}
		if (obj.xSpeed != 0) {
			obj.xSpeed = 0;
			if (obj.ySpeed == 0) obj.ySpeed = 0.5;
		}
		obj.ySpeed = abs(obj.ySpeed);
		if (obj.ySpeed > 3) obj.ySpeed = 3;
		obj.behave(BEHAVIOR::PICKUP, false);
		jjDrawSpriteFromCurFrame(obj.xPos, (obj.ySpeed != 0.f) ? obj.yPos : (obj.yPos + jjSin((obj.objectID*8+jjGameTicks+int(obj.xPos+obj.yPos*256))*16) * 4), obj.curFrame, obj.direction, SPRITE::NORMAL,0, 2);
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@, jjPLAYER@ player, int) {
		obj.behavior = BEHAVIOR::EXPLOSION2;
		obj.playerHandling = HANDLING::EXPLOSION;
		Keys[obj.special] += 1;
		jjEventSet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 0);
		jjSample(obj.xPos, obj.yPos, SOUND::COMMON_COIN);
		if (obj.special == 0) { //key
			if (player.charCurr != CHAR::FROG)
				SetTarget(9);
			else 
				SetTarget(12);
		} else //gem
			SetTarget(3);
		return true;
	}
}


class Lock : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		if (obj.state == STATE::START) {
			obj.var[0] = jjParameterGet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 0, 2);
			obj.var[1] = jjParameterGet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 2, 2);
			obj.curAnim = KeyCurFrame - obj.var[0];
			if (obj.var[0] == 3) obj.curAnim += 1;
			obj.curFrame = jjAnimations[jjAnimSets[ANIM::DESTSCEN]] + 2;
			obj.yPos += obj.var[1] * 16;
			obj.state = STATE::WAIT;
		} else if (obj.state == STATE::DEACTIVATE) {
			obj.deactivate();
		} else {
			jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curAnim, 256, 1, obj.var[0] != 2 ? 1 : -1, obj.var[0] <= 1 ? SPRITE::SINGLECOLOR : SPRITE::NORMAL, 0, 2);
		}
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@, jjPLAYER@ player, int) {
		if (obj.var[0] <= 1) { //key/gem
			if (Keys[obj.var[0]] <= 0) {
				if (obj.var[2] < jjGameTicks ) {
					obj.var[2] = jjGameTicks + 210;
					jjAnimations[jjObjectPresets[OBJECT::SILVERCOIN].curAnim].frameCount = 1;
					jjAnimations[jjObjectPresets[OBJECT::SILVERCOIN].curAnim].firstFrame = KeyCurFrame - obj.var[0];
					player.testForCoins(1);
					SetTarget(2);
				}
				return true;
			}
			Keys[obj.var[0]] -= 1;
		} else { //right/left
			if ((obj.var[0] == 2) == (player.xPos >= obj.xPos))
				return true;
		}
		const int xTile = int(obj.xOrg) >> 5, yTile = int(obj.yOrg) >> 5;
		jjGenerateSettableTileArea(4, xTile,yTile, 1, obj.var[1] + 1);
		jjEventSet(xTile,yTile, 0);
		jjSample(obj.xPos, obj.yPos, SOUND::COMMON_DAMPED1);
		for (int y = 0; y <= obj.var[1]; ++y) {
			const uint16 tileID = jjTileGet(4,xTile,yTile+y);
			//jjAddParticleTileExplosion(xTile,yTile+y,tileID,false);
			for (int q = 0; q < 4; ++q) {
				jjPARTICLE@ part = jjAddParticle(PARTICLE::TILE);
				if (part !is null) {
					part.xPos = xTile * 32 + (q & 1) * 16;
					part.yPos = (yTile + y) * 32 + (q & 2) * 8;
					part.ySpeed = y - obj.var[1] + ((q & 2) / 4.f) - 1;
					part.xSpeed = ((q & 1) * 2 - 1) / 2.f * (abs(part.ySpeed) + 1) / 1.75;
					part.tile.tileID = tileID;
					part.tile.quadrant = TILE::Quadrant(q);
				}
			}
			jjTileSet(4,xTile,yTile+y,0);
		}
		obj.delete();
		return true;
	}
}

class Apple : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::PICKUP);
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@, jjPLAYER@ player, int) {
		obj.behavior = BEHAVIOR::EXPLOSION2;
		obj.playerHandling = HANDLING::EXPLOSION;
		jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::COMMON_EAT1 + (jjRandom() & 3)));
		if (player.health < jjMaxHealth) {
			PlayerX@ playerX = PlayersX[player.localPlayerID];
			if (playerX.Apples < 10) {
				playerX.Apples += 1;
			} else {
				playerX.Apples = 2;
				player.health += 1;
			}
		}
		if (jjDifficulty > 2)
			jjEventSet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 0);
		return true;
	}
}

const int HeartCurFrame = jjAnimations[jjAnimSets[ANIM::PICKUPS] + 41];
bool onDrawHealth(jjPLAYER@ player, jjCANVAS@ canvas) {
	canvas.drawRectangle(jjSubscreenWidth - 51, 5, 44, 12, 30);
	if (player.health > 0 && (player.health > 1 || jjRenderFrame & 3 < 2)) {
		canvas.drawSpriteFromCurFrame(jjSubscreenWidth - 65, 11, HeartCurFrame);
		canvas.drawRectangle(jjSubscreenWidth - 50, 6, (player.health - 1) * 10 + PlayersX[player.localPlayerID].Apples, 10, 24);
	}
	return true;
}

void GenerateWaterLayer() { //one-time
	jjLAYER newLayer(jjLayerWidth[3], jjLayerHeight[3]);
	newLayer.xSpeed = newLayer.ySpeed = 1;
	dictionary createdTiles;
	auto newTileID = jjTileCount;
	for (int x = jjLayerWidth[3]; --x >= 0;)
		for (int y = jjLayerHeight[3]; --y >= 0;) {
			const uint16 waterTileID = jjTileGet(3,x,y);
			if (waterTileID >= 1365 && waterTileID <= 1427) {
				const uint16 wallTileID = jjTileGet(4,x,y);
				if (wallTileID != 0) { //would this ever come up? well, no harm in it
					const string combo = waterTileID + "_" + wallTileID;
					uint16 comboTileID;
					if (!createdTiles.get(combo, comboTileID)) {
						comboTileID = newTileID++;
						jjPIXELMAP water(waterTileID), wall(wallTileID);
						for (uint xx = 0; xx < 32; ++xx)
							for (uint yy = 0; yy < 32; ++yy)
								if (water[xx,yy] != 0)
									wall[xx,yy] = 0;
						createdTiles.set(combo, comboTileID);
						wall.save(comboTileID);
					}
					newLayer.generateSettableTileArea(x,y,1,1); //not the most efficient but we're probably not dealing with big enough numbers for it to be a problem
					newLayer.tileSet(x,y,comboTileID);
				}
			}
		}
	auto layers = jjLayerOrderGet();
	layers.insertAt(layers.findByRef(jjLayers[3]), newLayer);
	jjLayerOrderSet(layers);
}

void FrogFencer(jjOBJ@ obj) {
	if (obj.state == STATE::KILL) {
		obj.deactivate();
	} else if (obj.state == STATE::JUMP) {
		if (obj.ySpeed < 0) {
			if (jjMaskedPixel(int(obj.xPos), int(obj.yPos))) //hit ceiling
				obj.ySpeed = 0;
		}
		if (jjMaskedVLine(int(obj.xPos + obj.xSpeed), int(obj.yPos) - 10, 15)) { //hit wall
			obj.xPos -= obj.xSpeed;
		}
	} else if (obj.state == STATE::STILL) {
		if (obj.counter & 7 == 3 && obj.frameID == 7) {
			if ((obj.var[5] = (obj.var[5] ^ 1)) == 1)
				jjSample(obj.xPos, obj.yPos, SOUND::FROG_FROG);
		}
	} else if (obj.state == STATE::START) {
		obj.yPos -= 10;
	}
	obj.behave(BEHAVIOR::FENCER, false);
	jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos + 5, obj.curFrame, obj.direction);
}

class Ammo : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		if (obj.state == STATE::START) {
			if (jjParameterGet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 0, 1) == 0) {
				obj.determineCurAnim(ANIM::AMMO, 49);
				obj.var[3] = 
				obj.var[3] - 1;
			} else obj.determineCurAnim(ANIM::ROBOT, 0);
		}
		obj.behave(BEHAVIOR::PICKUP);
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@, jjPLAYER@ play, int) {
		WEAPON::Weapon gunID = WEAPON::Weapon(obj.var[3] + 1);
		for (int i = 0; i < jjLocalPlayerCount; ++i) {
			jjPLAYER@ player = jjLocalPlayers[i];
			if (player.ammo[gunID] <= 0) {
				player.ammo[gunID] = AMMOBUFFER;
				player.currWeapon = gunID;
			}
			player.ammo[gunID] = 
			player.ammo[gunID] + 1;
		}
		obj.behavior = BEHAVIOR::EXPLOSION2;
		obj.playerHandling = HANDLING::EXPLOSION;
		jjParameterSet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 1, 1, 1);
		jjSample(obj.xPos, obj.yPos, SOUND::COMMON_PICKUPW1);
		if (gunID == WEAPON::BLASTER)
			SetTarget((play.charCurr == CHAR::BIRD2) ? 7 : 8);
		else
			SetTarget(17);
		return true;
	}
}
bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	int x = jjSubscreenWidth - 24;
	int y = jjSubscreenHeight - 20;
	for (int weaponID = 1; weaponID <= 2; ++weaponID) {
		const jjANIMATION@ anim = jjAnimations[jjObjectPresets[OBJECT::BLASTERBULLET + weaponID - 1].curAnim];
		const int ammo = player.ammo[weaponID] - AMMOBUFFER;
		for (int b = 0; b < ammo; ++b)
			canvas.drawSpriteFromCurFrame(x, y - b * 24, anim.firstFrame + (jjGameTicks / 6 + b) % anim.frameCount, 1, (uint(weaponID) == player.currWeapon && player.charCurr != CHAR::BIRD2 && !PlayersX[player.localPlayerID].Fish) ? SPRITE::NORMAL : SPRITE::BLEND_NORMAL, 96);
		x -= 24;
	}
	return true;
}

bool onCheat(string &in cheat) {
	if (cheat == "jjammo" || cheat == "jjguns" || cheat == "jjgod") {
		for (int i = 0; i < jjLocalPlayerCount; ++i)
			for (int w = 1; w <= 2; ++w) {
				if (jjLocalPlayers[i].ammo[w] == 0)
					jjLocalPlayers[i].ammo[w] = AMMOBUFFER;
				jjLocalPlayers[i].ammo[w] =
					jjLocalPlayers[i].ammo[w] + 1;
			}
		jjAlert(cheat, false, STRING::MEDIUM);
		return true;
	} else if (cheat == "jjmorph") {
		return true;
	}
	return false;
}

void onFunction3() {
	for (int x = jjLayerWidth[3]; --x >= 0;)
		for (int y = jjLayerHeight[3]; --y >= 0;) {
			const uint8 ev = jjEventGet(x,y);
			if (ev == 146) //morph head
				jjEventSet(x,y, 0);
			else if (ev == 255) {
				jjEventSet(x,y, jjParameterGet(x,y,0,8));
			}
	}
}

void onFunction4(jjPLAYER@ play) {
	MLLE::GetLayer("River").hasTiles = false;
	MLLE::GetLayer("Far Trees").hasTiles = false;
	MLLE::GetLayer("Farther Trees").hasTiles = false;
	MLLE::GetLayer("Owls").hasTiles = false;
	jjTexturedBGUsed = false;
	jjMusicStop();
	PlayersX[play.localPlayerID].Immobile = true;
	SetTarget(22);
}