Downloads containing JungUltEx2.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Jungle UltimateFeatured Download DoubleGJ Tileset conversion 10 Download file

File preview

const bool MLLESetupSuccessful = MLLE::Setup(); ///@MLLE-Generated
#include "MLLE-Include-1.7.asc" ///@MLLE-Generated
#pragma require "JungUltEx2-MLLE-Data-2.j2l" ///@MLLE-Generated
#pragma require "JungUltEx2-MLLE-Data-1.j2l" ///@MLLE-Generated
#pragma require "JungUltEx2.j2l" ///@MLLE-Generated
#pragma require "DD-Anims.j2a"
#include "DD-Order.asc"
#include "DD-Rank.asc"
#pragma require "JungUlt-MountRange.png"
#pragma require "JungUlt-Savanna.png"
#pragma require "JungUlt-Clouds1.png"
#pragma require "JungUlt-Clouds2.png"
#pragma require "JungUlt-Village.png"
#pragma require "waterfall-loop.wav"
#pragma require "big-bite.wav"
#pragma require "aslime2.wav"
#pragma require "empty.wav"
#pragma require "common-gemsmsh1.wav"
#pragma require "common-eat1.wav"
#pragma require "common-eat2.wav"
#pragma require "common-eat3.wav"
#pragma require "common-eat4.wav"
#pragma require "cicada.wav"
#pragma require "drumchant1.wav"
#pragma require "drumchant2.wav"
#pragma require "birds1.wav"
#pragma require "birds2.wav"
#pragma require "bird-takeoff1.wav"
#pragma require "bird-takeoff2.wav"
#pragma require "bird-takeoff3.wav"
#pragma require "african-drumming.wav"
#pragma require "african-evening.wav"
#pragma require "african-evening.wav"
#pragma require "zebra-calls.wav"
#pragma require "lions-cicadas.wav"
#pragma require "lion-calls.wav"
#pragma require "cicada-loop.wav"
#pragma require "african-evening-pond.wav"
#pragma require "evening-cicadas.wav"
#pragma require "fooby.wav"
#pragma require "nakotak1.wav"
#pragma require "nakotak2.wav"
#pragma require "bonk.wav"
#pragma require "crazy-laugh.wav"
#pragma require "rustle.wav"
#pragma require "pray1n.wav"
#pragma require "bigcopter.wav"
#pragma offer "rain9.wav"
#pragma offer "DD-JungleMix.it"
#pragma offer "TM-RAGE.S3M"
#pragma offer "drgeno-boss2.mp3"

const int bestTime = 510;
const float maxScore = 110000;
const int easyTimer = 0;
const int normalTimer = 0;
const int hardTimer = 75600;
const int turboTimer = 37800;

const uint NumberOfBitsDevotedToSyncParameter = 3; //this should correspond to the number of bits you assign to the Sync parameter in your JCS.ini (or MLLE equivalent) entry for Swinging Vine. So for example if you give it a parameter Sync:2, this variable should ALSO equal 2.

float layer5_offset = 0.0;
float layer8_offset = 0.0;

array<int> simonSaysSequence = {0,3,0,1,2,3};
uint8 simonSaysRound = 0; //must be zero, but for testing purposes you can cheat!
uint8 simonDelay, simonCounter = 0;
bool simonSaysYourTurn = false;
uint8 simonLastRoundOutcome = 0; // 1 to win, 2 to lose

// NPC stuff:
bool chiefNear = false;
bool gateUnlocked = false;
bool bananasGiven = false;
int gateTimer, gateChannel, farPlayerID, nearPlayerID = 0;

// Bubba stuff:
int8 drawBubba = 1;
bool foughtBubba, bubbaExitStage = false;
int bubbaChannel, totemChannel, rainChannel, copterChannel;
bool totemActive, bubbaWait = false;
CustomBossHealthBar customBossHealthBar;
int bossHealth, bossPhase;
array<array<float>> totemPlacement = {
	{331,25},
	{331,19},
	{335,27},
	{327,27}
};
array<int> totemTile = {1052,1043,1041,1040};

void onLevelLoad() {
	YomanPalette();
	initiateDialogue();
	initiateRanking();
	initFade();

	jjAnimSets[ANIM::HELMUT].load(); // for pixelmaps
	jjAnimSets[ANIM::DOG].load(); // for ambient sfx
	jjSampleLoad(SOUND::SCIENCE_PLOPKAOS, "waterfall-loop.wav");
	jjSampleLoad(SOUND::COMMON_DRINKSPAZZ1, "common-eat1.wav");
	jjSampleLoad(SOUND::COMMON_DRINKSPAZZ2, "common-eat2.wav");
	jjSampleLoad(SOUND::COMMON_DRINKSPAZZ3, "common-eat3.wav");
	jjSampleLoad(SOUND::COMMON_DRINKSPAZZ4, "common-eat4.wav");
	jjSampleLoad(SOUND::EPICLOGO_EPIC2, "aslime2.wav");
	jjSampleLoad(SOUND::BILSBOSS_BILLAPPEAR, "cicada.wav");
	jjSampleLoad(SOUND::BILSBOSS_FINGERSNAP, "drumchant1.wav");
	jjSampleLoad(SOUND::BILSBOSS_FIRE, "drumchant2.wav");
	jjSampleLoad(SOUND::BILSBOSS_FIRESTART, "birds1.wav");
	jjSampleLoad(SOUND::BILSBOSS_SCARY3, "birds2.wav");
	jjSampleLoad(SOUND::BILSBOSS_THUNDER, "african-drumming.wav");
	jjSampleLoad(SOUND::BILSBOSS_ZIP, "african-evening.wav");
	jjSampleLoad(SOUND::BILSBOSS_ZIP, "african-evening.wav");
	jjSampleLoad(SOUND::DOG_AGRESSIV, "zebra-calls.wav");
	jjSampleLoad(SOUND::DOG_SNIF1, "lions-cicadas.wav");
	jjSampleLoad(SOUND::DOG_WAF1, "lion-calls.wav");
	jjSampleLoad(SOUND::DOG_WAF2, "cicada-loop.wav");
	jjSampleLoad(SOUND::DOG_WAF3, "african-evening-pond.wav");
	jjSampleLoad(SOUND::ENDING_OHTHANK, "evening-cicadas.wav");
	jjSampleLoad(SOUND::ORANGE_BUBBELSL, "rain9.wav");
		
	jjAnimSets[ANIM::CUSTOM[7]].load(3, "DD-Anims.j2a"); // custom pickups sprites
///@Event 67=Super Item                    |+|Goodies       |Super  |Item|Type:{Red Gem,Green Gem,Blue Gem,Purple Gem,Watermelon,Jackfruit}3
	jjObjectPresets[OBJECT::SUPERGEM].behavior = SuperGemWrapper;
	jjObjectPresets[OBJECT::FLICKERGEM].behavior = flickerWrapper;
	jjSampleLoad(SOUND::COMMON_GEMSMSH1, "empty.wav");
	jjSampleLoad(SOUND::INTRO_GRAB, "common-gemsmsh1.wav");
// not renaming Purple Gem since it's just a reskin
	jjObjectPresets[OBJECT::PURPLEGEM].behavior = specialBanana();
	jjObjectPresets[OBJECT::PURPLEGEM].determineCurAnim(ANIM::PICKUPS, 2);
///@Event 174=Kiwi |+|Food |Kiwi
	jjObjectPresets[OBJECT::BURGER].determineCurAnim(ANIM::CUSTOM[7], 1);
///@Event 175=Pineapple |+|Food | Pine-|apple
	jjObjectPresets[OBJECT::PIZZA].determineCurAnim(ANIM::CUSTOM[7], 13);
///@Event 168=Mango |+|Food |Man-|go
	jjObjectPresets[OBJECT::DONUT].determineCurAnim(ANIM::CUSTOM[7], 14);
///@Event 176=Melon |+|Food |Melon
	jjObjectPresets[OBJECT::FRIES].determineCurAnim(ANIM::CUSTOM[7], 15);
///@Event 180=Avocado |+|Food |Avo-|cado
	jjObjectPresets[OBJECT::WEENIE].determineCurAnim(ANIM::CUSTOM[7], 17);
///@Event 181=Dragon Fruit |+|Food |Dragn |Fruit
	jjObjectPresets[OBJECT::HAM].determineCurAnim(ANIM::CUSTOM[7], 18);
///@Event 182=Pomegranate |+|Food |Pome-|grnat
	jjObjectPresets[OBJECT::CHEESE].determineCurAnim(ANIM::CUSTOM[7], 19);
///@Event 167=Lychee |+|Food |Lyche
	jjObjectPresets[OBJECT::CAKE].determineCurAnim(ANIM::CUSTOM[7], 20);
///@Event 169=Star Fruit |+|Food |Star |Fruit
	jjObjectPresets[OBJECT::CUPCAKE].determineCurAnim(ANIM::CUSTOM[7], 21);
///@Event 170=White Grapes|+|Food      |White|Grapes
	jjObjectPresets[OBJECT::CHIPS].determineCurAnim(ANIM::CUSTOM[7], 11);
///@Event 166=Apricot |+|Food |Apri-|cot
	jjObjectPresets[OBJECT::PIE].determineCurAnim(ANIM::CUSTOM[7], 23);
///@Event 171=Passion Fruit|+|Food |Passn|Fruit
	jjObjectPresets[OBJECT::CANDY].determineCurAnim(ANIM::CUSTOM[7], 24);
///@Event 173=Fig |+|Food |Fig
	jjObjectPresets[OBJECT::ICECREAM].determineCurAnim(ANIM::CUSTOM[7], 44);
///@Event 179=Lulo |+|Food |Lulo
	jjObjectPresets[OBJECT::TACO].determineCurAnim(ANIM::CUSTOM[7], 26);
///@Event 146=Quince |+|Food |Quince
	jjObjectPresets[OBJECT::PRETZEL].determineCurAnim(ANIM::CUSTOM[7], 27);
///@Event 160=Papaya |+|Food |Pa-|paya
	jjObjectPresets[OBJECT::LETTUCE].determineCurAnim(ANIM::CUSTOM[7], 29);
///@Event 162=Kiwano |+|Food |Ki-|wano
	jjObjectPresets[OBJECT::CUCUMB].determineCurAnim(ANIM::CUSTOM[7], 30);
///@Event 172=Cocoa (fruit) |+|Food |Cocoa
	jjObjectPresets[OBJECT::CHOCBAR].determineCurAnim(ANIM::CUSTOM[7], 31);
///@Event 163=Plum |+|Food |Plum
	jjObjectPresets[OBJECT::COKE].determineCurAnim(ANIM::CUSTOM[7], 32);
///@Event 164=Bergamot |+|Food |Ber-|gamot
	jjObjectPresets[OBJECT::PEPSI].determineCurAnim(ANIM::CUSTOM[7], 33);
///@Event 147=Cherimoya |+|Food |Che-|rimoya
	jjObjectPresets[OBJECT::STRAWBERRY].determineCurAnim(ANIM::CUSTOM[7], 46);
///@Event 161=Guanabana |+|Food |Guana-|bana
	jjObjectPresets[OBJECT::EGGPLANT].determineCurAnim(ANIM::CUSTOM[7], 53);
///@Event 141=Salak |+|Food |Salak
	jjObjectPresets[OBJECT::APPLE].determineCurAnim(ANIM::CUSTOM[7], 64);
///@Event 178=Steak |+|Food |Steak
	jjObjectPresets[OBJECT::SANDWICH].determineCurAnim(ANIM::CUSTOM[7], 66);
///@Event 165=Meat 'n' Bone |+|Food |Meat|Bone
	jjObjectPresets[OBJECT::MILK].determineCurAnim(ANIM::CUSTOM[7], 67);

///@Event 111=Lori Block |+|Trigger |Lori |Scen	
	jjOBJ@ myDestructSceneryVariant = jjObjectPresets[OBJECT::CHESHIRE1];
	myDestructSceneryVariant.points = 50;
    myDestructSceneryVariant.playerHandling = HANDLING::SELFCOLLISION;
    myDestructSceneryVariant.behavior = loriBlock;

///@Event 243=Big Grub |+|Object    |Big    |Grub	
	jjAnimSets[ANIM::CUSTOM[10]].load(15, "DD-Anims.j2a"); // dragonfly extra sprites
	jjObjectPresets[OBJECT::AIRBOARD].determineCurAnim(ANIM::CUSTOM[10], 0);
	jjObjectPresets[OBJECT::AIRBOARD].behavior = bigGrub();
	jjSampleLoad(SOUND::INTRO_BRAKE, "big-bite.wav");
	CustomDragonfly(jjObjectPresets[OBJECT::DRAGONFLY]);
///@Event 196=Grub |-|Enemy |Grub | |Stick to:{Ground,Right wall,Ceiling,Left wall}2	
	jjOBJ@ grubPreset = jjObjectPresets[OBJECT::CRAB];
	grubPreset.behavior = grub();
	grubPreset.determineCurAnim(ANIM::CUSTOM[10], 2);
	grubPreset.energy = 1;
	grubPreset.points = 100;

///@Event 42=Swinging Vine |+|Object |Swing |Vine |Sync:3 |Length:4
	jjObjectPresets[OBJECT::SWINGINGVINE].behavior = SyncedVine;

///@Event 217=NPC |+|Object |NPC | |Type:{Jill,Yoman,Chief,Savage (male),Savage (female)}3 |Order ID:5
	jjAnimSets[ANIM::CUSTOM[3]].load(16, "DD-Anims.j2a"); // NPC sprites
	jjAnimSets[ANIM::CUSTOM[4]].load(5, "DD-Anims.j2a"); // dialogue prompt sprite
	jjObjectPresets[OBJECT::EVA].behavior = NPC();

///@Event 127=Parrot |-|Enemy |Parrot	
	jjAnimSets[ANIM::CUSTOM[2]].load(18, "DD-Anims.j2a"); // parrot sprite
	jjObjectPresets[OBJECT::FISH].determineCurAnim(ANIM::CUSTOM[2], 0);
	jjObjectPresets[OBJECT::FISH].behavior = parrot();
	jjObjectPresets[OBJECT::FISH].energy = 1;
	jjObjectPresets[OBJECT::FISH].points = 20;

///@Event 237=Lizard (Heavy Copter) |-|Enemy |Heavy |Fl.Liz
	jjSampleLoad(SOUND::ORANGE_BUBBELSR, "bigcopter.wav");
	jjAnimSets[ANIM::CUSTOM[23]].load(23, "DD-Anims.j2a");
	jjOBJ@ preset = jjObjectPresets[OBJECT::BEEBOY];
	preset.determineCurAnim(ANIM::CUSTOM[23], 3);
	preset.frameID = 0;
	preset.behavior = HeavyCopter();
	preset.energy = 35;
	preset.points = 1000;
	preset.xSpeed = preset.ySpeed = 0;
	preset.scriptedCollisions = true;
	preset.playerHandling = HANDLING::SPECIAL;
	preset.bulletHandling = HANDLING::DETECTBULLET;
	preset.causesRicochet = false;
	preset.isTarget = true;
	preset.isBlastable = true;
	preset.isFreezable = true;
	preset.triggersTNT = true;
	preset.state = STATE::START;
	preset.lightType = LIGHT::NONE;
	preset.direction = 1;
	preset.var[hcNextPhysicalInjury] = 0;
	preset.var[hcNextNearbyPlayerCheck] = 0;
	preset.var[hcTargetPlayerID] = -1;
	preset.var[hcCurrentAngle] = 0; //0 = forwards, 17 = down, 1-16 = turning (each frame is 4 ticks)

///@Event 204=Falling Log (single) |+|Object |Log	
	jjAnimSets[ANIM::CUSTOM[6]].load(10, "DD-Anims.j2a"); // log sprite
    Log(jjObjectPresets[OBJECT::PSYCHPOLE]);
///@Event 79=Log Spawner |+|Object |Log |Start|Amount:4 |Delay:4
	LogSpawner(jjObjectPresets[OBJECT::FASTFEET]);
///@Event 8=Log Resetter |+|Object |Log |Reset

///@Event 191=Gate Log |+|Trigger |Gate|Log |Adjust Y:5|Adjust X:-6
	jjObjectPresets[OBJECT::TUBETURTLE].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[OBJECT::TUBETURTLE].bulletHandling = HANDLING::DESTROYBULLET;
	jjObjectPresets[OBJECT::TUBETURTLE].scriptedCollisions = true;
	jjObjectPresets[OBJECT::TUBETURTLE].beSolid();
	jjObjectPresets[OBJECT::TUBETURTLE].behavior = gatePole;
	jjObjectPresets[OBJECT::TUBETURTLE].determineCurAnim(ANIM::CUSTOM[6], 1);

///@Event 223=Fooby the Kamikaze Watermelon |+|Object |Fooby	
	jjObjectPresets[OBJECT::SPIKEBOLL3D].scriptedCollisions = true;
	jjObjectPresets[OBJECT::SPIKEBOLL3D].playerHandling = HANDLING::SPECIAL;	
	jjObjectPresets[OBJECT::SPIKEBOLL3D].behavior = fooby();
	jjSampleLoad(SOUND::EPICLOGO_EPIC1, "fooby.wav");
	jjAnimSets[ANIM::CUSTOM[5]].load(17, "DD-Anims.j2a");

///@Event 100=Shaman Demon |-|Enemy |Shaman |Demon	
	jjAnimSets[ANIM::CUSTOM[9]].load(20, "DD-Anims.j2a"); // shaman demon sprites
	jjSampleLoad(SOUND::INTRO_INHALE, "nakotak1.wav");
	jjSampleLoad(SOUND::INTRO_UHTURT, "nakotak2.wav");
	jjSampleLoad(SOUND::INTRO_HITTURT, "bonk.wav");
	jjOBJ@ shamanDemonPreset = jjObjectPresets[OBJECT::TUFTURT];
	shamanDemonPreset.behavior = shamanDemon();
	shamanDemonPreset.energy = 15 + (jjDifficulty * 5) ;
	shamanDemonPreset.points = 1000;
///@Event 106=
	stormCloud(jjObjectPresets[OBJECT::RAPIER]);
///@Event 107=
	finalTotem(jjObjectPresets[OBJECT::SPARK]);
	jjObjectPresets[OBJECT::SPARK].energy = 100;
	jjObjectPresets[OBJECT::SPARK].points = 2000;
	
	jjAnimSets[ANIM::BUBBA].load();
	jjAnimSets[ANIM::CUSTOM[11]].load(24, "DD-Anims.j2a"); // Bubba extra sprites
	jjSampleLoad(SOUND::INTRO_MONSTER, "crazy-laugh.wav");
///@Event 186=Destruct Scenery (TNT) |+|Trigger |Bomb |Scen |ID:3	
	jjOBJ@ destPreset = jjObjectPresets[OBJECT::DESTRUCTSCENERYBOMB];
	destPreset.behavior = bombDestEdit;
	destPreset.playerHandling = HANDLING::SPECIAL;
	destPreset.bulletHandling = HANDLING::IGNOREBULLET;
	destPreset.isFreezable = false;
	destPreset.triggersTNT = false; // no TNT in this level but can't hurt
///@Event 38=
	jjOBJ@ spinnerPreset = jjObjectPresets[OBJECT::TNTAMMO3];
	spinnerPreset.behavior = spinner();
	spinnerPreset.playerHandling = HANDLING::SPECIAL;
	spinnerPreset.scriptedCollisions = true;
	spinnerPreset.bulletHandling = HANDLING::IGNOREBULLET;
	spinnerPreset.isTarget = false;
	spinnerPreset.isFreezable = false;
	spinnerPreset.triggersTNT = false;
	spinnerPreset.deactivates = false;
///@Event 105=
	jjObjectPresets[OBJECT::BEE].behavior = livingTotem();
	jjObjectPresets[OBJECT::BEE].determineCurAnim(ANIM::CUSTOM[11], 1);
	jjObjectPresets[OBJECT::BEE].energy = 100;
	jjObjectPresets[OBJECT::BEE].points = 2000;
	jjObjectPresets[OBJECT::BEE].scriptedCollisions = true;
	jjObjectPresets[OBJECT::BEE].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[OBJECT::BEE].var[7] = 0;
	jjObjectPresets[OBJECT::BEE].deactivates = false;
	jjSampleLoad(SOUND::BUMBEE_BEELOOP, "pray1n.wav");
	
///@Event 126=Lava Block |+|Scenery |Lava |Block | Adjust Y:5
	jjAnimSets[ANIM::CUSTOM[12]].load(9, "DD-Anims.j2a"); // visual fx
	jjOBJ@ lavaBlockPreset = jjObjectPresets[OBJECT::FENCER]; // not expecting anyone to put fencers in a level with lava in it
	lavaBlockPreset.behavior = lavaBlock();
	lavaBlockPreset.determineCurAnim(ANIM::DESTSCEN, 4);
	lavaBlockPreset.playerHandling = HANDLING::SPECIAL;
	lavaBlockPreset.scriptedCollisions = true;
	lavaBlockPreset.bulletHandling = HANDLING::IGNOREBULLET;
	lavaBlockPreset.isTarget = false;
	lavaBlockPreset.isFreezable = false;
	lavaBlockPreset.triggersTNT = false;
	lavaBlockPreset.deactivates = true;
	lavaBlockPreset.energy = 50; // just in case

///@Event 205=Fireball Turret  |+|Object    |Fire |Turret  |Adjust Y:5|Adjust X:-6 |Direction:{Left,Right}1 |Speed:3
	jjObjectPresets[OBJECT::DIAMONDUSPOLE].behavior = totemTurret();
	jjObjectPresets[OBJECT::DIAMONDUSPOLE].determineCurAnim(ANIM::CUSTOM[12], 0);

///@Event 225=Sparkle |+|Scenery    |Sparkle
	jjObjectPresets[OBJECT::BEES].behavior = sparkle;
	jjObjectPresets[OBJECT::BEES].determineCurAnim(ANIM::CUSTOM[12], 0);
	jjObjectPresets[OBJECT::BEES].playerHandling = HANDLING::PARTICLE;
	jjObjectPresets[OBJECT::BEES].bulletHandling = HANDLING::IGNOREBULLET;
	jjObjectPresets[OBJECT::BEES].triggersTNT = false;
	jjObjectPresets[OBJECT::BEES].isTarget = false;
	jjObjectPresets[OBJECT::BEES].isFreezable = false;

///@Event 103=Little Bird |+|Scenery |Bird |Bird	
	jjSampleLoad(SOUND::INTRO_UP1, "bird-takeoff1.wav");
	jjSampleLoad(SOUND::INTRO_UP2, "bird-takeoff2.wav");
	jjSampleLoad(SOUND::INTRO_SKI, "bird-takeoff3.wav");
	jjAnimSets[ANIM::CUSTOM[25]].load(25, "DD-Anims.j2a");
	jjOBJ@ presetObject = jjObjectPresets[OBJECT::DRAGON];
	presetObject.behavior = littleBird();
	presetObject.playerHandling = HANDLING::PARTICLE;
	presetObject.bulletHandling = HANDLING::IGNOREBULLET;
	presetObject.isTarget = false;
	presetObject.isFreezable = false;
	presetObject.triggersTNT = false;
	presetObject.deactivates = true;

///@Event 113=Guerrilla Doofus |-|Enemy |Guerla |Doofus
	jjAnimSets[ANIM::PLUS_SCENERY].load();
	jjAnimSets[ANIM::CUSTOM[22]].load(22, "DD-Anims.j2a");
	jjSampleLoad(SOUND::INTRO_GREN2, "rustle.wav");
	jjOBJ@ guerrillaPreset = jjObjectPresets[OBJECT::HATTER];
	guerrillaPreset.behavior = guerrillaDoofus();
	guerrillaPreset.energy = 5;
	guerrillaPreset.points = 500;

///@Event 254=Delet This |-|Modifier |NOT |v
    deleteUnwantedEvents();
	if (jjDifficulty > 0) jjObjectPresets[OBJECT::SPIKEBOLL].bulletHandling = HANDLING::IGNOREBULLET;

///@Event 190=Simon Says |+|Trigger |Simon |Says |Color:2	
	jjSampleLoad(SOUND::INTRO_LAND, "tineep.wav");
	jjANIMSET@ simonSaysSet = jjAnimSets[ANIM::CUSTOM[14]];
	simonSaysSet.allocate(array<uint>={1,9,9,9,9});
	jjPIXELMAP bush((10 * 32), (100 * 32), 32, 32, 4);
	jjANIMFRAME@ bushframe = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::CUSTOM[14]].firstAnim].firstFrame];
	bush.save(bushframe);
	bushframe.hotSpotX = -bushframe.width/2;
	bushframe.hotSpotY = -bushframe.height/2;
	for (int a = 100; a < 104; a++) {
		for (int b = 0; b < 10; b++) {
		jjPIXELMAP evileye((b * 32), (a * 32), 32, 32, 4);
		jjANIMFRAME@ eyeframe = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::CUSTOM[14]].firstAnim + a - 99].firstFrame + b];
		evileye.save(eyeframe);
		eyeframe.hotSpotX = -eyeframe.width/2;
		eyeframe.hotSpotY = -eyeframe.height/2;
		}
	}
	jjOBJ@ simonSaysPreset = jjObjectPresets[OBJECT::RAVEN];
	simonSaysPreset.behavior = simonSays();
	simonSaysPreset.determineCurAnim(ANIM::CUSTOM[14], 0);
	simonSaysPreset.playerHandling = HANDLING::SPECIAL;
	simonSaysPreset.bulletHandling = HANDLING::HURTBYBULLET;
	simonSaysPreset.scriptedCollisions = true;
	simonSaysPreset.isTarget = true;
	simonSaysPreset.isFreezable = false;
	simonSaysPreset.triggersTNT = true;
	simonSaysPreset.deactivates = true;
	simonSaysPreset.energy = 50;

///@Event 252=Trigger Target |+|Trigger |Trig |Targ |TriggerID:5|Set to:{On,Off}1|Direction:{Down,Right,Up,Left}2	
	jjAnimSets[ANIM::CUSTOM[15]].load(14, "DD-Anims.j2a");
	jjOBJ@ triggerTargetPreset = jjObjectPresets[OBJECT::CAT];
	triggerTargetPreset.behavior = triggerTarget();
	triggerTargetPreset.determineCurAnim(ANIM::CUSTOM[15], 0);
	triggerTargetPreset.playerHandling = HANDLING::SPECIAL;
	triggerTargetPreset.scriptedCollisions = true;
	triggerTargetPreset.isTarget = true;
	triggerTargetPreset.isFreezable = false;
	triggerTargetPreset.triggersTNT = true;
	triggerTargetPreset.deactivates = true;
	
// mountain range pixelmap:
	jjPIXELMAP mount("JungUlt-MountRange.png");
	jjANIMFRAME@ helmut3 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim].firstFrame + 2];
	mount.save(helmut3);
	helmut3.hotSpotX = 0;
	helmut3.hotSpotY = 0;

// savanna pixelmap:
	jjPIXELMAP savanna("JungUlt-Savanna.png");
	jjANIMFRAME@ helmut4 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim].firstFrame + 3];
	savanna.save(helmut4);
	helmut4.hotSpotX = 0;
	helmut4.hotSpotY = 0;

// clouds near pixelmap:
	jjPIXELMAP clouds1("JungUlt-Clouds1.png");
	jjANIMFRAME@ helmut6 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim + 1].firstFrame + 1];
	clouds1.save(helmut6);
	helmut6.hotSpotX = 0;
	helmut6.hotSpotY = 0;
	
// clouds far pixelmap:
	jjPIXELMAP clouds2("JungUlt-Clouds2.png");
	jjANIMFRAME@ helmut5 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim + 1].firstFrame];
	clouds2.save(helmut5);
	helmut5.hotSpotX = 0;
	helmut5.hotSpotY = 0;
	
// village silhouette pixelmap:
	jjPIXELMAP village("JungUlt-Village.png");
	jjANIMFRAME@ helmut7 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim + 1].firstFrame + 2];
	village.save(helmut7);
	helmut7.hotSpotX = 0;
	helmut7.hotSpotY = 0;

// foreground piece 1 pixelmap:
	jjPIXELMAP fore1("JungUlt-Foreground1.png");
	jjANIMFRAME@ helmut1 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim].firstFrame];
	fore1.save(helmut1);
	helmut1.hotSpotX = 0;
	helmut1.hotSpotY = 0;
	
// foreground piece 2 pixelmap:
	jjPIXELMAP fore2("JungUlt-Foreground2.png");
	jjANIMFRAME@ helmut2 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim].firstFrame + 1];
	fore2.save(helmut2);
	helmut2.hotSpotX = 0;
	helmut2.hotSpotY = 0;

// foreground piece 3 pixelmap:
	jjPIXELMAP fore3("JungUlt-Foreground3.png");
	jjANIMFRAME@ helmut8 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim + 1].firstFrame + 3];
	fore3.save(helmut8);
	helmut8.hotSpotX = 0;
	helmut8.hotSpotY = 0;

// foreground piece 4 pixelmap:
	jjPIXELMAP fore4("JungUlt-Foreground4.png");
	jjANIMFRAME@ helmut9 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim + 1].firstFrame + 4];
	fore4.save(helmut9);
	helmut9.hotSpotX = 0;
	helmut9.hotSpotY = 0;

// foreground piece 5 pixelmap:
	jjPIXELMAP fore5("JungUlt-Foreground5.png");
	jjANIMFRAME@ helmut10 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim + 1].firstFrame + 5];
	fore5.save(helmut10);
	helmut10.hotSpotX = 0;
	helmut10.hotSpotY = 0;

	jjLAYER@ clouds3 = MLLE::GetLayer("Clouds Low");
	clouds3.spriteParam = 224;
	clouds3.spriteMode = SPRITE::BLEND_NORMAL;

	jjLAYER@ sky = MLLE::GetLayer("Sky");
	sky.xSpeed = 0.02;
	sky.ySpeed = 0.02;
	sky.xAutoSpeed = -0.09;
	sky.yAutoSpeed = -0.01;
	sky.xSpeedModel = LAYERSPEEDMODEL::NORMAL;
	sky.ySpeedModel = LAYERSPEEDMODEL::NORMAL;
	
	jjLAYER@ water = MLLE::GetLayer("Water");
	water.spriteMode = SPRITE::TRANSLUCENT;

	jjPIXELMAP rain(32,32);
	for (uint x = 0; x < rain.width; ++x) {
		for (uint y = 0; y < rain.height; ++y) {
			if (x == 16) { //draw in the middle of the tile, xPixel 16
//				if (y <= 24) rain[x,y] = 75; //if at yPixel 24 or less, use color 75
//				else rain[x,y] = 74; //use color 74 for yPixels 25-32
				rain[x,y] = 207 - y;
			} else {
			rain[x,y] = 0;
			}
		}
	}
  
	jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::COMMON].firstAnim + 2];
	for (uint frameID = 0; frameID < anim.frameCount; ++frameID) {
		jjANIMFRAME@ frame = jjAnimFrames[anim.firstFrame + frameID];
		rain.save(frame);
		frame.hotSpotX = -frame.width/2;
		frame.hotSpotY = -frame.height;
	}
	
// totem tile backups:
	for (int i = 0; i < 4; i++) {
		jjPIXELMAP tile(totemTile[i]);
		jjANIMFRAME@ block = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::PLUS_SCENERY].firstAnim + 4].firstFrame + i];
		tile.save(block);
	}
}

void onLevelBegin() {
	MLLE::SpawnOffgrids();
	setTimer();
}

void onLevelReload() {
	MLLE::SpawnOffgridsLocal();
	YomanPalette();
	scoreSeconds = storeScoreSeconds;
	customBossHealthBar.ready = false;
	if (foughtBubba) {
		jjMusicLoad("DD-JungleMix.it");
		bossPhase = 0;
		bubbaWait = false;
		totemActive = false;
	}
	simonSaysRound = simonDelay = simonCounter = simonLastRoundOutcome = 0;
	simonSaysYourTurn = false; 
}

void onMain() {
	layer5_offset += 0.29;
	layer8_offset += 0.1;
	if (layer5_offset > 992) layer5_offset = 0;
	if (layer8_offset > 256) layer8_offset = 0;
	
	if (jjDifficulty == 0) oneUpsIntoBlueGems();
	if ((jjGameTicks % 70) == 0 && !levelOver && CurrentPopup is null) scoreSeconds++;	
	rankingSetup();
	if (levelOver) updateFade();
	if (gateTimer > 0) {
		gateChannel = jjSampleLooped(174 * 32, 65 * 32, SOUND::COMMON_LAND1, gateChannel);
		gateTimer = gateTimer - 1;
	}
	if (gateTimer < 0) {
		gateChannel = jjSampleLooped(174 * 32, 65 * 32, SOUND::COMMON_LAND1, gateChannel);
		gateTimer = gateTimer + 1;
	}
	if (jjIsSnowing) rainChannel = jjSampleLooped(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::ORANGE_BUBBELSL, rainChannel, 15);
		
	if (simonDelay > 0) { 
		simonDelay--;
	}
	else if (jjTriggers[3] && !simonSaysYourTurn) {
				if (simonCounter == 0) {
					simonDelay = 150;
					if (simonSaysRound >= simonSaysSequence.length() ) {
						jjSample(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::INTRO_BOEM1);
					}
					simonCounter = 1;
				}
				else if (simonCounter == simonSaysRound+2) {
					simonSaysYourTurn = true;
					simonCounter = 0;
				}
				else if (simonCounter > 0) {
					simonLastRoundOutcome = 0;
					if (simonSaysRound >= simonSaysSequence.length() ) {
						jjSample(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::COMMON_ITEMTRE);					
						jjTriggers[2] = true;
						jjTriggers[3] = false;
						for (int i = 1; i < jjObjectCount; i++) {
							jjOBJ@ obj = jjObjects[i];
							if (obj.eventID == OBJECT::RAVEN) {
								obj.state = STATE::KILL;
							}
						}
					}
					else {
						simonDelay = 80 - (simonSaysRound * 12); //how many ticks before next note, decreases to zero above
						for (int i = 1; i < jjObjectCount; i++) {
							jjOBJ@ obj = jjObjects[i];
							if (obj.eventID == OBJECT::RAVEN && obj.var[2] == simonSaysSequence[simonCounter-1]) {
								if (obj.state == STATE::ACTION) obj.var[1] = 0;
								else obj.state = STATE::ACTION;
							}
						}
					}
					simonCounter++;
				}
	}
}

void onPlayer(jjPLAYER@ player) {
	dialogueInterface(player);
	rankingInterface(player);
	if (jjGameTicks % (288 - (jjDifficulty * 40)) == 0 && jjTriggers[0]) {
		jjOBJ@ parr = jjObjects[jjAddObject(OBJECT::FISH, (jjRandom()%2 > 0) ? (player.xPos + 420) : (player.xPos - 420), player.yPos + (jjRandom()%200) - 100)]; // DUDE WEED LMAO
		parr.direction = (parr.xPos > player.xPos) ? -1 : 1;
	}
	if (bossPhase == 6 || bossPhase == 0) player.bossActivated = false;
	if (player.bossActivated && totemActive) {
		for (int i = 1; i < jjObjectCount; i++) {
			jjOBJ@ obj = jjObjects[i];
			if ((obj.eventID == OBJECT::BEE || obj.eventID == OBJECT::SPARK) && obj.state == STATE::KILL) {
					bossPhase += 1;
					totemActive = false;
			}
/*			if (obj.eventID == OBJECT::BEE || obj.eventID == OBJECT::SPARK) {
				if (obj.state == STATE::KILL) {
					bossPhase += 1;
					totemActive = false;
				}
				else {
					play.boss = obj.objectID;
					customBossHealthBar.initialize(obj.objectID);
				}
			}*/
		}
	}
	for (int i = 0; i < 1024; i++) { //loop through the global array jjParticles[1024]
		jjPARTICLE@ particle = jjParticles[i];
		if (particle.type == PARTICLE::RAIN) {
			particle.xSpeed = 0; //make rain fall straight down
			particle.ySpeed = player.ySpeed < 0? 10 : int(10 + player.ySpeed); //the rain speed accounts for differences in the player speed, and so won't appear to fall more slowly when the player is falling
		}
	}
	if (simonCounter == 0 && simonSaysRound >= simonSaysSequence.length() && jjTriggers[3] && !simonSaysYourTurn) {
		int groinky = int(jjRandom() % 8 + 8);
		for (int i = 0; i < groinky; i++) {
		jjPARTICLE@ particle = jjAddParticle(PARTICLE::STAR);
			particle.xPos = player.cameraX + (jjSubscreenWidth / 2);
			particle.yPos = player.cameraY + jjSubscreenHeight - (jjSubscreenHeight / 18);
			particle.xSpeed = int(jjRandom() % 9 - 4); // don't ask me why it needs to be converted to int. it just does
			particle.ySpeed = int(jjRandom() % 9 - 4);
			particle.star.angularSpeed = int(jjRandom() % 4 + 1);
			particle.star.color = int(jjRandom() % 3 + 64);
			particle.star.size = int(jjRandom() % 16 + 24);
		}
	}
}

bool onDrawScore(jjPLAYER@ player, jjCANVAS@ canvas) {
	if (jjTriggers[3] && simonSaysRound < simonSaysSequence.length()) {
		if (simonSaysYourTurn) bottomTextXD("Repeat", canvas);
		// else if (simonCounter < 2 && simonSaysRound > 0 && simonDelay > 50 && simonLastRoundOutcome == 1)
		else if (simonLastRoundOutcome == 1 && simonDelay > 50)
				bottomTextXD("Good!", canvas);
		else if (simonLastRoundOutcome == 2 && simonDelay > 50)
				bottomTextXD("Try again!", canvas);
		else bottomTextXD("Listen", canvas);
	}
	if (chiefNear) {
		canvas.drawSprite((jjSubscreenWidth / 2) - 16, jjSubscreenHeight - (jjSubscreenHeight / 32), ANIM::PICKUPS, 2, 0);
		canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight - (jjSubscreenHeight / 32), "x" + player.gems[GEM::PURPLE], smallScreen ? STRING::SMALL : STRING::MEDIUM);
	}
	if (CurrentPopup !is null) {
		CurrentPopup.Draw(canvas);
		return !CurrentPopup.DrawHUD();
	}
	if (levelOver) {
		drawFade(player, canvas);
		if (rankLine < 6) rankingDisplay(player, canvas);
	}
	if (rankLine >= 6) return true;
	else return false;
}

bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	if (jjColorDepth < 16) {
		canvas.drawRectangle(0, 0, jjSubscreenWidth, jjSubscreenHeight, 79);
		canvas.drawString((jjSubscreenWidth / 16), (jjSubscreenHeight / 2) - (jjSubscreenHeight / 8), "This level does not", smallScreen ? STRING::SMALL : STRING::MEDIUM);
		canvas.drawString((jjSubscreenWidth / 16), (jjSubscreenHeight / 2) - (jjSubscreenHeight / 16), "support 8-bit color depth.", smallScreen ? STRING::SMALL : STRING::MEDIUM);
		canvas.drawString((jjSubscreenWidth / 16), (jjSubscreenHeight / 2) + (jjSubscreenHeight / 16), "Please go to options menu", smallScreen ? STRING::SMALL : STRING::MEDIUM);
		canvas.drawString((jjSubscreenWidth / 16), (jjSubscreenHeight / 2) + (jjSubscreenHeight / 8), "and switch to 16-bit color.", smallScreen ? STRING::SMALL : STRING::MEDIUM);
	}
	if (rankLine >= 6) return true;
	else return CurrentPopup !is null && !CurrentPopup.DrawHUD();
}

bool onDrawHealth(jjPLAYER@ player, jjCANVAS@ canvas) {
	if (rankLine >= 6) return true;
	else return CurrentPopup !is null && !CurrentPopup.DrawHUD();
}

bool onDrawLives(jjPLAYER@ player, jjCANVAS@ canvas) { 
    if (customBossHealthBar.ready && !bubbaWait) customBossHealthBar.draw(canvas);
	if (jjDifficulty == 0 && CurrentPopup is null) {
		return true;
	}
	if (rankLine >= 6) return true;
    else return CurrentPopup !is null && !CurrentPopup.DrawHUD();
}

bool onDrawPlayerTimer(jjPLAYER@ play, jjCANVAS@ canvas) { return CurrentPopup !is null && !CurrentPopup.DrawHUD(); }

//applying village to layer:
void onDrawLayer2(jjPLAYER@ player, jjCANVAS@ canvas) {
	for (int i = 0; i < 2; i++) canvas.drawSprite(192 + (384 * i), 384, ANIM::HELMUT, 1, 2);
}

// putting foreground eyecandy on layer:
void onDrawLayer3(jjPLAYER@ player, jjCANVAS@ canvas) {
	canvas.drawSprite(0, (33 * 32), ANIM::HELMUT, 0, 0);
	canvas.drawSprite(269, (100 * 32), ANIM::HELMUT, 0, 1, -1);
	canvas.drawSprite((440 * 32), (75 * 32), ANIM::HELMUT, 0, 0, -1);
	canvas.drawSprite((440 * 32) - 269, (50 * 32), ANIM::HELMUT, 0, 1);
	canvas.drawSprite((170 * 32), 0, ANIM::HELMUT, 1, 3);
	canvas.drawSprite((298 * 32), 0, ANIM::HELMUT, 1, 3, -1);
	canvas.drawSprite((428 * 32), (118 * 32), ANIM::HELMUT, 1, 4);
	canvas.drawSprite((202 * 32), (122 * 32), ANIM::HELMUT, 1, 4);
	canvas.drawSprite((35 * 32), (115 * 32), ANIM::HELMUT, 1, 4, -1);
	canvas.drawSprite((239 * 32), 30, ANIM::HELMUT, 1, 5);
	canvas.drawSprite((128 * 32), (11 * 32), ANIM::HELMUT, 1, 5);
}

void onDrawLayer4(jjPLAYER@ player, jjCANVAS@ canvas) {
	if (drawBubba == 1) canvas.drawSprite((324 * 32), (30 * 32) + 10, ANIM::CUSTOM[11], 0, (jjGameTicks/10) & 3, 1);
	if (drawBubba == 2) canvas.drawSprite((324 * 32), (30 * 32) + 10, ANIM::BUBBA, 5, 0, -1);
}

// applying clouds near to layer:
void onDrawLayer5(jjPLAYER@ player, jjCANVAS@ canvas) {
	for (int i = 0; i < 8; i++) canvas.drawSprite((992 * i) + int(layer5_offset) - 1984, 0, ANIM::HELMUT, 1, 1); // Orwell was right
}

// applying savanna to layer:
void onDrawLayer6(jjPLAYER@ player, jjCANVAS@ canvas) { // don't forget there must be at least one normal tile in the layer or the bool will return false
	for (int i = -1; i < 9; i++) // you might want a bigger number of loops if your level is very wide
	canvas.drawSprite(0 + (384 * i), 80, ANIM::HELMUT, 0, 3); // you don't really HAVE to stick to the 32x32p grid but I just like to do it
}

// applying mountain range to layer:
void onDrawLayer7(jjPLAYER@ player, jjCANVAS@ canvas) {
	for (int i = -1; i < 9; i++) canvas.drawSprite(0 + (352 * i), 192, ANIM::HELMUT, 0, 2);
}
	
// applying clouds far to layer:
void onDrawLayer8(jjPLAYER@ player, jjCANVAS@ canvas) {
	for (int i = 0; i < 8; i++) canvas.drawSprite((256 * i) + int(layer8_offset) - 512, 2, ANIM::HELMUT, 1, 0);
}

class CustomBossHealthBar {
    int maxHealth;
    bool ready = false;
    jjOBJ@ boss;
    jjPIXELMAP healthBar(jjAnimFrames[jjAnimations[jjAnimSets[ANIM::BOSS].firstAnim].firstFrame + 1]);
    
    void initialize(jjOBJ@ boss)
    {
        @this.boss = @boss;
        maxHealth = boss.energy;
        ready = true;
    }
    
    void draw(jjCANVAS@ canvas)
    {
        if (boss.energy <= 0 && bubbaExitStage) return;
        
        canvas.drawSprite(jjResolutionWidth / 2, 4, ANIM::BOSS, 0, 0);
        
        if (bossPhase <= 4) canvas.drawString(jjResolutionWidth / 2 + 90, 20, "x" + (6 - bossPhase), STRING::SMALL, STRING::BOUNCE);
        
        for (int x = 0; x < 1 + int(ceil((healthBar.width * bossHealth) / maxHealth)); ++x)
            for (uint y = 0; y < healthBar.height; ++y)
                if (healthBar[x, y] != 0)
                    canvas.drawPixel(jjResolutionWidth / 2 + x - 69, 4 + y + 17, healthBar[x, y] + 16 * (5 - bossPhase));
    }
}

void bubbaEdit(jjOBJ@ obj) {

// STATE::START (0) - start
// STATE::DEACTIVATE (4) - deactivated
// STATE::JUMP (6) - jump up
// STATE::DONE (17) - dead
// STATE::FALL (19) - jump down
// STATE::ATTACK (22) - shoot fireball
// STATE::FREEZE (23) - frozen
// STATE::IDLE (28) - choosing action (jump, shoot or tornado)
// STATE::EXTRA (29) - land
// STATE::ROTATE (34) - tornado

	if (totemActive) {
		obj.isTarget = false;
		if (obj.age <= 24 && bossPhase < 4) {
			jjOBJ@ spin = jjObjects[jjAddObject(OBJECT::TNTAMMO3, obj.xPos, obj.yPos)];
			spin.creatorID = obj.objectID;
			spin.creatorType = CREATOR::OBJECT;
			spin.var[2] = bossPhase; // shield type
			spin.var[1] = obj.age; // number of object created (need 24 for full circle)
			obj.age++;
		}
		for (int i = 0; i < jjObjectCount; ++i) {
				if (jjObjects[i].creatorID == uint(obj.objectID) && jjObjects[i].eventID == 38) {
				jjObjects[i].xPos = obj.xPos + jjSin(jjGameTicks * 4 + (48 * jjObjects[i].var[1])) * (1.7 * 32);
				jjObjects[i].yPos = obj.yPos + jjCos(jjGameTicks * 4 + (48 * jjObjects[i].var[1])) * (1.7 * 32);
				}
		}
	}
	else if (obj.state != STATE::SPRING && bossPhase < 5) {
		for (int i = 0; i < jjObjectCount; ++i) {
			if (jjObjects[i].creatorID == uint(obj.objectID) && jjObjects[i].eventID == 38) jjObjects[i].state = STATE::KILL;
		}
		obj.age = 0;
		obj.var[6] = 120; // god I hope that's not used
		obj.state = STATE::SPRING; // start summoning if no totem is active and there's still totems left
	}
	if (obj.state == STATE::SPRING) { // summon totem state
		obj.putOnGround();
		bubbaChannel = jjSampleLooped(obj.xPos, obj.yPos, SOUND::RAPIER_GOSTLOOP, bubbaChannel);
		obj.determineCurAnim(ANIM::CUSTOM[11], 0);
		obj.frameID = (jjGameTicks/10) & 3;
		obj.determineCurFrame();
		obj.draw();
				jjPIXELMAP tile(totemTile[bossPhase - 1]);
				for (int x = 0; x < 32; x++) {
					for (int y = 0; y < 32; y++) {
						uint8 color = tile[x,y];
						if (color > 1) {
							if (color >= 88) color -= 72; //loop from purple back to green
							else color += 8;
							tile[x,y] = color;
						}
					}
				}
				tile.save(totemTile[bossPhase - 1]);
		if (obj.var[6] == 0) {
			jjPIXELMAP backup(jjAnimFrames[jjAnimations[jjAnimSets[ANIM::PLUS_SCENERY].firstAnim + 4].firstFrame + (bossPhase - 1)]);
			backup.save(totemTile[bossPhase - 1]);
			if (bossPhase == 4) {
				jjOBJ@ lastTotem = jjObjects[jjAddObject(OBJECT::SPARK, totemPlacement[bossPhase - 1][0] * 32 + 16, totemPlacement[bossPhase - 1][1] * 32 + 16)];
				jjLocalPlayers[obj.findNearestPlayer(1024 * 1024)].boss = lastTotem.objectID; // hopefully big enough range to contain the whole arena??
				customBossHealthBar.initialize(lastTotem);
			}
			else {
				jjOBJ@ totem = jjObjects[jjAddObject(OBJECT::BEE, totemPlacement[bossPhase - 1][0] * 32 + 16, totemPlacement[bossPhase - 1][1] * 32 + 16)];
				totem.var[6] = bossPhase;
				jjLocalPlayers[obj.findNearestPlayer(1024 * 1024)].boss = totem.objectID; // hopefully big enough range to contain the whole arena??
				customBossHealthBar.initialize(totem);
			}
			for (int i = 0; i < jjObjectCount; ++i) {
				if (jjObjects[i].eventID == OBJECT::DESTRUCTSCENERYBOMB && jjObjects[i].state != STATE::DONE && jjObjects[i].var[2] == bossPhase) jjObjects[i].state = STATE::KILL;
			}
			totemActive = true;
			if (obj.xPos > jjLocalPlayers[obj.findNearestPlayer(1024 * 1024)].xPos) obj.direction = -1;
			else obj.direction = 1;
			obj.state = STATE::EXTRA;
		} else obj.var[6] = obj.var[6] - 1;
	}
	if (bossPhase == 4 && totemActive) {
		obj.light = 15;
		obj.lightType = LIGHT::RING;
	} else obj.light = 0;
	if (bossPhase == 5) { // can only be hurt normally if all totems are defeated and isn't in cutscene
		obj.isTarget = true;
		obj.bulletHandling = HANDLING::HURTBYBULLET;
		obj.playerHandling = HANDLING::ENEMY;
		obj.scriptedCollisions = false;
		jjLocalPlayers[obj.findNearestPlayer(512 * 512)].boss = obj.objectID;
		bossHealth = obj.energy;
	} else {
		obj.bulletHandling = HANDLING::DETECTBULLET;
		obj.playerHandling = HANDLING::SPECIAL;
		obj.scriptedCollisions = true;
	}
	if (obj.energy <= 7 && obj.isActive && bossPhase == 5) {
		obj.energy = 7; // just in case
		bossPhase = 6; // cutscene & end level
		if (!bubbaWait) {
			obj.putOnGround();
			obj.state = STATE::FADEOUT;
			bubbaWait = true;
			endDialog();
		}
	}
	if (obj.state == STATE::FADEOUT) {
		obj.behave(BEHAVIOR::MONITOR); // stop doing everything and SIT YOUR ASS DOWN
		if (bubbaExitStage) {
			obj.determineCurAnim(ANIM::BUBBA, 6);
			obj.frameID = (jjGameTicks/12) & 11;
			obj.direction = 1;
			obj.xPos += 4;
			bubbaChannel = jjSampleLooped(obj.xPos, obj.yPos, SOUND::BUBBA_TORNADOATTACK2, bubbaChannel);
		} else {
			if (obj.xPos > jjLocalPlayers[obj.findNearestPlayer(1024 * 1024)].xPos) obj.direction = -1;
			else obj.direction = 1;
			obj.determineCurAnim(ANIM::BUBBA, 5);
			obj.frameID = 0;
			obj.putOnGround();
			obj.direction = jjLocalPlayers[obj.findNearestPlayer(512 * 256)].direction * -1; // FACE ME!!
		}
		obj.determineCurFrame();
		obj.draw();
	}
	else obj.behave(BEHAVIOR::BUBBA);
}

class spinner : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				obj.var[2] = bossPhase; // probably redundant
				obj.var[3] = jjRandom()%2; // randomize sprite
				obj.var[4] = jjRandom()%2; // randomize sprite direction
				obj.state = STATE::IDLE;
			case STATE::IDLE:
				obj.causesRicochet = true;
				obj.direction = obj.var[4] - 1;
				obj.frameID = (jjGameTicks/5) & 6 + obj.var[1];
				obj.determineCurFrame();
				if (obj.var[2] != bossPhase) obj.state = STATE::KILL;
				break;
			case STATE::DEACTIVATE:
				obj.deactivate();
				break;
			case STATE::KILL:
				if (obj.isActive) {
					jjOBJ@ poof = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)];
					poof.determineCurAnim(ANIM::PICKUPS, 86);
				}
				obj.delete();
				break;
		}
	}
	void onDraw(jjOBJ@ obj) {
		if (obj.var[2] == 1) obj.determineCurAnim(ANIM::AMMO, (obj.var[3] == 0) ? 13 : 55);
//		if (obj.var[2] == 2) obj.determineCurAnim(ANIM::AMMO, (obj.var[3] == 0) ? 12 : 75); // line backup in case anims below don't look better
		if (obj.var[2] == 2) obj.determineCurAnim(ANIM::PICKUPS, (obj.var[3] == 0) ? 27 : 85);
		if (obj.var[2] == 3) obj.determineCurAnim(ANIM::AMMO, (obj.var[3] == 0) ? 8: 71);
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::TRANSLUCENT);
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
        if (bullet is null) {
            if (force <= 0) {
                player.hurt(2);
            }
            return true;
        }
        return false;
    }
}

void bombDestEdit(jjOBJ@ obj) {
	obj.var[2] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,3);
	obj.behave(BEHAVIOR::DESTRUCTSCENERY);
}

class livingTotem : jjBEHAVIORINTERFACE {
int attackCD = 90 - (jjDifficulty * 15);
int lastHitID;
	void onBehave(jjOBJ@ obj) {
		bossHealth = obj.energy;
		switch (obj.state) {
			case STATE::START:
				obj.var[5] = 0;
				obj.state = STATE::IDLE;
			case STATE::IDLE:
				obj.behave(BEHAVIOR::BEE);
				obj.lightType = LIGHT::LASER;
				obj.light = 15;
				if (obj.xOrg > jjLocalPlayers[obj.findNearestPlayer(1024 * 1024)].xPos) obj.xOrg -= 1;
				if (obj.xOrg < jjLocalPlayers[obj.findNearestPlayer(1024 * 1024)].xPos) obj.xOrg += 1;
				if (obj.yOrg > jjLocalPlayers[obj.findNearestPlayer(1024 * 1024)].yPos) obj.xOrg -= 1;
				if (obj.yOrg < jjLocalPlayers[obj.findNearestPlayer(1024 * 1024)].yPos) obj.xOrg += 1; // this looks pretty stupid but should work
				break;
			case STATE::ATTACK:
				obj.lightType = LIGHT::LASER;
				obj.light = 15;
				obj.xOrg = jjLocalPlayers[obj.findNearestPlayer(256 * 256)].xPos;
				obj.yOrg = jjLocalPlayers[obj.findNearestPlayer(256 * 256)].yPos;
				obj.yPos += 128;
				obj.behave(BEHAVIOR::BEE, false);
				obj.yPos -= 128;
				if (obj.counter < 10) obj.counter += 100; // never stop!!!
				if (obj.var[5] <= 0 && (obj.xPos > jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos - 128 || obj.xPos < jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos + 128)) {
					if (obj.var[6] == 1) {
						jjSample(obj.xPos, obj.yPos, SOUND::AMMO_FIREGUN1A);
						jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::FIRESHIELDBULLET)];
						bullet.ySpeed = 5;
						bullet.xSpeed = 0;
						bullet.xAcc = 0;
						bullet.yAcc = 0.2;
						bullet.creatorID = obj.objectID;
						bullet.creatorType = CREATOR::OBJECT;
						bullet.playerHandling = HANDLING::ENEMYBULLET;
						if (jjDifficulty < 2) bullet.animSpeed = 1;
						else bullet.animSpeed = jjDifficulty;
						obj.var[5] = attackCD;
					}
					if (obj.var[6] == 2) {
						jjSample(obj.xPos, obj.yPos, SOUND::COMMON_ELECTRIC1); // I'll optimize this later (maybe)
						jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::LIGHTNINGSHIELDBULLET)];
						bullet.ySpeed = 5;
						bullet.xSpeed = 0;
						bullet.xAcc = 0;
						bullet.yAcc = 0.2;
						bullet.creatorID = obj.objectID;
						bullet.creatorType = CREATOR::OBJECT;
						bullet.playerHandling = HANDLING::ENEMYBULLET;
						if (jjDifficulty < 2) bullet.animSpeed = 1;
						else bullet.animSpeed = jjDifficulty;
						obj.var[5] = attackCD;
					}
				}
				else obj.var[5] = obj.var[5] - 1;
				if (obj.var[6] == 3 && (obj.xPos > jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos - 256 || obj.xPos < jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos + 256) && jjGameTicks % 20 == 0) {
					int rand = jjRandom()%2;
					if (rand==0)
					jjSample(obj.xPos, obj.yPos, SOUND::AMMO_BLUB1);
					else jjSample(obj.xPos, obj.yPos, SOUND::AMMO_BLUB2);
						jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::WATERSHIELDBULLET)];
						bullet.ySpeed = 5;
						bullet.xSpeed = jjRandom() % 9 - 4;
						bullet.xAcc = obj.xAcc;
						bullet.yAcc = 0.2;
						bullet.creatorID = obj.objectID;
						bullet.creatorType = CREATOR::OBJECT;
						bullet.playerHandling = HANDLING::ENEMYBULLET;
						if (jjDifficulty < 2) bullet.animSpeed = 1;
						else bullet.animSpeed = jjDifficulty;
				}
				break;
			case STATE::FREEZE:
				if (obj.freeze == 0) {
					obj.unfreeze(0);
					obj.state = obj.oldState;
				}
				else {
					if (obj.freeze >= 5) obj.freeze -= 5;
					else obj.freeze--;
				}
				break;
			case STATE::DEACTIVATE:
				obj.deactivate();
				break;
			case STATE::KILL:
				givePlayerPointsForObject(jjLocalPlayers[lastHitID], obj);
				obj.particlePixelExplosion(0);
				obj.delete();
				break;
		}
		if (obj.energy <= 0) obj.state = STATE::KILL;
	}
	void onDraw(jjOBJ@ obj) {
		if (obj.state == STATE::FREEZE) {
			jjPARTICLE@ particle = jjAddParticle(PARTICLE::SMOKE);
				if (particle !is null) {
					particle.xPos = obj.xPos;
					particle.yPos = obj.yPos;
				}
		}
		obj.draw();
/*		if ((jjGameTicks & 63) == 0) { // leaving this out I think... too much crap on screen
			const float dy = jjObjects[obj.creatorID].yPos - obj.yPos, dx = jjObjects[obj.creatorID].xPos - obj.xPos; //based on Pos, not Org, so we can't precalculate these
			const auto angle = atan2(dy, dx);
			jjOBJ@ pulse = jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, behavior: BEHAVIOR::INACTIVE)];
			pulse.var[6] = obj.var[6];
			pulse.xSpeed = cos(angle) * 2;
			pulse.ySpeed = sin(angle) * 2;
			pulse.counter = int(sqrt((dx*dx) + (dy*dy)) / 2);
			pulse.curFrame = jjAnimations[jjAnimSets[ANIM::AMMO] + 8] + 1;
			pulse.lightType = LIGHT::POINT2;
			pulse.behavior = TriggerPulse;
			pulse.age = jjRandom() & 511;
		}*/
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) { // copied from Violet I hope this works lol
		if (bullet !is null) {
			if ((bullet.var[6] & 16) == 0) { //not a fireball
				bullet.state = STATE::EXPLODE;
			}
			if (obj.freeze != 0) {
				if (force < 3)
					force = 3;
				obj.unfreeze(0);
			}
			obj.energy -= bullet.animSpeed;
			obj.justHit = 5;
		} else {
			if (force == 0) { //collision
				if (obj.state != STATE::FREEZE)
					player.hurt();
			} else {
				if (force == 1) { //sugar rush/buttstomp
					bool bounce = true;
					if (player.buttstomp == 41) //actually stomping
						bounce = !player.hurt(); //stomping but protected by something, including a sugar rush
					if (bounce) {
						player.buttstomp = 50; //landing
						player.ySpeed = player.ySpeed / -2 - 8;
						player.yAcc = 0;
						player.extendInvincibility(-70);
					}
				} else if (force == -101) { //frozen+running
					player.xAcc = 0;
					player.xSpeed /= -2;
					player.ySpeed = -6;
					player.extendInvincibility(-10);
				}
				if (obj.var[7] < jjGameTicks) { //hasn't been hurt recently
					obj.var[7] = jjGameTicks + 70; //next time this can be hurt by a physical attack
					if (obj.freeze != 0)
						obj.unfreeze(1);
					else 
						jjSample(obj.xPos, obj.yPos, ButtstompSounds[jjRandom() % ButtstompSounds.length]);
					obj.energy -= 4;
					obj.justHit = 5;
				}
			}
		}
		lastHitID = player.playerID;
		return true;
	}
}

class finalTotem : stormCloud {
	finalTotem(jjOBJ@ objectPreset) {
		super(objectPreset);
		followSpeed = 8 + jjDifficulty;
		attackCD = 120 - (jjDifficulty * 30);
		isTotem = true;
		objectPreset.isTarget = true;
		objectPreset.isFreezable = true;
		objectPreset.triggersTNT = true;
		objectPreset.deactivates = false;
	}
	void onDraw(jjOBJ@ obj) override {
		bossHealth = obj.energy;
		obj.determineCurAnim(ANIM::CUSTOM[11], 1);
		obj.draw();
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) override {
		if (bullet !is null) {
			if ((bullet.var[6] & 16) == 0) { //not a fireball
				bullet.state = STATE::EXPLODE;
			}
			if (obj.freeze != 0) {
				if (force < 3)
					force = 3;
				obj.unfreeze(0);
			}
			obj.energy -= bullet.animSpeed;
			obj.justHit = 5;
		} else {
			if (force == 0) { //collision
				if (obj.state != STATE::FREEZE)
					player.hurt();
			} else {
				if (force == 1) { //sugar rush/buttstomp
					bool bounce = true;
					if (player.buttstomp == 41) //actually stomping
						bounce = !player.hurt(); //stomping but protected by something, including a sugar rush
					if (bounce) {
						player.buttstomp = 50; //landing
						player.ySpeed = player.ySpeed / -2 - 8;
						player.yAcc = 0;
						player.extendInvincibility(-70);
					}
				} else if (force == -101) { //frozen+running
					player.xAcc = 0;
					player.xSpeed /= -2;
					player.ySpeed = -6;
					player.extendInvincibility(-10);
				}
				if (obj.var[7] < jjGameTicks) { //hasn't been hurt recently
					obj.var[7] = jjGameTicks + 70; //next time this can be hurt by a physical attack
					if (obj.freeze != 0)
						obj.unfreeze(1);
					else
						jjSample(obj.xPos, obj.yPos, ButtstompSounds[jjRandom() % ButtstompSounds.length]);
					obj.energy -= 4;
					obj.justHit = 5;
				}
			}
		}
		lastHitID = player.playerID;
		return true;
	}
}

bool givePlayerPointsForObject(jjPLAYER@ player, jjOBJ@ obj) { 
    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; 
        return true;
    }
    return false;
}

/*void TriggerPulse(jjOBJ@ obj) {
	if (--obj.counter > 0) {
		obj.xPos += obj.xSpeed;
		obj.yPos += obj.ySpeed;
		obj.age += 6;
		jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, abs(jjSin(obj.age)) + 0.65, abs(jjCos(obj.age)) + 0.65, SPRITE::TRANSLUCENTSINGLEHUE, obj.var[6], 4);
	} else
		obj.delete();
}*/

class shamanDemon : jjBEHAVIORINTERFACE {
int curseTarget;
int curseDistance = (256 * 256) + (jjDifficulty * 256 * 64);
	void onBehave(jjOBJ@ obj) {
		switch (obj.state) {
		case STATE::START:
			obj.putOnGround();
			curseTarget = -2; // clear targets after reloading
			obj.state = STATE::WALK;
		case STATE::WALK:
			obj.behave(BEHAVIOR::WALKINGENEMY);
			obj.var[3] = 0; // for summon animation
			obj.var[4] = 0; // for bonk animation
			if (obj.direction >= 0) {
				obj.xSpeed = 0.2;
			} else { obj.xSpeed = -0.2; }
			if (obj.findNearestPlayer(curseDistance) > -1 && obj.findNearestPlayer(curseDistance) != curseTarget) { // only one cloud per player can be active FROM THE SAME SHAMAN DEMON
				curseTarget = obj.findNearestPlayer(curseDistance);
				obj.state = STATE::EXTRA;
			}
			obj.var[2] = obj.var[2] - 1;
			if (jjRandom() % 512 == 0 && obj.var[2] <= 0) {
				obj.var[1] = 70 + (jjRandom() % 140); // time of idling
				obj.state = STATE::IDLE;
			}
			if (obj.findNearestPlayer(5000) > -1) {
				if (obj.xPos > jjLocalPlayers[obj.findNearestPlayer(5000)].xPos) { obj.direction = -1; }
				else { obj.direction = 1; }
				obj.state = STATE::ATTACK;
			}
			break;
		case STATE::IDLE:
			if (obj.var[1] == 0) {
				int rand = jjRandom()%2;
				obj.direction = rand - 1; // randomly change direction
				if (obj.direction >= 0) {
				obj.xSpeed = 0.2;
				} else { obj.xSpeed = -0.2; } // have to declare movement vector again or direction will never change here for some reason...
				obj.var[2] = 140; // idle cooldown
				obj.state = STATE::WALK;
			} else {
				obj.var[1] = obj.var[1] - 1;
			}
			break;
		case STATE::ATTACK:
			obj.var[4] = obj.var[4] + 1;
			if (obj.var[4] == 42) jjSample(obj.xPos, obj.yPos, SOUND::INTRO_HITTURT);
			if (obj.var[4] == 70) obj.state = STATE::WALK;
			break;
		case STATE::EXTRA:
			obj.var[3] = obj.var[3] + 1;
			if (obj.var[3] == 20) {
				int rand = jjRandom()%2;
				if (rand==0)
				jjSample(obj.xPos, obj.yPos, SOUND::INTRO_INHALE);
				else jjSample(obj.xPos, obj.yPos, SOUND::INTRO_UHTURT);
			}
			if (obj.var[3] == 90) {
				jjOBJ@ cloud = jjObjects[jjAddObject(OBJECT::RAPIER, jjLocalPlayers[curseTarget].xPos, jjLocalPlayers[curseTarget].yPos - 128, obj.objectID)];
				cloud.var[2] = curseTarget;
				int rand = jjRandom()%2;
				if (rand==0)
				jjSample(cloud.xPos, cloud.yPos, SOUND::AMMO_BLUB1);
				else jjSample(cloud.xPos, cloud.yPos, SOUND::AMMO_BLUB2);
				for( int n = 0; n < 2 + int(jjRandom()%3); n++ ) {
				jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::SHARD, cloud.xPos, cloud.yPos, cloud.objectID, CREATOR::OBJECT)];
				spark.determineCurAnim(ANIM::PICKUPS, 27);
				}
			}
			if (obj.var[3] == 112) {
				obj.state = STATE::WALK;
			}
			break;
		case STATE::FREEZE:
			if (obj.freeze == 0) obj.state = obj.oldState;
			else obj.freeze--;
			break;
		case STATE::DEACTIVATE:
			for (int i = 0; i < jjObjectCount; ++i)
			if (int(jjObjects[i].creatorID) == obj.objectID) jjObjects[i].state = STATE::KILL;
			obj.deactivate();
			break;
		case STATE::KILL:
			for (int i = 0; i < jjObjectCount; ++i)
			if (int(jjObjects[i].creatorID) == obj.objectID) jjObjects[i].state = STATE::KILL;
			obj.delete();
		}
	}
	void onDraw(jjOBJ@ obj) {
		switch (obj.state) {
		case STATE::IDLE:
			obj.determineCurAnim(ANIM::CUSTOM[9], 0);
			obj.frameID = ((jjGameTicks/9) % jjAnimations[obj.curAnim].frameCount);
			break;
		case STATE::WALK:
			obj.determineCurAnim(ANIM::CUSTOM[9], 1);
			obj.frameID = ((jjGameTicks/9) % jjAnimations[obj.curAnim].frameCount);
			break;
		case STATE::ATTACK:
			obj.determineCurAnim(ANIM::CUSTOM[9], 2);
			obj.frameID = int8(obj.var[4] / 7);
			break;
		case STATE::EXTRA:
			obj.determineCurAnim(ANIM::CUSTOM[9], 3);
			obj.frameID = int8(obj.var[3] / 7);
			break;
		}
		obj.determineCurFrame();
		obj.draw();
	}
}

class stormCloud : jjBEHAVIORINTERFACE {
float followSpeed = 5 + jjDifficulty;
int attackCD = 240 - (jjDifficulty * 40);
bool isTotem = false;
int lastHitID;
	stormCloud(jjOBJ@ objectPreset) {
		objectPreset.behavior = this;
		if (!isTotem) {
			objectPreset.determineCurAnim(ANIM::CUSTOM[9], 4);
			objectPreset.playerHandling = HANDLING::SPECIAL;
			objectPreset.scriptedCollisions = true;
			objectPreset.isTarget = false;
			objectPreset.isFreezable = false;
			objectPreset.triggersTNT = false;
			objectPreset.deactivates = true;
		}
	}
	void onBehave(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				obj.var[1] = attackCD;
				obj.state = STATE::IDLE;
			case STATE::IDLE:
				if (isTotem) {
					obj.lightType = LIGHT::LASER;
					obj.light = 15;
					totemChannel = jjSampleLooped(obj.xPos, obj.yPos, SOUND::BUMBEE_BEELOOP, totemChannel);
				}
				obj.var[3] = 50 - (jjDifficulty * 10);
				obj.frameID = (jjGameTicks/5) & 3;
				obj.determineCurFrame();
				if (abs(obj.xPos - jjLocalPlayers[obj.var[2]].xPos) > 8.f || abs(obj.yPos - (jjLocalPlayers[obj.var[2]].yPos - 128)) > 8.f) {
					float dirX = jjLocalPlayers[obj.var[2]].xPos - obj.xPos;
					float dirY = jjLocalPlayers[obj.var[2]].yPos - 128 - obj.yPos;
					float length = sqrt(dirX * dirX + dirY * dirY);
					
					if (length > 0) {
						dirX /= length;
						dirY /= length;
					}
					
					obj.xPos += dirX * followSpeed;
					obj.yPos += dirY * followSpeed;
				}
				obj.var[1] = obj.var[1] - 1;
				if (obj.var[1] <= 0) {
					obj.var[2] = 40;
					obj.state = STATE::ATTACK;
				}
				break;
			case STATE::ATTACK:
				if (isTotem) {
					obj.lightType = LIGHT::LASER;
					obj.light = 15;
					totemChannel = jjSampleLooped(obj.xPos, obj.yPos, SOUND::BUMBEE_BEELOOP, totemChannel);
				}
				obj.frameID = (jjGameTicks/3) & 3;
				obj.determineCurFrame();
				if (jjGameTicks % 10 == 0) obj.justHit = 5;
				obj.var[3] = obj.var[3] - 1;
				if (obj.var[3] <= 0) {
					if (isTotem) {
						jjSample(obj.xPos, obj.yPos, SOUND::AMMO_LASER);
						jjOBJ@ laser = jjObjects[obj.fireBullet(OBJECT::LASER)];
						laser.ySpeed = 5;
						laser.creatorID = obj.objectID;
						laser.creatorType = CREATOR::OBJECT;
						laser.playerHandling = HANDLING::ENEMYBULLET;
						int playerID = obj.findNearestPlayer(2000000);
							if (playerID >= 0 && jjLocalPlayers[playerID].yPos >= obj.yPos && abs(jjLocalPlayers[playerID].xPos - laser.xPos) <= 32.f) {
								if (jjDifficulty < 2) jjLocalPlayers[playerID].hurt(1);
								else jjLocalPlayers[playerID].hurt(2);
							}
					} else {
						jjSample(obj.xPos, obj.yPos, SOUND::COMMON_ELECTRIC1);
						jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::LIGHTNINGSHIELDBULLET)];
						bullet.ySpeed = 5;
						bullet.creatorID = obj.objectID;
						bullet.creatorType = CREATOR::OBJECT;
						bullet.playerHandling = HANDLING::ENEMYBULLET;
						if (jjDifficulty < 2) bullet.animSpeed = 1;
						else bullet.animSpeed = 2;
					}
					obj.var[1] = attackCD;
					obj.state = STATE::IDLE;
				}
				break;
			case STATE::FREEZE: // only used by totem
				if (obj.freeze == 0) {
					obj.unfreeze(0);
					obj.state = obj.oldState;
				}
				else {
					if (obj.freeze >= 5) obj.freeze -= 5;
					else obj.freeze--;
				}
				break;
			case STATE::DEACTIVATE:
				obj.delete();
				break;
			case STATE::KILL:
				if (isTotem) {
					givePlayerPointsForObject(jjLocalPlayers[lastHitID], obj);
					obj.particlePixelExplosion(0);
					obj.delete();
				}
				if (obj.isActive) {
					jjOBJ@ poof = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)];
					poof.determineCurAnim(ANIM::AMMO, 2);
				}
				obj.delete();
				break;
		}
		if (obj.energy <= 0 && isTotem) obj.state = STATE::KILL;
	}
	void onDraw(jjOBJ@ obj) {
		if (obj.state == STATE::ATTACK) {
			if (obj.justHit > 0) jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, SPRITE::SINGLECOLOR, 15, 1);
			jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, SPRITE::NORMAL, 0, 1);
		}
		else jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, SPRITE::TRANSLUCENT, 0, 1);
	}
	bool onObjectHit(jjOBJ@, jjOBJ@, jjPLAYER@ player, int) { return true; } // whatever man
}

class guerrillaDoofus : jjBEHAVIORINTERFACE {
// OBJECT VARIABLES:
// var[1] = jump stamina
// var[2] = blaster power
// var[3] = animation counter (start/end jump, start/end laying down)
// var[4] = animation counter (peak of jump)
// var[5] = idle counter
// var[6] = turning stuck check
	void onBehave(jjOBJ@ obj) {
	int count = jjRandom() % 3 + 8;
		switch (obj.state) {
		case STATE::START:
			obj.var[6] = 0;
			obj.state = STATE::SLEEP;
		case STATE::SLEEP: // hiding in bush
			obj.playerHandling = HANDLING::PARTICLE;
			obj.bulletHandling = HANDLING::IGNOREBULLET;
			obj.determineCurAnim(ANIM::CUSTOM[22], 12);
			obj.frameID = ((jjGameTicks/7) % jjAnimations[obj.curAnim].frameCount);
			obj.xPos = obj.xOrg;
			obj.yPos = obj.yOrg;
			if (obj.findNearestPlayer(256 * 128) > -1) {
				if (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos < obj.xPos) obj.direction = -1;
				else obj.direction = 1;		
				for (int i = 0; i < count; i++) {
					jjPARTICLE@ particle = jjAddParticle(PARTICLE::LEAF);
					particle.xPos = obj.xPos;
					particle.yPos = obj.yPos;
					particle.xSpeed = int(jjRandom() % 9 - 4);
					particle.ySpeed = int(jjRandom() % 4 - 4);
				}
				jjSample(obj.xPos, obj.yPos, SOUND::INTRO_GREN2);
				obj.xSpeed = obj.direction * 1.2;
				obj.ySpeed = -3;
				obj.state = STATE::JUMP;
			}
			break;
			
		case STATE::WALK:
			if (obj.var[6] > 0) obj.var[6] = obj.var[6] - 1;
			obj.xPos = obj.xPos + obj.xSpeed;
			obj.determineCurAnim(ANIM::CUSTOM[22], 8);
			obj.var[3] = 0;
			obj.frameID = ((jjGameTicks/7) % jjAnimations[obj.curAnim].frameCount);
			
			// turning check:
			if (!jjMaskedVLine(int(obj.xPos + obj.xSpeed), int(obj.yPos) + 27, 4) || jjMaskedVLine(int(obj.xPos + (obj.xSpeed * 8)), int(obj.yPos) - 8, 30) || jjEventGet(uint16(obj.xPos / 32), uint16(obj.yPos / 32)) == 14) {
				obj.var[4] = 14;
				obj.state = STATE::TURN;
			}
			
			// idling check:
			obj.var[5] = obj.var[5] - 1;
			if (jjRandom() % 512 == 0 && obj.var[5] <= 0) {
				obj.var[5] = 70 + (jjRandom() % 140); // time of idling
				obj.state = STATE::IDLE;
			}
			
			// duck check:
			if ((jjLocalPlayers[obj.findNearestPlayer(256 * 128)].yPos >= obj.yPos - 48 && obj.direction * (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos - obj.xPos) > 0 && (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].curAnim - jjAnimSets[jjLocalPlayers[obj.findNearestPlayer(256 * 128)].setID].firstAnim == RABBIT::DIVE) || (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].curAnim - jjAnimSets[jjLocalPlayers[obj.findNearestPlayer(256 * 128)].setID].firstAnim == RABBIT::DIVEFIRERIGHT)) || (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].yPos > obj.yPos + 90 && obj.direction * (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos - obj.xPos) > 0)) {
				obj.var[3] = 0;
				obj.state = STATE::DUCK;
			}
			
			// jump check:
			if (obj.var[1] > 0) obj.var[1] = obj.var[1] - 1;
			if (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].yPos < obj.yPos - 48 && obj.var[1] == 0 && obj.direction * (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos - obj.xPos) > 0) obj.state = STATE::SPRING;
			
			// fire check:
			if (obj.var[2] < 6 && jjGameTicks % 22 == 0) obj.var[2] = obj.var[2] + 1;
			else if ((jjLocalPlayers[obj.findNearestPlayer(256 * 128)].yPos < obj.yPos + 80 || jjLocalPlayers[obj.findNearestPlayer(256 * 128)].yPos >= obj.yPos + 1600) && obj.var[2] > 0 && obj.direction * (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos - obj.xPos) > 0) {
				if (jjGameTicks % 10 == 0) {
					jjSample(obj.xPos, obj.yPos, SOUND::DEVILDEVAN_PHASER2, 63, 13081);
					jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::FIREBALLBULLET)];
					jjOBJ@ flash = jjObjects[jjAddObject(OBJECT::EXPLOSION, bullet.xPos, bullet.yPos)];
					flash.determineCurAnim(ANIM::AMMO, 16);
					bullet.determineCurAnim(ANIM::DEVAN, 0);
					bullet.creatorID = obj.objectID;
					bullet.creatorType = CREATOR::OBJECT;
					bullet.playerHandling = HANDLING::ENEMYBULLET;
					bullet.direction = obj.direction;
					bullet.animSpeed = 1;
					bullet.xSpeed = obj.xSpeed * 2;
					if (bullet.direction < 0) {
						bullet.xSpeed *= -1;
						bullet.xAcc *= -1;
						flash.direction = -1;
						}
					else flash.direction = 1;
					obj.var[2] = obj.var[2] - 1;
				}
			}
			break;
			
		case STATE::IDLE:
			// jump check:
			if (obj.var[1] > 0) obj.var[1] = obj.var[1] - 1;
			if (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].yPos < obj.yPos - 48 && obj.var[1] == 0 && obj.direction * (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos - obj.xPos) > 0) obj.state = STATE::SPRING;
			
			// fire check:
			if (obj.var[2] < 6 && jjGameTicks % 22 == 0) obj.var[2] = obj.var[2] + 1;
			if (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].yPos < obj.yPos + 80 && obj.var[2] > 0 && obj.direction * (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos - obj.xPos) > 0) {
				obj.determineCurAnim(ANIM::CUSTOM[22], 7);
				if (jjGameTicks % 10 == 0) {
					jjSample(obj.xPos, obj.yPos, SOUND::DEVILDEVAN_PHASER2, 63, 13081);
					jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::FIREBALLBULLET)];
					jjOBJ@ flash = jjObjects[jjAddObject(OBJECT::EXPLOSION, bullet.xPos, bullet.yPos)];
					flash.determineCurAnim(ANIM::AMMO, 16);
					bullet.determineCurAnim(ANIM::DEVAN, 0);
					bullet.creatorID = obj.objectID;
					bullet.creatorType = CREATOR::OBJECT;
					bullet.playerHandling = HANDLING::ENEMYBULLET;
					bullet.direction = obj.direction;
					bullet.animSpeed = 1;
					bullet.xSpeed = obj.xSpeed * 2;
					if (bullet.direction < 0) {
						bullet.xSpeed *= -1;
						bullet.xAcc *= -1;
						flash.direction = -1;
						}
					else flash.direction = 1;
					obj.var[2] = obj.var[2] - 1;
				}
			}
			else obj.determineCurAnim(ANIM::CUSTOM[22], 5);
			obj.frameID = ((jjGameTicks/7) % jjAnimations[obj.curAnim].frameCount);
			
			// walk check:
			if (obj.var[5] <= 0 && uint(obj.curAnim) != jjAnimations[jjAnimSets[ANIM::CUSTOM[22] + 7]]) { // not busy shooting?
				obj.var[5] = 140 + (jjRandom() % 140); // time until next idle
				obj.state = STATE::WALK;
			}
			else obj.var[5] = obj.var[5] - 1;
			break;
		
		case STATE::DUCK: // start of laying down
			obj.determineCurAnim(ANIM::CUSTOM[22], 9);
			obj.frameID = int8(obj.var[3] / 7);
			obj.var[3] = obj.var[3] + 1;
			if (obj.var[3] == 20) {
				obj.state = STATE::HIDE;
			}
			break;
			
		case STATE::HIDE: // laying down
			if (obj.var[3] > 0 && jjGameTicks % 10 == 0) obj.var[3] = obj.var[3] - 1; // don't get up too fast
			obj.determineCurAnim(ANIM::CUSTOM[22], 10);
			
			// fire check:
			if (obj.var[2] < 6 && jjGameTicks % 22 == 0) obj.var[2] = obj.var[2] + 1;
			if (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].yPos >= obj.yPos - 4 && jjLocalPlayers[obj.findNearestPlayer(256 * 128)].yPos < obj.yPos + 90 && obj.var[2] > 0 && obj.direction * (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos - obj.xPos) > 0) {
				obj.frameID = ((jjGameTicks/7) % jjAnimations[obj.curAnim].frameCount);
				if (jjGameTicks % 10 == 0) {
					jjSample(obj.xPos, obj.yPos, SOUND::DEVILDEVAN_PHASER2, 63, 13081);
					jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::FIREBALLBULLET)];
					jjOBJ@ flash = jjObjects[jjAddObject(OBJECT::EXPLOSION, bullet.xPos, bullet.yPos)];
					flash.determineCurAnim(ANIM::AMMO, 16);
					bullet.determineCurAnim(ANIM::DEVAN, 0);
					bullet.creatorID = obj.objectID;
					bullet.creatorType = CREATOR::OBJECT;
					bullet.playerHandling = HANDLING::ENEMYBULLET;
					bullet.direction = obj.direction;
					bullet.animSpeed = 1;
					bullet.xSpeed = obj.xSpeed * 2;
					if (bullet.direction < 0) {
						bullet.xSpeed *= -1;
						bullet.xAcc *= -1;
						flash.direction = -1;
						}
					else flash.direction = 1;
					obj.var[2] = obj.var[2] - 1;
				}
			}
			else obj.frameID = 0;
			
			// get up check:
			if ((jjLocalPlayers[obj.findNearestPlayer(256 * 128)].yPos < obj.yPos - 4 || obj.findNearestPlayer(256 * 160) < 0 || (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos > obj.xPos && obj.direction < 0) || (jjLocalPlayers[obj.findNearestPlayer(256 * 128)].xPos < obj.xPos && obj.direction >= 0)) && jjGameTicks % 22 == 0 && obj.var[3] <= 0) {
				obj.var[3] = 0;
				obj.state = STATE::WAKE;
			}
			break;
			
		case STATE::WAKE: // get up
			obj.determineCurAnim(ANIM::CUSTOM[22], 11);
			obj.frameID = int8(obj.var[3] / 7);
			obj.var[3] = obj.var[3] + 1;
			if (obj.var[3] == 13) {
				obj.state = STATE::WALK;
			}
			break;
		
		case STATE::TURN:
			obj.determineCurAnim(ANIM::CUSTOM[22], 6);
			obj.frameID = 0;
			if (obj.var[4] == 7) {
				obj.direction = obj.direction * -1;
				obj.xSpeed = obj.xSpeed * -1;
			}
			if (obj.var[4] == 0) obj.state = STATE::WALK;
			else {
				obj.var[4] = obj.var[4] - 1;
				obj.var[6] = obj.var[6] + 2;
			}
			if (obj.var[6] >= 50) obj.state = STATE::SPRING; // help me stepbro I'm stuck!
			break;
			
		case STATE::SPRING: // a.k.a. start jump
			obj.determineCurAnim(ANIM::CUSTOM[22], 0);
			obj.frameID = int8(obj.var[3] / 7);
			obj.var[3] = obj.var[3] + 1;
			if (obj.var[3] == 41) {
				obj.ySpeed = -5;
				jjSample(obj.xPos, obj.yPos, SOUND::COMMON_JUMP2);
				obj.state = STATE::JUMP;
			}
			break;
			
		case STATE::JUMP:
			if (obj.var[6] > 0) obj.var[6] = obj.var[6] - 1;
			obj.playerHandling = HANDLING::ENEMY;
			obj.bulletHandling = HANDLING::HURTBYBULLET;
			obj.var[3] = 0;
			if (jjGameTicks % 10 == 0) obj.ySpeed += 1;
			obj.xPos += obj.xSpeed;
			obj.yPos += obj.ySpeed;
			if (obj.ySpeed < -0.1) {
				obj.determineCurAnim(ANIM::CUSTOM[22], 1);
				obj.frameID = ((jjGameTicks/7) % jjAnimations[obj.curAnim].frameCount);
			}
			else if (obj.var[4] < 20) { // peak of jump
				obj.determineCurAnim(ANIM::CUSTOM[22], 2);
				obj.frameID = int8(obj.var[4] / 7);
				obj.var[4] = obj.var[4] + 1;
			}
			else {
				obj.determineCurAnim(ANIM::CUSTOM[22], 3);
				obj.frameID = ((jjGameTicks/7) % jjAnimations[obj.curAnim].frameCount);
				}
			if (obj.ySpeed > 0 && jjMaskedPixel(int(obj.xPos + obj.xSpeed), int(obj.yPos + obj.ySpeed) + 18)) { // landing detection
				jjSample(obj.xPos, obj.yPos, SOUND::COMMON_LANDPOP);
				obj.state = STATE::LAND;
			}
			break;
			
		case STATE::LAND:
			obj.var[4] = 0;
			obj.determineCurAnim(ANIM::CUSTOM[22], 4);
			obj.frameID = int8(obj.var[3] / 7);
			obj.var[3] = obj.var[3] + 1;
			if (obj.var[3] == 41) {
				obj.var[1] = 120; // tired from jump
				obj.state = STATE::WALK;
			}
			break;
			
		case STATE::FREEZE:
			if (obj.freeze == 0) obj.state = obj.oldState;
			else obj.freeze--;
			break;
			
		case STATE::DEACTIVATE:
			obj.deactivate();
			break;
		case STATE::KILL:
			obj.delete();
			break;
		}
		
	}
	void onDraw(jjOBJ@ obj) {
		obj.determineCurFrame();
		if (obj.state == STATE::SLEEP) jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::PALSHIFT, 32); // I made the bush sprite to be universally usable, but for THESE levels I want it to blend in more
		else obj.draw();
	}
}

enum HeavyCopterVars {
	hcNextPhysicalInjury,
	hcNextNearbyPlayerCheck,
	hcTargetPlayerID,
	hcCurrentAngle
}

const array<SOUND::Sample> ButtstompSounds = {SOUND::COMMON_SPLAT1, SOUND::COMMON_SPLAT2, SOUND::COMMON_SPLAT3, SOUND::COMMON_SPLAT4};
class HeavyCopter : jjBEHAVIORINTERFACE {
int lastHitID;
	void onBehave(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				obj.state = STATE::IDLE;
			case STATE::IDLE: //fall through from START
				obj.yPos = obj.yOrg + jjSin(obj.counterEnd << 2) * 5;
				obj.frameID = (obj.counterEnd) >> 2;
				break;
			case STATE::FLY: {
				const int desiredDistanceFromPlayer = 150;
				const jjPLAYER@ targetPlayer = jjPlayers[obj.var[hcTargetPlayerID]];
				float targetX = targetPlayer.xPos;
				const float targetY = targetPlayer.yPos - 150;
				
				int desiredAngle = 0; //forwards
				int desiredDirection = obj.direction;
				copterChannel = jjSampleLooped(obj.xPos, obj.yPos, SOUND::ORANGE_BUBBELSR, copterChannel);
				if (abs(obj.xPos - targetPlayer.xPos) > desiredDistanceFromPlayer / 2) {
					if (obj.xPos < targetPlayer.xPos) {
						targetX -= desiredDistanceFromPlayer;
						desiredDirection = 1;
					} else {
						targetX += desiredDistanceFromPlayer;
						desiredDirection = -1;
					}
				} else {
					desiredAngle = 17; //down
				}
				
				if (desiredDirection != obj.direction) {
					if (obj.var[hcCurrentAngle] != 17)
						desiredAngle = 17;
					else
						obj.direction = desiredDirection;
				}
				
				if (desiredAngle > obj.var[hcCurrentAngle])
					obj.var[hcCurrentAngle] = obj.var[hcCurrentAngle] + 1;
				else if (desiredAngle < obj.var[hcCurrentAngle])
					obj.var[hcCurrentAngle] = obj.var[hcCurrentAngle] - 1;
				
				if (obj.var[hcCurrentAngle] == 0 || obj.var[hcCurrentAngle] == 17) {
					obj.frameID = (obj.counterEnd) >> 2;
					obj.determineCurAnim(ANIM::CUSTOM[23], obj.var[hcCurrentAngle] == 0 ? 3 : 5);
				} else {
					obj.frameID = (obj.var[hcCurrentAngle] - 1) / 4;
					obj.determineCurAnim(ANIM::CUSTOM[23], 4);
				}
				
				float dx = targetX - obj.xPos;
				float dy = targetY - obj.yPos;
				if (abs(dx) < 7 && abs(dy) < 16) {
					if (desiredDirection == obj.direction && desiredAngle == obj.var[hcCurrentAngle]) {
						obj.state = STATE::FIRE;
						obj.counter = 70;
						obj.counterEnd = 15;
					}
					obj.xSpeed = obj.ySpeed = 0; //only use xSpeed/ySpeed instead of temporary variables because the STATE::FALL code modifies them, just to be cute
				} else {
					int maxSpeed = (jjDifficulty >= 2) ? 3 : 2;
					dx /= 32;
					if (dx > maxSpeed) obj.xSpeed = maxSpeed; 
					else if (dx < -maxSpeed) obj.xSpeed = -maxSpeed;
					else obj.xSpeed = dx;
					obj.xPos += obj.xSpeed;
					
					maxSpeed -= 1; //slower max speed than X
					dy /= 32;
					if (dy > maxSpeed) obj.ySpeed = maxSpeed;
					else if (dy < -maxSpeed) obj.ySpeed = -maxSpeed;
					else obj.ySpeed = dy;
					obj.yPos += obj.ySpeed;
				}
				break; }
			case STATE::FIRE:
				copterChannel = jjSampleLooped(obj.xPos, obj.yPos, SOUND::ORANGE_BUBBELSR, copterChannel);
				if (obj.counterEnd & 15 == 0 && (jjDifficulty != 0 || obj.counterEnd & 31 == 0)) { //the animation is in the firing frame
					jjSample(obj.xPos, obj.yPos, SOUND::COMMON_PISTOL1);
					jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::BLASTERBULLET)];
					bullet.ySpeed = 2 + jjDifficulty;
					bullet.xSpeed = obj.var[hcCurrentAngle] == 0 ? 2 + jjDifficulty : 0;
					bullet.xAcc = 0;
					bullet.playerHandling = HANDLING::ENEMYBULLET;
					bullet.counterEnd = 64;
					bullet.determineCurAnim(ANIM::AMMO, 12);
				}
				obj.frameID = (obj.counterEnd) >> 2;
				if (--obj.counter == 0) //done firing, for now anyway
					obj.state = STATE::FLY;
				break;
			case STATE::FALL:
				copterChannel = jjSampleLooped(obj.xPos, obj.yPos, SOUND::PICKUPS_HELI2, copterChannel);
				obj.xPos += obj.xSpeed;
				obj.yPos += (obj.ySpeed += 0.08);
				if (jjGameTicks % 20 == 0) {
					jjOBJ@ smoke = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)];
					smoke.determineCurAnim(ANIM::AMMO, 72);
				}
				if (jjMaskedHLine(int(obj.xPos) - 20, 40, int(obj.yPos) + 20)) {
					obj.particlePixelExplosion(2);
					jjSample(obj.xPos, obj.yPos, SOUND::COMMON_EXPL_TNT);
					jjOBJ@ boom = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)];
					boom.determineCurAnim(ANIM::AMMO, 5);
					int rand = jjRandom() % 4;
					for( int n = 0; n < rand + 3; n++ ) {
						jjOBJ@ shard = jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos)];
						shard.behavior = BEHAVIOR::SHARD;
						shard.determineCurAnim(ANIM::AMMO, (jjRandom()%2 + 78));
					}
					obj.delete();
				} else {
					jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction);
				}
				return; //skip usual drawing code
			case STATE::FREEZE:
				if (obj.freeze == 0) {
					obj.state = obj.oldState;
					break;
				} else {
					--obj.freeze;
					jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, mode: SPRITE::FROZEN); //copter
					jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame - 12, obj.direction, mode: SPRITE::FROZEN); //lizard
					jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim + 3], obj.direction, mode: SPRITE::FROZEN); //gun
					return;
				}
			case STATE::DEACTIVATE:
				obj.deactivate();
				return;
		}
		
		//common code for IDLE, FLY, and FIRE: find a target player, and do drawing
		
		if (jjGameTicks > obj.var[hcNextNearbyPlayerCheck]) {
			const int nearestPlayerID = obj.findNearestPlayer(192 * 192); //same radius as float lizards
			if (nearestPlayerID >= 0 && obj.var[hcTargetPlayerID] != nearestPlayerID) {
				obj.var[hcNextNearbyPlayerCheck] = jjGameTicks + 3 * 70; //next time to start looking for a nearest player
				obj.var[hcTargetPlayerID] = nearestPlayerID;
				if (obj.state == STATE::IDLE)
					obj.state = STATE::FLY;
			}
		}
		
		++obj.counterEnd;
		obj.determineCurFrame();
		const SPRITE::Mode mode = obj.justHit == 0 ? SPRITE::NORMAL : SPRITE::SINGLECOLOR;
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, mode: mode, param: 15); //copter... the copter is a bigger sprite than the lizard, so it might as well have the collision detection
		if (obj.var[hcCurrentAngle] == 0 || obj.var[hcCurrentAngle] == 17) {
			jjDrawSpriteFromCurFrame( //lizard
				obj.xPos, obj.yPos,
				jjAnimations[obj.curAnim - 3] + ((obj.frameID >> 1) & 3), //slower animation speed (>>3) than the copter (>>2)
				obj.direction, mode: mode, param: 15
			);
			jjDrawSpriteFromCurFrame( //gun
				obj.xPos, obj.yPos,
				obj.state == STATE::FIRE ?
					jjAnimations[obj.curAnim + (obj.var[hcCurrentAngle] == 0 ? 6 : 5)] + (obj.frameID & 3) :
					jjAnimations[obj.curAnim + 3],
				obj.direction, mode: mode, param: 15
			);
		} else {
			jjDrawSpriteFromCurFrame( //lizard
				obj.xPos, obj.yPos,
				jjAnimations[obj.curAnim - 3] + obj.frameID, //when turning, lizard frameID and gun frameID match copter frameID
				obj.direction, mode: mode, param: 15
			);
			jjDrawSpriteFromCurFrame( //gun
				obj.xPos, obj.yPos,
				jjAnimations[obj.curAnim + 3] + obj.frameID,
				obj.direction, mode: mode, param: 15
			);
			
		}
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) { //lots taken from plus52Scripting.j2as
		if (bullet !is null) {
			if ((bullet.var[6] & 16) == 0) { //not a fireball
				bullet.state = STATE::EXPLODE;
			}
			if (obj.freeze != 0) {
				if (force < 3)
					force = 3;
				obj.unfreeze(0);
			}
			hurtObject(obj, force);
		} else {
			if (force == 0) { //collision
				if (obj.state != STATE::FREEZE)
					player.hurt();
			} else {
				if (force == 1) { //sugar rush/buttstomp
					bool bounce = true;
					if (player.buttstomp == 41) //actually stomping
						bounce = !player.hurt(); //stomping but protected by something, including a sugar rush
					if (bounce) {
						player.buttstomp = 50; //landing
						player.ySpeed = player.ySpeed / -2 - 8;
						player.yAcc = 0;
						player.extendInvincibility(-70);
					}
				} else if (force == -101) { //frozen+running
					player.xAcc = 0;
					player.xSpeed /= -2;
					player.ySpeed = -6;
					player.extendInvincibility(-10);
				}
				if (obj.var[hcNextPhysicalInjury] < jjGameTicks) { //hasn't been hurt recently
					obj.var[hcNextPhysicalInjury] = jjGameTicks + 70; //next time this can be hurt by a physical attack
					if (obj.freeze != 0)
						obj.unfreeze(1);
					else
						jjSample(obj.xPos, obj.yPos, ButtstompSounds[jjRandom() % ButtstompSounds.length]);
					hurtObject(obj, 4);
				}
			}
		}
		lastHitID = player.playerID;
		return true;
	}
	void hurtObject(jjOBJ@ obj, int force) const {
		obj.energy -= force;
		if (obj.energy <= 10) { //killed, but with a probably-unnecessary buffer
			givePlayerPointsForObject(jjLocalPlayers[lastHitID], obj);
			obj.state = STATE::FALL;
			obj.playerHandling = HANDLING::PARTICLE;
			jjOBJ@ lizard = jjObjects[jjAddObject(OBJECT::FLOATLIZARD, obj.xPos, obj.yPos, 1,CREATOR::LEVEL, BEHAVIOR::FLOATLIZARD)]; //mostly taken from g_hit.c
			lizard.state = STATE::FALL;
			lizard.xSpeed = obj.direction;
			lizard.ySpeed = 0.5;
			lizard.curAnim = jjAnimSets[ANIM::LIZARD] + 2;
			lizard.counter = 0;
			lizard.frameID = 0; 
			lizard.playerHandling = HANDLING::ENEMY;
			lizard.bulletHandling = HANDLING::HURTBYBULLET;
		} else {
			obj.justHit = 5;
		}
	}
}

class CustomDragonfly : jjBEHAVIORINTERFACE {
	jjOBJ@ rockTarget;
	float triggerDistance = 200;
	float extraStateSpeed = 2;

	CustomDragonfly(jjOBJ@ preset) {
		preset.behavior = this;
	}
	
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::DRAGONFLY, true);
		// 28 - IDLE
		// 8 - START FOLLOW
		// 13 - FOLLOW
		// 22 - ATTACK
		// 30 - RETURN TO SPAWN
		// 3 - DEAD
		
		if (obj.state != STATE::EXTRA) {
			obj.age = 0;
			obj.bulletHandling = HANDLING::HURTBYBULLET;
			obj.playerHandling = HANDLING::ENEMY;
		}
		
		if (obj.state == STATE::FLOAT) {
			for (int i = 0; i < jjObjectCount; i++) { 
				jjOBJ@ rock = jjObjects[i];
				
				if (rock.eventID == 243 && rock.state != STATE::DEACTIVATE) {
					float distance = sqrt(pow(rock.xPos - obj.xPos, 2) + pow(rock.yPos - obj.yPos, 2));
					if (distance <= triggerDistance) {
						@rockTarget = rock;
						obj.state = STATE::EXTRA;
						obj.bulletHandling = HANDLING::IGNOREBULLET;
						obj.playerHandling = HANDLING::PARTICLE;
					}
				}
			}
		}	
		else if (obj.state == STATE::EXTRA) {
			if (rockTarget !is null)
			{
				if (obj.doesCollide(rockTarget)) {
					obj.determineCurAnim(ANIM::CUSTOM[10], 1);
					obj.frameID = (int8(obj.age / 12));
					obj.determineCurFrame();
					
					if (obj.age == 30) jjSample(obj.xPos, obj.yPos, SOUND::INTRO_BRAKE);
					else if (obj.age == 47) {
						rockTarget.clearPlatform();
						rockTarget.particlePixelExplosion(0);
						rockTarget.state = STATE::DEACTIVATE;
						rockTarget.delete();
						
						
						obj.state = STATE::STOP;
						obj.determineCurAnim(ANIM::DRAGFLY, 0);

					}
					
					if (obj.age > 47) { obj.age = 0; }
					else { obj.age++; }
				}
				
				else {
					// Trigonometry. Don't worry about it, I don't understand it either :)
					float dirX = rockTarget.xPos - obj.xPos;
					float dirY = rockTarget.yPos - obj.yPos;
					float length = sqrt(dirX * dirX + dirY * dirY);
					
					if (length > 0) {
						dirX /= length;
						dirY /= length;
					}
					
					obj.direction = (obj.xPos - rockTarget.xPos > 0) ? -1 : 1;
					
					obj.xPos += dirX * extraStateSpeed;
					obj.yPos += dirY * extraStateSpeed;
				}
			}
		}
	}
}

class bigGrub : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::MONITOR);
	obj.beSolid();
	obj.causesRicochet = true;
	obj.bulletHandling = HANDLING::DETECTBULLET;
	obj.playerHandling = HANDLING::SPECIAL;
	obj.scriptedCollisions = true;
	obj.xPos = obj.xOrg;
}
	void onDraw(jjOBJ@ obj) {
		if (obj.state != STATE::FREEZE) obj.frameID = ((jjGameTicks/10) % jjAnimations[obj.curAnim].frameCount);
		obj.determineCurFrame();
		obj.draw();
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		if (player.doesCollide(obj) && abs(player.xPos - obj.xPos) <= 32.f) player.xPos = player.xPos - (player.xSpeed * 2); // nope.avi
		return true;
	}
}

class grub : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
	jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
	switch(jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,2)) {
		case 0:
			obj.behave(BEHAVIOR::WALKINGENEMY);
			obj.xSpeed = obj.direction * 0.1;
			break;
		case 1:
			switch (obj.state) {
				case STATE::START:
					while (!jjMaskedPixel(int(obj.xPos) - (frame.coldSpotY - frame.hotSpotY) - 4, int(obj.yPos))) obj.xPos = obj.xPos + 1; // fall right
						if (jjRandom()%2 == 0) obj.direction = -1;
						else obj.direction = 1;
						obj.ySpeed = obj.direction * 0.1;
						obj.state = STATE::WALK;
					break; // need to make sure relocation is done
				case STATE::WALK:
					if (
						!jjMaskedHLine(int(obj.xPos) - (frame.coldSpotY - frame.hotSpotY), 8, int(obj.yPos + (obj.ySpeed * (frame.width / 2))))
						||
						jjMaskedHLine(int(obj.xPos - (frame.height / 4)), int(frame.height / 2), int(obj.yPos + (obj.ySpeed * (frame.width / 2))))
						||
						jjEventGet(uint16(obj.xPos / 32), uint16((obj.yPos + obj.ySpeed) / 32)) == 14
					) {
						obj.direction = obj.direction * -1;
						obj.ySpeed = obj.ySpeed * -1;
					}
					else obj.yPos = obj.yPos + obj.ySpeed;
					break;
				case STATE::FREEZE:
					if (obj.freeze == 0) obj.state = obj.oldState;
					else obj.freeze--;
					break;
				case STATE::DEACTIVATE:
					obj.deactivate();
					break;
				case STATE::KILL:
					obj.delete();
					break;
			}
			break;
		case 2:
			switch (obj.state) {
				case STATE::START:
					while (!jjMaskedPixel(int(obj.xPos), int(obj.yPos) + (frame.coldSpotY - frame.hotSpotY) + 4)) obj.yPos = obj.yPos - 1; // fall up
						if (jjRandom()%2 == 0) obj.direction = -1;
						else obj.direction = 1;
						obj.xSpeed = obj.direction * 0.1;
						obj.state = STATE::WALK;
					break; // need to make sure relocation is done
				case STATE::WALK:
					if (
						!jjMaskedVLine(int(obj.xPos + (obj.xSpeed * (frame.width / 2))), int(obj.yPos) + (frame.coldSpotY - frame.hotSpotY) - 7, 10)
						||
						jjMaskedVLine(int(obj.xPos + (obj.xSpeed * (frame.width / 2))), int(obj.yPos - (frame.height / 4)), int(frame.height / 2))
						||
						jjEventGet(uint16(obj.xPos / 32), uint16(obj.yPos / 32)) == 14
					) {
						obj.direction = obj.direction * -1;
						obj.xSpeed = obj.xSpeed * -1;
					}
					else obj.xPos = obj.xPos + obj.xSpeed;
					break;
				case STATE::FREEZE:
					if (obj.freeze == 0) obj.state = obj.oldState;
					else obj.freeze--;
					break;
				case STATE::DEACTIVATE:
					obj.deactivate();
					break;
				case STATE::KILL:
					obj.delete();
					break;
			}
			break;
		case 3:
			switch (obj.state) {
				case STATE::START:
					while (!jjMaskedPixel(int(obj.xPos) + (frame.coldSpotY - frame.hotSpotY) + 4, int(obj.yPos))) obj.xPos = obj.xPos - 1; // fall left
						if (jjRandom()%2 == 0) obj.direction = -1;
						else obj.direction = 1;
						obj.ySpeed = obj.direction * 0.1;
						obj.state = STATE::WALK;
					break; // need to make sure relocation is done
				case STATE::WALK:
					if (
						!jjMaskedHLine(int(obj.xPos) + (frame.coldSpotY - frame.hotSpotY) - 7, 10, int(obj.yPos + (obj.ySpeed * (frame.width / 2))))
						||
						jjMaskedHLine(int(obj.xPos + (frame.height / 4)), int(frame.height / 2), int(obj.yPos + (obj.ySpeed * (frame.width / 2))))
						||
						jjEventGet(uint16(obj.xPos / 32), uint16((obj.yPos + obj.ySpeed) / 32)) == 14
					) {
						obj.direction = obj.direction * -1;
						obj.ySpeed = obj.ySpeed * -1;
					}
					else obj.yPos = obj.yPos + obj.ySpeed;
					break;
				case STATE::FREEZE:
					if (obj.freeze == 0) obj.state = obj.oldState;
					else obj.freeze--;
					break;
				case STATE::DEACTIVATE:
					obj.deactivate();
					break;
				case STATE::KILL:
					obj.delete();
					break;
			}
			break;
		}
	}
	void onDraw(jjOBJ@ obj) {
		
		obj.determineCurFrame();
		switch(jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,2)) {
		case 0:
			obj.draw(); break;
		case 1: 
			if (obj.state == STATE::FREEZE) jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 256, (obj.direction > 0) ? 1 : -1, 1, SPRITE::FROZEN);
			else {
				obj.frameID = ((jjGameTicks/7) % jjAnimations[obj.curAnim].frameCount);
				obj.determineCurFrame();
				if (obj.justHit > 0) jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 256, (obj.direction > 0) ? 1 : -1, 1, SPRITE::SINGLECOLOR, 15);
				else jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 256, (obj.direction > 0) ? -1 : 1, 1);
			}
			break;
		case 2:
			if (obj.state == STATE::FREEZE) jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, (obj.direction > 0) ? SPRITE::FLIPV : SPRITE::FLIPHV, SPRITE::FROZEN);
			else {
				obj.frameID = ((jjGameTicks/7) % jjAnimations[obj.curAnim].frameCount);
				obj.determineCurFrame();
				if (obj.justHit > 0) jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, (obj.direction > 0) ? SPRITE::FLIPV : SPRITE::FLIPHV, SPRITE::SINGLECOLOR, 15);
				else jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, (obj.direction > 0) ? SPRITE::FLIPV : SPRITE::FLIPHV);
			}
			break;
		case 3: 
			if (obj.state == STATE::FREEZE) jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 768, (obj.direction > 0) ? 1 : 1, -1, SPRITE::FROZEN);
			else {
				obj.frameID = ((jjGameTicks/7) % jjAnimations[obj.curAnim].frameCount);
				obj.determineCurFrame();
				if (obj.justHit > 0) jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 768, (obj.direction > 0) ? 1 : 1, -1, SPRITE::SINGLECOLOR, 15);
				else jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 768, (obj.direction > 0) ? 1 : -1, 1);
			}
			break;
		}
	}
}

class parrot : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.playerHandling = HANDLING::ENEMY;
		obj.bulletHandling = HANDLING::HURTBYBULLET;
		obj.isBlastable = obj.isFreezable = obj.isTarget = obj.triggersTNT = true;
		switch (obj.state) {
			case STATE::START:
				obj.age = jjRandom() % 10;
				obj.state = STATE::FLY;
			case STATE::FLY:
				obj.frameID = (jjGameTicks/7) & 7;
				obj.determineCurFrame();
				obj.counter += 1;
				if (obj.counter > 127) { obj.counter = 0; }
				obj.yPos = obj.yOrg + 100 * jjSin((obj.age + jjGameTicks) * 10);
				obj.xPos = obj.xPos + (2 * obj.direction);
				break;
			case STATE::FREEZE:
				if (obj.freeze == 0) obj.state = obj.oldState;
				else obj.freeze--;
				break;
			case STATE::DEACTIVATE:
				obj.delete(); // will be continuously spawned anyway
				break;
			case STATE::KILL:
				obj.delete(); // don't drop random item
				break;
		}
	}
	void onDraw(jjOBJ@ obj) {
		obj.draw();
	}
}

void loriBlock(jjOBJ@ obj) {
    obj.behave(BEHAVIOR::DESTRUCTSCENERY);
	int nearPlayerID = obj.findNearestPlayer(256);
    if (obj.state != STATE::KILL && obj.state != STATE::DONE) {
            if (jjLocalPlayers[nearPlayerID].charCurr == CHAR::LORI && jjLocalPlayers[obj.findNearestPlayer(256)].doesCollide(obj) && jjLocalPlayers[nearPlayerID].specialMove > 0) {
                givePlayerPointsForObject(jjLocalPlayers[nearPlayerID], obj);
				obj.state = STATE::KILL;
            }
	}
}

void flickerWrapper(jjOBJ@ obj) {
		if (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,3) > 3) {
			obj.eventID = 157; 
			obj.behave(BEHAVIOR::FLICKERGEM);
			obj.points = 50;
			obj.var[0] = 0;
			if (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,3) == 5) obj.determineCurAnim(ANIM::CUSTOM[7], 43);
			else obj.determineCurAnim(ANIM::PICKUPS, 92);
			}
		else {
			obj.behave(BEHAVIOR::FLICKERGEM);
			obj.var[0] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,3) + 1;
			int points = jjObjectPresets[OBJECT::REDGEM + obj.var[0] - 1].points;
			obj.points = points;
			obj.determineCurAnim(ANIM::PICKUPS, 22);
		}
}

void SuperGemWrapper(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		if (jjParameterGet(uint(obj.xPos) >> 5, uint(obj.yPos) >> 5, 0,3) < 4)
		obj.var[0] = jjParameterGet(uint(obj.xPos) >> 5, uint(obj.yPos) >> 5, 0,3) + 1; //color
		else {
			obj.var[0] = 0;
			obj.age = jjParameterGet(uint(obj.xPos) >> 5, uint(obj.yPos) >> 5, 0,3) - 3;
		}
	}
	obj.behave(BEHAVIOR::SUPERGEM);
	if (obj.state == STATE::SLEEP && obj.var[0] == 0) {
		if (obj.age == 1) obj.determineCurAnim(ANIM::CUSTOM[7], 6);
		if (obj.age == 2) obj.determineCurAnim(ANIM::CUSTOM[7], 42);
	}
	if (obj.state == STATE::ACTION) {
		if (jjParameterGet(uint(obj.xPos) >> 5, uint(obj.yPos) >> 5, 0,3) < 4) jjSample(obj.xOrg, obj.yOrg, SOUND::INTRO_GRAB);
		else jjSample(obj.xOrg, obj.yOrg, SOUND::MONKEY_SPLUT);
	}
}

class specialBanana : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.eventID = 66; 
		obj.behave(BEHAVIOR::PICKUP);
		obj.points = 50;
		obj.var[0] = 0;
	}
}

class NPC : jjBEHAVIORINTERFACE {
    void onBehave(jjOBJ@ obj) {
        obj.putOnGround();
        
        farPlayerID = obj.findNearestPlayer(256 * 32);
        nearPlayerID = obj.findNearestPlayer(256 * 16);
        
        if (obj.state == STATE::START) obj.state = STATE::STILL;

        if (farPlayerID > -1 && obj.state == STATE::DEACTIVATE) obj.state = STATE::STILL;
        
        if (nearPlayerID > -1 && obj.state == STATE::STILL && jjLocalPlayers[nearPlayerID].charCurr != CHAR::FROG) {
			obj.state = STATE::WAKE;
			if (jjParameterGet(int(obj.xOrg / 32), int(obj.yOrg / 32), 0, 3) == 2) chiefNear = true;
		}
        
        if (obj.state == STATE::WAKE) {
            if (farPlayerID > -1) {
                if (obj.xPos > jjLocalPlayers[farPlayerID].xPos) obj.direction = -1;
				else obj.direction = 1;
			}
            if (jjKey[0x54] && CurrentPopup is null) NPCspeaks(jjLocalPlayers[nearPlayerID], jjParameterGet(int(obj.xOrg / 32), int(obj.yOrg / 32), 3, 5));
            else if (nearPlayerID < 0) {
                obj.state = STATE::STILL;
				if (jjParameterGet(int(obj.xOrg / 32), int(obj.yOrg / 32), 0, 3) == 2) chiefNear = false;
            }              
        }
    }
	void onDraw(jjOBJ@ obj) {
		obj.frameID = ((jjGameTicks/10) % jjAnimations[obj.curAnim].frameCount);
		obj.determineCurAnim(ANIM::CUSTOM[3], jjParameterGet(int(obj.xOrg / 32), int(obj.yOrg / 32), 0, 3));
		obj.determineCurFrame();
		obj.draw();
		if (obj.state == STATE::WAKE) jjDrawSprite(obj.xPos - 40, obj.yPos - 48, ANIM::CUSTOM[4], 0, (jjGameTicks/12) & 1);
	}
}

void NPCspeaks(const jjPLAYER@ play, int8 orderID) {
	switch (orderID) {
		case 0:
			@CurrentPopup = Conversation(array<Screen@> = {
				Screen(top: Line("yo check it out kiddos, i gots me a fresh new RHYME i came up with just fer YOU!", right: Yoman, direction: 1, color: 72)),
				Screen(top: Line("now feel my MAGIC FLOW and LISTEN:", right: Yoman, direction: 1, color: 72)),
				Screen(top: Line("yo, them mean pesky parrots sure are APLENTY, but huntin' them down is worth a mere TWENTY", right: Yoman, direction: 1, color: 72)),
				Screen(top: Line("y'all be better off just keepin' yer distance, tho sometimes the birdbrains can be of ASSISTANCE", right: Yoman, direction: 1, color: 72)),
				Screen(top: Line("STOMP at da right time and hold down yer JUMP, and if ya don't miss, that'll give ya a BUMP", right: Yoman, direction: 1, color: 72)),
				Screen(top: Line("yer butt is a WEAPON, but also a TOOL - use it fer PLATFORMIN' 'n' don't be no fool!", right: Yoman, direction: 1, color: 72)),
				Screen(top: Line(". . .that's it fer now, see ya next time!", right: Yoman, direction: 1, color: 72)),
			});
			break;
		case 1:
			if (!bananasGiven && play.gems[GEM::PURPLE] < 50) {
				@CurrentPopup = Conversation(array<Screen@> = {
					Screen(top: Line("you bring goods?", right: Chief, direction: 1, color: 64)),
					Screen(
						top: Line(right: Chief),
						bottom: Line("not yet, but we're working on it.", left: Lori, direction: -1, color: 40)
					),
					Screen(
						top: Line("hey, how about we just quickly go over there, defeat the demon, then we go back and help you with anything you'd like?", left: Jazz, right: Chief, direction: -1, color: 16),
						bottom: Line("(bro, come on...)", left: Lori, direction: -1, color: 40)
					),
					Screen(
						top: Line("it'll only take a moment, promise! we're kind of in a hurry!", left: Jazz, right: Chief, direction: -1, color: 16),
						bottom: Line(left: Lori)
					),
					Screen(
						top: Line("entire village depend on this task. me cannot leave, must keep people safe from evil spirits.", left: Jazz, right: Chief, direction: 1, color: 64),
						bottom: Line(left: Lori)
					),
					Screen(
						top: Line("also, me have feeling you quest far from over. many ways to go you have. the trees and the rivers tell me so.", left: Jazz, right: Chief, direction: 1, color: 64),
						bottom: Line(left: Lori)
					),
					Screen(
						top: Line("aren't you an optimistic fellow...", left: Jazz, right: Chief, direction: -1, color: 16),
						bottom: Line(left: Lori)
					),
				});
			}
			if (!bananasGiven && play.gems[GEM::PURPLE] >= 50) {
				@CurrentPopup = Conversation(array<Screen@> = {
					Screen(bottom: Line("here are the bananas, chief.", left: Lori, direction: -1, color: 40)),
					Screen(
						top: Line("good. you save village. me open gate for you.", right: Chief, direction: 1, color: 64),
						bottom: Line(left: Lori)
					),
					Screen(
						top: Line("go fast. do not linger! when gate open, evil spirits can come inside.", right: Chief, direction: 1, color: 64),
						bottom: Line(left: Lori)
					),
					Screen(
						top: Line("ceremonial spot filled with magic. there we speak with gods. you do what you must do, then leave and not disturb MAGIC FORCES no more.", right: Chief, direction: 1, color: 64),
						bottom: Line(left: Lori)
					),
					Screen(
						top: Line(right: Chief),
						bottom: Line("thank you. we'll do our best. we leave in peace and respect for your duty.", left: Lori, direction: -1, color: 40),
						finish: function(bool skipping) { giveBananas(jjLocalPlayers[nearPlayerID]); }
					),
				});
			}
			if (bananasGiven) {
				@CurrentPopup = Conversation(array<Screen@> = {
					Screen(top: Line("what you wait for? GO! me must close gate or evil spirits come to village!", right: Chief, direction: 1, color: 64)),
				});
			}
	//		else {
	//			@CurrentPopup = Conversation(array<Screen@> = {
	//				Screen(top: Line("you not supposed to see this. something break. very bad!", direction: 1, color: 64)),
	//			});
	//		}
			break;
		case 2:
			if (jjTriggers[1]) {
				@CurrentPopup = Conversation(array<Screen@> = {
					Screen(top: Line("you open lock?? not good. me not want part of this! chief deal with you!", direction: 1, color: 88)),
				});
			}
			else {
				@CurrentPopup = Conversation(array<Screen@> = {
					Screen(top: Line("village center be closed to outsider by order of CHIEF BUKTOOF, unga!", direction: 1, color: 88)),
				});
			}
			break;
		case 3:
			@CurrentPopup = Conversation(array<Screen@> = {
				Screen(top: Line("me team unga.", direction: 1, color: 88)),
			});
			break;
		case 4:
			@CurrentPopup = Conversation(array<Screen@> = {
				Screen(top: Line("me team bunga.", direction: 1, color: 48)),
			});
			break;
		case 5:
			@CurrentPopup = Conversation(array<Screen@> = {
				Screen(top: Line("me ship unga x bunga. LOVE WIN!", direction: 1, color: 88)),
			});
			break;
		case 6:
			@CurrentPopup = Conversation(array<Screen@> = {
				Screen(top: Line("chief buktoof grumpy and strict, but he good leader. get village through some hard times. chief not easy job!", direction: 1, color: 48)),
			});
			break;
		case 7:
			@CurrentPopup = Conversation(array<Screen@> = {
				Screen(top: Line("maybe you think 'ooo why them not just pick up bananas, bananas right there'.", direction: 1, color: 88)),
				Screen(top: Line("well contrary to what many think, not ALL rabbit jump that high! is VERY hard work for some!!", direction: 1, color: 88)),
			});
			break;
		case 8:
			@CurrentPopup = Conversation(array<Screen@> = {
				Screen(top: Line("me in charge of put bottomless pit signs.", direction: 1, color: 48)),
				Screen(top: Line("is kinda stupid job. everybody can see pit THIS big, yes?", direction: 1, color: 48)),
				Screen(top: Line("me think bottomless pit sign putter just job created to fill void in job market created by automation taking many job from people.", direction: 1, color: 48)),
			});
			break;
		case 9:
			@CurrentPopup = Conversation(array<Screen@> = {
				Screen(top: Line("village have tradition to not hang out with you outsider. elders say we TRICKED many times before by you people.", direction: 1, color: 88)),
				Screen(top: Line("but not ALL in village want to do this. current day not many people like this rule.", direction: 1, color: 88)),
				Screen(top: Line("but CHIEF BUKTOOF believe it. so we stay hide. FOR NOW.", direction: 1, color: 88)),
				Screen(top: Line("when me have children, me hope they walk the world and meet many friend.", direction: 1, color: 88)),
			});
			break;
		case 10:
			@CurrentPopup = Conversation(array<Screen@> = {
				Screen(top: Line("rain time never come this fast before. is very bad... me think EVIL SPIRITS do this.", direction: 1, color: 48)),
				Screen(top: Line("when you meet big bad spirit, PUNCH HARD IN FACE from me!", direction: 1, color: 48)),
			});

[preview ends here]