/*
#include "actor.h"
#include "p_enemy.h"
#include "a_action.h"
#include "m_random.h"
#include "p_local.h"
#include "a_doomglobal.h"
#include "s_sound.h"
#include "r_data/r_translate.h"
#include "thingdef/thingdef.h"
#include "g_level.h"
*/

#define MARINE_PAIN_CHANCE 160

static FRandom pr_m_refire ("SMarineRefire");
static FRandom pr_m_punch ("SMarinePunch");
static FRandom pr_m_gunshot ("SMarineGunshot");
static FRandom pr_m_saw ("SMarineSaw");
static FRandom pr_m_fireshotgun2 ("SMarineFireSSG");

IMPLEMENT_CLASS (AScriptedMarine)

void AScriptedMarine::Serialize (FArchive &arc)
{
	Super::Serialize (arc);

	if (arc.IsStoring ())
	{
		arc.WriteSprite (SpriteOverride);
	}
	else
	{
		SpriteOverride = arc.ReadSprite ();
	}
	arc << CurrentWeapon;
}

void AScriptedMarine::Activate (AActor *activator)
{
	if (flags2 & MF2_DORMANT)
	{
		flags2 &= ~MF2_DORMANT;
		tics = 1;
	}
}

void AScriptedMarine::Deactivate (AActor *activator)
{
	if (!(flags2 & MF2_DORMANT))
	{
		flags2 |= MF2_DORMANT;
		tics = -1;
	}
}

bool AScriptedMarine::GetWeaponStates(int weap, FState *&melee, FState *&missile)
{
	static ENamedName WeaponNames[] = 
	{
		NAME_None,
		NAME_Fist,
		NAME_Berserk,
		NAME_Chainsaw,
		NAME_Pistol,
		NAME_Shotgun,
		NAME_SSG,
		NAME_Chaingun,
		NAME_Rocket,
		NAME_Plasma,
		NAME_Railgun,
		NAME_BFG
	};

	if (weap < WEAPON_Dummy || weap > WEAPON_BFG) weap = WEAPON_Dummy;

	melee = FindState(NAME_Melee, WeaponNames[weap], true);
	missile = FindState(NAME_Missile, WeaponNames[weap], true);

	return melee != NULL || missile != NULL;
}

void AScriptedMarine::BeginPlay ()
{
	Super::BeginPlay ();

	// Set the current weapon
	for(int i=WEAPON_Dummy; i<=WEAPON_BFG; i++)
	{
		FState *melee, *missile;
		if (GetWeaponStates(i, melee, missile))
		{
			if (melee == MeleeState && missile == MissileState)
			{
				CurrentWeapon = i;
			}
		}
	}
}

void AScriptedMarine::Tick ()
{
	Super::Tick ();

	// Override the standard sprite, if desired
	if (SpriteOverride != 0 && sprite == SpawnState->sprite)
	{
		sprite = SpriteOverride;
	}

	if (special1 != 0)
	{
		if (CurrentWeapon == WEAPON_SuperShotgun)
		{ // Play SSG reload sounds
			int ticks = level.maptime - special1;
			if (ticks < 47)
			{
				switch (ticks)
				{
				case 14:
					S_Sound (this, CHAN_WEAPON, "weapons/sshoto", 1, ATTN_NORM, true);	// [BC] Inform the clients.
					break;
				case 28:
					S_Sound (this, CHAN_WEAPON, "weapons/sshotl", 1, ATTN_NORM, true);	// [BC] Inform the clients.
					break;
				case 41:
					S_Sound (this, CHAN_WEAPON, "weapons/sshotc", 1, ATTN_NORM, true);	// [BC] Inform the clients.
					break;
				}
			}
			else
			{
				special1 = 0;
			}
		}
		else
		{ // Wait for a long refire time
			if (level.maptime >= special1)
			{
				special1 = 0;
			}
			else
			{
				flags |= MF_JUSTATTACKED;
			}
		}
	}
}

//============================================================================
//
// A_M_Refire
//
//============================================================================

DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Refire)
{
	ACTION_PARAM_START(1);
	ACTION_PARAM_BOOL(ignoremissile, 0);

	// [BC] Let the server do this.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL || self->target->health <= 0)
	{
		if (self->MissileState && pr_m_refire() < 160)
		{ // Look for a new target most of the time
			if (P_LookForPlayers (self, true, NULL) && P_CheckMissileRange (self))
			{ // Found somebody new and in range, so don't stop shooting
				return;
			}
		}

		// [BC] Update the thing's state.
		if ( NETWORK_GetState( ) == NETSTATE_SERVER )
			SERVERCOMMANDS_SetThingFrame( self, self->state + 1 );

		self->SetState (self->state + 1);
		return;
	}
	if (((ignoremissile || self->MissileState == NULL) && !self->CheckMeleeRange ()) ||
		!P_CheckSight (self, self->target) ||
		pr_m_refire() < 4)	// Small chance of stopping even when target not dead
	{
		// [BC] Update the thing's state.
		if ( NETWORK_GetState( ) == NETSTATE_SERVER )
			SERVERCOMMANDS_SetThingFrame( self, self->state + 1 );

		self->SetState (self->state + 1);
	}
}

//============================================================================
//
// A_M_SawRefire
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_M_SawRefire)
{
	// [BC] Let the server do this.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL || self->target->health <= 0)
	{
		// [BC] Update the thing's state.
		if ( NETWORK_GetState( ) == NETSTATE_SERVER )
			SERVERCOMMANDS_SetThingFrame( self, self->state + 1 );

		self->SetState (self->state + 1);
		return;
	}
	if (!self->CheckMeleeRange ())
	{
		// [BC] Update the thing's state.
		if ( NETWORK_GetState( ) == NETSTATE_SERVER )
			SERVERCOMMANDS_SetThingFrame( self, self->state + 1 );

		self->SetState (self->state + 1);
	}
}

//============================================================================
//
// A_MarineNoise
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_MarineNoise)
{
	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (static_cast<AScriptedMarine *>(self)->CurrentWeapon == AScriptedMarine::WEAPON_Chainsaw)
	{
		S_Sound (self, CHAN_WEAPON, "weapons/sawidle", 1, ATTN_NORM, true);	// [BC] Inform the clients.
	}
}

//============================================================================
//
// A_MarineChase
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_MarineChase)
{
	CALL_ACTION(A_MarineNoise, self);
	A_Chase (self);
}

//============================================================================
//
// A_MarineLook
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_MarineLook)
{
	CALL_ACTION(A_MarineNoise, self);
	CALL_ACTION(A_Look, self);
}

//============================================================================
//
// A_M_Saw
//
//============================================================================

DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Saw)
{
	ACTION_PARAM_START(4);
	ACTION_PARAM_SOUND(fullsound, 0);
	ACTION_PARAM_SOUND(hitsound, 1);
	ACTION_PARAM_INT(damage, 2);
	ACTION_PARAM_CLASS(pufftype, 3);

	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	if (pufftype == NULL) pufftype = PClass::FindClass(NAME_BulletPuff);
	if (damage == 0) damage = 2;

	A_FaceTarget (self);
	if (self->CheckMeleeRange ())
	{
		angle_t 	angle;
		AActor		*linetarget;

		damage *= (pr_m_saw()%10+1);
		angle = self->angle + (pr_m_saw.Random2() << 18);
		
		P_LineAttack (self, angle, MELEERANGE+1,
					P_AimLineAttack (self, angle, MELEERANGE+1, &linetarget), damage,
					NAME_Melee, pufftype, false, &linetarget);

		if (!linetarget)
		{
			S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM, true);	// [BC] Inform the clients.
			return;
		}
		S_Sound (self, CHAN_WEAPON, hitsound, 1, ATTN_NORM, true);	// [BC] Inform the clients.
			
		// turn to face target
		angle = R_PointToAngle2 (self->x, self->y, linetarget->x, linetarget->y);
		if (angle - self->angle > ANG180)
		{
			if (angle - self->angle < (angle_t)(-ANG90/20))
				self->angle = angle + ANG90/21;
			else
				self->angle -= ANG90/20;
		}
		else
		{
			if (angle - self->angle > ANG90/20)
				self->angle = angle - ANG90/21;
			else
				self->angle += ANG90/20;
		}
	}
	else
	{
		S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM, true);	// [BC] Inform the clients.
	}
	//A_Chase (self);
}

//============================================================================
//
// A_M_Punch
//
//============================================================================

static void MarinePunch(AActor *self, int damagemul)
{
	angle_t 	angle;
	int 		damage;
	int 		pitch;
	AActor		*linetarget;

	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	damage = ((pr_m_punch()%10+1) << 1) * damagemul;

	A_FaceTarget (self);
	angle = self->angle + (pr_m_punch.Random2() << 18);
	pitch = P_AimLineAttack (self, angle, MELEERANGE, &linetarget);
	P_LineAttack (self, angle, MELEERANGE, pitch, damage, NAME_Melee, NAME_BulletPuff, true, &linetarget);

	// turn to face target
	if (linetarget)
	{
		S_Sound (self, CHAN_WEAPON, "*fist", 1, ATTN_NORM, true);	// [BC] Inform the clients.
		self->angle = R_PointToAngle2 (self->x, self->y, linetarget->x, linetarget->y);

		// [BB] Update the thing's angle.
		if ( NETWORK_GetState( ) == NETSTATE_SERVER )
			SERVERCOMMANDS_SetThingAngle( self );
	}
}

DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Punch)
{
	ACTION_PARAM_START(1);
	ACTION_PARAM_INT(mult, 0);

	MarinePunch(self, mult);
}

//============================================================================
//
// P_GunShot2
//
//============================================================================

void P_GunShot2 (AActor *mo, bool accurate, int pitch, const PClass *pufftype)
{
	angle_t 	angle;
	int 		damage;
		
	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	damage = 5*(pr_m_gunshot()%3+1);
	angle = mo->angle;

	if (!accurate)
	{
		angle += pr_m_gunshot.Random2 () << 18;
	}

	P_LineAttack (mo, angle, MISSILERANGE, pitch, damage, NAME_Hitscan, pufftype);
}

//============================================================================
//
// A_M_FirePistol
//
//============================================================================

DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_FirePistol)
{
	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	ACTION_PARAM_START(1);
	ACTION_PARAM_BOOL(accurate, 0);

	S_Sound (self, CHAN_WEAPON, "weapons/pistol", 1, ATTN_NORM, true);	// [BC] Inform the clients.
	A_FaceTarget (self);
	P_GunShot2 (self, accurate, P_AimLineAttack (self, self->angle, MISSILERANGE),
		PClass::FindClass(NAME_BulletPuff));
}

//============================================================================
//
// A_M_FireShotgun
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_M_FireShotgun)
{
	int pitch;

	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	S_Sound (self, CHAN_WEAPON,  "weapons/shotgf", 1, ATTN_NORM, true);	// [BC] Inform the clients.
	A_FaceTarget (self);
	pitch = P_AimLineAttack (self, self->angle, MISSILERANGE);
	for (int i = 0; i < 7; ++i)
	{
		P_GunShot2 (self, false, pitch, PClass::FindClass(NAME_BulletPuff));
	}
	self->special1 = level.maptime + 27;
}

//============================================================================
//
// A_M_CheckAttack
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_M_CheckAttack)
{
	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->special1 != 0 || self->target == NULL)
	{
		// [Dusk] Update the state to clients
		if ( NETWORK_GetState( ) == NETSTATE_SERVER )
			SERVERCOMMANDS_SetThingFrame( self, self->FindState("SkipAttack") );

		self->SetState (self->FindState("SkipAttack"));
	}
	else
	{
		A_FaceTarget (self);
	}
}

//============================================================================
//
// A_M_FireShotgun2
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_M_FireShotgun2)
{
	int pitch;

	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	S_Sound (self, CHAN_WEAPON, "weapons/sshotf", 1, ATTN_NORM, true);	// [BC] Inform the clients.
	A_FaceTarget (self);
	pitch = P_AimLineAttack (self, self->angle, MISSILERANGE);
	for (int i = 0; i < 20; ++i)
	{
		int damage = 5*(pr_m_fireshotgun2()%3+1);
		angle_t angle = self->angle + (pr_m_fireshotgun2.Random2() << 19);

		P_LineAttack (self, angle, MISSILERANGE,
					  pitch + (pr_m_fireshotgun2.Random2() * 332063), damage,
					  NAME_Hitscan, NAME_BulletPuff);
	}
	self->special1 = level.maptime;
}

//============================================================================
//
// A_M_FireCGun
//
//============================================================================

DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_FireCGun)
{
	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	ACTION_PARAM_START(1);
	ACTION_PARAM_BOOL(accurate, 0);

	S_Sound (self, CHAN_WEAPON, "weapons/chngun", 1, ATTN_NORM, true);	// [BC] Inform the clients.
	A_FaceTarget (self);
	P_GunShot2 (self, accurate, P_AimLineAttack (self, self->angle, MISSILERANGE),
		PClass::FindClass(NAME_BulletPuff));
}

//============================================================================
//
// A_M_FireMissile
//
// Giving a marine a rocket launcher is probably a bad idea unless you pump
// up his health, because he's just as likely to kill himself as he is to
// kill anything else with it.
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_M_FireMissile)
{
	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	if (self->CheckMeleeRange ())
	{ // If too close, punch it
		MarinePunch(self, 1);
	}
	else
	{
		A_FaceTarget (self);
		P_SpawnMissile (self, self->target, PClass::FindClass("Rocket"), NULL, true); // [BB] Inform clients
	}
}

//============================================================================
//
// A_M_FireRailgun
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_M_FireRailgun)
{
	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	CALL_ACTION(A_MonsterRail, self);
	self->special1 = level.maptime + 50;
}

//============================================================================
//
// A_M_FirePlasma
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_M_FirePlasma)
{
	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	A_FaceTarget (self);
	P_SpawnMissile (self, self->target, PClass::FindClass("PlasmaBall"), NULL, true); // [BB] Inform clients
	self->special1 = level.maptime + 20;
}

//============================================================================
//
// A_M_BFGsound
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_M_BFGsound)
{
	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	if (self->special1 != 0)
	{
		// [Dusk] Update the state change to clients.
		if ( NETWORK_GetState( ) == NETSTATE_SERVER )
			SERVERCOMMANDS_SetThingFrame( self, self->SeeState );

		self->SetState (self->SeeState);
	}
	else
	{
		A_FaceTarget (self);
		S_Sound (self, CHAN_WEAPON, "weapons/bfgf", 1, ATTN_NORM, true);	// [BC] Inform the clients.
		// Don't interrupt the firing sequence
		self->PainChance = 0;
	}
}

//============================================================================
//
// A_M_FireBFG
//
//============================================================================

DEFINE_ACTION_FUNCTION(AActor, A_M_FireBFG)
{
	// [BC] Don't do this in client mode.
	if ( NETWORK_InClientMode() )
	{
		return;
	}

	if (self->target == NULL)
		return;

	A_FaceTarget (self);
	P_SpawnMissile (self, self->target, PClass::FindClass("BFGBall"), NULL, true); // [BB] Inform clients
	self->special1 = level.maptime + 30;
	self->PainChance = MARINE_PAIN_CHANCE;
}

//---------------------------------------------------------------------------

void AScriptedMarine::SetWeapon (EMarineWeapon type)
{
	if (GetWeaponStates(type, MeleeState, MissileState))
	{
		static const char *classes[] = {
			"ScriptedMarine",
			"MarineFist",
			"MarineBerserk",
			"MarineChainsaw",
			"MarinePistol",
			"MarineShotgun",
			"MarineSSG",
			"MarineChaingun",
			"MarineRocket",
			"MarinePlasma",
			"MarineRailgun",
			"MarineBFG"
		};

		const PClass *cls = PClass::FindClass(classes[type]);
		if (cls != NULL)
			DecalGenerator = GetDefaultByType(cls)->DecalGenerator;
		else 
			DecalGenerator = NULL;
	}
}

void AScriptedMarine::SetSprite (const PClass *source)
{
	if (source == NULL || source->ActorInfo == NULL)
	{ // A valid actor class wasn't passed, so use the standard sprite
		SpriteOverride = sprite = GetClass()->ActorInfo->OwnedStates[0].sprite;
		// Copy the standard scaling
		scaleX = GetDefault()->scaleX;
		scaleY = GetDefault()->scaleY;
	}
	else
	{ // Use the same sprite and scaling the passed class spawns with
		SpriteOverride = sprite = GetDefaultByType (source)->SpawnState->sprite;
		scaleX = GetDefaultByType(source)->scaleX;
		scaleY = GetDefaultByType(source)->scaleY;
	}
}
