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-1.j2l" ///@MLLE-Generated
#pragma require "More Mori.j2l" ///@MLLE-Generated
///@SaveAndRunArgs -hard ///@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::MONITOR ||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;
}
 
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;
	
	// On the other hand, this is coordinated between all connections.
	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() {
		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));
		}
	}
 
	// Probably will not be necessary any time soon
	/* solids.sort(function(ob1, ob2) {
		if (ob1.yOrg < ob1.yOrg) {
			return true;
		} else if (ob1.yOrg > ob2.yOrg) {
			return false;
		}
		return ob1.xOrg < ob2.xOrg;
	}); */
}
 
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;
 
        // Switch the current character to Lori.
        //  •  morphTo() is the safe, network-synced way to change chars
        //  •  second argument = false => no white “morph” flash if you don’t want it
        p.morphTo(CHAR::LORI, /*morphEffect=*/false);
 
        // Make Lori count as their “original” character too,
        // so Revert Morph monitors can’t send them back to Jazz/Spaz.
        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 (jjIsServer || (/* jjGameConnection == GAME::LOCAL &&  */jjMaxHealth != 2)) {
        // Normal mode: max health 2 hearts
        jjChat("/maxhealth 2");
    }
 
	if (!jjIsServer) {
		ohServerPleaseGiveMeYourAvailableTriggers();
		ohServerPleaseGiveMeYourUpdatedSolidPositions();
	}
 
}

 
void onLevelLoad(){

    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;

/* 	int BIGROCKframeID = jjAnimations[jjAnimSets[ANIM::BIGROCK].firstAnim].firstFrame;

    jjAnimFrames[BIGROCKframeID].hotSpotX = -30;
    jjAnimFrames[BIGROCKframeID].hotSpotY = -40;

    jjAnimFrames[BIGROCKframeID].coldSpotX = 60;
    jjAnimFrames[BIGROCKframeID].coldSpotY = 48; */

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::ICEPOWERUP].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::QUEEN].deactivates = true;
	jjObjectPresets[OBJECT::SPIKEBOLL].energy = 127;
	jjObjectPresets[OBJECT::SPIKEBOLL3D].energy = 127;
	//jjAnimSets[ANIM::QUEEN +4].load(); // just in case
	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();
}
 
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;
	}
 
/*  	    if (obj.eventID == OBJECT::FISH) {
        // Apply custom damage logic
        if (player !is null && player.health > 0) {
            player.hurt(2); // Deal 2 HP damage
			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;
    }
 
    // 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 hasTriggeredCrate8 = false;
bool hasTriggeredCrate5 = false;
int stage = 0;
int switchTime = 0;

bool isMultiplayerMode = false;

void onFunction66(jjPLAYER@ player) {
    isMultiplayerMode = true;
    // Other event logic here...
}

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

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 text24 = false;
int text24Timer = -1;
jjPLAYER@ text24Player;

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

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

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


void onMain() {

    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            // Only trigger if no one has triggered it yet
            if (!text9 && playerIsStillInTrigger9(play)) {
                onFunction9(play);
            }
        }
    }

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

    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            // Only trigger if no one has triggered it yet
            if (!text17 && playerIsStillInTrigger17(play)) {
                onFunction17(play);
            }
        }
    }

    if (text17) {
        if (playerIsStillInTrigger17(text17Player)) {
            if (text17Timer > 0) {
                text17Timer--;
            } else {
                onFunction17(text17Player); // Re-show text
            }
        } else {
            // Player left the trigger: clear message
            text17Player.showText(""); // Clear text
            text17 = false;
            text17Timer = -1;
            @text17Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            // Only trigger if no one has triggered it yet
            if (!text18 && playerIsStillInTrigger18(play)) {
                onFunction18(play);
            }
        }
    }

    if (text18) {
        if (playerIsStillInTrigger18(text18Player)) {
            if (text18Timer > 0) {
                text18Timer--;
            } else {
                onFunction18(text18Player); // Re-show text
            }
        } else {
            // Player left the trigger: clear message
            text18Player.showText(""); // Clear text
            text18 = false;
            text18Timer = -1;
            @text18Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            // Only trigger if no one has triggered it yet
            if (!text19 && playerIsStillInTrigger19(play)) {
                onFunction19(play);
            }
        }
    }

    if (text19) {
        if (playerIsStillInTrigger19(text19Player)) {
            if (text19Timer > 0) {
                text19Timer--;
            } else {
                onFunction19(text19Player); // Re-show text
            }
        } else {
            // Player left the trigger: clear message
            text19Player.showText(""); // Clear text
            text19 = false;
            text19Timer = -1;
            @text19Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            // Only trigger if no one has triggered it yet
            if (!text23 && playerIsStillInTrigger23(play)) {
                onFunction23(play);
            }
        }
    }

    if (text23) {
        if (playerIsStillInTrigger23(text23Player)) {
            if (text23Timer > 0) {
                text23Timer--;
            } else {
                onFunction23(text23Player); // Re-show text
            }
        } else {
            // Player left the trigger: clear message
            text23Player.showText(""); // Clear text
            text23 = false;
            text23Timer = -1;
            @text23Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            // Only trigger if no one has triggered it yet
            if (!text24 && playerIsStillInTrigger24(play)) {
                onFunction24(play);
            }
        }
    }

    if (text24) {
        if (playerIsStillInTrigger24(text24Player)) {
            if (text24Timer > 0) {
                text24Timer--;
            } else {
                onFunction24(text24Player); // Re-show text
            }
        } else {
            // Player left the trigger: clear message
            text24Player.showText(""); // Clear text
            text24 = false;
            text24Timer = -1;
            @text24Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            // Only trigger if no one has triggered it yet
            if (!text28 && playerIsStillInTrigger28(play)) {
                onFunction28(play);
            }
        }
    }

    if (text28) {
        if (playerIsStillInTrigger28(text28Player)) {
            if (text28Timer > 0) {
                text28Timer--;
            } else {
                onFunction28(text28Player); // Re-show text
            }
        } else {
            // Player left the trigger: clear message
            text28Player.showText(""); // Clear text
            text28 = false;
            text28Timer = -1;
            @text28Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            // Only trigger if no one has triggered it yet
            if (!text29 && playerIsStillInTrigger29(play)) {
                onFunction29(play);
            }
        }
    }

    if (text29) {
        if (playerIsStillInTrigger29(text29Player)) {
            if (text29Timer > 0) {
                text29Timer--;
            } else {
                onFunction29(text29Player); // Re-show text
            }
        } else {
            // Player left the trigger: clear message
            text29Player.showText(""); // Clear text
            text29 = false;
            text29Timer = -1;
            @text29Player = null;
        }
    }
	
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (play.isActive) {
            // Only trigger if no one has triggered it yet
            if (!text30 && playerIsStillInTrigger30(play)) {
                onFunction30(play);
            }
        }
    }

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

    if (jjGameMode != GAME::COOP && isMultiplayerMode) {
        for (int i = 0; i < 32; ++i) {
            if (!jjPlayers[i].isActive) continue;

            jjPLAYER@ p = jjPlayers[i];
            int screenX = int(p.cameraX) + 90;
            int screenY = int(p.cameraY) + 350;

            jjDrawString(screenX, screenY, "!!!Type T -> /coop to switch to COOP mode!!!", STRING::MEDIUM, STRING::NORMAL, 0, i);
            jjDrawString(screenX + 4, screenY + 30, "         Online, this level only works well in Coop mode.", STRING::SMALL, STRING::NORMAL, 0, i);
        }
    }
	if (jjTriggers[5] && !hasTriggeredCrate5) {
		jjTriggers[22] = true;
		hasTriggeredCrate5 = true;
		}
//Lori says piecofcake and music plays after
    const int delayTicks = 140; // 2 seconds

    if (jjTriggers[8] && !hasTriggeredCrate8) {
        hasTriggeredCrate8 = true; // prevents retriggering
        stage = 0;

        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 (jjDifficulty == 2) {
        removeCarrotsInArea(89, 0, 94, 1);
    }
    if (jjDifficulty >= 3) {
        removeCarrotsInArea(89, 1, 94, 3);
    }
	    if (layer5Timer > 0) {
        layer5Timer--;
        if (layer5Timer == 0) {
            jjLayers[5].hasTiles = false;
        }
    }
 
	if (jjGameConnection != GAME::LOCAL) {
		handleAllTriggers();
		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 removeCarrotsInArea(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::CARROT) {
                    jjDeleteObject(i);
                } else {
                    // Helpful debug line
                    // jjAlert("Not a carrot: ID=" + jjObjects[i].eventID);
                }
            }
        }
    }
}	
 
 
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 == 2){ //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);
  }
}

void onFunction27(jjPLAYER@ play) {
    // Warp only if the player is Jazz or Spaz
    if (play.charCurr == CHAR::JAZZ || play.charCurr == CHAR::SPAZ) {
        play.warpToID(27); // Warp to warp target ID 27
    }
	if (jjGameConnection == GAME::LOCAL) {
	jjTriggers[22]=true;
	}
}

array<bool> hasBeenRoasted(32, false);

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

/* void onFunction134(jjPLAYER@ player) {
    jjMusicStop(); // Mutes/stops all music
} */

 
// 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
}
 
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 or you are the only player;@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;  // X coordinate * 32 (check (X, y)
    int triggerY = 0 * 32;  // Y coordinate * 32 (check (x, Y)
    
    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) {
    play.showText("#|||||||~@@@@@@@@@                  Thanks for playing!");	
}

void onFunction17(jjPLAYER@ play) {
    play.showText("@@@@@@You might wanna reset the whole level... @#||||~IF the puzzle got too scrambled: @#type jjnext (offline SP) or press t and type /c (online COOP).@@If Lori gets stuck in a wall use f10@or in coop: t -> /sp -> f10 -> 'move Lori from wall' @then put the gamemode back to /coop");
    text17 = true;
    @text17Player = play;
    text17Timer = 70 * 5;  // reset timer here
}

bool playerIsStillInTrigger17(jjPLAYER@ play) {
    int triggerX = 13 * 32;  // X coordinate * 32 (check (X, y)
    int triggerY = 36 * 32;  // Y coordinate * 32 (check (x, Y)
    
    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;  // X coordinate * 32 (check (X, y)
    int triggerY = 40 * 32;  // Y coordinate * 32 (check (x, Y)
    
    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;  // X coordinate * 32 (check (X, y)
    int triggerY = 49 * 32;  // Y coordinate * 32 (check (x, Y)
    
    int px = int(play.xPos);
    int py = int(play.yPos);
    
    return (px >= triggerX && px < triggerX + 32 &&
            py >= triggerY && py < triggerY + 32);
}

void onFunction23(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;  // X coordinate * 32 (check (X, y)
    int triggerY = 60 * 32;  // Y coordinate * 32 (check (x, Y)
    
    int px = int(play.xPos);
    int py = int(play.yPos);
    
    return (px >= triggerX && px < triggerX + 32 &&
            py >= triggerY && py < triggerY + 32);
}
 
void onFunction24(jjPLAYER@ play) {
    play.showText("@@@@@@#|||||||~#Jump over the purple line@or the gate to the Queen will close again.@You can always open it again with the purple button though.");
	text24 = true;
    @text24Player = play;
    text24Timer = 70 * 5;  // reset timer here
}

bool playerIsStillInTrigger24(jjPLAYER@ play) {
    int triggerX = 9 * 32;  // X coordinate * 32 (check (X, y)
    int triggerY = 7 * 32;  // Y coordinate * 32 (check (x, Y)
    
    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.@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 triggerX = 7 * 32;  // X coordinate * 32 (check (X, y)
    int triggerY = 12 * 32;  // Y coordinate * 32 (check (x, Y)
    
    int px = int(play.xPos);
    int py = int(play.yPos);
    
    return (px >= triggerX && px < triggerX + 32 &&
            py >= triggerY && py < triggerY + 32);
}

void onFunction29(jjPLAYER@ play) {
    play.showText("@@@@@@#|||||||~#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;  // X coordinate * 32 (check (X, y)
    int triggerY = 6 * 32;  // Y coordinate * 32 (check (x, Y)
    
    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;
}
 
//layer visibility 
 
  
void onFunction21(jjPLAYER@ play) {
    jjLayers[3].hasTiles = true; // Show layer 3
}
 
void onFunction22(jjPLAYER@ play) {
    jjLayers[3].hasTiles = false; // Hide layer 3
}

void onFunction31(jjPLAYER@ play) {
    jjLayers[5].hasTiles = true; // show layer 5
}

void onFunction32(jjPLAYER@ play) {
    jjLayers[5].hasTiles = false; // hide layer 5
}
 
//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
}
 
// DISCLAIMER: some triggers may or may not necessarily need this.
array<bool> triggersSync(32);
 
void handleAllTriggers() {
	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 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);
	// There's probably a better way to convey this?
	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);
				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 (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) {
						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 a player gets "server denied you" it almost always means you're sending too large packets. Decrease the limit variable in that case.
				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 { // This is the most inefficient shit ever but it should be okay because it only happens on join, right? :')
					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;
 
		}
	}
}

[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;
 
    // Preferably, you should remove no fire zones in your level editor instead since you're
    // now handling no fire zones in AngelScript. The below is the totally unrecommended way of
    // removing no fire zones originally added using your level editor though.
    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 || jjred", 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;
}