Downloads containing SEdec.j2as

Downloads
Name Author Game Mode Rating
JJ2+ Only: Anniversary Bash 25 Battle Jazz2Online Battle N/A Download file
TSF with JJ2+ Only: Anniversary Bash 20 Levels Jazz2Online Multiple N/A Download file
TSF with JJ2+ Only: Anniversary Bash 18 Levels Jazz2Online Multiple N/A Download file
JJ2+ Only: DecimationFeatured Download Seren Battle 8.5 Download file

File preview

namespace palshift {
	const array<uint8> ammoCount = {208, 232, 240, 216};
	const uint8 asmdParticle = 221;
	const uint8 barrel = 64;
	const uint8 barrelDurability = 232;
	const uint8 enemyHealth = 216;
	const uint8 flak = 32;
	const array<uint8> machineBadge = {249, 1, 241, 1, 249, 249, 9, 9, 17, 33};
	const uint8 machineShard = 248;
	const array<uint8> machineSpring = {0, 8, 248, 8, 0, 0, 16, 16, 0, 248};
	const array<uint8> rocketShard = {200, 248};
	const array<uint8> turret = {0, 248, 232, 248, 240, 240, 0, 0, 8, 24};
	const uint8 turretDurability = 16;
}
namespace rabbit {
	const int blinkingTime = 40;
}
namespace tileset {
	const uint blackLine = 131;
	const uint blackSquare = 114;
	const uint empty = 0;
	const uint healthBar = 230;
	const uint ventShaft = 19;
	const uint wall = 10;
	const uint weaponLarge = 140;
	const uint weaponSmall = 200;
	const uint weaponSmallInactive = 205;
}
namespace trigger {
	enum option {disableSD, friendlyFire, damageAmplifier}
}
namespace weapon {
	const uint8 count = client::isCoopOrSP() ? 4 : 6;
	const int dispersionPistolRegenerationTime = 80;
	const int displayTime = 40;
	const array<int> fireInterval = {0, 25, 5, 32, 70, 40, 1, 0, 0, 0};
	const uint8 first = 1;
	enum flags {maxDamage = 7, allowSD = 8}
	const array<string> name = {"0", "Dispersion Pistol", "Machine Gun", "ASMD", "Rocket Launcher", "Flak Cannon", "Flamethrower", "7", "8", "9"};
	const array<int> pickupMultiplier = {0, 1, 20, 4, 1, 1, 50, 0, 0, 0};
}
interface Itile {
	void step();
}
class TlocalPlayer {
	int dispersionPistolRegenerationTimer, fireIntervalTimer, weaponChangeTimer;
	bool keyFirePrevious;
	uint8 weaponLastShot, weaponPrevious;
	TlocalPlayer() {
		dispersionPistolRegenerationTimer = fireIntervalTimer = weaponChangeTimer = 0;
		weaponPrevious = weaponLastShot = WEAPON::BLASTER;
		keyFirePrevious = false;
	}
}
class Tsprite {
	int x, y, direction;
	uint frame;
	SPRITE::Mode mode;
	uint8 param;
	void draw(jjCANVAS@ canvas) const {
		canvas.drawSpriteFromCurFrame(x, y, frame, direction, mode, param);
	}
}
class TventShaft : Itile {
	private int x, y;
	private float xHigh, xLow, yHigh, yLow;
	TventShaft(int X, int Y) {
		x = X * 32 + 12;
		y = Y * 32 + 12;
		xHigh = x + 328;
		yHigh = y + 248;
		xLow = x - 320;
		yLow = y - 240;
	}
	void step() {
		if (client::isIdleServer())
			return;
		if (jjGameTicks & 1 == 0) {
			const uint random = jjRandom();
			if (random & 1 == 0) {
				for (int i = 0; i < jjLocalPlayerCount; i++) {
					const jjPLAYER@ player = jjLocalPlayers[i];
					if (player.cameraX < xHigh && player.cameraY <= yHigh && player.cameraX + jjSubscreenWidth > xLow && player.cameraY + jjSubscreenHeight > yLow) {
						jjPARTICLE@ part = jjAddParticle(PARTICLE::SMOKE);
						if (part !is null) {
							part.xPos = x + (random >> 1 & 7);
							part.yPos = y + (random >> 4 & 7);
							part.ySpeed = (random >> 7 & 3) / 4.f - 1.5f;
						}
						return;
					}
				}
			}
		}
	}
}
bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	if (client::isIdleServer())
		return true;
	if (jjSubscreenWidth >= 640 && jjSubscreenHeight >= 384) {
		draw::weaponIconBarVertical(canvas, jjSubscreenWidth - 88, jjSubscreenHeight / 2, player);
	} else if (jjSubscreenWidth >= 640) {
		draw::weaponIconBarHorizontal(canvas, jjSubscreenWidth / 2, jjSubscreenHeight - 40, player);
	} else if (jjSubscreenWidth >= 512 || jjSubscreenHeight >= 400) {
		uint8 weapon = player.currWeapon;
		draw::largeWeaponIcon(canvas, jjSubscreenWidth - 176, jjSubscreenHeight - 80, weapon - 1);
		draw::ammoCounter(canvas, jjSubscreenWidth - 176, jjSubscreenHeight - 24, player.ammo[weapon], jjWeapons[weapon].maximum, true);
		if (client::localPlayer[player.localPlayerID].weaponChangeTimer > 0)
			draw::weaponName(canvas, jjSubscreenWidth - 16, jjSubscreenHeight - 72, weapon);
	} else {
		uint8 weapon = player.currWeapon;
		draw::smallWeaponIcon(canvas, jjSubscreenWidth - 88, jjSubscreenHeight - 40, weapon - 1, true);
		draw::ammoCounter(canvas, jjSubscreenWidth - 88, jjSubscreenHeight - 8, player.ammo[weapon], jjWeapons[weapon].maximum, true);
	}
	return true;
}
bool onDrawHealth(jjPLAYER@ player, jjCANVAS@ canvas) {
	if (client::isIdleServer())
		return true;
	if (jjSubscreenWidth >= 512 || jjSubscreenHeight >= 400) {
		draw::healthBar(canvas, 16, text::getScoreHUDHeight(jjGameMode), player.health, jjMaxHealth, player.blink > 0 && jjGameTicks >> 2 & 1 == 0);
		return true;
	}
	return false;
}
void onDrawLayer3(jjPLAYER@, jjCANVAS@ canvas) {
	if (client::isIdleServer())
		return;
	sprite::drawQueue(canvas, 2);
	jjTileType[tileset::blackLine] = jjTileType[tileset::blackSquare] = 1;
}
void onDrawLayer4(jjPLAYER@, jjCANVAS@ canvas) {
	if (client::isIdleServer())
		return;
	if (!client::isCoopOrSP()) {
		for (int i = 0; i < jjObjectCount; i++) {
			jjOBJ@ obj = jjObjects[i];
			if (!obj.isActive || obj.eventID != OBJECT::GENERATOR)
				continue;
			if (obj.direction == 0)
				obj.direction = obj.var[3] != OBJECT::TNT && (obj.xPos / 32 % 2 > 1 ^^ obj.yPos / 32 % 2 > 1) ? -1 : 1;
			bool draw = obj.var[0] <= 0;
			if (!draw) {
				jjOBJ@ item = jjObjects[obj.var[0]];
				draw = !item.isActive || item.creatorType != CREATOR::LEVEL || item.creatorID != uint(obj.objectID);
			}
			if (draw)
				canvas.drawSpriteFromCurFrame(int(obj.xPos), int(obj.yPos), jjObjectPresets[obj.var[3]].curFrame, obj.direction, SPRITE::SINGLECOLOR, 79);
		}
	}
	sprite::drawQueue(canvas, 3);
}
void onDrawLayer7(jjPLAYER@, jjCANVAS@) {
	jjTileType[tileset::blackLine] = jjTileType[tileset::blackSquare] = 0;
}
bool onDrawLives(jjPLAYER@ player, jjCANVAS@ canvas) {
	if (client::isIdleServer())
		return true;
	if ((jjGameMode == GAME::COOP || jjGameMode == GAME::SP) && (jjSubscreenWidth >= 512 || jjSubscreenHeight >= 400)) {
		string text = formatInt(player.lives, "");
		canvas.drawString(43 - text::getSmallFontIntWidth(text) / 2, text::getScoreHUDHeight(jjGameMode) + 33, text);
		return true;
	}
	return false;
}
bool onDrawScore(jjPLAYER@, jjCANVAS@) {
	return true;
}
void onLevelLoad() {
	initialize::objectPresets();
	initialize::weaponProfiles();
	initialize::specialTiles();
	initialize::palette();
	jjTexturedBGTexture = TEXTURE::WTF;
	if (client::isCoopOrSP()) {
		for (int i = 0; i < jjLayerHeight[4]; i++) {
			for (int j = 0; j < jjLayerWidth[4]; j++) {
				if (jjParameterGet(j, i, -4, 2) == 3)
					jjEventSet(j, i, 0);
			}
		}
		if (jjGameConnection == GAME::LOCAL) {
			jjChat("/flipmousewheel on");
			jjChat("/allowmouseaim on");
			if (jjLocalPlayerCount == 1)
				jjChat("/mouseaim on");
			else
				jjChat("/mouseaim off");
			int health = 6 - jjDifficulty;
			if (jjStartHealth != health || jjMaxHealth != health)
				jjChat("/smhealth " + formatInt(health, ""));
		}
	}
}
void onLevelReload() {
	initialize::palette();
}
void onMain() {
	for (int i = 0; i < 32; i++) {
		jjPLAYER@ player = jjPlayers[i];
		if (player.blink < -rabbit::blinkingTime)
			player.blink = -rabbit::blinkingTime;
	}
	for (uint i = 0; i < level::specialTile.length(); i++) {
		level::specialTile[i].step();
	}
	if ((jjIsServer || jjIsAdmin) && !client::admin) {
		jjAlert("/trigger " + formatInt(trigger::disableSD, "") + " " + (jjTriggers[trigger::disableSD] ? "off - enable" : "on - disable") + " Self-Destruction");
		jjAlert("/trigger " + formatInt(trigger::friendlyFire, "") + " " + (jjTriggers[trigger::friendlyFire] ? "off - disable" : "on - enable") + " Friendly Fire");
		jjAlert("/trigger " + formatInt(trigger::damageAmplifier, "") + " " + (jjTriggers[trigger::damageAmplifier] ? "off - disable" : "on - enable") + " Damage Amplifier");
		client::admin = true;
	}
	if (client::isCoopOrSP()) {
		for (int i = 0; i < 1024; i++) {
			jjPARTICLE@ part = jjParticles[i];
			if (part.type == PARTICLE::STRING) {
				if (part.string.text == "1") {
					part.xSpeed = 0;
					part.ySpeed = -5;
					part.string.text = "|+ HP";
				}
				if ((part.ySpeed = part.ySpeed * 16 / 17 + 0.125f) > 0) {
					part.string.text = "";
				}
			}
		}
	}
	if (client::isCoopOrSP() != client::startedAsCoopOrSP) {
		for (int i = 0; i < jjLocalPlayerCount; i++) {
			jjPLAYER@ player = jjLocalPlayers[i];
			for (int j = 1; j < 10; j++) {
				player.ammo[j] = 0;
				player.powerup[j] = false;
			}
		}
		client::startedAsCoopOrSP = !client::startedAsCoopOrSP;
		if (jjIsServer || jjGameConnection == GAME::LOCAL)
			jjChat("/r");
	}
}
void onObjectHit(jjOBJ@ obj, jjOBJ@ other, jjPLAYER@ player, int) {
	if (obj is null)
		return;
	if (other !is null) {
		if (obj.eventID == OBJECT::TNT)
			collision::barrelExplode(obj, other);
		return;
	}
	if (player !is null) {
		switch (obj.eventID) {
			case OBJECT::BOUNCERAMMO3:
				collision::ammoPickup(player, obj, WEAPON::BOUNCER);
				break;
			case OBJECT::ICEAMMO3:
				collision::ammoPickup(player, obj, WEAPON::ICE);
				break;
			case OBJECT::SEEKERAMMO3:
				collision::ammoPickup(player, obj, WEAPON::SEEKER);
				break;
			case OBJECT::RFAMMO3:
				collision::ammoPickup(player, obj, WEAPON::RF);
				break;
			case OBJECT::TOASTERAMMO3:
				collision::ammoPickup(player, obj, WEAPON::TOASTER);
				break;
			case OBJECT::BLASTERBULLET:
			case OBJECT::BOUNCERBULLET:
			case OBJECT::ICEBULLET:
			case OBJECT::SEEKERBULLET:
			case OBJECT::RFBULLET:
			case OBJECT::TOASTERBULLET:
				collision::projectile(player, obj);
				break;
		}
	}
}
void onPlayer(jjPLAYER@ player) {
	sprite::clearQueues();
	player.frozen = player.score = 0;
	player.fastfire = weapon::fireInterval[player.currWeapon];
	TlocalPlayer@ thisPlayer = client::localPlayer[player.localPlayerID];
	if (--thisPlayer.dispersionPistolRegenerationTimer < 0) {
		if (player.ammo[WEAPON::BLASTER] < jjWeapons[WEAPON::BLASTER].maximum) {
			player.ammo[WEAPON::BLASTER] = player.ammo[WEAPON::BLASTER] + weapon::pickupMultiplier[WEAPON::BLASTER];
			thisPlayer.dispersionPistolRegenerationTimer = weapon::dispersionPistolRegenerationTime;
		} else {
			thisPlayer.dispersionPistolRegenerationTimer = 0;
		}
	}
	thisPlayer.weaponChangeTimer--;
	if (player.currWeapon != thisPlayer.weaponPrevious) {
		thisPlayer.weaponChangeTimer = weapon::displayTime;
		thisPlayer.weaponPrevious = player.currWeapon;
	}
	thisPlayer.fireIntervalTimer++;
	if (client::isCoopOrSP() && jjLocalPlayerCount == 1) {
		if (jjMouseX >= 0 && jjMouseX < jjResolutionWidth && jjMouseY >= 0 && jjMouseY < jjResolutionHeight)
			player.cameraFreeze(player.xPos + jjMouseX - jjSubscreenWidth, player.yPos + jjMouseY - jjSubscreenHeight, false, true);
		else
			player.cameraUnfreeze();
	}
}
void onPlayerInput(jjPLAYER@ player) {
	TlocalPlayer@ thisPlayer = client::localPlayer[player.localPlayerID];
	if (!thisPlayer.keyFirePrevious && player.keyFire) {
		if (thisPlayer.fireIntervalTimer < player.fastfire)
			player.keyFire = false;
		else
			thisPlayer.fireIntervalTimer = 0;
	}
	thisPlayer.keyFirePrevious = player.keyFire;
}
namespace behavior {
	void asmdBeam(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				obj.var[5] = jjGameTicks;
				break;
			case STATE::ACTION:
				obj.state = STATE::EXPLODE;
				break;
		}
		obj.behave(BEHAVIOR::BULLET, false);
		if (obj.state != STATE::EXPLODE) {
			obj.xPos = obj.xPos + obj.xSpeed / 2;
			obj.yPos = obj.yPos + obj.ySpeed / 2;
			if (obj.counter % 2 == 0) {
				explosion::simpleExplosion(obj.xPos, obj.yPos, ANIM::AMMO, 71);
				if (obj.counter % 10 == 0)
					explosion::asmdRing(obj.xPos, obj.yPos, obj.xSpeed, obj.ySpeed, 30.f / obj.counter, 10.f / obj.counter);
			}
		} else if (obj.age++ == 0 && jjMaskedPixel(int(obj.xPos), int(obj.yPos))){
			explosion::asmdRing(obj.xPos, obj.yPos, obj.xSpeed, obj.ySpeed, 6, 2);
			explosion::harmfulExplosion(obj.xPos, obj.yPos, 32, 2, 50, obj.creatorType == CREATOR::PLAYER ? jjPlayers[obj.creatorID] : null);
		}
	}
	void barrel(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				if (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, -4, 2) != 3 ^^ client::isCoopOrSP()) {
					obj.delete();
					return;
				}
				obj.putOnGround();
				obj.state = STATE::IDLE;
			case STATE::IDLE:
				sprite::add(obj.xPos, obj.yPos, obj.curFrame, 0, SPRITE::PALSHIFT, palshift::barrel);
				for (int i = 1; i < jjObjectCount; i++) {
					jjOBJ@ bull = jjObjects[i];
					if (bull.isActive
						&& bull.playerHandling == HANDLING::SPECIAL
						&& bull.animSpeed > 0
						&& bull.state != STATE::START
						&& bull.state != STATE::EXPLODE
						&& bull.xPos > obj.xPos - 11
						&& bull.xPos < obj.xPos + 15
						&& bull.yPos > obj.yPos - 19
						&& bull.yPos < obj.yPos + 15)
							collision::barrel(obj, bull);
				}
				if (obj.energy < jjObjectPresets[obj.eventID].energy) {
					string text = formatInt(obj.energy, "");
					jjDrawString(obj.xPos - text::getSmallFontIntWidth(text) / 2, obj.yPos + 16, text, STRING::SMALL, STRING::PALSHIFT, palshift::barrelDurability);
				}
				break;
			case STATE::EXTRA:
				{
					uint16 creatorID = obj.special >= 0 ? obj.special : 0;
					CREATOR::Type creatorType = obj.special >= 0 ? CREATOR::PLAYER : CREATOR::OBJECT;
					int ID = jjAddObject(OBJECT::BULLET, obj.xPos, obj.yPos, creatorID, creatorType, behavior::delete);
					if (ID != 0) {
						jjOBJ@ bull = jjObjects[ID];
						bull.curFrame = obj.curFrame;
						bull.freeze = 1;
						bull.playerHandling = HANDLING::PLAYERBULLET;
						bull.state = STATE::IDLE;
					}
				}
				break;
			case STATE::ACTION:
				if (obj.special >= 0 || client::isCoopOrSP() || uint(++obj.counter) > obj.counterEnd) {
					explosion::barrelExplosion(obj.xPos, obj.yPos, obj.special >= 0 ? jjPlayers[obj.special] : null);
					obj.delete();
				}
				break;
		}
	}
	void bullet(jjOBJ@ obj) {
		if (obj.state == STATE::ACTION)
			obj.state = STATE::EXPLODE;
		obj.behave(BEHAVIOR::BULLET, false);
		sprite::add(obj.xPos, obj.yPos, obj.curFrame, int(obj.xSpeed));
	}
	void delete(jjOBJ@ obj) {
		if (++obj.age > 3)
			obj.delete();
	}
	void destructScenery(jjOBJ@ obj) {
		if (obj.special == 0) {
			if (client::isCoopOrSP()) {
				obj.behavior = BEHAVIOR::DESTRUCTSCENERY;
				obj.behave();
				if (obj.eventID == OBJECT::DESTRUCTSCENERYBOMB)
					obj.delete();
				return;
			}
			jjTileSet(4, uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, obj.eventID == OBJECT::DESTRUCTSCENERY ? tileset::wall : tileset::empty);
			obj.delete();
		}
	}
	void enemy(jjOBJ@ obj) {
		if (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 8, 1) == 0) {
			obj.behavior = turret;
			obj.doesHurt = 1;
			obj.energy = 50;
			obj.determineCurAnim(ANIM::SONCSHIP, 6);
		} else {
			obj.behavior = machine;
			obj.doesHurt = 0;
			obj.var[0] = obj.var[1] = 30;
			obj.determineCurAnim(ANIM::SONCSHIP, 2);
		}
		obj.determineCurFrame();
		obj.behave();
	}
	void explosion(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::EXPLOSION, false);
		sprite::add(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::RESIZED, obj.age, 3);
	}
	void flak(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				obj.var[5] = 0;
				obj.determineCurAnim(ANIM::PICKUPS, 44 + (jjRandom() & 3));
				obj.determineCurFrame();
				obj.state = STATE::DELAYEDSTART;
				break;
			case STATE::DELAYEDSTART:
				if (obj.special > 0) {
					if (obj.var[7] * obj.xSpeed > 0)
						obj.xSpeed = obj.xSpeed + float(obj.var[7]) / 0x10000;
					const float angle = atan2(obj.ySpeed, obj.xSpeed);
					const float speed = sqrt(obj.xSpeed * obj.xSpeed + obj.ySpeed * obj.ySpeed);
					for (int i = -obj.special; i <= obj.special; i++) {
						int ID = jjAddObject(OBJECT::RFBULLET, obj.xPos, obj.yPos, obj.creatorID, obj.creatorType);
						if (ID != 0) {
							float newAngle = angle + i * 0.05f;
							float newSpeed = speed + (i * i & 18);
							jjOBJ@ bull = jjObjects[ID];
							bull.counterEnd = obj.counterEnd + (i * i & 5);
							bull.direction = obj.direction;
							bull.special = 0;
							bull.xAcc = -obj.xAcc * cos(newAngle);
							bull.xSpeed = newSpeed * cos(newAngle);
							bull.yAcc = obj.yAcc;
							bull.ySpeed = newSpeed * sin(newAngle);
						}
					}
					obj.delete();
					return;
				}
				obj.state = STATE::FLY;
				break;
			case STATE::ACTION:
				obj.state = STATE::EXPLODE;
			case STATE::EXPLODE:
				explosion::simpleExplosion(obj.xPos, obj.yPos, ANIM::PICKUPS, 4);
				obj.delete();
				return;
		}
		uint8 damage = jjObjectPresets[obj.eventID].doesHurt * (obj.counterEnd - obj.counter) / (obj.counterEnd * (obj.var[9] + 1)) + 1;
		if (damage > weapon::maxDamage)
			damage = weapon::maxDamage;
		obj.doesHurt = damage | obj.var[5];
		if (uint(++obj.counter) > obj.counterEnd)
			obj.state = STATE::EXPLODE;
		obj.xSpeed = obj.xSpeed + obj.xAcc;
		obj.ySpeed = obj.ySpeed + obj.yAcc;
		float x = obj.xPos;
		obj.xPos = obj.xPos + obj.xSpeed;
		if (obj.counter > 5)
			obj.var[5] = weapon::allowSD;
		if (jjMaskedPixel(int(obj.xPos), int(obj.yPos))) {
			if (jjRandom() & 3 == 0)
				jjSample(obj.xPos, obj.yPos, SOUND::COMMON_CUP);
			obj.counter += 4;
			obj.var[9] = obj.var[9] + 1;
			obj.xPos = x;
			obj.xSpeed = -obj.xSpeed;
			obj.xAcc = -obj.xAcc;
		}
		float y = obj.yPos;
		obj.yPos = obj.yPos + obj.ySpeed;
		if (jjMaskedPixel(int(obj.xPos), int(obj.yPos))) {
			obj.counter += 4;
			obj.var[9] = obj.var[9] + 1;
			obj.yPos = y;
			obj.ySpeed = -obj.ySpeed;
		}
		if (jjGameTicks % 7 == 0) {
			obj.frameID++;
			obj.determineCurFrame();
		}
		sprite::add(obj.xPos, obj.yPos, obj.curFrame, int(obj.xSpeed), SPRITE::PALSHIFT, palshift::flak);
	}
	void flame(jjOBJ@ obj) {
		obj.xSpeed = obj.xSpeed + obj.xAcc;
		obj.ySpeed = obj.ySpeed + obj.yAcc;
		float xPrev = obj.xPos;
		float yPrev = obj.yPos;
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		if (jjRandom() & 15 == 0) {
			jjPARTICLE@ part = jjAddParticle(PARTICLE::SMOKE);
			if (part !is null) {
				part.xPos = obj.xPos;
				part.yPos = obj.yPos;
			}
		}
		switch (obj.state) {
			case STATE::START:
				if (jjGameTicks & 6 == 0)
					jjSample(obj.xPos, obj.yPos, SOUND::BILSBOSS_FIRE);
				obj.xSpeed = obj.xSpeed * 4 + float(obj.var[7]) / 0x20000;
				obj.ySpeed = obj.ySpeed * 4;
				obj.age = abs(obj.xSpeed) > abs(obj.ySpeed) ? 1 : 0;
				obj.state = STATE::FIRE;
			case STATE::FIRE:
				{
					bool masked = jjMaskedPixel(int(obj.xPos), int(obj.yPos));
					if (masked || (obj.age == 1 ? abs(obj.xSpeed) < 1 : abs(obj.ySpeed) < 1)) {
						obj.xAcc = obj.yAcc = 0;
						obj.doesHurt = (obj.doesHurt & weapon::maxDamage) / 2 | weapon::allowSD;
						if (masked) {
							obj.xPos = xPrev;
							obj.yPos = yPrev;
							obj.xSpeed = obj.ySpeed = 0;
						}
						obj.state = STATE::FALL;
					}
				}
				break;
			case STATE::FALL:
				if (obj.yAcc < 0.125f)
					obj.yAcc = obj.yAcc + 0.005f;
				if (jjMaskedPixel(int(obj.xPos), int(obj.yPos))) {
					obj.xAcc = obj.xSpeed = obj.yAcc = obj.ySpeed = 0;
					if (uint(++obj.counter) > obj.counterEnd)
						obj.state = STATE::EXPLODE;
				}
				break;
			case STATE::ACTION:
				obj.state = STATE::EXPLODE;
			case STATE::EXPLODE:
				explosion::simpleExplosion(obj.xPos, obj.yPos, ANIM::AMMO, 55);
				obj.delete();
				return;
		}
		if (jjGameTicks % 7 == 0) {
			obj.frameID++;
			obj.determineCurFrame();
		}
		sprite::add(obj.xPos, obj.yPos, obj.curFrame, int(obj.xSpeed));
	}
	void machine(jjOBJ@ obj) {
		const jjPLAYER@ nearestPlayer;
		float shortestDistanceSqr = 640 * 640;
		for (int i = 0; i < 32; i++) {
			const jjPLAYER@ player = jjPlayers[i];
			if (!player.isActive || player.health <= 0)
				continue;
			float dx = player.xPos - obj.xPos;
			float dy = player.yPos - obj.yPos;
			float distanceSqr = dx * dx + 4 * dy * dy;
			if (distanceSqr < shortestDistanceSqr && level::isLineMaskFree(int(obj.xPos), int(obj.yPos), int(player.xPos), int(player.yPos))) {
				@nearestPlayer = player;
				shortestDistanceSqr = distanceSqr;
			}
		}
		int8 springFrame = 0;
		int yMod = 0;
		switch (obj.state) {
			case STATE::START:
				obj.age = obj.var[0];
				obj.counterEnd = 50 - jjDifficulty * 15;
				obj.counter = obj.counterEnd / 2;
				obj.direction = 1;
				obj.special = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 8) % 10;
				obj.var[4] = weapon::fireInterval[obj.special] * (4 - jjDifficulty);
				obj.yAcc = 0.125f;
				obj.yPos = obj.yPos - 25;
				obj.state = STATE::IDLE;
			case STATE::IDLE:
				if ((yMod = ((jjGameTicks >> 3) + obj.objectID) & 3) == 3)
					yMod = 1;
				if (obj.var[3] == 0 && nearestPlayer !is null) {
					if (--obj.counter < 4)
						springFrame = 1;
					if (obj.counter < 0) {
						obj.counter = 0;
						float xDistance = nearestPlayer.xPos - obj.xPos;
						if (xDistance * obj.direction >= 0) {
							obj.doesHurt = 0;
							obj.xSpeed = xDistance / 64;
							if (obj.xSpeed > 3)
								obj.xSpeed = 3;
							else if (obj.xSpeed < -3)
								obj.xSpeed = -3;
							obj.ySpeed = -5;
							jjSample(obj.xPos, obj.yPos + 40, SOUND::SPRING_SPRING1);
						} else {
							obj.doesHurt = 1;
							obj.xSpeed = 0;
							obj.ySpeed = -3;
							jjSample(obj.xPos, obj.yPos + 40, SOUND::SPRING_BOING_DOWN);
						}
						obj.state = STATE::JUMP;
					}
				}
				break;
			case STATE::JUMP:
				obj.xPos = obj.xPos + obj.xSpeed;
				obj.yPos = obj.yPos + obj.ySpeed;
				obj.ySpeed = obj.ySpeed + obj.yAcc;
				if (obj.doesHurt > 0 && obj.ySpeed > 0 && nearestPlayer !is null && (nearestPlayer.xPos - obj.xPos) * obj.direction < 0) {
					obj.direction = -obj.direction;
					obj.doesHurt--;
				}
				if (++obj.counter < 12)
					springFrame = (obj.counter >> 2) + 2;
				if (jjMaskedVLine(int(obj.xPos) + obj.direction * 20, int(obj.yPos) - 12, 48)) {
					obj.xSpeed = -obj.xSpeed;
					obj.doesHurt++;
				}
				if (obj.ySpeed < 0 && jjMaskedHLine(int(obj.xPos) - 16, 32, int(obj.yPos) - 12))
					obj.ySpeed = -obj.ySpeed / 2;
				if (jjMaskedHLine(int(obj.xPos) - 16, 32, int(obj.yPos) + 42)) {
					while (jjMaskedHLine(int(obj.xPos) - 16, 32, int(obj.yPos) + 41) && obj.yPos > 0) {
						obj.yPos = obj.yPos - 1;
					}
					explosion::machineStomp(obj.xPos, obj.yPos + 42);
					obj.counter = obj.counterEnd;
					obj.state = STATE::IDLE;
				}
				break;
			case STATE::EXPLODE:
				for (int i = 0; i < 3; i++) {
					if (obj.var[2] == 0)
						jjAddParticlePixelExplosion(obj.xPos - obj.direction * 9, obj.yPos + yMod - 7, obj.determineCurFrame(false), obj.direction, 1);
					if (obj.var[3] == 0)
						jjAddParticlePixelExplosion(obj.xPos + obj.direction * 9, obj.yPos + yMod - 7, obj.determineCurFrame(false), obj.direction, 1);
				}
				explosion::machineExplosion(obj.xPos, obj.yPos);
				obj.delete();
				return;
		}
		if (obj.var[2] == 0
			&& nearestPlayer !is null
			&& (nearestPlayer.xPos - obj.xPos) * obj.direction >= 0
			&& abs(nearestPlayer.yPos - obj.yPos) < 128
			&& jjGameTicks % obj.var[4] == 0) {
				int ID = jjAddObject(obj.special, obj.xPos + obj.direction * 35, obj.yPos + 13, obj.objectID);
				if (obj.special > 3 && ID != 0) {
					jjOBJ@ bull = jjObjects[ID];
					bull.xPos = bull.xOrg;
					bull.yPos = bull.yOrg;
					bull.xSpeed = bull.xSpeed * obj.direction;
					bull.xAcc = bull.xAcc * obj.direction;
					bull.direction = obj.direction;
				}
		}
		for (int i = 1; i < jjObjectCount; i++) {
			jjOBJ@ bull = jjObjects[i];
			if (bull.isActive
				&& bull.playerHandling == HANDLING::SPECIAL
				&& bull.animSpeed > 0
				&& bull.state != STATE::START
				&& bull.state != STATE::EXPLODE
				&& bull.xPos > obj.xPos - 30
				&& bull.xPos < obj.xPos + 30
				&& bull.yPos > obj.yPos - 30
				&& bull.yPos < obj.yPos + 30) {
					float dx = bull.xPos - obj.xPos;
					float dy = bull.yPos - obj.yPos;
					if (dx * dx + dy * dy < 30 * 30)
						collision::machine(obj, bull);
				}
		}
		obj.determineCurAnim(ANIM::SPRING, 4);
		obj.frameID = springFrame;
		sprite::add(obj.xPos, obj.yPos + 39, obj.determineCurFrame(false), 0, SPRITE::PALSHIFT, palshift::machineSpring[obj.special]);
		obj.determineCurAnim(ANIM::LIZARD, 2);
		obj.frameID = jjGameTicks >> 3 & 3;
		if (obj.var[2] == 0) {
			sprite::add(obj.xPos - obj.direction * 9, obj.yPos + yMod - 7, obj.determineCurFrame(false), obj.direction);
			if (obj.var[0] < obj.age) {
				string text = formatInt(obj.var[0], "");
				jjDrawString(obj.xPos - obj.direction * 20 - text::getSmallFontIntWidth(text) / 2, obj.yPos, text, STRING::SMALL, STRING::PALSHIFT, palshift::enemyHealth);
			}
		} else if (obj.var[2] == 1) {
			jjAddParticlePixelExplosion(obj.xPos - obj.direction * 9, obj.yPos + yMod - 7, obj.determineCurFrame(false), obj.direction, 0);
			obj.var[2] = 2;
		}
		obj.frameID = obj.frameID + 2 & 3;
		if (obj.var[3] == 0) {
			sprite::add(obj.xPos + obj.direction * 9, obj.yPos + yMod - 9, obj.determineCurFrame(false), obj.direction);
			if (obj.var[1] < obj.age) {
				string text = formatInt(obj.var[1], "");
				jjDrawString(obj.xPos + obj.direction * 20 - text::getSmallFontIntWidth(text) / 2, obj.yPos, text, STRING::SMALL, STRING::PALSHIFT, palshift::enemyHealth);
			}
		} else if (obj.var[3] == 1) {
			jjAddParticlePixelExplosion(obj.xPos + obj.direction * 9, obj.yPos + yMod - 7, obj.determineCurFrame(false), obj.direction, 0);
			obj.var[3] = 2;
		}
		sprite::add(obj.xPos, obj.yPos + yMod, obj.curFrame, obj.direction);
		obj.determineCurAnim(ANIM::AMMO, 8);
		obj.frameID = 4;
		sprite::add(obj.xPos - obj.direction * 9, obj.yPos + yMod + 11, obj.determineCurFrame(false), 0, SPRITE::PALSHIFT, palshift::machineBadge[obj.special]);
	}
	void pickup(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				if (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, -4, 2) != 3 ^^ client::isCoopOrSP()) {
					obj.delete();
					return;
				}
				break;
			case STATE::ACTION:
				jjSample(obj.xPos, obj.yPos, SOUND::COMMON_PICKUPW1);
				explosion::simpleExplosion(obj.xPos, obj.yPos, ANIM::PICKUPS, 86);
				obj.delete();
				return;
			case STATE::FLOATFALL:
				obj.state = STATE::FLOAT;
				break;
		}
		obj.behave(BEHAVIOR::PICKUP, false);
		sprite::add(obj.xPos, obj.yPos + 4 * jjSin(((obj.objectID << 3) + jjGameTicks) << 4), obj.curFrame);
	}
	void rocket(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				obj.state = STATE::ROCKETFLY;
				break;
			case STATE::ACTION:
				obj.state = STATE::EXPLODE;
			case STATE::EXPLODE:
				explosion::rocketExplosion(obj.xPos, obj.yPos, obj.creatorType == CREATOR::PLAYER ? jjPlayers[obj.creatorID] : null);
				obj.delete();
				return;
		}
		obj.xSpeed = obj.xSpeed + obj.xAcc;
		obj.ySpeed = obj.ySpeed + obj.yAcc;
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		if (jjMaskedPixel(int(obj.xPos), int(obj.yPos)))
			obj.state = STATE::EXPLODE;
		if (jjGameTicks % 7 == 0) {
			obj.frameID++;
			obj.determineCurFrame();
		}
		sprite::add(obj.xPos, obj.yPos, obj.curFrame, int(obj.xSpeed));
	}
	void savePoint(jjOBJ@ obj) {
		if (!client::isCoopOrSP()) {
			obj.delete();
			return;
		}
		obj.behave(BEHAVIOR::CHECKPOINT, false);
		sprite::add(obj.xPos, obj.yPos, obj.curFrame);
	}
	void shard(jjOBJ@ obj) {
		if (obj.age == 0)
			obj.age = (4 << (jjRandom() & 3)) - 1;
		obj.behave(BEHAVIOR::SHARD, false);
		obj.determineCurFrame();
		sprite::add(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::PALSHIFT, obj.special);
		uint random = jjRandom();
		if (random & obj.age == 0) {
			jjPARTICLE@ part = jjAddParticle(PARTICLE::FIRE);
			if (part !is null) {
				part.xPos = obj.xPos;
				part.yPos = obj.yPos;
			}
		}
	}
	void shortLivedParticle(jjOBJ@ obj) {
		if (--obj.counter < 0) {
			obj.delete();
			return;
		}
		obj.xSpeed = obj.xSpeed + obj.xAcc;
		obj.ySpeed = obj.ySpeed + obj.yAcc;
		obj.xPos = obj.xPos + obj.xSpeed;
		obj.yPos = obj.yPos + obj.ySpeed;
		if (jjGameTicks % 7 == 0 || obj.state == STATE::START) {
			obj.frameID++;
			obj.determineCurFrame();
		}
		sprite::add(obj.xPos, obj.yPos, obj.curFrame, 0, SPRITE::Mode(obj.special), obj.var[0]);
	}
	void spring(jjOBJ@ obj) {
		if (obj.state == STATE::START && jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, -4, 2) == 3 && client::isCoopOrSP()) {
			obj.delete();
			return;
		}
		obj.behave(BEHAVIOR::SPRING, false);
		sprite::add(obj.xPos, obj.yPos, obj.curFrame);
	}
	void turret(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				obj.age = obj.energy;
				obj.direction = 64;
				obj.special = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 8) % 10;
				obj.yPos = obj.yPos - 8;
				obj.state = STATE::DELAYEDSTART;
				break;
			case STATE::EXPLODE:
				obj.counter = 35;
				obj.state = STATE::EXTRA;
			case STATE::EXTRA:
				if (--obj.counter & 15 == 0)
					explosion::turretExplosion(obj.xPos, obj.yPos);
				if (obj.counter < 0)
					obj.delete();
				return;
		}
		obj.counter--;
		const jjPLAYER@ nearestPlayer;
		float xDistance = 0;
		float yDistance = 0;
		for (int i = 0; i < 32; i++) {
			const jjPLAYER@ player = jjPlayers[i];
			if (!player.isActive || player.health <= 0 || player.yPos <= obj.yPos)
				continue;
			float dx = player.xPos - obj.xPos;
			float dy = player.yPos - obj.yPos;
			if ((nearestPlayer is null || dx * dx + dy * dy < xDistance * xDistance + yDistance * yDistance)
				&& level::isLineMaskFree(int(obj.xPos), int(obj.yPos), int(player.xPos), int(player.yPos))) {
					@nearestPlayer = player;
					xDistance = dx;
					yDistance = dy;
			}
		}
		if (nearestPlayer !is null) {
			float angle = atan2(yDistance, xDistance);
			int convertedAngle = int(angle * 125 / 3.1415927f);
			if (obj.direction != convertedAngle) {
				if (obj.state == STATE::DELAYEDSTART) {
					obj.direction = convertedAngle;
				} else {
					if (obj.direction < convertedAngle)
						obj.direction++;
					else
						obj.direction--;
				}
				if (obj.direction < 8)
					obj.direction = 8;
				else if (obj.direction >= 120)
					obj.direction = 120;
				obj.frameID = (125 - obj.direction) / 14 - 1;
				if (obj.frameID < 0)
					obj.frameID = 0;
				else if (obj.frameID > 6)
					obj.frameID = 6;
				obj.determineCurFrame();
			}
			if (obj.counter <= 0) {
				int convertedDirection = obj.direction * 512 / 125;
				int8 direction = obj.direction;
				obj.direction = 1;
				int ID = jjAddObject(obj.special, obj.xPos + 20 * jjCos(convertedDirection), obj.yPos + 20 * jjSin(convertedDirection), obj.objectID);
				obj.direction = direction;
				if (ID != 0) {
					jjOBJ@ bull = jjObjects[ID];
					float speed = bull.xSpeed;
					float acc = bull.xAcc;
					bull.xPos = bull.xOrg;
					bull.yPos = bull.yOrg;
					bull.xSpeed = speed * jjCos(convertedDirection);
					bull.ySpeed = speed * jjSin(convertedDirection);
					bull.xAcc = acc * jjCos(convertedDirection);
					bull.yAcc = acc * jjSin(convertedDirection);
					bull.direction = bull.xSpeed >= 0 ? 1 : -1;
				}
				obj.counter = weapon::fireInterval[obj.special] << 3 >> jjDifficulty;
			}
		}
		if (obj.state == STATE::DELAYEDSTART)
			obj.state = STATE::ROTATE;
		for (int i = 1; i < jjObjectCount; i++) {
			jjOBJ@ bull = jjObjects[i];
			if (bull.isActive
				&& bull.playerHandling == HANDLING::SPECIAL
				&& bull.animSpeed > 0
				&& bull.state != STATE::START
				&& bull.state != STATE::EXPLODE
				&& bull.xPos > obj.xPos - 10
				&& bull.xPos < obj.xPos + 10
				&& bull.yPos > obj.yPos - 10
				&& bull.yPos < obj.yPos + 10)
					collision::turret(obj, bull);
		}
		sprite::add(obj.xPos, obj.yPos, obj.curFrame, 0, SPRITE::PALSHIFT, palshift::turret[obj.special]);
		if (obj.energy < obj.age) {
			string text = formatInt(obj.energy, "");
			jjDrawString(obj.xPos - text::getSmallFontIntWidth(text) / 2, obj.yPos + 32, text, STRING::SMALL, STRING::PALSHIFT, palshift::turretDurability);
		}
	}
}
namespace client {
	bool admin = false;
	int lastIdleServer = 0;
	array<TlocalPlayer> localPlayer(jjLocalPlayerCount);
	bool startedAsCoopOrSP = isCoopOrSP();
	bool isCoopOrSP() {
		return jjGameMode == GAME::SP || jjGameMode == GAME::COOP;
	}
	bool isIdleServer() {
		return lastIdleServer == jjGameTicks || (jjIsServer && jjLocalPlayers[0].xPos == 0 && jjLocalPlayers[0].yPos == 0 && (lastIdleServer = jjGameTicks) != 0);
	}
}
namespace collision {
	void ammoPickup(jjPLAYER@ player, jjOBJ@ obj, WEAPON::Weapon type) {
		if (player.ammo[type] >= jjWeapons[type].maximum)
			return;
		if (player.ammo[type] == 0 && !player.keyFire && (uint8(type) > player.currWeapon || type == WEAPON::SEEKER))
			player.currWeapon = type;
		player.ammo[type] = player.ammo[type] + weapon::pickupMultiplier[type];
		if (player.ammo[type] > jjWeapons[type].maximum)
			player.ammo[type] = jjWeapons[type].maximum;
		if (client::isCoopOrSP()) {
			jjPARTICLE@ part = jjAddParticle(PARTICLE::STRING);
			if (part !is null) {
				part.xPos = obj.xPos;
				part.yPos = obj.yPos;
				part.xSpeed = 0;
				part.ySpeed = -5;
				part.string.text = "||+ AMMO";
			}
		}
		obj.state = STATE::ACTION;
	}
	void barrel(jjOBJ@ obj, jjOBJ@ bull) {
		if (bull.creatorType == CREATOR::PLAYER)
			obj.special = bull.creatorID;
		if (obj.state == STATE::ACTION || obj.state == STATE::EXTRA)
			return;
		switch (bull.eventID) {
			case OBJECT::BLASTERBULLET:
				jjSample(obj.xPos, obj.yPos, SOUND::AMMO_GUNJAZZ);
				bull.state = STATE::EXPLODE;
				break;
			case OBJECT::BOUNCERBULLET:
				bull.direction = bull.xSpeed > 0 ? -1 : 1;
				bull.ricochet();
				bull.state = STATE::EXPLODE;
				break;
			case OBJECT::ICEBULLET:
				if (obj.var[5] == bull.var[5])
					return;
				obj.var[5] = bull.var[5];
				break;
			case OBJECT::RFBULLET:
				if (jjRandom() & 2 == 0)
					jjSample(obj.xPos, obj.yPos, SOUND::COMMON_CUP);
				bull.counter += 8;
				bull.var[9] = bull.var[9] + 1;
				if (!jjMaskedPixel(int(bull.xPos - bull.xSpeed), int(bull.yPos))) {
					bull.xSpeed = -bull.xSpeed;
				} else {
					if (jjMaskedPixel(int(bull.xPos), int(bull.yPos - bull.ySpeed)))
						bull.xSpeed = -bull.xSpeed;
					bull.ySpeed = -bull.ySpeed;
				}
				break;
			default:
				bull.state = STATE::EXPLODE;
		}
		if (obj.energy > bull.animSpeed)
			obj.energy -= bull.animSpeed;
		else
			obj.state = STATE::EXTRA;
	}
	void barrelExplode(jjOBJ@ obj, jjOBJ@ bull) {
		obj.state = STATE::ACTION;
		bull.delete();
	}
	bool hurtPlayer(jjPLAYER@ player, jjPLAYER@ attacker, uint8 damage) {
		if (player.blink != 0 || player.health <= 0)
			return false;
		if (attacker !is null) {
			if (attacker.blink != 0)
				return false;
			if (player is attacker) {
				if (damage & weapon::allowSD == 0 || jjTriggers[trigger::disableSD])
					return false;
			} else if (jjGameMode == GAME::CTF && player.teamRed == attacker.teamRed && !jjTriggers[trigger::friendlyFire]) {
				return false;
			}
		}
		if (jjTriggers[trigger::damageAmplifier] && damage & weapon::maxDamage < weapon::maxDamage)
			damage++;
		if (player.hurt(damage & weapon::maxDamage, true, attacker)) {
			player.blink = 60;
			return true;
		}
		return false;
	}
	void machine(jjOBJ@ obj, jjOBJ@ bull) {
		if (bull.yPos > obj.yPos - 12) {
			if (bull.eventID == OBJECT::BOUNCERBULLET) {
				bull.direction = bull.xSpeed > 0 ? -1 : 1;
				bull.ricochet();
			}
		} else {
			int index = (bull.xPos - obj.xPos) * obj.direction >= 0 ? 1 : 0;
			if (obj.var[index + 2] != 0)
				return;
			if ((obj.var[index] = obj.var[index] - bull.animSpeed) <= 0)
				obj.var[index + 2] = 1;
		}
		bull.state = STATE::EXPLODE;
	}
	void projectile(jjPLAYER@ player, jjOBJ@ bull) {
		jjPLAYER@ creator;
		if (bull.creatorType == CREATOR::PLAYER)
			@creator = jjPlayers[bull.creatorID];
		if (hurtPlayer(player, creator, bull.doesHurt))
			bull.state = STATE::ACTION;
	}
	void turret(jjOBJ@ obj, jjOBJ@ bull) {
		bull.state = STATE::EXPLODE;
		if (obj.energy > bull.animSpeed)
			obj.energy -= bull.animSpeed;
		else
			obj.state = STATE::EXPLODE;
	}
}
namespace draw {
	void ammoCounter(jjCANVAS@ canvas, int x, int y, int value, int max, bool active) {
		int color = 0;
		if (active) {
			if (2 * value <= max) {
				if (4 * value <= max) {
					if (value <= 0)
						color = 3;
					else
						color = 2;
				} else {
					color = 1;
				}
			}
		}
		STRING::Mode mode = active ? STRING::PALSHIFT : STRING::DARK;
		string text = formatInt(value, "") + "/" + formatInt(max, "");
		canvas.drawString(x, y, text, STRING::SMALL, mode, palshift::ammoCount[color]);
	}
	void healthBar(jjCANVAS@ canvas, int x, int y, int value, int max, bool red) {
		const uint backgroundTile = tileset::healthBar + 12;
		const int backgroundX = x + 32;
		const uint insideTile = tileset::healthBar + 5;
		const int insideX = x + 46;
		const int insideY = y + 8;
		const int length = 105 * value / max;
		const bool low = value <= max / 2;
		const TILE::Quadrant quadrantLeft = low ? TILE::BOTTOMLEFT : TILE::TOPLEFT;
		const TILE::Quadrant quadrantRight = low ? TILE::BOTTOMRIGHT : TILE::TOPRIGHT;
		if (value < max) {
			for (int i = 0; i < 4; i++) {
				canvas.drawTile(backgroundX + i * 32, y, backgroundTile + i);
			}
		}
		for (int i = 0; i < length; i++) {
			uint random = jjRandom();
			canvas.drawTile(insideX + i, insideY, insideTile + (random & 1), random & 2 != 0 ? quadrantLeft : quadrantRight);
		}
		if (value < max)
			canvas.drawTile(insideX + length, insideY, insideTile + 11, jjRandom() & 1 != 0 ? quadrantLeft : quadrantRight);
		if (length >= 9) {
			canvas.drawTile(x + 48, y, insideTile + 4, quadrantLeft);
			if (length >= 50) {
				canvas.drawTile(x + 80, y, insideTile + 4, quadrantRight);
				if (length >= 82)
					canvas.drawTile(x + 112, y, insideTile + 4, quadrantRight);
			}
		}
		for (int i = 0; i < 5; i++) {
			canvas.drawTile(x + i * 32, y, tileset::healthBar + i);
		}
		for (int i = 0; i < 2; i++) {
			canvas.drawTile(x + i * 32, y + 32, tileset::healthBar + 10 + i);
		}
		if (!red) {
			const uint heartTile = tileset::healthBar + (low ? 17 : 7);
			for (int i = 0; i < 2; i++) {
				canvas.drawTile(x + i * 32, y + 16, heartTile + i);
			}
		}
	}
	void largeWeaponIcon(jjCANVAS@ canvas, int x, int y, uint8 type) {
		uint modifier = tileset::weaponLarge + (type + (type & ~1)) * 5;
		for (int i = 0; i < 2; i++) {
			for (int j = 0; j < 5; j++) {
				canvas.drawTile(x + j * 32, y + i * 32, modifier + i * 10 + j);
			}
		}
	}
	void smallWeaponIcon(jjCANVAS@ canvas, int x, int y, uint8 type, bool active) {
		uint modifier = (active ? tileset::weaponSmall : tileset::weaponSmallInactive) + type * 5;
		if (type & 1 == 0) {
			canvas.drawTile(x, y, modifier);
			canvas.drawTile(x + 32, y, modifier + 1);
			canvas.drawTile(x + 64, y, modifier + 2, TILE::TOPLEFT);
			canvas.drawTile(x + 64, y + 16, modifier + 2, TILE::BOTTOMLEFT);
		} else {
			canvas.drawTile(x, y, modifier - 3, TILE::TOPRIGHT);
			canvas.drawTile(x, y + 16, modifier - 3, TILE::BOTTOMRIGHT);
			canvas.drawTile(x + 16, y, modifier - 2);
			canvas.drawTile(x + 48, y, modifier - 1);
		}
	}
	void weaponIconBarHorizontal(jjCANVAS@ canvas, int x, int y, const jjPLAYER@ player) {
		const bool largeDisplay = client::localPlayer[player.localPlayerID].weaponChangeTimer > 0;
		x -= weapon::count * 40 + (largeDisplay ? 40 : 0);
		int x2 = x + (largeDisplay ? 80 : 0);
		const uint8 currentWeaponID = player.currWeapon - weapon::first;
		for (uint i = 0; i < weapon::count; i++) {
			uint8 weapon = i + 1;
			bool hasAmmoOrIsCurrent = player.ammo[weapon] > 0 || i == currentWeaponID;
			if (i == currentWeaponID && largeDisplay) {
				largeWeaponIcon(canvas, x + i * 80, y - 32, i);
				ammoCounter(canvas, x + i * 80, y + 24, player.ammo[weapon], jjWeapons[weapon].maximum, true);
				weaponName(canvas, x + i * 80 + 160, y - 24, weapon);
			} else {
				int xUsed = i < currentWeaponID ? x : x2;
				smallWeaponIcon(canvas, xUsed + i * 80, y - 16, i, hasAmmoOrIsCurrent);
				if (hasAmmoOrIsCurrent)
					ammoCounter(canvas, xUsed + i * 80, y + 16, player.ammo[weapon], jjWeapons[weapon].maximum, i == currentWeaponID);
			}
		}
	}
	void weaponIconBarVertical(jjCANVAS@ canvas, int x, int y, const jjPLAYER@ player) {
		const bool largeDisplay = client::localPlayer[player.localPlayerID].weaponChangeTimer > 0;
		y -= weapon::count * 24 + (largeDisplay ? 16 : 0);
		const int x2 = x - 32;
		const int y2 = y + (largeDisplay ? 32 : 0);
		const uint8 currentWeaponID = player.currWeapon - weapon::first;
		for (uint i = 0; i < weapon::count; i++) {
			uint8 weapon = i + 1;
			bool hasAmmoOrIsCurrent = player.ammo[weapon] > 0 || i == currentWeaponID;
			if (i == currentWeaponID && largeDisplay) {
				largeWeaponIcon(canvas, x - 80, y + i * 48 + 8, i);
				ammoCounter(canvas, x - 80, y + i * 48 + 72, player.ammo[weapon], jjWeapons[weapon].maximum, true);
				weaponName(canvas, x + 80, y + i * 48 + 8, weapon);
			} else {
				int yUsed = i < currentWeaponID ? y : y2;
				int xUsed = i != currentWeaponID ? x : x2;
				smallWeaponIcon(canvas, xUsed, yUsed + i * 48, i, hasAmmoOrIsCurrent);
				if (hasAmmoOrIsCurrent)
					ammoCounter(canvas, xUsed, yUsed + i * 48 + 32, player.ammo[weapon], jjWeapons[weapon].maximum, i == currentWeaponID);
			}
		}
	}
	void weaponName(jjCANVAS@ canvas, int x, int y, uint8 type) {
		canvas.drawString(x - text::getSmallFontIntWidth(weapon::name[type]), y, weapon::name[type]);
	}
}
namespace explosion {
	void asmdRing(float x, float y, float xSpeed, float ySpeed, float semiMajor, float semiMinor) {
		if (client::isIdleServer())
			return;
		const int shift = semiMajor > 2 ? 5 : 6;
		const int count = 1 << (10 - shift);
		const float multiplier = 6.2831853 / count;
		const float hypot = sqrt(xSpeed * xSpeed + ySpeed * ySpeed);
		const float axisSin = ySpeed / hypot;
		const float axisCos = xSpeed / hypot;
		const float majorSin = semiMajor * axisSin;
		const float minorSin = semiMinor * axisSin;
		const float majorCos = semiMajor * axisCos;
		const float minorCos = semiMinor * axisCos;
		for (int i = 0; i < count; i++) {
			const int angle = i << shift;
			const float angleSin = jjSin(angle);
			const float angleCos = jjCos(angle);
			const float xSpeedUlt = minorCos * angleCos - majorSin * angleSin;
			const float ySpeedUlt = minorSin * angleCos + majorCos * angleSin;
			particle(x, y, xSpeedUlt, ySpeedUlt, 0, 0, 35, ANIM::AMMO, 9, SPRITE::PALSHIFT, palshift::asmdParticle);
		}
	}
	void barrelExplosion(float x, float y, jjPLAYER@ creator) {
		harmfulExplosion(x, y, 128, 4, 80, creator);
		if (client::isIdleServer())
			return;
		jjSample(x, y, SOUND::COMMON_GLASS2);
		for (int i = 0; i < 20; i++) {
			int ID = jjAddObject(OBJECT::SHARD, x, y);
			if (ID != 0) {
				jjOBJ@ obj = jjObjects[ID];
				obj.behavior = behavior::shard;
				obj.special = palshift::barrel;
				uint random = jjRandom();
				int speed = 8 + (random >> 2 & 7);
				uint angle = random >> 5;
				obj.xSpeed = jjSin(angle) * speed;
				obj.ySpeed = jjCos(angle) * speed;
				obj.determineCurAnim(ANIM::PICKUPS, 6 + (random & 3));
			}
		}
		simpleExplosion(x, y, ANIM::AMMO, 5, 128);
		for (int i = 0; i < 3; i++) {
			uint angle = jjRandom();
			simpleExplosion(x + jjSin(angle) * 64, y + jjCos(angle) * 64, ANIM::AMMO, 3, 64);
			angle >>= 10;
			simpleExplosion(x + jjSin(angle) * 96, y + jjCos(angle) * 96, ANIM::AMMO, 3, 48);
			angle >>= 10;
			simpleExplosion(x + jjSin(angle) * 96, y + jjCos(angle) * 96, ANIM::AMMO, 3, 32);
		}
		uint16 creatorID = creator !is null ? creator.playerID : 0;
		CREATOR::Type creatorType = creator !is null ? CREATOR::PLAYER : CREATOR::OBJECT;
		for (int i = 0; i < 5; i++) {
			int ID = jjAddObject(OBJECT::TOASTERBULLET, x, y, creatorID, creatorType);
			if (ID != 0) {
				uint angle = (i - 2) << 6;
				jjOBJ@ obj = jjObjects[ID];
				obj.doesHurt = (obj.doesHurt & weapon::maxDamage) / 2 | weapon::allowSD;
				obj.state = STATE::FALL;
				obj.xAcc = obj.yAcc = 0;
				obj.xSpeed = jjSin(angle) * 2;
				obj.ySpeed = -jjCos(angle) * 2;
			}
		}
	}
	void harmfulExplosion(float x, float y, float radius, uint damage, int objectDamage, jjPLAYER@ creator = null) {
		damage &= weapon::maxDamage;
		for (int i = 0; i < jjLocalPlayerCount; i++) {
			jjPLAYER@ player = jjLocalPlayers[i];
			if (player.xPos < x - radius || player.xPos > x + radius || player.yPos < y - radius || player.yPos > y + radius)
				continue;
			float dx = player.xPos - x;
			float dy = player.yPos - y;
			float distance = sqrt(dx * dx + dy * dy);
			if (distance > radius)
				continue;
			uint value = uint(damage * (radius - distance) / radius) + 1;
			if (value > damage)
				value = damage;
			collision::hurtPlayer(player, creator, value | weapon::allowSD);
		}
		for (int i = 0; i < jjObjectCount; i++) {
			jjOBJ@ obj = jjObjects[i];
			if (!obj.isActive || obj.xPos < x - radius || obj.xPos > x + radius || obj.yPos < y - radius || obj.yPos > y + radius)
				continue;
			float dx = obj.xPos - x;
			float dy = obj.yPos - y;
			float distance = sqrt(dx * dx + dy * dy);
			if (distance > radius)
				continue;
			int value = int(objectDamage * (radius - distance) / radius) + 1;
			switch (obj.eventID) {
				case OBJECT::TNT:
					if (value > objectDamage)
						value = objectDamage;
					if (obj.energy > value)
						obj.energy -= value;
					else
						obj.state = STATE::EXTRA;
					if (creator !is null)
						obj.special = creator.playerID;
					break;
				case OBJECT::DESTRUCTSCENERY:
					if (obj.state != STATE::DONE && value > 50)
						obj.state = STATE::KILL;
					break;
				case OBJECT::FLOATLIZARD:
					if (obj.doesHurt == 1) {
						if (obj.energy > value)
							obj.energy -= value;
						else
							obj.state = STATE::EXPLODE;
					} else if (value > 50) {
						obj.state = STATE::EXPLODE;
					}
					break;
			}
		}
	}
	void machineExplosion(float x, float y) {
		harmfulExplosion(x, y, 160, 7, 200);
		if (client::isIdleServer())
			return;
		jjSample(x, y, SOUND::COMMON_DAMPED1);
		for (int i = 0; i < 40; i++) {
			int ID = jjAddObject(OBJECT::SHARD, x, y);
			if (ID != 0) {
				jjOBJ@ obj = jjObjects[ID];
				obj.behavior = behavior::shard;
				uint random = jjRandom();
				obj.special = palshift::machineShard;
				int speed = 7 + (random >> 2 & 7);
				uint angle = random >> 5;
				obj.xSpeed = jjSin(angle) * speed;
				obj.ySpeed = jjCos(angle) * speed;
				obj.determineCurAnim(ANIM::PICKUPS, 6 + (random & 3));
			}
		}
		simpleExplosion(x, y, ANIM::AMMO, 5, 160);
		for (int i = 0; i < 2; i++) {
			uint angle = jjRandom();
			simpleExplosion(x + jjSin(angle) * 64, y + jjCos(angle) * 64, ANIM::AMMO, 5, 96);
			angle >>= 10;
			simpleExplosion(x + jjSin(angle) * 96, y + jjCos(angle) * 96, ANIM::AMMO, 5, 96);
			angle >>= 10;
			simpleExplosion(x + jjSin(angle) * 128, y + jjCos(angle) * 128, ANIM::AMMO, 5, 96);
		}
	}
	void machineStomp(float x, float y) {
		harmfulExplosion(x, y, 32, 3, 100);
		if (client::isIdleServer())
			return;
		jjSample(x, y, SOUND::COMMON_METALHIT);
		uint random = jjRandom();
		for (int i = 0; i < 3; i++) {
			simpleExplosion(x + (i - 1) * 16, y + (random & 7), ANIM::AMMO, 72, 48);
			random >>= 3;
		}
	}
	void particle(float x, float y, float xSpeed, float ySpeed, float xAcc, float yAcc, int counter, ANIM::Set setID, uint8 animation, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0) {
		if (client::isIdleServer())
			return;
		int ID = jjAddObject(OBJECT::EXPLOSION, x, y);
		if (ID != 0) {
			jjOBJ@ obj = jjObjects[ID];
			obj.behavior = behavior::shortLivedParticle;
			obj.xSpeed = xSpeed;
			obj.ySpeed = ySpeed;
			obj.xAcc = xAcc;
			obj.yAcc = yAcc;
			obj.counter = counter;
			obj.determineCurAnim(setID, animation);
			obj.special = mode;
			obj.var[0] = param;
		}
	}
	void rocketExplosion(float x, float y, jjPLAYER@ creator) {
		harmfulExplosion(x, y, 160, 5, 120, creator);
		if (client::isIdleServer())
			return;
		jjSample(x, y, SOUND::COMMON_DAMPED1);
		for (int i = 0; i < 20; i++) {
			int ID = jjAddObject(OBJECT::SHARD, x, y);
			if (ID != 0) {
				jjOBJ@ obj = jjObjects[ID];
				obj.behavior = behavior::shard;
				uint random = jjRandom();
				obj.special = palshift::rocketShard[random >> 2 & 1];
				int speed = 7 + (random >> 3 & 3);
				uint angle = random >> 5;
				obj.xSpeed = jjSin(angle) * speed;
				obj.ySpeed = jjCos(angle) * speed;
				obj.determineCurAnim(ANIM::PICKUPS, 6 + (random & 3));
			}
		}
		simpleExplosion(x, y, ANIM::AMMO, 5, 160);
		for (int i = 0; i < 2; i++) {
			uint angle = jjRandom();
			simpleExplosion(x + jjSin(angle) * 64, y + jjCos(angle) * 64, ANIM::AMMO, 5, 96);
			angle >>= 10;
			simpleExplosion(x + jjSin(angle) * 96, y + jjCos(angle) * 96, ANIM::AMMO, 5, 64);
			angle >>= 10;
			simpleExplosion(x + jjSin(angle) * 128, y + jjCos(angle) * 128, ANIM::AMMO, 5, 48);
		}
	}
	void simpleExplosion(float x, float y, ANIM::Set setID, uint8 animation, uint8 scale = 32) {
		if (client::isIdleServer())
			return;
		int ID = jjAddObject(OBJECT::EXPLOSION, x, y);
		if (ID != 0) {
			jjOBJ@ obj = jjObjects[ID];
			obj.determineCurAnim(setID, animation);
			obj.age = scale;
		}
	}
	void turretExplosion(float x, float y) {
		if (client::isIdleServer())
			return;
		jjSample(x, y, SOUND::COMMON_GLASS2);
		simpleExplosion(x, y, ANIM::AMMO, 5, 64);
		for (int i = 0; i < 3; i++) {
			uint angle = jjRandom();
			simpleExplosion(x + jjSin(angle) * 32, y + jjCos(angle) * 32, ANIM::AMMO, 3);
			angle >>= 10;
			simpleExplosion(x + jjSin(angle) * 48, y + jjCos(angle) * 48, ANIM::AMMO, 3);
			angle >>= 10;
			simpleExplosion(x + jjSin(angle) * 48, y + jjCos(angle) * 48, ANIM::AMMO, 3);
		}
	}
}
namespace initialize {
	void objectPresets() {
		jjOBJ@ preset;
		@preset = jjObjectPresets[OBJECT::BLASTERBULLET];
		preset.animSpeed = 10;
		preset.behavior = behavior::bullet;
		preset.bulletHandling = HANDLING::IGNOREBULLET;
		preset.counterEnd = 50;
		preset.doesHurt = 2;
		preset.freeze = 0;
		preset.isFreezable = false;
		preset.killAnim = preset.determineCurAnim(ANIM::AMMO, 82, false);
		preset.lightType = LIGHT::POINT2;
		preset.playerHandling = HANDLING::SPECIAL;
		preset.scriptedCollisions = true;
		preset.special = preset.determineCurAnim(ANIM::AMMO, 32, false);
		preset.xAcc = preset.yAcc = preset.ySpeed = 0;
		preset.xSpeed = 7;
		preset.determineCurAnim(ANIM::AMMO, 30);
		preset.determineCurFrame();
		@preset = jjObjectPresets[OBJECT::BOUNCERBULLET];
		preset.animSpeed = 6;
		preset.behavior = behavior::bullet;
		preset.bulletHandling = HANDLING::IGNOREBULLET;
		preset.counterEnd = 100;
		preset.doesHurt = 1;
		preset.freeze = 0;
		preset.isFreezable = false;
		preset.killAnim = preset.determineCurAnim(ANIM::AMMO, 10, false);
		preset.lightType = LIGHT::NONE;
		preset.playerHandling = HANDLING::SPECIAL;
		preset.scriptedCollisions = true;
		preset.special = preset.determineCurAnim(ANIM::AMMO, 22, false);
		preset.xAcc = preset.yAcc = preset.ySpeed = 0;
		preset.xSpeed = 10;
		preset.determineCurAnim(ANIM::AMMO, 20);
		preset.determineCurFrame();
		@preset = jjObjectPresets[OBJECT::ICEBULLET];
		preset.animSpeed = 25;
		preset.behavior = behavior::asmdBeam;
		preset.bulletHandling = HANDLING::IGNOREBULLET;
		preset.counterEnd = 40;
		preset.doesHurt = 3;
		preset.freeze = 0;
		preset.isFreezable = false;
		preset.killAnim = preset.determineCurAnim(ANIM::AMMO, 71, false);
		preset.light = 4;
		preset.lightType = LIGHT::RING2;
		preset.playerHandling = HANDLING::SPECIAL;
		preset.scriptedCollisions = true;
		preset.special = preset.determineCurAnim(ANIM::AMMO, 65, false);
		preset.xAcc = preset.yAcc = preset.ySpeed = 0;
		preset.xSpeed = 10;
		preset.determineCurAnim(ANIM::AMMO, 73);
		preset.determineCurFrame();
		@preset = jjObjectPresets[OBJECT::SEEKERBULLET];
		preset.animSpeed = 1000;
		preset.behavior = behavior::rocket;
		preset.bulletHandling = HANDLING::IGNOREBULLET;
		preset.doesHurt = 7;
		preset.freeze = 0;
		preset.isFreezable = false;
		preset.lightType = LIGHT::NONE;
		preset.playerHandling = HANDLING::SPECIAL;
		preset.scriptedCollisions = true;
		preset.xAcc = preset.yAcc = preset.ySpeed = 0;
		preset.xSpeed = 10;
		@preset = jjObjectPresets[OBJECT::RFBULLET];
		preset.animSpeed = 10;
		preset.behavior = behavior::flak;
		preset.bulletHandling = HANDLING::IGNOREBULLET;
		preset.counterEnd = 35;
		preset.doesHurt = 6;
		preset.freeze = 0;
		preset.isFreezable = false;
		preset.lightType = LIGHT::NONE;
		preset.playerHandling = HANDLING::SPECIAL;
		preset.scriptedCollisions = true;
		preset.special = 3;
		preset.xAcc = -0.1f;
		preset.yAcc = 0.1f;
		preset.xSpeed = 15;
		preset.ySpeed = 0;
		@preset = jjObjectPresets[OBJECT::TOASTERBULLET];
		preset.animSpeed = 5;
		preset.behavior = behavior::flame;
		preset.bulletHandling = HANDLING::IGNOREBULLET;
		preset.counterEnd = 40;
		preset.doesHurt = 2;
		preset.freeze = 0;
		preset.isFreezable = false;
		preset.lightType = LIGHT::NONE;
		preset.playerHandling = HANDLING::SPECIAL;
		preset.scriptedCollisions = true;
		preset.xAcc = -0.2f;
		preset.xSpeed = 3;
		preset.yAcc = preset.ySpeed = 0;
		preset.determineCurAnim(ANIM::AMMO, 13);
		@preset = jjObjectPresets[OBJECT::TNT];
		preset.behavior = behavior::barrel;
		preset.bulletHandling = HANDLING::DETECTBULLET;
		preset.counterEnd = 15;
		preset.energy = 100;
		preset.freeze = 0;
		preset.isFreezable = false;
		preset.lightType = LIGHT::NONE;
		preset.playerHandling = HANDLING::SPECIAL;
		preset.scriptedCollisions = true;
		preset.special = -1;
		preset.determineCurAnim(ANIM::PICKUPS, 3);
		preset.determineCurFrame();
		@preset = jjObjectPresets[OBJECT::BOUNCERAMMO3];
		preset.behavior = behavior::pickup;
		preset.scriptedCollisions = true;
		preset.determineCurAnim(ANIM::HATTER, 1);
		preset.determineCurFrame();
		@preset = jjObjectPresets[OBJECT::ICEAMMO3];
		preset.behavior = behavior::pickup;
		preset.scriptedCollisions = true;
		preset.determineCurAnim(ANIM::AMMO, 69);
		preset.determineCurFrame();
		@preset = jjObjectPresets[OBJECT::SEEKERAMMO3];
		preset.behavior = behavior::pickup;
		preset.scriptedCollisions = true;
		preset.determineCurAnim(ANIM::SONCSHIP, 0);
		preset.determineCurFrame();
		@preset = jjObjectPresets[OBJECT::RFAMMO3];
		preset.behavior = behavior::pickup;
		preset.scriptedCollisions = true;
		preset.determineCurAnim(ANIM::LIZARD, 1);
		preset.determineCurFrame();
		@preset = jjObjectPresets[OBJECT::TOASTERAMMO3];
		preset.behavior = behavior::pickup;
		preset.scriptedCollisions = true;
		preset.determineCurAnim(ANIM::BILSBOSS, 3);
		preset.determineCurFrame();
		@preset = jjObjectPresets[OBJECT::CARROT];
		preset.behavior = behavior::pickup;
		preset.points = 1;
		@preset = jjObjectPresets[OBJECT::EXPLOSION];
		preset.behavior = behavior::explosion;
		@preset = jjObjectPresets[OBJECT::COLLAPSESCENERY];
		preset.behavior = behavior::destructScenery;
		@preset = jjObjectPresets[OBJECT::DESTRUCTSCENERY];
		preset.behavior = behavior::destructScenery;
		preset.points = 0;
		@preset = jjObjectPresets[OBJECT::DESTRUCTSCENERYBOMB];
		preset.behavior = behavior::destructScenery;
		preset.points = 0;
		@preset = jjObjectPresets[OBJECT::STOMPSCENERY];
		preset.behavior = behavior::destructScenery;
		preset.points = 0;
		@preset = jjObjectPresets[OBJECT::REDSPRING];
		preset.behavior = behavior::spring;
		@preset = jjObjectPresets[OBJECT::GREENSPRING];
		preset.behavior = behavior::spring;
		@preset = jjObjectPresets[OBJECT::BLUESPRING];
		preset.behavior = behavior::spring;
		@preset = jjObjectPresets[OBJECT::FLOATLIZARD];
		preset.behavior = behavior::enemy;
		preset.isFreezable = false;
		preset.playerHandling = HANDLING::PARTICLE;
		@preset = jjObjectPresets[OBJECT::CHECKPOINT];
		preset.behavior = behavior::savePoint;
		for (int i = 1; i < 256; i++) {
			@preset = jjObjectPresets[i];
			preset.deactivates = false;
		}
	}
	void palette() {
		jjPalette.fill(255, 255, 0, 104, 1);
		jjPalette.fill(192, 192, 0, 117, 1);
		jjPalette.gradient(200, 200, 0, 0, 0, 0, 123, 5);
		jjPalette.gradient(255, 255, 0, 192, 0, 0, 144, 5);
		jjPalette.gradient(192, 0, 0, 0, 0, 0, 148, 4);
		jjPalette.gradient(128, 192, 192, 32, 48, 48);
		jjPalette.apply();
	}
	void specialTiles() {
		for (int i = 0; i < jjLayerHeight[4]; i++) {
			for (int j = 0; j < jjLayerWidth[4]; j++) {
				switch (jjTileGet(4, j, i)) {
					case tileset::ventShaft:
						level::specialTile.insertLast(TventShaft(j, i));
						break;
				}
			}
		}
	}
	void weaponProfiles() {
		jjWEAPON@ weapon;
		@weapon = jjWeapons[WEAPON::BLASTER];
		weapon.infinite = false;
		weapon.maximum = 24;
		weapon.multiplier = 1;
		weapon.replenishes = true;
		weapon.style = WEAPON::NORMAL;
		@weapon = jjWeapons[WEAPON::BOUNCER];
		weapon.infinite = false;
		weapon.maximum = 120;
		weapon.multiplier = 1;
		weapon.replenishes = false;
		weapon.style = WEAPON::NORMAL;
		@weapon = jjWeapons[WEAPON::ICE];
		weapon.infinite = false;
		weapon.maximum = 40;
		weapon.multiplier = 1;
		weapon.replenishes = false;
		weapon.style = WEAPON::NORMAL;
		@weapon = jjWeapons[WEAPON::SEEKER];
		weapon.infinite = false;
		weapon.maximum = 4;
		weapon.multiplier = 1;
		weapon.replenishes = false;
		weapon.style = WEAPON::NORMAL;
		@weapon = jjWeapons[WEAPON::RF];
		weapon.infinite = false;
		weapon.maximum = client::isCoopOrSP() ? 0 : 16;
		weapon.multiplier = 1;
		weapon.replenishes = false;
		weapon.style = WEAPON::NORMAL;
		@weapon = jjWeapons[WEAPON::TOASTER];
		weapon.infinite = false;
		weapon.maximum = client::isCoopOrSP() ? 0 : 300;
		weapon.multiplier = 1;
		weapon.replenishes = false;
		weapon.style = WEAPON::NORMAL;
		@weapon = jjWeapons[WEAPON::TNT];
		weapon.infinite = false;
		weapon.maximum = 0;
		@weapon = jjWeapons[WEAPON::GUN8];
		weapon.infinite = false;
		weapon.maximum = 0;
		@weapon = jjWeapons[WEAPON::GUN9];
		weapon.infinite = false;
		weapon.maximum = 0;
	}
}
namespace level {
	array<Itile@> specialTile;
	bool isLineMaskFree(int xPos1, int yPos1, int xPos2, int yPos2) {
		int xLength = xPos2 - xPos1;
		int yLength = yPos2 - yPos1;
		if (xLength == 0 && yLength == 0)
			return !jjMaskedPixel(xPos1, yPos1);
		int x, y;
		if (abs(xLength) >= abs(yLength)) {
			if (xPos1 < xPos2) {
				x = xPos1;
				y = yPos1;
			} else {
				x = xPos2;
				y = yPos2;
				xLength = -xLength;
				yLength = -yLength;
			}
			for (int i = 0; i <= xLength; i++) {
				if (jjMaskedPixel(x + i, y + i * yLength / xLength))
					return false;
			}
		} else {
			if (yPos1 < yPos2) {
				x = xPos1;
				y = yPos1;
			} else {
				x = xPos2;
				y = yPos2;
				xLength = -xLength;
				yLength = -yLength;
			}
			for (int i = 0; i <= yLength; i++) {
				if (jjMaskedPixel(x + i * xLength / yLength, y + i))
					return false;
			}
		}
		return true;
	}
}
namespace sprite {
	array<array<Tsprite>> spriteQueue(8);
	void add(float x, float y, uint frame, int direction = 0, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, uint8 layer = 4) {
		if (client::isIdleServer())
			return;
		Tsprite sprite;
		sprite.x = int(floor(x));
		sprite.y = int(floor(y));
		sprite.frame = frame;
		sprite.direction = direction;
		sprite.mode = mode;
		sprite.param = param;
		spriteQueue[layer - 1].insertLast(sprite);
	}
	void clearQueues() {
		for (uint i = 0; i < spriteQueue.length(); i++) {
			spriteQueue[i].resize(0);
		}
	}
	void drawQueue(jjCANVAS@ canvas, uint8 layer) {
		for (uint i = 0; i < spriteQueue[layer].length(); i++) {
			spriteQueue[layer][i].draw(canvas);
		}
	}
}
namespace text {
	const array<int> smallFontCharacterWidth = {8, 6, 7, 15, 10, 12, 0, 4, 9, 9, 10, 13, 6, 10, 5, 11, 10, 8 /*actually 6*/, 11, 10, 11, 10, 9, 8, 9, 8, 6, 7, 9, 11, 9, 10, 14, 11, 11, 10, 12, 12, 9, 11, 9, 10, 11, 12, 10, 12, 12, 11, 10, 11, 12, 9, 11, 11, 9, 12, 10, 9, 11, 8, 11, 8, 13, 10, 6, 10, 9, 9, 9, 11, 9, 10, 10, 6, 9, 10, 6, 11, 10, 9, 10, 10, 10, 10, 9, 10, 9, 12, 9, 8, 9, 0, 0, 0, 0, 0};
	int getScoreHUDHeight(GAME::Mode mode) {
		switch (jjGameMode) {
			case GAME::BATTLE:
			case GAME::TREASURE:
				return 36;
			case GAME::CTF:
				switch (jjGameCustom) {
					case GAME::DOM:
					case GAME::JB:
					case GAME::TLRS:
						return 80;
					default:
						return 62;
				}
			case GAME::RACE:
				return 62;
			case GAME::COOP:
			case GAME::SP:
				return 16;
		}
		return 0;
	}
	int getSmallFontIntWidth(string text) {
		int width = 0;
		for (uint i = 0; i < text.length(); i++) {
			if(text[i] - 32 < smallFontCharacterWidth.length())
				width += smallFontCharacterWidth[text[i] - 32] + 1;
			else
				width++;
		}
		return width;
	}
}