/**
 * Base Actor for non-inventory/weapon things in Square.
 * This actor class should be extended.
 */
class SquareActor : Actor
{
	/**
	 * A handy extension of A_SpawnItemEx that spawns multiple actors at once.
	 * Returns the number of actors successfully spawned.
	 */
	int SQ_SpawnMultiple(class<Actor> classtype, int spawnCount = 1, Vector2 xofs = (0,0), Vector2 yofs = (0,0), Vector2 zofs = (0,0), Vector2 xvel = (0,0), Vector2 yvel = (0,0), Vector2 zvel = (0,0), Vector2 angle = (0,0), int flags = 0, int failchance = 0, int tid = 0)
	{
		int spawnedCount = 0;
		for(int i = 0; i < spawnCount; i++) {
			if(A_SpawnItemEx(classtype,
				frandom[SquareEffectRandom](xofs.x, xofs.y),
				frandom[SquareEffectRandom](yofs.x, yofs.y),
				frandom[SquareEffectRandom](zofs.x, zofs.y),
				frandom[SquareEffectRandom](xvel.x, xvel.y),
				frandom[SquareEffectRandom](yvel.x, yvel.y),
				frandom[SquareEffectRandom](zvel.x, zvel.y),
				frandom[SquareEffectRandom](angle.x, angle.y),
				flags,
				failchance,
				tid)) {
				spawnedCount++;
			}
		}
		return spawnedCount;
	}
}

/**
 * Base Square Voice-Over object characteristics.
 * "Given" to players via enemies or scripts.
 * This actor class should be extended.
 */
class SquareVO : CustomInventory
{
	Default
	{
		+INVENTORY.QUIET;
		+INVENTORY.ALWAYSPICKUP;
		+INVENTORY.AUTOACTIVATE;
	}

	States
	{
	Spawn:
		TNT1 A -1;
		Stop;
	Pickup:
		TNT1 A 0 ResolveState('PlaySound'); // Virtual Jump (for overriding)
		Stop;
	PlaySound:
		TNT1 A 0 ACS_NamedExecuteAlways('VO_Jabber', 0, args[0], args[1]);
		Stop;
	}
}

/**
 * An actor that spawns items based on gametype, in order to spawn completely
 * different classes using one spot, and then removes itself.
 * USE WITH CARE!
 * This actor class should be extended.
 */
class SquareGameTypeSpawner : MapSpot
{
	Default {
		+NOINTERACTION;
	}

	States
	{
	Spawn:
		TNT1 A 0; // [XA] Gonna simplify this later. Should be able to check gametype directly in ZScript.
		TNT1 A 0 A_JumpIf(CallACS('Map_GameType') == 0, 'SingleSpawn'); // for overloading
		TNT1 A 0 A_JumpIf(CallACS('Map_GameType') == 1, 'CoopSpawn'); // for overloading
		TNT1 A 0 A_JumpIf(CallACS('Map_GameType') == 2, 'DeathmatchSpawn'); // for overloading
		TNT1 A 0 A_JumpIf(CallACS('Map_GameType') == 3, 'TitlemapSpawn'); // for overloading
		TNT1 A 0 A_JumpIf(256, 'OtherSpawn'); // for overloading
		Stop;
	SingleSpawn:
		TNT1 A 0;
		Stop;
	CoopSpawn:
		TNT1 A 0;
		Stop;
	DeathmatchSpawn:
		TNT1 A 0;
		Stop;
	TitlemapSpawn:
		TNT1 A 0;
		Stop;
	OtherSpawn:
		TNT1 A 0;
		Stop;
	}
}

/**
 * An actor that, when activated, spawns a thing using
 * its first argument as a SpawnID.
 */
class SquareTimeAttackSpawner : SwitchableDecoration
{
	Default
	{
		+NOBLOCKMAP;
		+NOSECTOR;
		+NOGRAVITY;
		+DONTSPLASH;
		RenderStyle 'None';
	}

	States
	{
	Spawn:
		TNT1 A -1;
		Loop;
	Active:
		TNT1 A 0 ACS_NamedExecuteAlways('TimeAttack_Spawn', 0, args[0], angle);
		Goto Spawn;
	}
}

/**
 * An actor that, when activated, spawns a thing using
 * its first argument as a SpawnID and removes itself.
 * This actor counts as a kill until removed, meaning
 * the map kill count factors in enemies spawned by this
 * method. Hooray!
 */
class SquareMonsterSpawner : SwitchableDecoration
{
	Default
	{
		+NOBLOCKMAP;
		+NOSECTOR;
		+NOGRAVITY;
		+DONTSPLASH;
		+COUNTKILL;
		RenderStyle 'None';
	}

	States
	{
	Spawn:
		TNT1 A -1;
		Loop;
	Active:
		TNT1 A 0 A_SpawnMonster();
		Stop;
	}

	void A_SpawnMonster(int spawnnum = 0, int nofog = -1)
	{
		// default spawn number to args[0] if we don't pass anything in.
		if(spawnnum == 0) {
			spawnnum = args[0];
		}

		// should we skip spawning teleport fog? default to args[1] if not specified.
		if(nofog == -1) {
			nofog = args[1];
		}

		// Spawn the actor. If successful, remove the COUNTKILL flag
		// from this spawner so the overall monster count is maintained.
		Class<Actor> type = GetSpawnableType(spawnnum);
		if (type != null) {
			if (!(level.nomonsters || sv_nomonsters) || !(GetDefaultByType (type).bIsMonster)) {
				Actor newMonster = Spawn(type, self.pos, ALLOW_REPLACE);
				if(newMonster) {
					newMonster.angle = self.angle;
					self.ClearCounters(); // decrements countkill
					if(nofog <= 0) {
						Spawn("TeleportFog", self.pos, ALLOW_REPLACE);
					}
				}
			}
		}
	}

	/**
	 * ACS-callable static spawner function. Use via ScriptCall, like so:
	 *
	 * ScriptCall("SquareMonsterSpawner", "SpawnMonster", [tid], [spawnNum], [nofog]);
	 *
	 * If spawnNum is zero, the monster spawner's args[0] field is used to
	 * determine what to spawn.
	 */
	static void SpawnMonster(int tid, int spawnnum = 0, int nofog = -1)
	{
		// iterate through all SquareMonsterSpawners that match the given TID,
		// then spawn 'em. 'Nuff said.
		ActorIterator iter = ActorIterator.Create(tid, 'SquareMonsterSpawner');
		let spawner = SquareMonsterSpawner(iter.Next());
		while (spawner != null) {
			spawner.A_SpawnMonster(spawnnum, nofog);
			SquareMonsterSpawner oldspawner = spawner;
			spawner = SquareMonsterSpawner(iter.Next());
			// only destroy the actor after fetching the next one, else the
			// iterator aborts early for whatever reason. welp.
			oldspawner.Destroy();
		}
	}
}


/** If the player has these, do not draw the status bar. */

class StatusbarRemover : Inventory
{
	Default
	{
		Inventory.Amount 1;
		Inventory.MaxAmount 16; // for call stacking
		Inventory.InterHubAmount 0;
		+INVENTORY.QUIET;
		+INVENTORY.ALWAYSPICKUP;
		+INVENTORY.NOSCREENFLASH;
	}

	States
	{
	Spawn:
		TNT1 A 1;
		Stop;
	}
}

class IntermissionModeItem : StatusbarRemover {}
class CutsceneModeItem : StatusbarRemover {}

// Special case for telescope special.
class TelescopeCheck : Inventory {}

/**********************************
 * All Monsters
 **********************************/
class SquareMonster : Actor
{
	Default
	{
		Monster;
		Health 100;
		Radius 20;
		Height 48;
		Speed 8;
		FastSpeed 16;
		MaxStepHeight 16;
		Mass 1000;
		PainChance 64;
		BloodType 'SquareBlood';
		PainSound 'monster/pain';

		+NEVERRESPAWN;
		+NOINFIGHTING;
		+RANDOMIZE; // stagger most A_Look calls on spawn

		MaxTargetRange 2048;
	}

	States
	{
	Death.Massacre:
		RIPM A -1 A_RipInPeace();
		Stop;
	}

	void A_RipInPeace()
	{
		A_NoBlocking();
		A_StopSound(5); // for tritankles
		self.bFloatBob = false;
		self.bNoGravity = false;
	}
}

/** NOTHING! */

class Nothing : Actor {}
