Downloads containing SwitchTestV.j2as

Downloads
Name Author Game Mode Rating
JJ2+ Only: Mighty Switch Test!Featured Download Violet CLM Test 8.4 Download file

File preview

#pragma offer "SUCCESSF.WAV"
#pragma offer "TC_TRANS.WAV"

array<int> SwitchesPerLevel;
array<CHAR::Char> CharactersPerLevel;

array<bool> TriggerColorsOld(3, true);

class Point {
	float x; float y;
	Point(){}
	Point(int xx, int yy) {
		x = xx * 32.f + 16;
		y = yy * 32.f + 16;
	}
}
array<Point> TargetsPerLevel;

void onLevelLoad() {
	jjUseLayer8Speeds = true;
	
	jjObjectPresets[OBJECT::ICEBULLET].behavior = Switch;
	jjWeapons[WEAPON::ICE].defaultSample = false;
	jjWeapons[WEAPON::BLASTER].infinite = false;
	jjWeapons[WEAPON::BLASTER].replenishes = false;
	
	jjObjectPresets[OBJECT::MORPH].behavior = InfiniteMonitor;
	jjObjectPresets[OBJECT::SPARK].behavior = Electricity;
	jjObjectPresets[OBJECT::SPARK].lightType = LIGHT::POINT2;
	jjObjectPresets[OBJECT::SPARK].playerHandling = HANDLING::PARTICLE;
	
	jjObjectPresets[OBJECT::BURGER].determineCurAnim(ANIM::TWEEDLE, 7);
	jjObjectPresets[OBJECT::BURGER].playerHandling = HANDLING::SPECIAL;
	jjObjectPresets[OBJECT::BURGER].scriptedCollisions = true;
	jjObjectPresets[OBJECT::BURGER].energy = 5;
	jjObjectPresets[OBJECT::BURGER].behavior = Tweedle;
	jjObjectPresets[OBJECT::BURGER].lightType = LIGHT::NORMAL;
	jjObjectPresets[OBJECT::BURGER].light = 17;
	jjObjectPresets[OBJECT::BURGER].direction = -1;
	
	jjObjectPresets[OBJECT::APPLE].behavior = MovingTarget;
	jjObjectPresets[OBJECT::APPLE].deactivates = false;
	jjObjectPresets[OBJECT::APPLE].playerHandling = HANDLING::PARTICLE;
	jjObjectPresets[OBJECT::APPLE].lightType = LIGHT::BRIGHT;
	jjObjectPresets[OBJECT::APPLE].light = 14;
	
	jjObjectPresets[OBJECT::FLICKERGEM].var[2] = 17;
	jjObjectPresets[OBJECT::FLICKERGEM].playerHandling = HANDLING::DELAYEDPICKUP;
	jjObjectPresets[OBJECT::FLICKERGEM].points = 0;
	
	jjSampleLoad(SOUND::COMMON_HARP1, "SUCCESSF.WAV");
	jjSampleLoad(SOUND::COMMON_NOCOIN, "TC_TRANS.WAV");
	
	jjAnimSets[ANIM::FLARE].load();
	
	jjGenerateSettableTileArea(5, 0, 0, jjLayerWidth[5], jjLayerHeight[5]);
	
	for (int x = 0; jjEventGet(x, 0) != 0; ++x) {
		const uint16 tileID = jjTileGet(4, x, 0);
		if (tileID == 50) CharactersPerLevel.insertLast(CHAR::SPAZ);
		else if (tileID == 60) CharactersPerLevel.insertLast(CHAR::JAZZ);
		else if (tileID == 40) CharactersPerLevel.insertLast(jjIsTSF ? CHAR::LORI : CHAR::JAZZ); //I guess
		else CharactersPerLevel.insertLast(CHAR::FROG); //placeholder
		SwitchesPerLevel.insertLast(jjParameterGet(x, 0, 0, -8));
		LaserBlockArraysPerLevel.insertLast(array<LaserBlock>());
	}
	TargetsPerLevel.resize(LaserBlockArraysPerLevel.length + 1);
	
	for (int x = LayerWidth - 1; x >= 0; --x)
		for (int y = LayerHeight - 1; y >= 0; --y) {
			const uint8 eventID = jjEventGet(x, y);
			if (eventID == OBJECT::APPLE) {
				SwitchBlocks.insertLast(SwitchBlock(x, y));
				jjEventSet(x, y, 0);
			} else if (eventID == OBJECT::BANANA) {
				LaserBlockArraysPerLevel[jjParameterGet(x, y, 0, 5)].insertLast(LaserBlock(x, y));
				jjEventSet(x, y, 0);
			} else if (eventID == AREA::WARPTARGET) {
				const uint8 warpID = jjParameterGet(x, y, 0, 8);
				if (warpID >= 200) {
					TargetsPerLevel[warpID - 200] = Point(x, y);
				}
			}
			if (jjTileGet(4, x, y) == 17 | TILE::ANIMATED) { //warp
				jjTileSet(4, x, y, 0);
				jjTileSet(5, x, y, 17 | TILE::ANIMATED);
			}
		}
	
	int tileSourceID = 260;
	jjANIMATION@ animation = jjAnimations[jjAnimSets[ANIM::CUSTOM[0]].allocate(array<uint> = {12})];
	int curFrame = animation.firstFrame;
	for (uint frameID = 0; frameID < animation.frameCount; ) {
		jjPIXELMAP fullTile(tileSourceID); tileSourceID += 10;
		jjPIXELMAP quarterTile(16, 16);
		for (uint x = 0; x < quarterTile.width; ++x)
			for (uint y = 0; y < quarterTile.height; ++y)
				quarterTile[x,y] = fullTile[x,y];
		for (int repetitionCount = (frameID == 0 || frameID == 6) ? 4 : 1; repetitionCount > 0; --repetitionCount) {
			jjANIMFRAME@ frame = jjAnimFrames[curFrame++];
			quarterTile.save(frame);
			frame.hotSpotX = -8; frame.hotSpotY = -4;
			++frameID;
		}
	}
	jjAnimations[jjAnimSets[ANIM::AMMO] + 29] = animation; //ice
	//jjAnimations[jjAnimSets[ANIM::PICKUPS] + 29] = animation; //jazz gun
	//jjAnimations[jjAnimSets[ANIM::PICKUPS] + 30] = animation; //spaz gun
	//if (jjIsTSF)
	//	jjAnimations[jjAnimSets[ANIM::PLUS_AMMO] + 5] = animation; //lori gun

	/*if (jjObjectCount > 1) { //Load Game
		WarpID = jjLocalPlayers[0].score - 1;
		for (int i = jjObjectCount - 1; i >= 1; --i)
			jjObjects[i].behavior = jjObjectPresets[jjObjects[i].eventID].behavior;
	}*/
	
	jjAddObject(OBJECT::APPLE, 0, 0); //target
}
void onLevelBegin() {
	jjWeapons[WEAPON::ICE].allowed = true;
	jjWeapons[WEAPON::ICE].allowedPowerup = true;
	
	jjPLAYER@ play = jjLocalPlayers[0];
	play.spriteMode = SPRITE::INVISIBLE;
	play.lightType = LIGHT::NONE;
	play.ammo[WEAPON::BLASTER] = 0;
	
	onFunction0(play, 0); //play.score);
	
	if (jjLocalPlayerCount > 1)
		jjAlert("|This level is not designed for splitscreeners!");
}
int QuakeLayer4 = 0;
void onMain() {
	for (int i = 0; i < 32; ++i) {
		jjPLAYER@ play = jjPlayers[i];
		if (play.isInGame && (play.blink == 0 || ((jjRenderFrame >> 2) & 1) == 1)) {
			if (play.isLocal && crispyFried)
				continue;
			else if (play.isLocal && suckerOn) {
				jjANIMATION@ BallAnim = jjAnimations[jjAnimSets[play.setID] + RABBIT::ROLLING];
				jjDrawSpriteFromCurFrame(play.xOrg, play.yOrg, BallAnim.firstFrame + ((jjGameTicks >> 2) % BallAnim.frameCount), play.direction, SPRITE::PLAYER, i, 5);
			} else if (play.isLocal && knockedIntoScreen != 0) {
				jjANIMATION@ FallAnim = jjAnimations[jjAnimSets[play.setID] + RABBIT::FALL];
				knockedIntoScreen += 0.2;
				float scale = knockedIntoScreen;
				if (scale > 3.0f)
					scale = 3.0f;
				//else 
					//play.cameraFreeze(play.cameraX + (jjRandom() % 7) - 3, play.cameraY + (jjRandom() % 7) - 3, false, true);
				jjDrawResizedSprite(play.xOrg, play.yOrg, ANIM::AMMO, 5, int(scale * 2), scale, scale, SPRITE::SINGLEHUE, 210, 1);
				jjDrawRotatedSpriteFromCurFrame(play.xOrg, play.yOrg + knockedIntoScreen * 2, FallAnim.firstFrame + ((jjGameTicks >> 3) % FallAnim.frameCount), int(knockedIntoScreen * 4 * play.direction), scale, scale, SPRITE::PLAYER, i, 1);
			} else
				jjDrawSpriteFromCurFrame(play.xPos, play.yPos, play.curFrame, play.direction, SPRITE::PLAYER, i, 5);
			//jjDrawTile(int(play.xPos / 32) * 32, int(play.yPos / 32) * 32, 51);
			//jjDrawPixel(play.xPos, play.yPos, 255);
			//jjDrawString(play.xPos, play.yPos - 50, "" + (int(play.xPos) & 31));
			//jjDrawString(play.xPos, play.yPos - 30, "" + (int(play.yPos) & 31));
		}
	}
	if (QuakeLayer4 > 0) {
		if (--QuakeLayer4 == 0) {
			jjLayerXOffset[4] = 0;
			jjLayerYOffset[4] = 0;
		} else {
			jjLayerXOffset[4] = (jjRandom() & 7) - 3.5;
			jjLayerYOffset[4] = (jjRandom() & 7) - 3.5;
		}
	}
	{ //update all special blocks
		const bool atLeastOneColorChanged =
			TriggerColorsOld[0] != jjTriggers[0] ||
			TriggerColorsOld[1] != jjTriggers[1] ||
			TriggerColorsOld[2] != jjTriggers[2];
		const bool updateTubeDirections = (jjGameTicks % 70) == 69; //once a second
		if (atLeastOneColorChanged)
			for (int i = SwitchBlocks.length - 1; i >= 0; --i) {
				SwitchBlocks[i].switchTile();
			}
		if (updateTubeDirections)
			for (int i = SwitchBlocks.length - 1; i >= 0; --i) {
				SwitchBlock@ block = SwitchBlocks[i];
				if (updateTubeDirections && block.TubeBlock)
					block.changeDirection();
			}
		for (int i = LaserBlockArraysPerLevel.length - 1; i >= 0; --i) {
			array<LaserBlock>@ lasers = LaserBlockArraysPerLevel[i];
			for (int j = lasers.length - 1; j >= 0; --j) {
				if (i == WarpID) {
					if (updateTubeDirections || atLeastOneColorChanged)
						lasers[j].updatePath();
					lasers[j].draw();
				} else
					lasers[j].dead();
			}
		}
		if (atLeastOneColorChanged)
			for (int i = 0; i < 3; ++i)
				TriggerColorsOld[i] = jjTriggers[i];
	}
}

bool suckerOn = false;
float suckerSpeedX = 0;
float suckerSpeedY = 0;
int lastSuckTile = 0;
void suckPlayer(jjPLAYER@ play, int xSpeed, int ySpeed) {
	if (lastSuckTile != play.currTile) {
		lastSuckTile = play.currTile;
		suckerSpeedX = xSpeed * SuckerSpeedSpeed;
		suckerSpeedY = ySpeed * SuckerSpeedSpeed;
		play.xPos = play.xOrg = int(play.xPos / 32) * 32 + 15;
		play.yPos = play.yOrg = int(play.yPos / 32) * 32 + 15;
		suckerOn = true;
	}
}
void unsuckPlayer(jjPLAYER@ play) {
	suckerOn = false;
	lastSuckTile = 0;
	play.xSpeed = play.xAcc = play.ySpeed = play.yAcc = 0;
}
void onBelt(jjPLAYER@ play, int dir) {
	if (play.keyJump) {
		play.xSpeed = 0;
	} else {
		play.xSpeed = dir * 2;
		play.direction = dir;
	}
	play.keyLeft = play.keyRight = false;
}
int getCurrTileValue(int x, int y) {
	return (x >> 5) + (y >> 5 << 16);
}
bool maskedPixel(int x, int y) {
	x += int(suckerSpeedX * 4) - 2;
	y += int(suckerSpeedY * 4) + 4;
	return getCurrTileValue(x, y) != lastSuckTile && jjEventGet(x / 32, y / 32) != AREA::ONEWAY && jjMaskedPixel(x, y);
}
int warpInsideTile(int xTile, int yTile) {
	const uint16 tileID = jjTileGet(4, xTile, yTile);
	if (tileID == 40 || tileID == 50 || tileID == 60) //special block came into solidity
		return 1;
	else if (tileID == 10 && jjEventGet(xTile, yTile) != AREA::ONEWAY) //wallclimbing
		return 2;
	else if (tileID == (4 | TILE::ANIMATED)) //tube block
		return -1;
	return 0;
}
int warpInsidePixel(float x, float y) {
	return warpInsideTile(int(x) / 32, int(y) / 32);
}
const float SuckerSpeedSpeed = 4.f;
uint16 floorTileID = 0;
uint SpendFramesCheckingIfPlayerIsStuck = 0;
float knockedIntoScreen = 0.f;
bool crispyFried = false;
bool warping = false;
void onPlayer(jjPLAYER@ play) {
	if (!jjLowDetail) {
		uint rand = jjRandom();
		if ((rand & 3) == 1) {
			rand >>= 2;
			const uint xOffset = rand % jjResolutionWidth;
			rand >> 10;
			const uint yOffset = rand % jjResolutionHeight;
			rand >>= 9;
			const uint xTile = uint(play.cameraX + xOffset) / 32;
			const uint yTile = uint(play.cameraY + yOffset) / 32;
			if (jjTileGet(4, xTile, yTile) == 10) { //normal wall
				jjAddObject(OBJECT::SPARK, xTile * 32 + 16, yTile * 32 + 16);
			}
		}
	}
	
	if (!play.isInGame || play.xPos < 0 || play.yPos < 0) //spectating
		return;
	play.currWeapon = WEAPON::ICE;
	floorTileID = 0;
	if (attackingWildly) {
		play.keyDown = play.ySpeed >= 0;
		play.direction = -1;
		play.keyUp = play.keyLeft = play.keyRight = play.keyFire = play.keySelect = play.keyJump = play.keyRun = false;
	} else if (Endgame) {
		play.keyFire = false;
		const int timeTillFire = jjGameTicks % 210; //every third second
		if (timeTillFire == 209)
			play.fireBullet(WEAPON::ICE, false, false);
		else if (timeTillFire == 209 - 35 || timeTillFire == 209 - 70 || timeTillFire == 209 - 105)
			//jjSample(play.xPos, play.yPos, SOUND::COMMON_COIN);
			jjSample(play.xPos, play.yPos, SOUND::COMMON_BELL_FIRE2);
		if (timeTillFire >= 209 - 105) {
			const int frameID = ((timeTillFire + 1) % 35) / 2;
			if (frameID < 6)
				jjDrawSprite(play.xPos, play.yPos, ANIM::FLARE, 0, frameID, 0, SPRITE::TRANSLUCENTCOLOR, 15, 1);
		}
	}
	if (warping && play.warpID == 0) {
		if (crispyFried || knockedIntoScreen != 0)
			play.blink = 70;
		knockedIntoScreen = 0;
		crispyFried = false;
		warping = false;
		play.cameraUnfreeze();
		
		jjTriggers[0] = jjTriggers[1] = jjTriggers[2] = false;
		
		array<LaserBlock>@ lasers = LaserBlockArraysPerLevel[WarpID];
			for (int i = lasers.length - 1; i >= 0; --i)
				lasers[i].countdownToDestruction = 0; //restore to life
	}
	if (SpendFramesCheckingIfPlayerIsStuck > 0) {
		--SpendFramesCheckingIfPlayerIsStuck;
		if (!warping && warpInsidePixel(play.xPos, play.yPos) == 0) {
			const int truncatedX = int(play.xPos) & 31;
			if (truncatedX < 12) {
				const int shouldWarp = warpInsidePixel(play.xPos - 32, play.yPos);
				if (shouldWarp >= 1)
					play.xPos += (12 - truncatedX); //extract
				else if (shouldWarp == -1)
					play.xPos -= 32; //squeeze into tube
			} else if (truncatedX > 20) { 
				const int shouldWarp = warpInsidePixel(play.xPos + 32, play.yPos);
				if (shouldWarp >= 1)
					play.xPos -= (truncatedX - 20);
				else if (shouldWarp == -1)
					play.xPos += 32;
			}
			const int truncatedY = int(play.yPos) & 31;
			if (truncatedY > 12) { 
				int shouldWarp = warpInsidePixel(play.xPos, play.yPos + 32);
				if (shouldWarp == 0) {
					if (truncatedX < 12)
						shouldWarp = warpInsidePixel(play.xPos - 32, play.yPos + 32);
					else if (truncatedX > 20)
						shouldWarp = warpInsidePixel(play.xPos + 32, play.yPos + 32);
				}
				if (shouldWarp >= 1)
					play.yPos -= (truncatedY - 12);
				else if (shouldWarp == -1)
					play.yPos += 32;
			} /*else if (truncatedY < 4 || truncatedY ) { 
				if (jjMaskedPixel(int(play.xPos), int(play.yPos - 32)))
					play.yPos += (4 - truncatedY);
			} */
		}
	}
	if (suckerOn) {
		for (int i = 0; i < 2; ++i)
			if (suckerSpeedX != 0) {
				if (maskedPixel(int(play.xOrg), int(play.yOrg))) {
					unsuckPlayer(play);
					break;
				} else {
					play.xPos = play.xOrg += suckerSpeedX;
					play.yPos = play.yOrg;
					play.ySpeed = 0; //testing to make spectating look better
				}
			} else if (suckerSpeedY != 0) {
				if (maskedPixel(int(play.xOrg), int(play.yOrg))) {
					unsuckPlayer(play);
					break;
				} else {
					play.xPos = play.xOrg;
					play.yPos = play.yOrg += suckerSpeedY;
				}
			} else { //waiting for feedback
				if (play.keyRight)
					suckerSpeedX = SuckerSpeedSpeed;
				else if (play.keyLeft)
					suckerSpeedX = -SuckerSpeedSpeed;
				else if (play.keyDown)
					suckerSpeedY = SuckerSpeedSpeed;
				else if (play.keyUp)
					suckerSpeedY = -SuckerSpeedSpeed;
				else {
					play.xPos = play.xOrg;
					play.yPos = play.yOrg;
					play.xSpeed = play.xAcc = play.ySpeed = play.yAcc = 0;
					suckerOn = false; lastSuckTile = 0;
				}
				const int distance = jjGameTicks & 15;
				const int xTile = int(play.xPos / 32) * 32;
				const int yTile = int(play.yPos / 32) * 32;
				jjDrawTile(xTile + 32 + distance, yTile, BigArrowTileIDs[0] + 2);
				jjDrawTile(xTile - 32 - distance, yTile, BigArrowTileIDs[2] + 2);
				jjDrawTile(xTile, yTile + 32 + distance, BigArrowTileIDs[1] + 2);
				jjDrawTile(xTile, yTile - 32 - distance, BigArrowTileIDs[3] + 2);
				break;
			}
	} else {
		if (play.warpID == 0 && (play.ySpeed == 0 || warpInsidePixel(play.xPos, play.yPos - 32) != 0)) { //probably standing
			const int floorTileY = int(play.yPos / 32) + 1;
			int floorTileX = int(play.xPos / 32);
			bool onTileCorner = false;
			if (!jjMaskedPixel(floorTileX * 32, floorTileY * 32)) {
				const int playTruncatedX = int(play.xPos) & 31;
				if (playTruncatedX < 12) --floorTileX;
				else if (playTruncatedX > 18) ++floorTileX;
				onTileCorner = true;
			}
			//jjDrawTile(floorTileX * 32, floorTileY * 32, 51);
			floorTileID = jjTileGet(4, floorTileX, floorTileY);
			
			if (floorTileID == 26 | TILE::ANIMATED) {
				onBelt(play, -1);
			} else if (floorTileID == 26 | TILE::ANIMATED | TILE::HFLIPPED) {
				onBelt(play, 1);
			} else if (floorTileID == 50 || floorTileID == 60) { //red, green specials
				if (onTileCorner)
					floorTileID = 0; //must be standing full on for one of these tiles
				else
					jjDrawTile(floorTileX * 32, floorTileY * 32, 231);
			}
		}
	}
	if (play.warpID == 0) {
		play.xOrg = play.xPos;
		play.yOrg = play.yPos;
		const int xTile = int(play.xPos / 32);
		const int yTile = int(play.yPos / 32);
		
		array<LaserBlock>@ lasers = LaserBlockArraysPerLevel[WarpID];
		for (int i = lasers.length - 1; i >= 0; --i) {
			if (lasers[i].tileIsInPath(xTile, yTile)) {
				crispyFried = true;
				jjObjects[0].xPos = play.xPos;
				jjObjects[0].yPos = play.yPos;
				jjObjects[0].curFrame = play.curFrame;
				jjObjects[0].direction = play.direction;
				jjObjects[0].particlePixelExplosion(1);
				play.cameraFreeze(play.cameraX, play.cameraY, false, true);
				jjSample(play.xPos, play.yPos, SOUND::COMMON_BURN);
				jjSample(play.xPos, play.yPos, SOUND::COMMON_ELECTRIC1);
				onFunction1(play);
				return;
			}
		}
		const int shouldWarp = warpInsideTile(xTile, yTile);
		if (shouldWarp >= 1) {
			if (shouldWarp == 1 && play.fly == 0) {
				jjSample(play.xPos, play.yPos, SOUND::COMMON_SMASH);
				jjSample(play.xPos, play.yPos, SOUND::SPAZSOUNDS_AUTSCH1);
				knockedIntoScreen = 1.0;
				play.cameraFreeze(play.cameraX, play.cameraY, false, true);
			}
			onFunction1(play);
		} else if (shouldWarp == -1) {
			const uint16 backTileID = jjTileGet(5, xTile, yTile);
			if (backTileID == BigArrowTileIDs[0]) {
				suckPlayer(play, 1, 0);
			} else if (backTileID == BigArrowTileIDs[1]) {
				suckPlayer(play, 0, 1);
			} else if (backTileID == BigArrowTileIDs[2]) {
				suckPlayer(play, -1, 0);
			} else if (backTileID == BigArrowTileIDs[3]) {
				suckPlayer(play, 0, -1);
			} else {
				suckPlayer(play, 0, 0);
			}
		}
	}
}
void Switch(jjOBJ@ obj) {
	if (jjPlayers[obj.creator].isLocal) {
		jjTriggers[0] = !jjTriggers[0];
		if (floorTileID != 50) //red
			jjTriggers[1] = !jjTriggers[1];
		if (floorTileID != 60) //green
			jjTriggers[2] = !jjTriggers[2];
		QuakeLayer4 = 10;
		jjSamplePriority(SOUND::COMMON_CANSPS);
	}
	obj.delete();
	SpendFramesCheckingIfPlayerIsStuck = 2;
}

bool Successful = false;
int WarpID = -1;
void onFunction0(jjPLAYER@ play, int8 newWarpID) {
	if (WarpID + 1 == newWarpID) {
		Successful = true;
		const bool wasEndgame = Endgame;
		Endgame = SwitchesPerLevel[newWarpID] < 0;
		jjTriggers[ENDGAMETRIGGERID] = Endgame;
		WarpID = newWarpID;
		play.score = newWarpID + 1; //in case if playing in SP
		if (!Endgame) {
			if (WarpID > 0)
				jjSamplePriority(SOUND::COMMON_HARP1);
			if (wasEndgame) {
				jjLayerHasTiles[1] = true;
				jjPalette.reset();
				jjPalette.apply();
			}
		} else {
			if (!wasEndgame) {
				jjLayerHasTiles[1] = false;
				jjSamplePriority(SOUND::COMMON_NOCOIN);
				for (int i = 151; i <= 159; ++i)
					jjPalette.color[i].swizzle(COLOR::BLUE, COLOR::BLUE, COLOR::RED);
				for (int i = 176; i <= 208; ++i)
					jjPalette.color[i].swizzle(COLOR::BLUE, COLOR::GREEN, COLOR::RED);
				jjPalette.apply();
			} else
				jjSamplePriority(SOUND::COMMON_HARP1);
		}
		const int maxSwitches = int(abs(SwitchesPerLevel[newWarpID]));
		jjWeapons[WEAPON::ICE].maximum = maxSwitches;
		jjWeapons[WEAPON::ICE].infinite = maxSwitches >= 99;
		play.ammo[WEAPON::ICE] = jjWeapons[WEAPON::ICE].maximum; //double sure?
		if (!Endgame)
			play.showText("@@@@@@@@@@@@@@#" + (CharactersPerLevel[newWarpID] == CHAR::JAZZ ? "|||" : (CharactersPerLevel[newWarpID] == CHAR::FROG ? "~" : "||||")) +"~  Incident " + formatInt(newWarpID + 1, "0", 2) + "!@@" + ((maxSwitches >= 99) ? "Unlimited" : formatInt(maxSwitches, "d")) + " switches", STRING::MEDIUM);
		onFunction1(play);
	}
}
void onFunction1(jjPLAYER@ play) {
	attackingWildly = false;
	//if (!warping) {
	{
		unsuckPlayer(play);
		play.warpToID(WarpID);
		warping = true;
		play.ammo[WEAPON::ICE] = jjWeapons[WEAPON::ICE].maximum;
		const CHAR::Char newChar = CharactersPerLevel[WarpID];
		if (play.charCurr != newChar && newChar != CHAR::FROG)
			play.morphTo(newChar, true);
	}
		
}

bool Endgame = false;
const uint8 ENDGAMETRIGGERID = 21;
const array<uint16> BigArrowTileIDs = {0 | TILE::ANIMATED, 1 | TILE::ANIMATED, 0 | TILE::ANIMATED | TILE::HFLIPPED, 1 | TILE::ANIMATED | TILE::VFLIPPED};
class SwitchBlock {
	bool TubeBlock = false;
	bool EndgameOnly = false;
	uint triggerID = 0;
	bool triggerWasOn = false;
	uint16 offTileID, onTileID;
	uint currentDirection;
	uint legalDirections;
	uint xTile, yTile;
	SwitchBlock(){}
	SwitchBlock(uint x, uint y) {
		xTile = x; yTile = y;
		const uint16 animatedTileID = jjTileGet(4, xTile, yTile);
		switch (animatedTileID ^ TILE::ANIMATED) {
			case 18:
				offTileID = 40;
				onTileID = 41;
				break;
			case 19:
				offTileID = 41;
				onTileID = 40;
				break;
			case 20:
				offTileID = 50;
				onTileID = 51;
				triggerID += 1;
				break;
			case 21:
				offTileID = 51;
				onTileID = 50;
				triggerID += 1;
				break;
			case 22:
				offTileID = 60;
				onTileID = 61;
				triggerID += 2;
				break;
			case 23:
				offTileID = 61;
				onTileID = 60;
				triggerID += 2;
				break;
			case 24:
				offTileID = (4 | TILE::ANIMATED);
				onTileID = 81;
				TubeBlock = true;
				break;
			case 25:
				offTileID = 81;
				onTileID = (4 | TILE::ANIMATED);
				TubeBlock = true;
				break;
			case 26:
				offTileID = 26 | TILE::ANIMATED;
				onTileID = 26 | TILE::ANIMATED | TILE::HFLIPPED;
				break;
			case 26 | TILE::HFLIPPED:
				offTileID = 26 | TILE::ANIMATED | TILE::HFLIPPED;
				onTileID = 26 | TILE::ANIMATED;
				break;
			case 28:
			case 29:
			case 30: { //mirrors
				const int color = ((animatedTileID & TILE::RAWRANGE) - 28);
				triggerID += color;
				offTileID = 271 + color * 10;
				onTileID = offTileID | TILE::HFLIPPED;
				break; }
			case 28 | TILE::HFLIPPED:
			case 29 | TILE::HFLIPPED:
			case 30 | TILE::HFLIPPED: { //flipped mirrors
				const int color = ((animatedTileID & TILE::RAWRANGE) - 28);
				triggerID += color;
				onTileID = 271 + color * 10;
				offTileID = onTileID | TILE::HFLIPPED;
				break; }
			default:
				jjDebug("invalid trigger tile!");
		}
		if (TubeBlock) {
			legalDirections = jjParameterGet(xTile, yTile, 0, 4);
			currentDirection = jjParameterGet(xTile, yTile, 4, 2);
		}
		EndgameOnly = jjParameterGet(xTile, yTile, 7, 1) == 1;
		if (jjParameterGet(xTile, yTile, 6, 1) == 1) { //move downwards one tile, to allow placing blocks on top of warps
			jjTileSet(4, xTile, yTile, 0);
			yTile += 1;
		}
	}
	void switchTile() {
		const bool triggerOn = jjTriggers[triggerID];
		if (triggerOn != TriggerColorsOld[triggerID])
			jjTileSet(4, xTile,yTile, (!EndgameOnly || Endgame) ? (triggerOn ? onTileID : offTileID) : 0);
	}
	void changeDirection() {
		if ((!EndgameOnly || Endgame) && legalDirections != 0) { //level-set direction, not player-set
			int newDirection = currentDirection;
			for (int i = 0; i < 3; ++i) {
				newDirection = (newDirection + 1) & 3;
				if ((legalDirections & (1 << newDirection)) != 0) {
					currentDirection = newDirection;
					break;
				}
			}
			jjTileSet(5, xTile,yTile, BigArrowTileIDs[currentDirection]);
		}
	}
}
array<SwitchBlock> SwitchBlocks;
class TileCoordinates {
	uint16 xTile, yTile;
	float xPos, yPos;
	uint16 tileID;
	TileCoordinates(){}
	TileCoordinates(uint x, uint y, uint oldDir, uint newDir) {
		xPos = (xTile = x) * 32.f; yPos = (yTile = y) * 32.f;
		if (oldDir == newDir) {
			if ((oldDir & 1) == 0)
				tileID = 32 | TILE::ANIMATED; //horizontal
			else
				tileID = 31 | TILE::ANIMATED; //vertical
		} else switch(oldDir | (newDir << 2)) { //corner
			case 12:
			case 9:
				tileID = 33 | TILE::ANIMATED;
				break;
			case 1:
			case 14:
				tileID = 33 | TILE::ANIMATED | TILE::HFLIPPED;
				break;
			case 4:
			case 11:
				tileID = 33 | TILE::ANIMATED | TILE::VFLIPPED;
				break;
			case 3:
			case 6:
				tileID = 33 | TILE::ANIMATED | TILE::HFLIPPED | TILE::VFLIPPED;
				break;
		}
	}
}
const uint LayerWidth = jjLayerWidth[4];
const uint LayerHeight = jjLayerHeight[4];
class LaserBlock {
	uint8 currentDirection;
	uint8 countdownToDestruction = 0;
	int xTile, yTile;
	array<TileCoordinates> path(0);
	LaserBlock(){}
	LaserBlock(uint x, uint y) {
		xTile = x; yTile = y;
		const uint16 tileID = jjTileGet(4, xTile, yTile);
		switch (tileID) {
			case 301:
				currentDirection = 3; //up
				break;
			case 301 | TILE::VFLIPPED:
				currentDirection = 1; //down
				break;
			case 311:
				currentDirection = 0; //right
				break;
			case 311 | TILE::HFLIPPED:
				currentDirection = 2; //left
				break;
			default:
				jjDebug("invalid laser tile!");
		}
	}
	void updatePath() {
		if (countdownToDestruction >= 2)
			return;
		path.resize(0);
		uint pathDirection = currentDirection;
		uint newDirection = pathDirection;
		int newTileX = xTile;
		int newTileY = yTile;
		while (true) {
			switch (pathDirection) {
				case 0: //right
					newTileX += 1;
					break;
				case 1: //down
					newTileY += 1;
					break;
				case 2: //left
					newTileX -= 1;
					break;
				case 3: //break
					newTileY -= 1;
			}
			while (newTileX < 0) newTileX += LayerWidth;
			while (newTileY < 0) newTileY += LayerHeight;
			const uint16 tileID = jjTileGet(4, newTileX %= LayerWidth, newTileY %= LayerHeight);
			//if (tileID != 0)
			if (tileID == 40 || tileID == 50 || tileID == 60 || tileID == (26 | TILE::ANIMATED) || tileID == (26 | TILE::ANIMATED | TILE::HFLIPPED) || (tileID == 10 && jjEventGet(newTileX, newTileY) != AREA::ONEWAY)) //wall
				break;
			else if (tileID == 301 || tileID == (301 | TILE::VFLIPPED) || tileID == 311 || tileID == (311 | TILE::HFLIPPED)) { //hit another laser!
				array<LaserBlock>@ lasers = LaserBlockArraysPerLevel[WarpID];
				for (int i = lasers.length - 1; i >= 0; --i) {
					LaserBlock@ laser = lasers[i];
					if (laser.xTile == newTileX && laser.yTile == newTileY) { //found the right one
						if (laser.countdownToDestruction == 0)
							laser.countdownToDestruction = 1;
						break;
					}
				} //if none are found, oh well, that's weird but keep going
				break;
			} else if (tileID == 4 | TILE::ANIMATED) { //sucker tube
				const uint16 arrowTileID = jjTileGet(5, newTileX, newTileY);
				for (int i = 0; i < 4; ++i)
					if (arrowTileID == BigArrowTileIDs[i]) {
						newDirection = i;
						break;
					}
				if (((newDirection + 2) & 3) == pathDirection) //reversed
					break;
			} else if (tileID == 261 || tileID == 271 || tileID == 281 || tileID == 291) { //mirror
				newDirection ^= 1;
			} else if (tileID == (261 | TILE::HFLIPPED) || tileID == (271 | TILE::HFLIPPED) || tileID == (281 | TILE::HFLIPPED) || tileID == (291 | TILE::HFLIPPED)) { //mirror 2
				newDirection = 3 - pathDirection;
			}
			path.insertLast(TileCoordinates(newTileX, newTileY, pathDirection, newDirection));
			pathDirection = newDirection;
		}
	}
	void dead() {
		jjPARTICLE@ fire = jjAddParticle(((jjRandom() & 1) == 1) ? PARTICLE::SMOKE : PARTICLE::SPARK);
		if (fire !is null) {
			fire.xPos = xTile * 32 + (jjRandom() & 31);
			fire.yPos = yTile * 32 + (jjRandom() & 31);
		}
	}
	void draw() {
		if (countdownToDestruction == 1) //just got hit, but stay alive
			countdownToDestruction = 2;
		else if (countdownToDestruction == 2) { //dead
			dead();
		} else {
			const uint8 glowColor = 40 + (jjRenderFrame & 3);
			const uint8 glowIntensity = uint8(abs(jjSin(jjRenderFrame << 4) * 64));
			jjDrawRectangle(xTile * 32, yTile * 32, 32, 32, glowColor, SPRITE::BLEND_NORMAL, glowIntensity);
			for (uint i = 0; i < path.length; ++i) {
				jjDrawRectangle(path[i].xPos, path[i].yPos, 32, 32, glowColor, SPRITE::BLEND_NORMAL, glowIntensity);
				jjDrawTile(path[i].xPos, path[i].yPos, path[i].tileID);
			}
		}
	}
	bool tileIsInPath(uint x, uint y) {
		if (countdownToDestruction >= 2)
			return false;
		for (uint i = 0; i < path.length; ++i)
			if (x == path[i].xTile && y == path[i].yTile)
				return true;
		return false;
	}
}
array<array<LaserBlock>> LaserBlockArraysPerLevel;

bool onDrawLives(jjPLAYER@ play, jjCANVAS@ screen) {
	return true;
}
bool onDrawHealth(jjPLAYER@ play, jjCANVAS@ screen) {
	return true;
}
bool onDrawAmmo(jjPLAYER@ play, jjCANVAS@ screen) {
	//if (SwitchesPerLevel[WarpID] < 99)
	//	screen.drawString(20, jjResolutionHeight - 50, "" + SwitchesPerLevel[WarpID], STRING::LARGE);
	const CHAR::Char intendedChar = CharactersPerLevel[WarpID];
	const int frameID = jjRenderFrame >> 3;
	if (jjIsTSF && (intendedChar == CHAR::LORI || intendedChar == CHAR::FROG))
		screen.drawSprite(50, jjResolutionHeight, ANIM::FACES, 4, frameID, 0, SPRITE::PLAYER, play.playerID);
	if (intendedChar == CHAR::SPAZ || intendedChar == CHAR::FROG)
		screen.drawSprite(30, jjResolutionHeight, ANIM::FACES, (jjIsTSF) ? 5 : 4, frameID, 0, SPRITE::PLAYER, play.playerID);
	if (intendedChar == CHAR::JAZZ || intendedChar == CHAR::FROG)
		screen.drawSprite(10, jjResolutionHeight, ANIM::FACES, 3, frameID, 0, SPRITE::PLAYER, play.playerID);
	return Endgame;
}

void InfiniteMonitor(jjOBJ@ obj) {
	if (obj.state == STATE::KILL) { //destroyed
		obj.state = STATE::START; //restart
		obj.xPos = obj.xOrg;
		obj.yPos = obj.yOrg;
		obj.objType = jjObjectPresets[obj.eventID].objType;
		obj.energy = jjObjectPresets[obj.eventID].energy;
	}
	obj.behave(BEHAVIOR::MONITOR);
}
void Electricity(jjOBJ@ obj) {
	if (obj.state == STATE::DEACTIVATE || jjLowDetail) {
		obj.delete(); //don't bother deactivating
		return;
	} else if (obj.state == STATE::START) {
		obj.state = STATE::FLY;
		switch (jjRandom() & 3) {
			case 0:
				obj.xSpeed = 2;
				obj.ySpeed = 0;
				break;
			case 1:
				obj.xSpeed = 0;
				obj.ySpeed = 2;
				break;
			case 2:
				obj.xSpeed = -2;
				obj.ySpeed = 0;
				break;
			case 3:
				obj.xSpeed = 0;
				obj.ySpeed = -2;
				break;
		}
	}
	if (!jjMaskedPixel(int(obj.xPos+= obj.xSpeed), int(obj.yPos+= obj.ySpeed))) { //outside the wall
		obj.delete();
		uint rand = jjRandom();
		for (int i = 0; i < 7; ++i) {
			const int angle = (rand & 31) << 5;
			rand >>= 5;
			jjPARTICLE@ spark = jjAddParticle(PARTICLE::SPARK);
			if (spark !is null) {
				spark.xPos = obj.xPos;
				spark.yPos = obj.yPos;
				spark.xSpeed = jjSin(angle) * 2.f;
				spark.ySpeed = jjCos(angle) * 2.f;
				if (!Endgame) {
					spark.spark.color -= 8; spark.spark.colorStop -= 8; //blue
				}
			}
		}
	} else if ((int(obj.xPos) & 31) == 16 && (int(obj.yPos) & 31) == 16) {
		const uint rand = jjRandom();
		if ((rand & 1) == 1) { //turn
			if (obj.xSpeed != 0) {
				obj.xSpeed = 0;
				obj.ySpeed = ((rand & 2) == 2) ? 2 : -2;
			} else {
				obj.ySpeed = 0;
				obj.xSpeed = ((rand & 2) == 2) ? 2 : -2;
			}
		}
	}
	//else
		
}

void Tweedle(jjOBJ@ obj) {
	if (obj.state == STATE::DEACTIVATE)
		obj.deactivate();
	else if (obj.state == STATE::DONE) {
		obj.state = STATE::FALL;
		obj.determineCurAnim(ANIM::TWEEDLE, 5);
		obj.ySpeed = -7;
		obj.yAcc = 0.3;
	} else if (obj.state == STATE::FALL) {
		obj.xPos -= 2;
		if ((obj.yPos += (obj.ySpeed += obj.yAcc)) > obj.yOrg + 5*32) {
			obj.state = STATE::EXPLODE;
			obj.counterEnd = 0;
		}
	} else if (obj.state == STATE::EXPLODE) {
		if (obj.counterEnd++ < 240) {
			QuakeLayer4 = 10;
			obj.xPos += jjLayerXOffset[4];
			obj.yPos += jjLayerYOffset[4];
			if ((obj.counterEnd & 15) == 0) {
				obj.justHit = 8;
				obj.particlePixelExplosion(1);
				jjSamplePriority(SOUND::COMMON_ELECTRIC1);
			}
		} else {
			obj.state = STATE::START;
			obj.xPos = obj.xOrg;
			obj.yPos = obj.yOrg;
			obj.energy = jjObjectPresets[obj.eventID].energy;
			obj.curAnim = jjObjectPresets[obj.eventID].curAnim;
			attackingWildly = false;
			Endgame = false;
			jjTriggers[ENDGAMETRIGGERID] = false;
			WarpID = 0;
			jjLocalPlayers[0].score = 1;
			jjLocalPlayers[0].showText("#|||||~@@@CONGRATULATIONS!", STRING::LARGE);
			jjLayerHasTiles[1] = true;
			jjPalette.reset();
			jjPalette.apply();
			jjSamplePriority(SOUND::COMMON_EXPL_TNT);
		}
	} else if (WarpID == 0) {
		return; //don't delete; wait until can be deactivated
	}
	obj.frameID = jjGameTicks >> 2;
	obj.determineCurFrame();
	obj.draw();
}

bool attackingWildly;
void onFunction2(jjPLAYER@ play) {
	attackingWildly = true;
}
void onObjectHit(jjOBJ@ object, jjOBJ@ bullet, jjPLAYER@ player, int force) {
	if (bullet is null && force == 1 && WarpID != 0) {
		player.buttstomp = 121;
		player.ySpeed = -10;
		player.yAcc = 0;
		object.justHit = 6;
		if (--object.energy <= 0) {
			object.state = STATE::DONE;
		}
	}
}

void MovingTarget(jjOBJ@ obj) { //only an object because it has a lighttype
	if (Successful) {
		Successful = false;
		for (int i = 0; i < 11; ++i) {
			jjOBJ@ reward = jjObjects[jjAddObject(OBJECT::FLICKERGEM, obj.xPos, obj.yPos, obj.objectID)];
			reward.xSpeed = ((jjRandom() & 15) - 7.5f) / 2.f;
			reward.ySpeed = -1.1 - ((jjRandom() & 7) / 2.f);
			reward.var[0] = (jjRandom() & 3) + 1;
		}
	}
	uint targetID = WarpID + 1;
	if (targetID >= TargetsPerLevel.length)
		targetID = TargetsPerLevel.length - 1;
	obj.xPos += (TargetsPerLevel[targetID].x - obj.xPos) / 128;
	obj.yPos += (TargetsPerLevel[targetID].y - obj.yPos) / 128;
	const float scale = 4.5f + jjSin(jjGameTicks << 4) * 2;
	jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::PLUS_RETICLES, 1, 0, jjGameTicks << 2, scale, scale, SPRITE::SINGLECOLOR, 47 - (jjRenderFrame & 31), 1);
}








































































//go away you silly cheater
bool onLocalChat(string &in stringReceived, CHAT::Type chatType) {
	array<string> regexResults;
	if (jjRegexMatch(stringReceived, "SKIPTO (\\d+)", regexResults, false)) {
		int newWarpID = parseInt(regexResults[1]) - 1;
		if (newWarpID >= 0 && newWarpID < int(SwitchesPerLevel.length)) {
			WarpID = newWarpID - 1; //force function to accept value
			onFunction0(jjLocalPlayers[0], newWarpID);
		} else
			jjAlert("|Invalid Incident!");
		return true;
	}
	return false;
}