#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "effects.h"
#include "aura.h"
#include "classes.h"
#include "magicattack.h"
#include "explodingattack.h"
#include "player.h"
#include "effects.h"
#include "chargedattack.h"
#include "magicattack.h"
#include "envmodel.h"
#include "../common/particletypes.h"

#define MAX_ROCKS 6
#define ROCK_ROTATION_RADIUS 15.0
#define ROCK_ROTATION_SPEED 2.0
#define ROCK_RAISE_SPEED 100.0
#define ENVMODEL_RAISE 50.0

// Added by Pcjoe
extern short g_sRockGibs;
extern int gmsgAddEffect;
extern int gmsgRemoveEffect;

class CRock:public CExplodingAttack
{
public:
	void Spawn();
	/**
	 * Create a rock
	 * @param entvars_t *pevOwner: Monster/Player who shoots it
	 * @param float rotPos: rotation position
	 * @param raiseSpeed: raising speed
	 */
	static CRock *CreateRock(Vector vecSrc, entvars_t *pevOwner,float rotPos, float raiseSpeed);

	/**
	 * Rotates around the owner
	 */	
	void EXPORT RotateThink(void);

	/**
	 * Shoot the rock
	 */
	void Shoot(Vector dir);
private:
	/**
	 * Draws the particle explosion
	 * @param radius: radius of the explosion
	 * @param origin: the origin of the explosion
	 */
	void DrawExplosionFX(int radius, Vector vecOrigin);

	/**
	 * Explode on touch
	 * @param pOther Entity touching this entity
	 */
	void EXPORT ExplodeTouch( CBaseEntity *pOther );

	float m_flStartTime;
	float m_flGlobalStartTime;
};

void CRock::Spawn( void )
{	Precache();
	m_fPowerStruggle = false;
	m_fBlockStruggle = false;
	m_fRemoveAfterExplode = true;
	m_fExploded = false;
	m_pPowerStruggle = NULL;
	pev->gravity = 0.000001;//rock is not affected by gravity	
}

CRock *CRock::CreateRock(Vector vecRockOrigin, entvars_t *pevOwner, float rotPos, float raiseSpeed)
{
	CRock *pRock=GetClassPtr( (CRock *)NULL );
	pRock->Spawn();	
	//pRock->pev->movetype=MOVETYPE_FLY;
	pRock->pev->movetype=MOVETYPE_NOCLIP;
	pRock->pev->solid=SOLID_NOT;	//don't touch anything yet

	//set model 
	SET_MODEL(ENT(pRock->pev), "models/rocks.mdl");
	pRock->pev->scale = RANDOM_FLOAT(0.5,1.5);
	pRock->pev->body = RANDOM_LONG(0,6);
	pRock->pev->classname = MAKE_STRING( "rock" );

	pRock->pev->owner=ENT(pevOwner);
	pRock->pev->dmg = ROCK_DAMAGE;
	pRock->m_iRadius = ROCK_RADIUS;

	Vector vecOrigin;
	float flTime = rotPos * M_PI * 2.0;
	vecOrigin.x = ROCK_ROTATION_RADIUS * sin(flTime*ROCK_ROTATION_SPEED);
	vecOrigin.y = ROCK_ROTATION_RADIUS * cos(flTime*ROCK_ROTATION_SPEED);
	vecOrigin.z = 0;
	UTIL_SetOrigin(pRock->pev,vecRockOrigin+vecOrigin);
	pRock->pev->velocity.x = ROCK_ROTATION_RADIUS * ROCK_ROTATION_SPEED * cos(flTime*ROCK_ROTATION_SPEED);
	pRock->pev->velocity.y = ROCK_ROTATION_RADIUS * ROCK_ROTATION_SPEED * -sin(flTime*ROCK_ROTATION_SPEED);
	pRock->pev->velocity.z = raiseSpeed;
	
	pRock->m_flStartTime = flTime;
	pRock->m_flGlobalStartTime = gpGlobals->time;
	
	pRock->SetTouch(NULL);
	pRock->SetThink(&CRock::RotateThink);
	pRock->pev->nextthink = gpGlobals->time;
	
	return pRock;		
}

void CRock :: RotateThink( void  )
{	if (pev->owner)
	{	entvars_t *pevOwner=VARS(pev->owner);

		float height = pevOwner->origin.z + pevOwner->view_ofs.z + 5;
		if (pev->origin.z>height)
		{
			pev->velocity.z = 0;			
		}
		//TODO: error correction	
		float flTime = gpGlobals->time - m_flGlobalStartTime + m_flStartTime;
		pev->velocity.x = ROCK_ROTATION_RADIUS * ROCK_ROTATION_SPEED * cos(flTime*ROCK_ROTATION_SPEED);
		pev->velocity.y = ROCK_ROTATION_RADIUS * ROCK_ROTATION_SPEED * -sin(flTime*ROCK_ROTATION_SPEED);			
					
	}
	else
	{	SetThink(&CRock::NormalThink);
	}	
	pev->nextthink = gpGlobals->time + 0.02;	
}

void CRock::Shoot(Vector dir)
{
	pev->velocity = dir;
	pev->solid=SOLID_TRIGGER;
	pev->movetype=MOVETYPE_FLY;
	SetThink(&CRock::NormalThink);
	SetTouch(&CRock::ExplodeTouch);
	pev->nextthink = gpGlobals->time + 0.1;	
}

void CRock::DrawExplosionFX(int radius, Vector vecOrigin)
{
	//PLACEHOLDER. We should add real particle rock explosions here later on

	// Edited by Pcjoe, Fixed the explosion going through the ground problem
	TraceResult tr;
	Vector vecBall = pev->velocity.Normalize() * radius;
	Vector vecVelocity = pev->velocity.Normalize();

	// Move it back a bit so the origin wont be in SOLID
	vecOrigin = vecOrigin + vecVelocity * -20;
	UTIL_TraceLine ( vecOrigin, vecOrigin + vecBall + vecVelocity * 30,  ignore_monsters, ENT(pev), &tr);
	Vector vecSrc = tr.vecEndPos;

	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();
	}
}

void CRock::ExplodeTouch( CBaseEntity *pOther )
{	//shield touch is handled in shields
	if (pOther->Classify()==CLASS_SHIELD)
	{	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;
	}	

	
	//go into block-ps after hitting a blocking player
	if (pOther->IsPlayer())
	{	CBasePlayer *pPlayer =(CBasePlayer*)pOther;
		if (BlocksAttack(pPlayer) && !pPlayer->m_fBlockStruggling)
		{	SwatAway(pPlayer, true);			
		}
	}
	Explode();	
}
//-----------------------------------------------------------------------------------------------------------------------
/**
* Telekinesis weapon
* @version 31-7-2003
* @author Herwin 'harSens' van Welbergen
*/
class CTelekinesis:public CChargedAttack
{	
public:
	/**
	* Init function
	*/
	void Spawn( void );

	/**
	 * Precaches sprites and sounds
	 */
	void Precache( void );
	
	/**
	 * Deploys telekinesis
	 */
	BOOL Deploy();

	/**
	* Get  bigbang slot
	* return int: the slot, is 7 for telekinesis
	*/
	int iItemSlot( void ) { return 7; }

	/**
	* Get bigbang item info
	* @param ItemInfo *p: The ItemInfo gets stored here
	* @return int: Always returns 1 (why???)
	*/
	int GetItemInfo(ItemInfo *p);

	/**
	 * primary attack
	 */
	void PrimaryAttack();

	/**
	 * idle
	 */
	void WeaponIdle();

	/**
	 * Holster
	 * @param skiplocal ?
	 */
	void Holster(int skiplocal);

	/**
	 * shoot
	 */
	void Shoot();
private:

	void DisableChargeSprite();

	void GrowChargeSprite();

	/**
	 * shoots the bigbang
	 */
	void ShootAttack();
	
	/**
	 * emits the shooting sound
	 */
	void EmitShootSound();

	/**
	 * emits the charging sound
	 */	
	void EmitChargeSound();

	CRock *m_pRock[MAX_ROCKS];

	int m_iRock;
	Vector vecRockOrigin;
	float m_flRockRaiseSpeed;
	CEnvModel *m_pEnvModel;
	bool m_fRockCharge;
	bool m_fEnvModelCharge;	
	bool m_fRotatingCrosshair;
};

LINK_ENTITY_TO_CLASS( weapon_telekinesis, CTelekinesis );

void CTelekinesis::Spawn( void )
{	Precache();
	m_iId = WEAPON_TELEKINESIS;
	m_iDefaultAmmo = 0;
	m_iClip = -1;

	m_iMaxCharge = TELEKINESIS_MINCHARGETIME;
	m_iKiCost = TELEKINESIS_KI_COST;
	m_flPlayerSlowDown = TELEKINESIS_PLAYERSLOWDOWN;
	strcpy(m_pAttackName,"telekinesis");
	FallInit();// get ready to fall down.
}

void CTelekinesis::Precache()
{	PRECACHE_MODEL("models/rocks.mdl");
	PRECACHE_SOUND("weapons/telekinesis_charge.wav");
	PRECACHE_SOUND("weapons/telekinesis_fire.wav");
	PRECACHE_SOUND("frieza/telekinesis.wav");
	PRECACHE_SOUND("frieza/telekinesis_charge.wav");
}

BOOL CTelekinesis::Deploy()
{
	m_iRock = 0;
	m_flRockRaiseSpeed = ROCK_RAISE_SPEED;
	m_fRockCharge = false;
	m_fEnvModelCharge = false;
	m_fRotatingCrosshair = false;
	return CChargedAttack::Deploy();
}

void CTelekinesis::Holster(int skiplocal)
{
	if (m_fRotatingCrosshair)
	{
		MESSAGE_BEGIN( MSG_ONE, gmsgRemoveEffect, NULL, m_pPlayer->edict() );
			WRITE_BYTE(ROTATECROSSHAIR);				
		MESSAGE_END();
	}
	if(m_fEnvModelCharge)
	{
		m_pEnvModel->SetThink(NULL);
		m_pEnvModel->SetTouch(&CEnvModel::ExplodeTouch);
		m_pEnvModel->Drop();
	}
	CChargedAttack::Holster(skiplocal);
}

int CTelekinesis::GetItemInfo(ItemInfo *p)
{	p->pszName = STRING(pev->classname);
	p->pszAmmo1 = "ki";
	p->iMaxAmmo1 = -1;	//max ammo is weapon independent
	p->pszAmmo2 = NULL;
	p->iMaxAmmo2 = -1;
	p->iMaxClip = WEAPON_NOCLIP;
	p->iSlot = 6;
	p->iPosition = 4;
	p->iId = m_iId = WEAPON_TELEKINESIS;
	p->iWeight = TELEKINESIS_WEIGHT;
	p->iFlags = 0;
	return 1;
}

void CTelekinesis::GrowChargeSprite()
{
	if(m_fRockCharge)
	{
		int iEffCharge = m_iCharge - m_iMaxCharge/3;
		int iMaxEffCharge = m_iMaxCharge*2/3;		
		/*
		ALERT(at_console,"Max charge %d\n", m_iMaxCharge);
		ALERT(at_console,"Eff charge %d\n", iEffCharge);
		ALERT(at_console,"Eff Max charge %d\n", iMaxEffCharge);
		*/
		while ( (iEffCharge*MAX_ROCKS)/iMaxEffCharge>=m_iRock && m_iRock<MAX_ROCKS)//ugleh release time hack
		{
			//ALERT(at_console,"Rock %d",m_iRock);			
			m_pRock[m_iRock] = CRock::CreateRock(vecRockOrigin,m_pPlayer->pev,(float)m_iRock/(float)MAX_ROCKS,m_flRockRaiseSpeed);
			m_iRock++;			
		}
	}
	else if(m_fEnvModelCharge){
		m_pEnvModel->pev->velocity = Vector(0,0,0);
		m_pEnvModel->pev->avelocity = Vector(0,0,0);
	}
}

void CTelekinesis::DisableChargeSprite()
{
	for(int i=0;i<m_iRock;i++)
	{
		m_pRock[i]->Vaporize();
	}
	m_iRock=0;
	m_fRockCharge = false;
}

void CTelekinesis::EmitShootSound()
{	EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/telekinesis_fire.wav", 1.0, ATTN_NORM);	
	m_pPlayer->EmitClassSound("telekinesis.wav");
}

void CTelekinesis::EmitChargeSound()
{	EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/telekinesis_charge.wav", 1.0, ATTN_NORM);
	m_pPlayer->EmitClassSound("telekinesis_charge.wav");
}

void CTelekinesis::WeaponIdle()
{
	UTIL_MakeVectors( m_pPlayer->pev->v_angle );
	TraceResult tr;
	Vector vecSrc = m_pPlayer->GetGunPosition();
	UTIL_TraceLine (vecSrc,vecSrc + gpGlobals->v_forward * 8096, dont_ignore_monsters, ENT(m_pPlayer->pev), &tr );
		
	CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit);

	int fEnvHit = false;
	if(tr.flFraction<1.0 && !m_fRockCharge )
	{	if(FClassnameIs(pEntity->pev, "env_model"))
		{
			if (!m_fRotatingCrosshair)
			{
				ALERT(at_console,"Size: %f\n",((CEnvModel*)pEntity)->m_flSize);
				m_fRotatingCrosshair = true;
				MESSAGE_BEGIN( MSG_ONE, gmsgAddEffect, NULL, m_pPlayer->edict() );
					WRITE_BYTE(ROTATECROSSHAIR);				
				MESSAGE_END();					
			}
			fEnvHit = true;
		}
	}
	if (m_fRotatingCrosshair && !fEnvHit)
	{
		m_fRotatingCrosshair = false;
		MESSAGE_BEGIN( MSG_ONE, gmsgRemoveEffect, NULL, m_pPlayer->edict() );
			WRITE_BYTE(ROTATECROSSHAIR);				
		MESSAGE_END();	
	}
	CChargedAttack::WeaponIdle();
}

void CTelekinesis::PrimaryAttack()
{
	if (m_fRotatingCrosshair)
	{
		m_fRotatingCrosshair = false;
		MESSAGE_BEGIN( MSG_ONE, gmsgRemoveEffect, NULL, m_pPlayer->edict() );
			WRITE_BYTE(ROTATECROSSHAIR);				
		MESSAGE_END();
	}

	if (!m_pPlayer->m_fCharging)
	{	UTIL_MakeVectors( m_pPlayer->pev->v_angle );
		TraceResult tr;
		Vector vecSrc = m_pPlayer->GetGunPosition();
		UTIL_TraceLine (vecSrc,vecSrc + gpGlobals->v_forward * 8096, dont_ignore_monsters, ENT(m_pPlayer->pev), &tr );
		
		CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit);

		if(tr.flFraction<1.0 && !m_fRockCharge )
		{	if(FClassnameIs(pEntity->pev, "env_model"))
			{	
				m_pEnvModel = (CEnvModel*)pEntity;
				if(!m_pEnvModel->m_fLift && m_pEnvModel->IsAlive())
				{	
					m_fEnvModelCharge = true;
					float flTime = (float)m_iMaxCharge*CHARGETIME/3.0;
					m_iMaxCharge = TELEKINESIS_MAXCHARGETIME;
					float fRaiseSpeed = ENVMODEL_RAISE/flTime;
					
					m_pEnvModel->Lift(m_pPlayer->pev,fRaiseSpeed,flTime);
				}
			}
		}
		if(!m_fEnvModelCharge)
		{	m_fRockCharge = true;				
			UTIL_TraceLine (vecSrc,vecSrc + Vector(0,0,-8096), ignore_monsters, ENT(m_pPlayer->pev), &tr );
			if(tr.flFraction<1.0 )
			{
				vecRockOrigin = tr.vecEndPos;
				m_iMaxCharge = (vecSrc.z-vecRockOrigin.z)*3/(ROCK_RAISE_SPEED*CHARGETIME);
				//make dividable by 3
				m_iMaxCharge += (3-m_iMaxCharge%3);

				m_flRockRaiseSpeed = ROCK_RAISE_SPEED;
				if(m_iMaxCharge<TELEKINESIS_MINCHARGETIME)
				{
					m_iMaxCharge=TELEKINESIS_MINCHARGETIME;			
				}
				else if(m_iMaxCharge>TELEKINESIS_MAXCHARGETIME)
				{
					m_flRockRaiseSpeed = (vecSrc.z-vecRockOrigin.z)*3/(TELEKINESIS_MAXCHARGETIME*CHARGETIME);
					m_iMaxCharge = TELEKINESIS_MAXCHARGETIME;			
				}
			}
			else
			{
				m_iMaxCharge=TELEKINESIS_MINCHARGETIME;
			}
		}
	}
	CChargedAttack::PrimaryAttack();
}

void CTelekinesis::Shoot()
{
	CChargedAttack::Shoot();	
	m_fRockCharge = false;		
	//drop unshot envmodel
	if(m_fEnvModelCharge)
	{	m_fEnvModelCharge = false;
		if (m_pEnvModel->m_fLift)
		{
			m_pEnvModel->Drop();
			m_pEnvModel->SetTouch(&CEnvModel::ExplodeTouch);
		}
	}	
}

void CTelekinesis::ShootAttack()	
{	UTIL_MakeVectors( m_pPlayer->pev->v_angle );		
	//shoot the attack
	if(m_fRockCharge)
	{
		if (m_iRock > MAX_ROCKS)
		{	ALERT(at_console,"Invalid m_iRock: %d\n",m_iRock);
			//m_iRock = MAX_ROCKS;
		}		
			
		Vector vecThrow = gpGlobals->v_forward * ROCK_SPEED;
		for(int i=0;i<m_iRock;i++)
		{
			m_pRock[i]->Shoot(vecThrow);
		}
		m_iRock=0;
		m_fRockCharge = false;
	}
	else if (m_fEnvModelCharge)
	{
		m_fEnvModelCharge = false;
		Vector vecAiming = gpGlobals->v_forward;
		Vector vecSrc = m_pPlayer->GetGunPosition();
		TraceResult tr;
		UTIL_TraceLine (vecSrc,vecSrc + vecAiming * 8192, dont_ignore_monsters, ENT(pev), &tr );
	
		vecAiming = tr.vecEndPos - m_pEnvModel->pev->origin;
		vecAiming = vecAiming.Normalize();
	
		m_pEnvModel->Shoot(vecAiming,(float)m_iCharge/(float)m_iMaxCharge,GetPowerRatio());
	}
}