

class MAP56Uranium : Inventory {
	default {
		Inventory.PickupMessage "Picked up the fuel cell.";
	}

	states {
		spawn:
			M56U A -1;
	}
}

class MAP56DepositedUranium : Actor {
	default {
		Radius 1;
		Height 1;
	}

	states {
		spawn:
			M56U A -1;
	}
}

class Map56Fist : Weapon {
	int fistTic;

	default {
		// Hack to hide in Alt HUD
		+WEAPON.POWERED_UP;
		// Just in case other code relies on a powered-up weapon having a sister weapon.
		Weapon.SisterWeapon 'Fist';
	}

	states {
		ready:
			M56F A 1 A_WeaponReady;
			Loop;
		deselect:
			M56F A 1 A_Lower;
			loop;
		select:
			M56F A 1 A_Raise;
			loop;
		fire:
			M56F A 1 Punch;
			loop;
	}

	private action void Punch() {
		let tic = invoker.fistTic++;
		if (tic > 14) {
			tic = 14;
		}

		let fistPosX = tic * -4;
		let fistPosY = sin(tic * 18) * 16;
		let fistZoom = cos((tic + 4) * 14) / 2.5 + 1;

		A_WeaponOffset(fistPosX, fistPosY);
		A_OverlayScale(PSP_WEAPON, fistZoom);
	}
}

class Map56 {
	static play void Punch(Actor player) {
		PlayerPawn(player).FireWeapon(null);
	}

	static void QuickSort(in out Array<Actor> a, int lo, int hi) {
		if (lo >= 0 && hi >= 0 && lo < hi) {
			let p = Partition(a, lo, hi);
			QuickSort(a, lo, p);
			QuickSort(a, p + 1, hi);
		}
	}

	static bool SortPosA(Vector3 a, Vector3 b) {
		if (a.y == b.y) {
			return a.x < b.x;
		}
		return a.y < b.y;
	}

	static bool SortPosB(Vector3 a, Vector3 b) {
		if (a.y == b.y) {
			return a.x > b.x;
		}
		return a.y > b.y;
	}

	static int Partition(in out Array<Actor> a, int lo, int hi) {
		let pivot = a[lo].pos;
		let i = lo - 1;
		let j = hi + 1;
		while (true) {
			do {
				++i;
			} while (SortPosA(a[i].pos, pivot));
			do {
				--j;
			} while (SortPosB(a[j].pos, pivot));
			if (i >= j) {
				break;
			}
			let t = a[i];
			a[i] = a[j];
			a[j] = t;
		}
		return j;
	}

	static play void FinalFightTeleport(Actor player, int tid) {
		let it = level.CreateActorIterator(9);
		let tpDest = level.CreateActorIterator(tid).Next();

		Array<Actor> actorList;
		while (true) {
			let actor = it.Next();
			if (actor == null) break;
			actorList.Push(actor);
		}
		if (actorList.Size() == 0) {
			return;
		}

		QuickSort(actorList, 0, actorList.Size() - 1);

		for (let i = 0; i < actorList.Size(); i++) {
			let actor = actorList[i];
			if (actor.Teleport(tpDest.pos, tpDest.angle, TELF_DESTFOG)) {
				actor.ChangeTid(900);
				actor.SoundAlert(player);
				return;
			}
		}
	}

	static int GetUsableAmmo(Actor mo) {
		let sum = 0;

		// Designed to minimize iterations through the inventory.
		array<Inventory> usedAmmo;
		array<Weapon> weapons;
		for (let inv = mo.inv; inv; inv = inv.inv) {
			let weap = Weapon(inv);
			if (weap != null) {
				weapons.Push(weap);
			}
		}

		for (let i = 0; i < weapons.Size(); i++) {
			let weap = weapons[i];
			if (weap.ammoType1 != null) {
				let am = mo.FindInventory(weap.ammoType1);
				if (am != null && am.amount >= weap.ammoUse1 && usedAmmo.Find(am) == usedAmmo.Size()) {
					usedAmmo.Push(am);
				}
			}
			if (weap.ammoType2 != null) {
				let am = mo.FindInventory(weap.ammoType2);
				if (am != null && am.amount >= weap.ammoUse2 && usedAmmo.Find(am) == usedAmmo.Size()) {
					usedAmmo.Push(am);
				}
			}
		}

		for (let i = 0; i < usedAmmo.Size(); i++) {
			sum += usedAmmo[i].amount;
		}

		return sum;
	}

	static play bool ArePlayersLowOnAmmo() {
		let highestAmount = 0;

		for (let i = 0; i < MAXPLAYERS; i++) {
			if (!playeringame[i])
				continue;

			let sum = GetUsableAmmo(players[i].mo);

			if (sum > highestAmount) {
				highestAmount = sum;
			}
		}

		return highestAmount < 10;
	}
}

class Map56MirrorArchvile : Actor {
	default {
		+ONLYVISIBLEINMIRRORS;

		Height 0;
	}

	states {
		spawn:
			VILE AB 10;
			loop;
	}
}

class Map56DVDLogo : Actor {
	private vector3 bounceVel;
	private int colorIndex;
	private double x0;
	private double x1;
	private double z0;
	private double z1;

	default {
		+WALLSPRITE;
		+NOCLIP;
		+NOGRAVITY;
	}

	states {
		spawn:
			TNT1 A 0;
			TNT1 A 0 {
				bounceVel = (1, 0, -1);
				x0 = pos.x + 98;
				x1 = pos.x - 98;
				z0 = pos.z + 40;
				z1 = pos.z - 48;
				Recolor();
			}
			M56D A 1 {
				let newPos = pos + bounceVel;

				if (newPos.x > x0) {
					newPos.x = x0;
					bounceVel.x = -bounceVel.x;
					Recolor();
				} else if (newPos.x < x1) {
					newPos.x = x1;
					bounceVel.x = -bounceVel.x;
					Recolor();
				}

				if (newPos.z > z0) {
					newPos.z = z0;
					bounceVel.z = -bounceVel.z;
					Recolor();
				} else if (newPos.z < z1) {
					newPos.z = z1;
					bounceVel.z = -bounceVel.z;
					Recolor();
				}

				SetOrigin(newPos, true);
			}
			wait;
	}

	private void Recolor() {
		let r = 0, g = 0, b = 0;
		switch (random(0, 2)) {
			case 0:
				r = random(0, 255);
				if (random(0, 1)) g = 255; else b = 255;
				break;
			case 1:
				g = random(0, 255);
				if (random(0, 1)) b = 255; else r = 255;
				break;
			case 2:
				b = random(0, 255);
				if (random(0, 1)) r = 255; else g = 255;
				break;
		}
		SetShade(color(r, g, b));
	}
}
