Downloads containing More Mori.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: More Mori (Modded Lori,...Featured Download jjturbo9 Single player 8 Download file

File preview

const bool MLLESetupSuccessful = MLLE::Setup(); ///@MLLE-Generated
#include "MLLE-Include-1.8.asc" ///@MLLE-Generated
#pragma require "More Mori-MLLE-Data-2.j2l" ///@MLLE-Generated
#pragma require "More Mori-MLLE-Data-1.j2l" ///@MLLE-Generated
#pragma require "More Mori.j2l" ///@MLLE-Generated
///@SaveAndRunArgs -lori  ///@MLLE-Generated
#pragma require "Hailstone.png"
#pragma require "SmallerTriggerCrate.png"
#pragma require "1tilePushable.png"
#pragma require "mirroredbigrock.png"
#pragma require "LoriPieceOfCake.wav"
#pragma require "artsun.it"
//Sugar rush counter
 
int layer5Timer = 0;


bool onDrawLives(jjPLAYER@ player, jjCANVAS@ screen) {
if (jjDifficulty == 0) {
    screen.drawSprite(590, 45, ANIM::PICKUPS, 92, jjGameTicks>>2, -1, SPRITE::NORMAL);
	    screen.drawSprite(575, 45, ANIM::PICKUPS, 38, jjGameTicks>>2, -1, SPRITE::NORMAL);
    screen.drawString(570, 15, "SUGAR RUSH", STRING::SMALL, STRING::PALSHIFT, 16);
    screen.drawString(600, 35, formatInt(player.food%100, "1") + " / 100", STRING::MEDIUM, STRING::PALSHIFT, 16);}
    return false;
}
 
//vvvvvvvvvv crate push logic vvvvvvvvvvvvv
bool isSolidObject(const jjOBJ@ obj) {
	return obj.behavior == BEHAVIOR::AMMO15 || obj.behavior == BEHAVIOR::BIGOBJECT ||obj.behavior == BEHAVIOR::CRATE || obj.behavior == BEHAVIOR::MONITOR || obj.eventID == OBJECT::FIRESHIELD || obj.eventID == OBJECT::WATERSHIELD ||obj.eventID == OBJECT::CARROTCRATE || obj.eventID == OBJECT::BIGROCK || obj.eventID == OBJECT::BIGBOX || obj.eventID == OBJECT::LIGHTNINGSHIELD || obj.eventID == OBJECT::MORPH || obj.eventID == OBJECT::BIRDMORPH || obj.eventID == OBJECT::BLASTERPOWERUP || obj.eventID == OBJECT::BOUNCERPOWERUP || obj.eventID == OBJECT::ICEPOWERUP || obj.eventID == OBJECT::SEEKERPOWERUP || obj.eventID == OBJECT::RFPOWERUP || obj.eventID == OBJECT::TOASTERPOWERUP || obj.eventID == OBJECT::TNTPOWERUP || obj.eventID == OBJECT::GUN8POWERUP || obj.eventID == OBJECT::GUN9POWERUP;
}

array<bool> hasBeenRoasted(32, false); 
bool checkGamemode = true;
bool solidsReset = false;
 
array<SolidWrapper> solids;

class SolidWrapper {
	jjOBJ@ obj;
	private int8 localID = jjLocalPlayers[0].playerID;
	
	// These are only used by the server.
	uint8 eventID;
	uint8 eventIDActual; // If generator then this is different from eventID
	float xOrg, yOrg;
	float xPos, yPos;
	
	array<int8> forces(32);
	
	private SolidWrapper() {}
	
	SolidWrapper(jjOBJ@ obj) {
		@this.obj = obj;
		eventIDActual = eventID = obj.eventID;
		if (eventID == OBJECT::GENERATOR) {
			eventIDActual = jjParameterGet(int(obj.xPos / 32),int(obj.yPos / 32), 0, 8);
		}
		xOrg = obj.xOrg;
		yOrg = obj.yOrg;
		updatePos();
	}
	
	void updatePos() {
		jjOBJ@ actual = getSolid();
		
		if (actual.isActive && isSolidObject(actual)) {
			xPos = actual.xPos;
			yPos = actual.yPos;
		}
	}
	
	void updateForce(int8 solidness) {
		jjOBJ@ obj = getSolid();
		forces[localID] = solidness;
		jjSTREAM update;
		update.push(uint8(SOLID_FORCE));
		update.push(localID);
		update.push(xOrg);
		update.push(yOrg);
		update.push(solidness);
		jjSendPacket(update);
	}
	
void resetForcesForAllInactivePlayers() {
	if ((jjGameTicks % 140) != 0) return; // ✅ Run every 2 seconds max

	for (int i = 0; i < 32; i++) {
		jjPLAYER@ player = jjPlayers[i];
		if (!player.isActive && forces[i] != 0) {
			forces[i] = 0;
			jjSTREAM update;
			update.push(uint8(SOLID_FORCE));
			update.push(int8(i));
			update.push(xOrg);
			update.push(yOrg);
			update.push(int8(0));
			jjSendPacket(update);
		}
	}
}
	
	int8 getForce() {
		return forces[localID];
	}
	
	jjOBJ@ getSolid() {
		// obj.var[0] will sometimes be -1 which will epically crash the client with an access violation if not taken care of
		return obj.eventID == OBJECT::GENERATOR ? jjObjects[obj.var[0] < 0 ? 0 : obj.var[0]] : obj;
	}
}
 
void initSolids() {
	solids = array<SolidWrapper>();
 
	for (int i = 1; i < jjObjectCount; i++) {
		jjOBJ@ obj = jjObjects[i];
		if (
		(jjEventGet(int(obj.xOrg / 32), int(obj.yOrg / 32)) == int(obj.eventID) && isSolidObject(obj)) ||
		(obj.eventID == OBJECT::GENERATOR && obj.creatorType == CREATOR::LEVEL && isSolidObject(jjObjectPresets[jjParameterGet(int(obj.xPos / 32),int(obj.yPos / 32), 0, 8)]))) {
			solids.insertLast(SolidWrapper(obj));
		}
	}

}
 
void moveSolid(jjOBJ@ obj, float dx, float dy) {
	if (dx != 0 || dy != 0) {
		obj.xPos += dx;
		obj.yPos += dy;
		jjSTREAM update;
		update.push(uint8(SOLID_MOVE));
		update.push(jjLocalPlayers[0].playerID);
		update.push(obj.xOrg);
		update.push(obj.yOrg);
		update.push(obj.xPos);
		update.push(obj.yPos);
		jjSendPacket(update);
	}
}
 
void ohServerPleaseGiveMeYourUpdatedSolidPositions() {
	jjSTREAM request;
	request.push(uint8(SOLID_JOINED));
	request.push(jjLocalPlayers[0].playerID);
	jjSendPacket(request);
}
 
//^^^^^^^^crate push logic ^^^^^^^^^^^^



void onLevelBegin() {
	
    jjLayers[5].hasTiles = false;
 
//	loadAssets("Hailstone.png", ANIM::CUSTOM, 28, 24, 0, 0, pal);
	MLLE::SpawnOffgrids();
	    // loop over every player slot that might exist
    for (uint i = 0; i < 32; ++i)
    {
        jjPLAYER@ p = jjPlayers[i];
        if (!p.isActive)      // skip empty slots
            continue;
 
        p.morphTo(CHAR::LORI, /*morphEffect=*/false);
 
        p.charOrig = CHAR::LORI;
    }
			initSolids();
/* 	if (jjGameConnection != GAME::LOCAL) {
	} */
 
    if (jjDifficulty == 3 && jjMaxHealth != 1) {
        // Turbo mode: set max health to 1 heart
        jjChat("/maxhealth 1");
    }
	else if (jjDifficulty == 0 && jjMaxHealth != 3) {
        // Easy mode: set max health to 3 hearts
        jjChat("/maxhealth 3");
    }
	
    else if (jjIsServer || (/* jjGameConnection == GAME::LOCAL &&  */jjMaxHealth != 2)) {
        // Normal mode: max health 2 hearts
        jjChat("/maxhealth 2");
    }
 
	if (!jjIsServer) {
		ohServerPleaseGiveMeYourAvailableTriggers();
		ohServerPleaseGiveMeYourUpdatedSolidPositions();
	}
 
}

void onLevelLoad(){

    if (jjDifficulty == 2) {
		jjEventSet(91, 0, 0);
		jjEventSet(92, 0, 0);
    }
    if (jjDifficulty == 3) {
		jjEventSet(91, 1, 0);
		jjEventSet(92, 1, 0);
    }
	if (jjDifficulty == 2 || jjDifficulty == 3) {
		jjEventSet(55, 17, 0);
		jjEventSet(39, 25, 0);
		jjEventSet(41, 25, 0);
		jjEventSet(43, 25, 0);
	}
	if (jjDifficulty == 1) {
		jjEventSet(45, 17, 0);
		jjEventSet(47, 17, 0);
		jjEventSet(49, 17, 0);
	}
	if (jjDifficulty == 0) {
		jjEventSet(32, 9, 0);
    }

    // Hide layer 6 only in single player mode
    if (jjGameMode == GAME::SP && jjGameConnection == GAME::LOCAL) {
        jjLayers[6].hasTiles = false;
    }
	// Hide layer 2 only in online mp mode and have some tiles green instead of default blue and 2nd yellow button reachable
    if (jjGameConnection != GAME::LOCAL) {
        jjLayers[2].hasTiles = false;
		if (!jjTriggers[24]) {jjTriggers[24] = true;}
		if (!jjTriggers[7]) {jjTriggers[7] = true;}
    }
	
	if (jjDifficulty == 0) {
	if (jjTriggers[24]) {jjTriggers[24] = false;}
	}


jjObjectPresets[OBJECT::SNOW].behavior = BEHAVIOR::DESTRUCTSCENERY;

	if (jjDifficulty == 1) { // normal mode
        jjObjectPresets[OBJECT::RAVEN].energy = 1;
    } else if (jjDifficulty == 2) { // hard mode
        jjObjectPresets[OBJECT::RAVEN].energy = 2;
    } else if (jjDifficulty == 3) { // turbo mode
        jjObjectPresets[OBJECT::RAVEN].energy = 3;
    }

    jjObjectPresets[OBJECT::SUCKER].energy = 2;

    jjLayers[5].hasTiles = false;

    int CARROTCRATEPUSHABLEframeID = jjAnimations[jjAnimSets[ANIM::PICKUPS].firstAnim + 5].firstFrame;

    jjAnimFrames[CARROTCRATEPUSHABLEframeID].hotSpotX = -16;
    jjAnimFrames[CARROTCRATEPUSHABLEframeID].hotSpotY = -16;

    jjAnimFrames[CARROTCRATEPUSHABLEframeID].coldSpotX = 32;
    jjAnimFrames[CARROTCRATEPUSHABLEframeID].coldSpotY = 32;

revampNoFireHandling();
 
	for (uint i = 0; i < 32; ++i) {
		jjPLAYER@ p = jjPlayers[i];
		if (!p.isActive) // skip empty slots
			continue;
		p.fastfire = 35;
		p.currWeapon = WEAPON::BLASTER;
		for (int w = 2; w <= 9; w++) {
			p.ammo[w] = 0;
		}
	}
 
	jjObjectPresets[OBJECT::WATERSHIELD].deactivates = false;
	jjObjectPresets[OBJECT::TOASTERPOWERUP].deactivates = false;
	jjObjectPresets[OBJECT::BIGROCK].deactivates = false;
	jjObjectPresets[OBJECT::ICEPOWERUP].deactivates = false;
	jjObjectPresets[OBJECT::BOMBCRATE].deactivates = false;
	jjObjectPresets[OBJECT::CARROTBARREL].deactivates = false;
	jjObjectPresets[OBJECT::CARROTCRATE].deactivates = false;
	jjObjectPresets[OBJECT::TRIGGERCRATE].deactivates = false;
	jjObjectPresets[OBJECT::SPRINGCRATE].deactivates = false;
	jjObjectPresets[OBJECT::BLUESPRING].deactivates = false;
	jjObjectPresets[OBJECT::GREENSPRING].deactivates = false;
	jjObjectPresets[OBJECT::HORBLUESPRING].deactivates = false;
	jjObjectPresets[OBJECT::SILVERCOIN].deactivates = false;
	jjObjectPresets[OBJECT::GOLDCOIN].deactivates = false;
	jjObjectPresets[OBJECT::TNT].deactivates = false;	
	jjObjectPresets[OBJECT::SPIKEBOLL].deactivates = false;
	jjObjectPresets[OBJECT::SPIKEBOLL3D].deactivates = false;
	jjObjectPresets[OBJECT::CUPCAKE].deactivates = false;
	jjObjectPresets[OBJECT::GRAPES].deactivates = false;
	jjObjectPresets[OBJECT::WATERMELON].deactivates = false;
	jjObjectPresets[OBJECT::GUN9POWERUP].deactivates = false;
	jjObjectPresets[OBJECT::GUN9AMMO3].deactivates = false;
	jjObjectPresets[OBJECT::EXPLOSION].deactivates = false;
	jjObjectPresets[OBJECT::ICEAMMO15].deactivates = false;
	jjObjectPresets[OBJECT::EXTRALIFE].deactivates = false;
	jjObjectPresets[OBJECT::CHECKPOINT].deactivates = false;
	jjObjectPresets[OBJECT::EVA].deactivates = false;
	jjObjectPresets[OBJECT::SUPERGEM].deactivates = false;
	jjObjectPresets[OBJECT::QUEEN].deactivates = false;	
	jjObjectPresets[OBJECT::SPIKEBOLL].energy = 127;
	jjObjectPresets[OBJECT::SPIKEBOLL3D].energy = 127;
	jjAnimSets[ANIM::QUEEN].load();
	jjPIXELMAP("Hailstone.png").save(jjAnimFrames[jjAnimations[jjAnimSets[ANIM::QUEEN].firstAnim + 4].firstFrame]);
	jjPIXELMAP("SmallerTriggerCrate.png").save(jjAnimFrames[jjAnimations[jjAnimSets[ANIM::PICKUPS].firstAnim + 52].firstFrame]); 
/* 	if (obj.eventID == OBJECT::CARROTCRATE) { */

	jjPIXELMAP("1tilePushable.png").save(jjAnimFrames[jjAnimations[jjAnimSets[ANIM::PICKUPS].firstAnim + 3].firstFrame]);
	jjPIXELMAP("mirroredbigrock.png").save(jjAnimFrames[jjAnimations[jjAnimSets[ANIM::BIGROCK].firstAnim].firstFrame]);
	jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::PICKUPS].firstAnim + 65];
	jjPIXELMAP sprite("1tilePushable.png");
 
for (int i = 0; i <= 9; i++)
    sprite.save(jjAnimFrames[anim.firstFrame + i]);
 
	jjAnimations[jjAnimSets[ANIM::LORI] + RABBIT::STATIONARYJUMP] = jjAnimations[jjAnimSets[ANIM::LORI] + RABBIT::STATIONARYJUMPEND] = jjAnimations[jjAnimSets[ANIM::LORI] + RABBIT::STATIONARYJUMPSTART];
	jjObjectPresets[OBJECT::STOMPSCENERY].scriptedCollisions = true;
	jjObjectPresets[OBJECT::FISH].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[OBJECT::FISH].behavior = function(obj) {
		for (int i = 0; i < jjLocalPlayerCount; i++) {
			jjPLAYER@ victim = jjLocalPlayers[i];
			if (obj.doesCollide(victim)) {
				int force = victim.getObjectHitForce(obj);
				if (force != 0) {
					victim.objectHit(obj, force, HANDLING::ENEMY);
				} else {
					victim.hurt(2);
				}
			}
		}
		obj.behave(BEHAVIOR::FISH);
	};
	    if (jjGameConnection != GAME::LOCAL) {
        jjObjectPresets[OBJECT::BIGROCK].behavior = BigObjectBehaviorBandaid;
    }
	modifyCarrotCrate();
    jjAddObject(OBJECT::FISH, -10000, -10000);
}

bool QueenSpawned = false;

void onFunction1(jjPLAYER@ player) {


if (!QueenSpawned){
    // Spawn Queen and get her object ID
    int queenID = jjAddObject(OBJECT::QUEEN, 96 * 32 + 16, 8 * 32 + 16);

    // Get the actual object reference
    jjOBJ@ queen = jjObjects[queenID];

    if (queen !is null) {
        // Ensure animation is correct
        queen.determineCurAnim(ANIM::QUEEN, 0);
        queen.determineCurFrame(); // optional, for visual consistency
    }
QueenSpawned = true;
}
  if (!jjTriggers[12]) {
    jjTriggers[12] = true;
	}
}

 
void onLevelReload() {

    jjLayers[5].hasTiles = false;
 
/* 	if (jjIsServer || jjGameConnection == GAME::LOCAL && jjMaxHealth != 1)
    {
        jjChat("/maxhealth 1");   // never allow more than 1 heart
    } */
	MLLE::SpawnOffgridsLocal();
	initSolids();
/* 	if (jjGameConnection != GAME::LOCAL) {
 
	} */
}
 
 
void onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int) {
   	if (obj.eventID == OBJECT::STOMPSCENERY && player.specialMove != 0){
		obj.state = STATE::KILL;
	}
 
}
 
const int X = 0x58;
const int Z = 0x5A; // legacy, not used
const int VK_TAB = 0x09; // for tab
 
bool zKeyHeld;       // previous X key state
bool xKeyHeld;       // current X key state
 
bool vk_tabKeyPrev;       // previous Tab key state
bool vk_tabKeyCurr;       // current Tab key state
 
bool helicopterMode = true;   // start in Helicopter-Ears mode
 
int tabPressCount = 0; // counts Tab presses
 
// displayMode will be 0,1,2 for the three visible modes, or -1 for off
int displayMode = -1; // start off (no display)
 
 
void onPlayer(jjPLAYER@ p) {

if (p.health == 0 && !hasBeenRoasted[p.playerID]) {
        hasBeenRoasted[p.playerID] = true;
    }
	
if (p.health == 0){
if (jjGameMode == GAME::COOP){
	removeQueenAtFixedTiles(95, 6, 99, 10);
	jjTriggers[12] = false;
}
QueenSpawned = false;
if (jjGameConnection == GAME::LOCAL){ ItemsSpawned = false; }
jjLayers[2].hasTiles = false;
hasTriggeredCrate31 = false;
hasTriggeredZone6 = false;
hasTriggeredCrate23 = false;
hasTriggeredCrate5 = false;
hasTriggeredCrate11 = false;

if (jjGameConnection != GAME::LOCAL){jjLayers[3].hasTiles = true; jjLayers[2].hasTiles = true; jjLayers[1].hasTiles = true;}

            text9 = false;
            text9Timer = -1;
            @text9Player = null; 
			text16 = false;   
            text16Timer = -1;
            @text16Player = null;
            text17 = false;   
            text17Timer = -1;
            @text17Player = null;
            text18 = false;
            text18Timer = -1;
            @text18Player = null;
            text19 = false;
            text19Timer = -1;
            @text19Player = null;
            text23 = false;
            text23Timer = -1;
            @text23Player = null;
            text28 = false;    
            text28Timer = -1;       
            @text28Player = null;		
            text29 = false;
            text29Timer = -1;      
            @text29Player = null;     
            text30 = false;
            text30Timer = -1;   
            @text30Player = null;
}
 
    // Define the area in tile units (converted to pixels)
    int left   = 60 * 32;
    int right  = 88 * 32;
    int top    = 13 * 32;
    int bottom = 28 * 32;
	int left2   = 63 * 32;
    int right2  = 85 * 32;
    int top2    = 27 * 32;
    int bottom2 = 30 * 32;

    // Check if the player is within the rectangle
    bool inAntiGravZone = 
        p.xPos >= left && p.xPos <= right &&
        p.yPos >= top  && p.yPos <= bottom;
		
	bool inAntiGravZone2 = 
        p.xPos >= left2 && p.xPos <= right2 &&
        p.yPos >= top2  && p.yPos <= bottom2;

    // Apply or remove anti-gravity accordingly
	p.antiGrav = inAntiGravZone || inAntiGravZone2;
 
if (p.health == 0) {
    jjLayers[3].hasTiles = true; // Show layer 3
}
 
    if (p.powerup[WEAPON::TOASTER]) {
        // Remove the power-up status
        p.powerup[WEAPON::TOASTER] = false;
 
        // Reset TOASTER ammo to zero, no matter what
        p.ammo[WEAPON::TOASTER] = 0;
        // If currently holding TOASTER weapon, switch back to default blaster
        if (p.currWeapon == WEAPON::TOASTER) {
            p.currWeapon = WEAPON::BLASTER;
        }
    }
 
	noFireOnPlayer(p);
 
    p.helicopter = 0;
 
    jjCHARACTER@ char = jjCharacters[p.charCurr];
 
    if (p.charCurr == CHAR::LORI) {
        // Toggle jump mode with X key press
        if (xKeyHeld && !zKeyHeld)
            helicopterMode = !helicopterMode;
 
        // Detect Tab press and update displayMode accordingly
        if (vk_tabKeyCurr && !vk_tabKeyPrev) {
            tabPressCount = (tabPressCount + 1) % 4; // cycle 0,1,2,3
 
            if (tabPressCount == 0) {
                displayMode = -1; // off mode (no display)
            } else {
                displayMode = tabPressCount - 1; // 1->0, 2->1, 3->2
            }
        }
 
        // Apply jump behavior based on helicopterMode
        if (helicopterMode) {
            char.airJump = AIR::HELICOPTER;
            char.groundJump = GROUND::LORI;
            char.canHurt = true;
            p.furSet(88, 33, 88, furColor); //40 for yellow, 88 for purple
        } else {
            char.airJump = AIR::DOUBLEJUMP;
            char.doubleJumpXSpeed = 0;
            char.doubleJumpYSpeed = -6.5;
            char.doubleJumpCountMax = 2;
            char.groundJump = GROUND::JAZZ;
            p.furSet(77, 33, 88, furColor); //40 for yellow, 88 for purple
        }
 
        // Show below player text only if displayMode == 2
        if (displayMode == 2) {
            jjDrawString(
                p.xPos - 78, //-78
                p.yPos + 37, //+46
                helicopterMode ? "Helicopter Ears" : "   Double Jump",
                STRING::SMALL,
                STRING::PALSHIFT,      // colored text below player
                helicopterMode ? 24 : 16,
                2 //layer for text
            );
        }
    }
 
    // Update key states
    zKeyHeld = xKeyHeld;
    xKeyHeld = jjKey[X];
 
    vk_tabKeyPrev = vk_tabKeyCurr;
    vk_tabKeyCurr = jjKey[VK_TAB];
}
 
bool onDrawScore(jjPLAYER@ p, jjCANVAS@ canvas) {
    if (p.charCurr == CHAR::LORI) {
        // Draw jump mode text in corner only if displayMode is 0 or 1
        if (displayMode == 0 || displayMode == 1) {
            canvas.drawString(
                5, 50,
                helicopterMode ? "Helicopter Ears" : "Double Jump",
                STRING::SMALL,
                displayMode == 1 ? STRING::PALSHIFT : STRING::NORMAL,
                displayMode == 1 ? (helicopterMode ? 24 : 16) : 0
            );
        }
        // If displayMode == -1 (off), or 2 (below player), no corner text is drawn here
    }
    return false;
}

bool hasTriggeredCrate31 = false;
bool hasTriggeredZone6 = false;

bool hasTriggeredCrate8 = false;
bool hasTriggeredCrate23 = false;
bool hasTriggeredCrate5 = false;
bool hasTriggeredCrate11 = false;
int stage = 0;
int switchTime = 0;

bool text9 = false;
int text9Timer = -1;
jjPLAYER@ text9Player;

bool text16 = false;
int text16Timer = -1;
jjPLAYER@ text16Player;

bool text17 = false;
int text17Timer = -1;
jjPLAYER@ text17Player;

bool text18 = false;
int text18Timer = -1;
jjPLAYER@ text18Player;

bool text19 = false;
int text19Timer = -1;
jjPLAYER@ text19Player;

bool text23 = false;
int text23Timer = -1;
jjPLAYER@ text23Player;

bool text28 = false;
int text28Timer = -1;
jjPLAYER@ text28Player;

bool text29 = false;
int text29Timer = -1;
jjPLAYER@ text29Player;

bool text30 = false;
int text30Timer = -1;
jjPLAYER@ text30Player;

bool ItemsSpawned = false;

bool fishReady = false;
int fishBase = 0;

bool startWarpTimer = false;
uint warpStartTime = 0;

bool warpTimerActive = false;
uint warpTimerStart = 0;

// global cooldown tracker
uint lastFunction31Tick = 0;

bool playerIsStillInTrigger31(jjPLAYER@ play) {
    int px = int(play.xPos);
    int py = int(play.yPos);

    // Range: X = 34–43, Y = 57–62
    return (px >= 33 * 32 && px < 44 * 32 &&   // 44 = one past 43
            py >= 56 * 32 && py < 63 * 32);    // 63 = one past 62
}

// Track whether the player has played another mode (SP, Battle, etc.)
bool PlayedOtherModeThanCoopInMp = false;
bool StopWarping26 = false;

uint stopWarpResetTimer = 0;

void onMain() {

// sets warping on every 2 seconds 140 ticks
if (jjGameConnection != GAME::LOCAL) {
    if (jjGameTicks - stopWarpResetTimer >= 140) {
        StopWarping26 = false;
        stopWarpResetTimer = jjGameTicks;
    }
}

    jjSetWaterLevel(38 * 32, true);
    jjPLAYER@ player = jjLocalPlayers[0];

    // if we're in multiplayer and *not* in Coop, enable flag
    if (jjGameConnection != GAME::LOCAL && jjGameMode != GAME::COOP && !PlayedOtherModeThanCoopInMp) {
        PlayedOtherModeThanCoopInMp = true;
    }

    // only check warp zone logic if in Coop mode
    if (jjGameConnection != GAME::LOCAL && jjGameMode == GAME::COOP) {
        if (playerIsStillInTrigger31(player)) {
            onFunction31(player);
        }
    }
	
    if (jjGameConnection != GAME::LOCAL && jjGameMode != GAME::COOP) {
        // get player position in tiles
        int tileX = int(player.xPos / 32);
        int tileY = int(player.yPos / 32);

        // check if player is outside the rectangle
        bool outsideWarpSafeZone = (tileX < 34 || tileX > 43 || tileY < 57 || tileY > 62);

        // start timer if player leaves safe zone
        if (!warpTimerActive && outsideWarpSafeZone) {
            warpTimerActive = true;
            warpTimerStart = jjGameTicks;
        }

        // cancel timer if player re-enters safe zone
        if (!outsideWarpSafeZone) {
            warpTimerActive = false;
        }

        // warp after 1 second (70 ticks) outside safe zone
        if (warpTimerActive && outsideWarpSafeZone && jjGameTicks - warpTimerStart >= 50 && !StopWarping26) {
            player.warpToID(26);
			warpTimerActive = false;
			StopWarping26 = true;
        }
    } else {
        // reset if condition no longer holds
        warpTimerActive = false;
    }
	
if (jjGameConnection != GAME::LOCAL && !jjTriggers[25]) {jjTriggers[25] = true;}
if ((jjDifficulty == 2 || jjDifficulty == 3) && !jjTriggers[17]) {jjTriggers[17] = true;}
    // Convert pixel position to tile coordinates
    int tx = int(player.xPos / 32.0f);
    int ty = int(player.yPos / 32.0f);

    // Check if player is inside the rectangle (tiles 5–10, 8–13)
    bool insideRect = (tx >= 5 && tx <= 10 && ty >= 8 && ty <= 13);

    // Check if player is Jazz or Spaz
    bool isJazzOrSpaz = (player.charCurr == CHAR::JAZZ || player.charCurr == CHAR::SPAZ);

    // Start the timer only if player is Jazz/Spaz and outside the no-warp zone
    if (isJazzOrSpaz && !insideRect) {
        if (!startWarpTimer) {
            startWarpTimer = true;
            warpStartTime = jjGameTicks;
        }

        // Warp only if player is *still* Jazz/Spaz after 2 seconds
        if (jjGameTicks - warpStartTime >= 140) {
            if (player.charCurr == CHAR::JAZZ || player.charCurr == CHAR::SPAZ) {
                player.warpToID(27);
            }
            startWarpTimer = false; // reset timer afterward
        }
    } else {
        // Reset if not Jazz/Spaz, or inside rectangle
        startWarpTimer = false;
    }
		
    if (WaitingForTriggerQ) {
		if  (jjGameTicks - startTick >= 30){
		AwesomeCountingDownBellForFirstThree(player);
		}
		if  (jjGameTicks - startTick >= 100){
		AwesomeCountingDownBellForFirstThree(player);
		}
		if  (jjGameTicks - startTick >= 170){
		AwesomeCountingDownBellForFirstThree(player);
		}
		if  (jjGameTicks - startTick >= 240){
		jjLayers[2].hasTiles = true;
		jjTriggers[14] = false;
		AwesomeCountingDownBellForLastOne(player);
        WaitingForTriggerQ = false; // stop checking
		}
    }

    // Ensure fish anims exist
    if (!fishReady && jjAnimSets[ANIM::FISH].firstAnim > 0) {
        fishBase = jjAnimSets[ANIM::FISH].firstAnim;
        fishReady = true;
    }

    for (int i = 0; i < jjObjectCount; i++) {
        jjOBJ@ o = jjObjects[i];

        if (o.eventID == OBJECT::CRAB) {
            // --- Initialize once ---
            if (fishReady && o.var[0] == 0) {
                o.var[0] = 1;
                o.curAnim = fishBase;
                o.state = STATE::SLEEP;
                o.var[1] = int(o.xPos); // base X
                o.var[2] = int(o.yPos); // base Y (stay here!)
                o.var[3] = 0;           // dir: 0=left, 1=right
                o.playerHandling = HANDLING::SPECIAL;
                o.ySpeed = 0;
                o.yAcc = 0;
                o.bulletHandling = HANDLING::IGNOREBULLET;
            }

            if (o.var[0] == 0)
                continue; // not ready yet

            // --- Keep constant vertical position ---
            float baseY = float(o.var[2]);
            if (o.yPos != baseY) {
                o.yPos = baseY;
                o.ySpeed = 0;
                o.yAcc = 0;
            }
			//move fish 64 to left 0 to right
            const int leftDist  = 52;
            const int rightDist = -12;
            const float moveSpeed = 0.5f;

            if (o.var[3] == 0) { // moving left
                o.xPos -= moveSpeed;
                if (o.xPos <= float(o.var[1] - leftDist))
                    o.var[3] = 1; // switch to right
            } else {              // moving right
                o.xPos += moveSpeed;
                if (o.xPos >= float(o.var[1] + rightDist))
                    o.var[3] = 0; // switch to left
            }

            // --- Face direction + draw ---
            o.direction = (o.var[3] == 0 ? -1 : 1);
            o.determineCurFrame();
            o.draw();
        }
    }

if (!ItemsSpawned && jjGameConnection == GAME::LOCAL) {
jjAddObject(OBJECT::SUPERGEM, 38*32 + 16, 44*32 + 16);
jjAddObject(OBJECT::GOLDCOIN, 84*32 + 16, 59*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 14*32 + 16, 1*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 19*32 + 16, 1*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 24*32 + 16, 1*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 29*32 + 16, 1*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 34*32 + 16, 1*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 39*32 + 16, 1*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 6*32 + 16, 26*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 18*32 + 16, 55*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 47*32 + 16, 61*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 55*32 + 16, 61*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 59*32 + 16, 61*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 94*32 + 16, 40*32 + 16);
if (jjDifficulty == 0 || jjDifficulty == 1){
jjAddObject(OBJECT::SILVERCOIN, 39*32 + 16, 25*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 41*32 + 16, 25*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 43*32 + 16, 25*32 + 16);
}
if (jjDifficulty == 2 || jjDifficulty == 3){
jjAddObject(OBJECT::SILVERCOIN, 45*32 + 16, 17*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 47*32 + 16, 17*32 + 16);
jjAddObject(OBJECT::SILVERCOIN, 49*32 + 16, 17*32 + 16);
}
ItemsSpawned = true;
}

    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            
            if (!text9 && playerIsStillInTrigger9(play)) {
                onFunction9(play);
            }
        }
    }

    if (text9) {
        if (playerIsStillInTrigger9(text9Player)) {
            if (text9Timer > 0) {
                text9Timer--;
            } else {
                onFunction9(text9Player); // Re-show text
            }
        } else {
            text9Player.showText(""); // Clear text
            text9 = false;
            text9Timer = -1;
            @text9Player = null;
        }
    }

    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            
            if (!text16 && playerIsStillInTrigger16(play)) {
                onFunction16(play);
            }
        }
    }

    if (text16) {
        if (playerIsStillInTrigger16(text16Player)) {
            if (text16Timer > 0) {
                text16Timer--;
            } else {
                onFunction16(text16Player); // Re-show text
            }
        } else {
            text16Player.showText(""); // Clear text
            text16 = false;
            text16Timer = -1;
            @text16Player = null;
        }
    }

    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            
            if (!text17 && playerIsStillInTrigger17(play)) {
                onFunction17(play);
            }
        }
    }

    if (text17) {
        if (playerIsStillInTrigger17(text17Player)) {
            if (text17Timer > 0) {
                text17Timer--;
            } else {
                onFunction17(text17Player); // Re-show text
            }
        } else {
            
            text17Player.showText(""); // Clear text
            text17 = false;
            text17Timer = -1;
            @text17Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            
            if (!text18 && playerIsStillInTrigger18(play)) {
                onFunction18(play);
            }
        }
    }

    if (text18) {
        if (playerIsStillInTrigger18(text18Player)) {
            if (text18Timer > 0) {
                text18Timer--;
            } else {
                onFunction18(text18Player); // Re-show text
            }
        } else {
            
            text18Player.showText(""); // Clear text
            text18 = false;
            text18Timer = -1;
            @text18Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            
            if (!text19 && playerIsStillInTrigger19(play)) {
                onFunction19(play);
            }
        }
    }

    if (text19) {
        if (playerIsStillInTrigger19(text19Player)) {
            if (text19Timer > 0) {
                text19Timer--;
            } else {
                onFunction19(text19Player); // Re-show text
            }
        } else {
            
            text19Player.showText(""); // Clear text
            text19 = false;
            text19Timer = -1;
            @text19Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            
            if (!text23 && playerIsStillInTrigger23(play)) {
                onFunction123(play);
            }
        }
    }

    if (text23) {
        if (playerIsStillInTrigger23(text23Player)) {
            if (text23Timer > 0) {
                text23Timer--;
            } else {
                onFunction123(text23Player); // Re-show text
            }
        } else {
            
            text23Player.showText(""); // Clear text
            text23 = false;
            text23Timer = -1;
            @text23Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            
            if (!text28 && playerIsStillInTrigger28(play)) {
                onFunction28(play);
            }
        }
    }

    if (text28) {
        if (playerIsStillInTrigger28(text28Player)) {
            if (text28Timer > 0) {
                text28Timer--;
            } else {
                onFunction28(text28Player); // Re-show text
            }
        } else {
            
            text28Player.showText(""); // Clear text
            text28 = false;
            text28Timer = -1;
            @text28Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            
            if (!text29 && playerIsStillInTrigger29(play)) {
                onFunction29(play);
            }
        }
    }

    if (text29) {
        if (playerIsStillInTrigger29(text29Player)) {
            if (text29Timer > 0) {
                text29Timer--;
            } else {
                onFunction29(text29Player); // Re-show text
            }
        } else {
            
            text29Player.showText(""); // Clear text
            text29 = false;
            text29Timer = -1;
            @text29Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            
            if (!text30 && playerIsStillInTrigger30(play)) {
                onFunction30(play);
            }
        }
    }

    if (text30) {
        if (playerIsStillInTrigger30(text30Player)) {
            if (text30Timer > 0) {
                text30Timer--;
            } else {
                onFunction30(text30Player); // Re-show text
            }
        } else {
            
            text30Player.showText(""); // Clear text
            text30 = false;
            text30Timer = -1;
            @text30Player = null;
        }
    }

if (jjGameMode != GAME::COOP && jjGameConnection != GAME::LOCAL) {
    for (int i = 0; i < jjLocalPlayerCount; ++i) {
        jjPLAYER@ p = jjLocalPlayers[i];
        int screenX = int(p.cameraX) + 57;
        int screenY = int(p.cameraY);

        // set vertical placement line 1
        int line1Y = screenY + 200; // adjust this for the first line

        jjDrawString(screenX, line1Y,
            "   !!!!Type T -> /coop to switch to COOP mode!!!!",
            STRING::MEDIUM, STRING::NORMAL, 0, p.localPlayerID);

        // set vertical placement line 2
        int line2Y = screenY + 230; // adjust this for the second line

        jjDrawString(screenX + 5, line2Y,
            "             Online, this level only works well in Coop mode.",
            STRING::SMALL, STRING::NORMAL, 0, p.localPlayerID);

        // set vertical placement line 3
        int line3Y = screenY + 255; // adjust this for the third line

        jjDrawString(screenX + 5, line3Y,
            "        If you switch to coop you'll be send back to the start...",
            STRING::SMALL, STRING::NORMAL, 4, p.localPlayerID);
			
        // set vertical placement line 4
        int line4Y = screenY + 280; // adjust this for the third line

        jjDrawString(screenX + 5, line4Y,
            "  Use cheats at your own 'risk', dying in sp breaks game mechanics.",
            STRING::SMALL, STRING::NORMAL, 4, p.localPlayerID);				
    }
}

	if (jjTriggers[5] && !hasTriggeredCrate5) {
		jjTriggers[22] = true;
		hasTriggeredCrate5 = true;
		}
		
	if (jjTriggers[6] && !hasTriggeredZone6) {
		jjTriggers[27] = true;
		hasTriggeredZone6 = true;
		}		
		
	if (jjTriggers[31] && !hasTriggeredCrate31) {
		jjTriggers[27] = false;
		removeTntcratesAtFixedTiles(0, 0, 42, 62);
		hasTriggeredCrate31 = true;
		}
		
    if (jjTriggers[23] && !hasTriggeredCrate23) {
        hasTriggeredCrate23 = true; // prevents retriggering
		jjLayers[1].hasTiles = false; // Hide layer 1
		}

//Lori says piecofcake and music plays after
    const int delayTicks = 140; // 2 seconds

    if (jjTriggers[8] && !hasTriggeredCrate8) {
        hasTriggeredCrate8 = true; // prevents retriggering
        stage = 0;
		 // Hide layer 3
		startTick = jjGameTicks;
		
    WaitingForTriggerQ = true;
	
	if (jjTriggers[23]){
	jjLayers[3].hasTiles = false;
	}

        if (jjMusicLoad("LoriPieceOfCake.wav")) {
            jjMusicPlay();
            switchTime = jjGameTicks + delayTicks;
            stage = 1;
        }
    }


    if (stage == 1 && jjGameTicks >= switchTime) {
        if (jjMusicLoad("artsun.it")) {
            jjMusicPlay();
        }
        stage = 2;
    }
//^^^ Lori says piecofcake and music plays after ^^^

	

	if (jjTriggers[11] && (jjDifficulty == 1 || jjDifficulty == 2 || jjDifficulty == 3)) {
		if (!hasTriggeredCrate11) {
        removeTntcratesAtFixedTiles(0, 0, 42, 62);
        hasTriggeredCrate11 = true;} 
		else if ((jjGameTicks % 140) == 0) { 
        removeTntcratesAtFixedTiles(0, 0, 42, 62);}
	}
	    if (layer5Timer > 0) {
        layer5Timer--;
        if (layer5Timer == 0) {
            jjLayers[5].hasTiles = false;
        }
    }
 
	if (jjGameConnection != GAME::LOCAL) {
		handleAllTriggers();
		handleGoldCoinSync();
		if (!solidsReset) {
			for (int i=0; i < jjObjectCount; i++) {
				jjOBJ@ obj = jjObjects[i];
				if (isSolidObject(obj)) { 
					obj.clearPlatform();
				}
			}
			solidsReset = true;
		}
		//crate pushing code
		if (jjGameMode == GAME::SP || jjGameMode == GAME::COOP) {
			checkGamemode = true;
			for (uint i = 0; i < solids.length(); i++) {
				jjOBJ@ obj = solids[i].getSolid();
				
				if (isSolidObject(obj)) {
					if (!obj.isActive || obj.state == STATE::KILL) {
						obj.clearPlatform();
					} else {
						jjANIMFRAME@ frame = @::jjAnimFrames[obj.curFrame];
						int width = frame.width;
						int height = frame.height;
						//if (height > 30) height = 30;
						int hotX = frame.hotSpotX;
						int hotY = frame.hotSpotY;
						int solidness = obj.beSolid();
 
						if ((jjMaskedVLine(int(obj.xPos)+hotX, int(obj.yPos)+hotY, height-3) && obj.beSolid() == -1) || (jjMaskedVLine(int(obj.xPos)+hotX+width, int(obj.yPos)+hotY, height-3) && obj.beSolid() == 1)) {
							//collision
						} else {
							bool col = false;
							bool descend = true;
							for (uint j = 0; j < solids.length(); j++) {
								jjOBJ@ obj2 = solids[j].getSolid();
								if (i != j && obj.objectID != obj2.objectID && isSolidObject(obj2) && obj2.state == STATE::SLEEP) { //state of crate
									jjANIMFRAME@ frame2 = @::jjAnimFrames[obj2.curFrame];
									int width2 = frame2.width;
									int height2 = frame2.height;
									int hotX2 = frame2.hotSpotX;
									int hotY2 = frame2.hotSpotY;
									if (abs(obj.yPos - obj2.yPos) <= 16 && abs(obj.xPos - obj2.xPos) <= 100) {
										if ((abs((obj.xPos + hotX) - (obj2.xPos + hotX2 + width2)) <= 2 && obj.beSolid() == -1) || (abs((obj.xPos + hotX + width) - (obj2.xPos + hotX2)) <= 2 && obj.beSolid() == 1)) {
											col = true;
											break;
										}
									} else if (abs(obj.xPos - obj2.xPos) <= 32 && obj2.yPos - obj.yPos >= 0 && obj2.yPos - obj.yPos <= height/2 + height2/2) {
										descend = false;
									}
								}
							}
							//can push object
							if (!col && solids[i].getForce() != solidness) {
								solids[i].updateForce(solidness);
							} else if (col && solids[i].getForce() != 0) {
								solids[i].updateForce(0);
							}
							
							if (!col) {
								int netForce = 0;
								for (uint n = 0; n < 32; n++) {
									netForce += solids[i].forces[n];
								}
								obj.xPos += netForce * 0.75f;
							}
 
							if (!jjMaskedHLine(int(obj.xPos)+hotX+1, width-1, int(obj.yPos)+hotY+height) && descend && (obj.eventID == OBJECT::WATERSHIELD ||obj.eventID == OBJECT::CARROTCRATE || obj.eventID == OBJECT::SPRINGCRATE || obj.eventID == OBJECT::BOMBCRATE  || obj.eventID == OBJECT::CARROTBARREL || obj.eventID == OBJECT::TRIGGERCRATE)) {
								obj.yPos -= 3;
 
							} 
 
							if (!jjMaskedHLine(int(obj.xPos)+hotX+1, width-1, int(obj.yPos)+hotY+height) && descend && (obj.eventID == OBJECT::TOASTERPOWERUP || obj.eventID == OBJECT::BIGROCK || obj.eventID == OBJECT::BIGBOX)) {
								obj.yPos += 3;
 
							} 
 
							else if (!jjMaskedHLine(int(obj.xPos)+hotX+1, width-1, int(obj.yPos)+hotY+height) && descend && (obj.eventID == OBJECT::WATERSHIELD ||obj.eventID == OBJECT::CARROTCRATE || obj.eventID == OBJECT::BIGROCK || obj.eventID == OBJECT::BIGBOX || obj.eventID == OBJECT::TOASTERPOWERUP || obj.eventID == OBJECT::TRIGGERCRATE || obj.eventID == OBJECT::SPRINGCRATE || obj.eventID == OBJECT::BOMBCRATE  || obj.eventID == OBJECT::CARROTBARREL)) {
								//moveSolid(obj, 0, 0);
 
							} 
 
							else if (obj.state == STATE::FALL) {
 								if (obj.eventID == OBJECT::SPRINGCRATE || obj.eventID == OBJECT::BOMBCRATE  || obj.eventID == OBJECT::CARROTBARREL || obj.eventID == OBJECT::WATERSHIELD ||obj.eventID == OBJECT::CARROTCRATE || obj.eventID == OBJECT::BIGROCK || obj.eventID == OBJECT::BIGBOX || obj.eventID == OBJECT::TOASTERPOWERUP || obj.eventID == OBJECT::TRIGGERCRATE) {
									//moveSolid(obj, 0, 0); // drop the crate x pixels when falling
 								if (obj.yPos > jjWaterLevel){
									//moveSolid(obj, 0, 0); // Raise the crate x pixels if underwater
									}
								}
 
								obj.state = STATE::SLEEP;
							}
 
						}
					}
					
					if (jjIsServer) {
						solids[i].resetForcesForAllInactivePlayers();
					}

					solids[i].updatePos();
				}
			}
		}
		else if (checkGamemode) {
			for (int i = 0; i < 32; i++) {
				if (jjPlayers[i].isInGame && jjPlayers[i].platform != 0) {
					jjOBJ@ obj = jjObjects[jjPlayers[i].platform];
					obj.clearPlatform();
					if (obj.isActive && isSolidObject(obj)) { 
						jjPlayers[i].platform = 0;
					}
				}
			}
			checkGamemode = false;
			solidsReset = false;
		}
	}       
	
}

void removeTntcratesAtFixedTiles(int x1, int y1, int x2, int y2) {
    for (int i = jjObjectCount - 1; i >= 0; i--) {
        if (jjObjects[i].isActive) {
            float x = jjObjects[i].xPos;
            float y = jjObjects[i].yPos;

            if (x >= x1 * 32 && x <= x2 * 32 && y >= y1 * 32 && y <= y2 * 32) {
                if (jjObjects[i].eventID == OBJECT::BOMBCRATE) {
                    jjDeleteObject(i);
                }
            }
        }
    }
}	

void removeQueenAtFixedTiles(int x1, int y1, int x2, int y2) {
    for (int i = jjObjectCount - 1; i >= 0; i--) {
        jjOBJ@ obj = jjObjects[i];
        if (obj.isActive) {
            float x = obj.xPos;
            float y = obj.yPos;

            // Check if the Queen is inside the given tile area
            if (x >= x1 * 32 && x <= x2 * 32 && y >= y1 * 32 && y <= y2 * 32) {
                if (jjObjects[i].eventID == OBJECT::QUEEN) {
                    jjDeleteObject(i);
                }
            }
        }
    }
}	

void onFunction100(jjPLAYER@ player) {
  if (!jjTriggers[0]) {
    jjTriggers[0] = true;
    jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
  }
}
 
void onFunction101(jjPLAYER@ player) {
  if (!jjTriggers[1]) {
    jjTriggers[1] = true;
    jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
  }
}
 
void onFunction102(jjPLAYER@ player) {
  if (!jjTriggers[2]) {
    jjTriggers[2] = true;
    jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
  }
}
 
void onFunction103(jjPLAYER@ player) {
	jjTriggers[3] = !jjTriggers[3];
	jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
}
 
void onFunction104(jjPLAYER@ player) {
  if (!jjTriggers[4]) {
    jjTriggers[4] = true;
    jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
  }
}
 
void onFunction106(jjPLAYER@ player) {
  if (!jjTriggers[6]) {
    jjTriggers[6] = true;
    jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
  }
  if (!jjTriggers[21]) {
    jjTriggers[21] = true;
    jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
  }
}
 
void onFunction107(jjPLAYER@ p)
{
	if (jjDifficulty == 0 && jjMaxHealth == 3){ //easy mode
	jjChat("/maxhealth 5");
		p.health = 4;
	}
 
    else if (jjDifficulty == 1 && jjMaxHealth == 2){  	 //normal mode
        jjChat("/maxhealth 3");
		p.health = 2;
    }
 
	else if ((jjDifficulty == 3) && jjMaxHealth == 1) { //turbo mode
    jjChat("/maxhealth 2");
    p.health = 1;
	} 
}
 
void onFunction109(jjPLAYER@ player) {
	jjTriggers[9] = !jjTriggers[9];
	jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
}
 
void onFunction110(jjPLAYER@ player) {
  if (!jjTriggers[10]) {
    jjTriggers[10] = true;
    jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
  }
}

void onFunction119(jjPLAYER@ player) {
  if (!jjTriggers[19]) {
    jjTriggers[19] = true;
    jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
  }
}

uint lastBellTick1 = 0;
uint lastBellTick2 = 0;

void AwesomeCountingDownBellForFirstThree(jjPLAYER@ player) {
    if (jjGameTicks - lastBellTick1 >= 70) {
        jjSample(player.xPos, player.yPos, SOUND::COMMON_BELL_FIRE2);
        lastBellTick1 = jjGameTicks;
    }
}

void AwesomeCountingDownBellForLastOne(jjPLAYER@ player) {
    if (jjGameTicks - lastBellTick2 >= 70) {
        jjSample(player.xPos, player.yPos, SOUND::COMMON_BELL_FIRE);
        lastBellTick2 = jjGameTicks;
    }
}


void onFunction255(jjPLAYER@ player) {
    if (jjTriggers[29]) {

        int activePlayers = 0;
        for (int i = 0; i < 32; ++i) {
            if (jjPlayers[i].isActive)
                ++activePlayers;
        }

        if (activePlayers == 1) {
            jjTriggers[29] = false;
            jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
        } 
        else if (hasBeenRoasted[player.playerID]) {
            jjTriggers[29] = false;
            jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
        }
        else {
            jjSample(player.xPos, player.yPos, SOUND::COMMON_HORN1);
        }
    }
	else {
            jjSample(player.xPos, player.yPos, SOUND::COMMON_HORN1);
    }
}

void onFunction129(jjPLAYER@ player) {
	jjTriggers[29] = !jjTriggers[29];
	jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
}

 
// object respawn trigger
void onFunction111(jjPLAYER@ player) {
    jjSample(player.xPos, player.yPos, SOUND::MENUSOUNDS_TYPEENTER);
    for (int i = 1; i < jjObjectCount; i++) {
        jjOBJ@ obj = jjObjects[i];
        if (obj.isActive && isSolidObject(obj)) {
            obj.particlePixelExplosion(1);
            moveSolid(obj, obj.xOrg - obj.xPos, obj.yOrg - obj.yPos);
            obj.putOnGround(false);
        }
    }
}
 
//Death on tile if you put a text-event (id100, AS enabled)
void onFunction112(jjPLAYER@ player) {
	player.hurt(1); // -1 hp
}

//Death on tile if you put a text-event (id100, AS enabled)
void onFunction254(jjPLAYER@ player) {
	player.hurt(5); // -5 hp
}
 
void onFunction113(jjPLAYER@ player) {
    int oldFastfire = player.fastfire;
    player.fastfire = 35;
 
    bool tookAmmo = false;
 
    for (int i = 2; i <= 9; i++) {
        if (player.ammo[i] > 0) {
            tookAmmo = true;
            player.ammo[i] = 0;
        }
    }
 
    player.currWeapon = WEAPON::BLASTER;
 
    // Play sound if ammo was taken OR fire rate changed from less than 35 to 35
    if (tookAmmo || (oldFastfire < 35 && player.fastfire == 35)) {
        jjSample(player.xPos, player.yPos, SOUND::COMMON_HORN1);
    }
}
 
// Texts stay on screen until you leave the text block. Use 2xx instead of xx (so 201 instead of 1, 215 instead of 15, etc.)
 
class HelpString {
  int HelpID = -1;
  int Timer = -200;
}
array<HelpString> HelpStrings(jjLocalPlayerCount);
jjTEXTAPPEARANCE spin(STRING::SPIN);
 
bool onDrawHealth(jjPLAYER@ player, jjCANVAS@ canvas) {
  HelpString@ helpString = HelpStrings[player.localPlayerID];
  const uint xTile = uint(player.xPos) / 32, yTile = uint(player.yPos) / 32;
  if (jjEventGet(xTile,yTile) == AREA::TEXT && jjParameterGet(xTile,yTile, 8,10) == 0) {
	const int helpID = jjParameterGet(xTile,yTile, 0,8);
	if (helpID >= 200 && helpID <= 215) {
	  if (helpID - 200 != helpString.HelpID) {
		helpString.Timer = jjGameTicks + 70;
		helpString.HelpID = helpID - 200;
	  } else if (helpString.Timer < jjGameTicks) {
		helpString.Timer = jjGameTicks;
	  }
	}
  }
 
  const int time = helpString.Timer - jjGameTicks;
  if (time > -70) {
  spin.xAmp = spin.yAmp = (time == 0) ? 0 : 1; 
  spin.spacing = (time == 0) ? 2 : 1; 
	canvas.drawString(
	  0x8000 + time * int(abs(time)) / 10,
	  10,
	  jjHelpStrings[helpString.HelpID],
	  STRING::SMALL,
	 spin,
	  uint(abs(time)) * 3
	);
  } else
	helpString.HelpID = -1;
 
  return false;
}
 
// in-game Texts "#|~" is light blueish grey, "#||~" is Turquoise, "#|||~" is Green, "#||||~" is Red, "#|||||~" is light blue, 
// "#||||||~" is orange/yellow, "#|||||||~" is Pink, "#||||||||~" is white again, #for rainbow letters

void onFunction9 (jjPLAYER@ play) {
if (jjTriggers[29]) {
	play.showText("@@@@@@#And for MP Coop mode;@sorry if there's a turquoise block blocking your way,@ask the players already in to open it, they closed it.@They don't want new players to mess up the puzzle.@@If you just got roasted, you are the only player@or you got warped for not playing coop;@go all the way to the left to unblock the way.");
	text9 = true;
    @text9Player = play;
    text9Timer = 70 * 5;  // reset timer here
	}
}

bool playerIsStillInTrigger9(jjPLAYER@ play) {
    int triggerX = 53 * 32;  
    int triggerY = 0 * 32;  
    
    int px = int(play.xPos);
    int py = int(play.yPos);

    return (px >= triggerX && px < triggerX + 32 &&
            py >= triggerY && py < triggerY + 32);
}

void onFunction16(jjPLAYER@ play) {
	if (!BossActivated){
    play.showText("#|||||||~@@@@@@@@@@@@                 Thanks for playing!");
	}
	else if (BossActivated){
	play.showText("#|||||||~@@@@@@@@@@@                 Thanks for playing!");
	}
	text16 = true;
    @text16Player = play;
    text16Timer = 70 * 5;  // reset timer here	
}

bool playerIsStillInTrigger16(jjPLAYER@ play) {
    int triggerX = 99 * 32;  
    int triggerY = 24 * 32;  
    
    int px = int(play.xPos);
    int py = int(play.yPos);

    return (px >= triggerX && px < triggerX + 32 &&
            py >= triggerY && py < triggerY + 32);
}

void onFunction17(jjPLAYER@ play) {

if (jjGameMode == GAME::SP && jjGameConnection == GAME::LOCAL){
    play.showText("@@@@@ You might wanna reset the whole level... @@#||||~   IF the puzzle got too scrambled: @@ #-Type jjnext to start from the beginning.@@ -If Lori gets stuck in a wall use f10 @ and place her back.");
    text17 = true;
    @text17Player = play;
    text17Timer = 70 * 5;  // reset timer here
	}
else if (jjGameConnection != GAME::LOCAL){
    play.showText("@@@@@     You might wanna reset the whole level... @@#||||~       IF the puzzle got too scrambled: @@ #-To restart: Press t and type /c -> press enter.@@ -If Lori gets stuck in a wall press t -> /sp -> @ Lori will be warped out of the wall -> /coop.");
    text17 = true;
    @text17Player = play;
    text17Timer = 70 * 5;  // reset timer here
	}
}

bool playerIsStillInTrigger17(jjPLAYER@ play) {
    int triggerX = 13 * 32;  
    int triggerY = 36 * 32;  
    
    int px = int(play.xPos);
    int py = int(play.yPos);
    
    return (px >= triggerX && px < triggerX + 32 &&
            py >= triggerY && py < triggerY + 32);
}
 
void onFunction18(jjPLAYER@ play) {
    play.showText("@@@@@@#|||||~Shoot the springs with freezer bullets.");
	text18 = true;
    @text18Player = play;
    text18Timer = 70 * 5;  // reset timer here
}

bool playerIsStillInTrigger18(jjPLAYER@ play) {
    int triggerX = 38 * 32;  
    int triggerY = 40 * 32;  
    
    int px = int(play.xPos);
    int py = int(play.yPos);
    
    return (px >= triggerX && px < triggerX + 32 &&
            py >= triggerY && py < triggerY + 32);
}
 
void onFunction19(jjPLAYER@ play) {
    play.showText("@@@@@@#||||~The fish insta-roast you. @ You'll need a watershield to get through there. @ Hint: don't open the shield with TNT.");
	text19 = true;
    @text19Player = play;
    text19Timer = 70 * 5;  // reset timer here
}

bool playerIsStillInTrigger19(jjPLAYER@ play) {
    int triggerX = 16 * 32;  
    int triggerY = 49 * 32;  
    
    int px = int(play.xPos);
    int py = int(play.yPos);
    
    return (px >= triggerX && px < triggerX + 32 &&
            py >= triggerY && py < triggerY + 32);
}

void onFunction123(jjPLAYER@ play) {
    play.showText("@@@@@@#||||~#Hint: You can reach the coin from the other side,@ but first you need to shoot it with the electro blaster.");
	text23 = true;
    @text23Player = play;
    text23Timer = 70 * 5;  // reset timer here
}

bool playerIsStillInTrigger23(jjPLAYER@ play) {
    int triggerX = 95 * 32;  
    int triggerY = 60 * 32;  
    
    int px = int(play.xPos);
    int py = int(play.yPos);
    
    return (px >= triggerX && px < triggerX + 32 &&
            py >= triggerY && py < triggerY + 32);
}
 

void onFunction28(jjPLAYER@ play) {
    play.showText("@@@@@#|||||||~#Hey Jazz or Spaz, sorry but this level is made for Lori.@ If you wanna play it you have to own The Secret Files (TSF).@ Or any version that has Lori as a playable character. @@@ If your game has Lori please come back with her; @ you can use jjnext for example. At the level start you will@ automatically be turned into Lori if she's available. @ You can also use jjmorph a few times to turn into Lori -> f10. @@@ There's no exit from here, so if you wanna exit@ press exit -> quit.");
    text28 = true;
    @text28Player = play;
    text28Timer = 70 * 5;  // reset timer here 
}

bool playerIsStillInTrigger28(jjPLAYER@ play) {
    int px = int(play.xPos);
    int py = int(play.yPos);

    // Range: X = 6–9, Y = 9–12
    return (px >= 5 * 32 && px < 11 * 32 &&
            py >= 8 * 32 && py < 14 * 32);
}

void onFunction29(jjPLAYER@ play) {
    if (jjGameConnection != GAME::LOCAL) {
    play.showText("@@@@@@#|||||||~#For Coop-mode: newly joined players might mess up your progress.@ With this button you can keep them out at first, @ so you can chat a bit before you potentially let them in.@ You can also use 't -> /maxplayers x' or a server password.");
    text29 = true;
    @text29Player = play;
    text29Timer = 70 * 5;  // reset timer here
	}
}

bool playerIsStillInTrigger29(jjPLAYER@ play) {
    int triggerX = 62 * 32;  
    int triggerY = 6 * 32;  
    
    int px = int(play.xPos);
    int py = int(play.yPos);
    
    return (px >= triggerX && px < triggerX + 32 &&
            py >= triggerY && py < triggerY + 32);
}

void onFunction30(jjPLAYER@ play) {
if (jjDifficulty == 0) {
    play.showText("@@@@@@#On easy mode parts are blocked off.@ Open the trigger crate,@ then get the sugar rush and go through the fish maze!!!");
    text30 = true;
    @text30Player = play;
    text30Timer = 70 * 5;  // reset timer here
	}
}

bool playerIsStillInTrigger30(jjPLAYER@ play) {
    // Tile 1 coordinates
    int triggerX1 = 12 * 32;
    int triggerY1 = 20 * 32;

    // Tile 2 coordinates
    int triggerX2 = 37 * 32;
    int triggerY2 = 37 * 32;

    int px = int(play.xPos);
    int py = int(play.yPos);

    bool insideTile1 = (px >= triggerX1 && px < triggerX1 + 32 &&
                        py >= triggerY1 && py < triggerY1 + 32);

    bool insideTile2 = (px >= triggerX2 && px < triggerX2 + 32 &&
                        py >= triggerY2 && py < triggerY2 + 32);

    return insideTile1 || insideTile2;
}


void onFunction31(jjPLAYER@ play) {
    if (PlayedOtherModeThanCoopInMp) {
        hasBeenRoasted[play.playerID] = true;
		StopWarping26 = false;

        if (jjGameConnection != GAME::LOCAL && jjGameMode == GAME::COOP) {
            play.warpToID(25);
        }

        // reset the flag so it won't warp again until another mode switch
        PlayedOtherModeThanCoopInMp = false;
		jjLayers[2].hasTiles = false;
		jjLayers[3].hasTiles = true;
		jjLayers[5].hasTiles = false;
    }
}
 
//layer visibility 
int startTick = 0;
bool WaitingForTriggerQ = false;

bool BossActivated = false;

void onFunction11(jjPLAYER@ play){
BossActivated = true;
}

void onFunction12(jjPLAYER@ play) {

if (jjGameConnection == GAME::LOCAL){
	jjLayers[2].hasTiles = true;}
}

void onFunction214(jjPLAYER@ play) {
    if (!jjTriggers[13]) {
        jjTriggers[14] = true;
    }
}

void onFunction215(jjPLAYER@ play) {
if (jjTriggers[13]){
	jjLayers[2].hasTiles = true;
	}
}

void onFunction216(jjPLAYER@ play) {
if (jjGameConnection != GAME::LOCAL){
jjLayers[2].hasTiles = false;}
}

void onFunction217(jjPLAYER@ play) {
if (jjGameConnection == GAME::LOCAL && !jjTriggers[22]) {jjTriggers[22] = true;}
}


void onFunction21(jjPLAYER@ play) {
    jjLayers[3].hasTiles = true; // Show layer 3
}
 
void onFunction22(jjPLAYER@ play) {
    jjLayers[3].hasTiles = false; // Hide layer 3
}

void onFunction32(jjPLAYER@ play) {
    jjLayers[5].hasTiles = false;
}

void onFunction33(jjPLAYER@ player) {
    jjLayers[5].hasTiles = true;
}
 
//sync triggers for all player in online coop (also newly joined players)
 
enum PACKET_TYPE {
	TRIGGER_CHANGE,
	TRIGGER_JOINED,
	SOLID_MOVE,
	SOLID_FORCE,
	SOLID_JOINED,
	GOLDCOIN_MOVE
}
 
// DISCLAIMER: some triggers may or may not necessarily need this.
array<bool> triggersSync(32);
 
void handleAllTriggers() {
	if ((jjGameTicks % 140) == 0) { // ✅ Only run every 2 seconds
		for (int i = 0; i < 32; i++) {
			if (jjTriggers[i] != triggersSync[i]) {
				triggersSync[i] = jjTriggers[i];
				jjSTREAM request;
				request.push(uint8(TRIGGER_CHANGE));
				request.push(jjLocalPlayers[0].playerID);
				request.push(i);
				request.push(jjTriggers[i]);
				jjSendPacket(request);
			}
		}
	}
}

void handleGoldCoinSync() {
	if (!jjIsServer) return;

	// Run every 2 seconds
	if ((jjGameTicks % 140) != 0) return;

	for (int i = 0; i < jjObjectCount; i++) {
		jjOBJ@ obj = jjObjects[i];
		if (obj.isActive && obj.eventID == OBJECT::GOLDCOIN) {
			jjSTREAM packet;
			packet.push(uint8(GOLDCOIN_MOVE));
			packet.push(int8(0)); // dummy player ID (server)
			packet.push(obj.xOrg);
			packet.push(obj.yOrg);
			packet.push(obj.xPos);
			packet.push(obj.yPos);
			jjSendPacket(packet);
		}
	}
}

void ohServerPleaseGiveMeYourAvailableTriggers() {
	jjSTREAM request;
	request.push(uint8(TRIGGER_JOINED));
	request.push(jjLocalPlayers[0].playerID);
	jjSendPacket(request);
}
 
void onReceive(jjSTREAM &in packet, int clientID) {
	jjSTREAM orig = packet;
	uint8 type;
	int8 playerID;
	packet.pop(type);
	packet.pop(playerID);

	bool accept = (jjIsServer && jjPlayers[playerID].clientID == clientID) || !jjIsServer;
	if (accept) {
		switch (type) {
			case TRIGGER_CHANGE: {
				int triggerID;
				packet.pop(triggerID);
				bool status;
				packet.pop(status);

				// Skip syncing trigger 28
				if (triggerID != 28) {
					triggersSync[triggerID] = status;
					jjTriggers[triggerID] = status;
					if (jjIsServer) {
						jjSendPacket(orig, -clientID);
					}
				}
			} break;

			case TRIGGER_JOINED: {
				uint bitset = 0;
				if (jjIsServer) {
					for (int i = 0; i < 32; i++) {
						if (i == 28) continue; // ← Skip trigger 28 here
						if (triggersSync[i]) {
							bitset |= 1 << i;
						}
					}
					jjSTREAM update;
					update.push(uint8(TRIGGER_JOINED));
					update.push(int8(0));
					update.push(bitset);
					jjSendPacket(update, clientID);
				} else {
					packet.pop(bitset);
					int id = 0;
					while (bitset != 0 && id < 32) {
						if (id != 28) { // ← Skip trigger 28 here too
							jjTriggers[id] = triggersSync[id] = ((bitset & 1) != 0);
						}
						bitset >>>= 1;
						id++;
					}
				}
			} break;

			case SOLID_FORCE: {
				float xOrg, yOrg;
				packet.pop(xOrg);
				packet.pop(yOrg);
				for (uint i = 0; i < solids.length(); i++) {
					jjOBJ@ obj = solids[i].getSolid();
					if (obj.xOrg == xOrg && obj.yOrg == yOrg) {
						packet.pop(solids[i].forces[playerID]);
						if (jjIsServer) {
							jjSendPacket(orig, -clientID);
						}
						return;
					}
				}
			} break;

			case SOLID_MOVE: {
				float xOrg, yOrg, newX, newY;
				packet.pop(xOrg);
				packet.pop(yOrg);
				packet.pop(newX);
				packet.pop(newY);
				for (uint i = 0; i < solids.length(); i++) {
					jjOBJ@ obj = solids[i].getSolid();
					if (obj.xOrg == xOrg && obj.yOrg == yOrg) {
						obj.xPos = newX;
						obj.yPos = newY;
						if (jjIsServer) {
							jjSendPacket(orig, -clientID);
						}
						return;
					}
				}
			} break;

			case SOLID_JOINED: {
				if (jjIsServer) {
					const int limit = 10;
					int objsLeft = 10;
					jjSTREAM objects;
					for (uint i = 0; i < solids.length(); i++) {
						SolidWrapper@ sw = solids[i];
						jjOBJ@ obj = sw.getSolid();
						if (sw.eventID != OBJECT::GENERATOR || obj.isActive) {
							objects.push(sw.eventIDActual);
							objects.push(sw.xOrg);
							objects.push(sw.yOrg);
							objects.push(sw.xPos);
							objects.push(sw.yPos);
							objsLeft--;
						}
						
						if (objsLeft <= 0 || i == solids.length() - 1) {
							jjSTREAM update;
							update.push(uint8(SOLID_JOINED));
							update.push(int8(0));
							update.push(objects.getSize());
							jjSTREAM objectsCompressed;
							jjZlibCompress(objects, objectsCompressed);
							update.push(objectsCompressed);
							jjSendPacket(update, clientID);
							objsLeft = limit;
							objects = jjSTREAM();
						}
					}

				} else {
					uint uncompressedSize;
					packet.pop(uncompressedSize);
					jjSTREAM objects;
					packet.pop(objects);
					if (jjZlibUncompress(objects, objects, uncompressedSize)) {
						while (objects.getSize() != 0) {
							uint8 eventID;
							float xOrg, yOrg;
							float xPos, yPos;
							objects.pop(eventID);
							objects.pop(xOrg);
							objects.pop(yOrg);
							objects.pop(xPos);
							objects.pop(yPos);
 
							bool wasActive = false;
							for (uint i = 0; i < solids.length(); i++) {
								jjOBJ@ obj = solids[i].getSolid();
								if (obj.isActive && obj.eventID == eventID && obj.xOrg == xOrg && obj.yOrg == yOrg) {
									obj.xPos = xPos;
									obj.yPos = yPos;
									wasActive = true;
									break;
								}
							}
 
							if (wasActive) {
								continue;
							}
 
							jjOBJ@ act = jjObjects[jjAddObject(eventID, xOrg, yOrg, 0, CREATOR::LEVEL)];
							if (act.behavior == BEHAVIOR::CRATE || act.eventID == OBJECT::CARROTCRATE) {
								act.state = STATE::ACTION;
								act.xPos = xPos;
								act.yPos = yPos;
								act.putOnGround();
							} else {
								jjKillObject(act.objectID);
							}
						}
					}
					
				}
			} break;
			case GOLDCOIN_MOVE: {
	float xOrg, yOrg, newX, newY;
	packet.pop(xOrg);
	packet.pop(yOrg);
	packet.pop(newX);
	packet.pop(newY);

	for (int i = 0; i < jjObjectCount; i++) {
		jjOBJ@ obj = jjObjects[i];
		if (obj.isActive && obj.eventID == OBJECT::GOLDCOIN &&
		    obj.xOrg == xOrg && obj.yOrg == yOrg) {
			obj.xPos = newX;
			obj.yPos = newY;
			return;
		}
	}
} break;
		}
	}
}

[SOLID]
void carrotCrateBehavior(jjOBJ@ obj) {
    if (obj.state == STATE::START) {
        obj.putOnGround();
        obj.state = STATE::SLEEP;
    } else if (obj.state == STATE::ACTION) {
        //jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, BEHAVIOR::DEFAULT)].curAnim = obj.killAnim;
        obj.particlePixelExplosion(2);
        jjSample(obj.xPos, obj.yPos, SOUND::COMMON_WOOD1);
        obj.bulletHandling = HANDLING::IGNOREBULLET;
        obj.playerHandling = HANDLING::EXPLOSION;
        obj.state = STATE::KILL;
    } else {
        obj.behave(BEHAVIOR::CRATE);
    }
}

void modifyCarrotCrate() {
    uint firstAnim = jjAnimSets[ANIM::PICKUPS].firstAnim;
    uint curFrame = jjAnimations[firstAnim + 5].firstFrame;
    for (uint i = 0; i < 256; i++) {
        jjANIMSET@ set = jjAnimSets[ANIM::CUSTOM[i]];
        if (set.firstAnim == 0) {
            set.load(jjPIXELMAP("1tilePushable.png"), 32, 32);
            firstAnim = set.firstAnim;
            curFrame = jjAnimations[firstAnim].firstFrame;
            break;
        }
    }
    
    jjObjectPresets[OBJECT::CARROTCRATE].killAnim = firstAnim;
    jjObjectPresets[OBJECT::CARROTCRATE].curFrame = curFrame;
    jjObjectPresets[OBJECT::CARROTCRATE].behavior = carrotCrateBehavior;
}

[SOLID]
void BigObjectBehaviorBandaid(jjOBJ@ obj) {
    switch (obj.state) {
        case STATE::START:
            obj.putOnGround();
            obj.state = STATE::SLEEP;
            break;
        case STATE::ACTION:
        case STATE::SLEEP:
            obj.draw();
            break;
        case STATE::FREEZE:
            if ((jjGameTicks & 3) == 0 && obj.freeze > 0) {
                obj.freeze--;
            }
            
            if (obj.freeze <= 0) {
                obj.state = obj.oldState;
            }
            obj.draw();
            break;
        default:
            obj.behave(BEHAVIOR::BIGOBJECT);
            break;
    }
}
 
// No Fire Zone Logic
array<bool> overrideNoFire(jjLocalPlayerCount);
 
class RevertNoFire : jjBEHAVIORINTERFACE {
    void onBehave(jjOBJ@ obj) {
        obj.behave(BEHAVIOR::PICKUP);
    }
 
    bool onObjectHit(jjOBJ@ obj, jjOBJ@, jjPLAYER@ player, int) {
        overrideNoFire[player.localPlayerID] = true;
        obj.behavior = BEHAVIOR::EXPLOSION2;
        obj.scriptedCollisions = false;
        obj.frameID = 0;
        jjSample(player.xPos, player.yPos, SOUND::COMMON_PICKUPW1);
        return true;
    }
}
 
void revampNoFireHandling() {
    jjObjectPresets[OBJECT::FASTFIRE].behavior = RevertNoFire();
    jjObjectPresets[OBJECT::FASTFIRE].scriptedCollisions = true;
 
    // now handling no fire zones in AngelScript don't place fire zones in MLLE
    for (int y = 0; y < jjLayers[4].height; y++) {
        for (int x = 0; x < jjLayers[4].width; x++) {
            if (jjEventGet(x, y) == AREA::NOFIREZONE && jjParameterGet(x, y, 2, 2) == 0) { // Remove iff NO FIRE ZONE's var is No Fire
                jjEventSet(x, y, 0);
            }
        }
    }
}
 
void noFireOnPlayer(jjPLAYER@ player) {
    int id = player.localPlayerID;
 
    bool isInNoFireZone = player.yPos >= jjWaterTarget || (
        player.xPos >= (61 * 32) && player.xPos <= (78 * 32) &&
        player.yPos >= (31 * 32) && player.yPos <= (38 * 32)
        );
 
    player.noFire = !overrideNoFire[id] && isInNoFireZone;
 
    // Reset if the player goes outside no fire zones;
    if (overrideNoFire[id] && !isInNoFireZone) {
        overrideNoFire[id] = false;
    }
}
 
// Fur color change via chat, but only in a specific area
uint8 furColor = 40;
 
dictionary furCommands = {
    {"!resetcolor", 40},
    {"!lightgreen", 15},
    {"!green", 16},
    {"!darkgreen", 17},
    {"!deeppurple", 89},
    {"!red", 24},
    {"!darkred", 25},
    {"!darkerred", 25},
    {"!blue", 32},
    {"!darkblue", 33},
    {"!pink", 48},
    {"!white", 64},
    {"!grey", 65},
    {"!blue-gray", 72},
    {"!blue-grey", 73},
    {"!turquoise", 81},
    {"!aqua", 80},
    {"!orange", 41},
    {"!purple", 88}
};
 
void onChat(int clientID, string &in text, CHAT::Type chatType) {
    // Make text lowercase
    for (uint i = 0; i < text.length(); i++) {
        uint8 codepoint = text[i] | 32;
        if (codepoint >= "a"[0] && codepoint <= "z"[0]) {
            text[i] = codepoint;
        }
    }
 
	if (furCommands.exists(text)) {
		for (int i = 0; i < 32; i++) {
			jjPLAYER@ player = jjPlayers[i];
			if (player.clientID == clientID) {
				bool inBooth = player.xPos >= 48 * 32 && player.xPos <= 53 * 32 &&
							   player.yPos >= 8 * 32 && player.yPos <= 12 * 32;
 
				if (inBooth) {
					if (jjLocalPlayers[0].clientID == clientID) {
						furColor = uint8(furCommands[text]);
						jjSample(player.xPos, player.yPos, SOUND::COMMON_SWISH4);
 
						                    // ✅ Show layer 5 for 30 frames
                    jjLayers[5].hasTiles = true;
                    layer5Timer = 50;
 
					}
 
					if (jjIsServer) {
						jjAlert(player.nameUnformatted + " turned " + 
								(text == "!resetcolor" ? "yellow" : text.substr(1)) + "!!!", true, STRING::SMALL);								
					}
				} else {
					if (jjLocalPlayers[0].clientID == clientID) {
						jjSample(player.xPos, player.yPos, SOUND::COMMON_HORN1);
					}
 
					if (jjIsServer) {
						jjAlert("Go back to the Hairspray Booth to change color...", true, STRING::SMALL);								
					}
				}
				break;
			}
		}
	}
}

dictionary furCheats = {
    {"jjcresetcolor", 40},
    {"jjclightgreen", 15},
    {"jjcgreen", 16},
    {"jjcdarkgreen", 17},
    {"jjcdeeppurple", 89},
    {"jjcred", 24},
    {"jjcdarkred", 25},
    {"jjcdarkerred", 25},
    {"jjcblue", 32},
    {"jjcdarkblue", 33},
    {"jjcpink", 48},
    {"jjcwhite", 64},
    {"jjcgrey", 65},
    {"jjcblue-gray", 72},
    {"jjcblue-grey", 73},
    {"jjcturquoise", 81},
    {"jjcaqua", 80},
    {"jjcorange", 41},
    {"jjcpurple", 88}
};

//!blue-gray                            !blue-grey

bool onCheat(string &in cheat) {

    if (cheat == "jjcpu")
    {
        // Prevent the cheat from activating
        return true;
    }

    if (furCheats.exists(cheat)) {
            jjPLAYER@ play = jjLocalPlayers[0];
                bool inBooth = play.xPos >= 48 * 32 && play.xPos <= 53 * 32 &&
                               play.yPos >= 8 * 32 && play.yPos <= 12 * 32;
 
                if (inBooth) {
                        furColor = uint8(furCheats[cheat]);
                        jjSample(play.xPos, play.yPos, SOUND::COMMON_SWISH4);
 
                                            // ✅ Show layer 5 for 30 frames
                    jjLayers[5].hasTiles = true;
                    layer5Timer = 50;
 
                    play.showText("@@@#" + play.nameUnformatted + " turned " + (cheat == "jjcresetcolor" ? "yellow" : cheat.substr(3)) + "!!!", STRING::SMALL);
                } 
				else {
                        jjSample(play.xPos, play.yPos, SOUND::COMMON_HORN1);
 
                        play.showText("@@@#Go back to the Hairspray Booth to change color...", STRING::SMALL);                               
                }
                return true;
    }
	return false;
}