#pragma name "Random Class Selection"
#pragma require "SEenergyblast.asc"
#pragma require "SEfirework.asc"
#pragma require "SEroller.asc"
#include "SEenergyblast.asc"
#include "SEfirework.asc"
#include "SEroller.asc"
enum weapon_t {
	weapon_blaster,
	weapon_bouncer,
	weapon_freezer,
	weapon_seeker,
	weapon_rf,
	weapon_toaster,
	weapon_tnt,
	weapon_fireball,
	weapon_electroblaster,
	weapon_energyblast,
	weapon_firework,
	weapon_roller,
	weapon_total
}
class Specialty {
	weapon_t weapon;
	bool powerup;
	int maximum;
	Specialty() {}
	Specialty(weapon_t w, bool pu, int m) {
		weapon = w;
		powerup = pu;
		maximum = m;
	}
}
class ClassPrototype {
	string name;
	array<array<Specialty>> specialties;
	ClassPrototype() {}
	ClassPrototype(const string &in n, const array<array<Specialty>>& s) {
		name = n;
		specialties = s;
	}
}
class PlayerClass {
	string name;
	array<Specialty> specialties;
	PlayerClass() {}
	PlayerClass(const string &in n, const array<Specialty>& s) {
		name = n;
		specialties = s;
	}
}
class PlayerSelection {
	int selection;
	int leftDelay;
	int rightDelay;
	int sinceFire;
}
class AmmoPickup : se::AmmoPickup {
	array<array<const jjANIMATION@>> anims(4);
	AmmoPickup() {
		const jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::AMMO] + 68];
		super(anim, anim);
	}
	void onBehave(jjOBJ@ obj) override {
		if (obj.state == STATE::START)
			obj.scriptedCollisions = true;
		obj.behave(BEHAVIOR::PICKUP, false);
		float y = getY(obj);
		for (int i = 0; i < jjLocalPlayerCount; i++) {
			const jjPLAYER@ player = @jjLocalPlayers[i];
			int count = anims[i].length();
			int dir = obj.direction < 0 ? -1 : 1;
			float x = obj.xPos - ((count - 1) * obj.var[7]) * dir;
			for (int j = count; j-- != 0;) {
				const jjANIMATION@ anim = anims[i][j];
				uint sprite = anim + jjGameTicks / 7 % anim.frameCount;
				float xMod = x + (j * obj.var[7] << 1) * dir;
				if (obj.var[7] == 8) {
					draw(xMod, y, sprite, obj.direction, jjAnimFrames[sprite].transparent, player.playerID);
				} else {
					float scale = obj.var[7] / 8.f;
					jjDrawResizedSpriteFromCurFrame(xMod, y, sprite, scale, scale, jjAnimFrames[sprite].transparent ? SPRITE::TRANSLUCENT : SPRITE::NORMAL, 0, 4, 4, player.playerID);
				}
			}
		}
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int) override {
		if (bullet is null) {
			const PlayerClass@ playerClass = availableClasses[selections[player.localPlayerID].selection];
			bool sp = jjGameMode == GAME::SP || jjGameMode == GAME::COOP;
			bool destroy = false;
			for (int i = playerClass.specialties.length(); i-- != 0;) {
				const Specialty@ specialty = playerClass.specialties[i];
				if (specialty.maximum != -1) {
					uint type = weaponMapping[specialty.weapon];
					int multiplier = jjWeapons[type].multiplier;
					jjWeapons[type].multiplier = 1;
					int maximum = specialty.maximum;
					maximum *= multiplier;
					if (player.ammo[type] >= maximum) {
						player.ammo[type] = maximum;
					} else {
						player.ammo[type] = player.ammo[type] + obj.var[specialty.weapon == weapon_energyblast ? 9 : 8] * multiplier;
						if (player.ammo[type] >= maximum)
							player.ammo[type] = maximum;
						destroy = true;
					}
					jjWeapons[type].multiplier = multiplier;
				}
			}
			if (destroy) {
				obj.frameID = 0;
				obj.behavior = BEHAVIOR::EXPLOSION2;
				jjSample(obj.xPos, obj.yPos, SOUND::COMMON_PICKUPW1);
				if (sp && obj.points != 0) {
					player.score += obj.points;
					jjPARTICLE@ part = jjAddParticle(PARTICLE::STRING);
					if (part !is null) {
						part.xPos = obj.xPos;
						part.yPos = obj.yPos;
						part.xSpeed = -0.5f - (jjRandom() & 0x3FFF) / 65536.f;
						part.ySpeed = -1.f - (jjRandom() & 0x7FFF) / 65536.f;
						part.string.text = formatInt(obj.points);
					}
					obj.points = 0;
				}
			}
			return true;
		}
		return false;
	}
}
class SimpleRNG {
	private uint seed;
	SimpleRNG(uint newSeed) {
		seed = newSeed;
	}
	uint opCall() {
		return seed = seed * 1103515245 + 12345;
	}
}
const array<ClassPrototype> possibleClasses = {
	ClassPrototype("Guardian", array<array<Specialty>> = {{
			Specialty(weapon_bouncer, true, 40),
			Specialty(weapon_seeker, true, 15)
		}, {
			Specialty(weapon_bouncer, true, 20),
			Specialty(weapon_bouncer, false, 40),
			Specialty(weapon_seeker, true, 5),
			Specialty(weapon_seeker, false, 20),
			Specialty(weapon_toaster, true, 20),
			Specialty(weapon_toaster, false, 60),
			Specialty(weapon_tnt, false, 50),
			Specialty(weapon_firework, false, 30)
		}
	}),
	ClassPrototype("Lightweight", array<array<Specialty>> = {{
			Specialty(weapon_bouncer, false, -1),
			Specialty(weapon_seeker, false, 40),
			Specialty(weapon_rf, false, -1)
		}, {
			Specialty(weapon_toaster, false, 50),
			Specialty(weapon_tnt, false, -1),
			Specialty(weapon_fireball, false, 50),
			Specialty(weapon_electroblaster, false, 50),
			Specialty(weapon_firework, false, 20)
		}
	}),
	ClassPrototype("Ninja", array<array<Specialty>> = {{
			Specialty(weapon_bouncer, true, 30),
			Specialty(weapon_energyblast, true, 60),
			Specialty(weapon_roller, true, 15)
		}, {
			Specialty(weapon_blaster, true, -1),
			Specialty(weapon_bouncer, false, 30),
			Specialty(weapon_freezer, true, -1),
			Specialty(weapon_fireball, false, 60),
			Specialty(weapon_electroblaster, true, 30),
			Specialty(weapon_energyblast, false, 40),
			Specialty(weapon_roller, false, 20)
		}
	}),
	ClassPrototype("Pyro", array<array<Specialty>> = {{
			Specialty(weapon_toaster, true, 40),
			Specialty(weapon_roller, true, 20)
		}, {
			Specialty(weapon_toaster, true, 25),
			Specialty(weapon_toaster, false, -1),
			Specialty(weapon_fireball, false, 50),
			Specialty(weapon_firework, true, 10),
			Specialty(weapon_firework, false, 50),
			Specialty(weapon_roller, true, 10),
			Specialty(weapon_roller, false, 20)
		}
	}),
	ClassPrototype("Shaman", array<array<Specialty>> = {{
			Specialty(weapon_energyblast, true, 100),
			Specialty(weapon_roller, false, 50)
		}, {
			Specialty(weapon_freezer, false, -1),
			Specialty(weapon_toaster, false, 30),
			Specialty(weapon_energyblast, false, 50),
			Specialty(weapon_roller, false, 10)
		}
	}),
	ClassPrototype("Sniper", array<array<Specialty>> = {{
			Specialty(weapon_rf, true, 40),
			Specialty(weapon_fireball, true, 50)
		}, {
			Specialty(weapon_blaster, true, -1),
			Specialty(weapon_bouncer, false, 60),
			Specialty(weapon_rf, true, 20),
			Specialty(weapon_rf, false, 50),
			Specialty(weapon_fireball, true, 25),
			Specialty(weapon_fireball, false, 50),
			Specialty(weapon_electroblaster, true, 40)
		}
	}),
	ClassPrototype("Soldier", array<array<Specialty>> = {{
			Specialty(weapon_bouncer, true, 30),
			Specialty(weapon_rf, true, 30),
			Specialty(weapon_seeker, true, 10)
		}, {
			Specialty(weapon_bouncer, true, 30),
			Specialty(weapon_rf, true, 30),
			Specialty(weapon_seeker, true, 10)
		}
	})
};
const array<se::WeaponInterface@> customWeapons = {se::energyBlast, se::firework, se::roller};
const uint selectionTime = 15 * 70;
const array<array<int>> animations = {
	{29, 25, 29, 34, 49, 57, 59, 62, 68, 0, 0, 0},
	{18, 24, 28, 33, 48, 56, 59, 61, 67, 1, 1, 1}
};
se::DefaultWeaponHook weaponHook(0);
array<PlayerClass> availableClasses;
array<array<bool>> weaponAllowed(2, array<bool>(weapon_total));
array<uint> weaponMapping(weapon_total);
array<uint> sinceDeath(32);
array<PlayerSelection> selections(4);
array<array<ANIM::Set>> animSets(2, array<ANIM::Set>(weapon_total, ANIM::AMMO));
uint randomSeed;
AmmoPickup ammoPickup;
void drawQuarterCircle(jjCANVAS@ canvas, int x, int y, uint r, bool xf, bool yf, uint8 color) {
	int w = xf ? -1 : 1;
	int h = yf ? -1 : 1;
	uint rsqrt2 = (r * 29 + 20) / 41;
	canvas.drawRectangle(x, y, rsqrt2 * w, rsqrt2 * h, color);
	for (uint i = rsqrt2; i < r; i++) {
		int a = int(sqrt(r * r - i * i));
		canvas.drawRectangle(x + i * w, y, w, a * h, color);
		canvas.drawRectangle(x, y + i * h, a * w, h, color);
	}
}
void drawSelectionCorner(jjCANVAS@ canvas, int x, int y, uint w, uint h, uint r, bool xf, bool yf, uint8 color) {
	x -= w / 2;
	y -= h / 2;
	int d = r * 2;
	int wd = w - d;
	int hd = h - d;
	int xr = x + r;
	int xwr = x + w - r;
	int yr = y + r;
	int yhr = y + h - r;
	canvas.drawRectangle(xr, yf ? yhr : y, wd, r, color);
	canvas.drawRectangle(xf ? xwr : x, yr, r, hd, color);
	if (!xf || !yf)
		drawQuarterCircle(canvas, xr, yr, r, true, true, color);
	if (xf || !yf)
		drawQuarterCircle(canvas, xwr, yr, r, false, true, color);
	if (!xf || yf)
		drawQuarterCircle(canvas, xr, yhr, r, true, false, color);
	if (xf || yf)
		drawQuarterCircle(canvas, xwr, yhr, r, false, false, color);
}
void initializeAmmoPresets() {
	for (int i = 1; i <= 9; i++) {
		int id;
		id = se::getAmmoPickupOfWeapon(i);
		uint curAnim = jjAnimSets[ANIM::AMMO] + 68;
		uint killAnim = jjObjectPresets[OBJECT::BOUNCERAMMO3].killAnim;
		if (id != 0) {
			jjOBJ@ preset = jjObjectPresets[id];
			preset.curAnim = curAnim;
			preset.var[7] = 8;
			preset.var[8] = 3;
			preset.var[9] = 5;
			preset.behavior = ammoPickup;
		}
		id = se::getAmmoCrateOfWeapon(i);
		if (id != 0) {
			jjOBJ@ preset = jjObjectPresets[id];
			preset.curAnim = curAnim;
			preset.killAnim = killAnim;
			preset.playerHandling = HANDLING::PICKUP;
			preset.var[7] = 10;
			preset.var[8] = 15;
			preset.var[9] = 25;
			preset.behavior = ammoPickup;
		}
		id = se::getPowerupMonitorOfWeapon(i);
		if (id != 0) {
			jjOBJ@ preset = jjObjectPresets[id];
			preset.curAnim = curAnim;
			preset.killAnim = killAnim;
			preset.playerHandling = HANDLING::PICKUP;
			preset.var[7] = 12;
			preset.var[8] = 30;
			preset.var[9] = 50;
			preset.behavior = ammoPickup;
		}
	}
}
void randomizeClasses(uint seed) {
	if (availableClasses.length() != 0)
		return;
	array<ClassPrototype> classes = possibleClasses;
	SimpleRNG rand(seed);
	for (int i = 4; i-- != 0 && !classes.isEmpty();) {
		int index = rand() % classes.length();
		const ClassPrototype@ prototype = classes[index];
		array<Specialty> specialties(prototype.specialties.length());
		for (int j = specialties.length(); j-- != 0;) {
			const array<Specialty>@ src = prototype.specialties[j];
			specialties[j] = src[rand() % src.length()];
		}
		for (int j = specialties.length(); j-- != 0;) {
			Specialty@ lhs = specialties[j];
			for (int k = specialties.length(); --k != j;) {
				const Specialty@ rhs = specialties[k];
				if (lhs.weapon == rhs.weapon) {
					if (lhs.maximum == -1 || rhs.maximum == -1) {
						lhs.maximum = -1;
						if (rhs.powerup)
							lhs.powerup = true;
					} else {
						if (!lhs.powerup && rhs.powerup) {
							lhs.maximum /= 2;
							lhs.powerup = true;
						}
						lhs.maximum += rhs.maximum / (lhs.powerup && !rhs.powerup ? 2 : 1);
					}
					specialties.removeAt(k);
				}
			}
		}
		availableClasses.insertLast(PlayerClass(prototype.name, specialties));
		classes.removeAt(index);
	}
	weaponAllowed[0][0] = true;
	array<weapon_t> customEnabled;
	for (int i = availableClasses.length(); i-- != 0;) {
		const PlayerClass@ playerClass = availableClasses[i];
		for (int j = playerClass.specialties.length(); j-- != 0;) {
			const Specialty@ specialty = playerClass.specialties[j];
			if (specialty.weapon < 9)
				weaponAllowed[specialty.powerup ? 1 : 0][specialty.weapon] = true;
			else if (customEnabled.find(specialty.weapon) < 0)
				customEnabled.insertLast(specialty.weapon);
		}
	}
	int customAnimID = 0, customSampleID = 0;
	const array<SOUND::Sample> customSamples = {SOUND::INTRO_BLOW, SOUND::INTRO_BOEM1, SOUND::INTRO_BOEM2, SOUND::INTRO_BRAKE};
	for (int i = 0; i < 9; i++) {
		weaponMapping[i] = i + 1;
		if (!customEnabled.isEmpty() && !weaponAllowed[0][i] && !weaponAllowed[1][i]) {
			weapon_t weapon = customEnabled[customEnabled.length() - 1];
			customEnabled.removeLast();
			for (int j = 2; j-- != 0;) {
				weaponAllowed[j][i] = weaponAllowed[j][weapon];
			}
			se::WeaponInterface@ customWeapon = customWeapons[weapon - 9];
			while (jjAnimSets[ANIM::CUSTOM[customAnimID]] != 0) {
				customAnimID++;
			}
			customWeapon.loadAnims(jjAnimSets[ANIM::CUSTOM[customAnimID]]);
			for (int j = 2; j-- != 0;) {
				animSets[j][weapon] = ANIM::CUSTOM[customAnimID];
			}
			array<SOUND::Sample> samples;
			while (samples.length() < customWeapon.getSampleCount()) {
				samples.insertLast(customSamples[customSampleID++]);
			}
			customWeapon.loadSamples(samples);
			weaponMapping[weapon] = i + 1;
			customWeapon.setAsWeapon(i + 1, weaponHook);
		}
	}
	initializeAmmoPresets();
}
bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	if (sinceDeath[player.playerID] < selectionTime) {
		canvas.drawRectangle(0, 0, jjSubscreenWidth, jjSubscreenHeight, 0, SPRITE::TRANSLUCENT);
		jjTEXTAPPEARANCE centered;
		centered.align = STRING::CENTER;
		canvas.drawString(jjSubscreenWidth >> 1, jjSubscreenHeight >> 3, "Select Class", STRING::MEDIUM, centered);
		string timeLeft = formatInt((selectionTime - sinceDeath[player.playerID] - 1) / 70);
		canvas.drawString(jjSubscreenWidth - (jjSubscreenWidth >> 3), jjSubscreenHeight >> 3, timeLeft, STRING::MEDIUM, centered);
		int y = jjSubscreenHeight >> 1;
		for (int i = availableClasses.length(); i-- != 0;) {
			const PlayerClass@ playerClass = availableClasses[i];
			int x = (i << 1 | 1) * jjSubscreenWidth / (availableClasses.length() << 1);
			canvas.drawString(x, y - 64, playerClass.name, STRING::SMALL, centered);
			for (int j = playerClass.specialties.length(); j-- != 0;) {
				const Specialty@ specialty = playerClass.specialties[j];
				int pu = specialty.powerup ? 1 : 0;
				const jjANIMATION@ anim = jjAnimations[jjAnimSets[animSets[pu][specialty.weapon]] + animations[pu][specialty.weapon]];
				canvas.drawSpriteFromCurFrame(x - 32, y + (j << 6), anim + jjGameTicks / 7 % anim.frameCount);
				canvas.drawString(x, y + (j << 6), "x" + (specialty.maximum == -1 ? "^" : formatInt(specialty.maximum)), STRING::MEDIUM);
			}
			const PlayerSelection@ sel = selections[player.localPlayerID];
			int color, size, radius;
			if (sel.sinceFire != 0 && (sel.sinceFire > 50 || sel.sinceFire & 4 != 0)) {
				color = 40;
				size = 33;
				radius = 10;
			} else {
				color = 41;
				size = 32;
				radius = 8;
			}
			if (i == sel.selection) {
				for (int j = 2; j-- != 0;) {
					bool yf = j != 0;
					for (int k = 2; k-- != 0;) {
						bool xf = k != 0;
						drawSelectionCorner(canvas, x + (xf ? 64 : -64), y + (yf ? 96 : -96), size, size, radius, xf, yf, color);
					}
				}
			}
		}
		return true;
	}
	return weaponHook.drawAmmo(player, canvas);
}
void onLevelLoad() {
	initializeAmmoPresets();
	animSets[0][0] = ANIM::PICKUPS;
	if (jjIsServer) {
		randomSeed = jjUnixTimeMs();
		randomizeClasses(randomSeed);
		jjSTREAM packet;
		packet.push(uint8(1));
		packet.push(randomSeed);
		jjSendPacket(packet);
	}
}
void onLevelBegin() {
	if (jjIsServer) {
		for (int i = 0; i < 9; i++) {
			jjWeapons[i + 1].allowed = true;
			jjWeapons[i + 1].allowedPowerup = true;
		}
	} else {
		jjSendPacket(jjSTREAM());
	}
}
void onMain() {
	weaponHook.processMain();
	for (int i = 32; i-- != 0;) {
		jjPLAYER@ player = jjPlayers[i];
		if (player.health <= 0 || !player.isInGame)
			sinceDeath[i] = 0;
		else
			sinceDeath[i]++;
		if (sinceDeath[i] < selectionTime && closeTo(player.xPos, 0.f) && closeTo(player.xPos, 0.f))
			player.blink = 140;
	}
}
void onPlayer(jjPLAYER@ player) {
	PlayerSelection@ sel = selections[player.localPlayerID];
	if (sinceDeath[player.playerID] == selectionTime || sel.sinceFire > 70) {
		array<const jjANIMATION@>@ anims = ammoPickup.anims[player.localPlayerID];
		anims.resize(0);
		const PlayerClass@ playerClass = availableClasses[sel.selection];
		jjWeapons[WEAPON::BLASTER].maximum = -1;
		jjWeapons[WEAPON::BLASTER].infinite = true;
		player.ammo[WEAPON::BLASTER] = 50;
		player.powerup[WEAPON::BLASTER] = false;
		for (int i = 2; i <= 9; i++) {
			jjWeapons[i].maximum = 0;
			jjWeapons[i].infinite = false;
			player.ammo[i] = 0;
			player.powerup[i] = false;
		}
		for (int i = playerClass.specialties.length(); i-- != 0;) {
			const Specialty@ specialty = playerClass.specialties[i];
			uint weapon = weaponMapping[specialty.weapon];
			bool infinite = specialty.maximum == -1;
			jjWeapons[weapon].maximum = specialty.maximum;
			jjWeapons[weapon].infinite = infinite;
			player.ammo[weapon] = infinite ? 50 : specialty.maximum;
			player.powerup[weapon] = specialty.powerup;
			player.currWeapon = weapon;
			if (!infinite) {
				int pu = specialty.powerup ? 1 : 0;
				const jjANIMATION@ anim = jjAnimations[jjAnimSets[animSets[pu][specialty.weapon]] + animations[pu][specialty.weapon]];
				anims.insertLast(anim);
			}
		}
		player.xPos = player.xOrg;
		player.yPos = player.yOrg;
		player.cameraUnfreeze();
		sel.sinceFire = 0;
		sinceDeath[player.playerID] = selectionTime;
	} else if (sinceDeath[player.playerID] < selectionTime) {
		player.xPos = 0.f;
		player.yPos = 0.f;
		player.ballTime = 1;
		const jjLAYER@ spriteLayer = jjLayers[4];
		player.cameraFreeze(spriteLayer.width << 4, spriteLayer.height << 4, true, true);
		if (sel.sinceFire != 0)
			sel.sinceFire++;
	} else {
		const PlayerClass@ playerClass = availableClasses[sel.selection];
		for (int i = 1; i <= 9; i++) {
			player.powerup[i] = false;
		}
		for (int i = playerClass.specialties.length(); i-- != 0;) {
			const Specialty@ specialty = playerClass.specialties[i];
			uint weapon = weaponMapping[specialty.weapon];
			player.powerup[weapon] = specialty.powerup;
		}
	}
	weaponHook.processPlayer(player);
}
void onPlayerInput(jjPLAYER@ player) {
	PlayerSelection@ sel = selections[player.localPlayerID];
	if (sinceDeath[player.playerID] < selectionTime) {
		if (availableClasses.length() != 0) {
			if (sel.sinceFire == 0) {
				if (player.keyLeft) {
					if (sel.leftDelay == 0) {
						sel.selection--;
						sel.leftDelay = 20;
					} else {
						sel.leftDelay--;
					}
				} else {
					sel.leftDelay = 0;
				}
				if (player.keyRight) {
					if (sel.rightDelay == 0) {
						sel.selection++;
						sel.rightDelay = 20;
					} else {
						sel.rightDelay--;
					}
				} else {
					sel.rightDelay = 0;
				}
				sel.selection %= availableClasses.length();
				if (sel.selection < 0)
					sel.selection += availableClasses.length();
			}
			if (sel.sinceFire == 0 && player.keyFire)
				sel.sinceFire++;
		}
		player.keyDown = false;
		player.keyFire = false;
		player.keyJump = false;
		player.keyLeft = false;
		player.keyRight = false;
		player.keyRun = false;
		player.keySelect = false;
		player.keyUp = false;
	}
	weaponHook.processPlayerInput(player);
}
void onReceive(jjSTREAM &in packet, int clientID) {
	if (!weaponHook.processPacket(packet, clientID)) {
		if (jjIsServer) {
			jjSTREAM response;
			response.push(uint8(1));
			response.push(randomSeed);
			jjSendPacket(response, clientID);
		} else {
			packet.discard(1);
			packet.pop(randomSeed);
			randomizeClasses(randomSeed);
		}
	}
}
