Downloads containing ab16btl20.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Anniversary Bash 16 Levels Jazz2Online Multiple N/A 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 flak = 32;
	const array<uint8> rocketShard = {200, 248};
}
namespace tileset {
	const uint healthBar = 230;
	const uint ventShaft = 19;
	const uint weaponLarge = 140;
	const uint weaponSmall = 200;
	const uint weaponSmallInactive = 205;
}
namespace trigger {
	enum option {disableSD, friendlyFire, damageAmplifier}
}
namespace weapon {
	const uint8 count = 6;
	const int dispersionPistolRegenerationTime = 80;
	const int displayTime = 40;
	const array<int> fireInterval = {0, 25, 5, 40, 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};
}
const int blinkingTime = 40;
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 (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 admin = false;
array<TlocalPlayer> localPlayer(jjLocalPlayerCount);
array<Itile@> specialTile;
bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	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 (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 (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) {
	sprite::drawQueue(canvas, 2);
}
void onDrawLayer4(jjPLAYER@, jjCANVAS@ canvas) {
	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(obj.xPos, obj.yPos, jjObjectPresets[obj.var[3]].curFrame, obj.direction, SPRITE::SINGLECOLOR, 79);
	}
	sprite::drawQueue(canvas, 3);
}
bool onDrawLives(jjPLAYER@ player, jjCANVAS@ canvas) {
	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;
}
void onLevelReload() {
	initialize::palette();
}
void onMain() {
	for (int i = 0; i < 32; i++) {
		jjPLAYER@ player = jjPlayers[i];
		if (player.blink < -blinkingTime)
			player.blink = -blinkingTime;
	}
	for (uint i = 0; i < specialTile.length(); i++) {
		specialTile[i].step();
	}
	if ((jjIsServer || jjIsAdmin) && !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");
		admin = true;
	}
}
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 = 0;
	player.fastfire = weapon::fireInterval[player.currWeapon];
	TlocalPlayer@ thisPlayer = 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++;
}
void onPlayerInput(jjPLAYER@ player) {
	TlocalPlayer@ thisPlayer = 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(obj.xPos, obj.yPos)){
			explosion::asmdRing(obj.xPos, obj.yPos, obj.xSpeed, obj.ySpeed, 6, 2);
			explosion::harmfulExplosion(obj.xPos, obj.yPos, 32, 2, obj.creatorType == CREATOR::PLAYER ? jjPlayers[obj.creatorID] : null);
		}
	}
	void barrel(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				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:
				{
					int ID = jjAddObject(OBJECT::BULLET, obj.xPos, obj.yPos, obj.special, CREATOR::PLAYER, 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:
				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, obj.xSpeed);
	}
	void delete(jjOBJ@ obj) {
		if (++obj.age > 3)
			obj.delete();
	}
	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 + 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(obj.xPos, obj.yPos)) {
			if (jjRandom() & 3 == 0)
				jjSample(obj.xPos, obj.yPos, SOUND::COMMON_CUP);
			obj.counter += 5;
			obj.xPos = x;
			obj.xSpeed = -obj.xSpeed;
			obj.xAcc = -obj.xAcc;
		}
		float y = obj.yPos;
		obj.yPos = obj.yPos + obj.ySpeed;
		if (jjMaskedPixel(obj.xPos, obj.yPos)) {
			obj.counter += 5;
			obj.yPos = y;
			obj.ySpeed = -obj.ySpeed;
		}
		if (jjGameTicks % 7 == 0) {
			obj.frameID++;
			obj.determineCurFrame();
		}
		sprite::add(obj.xPos, obj.yPos, obj.curFrame, 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(obj.xPos, 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(obj.xPos, 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, obj.xSpeed);
	}
	void pickup(jjOBJ@ obj) {
		switch (obj.state) {
			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);
	}
	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(obj.xPos, obj.yPos))
			obj.state = STATE::EXPLODE;
		if (jjGameTicks % 7 == 0) {
			obj.frameID++;
			obj.determineCurFrame();
		}
		sprite::add(obj.xPos, obj.yPos, obj.curFrame, obj.xSpeed);
	}
	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]);
	}
}
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;
		obj.state = STATE::ACTION;
	}
	void barrel(jjOBJ@ obj, jjOBJ@ bull) {
		if (obj.state == STATE::ACTION || obj.state == STATE::EXTRA)
			return;
		if (bull.eventID == OBJECT::ICEBULLET) {
			if (obj.var[5] == bull.var[5])
				return;
			obj.var[5] = bull.var[5];
		} else {
			bull.state = STATE::EXPLODE;
		}
		if (obj.energy > bull.animSpeed)
			obj.energy -= bull.animSpeed;
		else
			obj.state = STATE::EXTRA;
		if (bull.creatorType == CREATOR::PLAYER)
			obj.special = bull.creatorID;
	}
	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 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;
	}
}
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 = 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 = 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) {
		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, creator);
		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::LEVEL;
		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, jjPLAYER@ creator) {
		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);
		}
	}
	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) {
		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, creator);
		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) {
		int ID = jjAddObject(OBJECT::EXPLOSION, x, y);
		if (ID != 0) {
			jjOBJ@ obj = jjObjects[ID];
			obj.determineCurAnim(setID, animation);
			obj.age = scale;
		}
	}
}
namespace initialize {
	void objectPresets() {
		jjOBJ@ preset;
		@preset = jjObjectPresets[OBJECT::BLASTERBULLET];
		preset.animSpeed = 4;
		preset.behavior = behavior::bullet;
		preset.bulletHandling = HANDLING::IGNOREBULLET;
		preset.counterEnd = 50;
		preset.doesHurt = 1;
		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 = 2;
		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.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 = jjObjectPresets[OBJECT::EXPLOSION];
		preset.behavior = behavior::explosion;
	}
	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:
						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 = 16;
		weapon.multiplier = 1;
		weapon.replenishes = false;
		weapon.style = WEAPON::NORMAL;
		@weapon = jjWeapons[WEAPON::TOASTER];
		weapon.infinite = false;
		weapon.maximum = 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 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) {
		Tsprite sprite;
		sprite.x = floor(x);
		sprite.y = 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:
			case GAME::RACE:
				return 72;
			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;
	}
}