Downloads containing charsMonsters - bk.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Characters Cranky Mutator N/A Download file

File preview

bool MONSTERS_ON = true;
bool OBJECTS_ENABLED = true;
bool MONSTER_HATS_ENABLED = false;
bool OBJECTS_HATS_ENABLED = false;
/// <summary>
/// Custom special moves, not really implemented yet, so disabled
/// </summary>
bool MONSTER_SPECIAL_MOVE_ON = false; 

namespace CharsMonsters
{
	const int SPECIAL_NONE = 0;
	const int SPECIAL_CHARGE = 1;
	const int SPECIAL_QUEEN_WIND = 2;
	const int SPECIAL_BUBBA_SPIN = 3;
	const int SPECIAL_TURTLE_BITE = 4;
	const int SPECIAL_PROJECTILE = 5;
	const int SHOOTING_FRAME_TICKS = 8;
	const int SPECIAL_FRAME_TICKS = 8;
	const int SPECIAL_HOLD_TICKS = 6;
	const int DEFAULT_ANIM_SPEED = 8;

	class MonsterDef
	{
		string name;
		string aliases;
		int eventID;
		int animSet;
		int idleAnim;
		int idleFrameCount;
		int idleXOffset;
		int idleYOffset;
		int walkingAnim;
		int walkingFrameCount;
		int walkingXOffset;
		int walkingYOffset;
		int shootingAnim;
		int shootingFrameCount;
		int shootingXOffset;
		int shootingYOffset;
		int specialAnim;
		int specialFrameCount;
		int specialXOffset;
		int specialYOffset;
		int crouchingAnim;
		int crouchingFrameCount;
		int crouchingXOffset;
		int crouchingYOffset;
		int specialType;
		bool canFly;
		float scale;
		int offsetY;

		// Extra upward name placement in unscaled pixels. Leave at 0 for the
		// normal name position; taller monsters can opt in per definition.
		int nameRaise;

		int cooldown;
		int direction;
		int idleSpeed;
		int walkingSpeed;
		int shootingSpeed;
		int specialSpeed;
		int crouchingSpeed;
		int rotation;
		bool isObject;
		bool crouchAfterSpecial;
	}

	array<MonsterDef> monsters;
	array<int> selectedMonster(32, -1);
	array<int> specialUntil(32, 0);
	array<int> specialStarted(32, 0);
	array<int> specialEffectUntil(32, 0);
	array<bool> specialMoveWasActive(32, false);
	array<int> shootingUntil(32, 0);
	array<int> shootingStarted(32, 0);
	array<int> specialCooldownUntil(32, 0);
	array<bool> shootingQueued(32, false);
	array<bool> gaveMonsterFlight(32, false);
	array<bool> fireWasPressed(4, false);
	array<bool> crouchWasDown(32, false);
	array<bool> crouchReleasing(32, false);
	array<int> crouchStarted(32, 0);
	array<int> crouchReleaseFrame(32, 0);
	array<bool> specialVisualWasActive(32, false);
	bool transmitAll = false;
	bool transmitServer = false;
	const int MONSTER_STATE_RESYNC_INTERVAL = 700;
	int lastMonsterStateResyncTick = -MONSTER_STATE_RESYNC_INTERVAL;

	void AddMonster(
		const string &in name, const string &in aliases, int eventID, int animSet,
		int idleAnim, int idleFrameCount, int idleXOffset, int idleYOffset,
		int walkingAnim, int walkingFrameCount, int walkingXOffset, int walkingYOffset,
		int shootingAnim, int shootingFrameCount, int shootingXOffset, int shootingYOffset,
		int specialAnim, int specialFrameCount, int specialXOffset, int specialYOffset,
		int specialType, bool canFly, float scale = 1.0, int offsetY = 0,
		int cooldown = 70, int direction = 1,
		int idleSpeed = 8, int walkingSpeed = 8,
		int shootingSpeed = 8, int specialSpeed = 8,
		int rotation = 0,
		bool isObject = false,
		int nameRaise = 0,
		int crouchingAnim = -1, int crouchingFrameCount = 0, int crouchingXOffset = 0, int crouchingYOffset = 0,
		int crouchingSpeed = 8,
		bool crouchAfterSpecial = false
	)
	{
		MonsterDef def;
		def.name = name;
		def.aliases = aliases;
		def.eventID = eventID;
		def.animSet = animSet;
		def.idleAnim = idleAnim;
		def.idleFrameCount = idleFrameCount;
		def.idleXOffset = idleXOffset;
		def.idleYOffset = idleYOffset;
		def.walkingAnim = walkingAnim;
		def.walkingFrameCount = walkingFrameCount;
		def.walkingXOffset = walkingXOffset;
		def.walkingYOffset = walkingYOffset;
		def.shootingAnim = shootingAnim;
		def.shootingFrameCount = shootingFrameCount;
		def.shootingXOffset = shootingXOffset;
		def.shootingYOffset = shootingYOffset;
		def.specialAnim = specialAnim;
		def.specialFrameCount = specialFrameCount;
		def.specialXOffset = specialXOffset;
		def.specialYOffset = specialYOffset;
		def.crouchingAnim = crouchingAnim;
		def.crouchingFrameCount = crouchingFrameCount;
		def.crouchingXOffset = crouchingXOffset;
		def.crouchingYOffset = crouchingYOffset;
		def.specialType = specialType;
		def.canFly = canFly;
		def.scale = scale;
		def.offsetY = offsetY;
		def.nameRaise = nameRaise;
		def.cooldown = cooldown;
		def.direction = direction;
		def.idleSpeed = idleSpeed;
		def.walkingSpeed = walkingSpeed;
		def.shootingSpeed = shootingSpeed;
		def.specialSpeed = specialSpeed;
		def.crouchingSpeed = crouchingSpeed;
		def.rotation = rotation;
		def.isObject = isObject;
		def.crouchAfterSpecial = crouchAfterSpecial;
		monsters.insertLast(def);
	}

	void AddObject(
		const string &in name, const string &in aliases, int eventID, int animSet,
		int anim, int frameCount, int xOffset, int yOffset,
		float scale = 1.0, int direction = 1, int speed = 8,
		int nameRaise = 0
	)
	{
		AddMonster(
			name: name, aliases: aliases, eventID: eventID, animSet: animSet,
			idleAnim: anim, idleFrameCount: frameCount, idleXOffset: xOffset, idleYOffset: yOffset,
			walkingAnim: anim, walkingFrameCount: frameCount, walkingXOffset: xOffset, walkingYOffset: yOffset,
			shootingAnim: anim, shootingFrameCount: frameCount, shootingXOffset: xOffset, shootingYOffset: yOffset,
			specialAnim: anim, specialFrameCount: frameCount, specialXOffset: xOffset, specialYOffset: yOffset,
			specialType: SPECIAL_NONE, canFly: false, scale: scale, offsetY: 0,
			cooldown: 70, direction: direction,
			idleSpeed: speed, walkingSpeed: speed, shootingSpeed: speed, specialSpeed: speed,
			rotation: 0, isObject: true, nameRaise: nameRaise,
			crouchingAnim: -1, crouchingFrameCount: 0, crouchingXOffset: 0, crouchingYOffset: 0, crouchingSpeed: speed,
			crouchAfterSpecial: false
		);
	}

	void InitializeMonsterTable()
	{
		if(monsters.length > 0) return;

		// Monster display is controlled here. Each entry has five animation groups:
		// idle, walking, shooting, special, and optional crouching. Every group has
		// anim, frame count, x offset, and y offset. Optional speed values replace
		// the jjGameTicks divisor directly. Leave crouchingAnim negative or
		// crouchingFrameCount <= 0 to use idle while pressing down.
		AddMonster(
			name: "bat", aliases: "bat,bats", eventID: OBJECT::BAT, animSet: ANIM::BAT,
			idleAnim: 1, idleFrameCount: 1, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 8, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "bee", aliases: "bee,bumbee", eventID: OBJECT::BEE, animSet: ANIM::BUMBEE,
			idleAnim:  0, idleFrameCount: 2, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 2, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 2, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 2, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "beeboy", aliases: "beeboy,bee boy", eventID: OBJECT::BEEBOY, animSet: ANIM::BEEBOY,
			idleAnim:  0, idleFrameCount: 4, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 4, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 4, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 4, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "bees", aliases: "bees,swarm", eventID: OBJECT::BEES, animSet: ANIM::BEES,
			idleAnim:  0, idleFrameCount: 4, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 4, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 4, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 4, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "butterfly", aliases: "butterfly,evilbutterfly", eventID: OBJECT::BUTTERFLY, animSet: ANIM::BUTTERFLY,
			idleAnim:  0, idleFrameCount: 12, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 12, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 12, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 12, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "crab", aliases: "crab", eventID: OBJECT::CRAB, animSet: ANIM::UTERUS,
			idleAnim: 7, idleFrameCount: 8, idleXOffset: 0, idleYOffset: 4,
			walkingAnim: 7, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 4,
			shootingAnim: 7, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: 4,
			specialAnim: 7, specialFrameCount: 8, specialXOffset: 0, specialYOffset: 4,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "demon", aliases: "demon", eventID: OBJECT::DEMON, animSet: ANIM::DEMON,
			idleAnim: 0, idleFrameCount: 15, idleXOffset:  0, idleYOffset: 2,
			walkingAnim: 2, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 2,
			shootingAnim: 2, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: 2,
			specialAnim: 2, specialFrameCount: 8, specialXOffset: 0, specialYOffset: 2,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "doggydogg", aliases: "doggydogg,dog,doggy", eventID: OBJECT::DOGGYDOGG, animSet: ANIM::DOG,
			idleAnim: 1, idleFrameCount: 8, idleXOffset:  0, idleYOffset: 4,
			walkingAnim: 1, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 4,
			shootingAnim: 0, shootingFrameCount: 5, shootingXOffset: 0, shootingYOffset: 4,
			specialAnim: 0, specialFrameCount: 5, specialXOffset: 0, specialYOffset: 4,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "dragon", aliases: "dragon", eventID: OBJECT::DRAGON, animSet: ANIM::DRAGON,
			idleAnim: 1, idleFrameCount: 1, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 2, walkingFrameCount: 3, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 8, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_PROJECTILE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "dragonfly", aliases: "dragonfly,dragfly", eventID: OBJECT::DRAGONFLY, animSet: ANIM::DRAGFLY,
			idleAnim:  0, idleFrameCount: 4, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 4, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 4, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 4, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "fatchick", aliases: "fatchick,fat chick", eventID: OBJECT::FATCHICK, animSet: ANIM::FATCHK,
			idleAnim: 1, idleFrameCount: 12, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 1, walkingFrameCount: 12, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 12, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 12, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "fencer", aliases: "fencer", eventID: OBJECT::FENCER, animSet: ANIM::FENCER,
			idleAnim: 1, idleFrameCount: 13, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 5, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 5, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 5, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1, idleSpeed: 5
		);

		AddMonster(
			name: "fish", aliases: "fish", eventID: OBJECT::FISH, animSet: ANIM::FISH,
			idleAnim: 1, idleFrameCount: 6, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 1, walkingFrameCount: 6, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 6, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 6, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "hatter", aliases: "hatter", eventID: OBJECT::HATTER, animSet: ANIM::HATTER,
			idleAnim: 4, idleFrameCount: 12, idleXOffset:  0, idleYOffset: -8,
			walkingAnim: 4, walkingFrameCount: 12, walkingXOffset: 0, walkingYOffset: -8,
			shootingAnim: 2, shootingFrameCount: 22, shootingXOffset: 0, shootingYOffset: -8,
			specialAnim: 2, specialFrameCount: 22, specialXOffset: 0, specialYOffset: -8,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "helmut", aliases: "helmut", eventID: OBJECT::HELMUT, animSet: ANIM::HELMUT,
			idleAnim:  0, idleFrameCount: 4, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 1, walkingFrameCount: 10, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 1, shootingFrameCount: 10, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 4, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "labrat", aliases: "labrat,rat", eventID: OBJECT::LABRAT, animSet: ANIM::LABRAT,
			idleAnim: 1, idleFrameCount: 8, idleXOffset:  0, idleYOffset: 4,
			walkingAnim: 2, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 4,
			shootingAnim: 0, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: 4,
			specialAnim: 0, specialFrameCount: 8, specialXOffset: 0, specialYOffset: 4,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "floatlizard", aliases: "floatlizard,flyinglizard", eventID: OBJECT::FLOATLIZARD, animSet: ANIM::LIZARD,
			idleAnim: 2, idleFrameCount: 4, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 2, walkingFrameCount: 4, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 13, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 13, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "xmaslizard", aliases: "xmaslizard,christmaslizard", eventID: OBJECT::XMASLIZARD, animSet: ANIM::XLIZARD,
			idleAnim: 4, idleFrameCount: 12, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 4, walkingFrameCount: 12, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 4, shootingFrameCount: 12, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 4, specialFrameCount: 12, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "lizard", aliases: "lizard", eventID: OBJECT::LIZARD, animSet: ANIM::LIZARD,
			idleAnim: 4, idleFrameCount: 12, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 4, walkingFrameCount: 12, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 4, shootingFrameCount: 12, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 4, specialFrameCount: 12, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "standmonkey", aliases: "standmonkey,standingmonkey", eventID: OBJECT::STANDMONKEY, animSet: ANIM::MONKEY,
			idleAnim: 2, idleFrameCount: 13, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 6, walkingFrameCount: 14, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 5, shootingFrameCount: 13, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 5, specialFrameCount: 13, specialXOffset: 0, specialYOffset: 0,
			crouchingAnim: 4, crouchingFrameCount: 4, crouchingXOffset: 0, crouchingYOffset: 0,
			specialType: SPECIAL_PROJECTILE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "monkey", aliases: "monkey", eventID: OBJECT::MONKEY, animSet: ANIM::MONKEY,
			idleAnim: 2, idleFrameCount: 13, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 6, walkingFrameCount: 14, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 5, shootingFrameCount: 13, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 5, specialFrameCount: 13, specialXOffset: 0, specialYOffset: 0,
			crouchingAnim: 4, crouchingFrameCount: 4, crouchingXOffset: 0, crouchingYOffset: 0,
			specialType: SPECIAL_PROJECTILE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "normturtle", aliases: "normturtle,normalturtle,turtle", eventID: OBJECT::NORMTURTLE, animSet: ANIM::TURTLE,
			idleAnim: 1, idleFrameCount: 11, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 7, walkingFrameCount: 12, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 11, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 5, specialFrameCount: 1, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_TURTLE_BITE, canFly: false, scale: 1.0, offsetY: 0,
			crouchingAnim: 2, crouchingFrameCount: 7, crouchingXOffset: 0, crouchingYOffset: 0,
			cooldown: 70, direction: 1, crouchAfterSpecial: true
		);
		
		AddMonster(
			name: "xmasnormturtle", aliases: "xmasnormturtle,xmasturtle,christmasturtle,xmasnormalturtle", eventID: OBJECT::XMASNORMTURTLE, animSet: ANIM::XTURTLE,
			idleAnim: 1, idleFrameCount: 11, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 7, walkingFrameCount: 12, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 11, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 5, specialFrameCount: 1, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_TURTLE_BITE, canFly: false, scale: 1.0, offsetY: 0,
			crouchingAnim: 2, crouchingFrameCount: 7, crouchingXOffset: 0, crouchingYOffset: 0,
			cooldown: 70, direction: 1, crouchAfterSpecial: true
		);

		AddMonster(
			name: "tubeturtle", aliases: "tubeturtle,tube turtle", eventID: OBJECT::TUBETURTLE, animSet: ANIM::TUBETURT,
			idleAnim: 0, idleFrameCount: 8, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 8, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_PROJECTILE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "tufturtle", aliases: "tufturtle,tufturt,tuf turtle", eventID: OBJECT::TUFTURT, animSet: ANIM::TUFTUR,
			idleAnim: 0, idleFrameCount: 10, idleXOffset:  0, idleYOffset: -8,
			walkingAnim: 0, walkingFrameCount: 10, walkingXOffset: 0, walkingYOffset: -8,
			shootingAnim: 0, shootingFrameCount: 10, shootingXOffset: 0, shootingYOffset: -8,
			specialAnim: 0, specialFrameCount: 10, specialXOffset: 0, specialYOffset: -8,
			specialType: SPECIAL_TURTLE_BITE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "rapier", aliases: "rapier,ghost", eventID: OBJECT::RAPIER, animSet: ANIM::RAPIER,
			idleAnim: 2, idleFrameCount: 8, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 1, specialFrameCount: 16, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "raven", aliases: "raven", eventID: OBJECT::RAVEN, animSet: ANIM::RAVEN,
			idleAnim: 1, idleFrameCount: 6, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 1, walkingFrameCount: 6, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 2, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 2, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "skeleton", aliases: "skeleton", eventID: OBJECT::SKELETON, animSet: ANIM::SKELETON,
			idleAnim: 2, idleFrameCount: 16, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 2, walkingFrameCount: 16, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 2, shootingFrameCount: 16, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 2, specialFrameCount: 16, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "spark", aliases: "spark", eventID: OBJECT::SPARK, animSet: ANIM::SPARK,
			idleAnim: 0, idleFrameCount: 5, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 5, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 5, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 5, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: -1
		);

		AddMonster(
			name: "floatsucker", aliases: "floatsucker,floating sucker", eventID: OBJECT::FLOATSUCKER, animSet: ANIM::SUCKER,
			idleAnim: 4, idleFrameCount: 8, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 4, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 4, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 4, specialFrameCount: 8, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "sucker", aliases: "sucker", eventID: OBJECT::SUCKER, animSet: ANIM::SUCKER,
			idleAnim: 6, idleFrameCount: 12, idleXOffset:  0, idleYOffset: 10,
			walkingAnim: 6, walkingFrameCount: 12, walkingXOffset: 0, walkingYOffset: 10,
			shootingAnim: 6, shootingFrameCount: 12, shootingXOffset: 0, shootingYOffset: 10,
			specialAnim: 6, specialFrameCount: 12, specialXOffset: 0, specialYOffset: 10,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "cat", aliases: "cat", eventID: OBJECT::CAT, animSet: ANIM::ZDOG,
			idleAnim: 1, idleFrameCount: 8, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 1, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 5, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 5, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "pacmanghost", aliases: "pacmanghost,pacman,ghost", eventID: OBJECT::PACMANGHOST, animSet: ANIM::ZSPARK,
			idleAnim: 0, idleFrameCount: 5, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 5, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 5, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 5, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: -1
		);

		AddMonster(
			name: "moth", aliases: "moth", eventID: OBJECT::MOTH, animSet: ANIM::MOTH,
			idleAnim:  0, idleFrameCount: 8, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 0, specialFrameCount: 8, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 70, direction: 1
		);

		AddMonster(
			name: "xmasbilsy", aliases: "xmasbilsy,christmasbilsy", eventID: OBJECT::XMASBILSY, animSet: ANIM::XBILSY,
			idleAnim: 4, idleFrameCount: 8, idleXOffset:  0, idleYOffset: -16,
			walkingAnim: 4, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: -16,
			shootingAnim: 0, shootingFrameCount: 18, shootingXOffset: 0, shootingYOffset: -16,
			specialAnim: 2, specialFrameCount: 17, specialXOffset: 0, specialYOffset: -16,
			crouchingAnim: 2, crouchingFrameCount: 17, crouchingXOffset: 0, crouchingYOffset: -16,
			specialType: SPECIAL_PROJECTILE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 90, direction: 1
		);

		AddMonster(
			name: "bilsy", aliases: "bilsy", eventID: OBJECT::BILSY, animSet: ANIM::BILSBOSS,
			idleAnim: 4, idleFrameCount: 8, idleXOffset:  0, idleYOffset: -16,
			walkingAnim: 4, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: -16,
			shootingAnim: 0, shootingFrameCount: 18, shootingXOffset: 0, shootingYOffset: -16,
			specialAnim: 2, specialFrameCount: 17, specialXOffset: 0, specialYOffset: -16,
			crouchingAnim: 2, crouchingFrameCount: 17, crouchingXOffset: 0, crouchingYOffset: -16,
			specialType: SPECIAL_PROJECTILE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 90, direction: 1
		);

		AddMonster(
			name: "bolly", aliases: "bolly", eventID: OBJECT::BOLLY, animSet: ANIM::BOSS,
			idleAnim:  1, idleFrameCount: 8, idleXOffset:  0, idleYOffset: -16,
			walkingAnim: 1, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: -16,
			shootingAnim: 1, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: -16,
			specialAnim: 1, specialFrameCount: 8, specialXOffset: 0, specialYOffset: -16,
			specialType: SPECIAL_CHARGE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 90, direction: 1
		);

		AddMonster(
			name: "bubba", aliases: "bubba", eventID: OBJECT::BUBBA, animSet: ANIM::BUBBA,
			idleAnim: 1, idleFrameCount: 4, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 2, walkingFrameCount: 9, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 16, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 6, specialFrameCount: 12, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_BUBBA_SPIN, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 95, direction: 1
		);

		AddMonster(
			name: "queen", aliases: "queen", eventID: OBJECT::QUEEN, animSet: ANIM::QUEEN,
			idleAnim: 3, idleFrameCount: 9, idleXOffset:  0, idleYOffset: -4,
			walkingAnim: 5, walkingFrameCount: 4, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 0, shootingFrameCount: 16, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 6, specialFrameCount: 12, specialXOffset: 0, specialYOffset: 0,
			crouchingAnim: 1, crouchingFrameCount: 23, crouchingXOffset: 0, crouchingYOffset: 0,
			specialType: SPECIAL_QUEEN_WIND, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 120, direction: -1
		);

		AddMonster(
			name: "robot", aliases: "robot", eventID: OBJECT::ROBOT, animSet: ANIM::ROBOT,
			idleAnim: 4, idleFrameCount: 4, idleXOffset:  0, idleYOffset: -8,
			walkingAnim: 15, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: -8,
			shootingAnim: 2, shootingFrameCount: 6, shootingXOffset: 0, shootingYOffset: -8,
			specialAnim: 3, specialFrameCount: 4, specialXOffset: 0, specialYOffset: -8,
			crouchingAnim: 17, crouchingFrameCount: 11, crouchingXOffset: 0, crouchingYOffset: -8,
			specialType: SPECIAL_PROJECTILE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 90, direction: 1
		);

		AddMonster(
			name: "rocketturtle", aliases: "rocketturtle,rocket turtle", eventID: OBJECT::ROCKETTURTLE, animSet: ANIM::ROCKTURT,
			idleAnim: 0, idleFrameCount: 6, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 6, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 1, shootingFrameCount: 6, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 3, specialFrameCount: 6, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_PROJECTILE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 90, direction: 1
		);

		AddMonster(
			name: "tufboss", aliases: "tufboss,turtleboss,schwarzenguard", eventID: OBJECT::TUFBOSS, animSet: ANIM::TUFBOSS,
			idleAnim: 4, idleFrameCount: 4, idleXOffset:  0, idleYOffset: -4,
			walkingAnim: 5, walkingFrameCount: 11, walkingXOffset: 0, walkingYOffset: -4,
			shootingAnim: 0, shootingFrameCount: 19, shootingXOffset: 0, shootingYOffset: -4,
			specialAnim: 0, specialFrameCount: 19, specialXOffset: 0, specialYOffset: -4,
			specialType: SPECIAL_TURTLE_BITE, canFly: false, scale: 1.0, offsetY: 0,
			crouchingAnim: 2, crouchingFrameCount: 1, crouchingXOffset: 0, crouchingYOffset: 4,
			cooldown: 90, direction: 1
		);

		AddMonster(
			name: "tweedle", aliases: "tweedle,tweedleboss", eventID: OBJECT::TWEEDLEBOSS, animSet: ANIM::TWEEDLE,
			idleAnim: 6, idleFrameCount: 11, idleXOffset:  0, idleYOffset: -8,
			walkingAnim: 8, walkingFrameCount: 12, walkingXOffset: 0, walkingYOffset: -8,
			shootingAnim: 3, shootingFrameCount: 15, shootingXOffset: 0, shootingYOffset: -8,
			specialAnim: 1, specialFrameCount: 11, specialXOffset: 0, specialYOffset: -8,
			crouchingAnim: 0, crouchingFrameCount: 10, crouchingXOffset: 0, crouchingYOffset: -8,
			specialType: SPECIAL_CHARGE, canFly: false, scale: 1.0, offsetY: 0,
			cooldown: 90, direction: 1
		);

		AddMonster(
			name: "uterus", aliases: "uterus", eventID: OBJECT::UTERUS, animSet: ANIM::UTERUS,
			idleAnim: 3, idleFrameCount: 8, idleXOffset:  0, idleYOffset: 0,
			walkingAnim: 0, walkingFrameCount: 8, walkingXOffset: 0, walkingYOffset: 0,
			shootingAnim: 3, shootingFrameCount: 8, shootingXOffset: 0, shootingYOffset: 0,
			specialAnim: 3, specialFrameCount: 8, specialXOffset: 0, specialYOffset: 0,
			specialType: SPECIAL_PROJECTILE, canFly: true, scale: 1.0, offsetY: 0,
			cooldown: 90, direction: 1, rotation: 256
		);

		if(OBJECTS_ENABLED)
		{
			int monitorX = 0;
			int monitorY = 0;
			int monitorDir = -1;
			int monitorFrames = 10;
			int monitorSpeed = 8;
			
			AddObject(
				name: "bubbleshield", aliases: "bubbleshield", eventID: OBJECT::MORPH, animSet: ANIM::PICKUPS,
				anim: 10, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);
			
			AddObject(
				name: "fireshield", aliases: "fireshield", eventID: OBJECT::MORPH, animSet: ANIM::PICKUPS,
				anim: 31, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);
			
			AddObject(
				name: "electricshield", aliases: "electricshield", eventID: OBJECT::MORPH, animSet: ANIM::PICKUPS,
				anim: 51, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			AddObject(
				name: "lasershield", aliases: "laser,lasershield,laser shield", eventID: OBJECT::LASERSHIELD, animSet: ANIM::PLUS_COMMON,
				anim: 2, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);
			
			AddObject(
				name: "morphmonitor", aliases: "morph,morphmonitor,morph monitor", eventID: OBJECT::MORPH, animSet: ANIM::PICKUPS,
				anim: 70, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			AddObject(
				name: "birdmonitor", aliases: "bird,birdmonitor,bird monitor,birdmorph,bird morph", eventID: OBJECT::BIRDMORPH, animSet: ANIM::PICKUPS,
				anim: 69, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);
			
			AddObject(
				name: "bird2monitor", aliases: "bird2,bird2monitor,bird2 monitor,bird2morph,bird morph", eventID: OBJECT::BIRDMORPH, animSet: ANIM::PICKUPS,
				anim: 68, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			AddObject(
				name: "blasterpowerup", aliases: "blaster,blasterpowerup,blaster powerup,blaster monitor", eventID: OBJECT::BLASTERPOWERUP, animSet: ANIM::PICKUPS,
				anim: 60, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);
			
			AddObject(
				name: "spazblasterpowerup", aliases: "spazblaster,spazblasterpowerup,spazblaster powerup,spazblaster monitor", eventID: OBJECT::BLASTERPOWERUP, animSet: ANIM::PICKUPS,
				anim: 83, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);
			
			AddObject(
				name: "loriblasterpowerup", aliases: "loriblaster,loriblasterpowerup,loriblaster powerup,loriblaster monitor", eventID: OBJECT::BLASTERPOWERUP, animSet: ANIM::PLUS_COMMON,
				anim: 5, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);
			
			AddObject(
				name: "bombpowerup", aliases: "bombpowerup, bombpu, bomb monitor", eventID: OBJECT::BLASTERPOWERUP, animSet: ANIM::PLUS_COMMON,
				anim: 4, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			AddObject(
				name: "bouncerpowerup", aliases: "bouncer,bouncerpowerup,bouncer powerup,bouncer monitor", eventID: OBJECT::BOUNCERPOWERUP, animSet: ANIM::PICKUPS,
				anim: 61, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			AddObject(
				name: "icepowerup", aliases: "ice,freezer,icepowerup,ice powerup,freezer powerup,ice monitor,freezer monitor", eventID: OBJECT::ICEPOWERUP, animSet: ANIM::PICKUPS,
				anim: 62, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			AddObject(
				name: "seekerpowerup", aliases: "seeker,seekerpowerup,seeker powerup,seeker monitor", eventID: OBJECT::SEEKERPOWERUP, animSet: ANIM::PICKUPS,
				anim: 63, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			AddObject(
				name: "rfpowerup", aliases: "rf,rfpowerup,rf powerup,rf monitor", eventID: OBJECT::RFPOWERUP, animSet: ANIM::PICKUPS,
				anim: 64, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			AddObject(
				name: "toasterpowerup", aliases: "toaster,toasterpowerup,toaster powerup,toaster monitor", eventID: OBJECT::TOASTERPOWERUP, animSet: ANIM::PICKUPS,
				anim: 65, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			AddObject(
				name: "gun8powerup", aliases: "gun8,pepper,pepperpowerup,gun8powerup,gun8 powerup,pepper powerup,gun8 monitor,pepper monitor", eventID: OBJECT::GUN8POWERUP, animSet: ANIM::PICKUPS,
				anim: 66, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			AddObject(
				name: "gun9powerup", aliases: "gun9,electro,electropowerup,gun9powerup,gun9 powerup,electro powerup,gun9 monitor,electro monitor", eventID: OBJECT::GUN9POWERUP, animSet: ANIM::PICKUPS,
				anim: 67, frameCount: monitorFrames, xOffset: monitorX, yOffset: monitorY,
				scale: 1.0, direction: monitorDir, speed: monitorSpeed
			);

			int pickupFrames = 8;
			int pickupSpeed = 5;

			AddObject(
				name: "coin", aliases: "coin,coins,silvercoin,silver coin", eventID: OBJECT::SILVERCOIN, animSet: ANIM::PICKUPS,
				anim: 84, frameCount: 20, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);
			
			AddObject(
				name: "goldcoin", aliases: "goldcoin,goldcoins,gold coin", eventID: OBJECT::GOLDCOIN, animSet: ANIM::PICKUPS,
				anim: 37, frameCount: 20, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			// All four basic gems share PICKUPS animation 22. SPRITE::GEM's parameter
			// chooses the original JJ2 gem palette: 0 red, 1 green, 2 blue, 3 purple.
			AddObject(
				name: "redgem", aliases: "gem,gems,redgem,red gem", eventID: OBJECT::REDGEM, animSet: ANIM::PICKUPS,
				anim: 22, frameCount: pickupFrames, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "greengem", aliases: "greengem,green gem", eventID: OBJECT::GREENGEM, animSet: ANIM::PICKUPS,
				anim: 22, frameCount: pickupFrames, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "bluegem", aliases: "bluegem,blue gem", eventID: OBJECT::BLUEGEM, animSet: ANIM::PICKUPS,
				anim: 22, frameCount: pickupFrames, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "purplegem", aliases: "purplegem,purple gem", eventID: OBJECT::PURPLEGEM, animSet: ANIM::PICKUPS,
				anim: 22, frameCount: pickupFrames, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "fastfire", aliases: "fastfire,fast fire", eventID: OBJECT::FASTFIRE, animSet: ANIM::PICKUPS,
				anim: 87, frameCount: pickupFrames, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "carrot", aliases: "carrot,health", eventID: OBJECT::CARROT, animSet: ANIM::PICKUPS,
				anim: 21, frameCount: pickupFrames, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "airboard", aliases: "airboard,air board,board", eventID: OBJECT::AIRBOARD, animSet: ANIM::PICKUPS,
				anim: 36, frameCount: pickupFrames, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);
			
			AddObject(
				name: "1up", aliases: "1up,oneup,one up,life,extra life", eventID: OBJECT::ONEUP, animSet: ANIM::PICKUPS,
				anim: 0, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);
			
			//FOOD:
			AddObject(
				name: "apple", aliases: "apple,apples", eventID: OBJECT::APPLE, animSet: ANIM::PICKUPS,
				anim: 1, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "banana", aliases: "banana,bananas", eventID: OBJECT::BANANA, animSet: ANIM::PICKUPS,
				anim: 2, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "burger", aliases: "burger,burgers,hamburger,hamburgers", eventID: OBJECT::BURGER, animSet: ANIM::PICKUPS,
				anim: 11, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "cake", aliases: "cake,cakes", eventID: OBJECT::CAKE, animSet: ANIM::PICKUPS,
				anim: 12, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "candy", aliases: "candy,candies,sweet,sweets", eventID: OBJECT::CANDY, animSet: ANIM::PICKUPS,
				anim: 13, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "cheese", aliases: "cheese", eventID: OBJECT::CHEESE, animSet: ANIM::PICKUPS,
				anim: 15, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "cherry", aliases: "cherry,cherries", eventID: OBJECT::CHERRY, animSet: ANIM::PICKUPS,
				anim: 16, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "chickenleg", aliases: "chickenleg,chicken leg", eventID: OBJECT::CHICKENLEG, animSet: ANIM::PICKUPS,
				anim: 17, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "chips", aliases: "chips", eventID: OBJECT::CHIPS, animSet: ANIM::PICKUPS,
				anim: 18, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "chocbar", aliases: "chocbar,chocolate,chocolatebar,chocolate bar", eventID: OBJECT::CHOCBAR, animSet: ANIM::PICKUPS,
				anim: 19, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "coke", aliases: "coke,sodapop,soda pop", eventID: OBJECT::COKE, animSet: ANIM::PICKUPS,
				anim: 20, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "cucumber", aliases: "cucumber,cucumbers", eventID: OBJECT::CUCUMBER, animSet: ANIM::PICKUPS,
				anim: 23, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "cupcake", aliases: "cupcake,cupcakes", eventID: OBJECT::CUPCAKE, animSet: ANIM::PICKUPS,
				anim: 24, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "donut", aliases: "donut,donuts,doughnut,doughnuts", eventID: OBJECT::DONUT, animSet: ANIM::PICKUPS,
				anim: 25, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "eggplant", aliases: "eggplant,eggplants", eventID: OBJECT::EGGPLANT, animSet: ANIM::PICKUPS,
				anim: 26, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "fries", aliases: "fries,french fries", eventID: OBJECT::FRIES, animSet: ANIM::PICKUPS,
				anim: 32, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "grapes", aliases: "grape,grapes", eventID: OBJECT::GRAPES, animSet: ANIM::PICKUPS,
				anim: 38, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "ham", aliases: "ham", eventID: OBJECT::HAM, animSet: ANIM::PICKUPS,
				anim: 39, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "icecream", aliases: "icecream,ice cream", eventID: OBJECT::ICECREAM, animSet: ANIM::PICKUPS,
				anim: 43, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "lemon", aliases: "lemon,lemons", eventID: OBJECT::LEMON, animSet: ANIM::PICKUPS,
				anim: 48, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "lettuce", aliases: "lettuce", eventID: OBJECT::LETTUCE, animSet: ANIM::PICKUPS,
				anim: 49, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "lime", aliases: "lime,limes", eventID: OBJECT::LIME, animSet: ANIM::PICKUPS,
				anim: 50, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "milk", aliases: "milk", eventID: OBJECT::MILK, animSet: ANIM::PICKUPS,
				anim: 53, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "orange", aliases: "orange,oranges", eventID: OBJECT::ORANGE, animSet: ANIM::PICKUPS,
				anim: 71, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "peach", aliases: "peach,peaches", eventID: OBJECT::PEACH, animSet: ANIM::PICKUPS,
				anim: 73, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "pear", aliases: "pear,pears", eventID: OBJECT::PEAR, animSet: ANIM::PICKUPS,
				anim: 74, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "pepsi", aliases: "pepsi,softdrink,soft drink", eventID: OBJECT::PEPSI, animSet: ANIM::PICKUPS,
				anim: 75, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "pie", aliases: "pie,pies", eventID: OBJECT::PIE, animSet: ANIM::PICKUPS,
				anim: 76, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "pizza", aliases: "pizza,pizzas", eventID: OBJECT::PIZZA, animSet: ANIM::PICKUPS,
				anim: 77, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "pretzel", aliases: "pretzel,pretzels", eventID: OBJECT::PRETZEL, animSet: ANIM::PICKUPS,
				anim: 79, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "sandwich", aliases: "sandwich,sandwiches", eventID: OBJECT::SANDWICH, animSet: ANIM::PICKUPS,
				anim: 80, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "strawberry", aliases: "strawberry,strawberries", eventID: OBJECT::STRAWBERRY, animSet: ANIM::PICKUPS,
				anim: 81, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "taco", aliases: "taco,tacos", eventID: OBJECT::TACO, animSet: ANIM::PICKUPS,
				anim: 88, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "weenie", aliases: "weenie,hotdog,hot dog", eventID: OBJECT::WEENIE, animSet: ANIM::PICKUPS,
				anim: 91, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);

			AddObject(
				name: "watermelon", aliases: "watermelon,watermelons", eventID: OBJECT::WATERMELON, animSet: ANIM::PICKUPS,
				anim: 92, frameCount: 1, xOffset: 0, yOffset: 0,
				scale: 1.0, direction: 1, speed: pickupSpeed
			);
		}
	}

	void onLevelLoad()
	{
		transmitAll = false;
		transmitServer = false;
		if(MONSTERS_ON) InitializeMonsterTable();
	}

	bool IsActive(int playerID)
	{
		return MONSTERS_ON && playerID >= 0 && playerID < int(selectedMonster.length) && selectedMonster[playerID] >= 0;
	}

	bool IsObjectActive(int playerID)
	{
		if(!IsActive(playerID)) return false;
		int index = selectedMonster[playerID];
		return index >= 0 && index < int(monsters.length) && monsters[index].isObject;
	}

	bool ShouldSuppressHats(int playerID)
	{
		if(!IsActive(playerID)) return false;
		return IsObjectActive(playerID) ? !OBJECTS_HATS_ENABLED : !MONSTER_HATS_ENABLED;
	}

	bool IsLocalMonsterActive()
	{
		for(int i = 0; i < jjLocalPlayerCount; i++)
			if(IsActive(jjLocalPlayers[i].playerID))
				return true;
		return false;
	}

	void LoadMonsterAnim(int index)
	{
		if(index < 0 || index >= int(monsters.length)) return;
		int animSet = monsters[index].animSet;
		if(animSet >= 0 && jjAnimSets[animSet].firstAnim == 0)
			jjAnimSets[animSet].load();
	}

	int FindEntry(const string &in value, bool objectEntry)
	{
		InitializeMonsterTable();
		for(uint i = 0; i < monsters.length; i++)
		{
			if(monsters[i].isObject != objectEntry) continue;
			array<string> aliases = monsters[i].aliases.split(",");
			for(uint j = 0; j < aliases.length; j++)
				if(jjRegexMatch(value, "^" + aliases[j] + "$", true))
					return int(i);
		}
		return -1;
	}

	int FindMonster(const string &in value)
	{
		return FindEntry(value, false);
	}

	int FindObject(const string &in value)
	{
		if(!OBJECTS_ENABLED) return -1;
		return FindEntry(value, true);
	}

	bool CanDrawMonster(int playerID)
	{
		if(CharsNyanRush::ShouldSuppressVisuals(playerID)) return false;
		if(!IsActive(playerID)) return false;
		int index = selectedMonster[playerID];
		if(index < 0 || index >= int(monsters.length)) return false;
		LoadMonsterAnim(index);
		return monsters[index].animSet >= 0 && jjAnimSets[monsters[index].animSet].firstAnim != 0;
	}

	void ClearMonster(int playerID, bool transmit = true)
	{
		if(playerID < 0 || playerID >= 32) return;
		CharsNew::SetExternalCharacterVisualSuppressed(playerID, false);
		CharsNew::ClearCharacterVisualReasons(playerID);
		selectedMonster[playerID] = -1;
		specialUntil[playerID] = 0;
		specialStarted[playerID] = 0;
		specialEffectUntil[playerID] = 0;
		specialMoveWasActive[playerID] = false;
		shootingUntil[playerID] = 0;
		shootingStarted[playerID] = 0;
		shootingQueued[playerID] = false;
		crouchWasDown[playerID] = false;
		crouchReleasing[playerID] = false;
		crouchStarted[playerID] = 0;
		crouchReleaseFrame[playerID] = 0;
		specialVisualWasActive[playerID] = false;
		if(gaveMonsterFlight[playerID] && jjPlayers[playerID].isActive)
			jjPlayers[playerID].fly = FLIGHT::NONE;
		gaveMonsterFlight[playerID] = false;
		if(transmit) SendMonster(playerID);
	}

	void ApplyMonster(int playerID, int index)
	{
		InitializeMonsterTable();
		if(playerID < 0 || playerID >= 32 || index < 0 || index >= int(monsters.length)) return;
		LoadMonsterAnim(index);
		ClearCharsNewCharacter(playerID);
		CharsNew::ClearCharacterVisualReasons(playerID);
		CharsNew::SetExternalCharacterVisualSuppressed(playerID, true);
		if(gaveMonsterFlight[playerID] && !monsters[index].canFly && jjPlayers[playerID].isActive)
			jjPlayers[playerID].fly = FLIGHT::NONE;
		gaveMonsterFlight[playerID] = false;
		selectedMonster[playerID] = index;
		specialUntil[playerID] = 0;
		specialStarted[playerID] = 0;
		specialEffectUntil[playerID] = 0;
		specialMoveWasActive[playerID] = false;
		shootingUntil[playerID] = 0;
		shootingStarted[playerID] = 0;
		shootingQueued[playerID] = false;
		crouchWasDown[playerID] = false;
		crouchReleasing[playerID] = false;
		crouchStarted[playerID] = 0;
		crouchReleaseFrame[playerID] = 0;
		specialVisualWasActive[playerID] = false;
	}

	void SetMonster(int playerID, int index, bool transmit = true)
	{
		ApplyMonster(playerID, index);
		if(transmit) SendMonster(playerID);
	}

	void ClearCharsNewCharacter(int playerID)
	{
		if(playerID < 0 || playerID >= 32) return;
		CharsNew::characters[playerID] = CharsNew::CharAnim::SPAZ;
		CharsNew::lastCharacter[playerID] = CharsNew::CharAnim::SPAZ;
	}

	void SendMonster(int playerID, int toClientID = 0)
	{
		if(playerID < 0 || playerID >= 32) return;
		jjSTREAM packet;
		packet.push("!monsterTransmitChat " + playerID + " " + selectedMonster[playerID]);
		if(toClientID == 0) jjSendPacket(packet);
		else jjSendPacket(packet, toClientID);
	}

	void SendMonsterBegin(int playerID, int toClientID)
	{
		if(playerID < 0 || playerID >= 32) return;
		jjSTREAM packet;
		packet.push("!monsterTransmitBegin " + playerID + " " + selectedMonster[playerID]);
		jjSendPacket(packet, toClientID);
	}

	void TransmitAll()
	{
		for(int i = 0; i < jjLocalPlayerCount; i++)
			SendMonster(jjLocalPlayers[i].playerID);
	}

	void SendAllMonstersToClient(int clientID)
	{
		if(clientID == 0) return;
		for(int i = 0; i < 32; i++)
			if(jjPlayers[i].isActive)
				SendMonsterBegin(i, clientID);
	}

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

	void PeriodicMonsterStateResync()
	{
		if(!jjIsServer || jjGameTicks < lastMonsterStateResyncTick + MONSTER_STATE_RESYNC_INTERVAL) return;
		lastMonsterStateResyncTick = jjGameTicks;
		SendAllMonsterStates();
	}

	void TransmitServer()
	{
		jjSTREAM packet;
		packet.push("!monsterAll");
		jjSendPacket(packet);
	}

	void TransmitSettings()
	{
		if(!transmitAll)
		{
			transmitAll = true;
			TransmitAll();
		}
		if(!transmitServer && !jjIsServer)
		{
			transmitServer = true;
			TransmitServer();
		}
	}

	void BroadcastMonsterSpecial(int playerID)
	{
		jjSTREAM packet;
		packet.push("!monsterSpecial " + playerID + " " + specialStarted[playerID] + " " + specialUntil[playerID] + " " + specialEffectUntil[playerID]);
		jjSendPacket(packet);
	}

	void BroadcastMonsterShooting(int playerID)
	{
		jjSTREAM packet;
		packet.push("!monsterShooting " + playerID + " " + shootingStarted[playerID] + " " + shootingUntil[playerID]);
		jjSendPacket(packet);
	}

	void StartShootingAnimation(int playerID, MonsterDef &in def)
	{
		shootingStarted[playerID] = jjGameTicks;
		shootingUntil[playerID] = jjGameTicks + AnimationDuration(def.shootingFrameCount, def.shootingSpeed);
		shootingQueued[playerID] = false;
		BroadcastMonsterShooting(playerID);
	}

	bool HasEnabledSpecial(MonsterDef &in def)
	{
		return true;// !MONSTER_SPECIAL_MOVE_ON || def.specialType == SPECIAL_QUEEN_WIND || def.specialType == SPECIAL_BUBBA_SPIN || def.specialType == SPECIAL_TURTLE_BITE;
	}

	bool HasSpecialAnimation(MonsterDef &in def)
	{
		return def.specialAnim >= 0;
	}

	int SpecialVisualDuration(MonsterDef &in def)
	{
		int duration = AnimationDuration(def.specialFrameCount, def.specialSpeed);
		return duration < SPECIAL_HOLD_TICKS ? SPECIAL_HOLD_TICKS : duration;
	}

	bool LooksLikeCharCommand(const string &in text)
	{
		array<string> parts = text.split(" ");
		if(parts.length == 0) return false;
		for(uint i = 0; i < CharsNew::chars.length; i++)
		{
			if(CharsNew::chars[i].Name != "" && jjRegexMatch(parts[0], "^!" + CharsNew::chars[i].Name + "$", true))
				return true;
		}
		return false;
	}

	void ClearLocalMonsters()
	{
		for(int i = 0; i < jjLocalPlayerCount; i++)
			ClearMonster(jjLocalPlayers[i].playerID);
	}

	bool onLocalChat(string &in text, CHAT::Type chatType)
	{
		if(!MONSTERS_ON) return false;
		array<string> parts = text.split(" ");
		if(parts.length == 0) return false;
		if(jjRegexMatch(parts[0], "^!monster$", true))
		{
			if(parts.length < 2)
			{
				jjAlert("Usage: !monster queen, !monster tufturtle, !monster off", false);
				return true;
			}
			string monsterName = text.substr(parts[0].length + 1);
			if(jjRegexMatch(monsterName, "^(off|none|normal)$", true))
			{
				ClearLocalMonsters();
				jjAlert("Monster character disabled.", false);
				return true;
			}
			int index = FindMonster(monsterName);
			if(index < 0)
			{
				jjAlert("Unknown monster: " + monsterName, false);
				return true;
			}
			for(int i = 0; i < jjLocalPlayerCount; i++)
			{
				int id = jjLocalPlayers[i].playerID;
				CharsNew::setCharacter(CharsNew::CharAnim::SPAZ, false, jjLocalPlayers[i].nameUnformatted, false, false);
				SetMonster(id, index);
			}
			//jjAlert("Monster character: " + monsters[index].name, false);
			return true;
		}
		if(jjRegexMatch(parts[0], "^!object$", true))
		{
			if(!OBJECTS_ENABLED)
			{
				jjAlert("Object character morphs are disabled.", false);
				return true;
			}
			if(parts.length < 2)
			{
				jjAlert("Usage: !object morph, !object bird, !object bouncer, !object off", false);
				return true;
			}
			string objectName = text.substr(parts[0].length + 1);
			if(jjRegexMatch(objectName, "^(off|none|normal)$", true))
			{
				ClearLocalMonsters();
				jjAlert("Object character disabled.", false);
				return true;
			}
			int index = FindObject(objectName);
			if(index < 0)
			{
				jjAlert("Unknown object: " + objectName, false);
				return true;
			}
			for(int i = 0; i < jjLocalPlayerCount; i++)
			{
				int id = jjLocalPlayers[i].playerID;
				CharsNew::setCharacter(CharsNew::CharAnim::SPAZ, false, jjLocalPlayers[i].nameUnformatted, false, false);
				SetMonster(id, index);
			}
			return true;
		}
		if(LooksLikeCharCommand(text))
			ClearLocalMonsters();
		return false;
	}

	void onReceive(jjSTREAM &in packet, int fromClientID)
	{
		if(!MONSTERS_ON) return;
		InitializeMonsterTable();
		jjSTREAM backup = packet;
		string e = "";
		packet.pop(e);
		array<string> parts = e.split(" ");
		if((e == "!all" || e == "!monsterAll") && jjIsServer)
		{
			SendAllMonstersToClient(fromClientID);
		}
		else if(parts.length >= 3 && (parts[0] == "!monsterTransmitBegin" || parts[0] == "!monsterTransmitChat" || parts[0] == "!monsterChar"))
		{
			int id = parseInt(parts[1]);
			int index = parseInt(parts[2]);
			if(id < 0 || id >= 32) return;
			if(index >= int(monsters.length)) return;
			if(jjIsServer && fromClientID > 0 && jjPlayers[id].clientID != fromClientID) return;
			if(index >= 0) ApplyMonster(id, index);
			else ClearMonster(id, false);
			if(jjIsServer && parts[0] != "!monsterTransmitBegin") SendMonster(id, -fromClientID);
		}
		else if(parts.length >= 4 && parts[0] == "!monsterSpecial")
		{
			int id = parseInt(parts[1]);
			if(id < 0 || id >= 32 || !IsActive(id)) return;
			if(jjIsServer && fromClientID > 0 && jjPlayers[id].clientID != fromClientID) return;
			int index = selectedMonster[id];
			if(index < 0 || index >= int(monsters.length) || !HasSpecialAnimation(monsters[index])) return;
			int remoteStarted = parseInt(parts[2]);
			int visualDuration = parseInt(parts[3]) - remoteStarted;
			if(visualDuration < SPECIAL_HOLD_TICKS) visualDuration = SPECIAL_HOLD_TICKS;
			specialStarted[id] = jjGameTicks;
			specialUntil[id] = jjGameTicks + visualDuration;
			if(MONSTER_SPECIAL_MOVE_ON && parts.length >= 5)
			{
				int effectDuration = parseInt(parts[4]) - remoteStarted;
				specialEffectUntil[id] = effectDuration > 0 ? jjGameTicks + effectDuration : 0;
			}
			else
				specialEffectUntil[id] = 0;
			if(jjIsServer) jjSendPacket(backup, -fromClientID);
		}
		else if(parts.length >= 4 && parts[0] == "!monsterShooting")
		{
			int id = parseInt(parts[1]);
			if(id < 0 || id >= 32 || !IsActive(id)) return;
			if(jjIsServer && fromClientID > 0 && jjPlayers[id].clientID != fromClientID) return;

			int index = selectedMonster[id];
			if(index < 0 || index >= int(monsters.length)) return;
			if(monsters[index].shootingAnim < 0) return;

			// The packet's absolute jjGameTicks belong to the sending machine.
			// Never compare them against this client's jjGameTicks: that can keep
			// a remote monster in its shooting animation indefinitely. A shooting
			// packet is only a trigger; start the known local animation duration now.
			shootingStarted[id] = jjGameTicks;
			shootingUntil[id] = jjGameTicks
				+ AnimationDuration(
					monsters[index].shootingFrameCount,
					monsters[index].shootingSpeed
				);
			shootingQueued[id] = false;

			if(jjIsServer) jjSendPacket(backup, -fromClientID);
		}
	}

	void onPlayer(jjPLAYER@ play)
	{
		if(!IsActive(play.playerID)) return;
		int index = selectedMonster[play.playerID];
		if(index < 0 || index >= int(monsters.length)) return;
		if(monsters[index].canFly && play.fly == FLIGHT::NONE)
		{
			play.fly = FLIGHT::AIRBOARD;
			gaveMonsterFlight[play.playerID] = true;
		}
		if(play.localPlayerID < 0) return;
		bool specialNow = play.specialMove > 0 && HasSpecialAnimation(monsters[index]);
		bool specialStartedNow = specialNow && !specialMoveWasActive[play.playerID];
		specialMoveWasActive[play.playerID] = specialNow;
		if(specialStartedNow)
		{
			specialStarted[play.playerID] = jjGameTicks;
			specialUntil[play.playerID] = jjGameTicks + SpecialVisualDuration(monsters[index]);
			specialEffectUntil[play.playerID] = 0;
			if(MONSTER_SPECIAL_MOVE_ON && HasEnabledSpecial(monsters[index]) && jjGameTicks >= specialCooldownUntil[play.playerID])
			{
				specialCooldownUntil[play.playerID] = jjGameTicks + monsters[index].cooldown;
				specialEffectUntil[play.playerID] = jjGameTicks + MonsterSpecialMoves::SpecialDuration(monsters[index].specialType);
				MonsterSpecialMoves::ApplySpecialStart(play, monsters[index]);
			}
			BroadcastMonsterSpecial(play.playerID);
		}
	}

	void onPlayerInput(jjPLAYER@ play)
	{
		int localID = play.localPlayerID;
		if(localID < 0 || localID >= int(fireWasPressed.length)) return;
		bool fireNow = play.keyFire;
		bool firePressed = fireNow && !fireWasPressed[localID];
		fireWasPressed[localID] = fireNow;
		if(firePressed && IsActive(play.playerID))
		{
			int shootingIndex = selectedMonster[play.playerID];
			if(shootingIndex >= 0 && shootingIndex < int(monsters.length) && monsters[shootingIndex].shootingAnim >= 0)
			{
				if(IsShootingActive(monsters[shootingIndex], play.playerID))
					shootingQueued[play.playerID] = true;
				else
					StartShootingAnimation(play.playerID, monsters[shootingIndex]);
			}
		}
		else if(fireNow && IsActive(play.playerID))
		{
			int shootingIndex = selectedMonster[play.playerID];
			if(shootingIndex >= 0 && shootingIndex < int(monsters.length) && monsters[shootingIndex].shootingAnim >= 0 && IsShootingActive(monsters[shootingIndex], play.playerID))
				shootingQueued[play.playerID] = true;
		}
	}

	void onPlayerDraw(jjPLAYERDRAW& draw)
	{
		if(CanDrawMonster(draw.player.playerID))
		{
			// DrawMonster renders the replacement sprite and its one replacement
			// name. Never leave JJ2's native name enabled underneath it.
			draw.sprite = false;
			draw.name = false;
		}
	}

	void onMain()
	{
		if(!MONSTERS_ON) return;
		TransmitSettings();
		PeriodicMonsterStateResync();
		for(int i = 0; i < 32; i++)
		{
			if(!jjPlayers[i].isActive || !jjPlayers[i].isInGame || !IsActive(i)) continue;
			int index = selectedMonster[i];
			if(MONSTER_SPECIAL_MOVE_ON && index >= 0 && index < int(monsters.length) && HasEnabledSpecial(monsters[index]) && specialEffectUntil[i] > jjGameTicks)
			{
				MonsterSpecialMoves::ApplySpecialMovement(jjPlayers[i], monsters[index]);
				MonsterSpecialMoves::ApplySpecialHit(jjPlayers[i], monsters[index]);
			}
			if(shootingQueued[i] && shootingUntil[i] <= jjGameTicks)
				StartShootingAnimation(i, monsters[index]);
			UpdateCrouchingAnimation(jjPlayers[i], monsters[index]);
			if(jjPlayers[i].isLocal || CharsNew::isPlayerVisible(i, 128))
				DrawMonster(jjPlayers[i]);
		}
	}

	bool HasCrouchingAnimation(MonsterDef &in def)
	{
		return def.crouchingAnim >= 0 && def.crouchingFrameCount > 0;
	}

	int CrouchFrame(MonsterDef &in def, int playerID)
	{
		int count = def.crouchingFrameCount <= 0 ? 1 : def.crouchingFrameCount;
		int speed = SafeAnimSpeed(def.crouchingSpeed);
		if(crouchReleasing[playerID])
		{
			int frame = crouchReleaseFrame[playerID] - ((jjGameTicks - crouchStarted[playerID]) / speed);
			if(frame < 0) frame = 0;
			return frame >= count ? count - 1 : frame;
		}
		int frame = (jjGameTicks - crouchStarted[playerID]) / speed;
		return frame >= count ? count - 1 : frame;
	}

	void UpdateCrouchingAnimation(jjPLAYER@ play, MonsterDef &in def)
	{
		int playerID = play.playerID;
		if(playerID < 0 || playerID >= 32) return;
		if(!HasCrouchingAnimation(def))
		{
			crouchWasDown[playerID] = false;
			crouchReleasing[playerID] = false;
			specialVisualWasActive[playerID] = false;
			return;
		}

		bool downNow = play.keyDown;
		bool specialNow = IsSpecialActive(def, playerID);
		if(def.crouchAfterSpecial && !specialNow && specialVisualWasActive[playerID])
		{
			crouchReleaseFrame[playerID] = def.crouchingFrameCount - 1;
			crouchStarted[playerID] = jjGameTicks;
			crouchReleasing[playerID] = true;
		}
		specialVisualWasActive[playerID] = specialNow;

		if(downNow && !crouchWasDown[playerID])
		{
			crouchStarted[playerID] = jjGameTicks;
			crouchReleasing[playerID] = false;
		}
		else if(!downNow && crouchWasDown[playerID])
		{
			crouchReleaseFrame[playerID] = CrouchFrame(def, playerID);
			crouchStarted[playerID] = jjGameTicks;
			crouchReleasing[playerID] = true;
		}
		else if(crouchReleasing[playerID] && CrouchFrame(def, playerID) <= 0)
		{
			crouchReleasing[playerID] = false;
		}
		crouchWasDown[playerID] = downNow;
	}

	bool IsCrouchingActive(MonsterDef &in def, int playerID, jjPLAYER@ play)
	{
		return HasCrouchingAnimation(def) && (play.keyDown || crouchReleasing[playerID]);
	}

	bool IsSpecialActive(MonsterDef &in def, int playerID)
	{
		return HasSpecialAnimation(def) && (specialUntil[playerID] > jjGameTicks || jjPlayers[playerID].specialMove > 0);
	}

	bool IsShootingActive(MonsterDef &in def, int playerID)
	{
		return def.shootingAnim >= 0 && shootingUntil[playerID] > jjGameTicks;
	}

	bool IsWalking(jjPLAYER@ play)
	{
		return abs(play.xSpeed) > 0.5 || abs(play.ySpeed) > 0.5;
	}

	int DrawFrameCount(MonsterDef &in def, int playerID, jjPLAYER@ play)
	{
		if(IsSpecialActive(def, playerID))
			return def.specialFrameCount <= 0 ? 1 : def.specialFrameCount;
		if(IsShootingActive(def, playerID))
			return def.shootingFrameCount <= 0 ? 1 : def.shootingFrameCount;
		if(IsCrouchingActive(def, playerID, play))
			return def.crouchingFrameCount <= 0 ? 1 : def.crouchingFrameCount;
		if(IsWalking(play))
			return def.walkingFrameCount <= 0 ? 1 : def.walkingFrameCount;
		return def.idleFrameCount <= 0 ? 1 : def.idleFrameCount;
	}

	int SafeAnimSpeed(int speed)
	{
		return speed <= 0 ? DEFAULT_ANIM_SPEED : speed;
	}

	int AnimationDuration(int frameCount, int speed)
	{
		int count = frameCount <= 0 ? 1 : frameCount;
		return count * SafeAnimSpeed(speed);
	}

	int DrawFrame(MonsterDef &in def, int playerID, jjPLAYER@ play)
	{
		int count = DrawFrameCount(def, playerID, play);
		if(IsSpecialActive(def, playerID))
			return ((jjGameTicks - specialStarted[playerID]) / SafeAnimSpeed(def.specialSpeed)) % count;
		if(IsShootingActive(def, playerID))
			return ((jjGameTicks - shootingStarted[playerID]) / SafeAnimSpeed(def.shootingSpeed)) % count;
		if(IsCrouchingActive(def, playerID, play))
			return CrouchFrame(def, playerID);
		if(IsWalking(play))
			return (jjGameTicks / SafeAnimSpeed(def.walkingSpeed)) % count;
		return (jjGameTicks / SafeAnimSpeed(def.idleSpeed)) % count;
	}

	int DrawAnim(MonsterDef &in def, int playerID, jjPLAYER@ play)
	{
		if(IsSpecialActive(def, playerID) && def.specialAnim >= 0)
			return def.specialAnim;
		if(IsShootingActive(def, playerID))
			return def.shootingAnim;
		if(IsCrouchingActive(def, playerID, play))
			return def.crouchingAnim;
		if(IsWalking(play) && def.walkingAnim >= 0)
			return def.walkingAnim;
		return def.idleAnim;
	}

	int DrawXOffset(MonsterDef &in def, int playerID, jjPLAYER@ play)
	{
		if(IsSpecialActive(def, playerID)) return def.specialXOffset;
		if(IsShootingActive(def, playerID)) return def.shootingXOffset;
		if(IsCrouchingActive(def, playerID, play)) return def.crouchingXOffset;
		if(IsWalking(play)) return def.walkingXOffset;
		return def.idleXOffset;
	}

	int DrawYOffset(MonsterDef &in def, int playerID, jjPLAYER@ play)
	{
		if(IsSpecialActive(def, playerID)) return def.specialYOffset;
		if(IsShootingActive(def, playerID)) return def.shootingYOffset;
		if(IsCrouchingActive(def, playerID, play)) return def.crouchingYOffset;
		if(IsWalking(play)) return def.walkingYOffset;
		return def.idleYOffset;
	}

	void DrawMonsterPlayerName(jjPLAYER@ play, float characterSizeScale, MonsterDef &in def)
	{
		if(play.playerID < 0 || play.playerID >= 32) return;

		STRING::Size nameSize = STRING::SMALL;
		float nameOffset = 28;

		if(characterSizeScale < 1)
		{
			// Same visual placement used for the tiny character name.
			nameOffset = 5;
		}
		else if(characterSizeScale > 1)
		{
			// Same visual placement and font used for the giant character name.
			nameSize = STRING::MEDIUM;
			nameOffset = 75;
		}

		// nameRaise is defined at normal monster size. Scale it with the monster
		// itself and with !tiny / !giant so tall monsters keep the same visual
		// proportion at every player size.
		nameOffset += def.nameRaise * def.scale * characterSizeScale;

		string nameText = play.name;
		if(nameText == "") nameText = play.nameUnformatted;
		if(nameText == "") return;

		jjDrawString(
			play.xPos - jjGetStringWidth(nameText, nameSize, STRING::NORMAL) * 0.5,
			play.yPos - (play.antiGrav ? -nameOffset : nameOffset),
			nameText,
			nameSize,
			STRING::NORMAL,
			0,
			play.playerID);
	}

	void DrawMonster(jjPLAYER@ play)
	{
		int index = selectedMonster[play.playerID];
		if(index < 0 || index >= int(monsters.length)) return;
		MonsterDef def = monsters[index];
		LoadMonsterAnim(index);
		float characterSizeScale = CharsNew::PlayerSizeScale(play.playerID);

		// This module owns both the monster/object sprite and its single name.
		// It draws one normal/tiny/giant name while both onPlayerDraw hooks keep
		// JJ2's native name disabled.
		DrawMonsterPlayerName(play, characterSizeScale, def);
		if(!CanDrawMonster(play.playerID)) return;
		if(play.blink != 0 && (jjGameTicks % 4 == 2 || jjGameTicks % 4 == 3)) return;

		int grav = play.antiGrav ? -1 : 1;
		SPRITE::Mode spriteMode = play.frozen == 0 ? (play.shieldType == SHIELD::WATER ? SPRITE::TRANSLUCENTPLAYER : SPRITE::PLAYER) : SPRITE::FROZEN;
		int spriteParam = play.frozen == 0 ? play.playerID : 0;
		// The base gem sprite is shared; SPRITE::GEM selects the color with its
		// palette offset: red 0, green 1, blue 2, purple 3.
		if(def.eventID == OBJECT::REDGEM || def.eventID == OBJECT::GREENGEM ||
			def.eventID == OBJECT::BLUEGEM || def.eventID == OBJECT::PURPLEGEM)
		{
			spriteMode = SPRITE::GEM;
			if(def.eventID == OBJECT::GREENGEM) spriteParam = 1;
			else if(def.eventID == OBJECT::BLUEGEM) spriteParam = 2;
			else if(def.eventID == OBJECT::PURPLEGEM) spriteParam = 3;
			else spriteParam = 0;
		}

		// Keep the monster/object's feet at the same ground position when the
		// player size changes. This is the same 23-pixel feet anchor used by
		// CharsNew's tiny/giant character renderer, adjusted for this monster's
		// own base scale.
		float sizeFeetOffset = (1 - characterSizeScale) * 23 * def.scale;

		float scaleX = def.scale * characterSizeScale * (play.direction >= 0 ? 1 : -1) * def.direction;
		float scaleY = def.scale * characterSizeScale * grav;
		float x = play.xPos + DrawXOffset(def, play.playerID, play) * (scaleX >= 0 ? 1 : -1);
		float y = play.yPos + (def.offsetY + DrawYOffset(def, play.playerID, play) + sizeFeetOffset) * grav;
		int drawAnim = DrawAnim(def, play.playerID, play);
		int drawFrame = DrawFrame(def, play.playerID, play);
		// Do not use jjObjectPresets[OBJECT::LASERSHIELD].curFrame here. Its
		// curFrame can still be zero when no real Laser Shield object was spawned.
		// The normal renderer loads and draws PLUS_COMMON animation 2 directly.
		if(def.rotation != 0)
		{
			int rotation = scaleX >= 0 ? (1024 - def.rotation) % 1024 : def.rotation;
			CharsNew::RecordTraceRotatedFrame(play.playerID, x, y, def.animSet, drawAnim, drawFrame, rotation, scaleX, scaleY);
			jjDrawRotatedSprite(x, y, def.animSet, drawAnim, drawFrame, rotation, scaleX, scaleY, spriteMode, spriteParam);
		}
		else
		{
			CharsNew::RecordTraceFrame(play.playerID, x, y, def.animSet, drawAnim, drawFrame, scaleX, scaleY);
			jjDrawResizedSprite(x, y, def.animSet, drawAnim, drawFrame, scaleX, scaleY, spriteMode, spriteParam);
		}
	}
}

namespace MonsterSpecialMoves
{
	int SpecialDuration(int specialType)
	{
		if(specialType == CharsMonsters::SPECIAL_QUEEN_WIND) return 42;
		if(specialType == CharsMonsters::SPECIAL_BUBBA_SPIN) return 50;
		if(specialType == CharsMonsters::SPECIAL_TURTLE_BITE) return 24;
		if(specialType == CharsMonsters::SPECIAL_PROJECTILE) return 18;
		return 20;
	}

	void ApplySpecialStart(jjPLAYER@ play, CharsMonsters::MonsterDef &in def)
	{
		if(def.specialType == CharsMonsters::SPECIAL_PROJECTILE)
		{
			int objectID = jjAddObject(OBJECT::BLASTERBULLET, play.xPos + 24 * play.direction, play.yPos - 8, play.playerID, CREATOR::PLAYER, BEHAVIOR::DEFAULT);
			if(objectID > 0)
			{
				jjOBJ@ bullet = jjObjects[objectID];
				bullet.xSpeed = 8 * play.direction;
				bullet.ySpeed = 0;
				bullet.direction = play.direction;
			}
		}
		ApplySpecialHit(play, def);
	}

	void ApplySpecialMovement(jjPLAYER@ play, CharsMonsters::MonsterDef &in def)
	{
		if(def.specialType == CharsMonsters::SPECIAL_QUEEN_WIND)
		{
			play.xSpeed *= 0.85;
			return;
		}
		if(def.specialType == CharsMonsters::SPECIAL_BUBBA_SPIN)
		{
			play.xSpeed = 7 * play.direction;
			if(play.ySpeed > 2) play.ySpeed = 2;
			return;
		}
		if(def.specialType == CharsMonsters::SPECIAL_TURTLE_BITE || def.specialType == CharsMonsters::SPECIAL_CHARGE)
			play.xSpeed = 6 * play.direction;
	}

	bool IsMonsterTargetEvent(int eventID)
	{
		for(uint i = 0; i < CharsMonsters::monsters.length; i++)
			if(CharsMonsters::monsters[i].eventID == eventID)
				return true;
		return false;
	}

	void ApplySpecialHit(jjPLAYER@ play, CharsMonsters::MonsterDef &in def)
	{
		if(!jjIsServer && jjGameMode != GAME::SP) return;
		float range = def.specialType == CharsMonsters::SPECIAL_QUEEN_WIND ? 150 : 48;
		for(int i = 1; i < jjObjectCount; i++)
		{
			jjOBJ@ obj = jjObjects[i];
			if(!obj.isActive || obj.energy <= 0 || !IsMonsterTargetEvent(obj.eventID)) continue;
			float dx = obj.xPos - play.xPos;
			float dy = obj.yPos - play.yPos;
			if(abs(dx) > range || abs(dy) > range) continue;
			if(def.specialType != CharsMonsters::SPECIAL_QUEEN_WIND && dx * play.direction < -8) continue;
			if(def.specialType == CharsMonsters::SPECIAL_QUEEN_WIND)
			{
				obj.xSpeed += dx >= 0 ? 3 : -3;
				obj.ySpeed -= 1;
			}
			else
			{
				obj.energy -= 1;
				obj.xSpeed += play.direction * 3;
			}
		}
		for(int i = 0; i < 32; i++)
		{
			if(i == play.playerID || !jjPlayers[i].isActive || !jjPlayers[i].isInGame || jjPlayers[i].blink != 0) continue;
			float dx = jjPlayers[i].xPos - play.xPos;
			float dy = jjPlayers[i].yPos - play.yPos;
			if(abs(dx) > range || abs(dy) > range) continue;
			if(def.specialType == CharsMonsters::SPECIAL_QUEEN_WIND)
			{
				jjPlayers[i].xSpeed += dx >= 0 ? 5 : -5;
				jjPlayers[i].ySpeed -= 1.5;
			}
			else if(dx * play.direction >= -8)
				jjPlayers[i].hurt(1, false, play);
		}
	}
}