#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "soundent.h"
#include "decals.h"
#include "effects.h"
#include "aura.h"
#include "classes.h"
#include "magicattack.h"
#include "explodingattack.h"
#include "player.h"
#include "gamerules.h"
#include "magicattack.h"
#include "explodingattack.h"
#include "powerstruggle.h"
#include "../common/particletypes.h"

#define BLOCKSTRUGGLE_DAMAGE_RATIO 0.1
LINK_ENTITY_TO_CLASS( explodingattack, CExplodingAttack);
extern int gmsgExplosion;
extern int gmsgPowerStruggle;
// added by Pcjoe
short g_sRockGibs;
extern int gmsgBlockStruggle;

extern int gmsgAddEffect;

void CExplodingAttack::Spawn( void )
{	Precache();
	
	m_fPowerStruggle = false;
	m_fBlockStruggle = false;
	m_fRemoveAfterExplode = true;
	m_fExploded = false;
	m_fStopped = false;
	m_fBlockStruggleDamage = false;
	m_pPowerStruggle = NULL;
	CMagicAttack::Spawn();
}

void CExplodingAttack::Precache( void )
{	PRECACHE_SOUND("weapons/explosion.wav");
	// Edited by Pcjoe
//	m_sRockGibs = PRECACHE_MODEL("models/rockgibs.mdl");
	g_sRockGibs = PRECACHE_MODEL("models/rockgibs.mdl");
}

void CExplodingAttack::DrawExplosionFX(int radius, Vector origin)
{
	// Edited by Pcjoe, Fixed the explosion going through the ground problem
	TraceResult tr;
	Vector vecVelocity = pev->velocity;
	if(vecVelocity.Length()>0)vecVelocity.Normalize();
	Vector vecBall = vecVelocity * radius;
	

	// Move it back a bit so the origin wont be in SOLID
	Vector vecOrigin = origin + vecVelocity * -20;

	UTIL_TraceLine ( vecOrigin, vecOrigin + vecBall + vecVelocity * 30,  ignore_monsters, ENT(pev), &tr);
	Vector vecSrc = tr.vecEndPos;
	if (tr.flFraction >= 1.0)
	{	vecSrc = origin;	
	}

	//shake ground
	UTIL_ScreenShake( vecSrc, 12, 100, 5, radius);
	
	//explosion sphere
	MESSAGE_BEGIN( MSG_ALL, gmsgExplosion);
		WRITE_COORD( vecSrc.x );	
		WRITE_COORD( vecSrc.y );
		WRITE_COORD( vecSrc.z );	
		WRITE_LONG (radius);		//radius
		WRITE_BYTE (m_iExplosionColor);
	MESSAGE_END();

	MESSAGE_BEGIN( MSG_ALL, gmsgAddEffect);
		WRITE_BYTE(EXPLOSION);
		WRITE_SHORT( entindex() );
		WRITE_COORD(vecSrc.x);
		WRITE_COORD(vecSrc.y);
		WRITE_COORD(vecSrc.z);
		WRITE_SHORT(radius);
	MESSAGE_END();

	if (pev->flags | FL_ONGROUND && tr.flFraction < 1.0)
	{	MESSAGE_BEGIN( MSG_ALL, SVC_TEMPENTITY);
			WRITE_BYTE(TE_BREAKMODEL);					// box of models or sprites
			WRITE_COORD( vecSrc.x );					// coord, coord, coord (position)
			WRITE_COORD( vecSrc.y );
			WRITE_COORD( vecSrc.z );
			WRITE_COORD( 50 );							// coord, coord, coord (size)
			WRITE_COORD( 50 );
			WRITE_COORD( 50 );
			WRITE_COORD( tr.vecPlaneNormal.x * 200);	// coord, coord, coord (velocity)
			WRITE_COORD( tr.vecPlaneNormal.y * 200);
			WRITE_COORD( tr.vecPlaneNormal.z * 200);
			WRITE_BYTE(30);								// byte (random velocity in 10's)
			WRITE_SHORT(g_sRockGibs);					// short (sprite or model index)
			WRITE_BYTE(20);								// byte (count)
			WRITE_BYTE(50);								// byte (life in 0.1 secs)
			WRITE_BYTE(0);								// byte (flags)
		MESSAGE_END();
	}

	if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 )
	{
		UTIL_DecalTrace( &tr, DECAL_SCORCH1 );
	}
	else
	{
		UTIL_DecalTrace( &tr, DECAL_SCORCH2 );
	}	
}

void CExplodingAttack::Vaporize()
{	if(m_fRemoveAfterExplode)
	{	Remove();
	}
	else
	{	m_fExploded = true;
		SetTouch(NULL);
		SetThink(NULL);
		pev->effects |= EF_NODRAW;
	}
}

void CExplodingAttack::Explode()
{	// Added by Pcjoe
	if(pev->solid == SOLID_NOT)
	{	return;
	}

	//Remove the ps it participated in. This will also explode this attack itself
	if(m_fPowerStruggle)
	{	if(m_pPowerStruggle)
		{	if(!m_pPowerStruggle->m_fExploding)
			{	m_pPowerStruggle->Remove();
				return;
			}
		}
	}

	Vector vecExplosionOrigin = pev->origin;
	//remove bar
	if (m_fBlockStruggle)
	{	vecExplosionOrigin = m_pBlockStruggler->pev->origin;
		m_fBlockStruggle = false;
				
		//restore movetype
		if (m_pBlockStruggler->m_fFlying)
		{	m_pBlockStruggler->pev->movetype = MOVETYPE_PLAYERFLY;
		}
		else
		{	m_pBlockStruggler->pev->movetype = MOVETYPE_WALK;
		}

		//remove struggle bar
		MESSAGE_BEGIN( MSG_ONE, gmsgPowerStruggle, NULL, m_pBlockStruggler->pev ); 
			WRITE_CHAR(127);
		MESSAGE_END();
		m_pBlockStruggler->m_fBlockStruggling = false;		
	}	
	
	//compensate for the size of the attacking ball
	float radius = GetExplosionRadius();
	
	entvars_t *pevOwner;
	if ( pev->owner )
		pevOwner = VARS( pev->owner );
	else
		pevOwner = NULL;

	// Added by Pcjoe, push people back if they are in the explosion radius
	CBaseEntity *pEntity = NULL;
	while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecExplosionOrigin, radius )) != NULL)
	{
		if(pEntity->IsPlayer())
		{
			CBasePlayer *pPlayer = (CBasePlayer*)pEntity;
			
			// Make sure attack has enough damage
			if(pev->dmg < MIN_EXPLOSION_PUSH_DMG)
			{	continue;
			}

			// Dont push back guys that are on your team!
			if(!g_pGameRules->FPlayerCanTakeDamage( (CBasePlayer*)CBaseEntity::Instance(pevOwner), pPlayer ))
			{	continue;
			}

			// Dont push back people in advanced melee fights
			if(pPlayer->m_Melee.InAdvMeleeBattle())
			{	continue;
			}

			// Dont push around dead people/laying from melee people
			if(!pPlayer->IsAlive() || pPlayer->m_Melee.m_eThrowStatus == THROW_LAY || pPlayer->m_Melee.m_eThrowStatus == THROW_LANDRECOVER)
			{	continue;
			}

			//added by harSens: don't push back players that are in a ps/bs/charging an sb
			if(pPlayer->m_fPowerStruggling||pPlayer->m_fBlockStruggling||pPlayer->m_fBlockStruggleAttacker||pPlayer->m_fChargeNamekSB)
				continue;

			// Distance from the center of the explosion to the player
			float flDist = (pPlayer->pev->origin - vecExplosionOrigin).Length();

			// HACK HACK: Fly wont disable if m_fControl is set, so temporarily disable it
			BOOL fControl = pPlayer->m_fControl;
			pPlayer->m_fControl = FALSE;

			
			// Remove walljump
			if(pPlayer->m_fWallJump)
			{	pPlayer->RemoveWallJump();
			}

			// If the player is within the inner 1/2 of the explosion, disable their swoop
			if(flDist < radius/2)
			{	if(pPlayer->m_Swoop.m_fSwoop)
				{	pPlayer->StopSwoop();
				}
			}

			// If the player is within the inner 1/4th of the explosion, disable their flight
			if(flDist < radius/4)
			{
				if(pPlayer->m_fFlying)
				{	pPlayer->StopFly();
				}
			}

			pPlayer->m_fControl = fControl;

			float flRatio = (radius - flDist) / radius;
			int iForce = EXP_PUSHBACK_FORCE + (int)(pev->dmg*EXP_PUSHBACK_DMGMULTI);

			// Push everybody out depending on the distance from the center of the exp
			Vector vecPush = (vecExplosionOrigin - pPlayer->pev->origin).Normalize() * iForce * flRatio * -1;
			pPlayer->pev->velocity = pPlayer->pev->velocity + vecPush;
		}
	}
	// End of add by Pcjoe

	::RadiusDamage( vecExplosionOrigin, pev, pevOwner, pev->dmg, radius, CLASS_NONE, DMG_MAGIC );

	
	//STOP_SOUND( ENT(pev), CHAN_WEAPON, "weapons/beamhead.wav" );
	//EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM);
	if (m_fRemoveAfterExplode)
	{
		Remove();
	}
	else
	{
		m_fExploded = true;
		SetTouch(NULL);
		SetThink(NULL);
		pev->effects |= EF_NODRAW;
	}
	DrawExplosionFX(radius, vecExplosionOrigin);
	
	// Pcjoe: Make sure it doesnt double explode
	pev->solid = SOLID_NOT;
}

void CExplodingAttack::StartPowerStruggle(CExplodingAttack *pStruggler,int type)
{	/*
	//powerstruggle doesn't exist yet. create it.	
	if(!pStruggler->m_fPowerStruggle)
	{	CPowerStruggle *pPowerStruggle = CPowerStruggle::CreatePowerStruggle(this,pStruggler);
	}
	else
	{	CPowerStruggle *pPowerStruggle = pStruggler->m_pPowerStruggle;			
		if (pPowerStruggle)
		{	if(pPowerStruggle->IsFull())
			{	return;
			}
			else
			{	pPowerStruggle->AddAttack(this);
			}
		}
	}
	*/
	CPowerStruggle *pPowerStruggle = CPowerStruggle::CreatePowerStruggle(this,pStruggler,type);
	pev->nextthink = gpGlobals->time;	
}

void CExplodingAttack::ExplodeTouch( CBaseEntity *pOther )
{	//shield touch is handled in shields
	if (pOther->Classify()==CLASS_SHIELD)
	{	return;
	}
	
	//lost the powerstruggle, give the winner credit
	if (m_fPowerStruggle && !FGlobalnameIs(pev, "magic_attack") && !(pOther->Classify()==CLASS_KIBLOB))
	{	Explode();
		return;		
	}	
	//find out who not to hit
	entvars_t *pevOwner;
	if (pev->owner)
	{	pevOwner = VARS(pev->owner);
	}
	else
	{	pevOwner=NULL;
	}

	entvars_t *pevOtherOwner;
	if (pOther->pev->owner)
	{	pevOtherOwner=VARS(pOther->pev->owner);
	}
	else
	{	pevOtherOwner=NULL;
	}

	//don't hit your owner
	if (pevOwner == pOther->pev)
	{	return;
	}	

	//don't hit your brother (aura, other attacks fired by dad)
	if (pevOtherOwner == pevOwner)
	{	if (pevOwner) return;
	}	

	//don't hit the one you're blockstruggling with
	if (pOther == m_pBlockStruggler && m_fBlockStruggle)
	{	return;
	}

	//don't struggle with rocks. TODO: setup class system in such a way this stuff can be arranged neater
	if (FClassnameIs(pOther->pev, "rock"))
		return;

	//HACK: kiblasts and scattershots don't hit eachother (should be done more generic)
	if (FClassnameIs(pOther->pev, "kiblast") || FClassnameIs(pOther->pev, "scattershot"))
	{	if(FClassnameIs(pev, "kiblast") || FClassnameIs(pev, "scattershot"))
		{	return;
		}
	}

	//go into powerstruggle after a collision with other large attacks
	if (pOther->Classify() == CLASS_EXPLODINGATTACK)
	{	CExplodingAttack *pStruggler = (CExplodingAttack*)pOther;
		if (!m_fPowerStruggle && !m_fBlockStruggle && !pStruggler->m_fStopped && !m_fStopped)
		{	//vaporize much smaller attacks that are not in a ps/bs
			if ( (float)pOther->pev->dmg/(float)pev->dmg < 0.2 )
			{	if (!pStruggler->m_fPowerStruggle && !pStruggler->m_fBlockStruggle)
				{	pStruggler->Vaporize();
				}				
			}

			//vaporize if we're much smaller
			else if ( (float)pev->dmg/(float)pOther->pev->dmg < 0.2 )
			{	Vaporize();
			}
			
			/*
			else if (FClassnameIs(pOther->pev, "scattershot") || FClassnameIs(pev, "scattershot"))
			{
				//scattershots don't ps
			}
			*/
			//go in powerstruggle with equally powered attacks
			else
			{	if (!pStruggler->m_fBlockStruggle)
				{	if (!pStruggler->m_fPowerStruggle)
					{
						double angle = acos(DotProduct( (pev->velocity).Normalize(),(pStruggler->pev->velocity).Normalize()));											
						//initialize ps						
						if (angle>M_PI*0.75 && IsGuided()&& pStruggler->IsGuided())
						{
							//frontward ps
							ALERT(at_console,"Initializing frontward ps");
							StartPowerStruggle(pStruggler,FRONTPS);
						}
						else if(angle>M_PI*0.25)
						{
							//sideways ps
							ALERT(at_console,"Initializing sideward ps");
							StartPowerStruggle(pStruggler,SIDEPS);
						}
						else
						{
							ALERT(at_console,"No PS, back hit\n");
						}
					}
					else
					{
						psAttack *pFirst = pStruggler->m_pPowerStruggle->m_pAttack;
						if (pFirst->side == 0)
						{	//join sideways														
							pStruggler->m_pPowerStruggle->AddAttack(this,0);
						}
						else
						{
							//join frontward
							UTIL_MakeAimVectors(pFirst->attack->pev->angles);
							double angle = acos(DotProduct( (pev->velocity).Normalize(),gpGlobals->v_forward));
							if(angle<M_PI*0.5)
							{	pStruggler->m_pPowerStruggle->AddAttack(this,pFirst->side);
							}
							else
							{	pStruggler->m_pPowerStruggle->AddAttack(this,pFirst->side==1?2:1);
							}
						}
					}
				}
			}				
		}
		return;
	}
	
	if (m_fBlockStruggle)
	{	return;
	}
	
	//go into block-ps after hitting a blocking player
	if (pOther->IsPlayer())
	{	if (!m_fPowerStruggle && !m_fBlockStruggle)
		{	CBasePlayer *pPlayer =(CBasePlayer*)pOther;
			
			if(pPlayer->m_Melee.InAdvMeleeBattle())
			{	Vaporize();
			}
		
			// Pcjoe: Dont blockstruggle if owner is beamjumping
			CBasePlayer *pOwner = (CBasePlayer*)CBaseEntity::Instance(pev->owner);
			if (BlocksAttack(pPlayer) && !pPlayer->m_fBlockStruggling && pOwner && pOwner->pev->movetype != MOVETYPE_RIDEBEAM)
			{	
				if((float)pPlayer->GetPowerLevel()/(float)g_pGameRules->m_iAveragePowerLevel * BLOCKSTRUGGLE_POWER * MIN_BLOCKPOINTS_FOR_STRUGGLE> pev->dmg)
				{	//attack is too weak. just swat it away
					SwatAway(pPlayer, true);
				}
				else
				{	//struggle with player
					m_fBlockStruggle = true;
					m_pBlockStruggler = pPlayer;								
										
					m_pBlockStruggler->pev->movetype = MOVETYPE_RIDEBEAM;
					pPlayer->m_fBlockStruggling = true;
					pPlayer->m_iBlockPoints = 0;
					pPlayer->m_pBlockStruggler = this;
					m_flBlockStruggleDamage = 0;
					SetThink(&CExplodingAttack::BlockStruggleThink);
					pev->velocity = pev->velocity * 0.1;
					pev->nextthink = gpGlobals->time + 0.1;
					
					MESSAGE_BEGIN( MSG_ALL, gmsgBlockStruggle);
						WRITE_SHORT(entindex());
						WRITE_SHORT(pPlayer->entindex());
					MESSAGE_END();
					
					//move camera to BS
					MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR );
						WRITE_BYTE ( 9 );
						WRITE_BYTE ( DRC_EVENT );
						WRITE_SHORT( entindex());
						WRITE_SHORT( pPlayer->entindex());
						WRITE_LONG(6);
					MESSAGE_END();
				}
				return;
			}
			else if(pev->dmg > pPlayer->pev->health && CanVaporizePlayer())
			{
				pev->dmg-=pPlayer->pev->health;	
				//make sure it kills the player
				pPlayer->TakeDamage(pev,pOwner->pev,10*pPlayer->pev->health,DMG_MAGIC);
				return;
			}
		}
	}	
	Explode();	
}

void CExplodingAttack::BlockStruggleThink()
{	//don't let the struggler turn away from the attack too much
	if (!BlocksAttack(m_pBlockStruggler))
	{	Explode();
		return;
	}

	//we have more block points, so swat it away
	if (m_pBlockStruggler->m_iBlockPoints > pev->dmg)
	{	m_fBlockStruggle = false;
				
		m_pBlockStruggler->m_fBlockStruggling = false;

		//remove struggle bar
		MESSAGE_BEGIN( MSG_ONE, gmsgPowerStruggle, NULL, m_pBlockStruggler->pev ); 
			WRITE_CHAR(127);
		MESSAGE_END();

		//restore movetype
		if (m_pBlockStruggler->m_fFlying)
		{	m_pBlockStruggler->pev->movetype = MOVETYPE_PLAYERFLY;
		}
		else
		{	m_pBlockStruggler->pev->movetype = MOVETYPE_WALK;
		}
		//UTIL_MakeVectors(m_pBlockStruggler->pev->v_angle);
		//UTIL_SetOrigin(pev,m_pBlockStruggler->GetGunPosition()+gpGlobals->v_forward * (pev->maxs.x+10));
		m_fBlockStruggleDamage = false;
		m_flBlockStruggleDamage = 0;
		SwatAway(m_pBlockStruggler, false);
		SetTouch(&CExplodingAttack::ExplodeTouch);
		return;
	}
	
	//calculate struggle ratio for use in bars
	int iBlockPoints = m_pBlockStruggler->m_iBlockPoints;
	if (pev->dmg > iBlockPoints)
	{	//attack is winning
		if (!pev->dmg)
		{	m_flStruggleRatio = 0;
		}
		else
		{	m_flStruggleRatio = (pev->dmg-iBlockPoints)/pev->dmg;
		}
	}
	else
	{	//blocker is winning
		if (!iBlockPoints)
		{	m_flStruggleRatio = 0;
		}
		else
		{	m_flStruggleRatio = -(iBlockPoints-pev->dmg)/iBlockPoints;
		}
	}
	//calculate speed
	UTIL_MakeAimVectors(pev->angles);
	float flSpeed = m_pBlockStruggler->GetMaxSpeed();
	if (iBlockPoints)
	{	pev->velocity = gpGlobals->v_forward * flSpeed * 0.2 * (float)pev->dmg/(float)iBlockPoints;
		if (pev->velocity.Length() > flSpeed)
		{	pev->velocity = gpGlobals->v_forward * flSpeed;
		}
	}
	else
	{	pev->velocity = gpGlobals->v_forward * flSpeed * 0.2;
	}	
	
	//draw block struggle bar
	MESSAGE_BEGIN( MSG_ONE, gmsgPowerStruggle, NULL, m_pBlockStruggler->pev ); 
		WRITE_CHAR( (int)(-m_flStruggleRatio * 100));	//struggle ratio in % (-100 till 100 %)
	MESSAGE_END();
	m_pBlockStruggler->pev->vuser4 = pev->velocity;

	if(m_fBlockStruggleDamage && m_flStruggleRatio>0)
	{
		//stack dmg
		m_flBlockStruggleDamage += m_flStruggleRatio * pev->dmg * BLOCKSTRUGGLE_DAMAGE_RATIO;
		
		//ALERT(at_console,"Struggle damage %f",m_flBlockStruggleDamage);
		
		int iDamage = (int)m_flBlockStruggleDamage;
		
		//only do damage if we can actually hurt the player
		if (iDamage >= 4)
		{	m_pBlockStruggler->TakeDamage(pev,VARS(pev->owner),iDamage,DMG_MAGIC);
			m_flBlockStruggleDamage -= iDamage;
		}
	}
	pev->nextthink = gpGlobals->time + 0.2;
}

void CExplodingAttack::SwatAway(CBasePlayer *pPlayer, int fRandomDir)
{
	Release();	
	CMagicAttack::SwatAway(pPlayer,fRandomDir);	
}

extern int gmsgDetachTrail;
void CExplodingAttack::Release()
{
	MESSAGE_BEGIN( MSG_ALL, gmsgDetachTrail);
		WRITE_LONG(entindex());
	MESSAGE_END();	
	
	SetThink(&CExplodingAttack::NormalThink);	
	pev->nextthink = gpGlobals->time + 0.5;
}

void CExplodingAttack::PowerStruggleThink ()
{	pev->nextthink = gpGlobals->time + 0.5;
}