#include "info.h"
#include "a_pickups.h"
#include "a_artifacts.h"
#include "gstrings.h"
#include "p_local.h"
#include "p_enemy.h"
#include "s_sound.h"
#include "p_lnspec.h"
#include "m_random.h"

#define TELEPORT_LIFE 1

static FRandom pr_telestarts ("TeleStarts");
static FRandom pr_teledm ("TeleDM");

void A_TeloSpawnA (AActor *);
void A_TeloSpawnB (AActor *);
void A_TeloSpawnC (AActor *);
void A_TeloSpawnD (AActor *);
void A_CheckTeleRing (AActor *);
void P_TeleportToPlayerStarts (AActor *victim);
void P_TeleportToDeathmatchStarts (AActor *victim);

// Teleport Other Artifact --------------------------------------------------

class AArtiTeleportOther : public AInventory
{
	DECLARE_ACTOR (AArtiTeleportOther, AInventory)
public:
	bool Use (bool pickup);
};

FState AArtiTeleportOther::States[] =
{
#define S_ARTI_TELOTHER1 0
	S_NORMAL (TELO, 'A',	5, NULL					    , &States[S_ARTI_TELOTHER1+1]),
	S_NORMAL (TELO, 'B',	5, NULL					    , &States[S_ARTI_TELOTHER1+2]),
	S_NORMAL (TELO, 'C',	5, NULL					    , &States[S_ARTI_TELOTHER1+3]),
	S_NORMAL (TELO, 'D',	5, NULL					    , &States[S_ARTI_TELOTHER1]),
};

IMPLEMENT_ACTOR (AArtiTeleportOther, Hexen, 10040, 17)
	PROP_Flags (MF_SPECIAL)
	PROP_Flags2 (MF2_FLOATBOB)
	PROP_SpawnState (S_ARTI_TELOTHER1)
	PROP_Inventory_DefMaxAmount
	PROP_Inventory_FlagsSet (IF_INVBAR|IF_PICKUPFLASH|IF_FANCYPICKUPSOUND)
	PROP_Inventory_Icon ("ARTITELO")
	PROP_Inventory_PickupSound ("misc/p_pkup")
	PROP_Inventory_PickupMessage("$TXT_ARTITELEPORTOTHER")
END_DEFAULTS

// Teleport Other FX --------------------------------------------------------

class ATelOtherFX1 : public AActor
{
	DECLARE_ACTOR (ATelOtherFX1, AActor)
public:
	int DoSpecialDamage (AActor *target, int damage);
};

FState ATelOtherFX1::States[] =
{
#define S_TELO_FX1 0
	S_BRIGHT (TRNG, 'E',    5, NULL                         , &States[S_TELO_FX1+1]),
	S_BRIGHT (TRNG, 'D',    4, NULL                         , &States[S_TELO_FX1+2]),
	S_BRIGHT (TRNG, 'C',    3, A_TeloSpawnC                 , &States[S_TELO_FX1+3]),
	S_BRIGHT (TRNG, 'B',    3, A_TeloSpawnB                 , &States[S_TELO_FX1+4]),
	S_BRIGHT (TRNG, 'A',    3, A_TeloSpawnA                 , &States[S_TELO_FX1+5]),
	S_BRIGHT (TRNG, 'B',    3, A_TeloSpawnB                 , &States[S_TELO_FX1+6]),
	S_BRIGHT (TRNG, 'C',    3, A_TeloSpawnC                 , &States[S_TELO_FX1+7]),
	S_BRIGHT (TRNG, 'D',    3, A_TeloSpawnD                 , &States[S_TELO_FX1+2]),

#define S_TELO_FX_DONE (S_TELO_FX1+8)
	S_BRIGHT (TRNG, 'E',    3, NULL                         , NULL),

#define S_TELO_FX2 (S_TELO_FX_DONE+1)
	S_BRIGHT (TRNG, 'B',    4, NULL                         , &States[S_TELO_FX2+1]),
	S_BRIGHT (TRNG, 'C',    4, NULL                         , &States[S_TELO_FX2+2]),
	S_BRIGHT (TRNG, 'D',    4, NULL                         , &States[S_TELO_FX2+3]),
	S_BRIGHT (TRNG, 'C',    4, NULL                         , &States[S_TELO_FX2+4]),
	S_BRIGHT (TRNG, 'B',    4, NULL                         , &States[S_TELO_FX2+5]),
	S_BRIGHT (TRNG, 'A',    4, A_CheckTeleRing              , &States[S_TELO_FX2+0]),

#define S_TELO_FX3 (S_TELO_FX2+6)
	S_BRIGHT (TRNG, 'C',    4, NULL                         , &States[S_TELO_FX3+1]),
	S_BRIGHT (TRNG, 'D',    4, NULL                         , &States[S_TELO_FX3+2]),
	S_BRIGHT (TRNG, 'C',    4, NULL                         , &States[S_TELO_FX3+3]),
	S_BRIGHT (TRNG, 'B',    4, NULL                         , &States[S_TELO_FX3+4]),
	S_BRIGHT (TRNG, 'A',    4, NULL                         , &States[S_TELO_FX3+5]),
	S_BRIGHT (TRNG, 'B',    4, A_CheckTeleRing              , &States[S_TELO_FX3+0]),

#define S_TELO_FX4 (S_TELO_FX3+6)
	S_BRIGHT (TRNG, 'D',    4, NULL                         , &States[S_TELO_FX4+1]),
	S_BRIGHT (TRNG, 'C',    4, NULL                         , &States[S_TELO_FX4+2]),
	S_BRIGHT (TRNG, 'B',    4, NULL                         , &States[S_TELO_FX4+3]),
	S_BRIGHT (TRNG, 'A',    4, NULL                         , &States[S_TELO_FX4+4]),
	S_BRIGHT (TRNG, 'B',    4, NULL                         , &States[S_TELO_FX4+5]),
	S_BRIGHT (TRNG, 'C',    4, A_CheckTeleRing              , &States[S_TELO_FX4+0]),

#define S_TELO_FX5 (S_TELO_FX4+6)
	S_BRIGHT (TRNG, 'C',    4, NULL                         , &States[S_TELO_FX5+1]),
	S_BRIGHT (TRNG, 'B',    4, NULL                         , &States[S_TELO_FX5+2]),
	S_BRIGHT (TRNG, 'A',    4, NULL                         , &States[S_TELO_FX5+3]),
	S_BRIGHT (TRNG, 'B',    4, NULL                         , &States[S_TELO_FX5+4]),
	S_BRIGHT (TRNG, 'C',    4, NULL                         , &States[S_TELO_FX5+5]),
	S_BRIGHT (TRNG, 'D',    4, A_CheckTeleRing              , &States[S_TELO_FX5+0])
};

IMPLEMENT_ACTOR (ATelOtherFX1, Any, -1, 0)
	PROP_DamageLong (10001)
	PROP_Flags (MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY|MF_NOBLOCKMAP)
	PROP_Flags2 (MF2_NOTELEPORT)
	PROP_Flags3 (MF3_BLOODLESSIMPACT)
	PROP_RadiusFixed (16)
	PROP_HeightFixed (16)
	PROP_SpeedFixed (20)

	PROP_SpawnState (S_TELO_FX1)
	PROP_DeathState (S_TELO_FX_DONE)
END_DEFAULTS

class ATelOtherFX2 : public ATelOtherFX1
{
	DECLARE_STATELESS_ACTOR (ATelOtherFX2, ATelOtherFX1)
};

IMPLEMENT_STATELESS_ACTOR (ATelOtherFX2, Any, -1, 0)
	PROP_SpeedFixed (16)
	PROP_SpawnState (S_TELO_FX2)
END_DEFAULTS

class ATelOtherFX3 : public ATelOtherFX1
{
	DECLARE_STATELESS_ACTOR (ATelOtherFX3, ATelOtherFX1)
};

IMPLEMENT_STATELESS_ACTOR (ATelOtherFX3, Any, -1, 0)
	PROP_SpeedFixed (16)
	PROP_SpawnState (S_TELO_FX3)
END_DEFAULTS

class ATelOtherFX4 : public ATelOtherFX1
{
	DECLARE_STATELESS_ACTOR (ATelOtherFX4, ATelOtherFX1)
};

IMPLEMENT_STATELESS_ACTOR (ATelOtherFX4, Any, -1, 0)
	PROP_SpeedFixed (16)
	PROP_SpawnState (S_TELO_FX4)
END_DEFAULTS

class ATelOtherFX5 : public ATelOtherFX1
{
	DECLARE_STATELESS_ACTOR (ATelOtherFX5, ATelOtherFX1)
};

IMPLEMENT_STATELESS_ACTOR (ATelOtherFX5, Any, -1, 0)
	PROP_SpeedFixed (16)
	PROP_SpawnState (S_TELO_FX5)
END_DEFAULTS

static void TeloSpawn (AActor *source, const PClass *type)
{
	AActor *fx;

	fx = Spawn (type, source->x, source->y, source->z, ALLOW_REPLACE);
	if (fx)
	{
		fx->special1 = TELEPORT_LIFE;			// Lifetime countdown
		fx->angle = source->angle;
		fx->target = source->target;
		fx->momx = source->momx >> 1;
		fx->momy = source->momy >> 1;
		fx->momz = source->momz >> 1;
	}
}

void A_TeloSpawnA (AActor *actor)
{
	TeloSpawn (actor, RUNTIME_CLASS(ATelOtherFX2));
}

void A_TeloSpawnB (AActor *actor)
{
	TeloSpawn (actor, RUNTIME_CLASS(ATelOtherFX3));
}

void A_TeloSpawnC (AActor *actor)
{
	TeloSpawn (actor, RUNTIME_CLASS(ATelOtherFX4));
}

void A_TeloSpawnD (AActor *actor)
{
	TeloSpawn (actor, RUNTIME_CLASS(ATelOtherFX5));
}

void A_CheckTeleRing (AActor *actor)
{
	if (actor->special1-- <= 0)
	{
		actor->SetState (actor->DeathState);
	}
}

//===========================================================================
//
// Activate Teleport Other
//
//===========================================================================

bool AArtiTeleportOther::Use (bool pickup)
{
	AActor *mo;

	mo = P_SpawnPlayerMissile (Owner, RUNTIME_CLASS(ATelOtherFX1));
	if (mo)
	{
		mo->target = Owner;
	}
	return true;
}

//===========================================================================
//
// Perform Teleport Other
//
//===========================================================================

int ATelOtherFX1::DoSpecialDamage (AActor *target, int damage)
{
	if ((target->flags3 & MF3_ISMONSTER || target->player != NULL) &&
		!(target->flags2 & MF2_BOSS) &&
		!(target->flags3 & MF3_NOTELEOTHER))
	{
		if (target->player)
		{
			if (deathmatch)
				P_TeleportToDeathmatchStarts (target);
			else
				P_TeleportToPlayerStarts (target);
		}
		else
		{
			// If death action, run it upon teleport
			if (target->flags3 & MF3_ISMONSTER && target->special)
			{
				target->RemoveFromHash ();
				LineSpecials[target->special] (NULL, level.flags & LEVEL_ACTOWNSPECIAL
					? target : this->target, false, target->args[0], target->args[1],
					target->args[2], target->args[3], target->args[4]);
				target->special = 0;
			}

			// Send all monsters to deathmatch spots
			P_TeleportToDeathmatchStarts (target);
		}
	}
	return -1;
}

//===========================================================================
//
// P_TeleportToPlayerStarts
//
//===========================================================================

void P_TeleportToPlayerStarts (AActor *victim)
{
	int i,selections=0;
	fixed_t destX,destY;
	angle_t destAngle;

	for (i = 0; i < MAXPLAYERS;i++)
	{
	    if (!playeringame[i]) continue;
		selections++;
	}
	i = pr_telestarts() % selections;
	destX = playerstarts[i].x << FRACBITS;
	destY = playerstarts[i].y << FRACBITS;
	destAngle = ANG45 * (playerstarts[i].angle/45);
	P_Teleport (victim, destX, destY, ONFLOORZ, destAngle, true, true, false);
}

//===========================================================================
//
// P_TeleportToDeathmatchStarts
//
//===========================================================================

void P_TeleportToDeathmatchStarts (AActor *victim)
{
	unsigned int i, selections;
	fixed_t destX,destY;
	angle_t destAngle;

	selections = deathmatchstarts.Size ();
	if (selections > 0)
	{
		i = pr_teledm() % selections;
		destX = deathmatchstarts[i].x << FRACBITS;
		destY = deathmatchstarts[i].y << FRACBITS;
		destAngle = ANG45 * (deathmatchstarts[i].angle/45);
		P_Teleport (victim, destX, destY, ONFLOORZ, destAngle, true, true, false);
	}
	else
	{
	 	P_TeleportToPlayerStarts (victim);
	}
}
