/*
   ================================================================
   PLASMA BOW WEAPON
   ================================================================

   Copyright (C) 1998 by 2015, Inc.
   All rights reserved.

   This source is may not be distributed and/or modified without
   expressly written permission by 2015, Inc.
 */

#include "crossbow.h"
#include "explosion.h"
#include "surface.h"
#include "jitter.h"
#include "misc.h"

void G_AddGravity( Entity *ent );

//=================================================================
// plasma bow bolt class

CLASS_DECLARATION( Projectile, PBolt, NULL );

Event EV_PBolt_Explode( "explode" );
Event EV_PBolt_EnergyTrail( "energytrail" );

ResponseDef PBolt::Responses[] =
{
	{&EV_Touch, (Response)PBolt::BoltTouch},
	{&EV_PBolt_Explode, (Response)PBolt::Explode},
	{&EV_PBolt_EnergyTrail, (Response)PBolt::EnergyTrail},
	{NULL, NULL}
};

// also used to do velocity adjustments
EXPORT_FROM_DLL void PBolt::EnergyTrail( Event *ev )
{
	Vector tmpvec;
	int roll;
	Vector grav;

	PostEvent( EV_PBolt_EnergyTrail, 0.1 );

	roll = angles[ROLL] + 35;
	grav[gravity_axis[gravaxis].z] = gravity*sv_gravity->value*FRAMETIME*2*gravity_axis[gravaxis].sign;
	tmpvec = velocity - grav;
	angles = tmpvec.toAngles();
	angles[PITCH] = -angles[PITCH];
	angles[ROLL] = roll;
	setAngles( angles );
}

void PBolt::Explode( Event *ev )
{
	float damg;
	Entity *owner;
	RadiusJitter *jitter;

	owner = G_GetEntity( this->owner );

	if(!owner->isClient())
	{
		damg = 20 + (int)G_Random( 10 );
	}
	else
	{
		damg = 110 + ( int )G_Random( 15 );
		if(!deathmatch->value)
			damg *= 1.5;
	}

	CreateExplosion( origin, damg, 0, false, this, owner, NULL, MOD_PLASMABOWSPLASH, 0.3, 0, ATTN_NORM, 0.2, 1.0, 0.2, 300, 0.6, 0.75 );

	// make the visual explosion
	TempModel( NULL, origin, "0 0 0", "sprites/cbshot.spr", 0, 3.0, 0.8f, TEMPMODEL_ANIMATE_ONCE|TEMPMODEL_ANIMATE_FAST, 2 );

	damg = damg*0.2 + 32;
	SpawnBowExplosion( origin, damg );

	// make the view jitter for the explosion
	jitter = new RadiusJitter;
	jitter->Setup( origin, 150, 0.25, 0.2, 6, 9, 0.2, 4, 6 );

	// make the explosion sound
	RandomPositionedSound( origin, "impact_largeplasmaexplosion", 1.0, CHAN_AUTO, ATTN_NORM );

	hideModel();
	PostEvent( EV_Remove, 0.1 );
}

void PBolt::BoltTouch( Event *ev )
{
	int damg, num;
	float decay;
	Vector v;
	Entity *other;
	Vector norm;
	RadiusJitter *jitter;
	float f1, f2;
	Entity *owner;

	other = ev->GetEntity( 1 );

	if(other)
	{
		if(other->isSubclassOf( Teleporter ))
			return;

		if(other->entnum == this->owner)
			return;
	}

	CancelEventsOfType( EV_PBolt_EnergyTrail );
	CancelEventsOfType( EV_PBolt_Explode );

	setSolidType( SOLID_NOT );
	setMoveType( MOVETYPE_NONE );
	if(HitSky())
	{
		PostEvent( EV_Remove, 0 );
		return;
	}

	owner = G_GetEntity( this->owner );

// here's the different explosion code for the bob's bolts
	if(!owner->isClient())
	{
		damg = 25 + (int)G_Random( 10 );

		if(other && other->takedamage)
		{
			other->Damage( this, owner, damg, origin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_PLASMABOW, -1, -1, 1.0f );
		}

		surfaceManager.DamageSurface ( &level.impact_trace, damg, owner );

		v = velocity;
		v.normalize();

		// don't do radius damage to the other, because all the damage
		// was done in the impact
		v = origin - v * 32;
		num = ((float)charge)*0.005;

		decay = 0.1 + ((float)charge)*0.0045;

		// make the visual explosion
		SpawnBowExplosion( v, 0 );

		charge = 100; // for view jitter

		// make the view jitter for the explosion
		f1 = 6;
		f2 = 4;
		jitter = new RadiusJitter;
		jitter->Setup( v, damg + 40, 0.25, 0.2, f1, f1*1.5, 0.2, f2, f2*1.5 );

		// make the explosion sound
		RandomPositionedSound( v, "impact_smallplasmaexplosion", 1.0, CHAN_AUTO, ATTN_NORM );

		hideModel();
		PostEvent( EV_Remove, 0.1 );

		return;
	}

	v = velocity;
	v.normalize();

	// don't do radius damage to the other, because all the damage
	// was done in the impact
	v = worldorigin - v * 24;

	damg = 95 + (int)G_Random( 30 );
	if(!deathmatch->value)
		damg *= 1.5;

	// hit a damagable entity, let's blow up now
	if(other && other->takedamage)
	{
		other->Damage( this, owner, damg, origin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_PLASMABOW, -1, -1, 1.0f );

		TempModel( NULL, v, "0 0 0", "sprites/cbshot.spr", 0, 0.5, 0.8f, TEMPMODEL_ANIMATE_ONCE|TEMPMODEL_ANIMATE_FAST, 2 );

		// make the view jitter for the explosion
		jitter = new RadiusJitter;
		jitter->Setup( v, 64, 0.25, 0.2, 6, 9, 0.2, 4, 6 );

		RandomPositionedSound( v, "impact_smallplasmaexplosion", 1.0, CHAN_AUTO, ATTN_NORM );

		PostEvent( EV_Remove, 0.1 );
		return;
	}

	// stick it into the wall
	setOrigin( v );
	velocity = vec_zero;

	surfaceManager.DamageSurface ( &level.impact_trace, damg, owner );

	RandomAnimate( "stick", NULL );

	// play sticking sound
	RandomSound( "snd_stick", 1.0, CHAN_AUTO, ATTN_NORM );

	PostEvent( EV_PBolt_Explode, 1 );
}

EXPORT_FROM_DLL void PBolt::Setup( Entity *owner, Vector pos, Vector dir, int firecharge )
{
	Event *ev;
	float fltcharge;

	this->owner = owner->entnum;
	edict->owner = owner->edict;

	// set energy bolt's charge amount
	charge = firecharge;
	fltcharge = charge;

	setMoveType( MOVETYPE_TOSS );
	setSolidType( SOLID_BBOX );
	edict->clipmask = MASK_SHOT;

	SetGravityAxis( owner->gravaxis );

	angles = dir.toAngles();
	angles[PITCH] = -angles[PITCH];
	angles[ROLL] = 45;
	setAngles( angles );

	if(charge < 0)
	{
		if(owner->isClient())
			speed = 1800;
		else
			speed = 1000;
	}
	else
	{
		speed = 600 + 14*charge;
		if(charge > 100)
			speed *= 0.5;
	}
	velocity = dir * speed;

	PostEvent( EV_PBolt_EnergyTrail, 0.1 );

	// set missile duration
	ev = new Event( EV_PBolt_Explode );
	ev->AddEntity( world );
	if(charge > 100) // overloaded, so blow mid-air
		PostEvent( ev, 0 );
	else
		PostEvent( ev, 10 );

	if(owner->isClient())
		setModel( "pbolt.def" );
	else
		setModel( "bobbolt.def" );
	edict->s.renderfx |= RF_DLIGHT;

	// pick the effect trail to use
	if(charge < 0)
	{
		edict->s.effects |= EF_PLASMATRAIL2;

		gravity = 0.15;
		edict->s.color_r    = 0.5;
		edict->s.color_g    = 1;
		edict->s.color_b    = 0.5;
		edict->s.radius = 100;
	}
	else
	{
		edict->s.effects |= EF_PLASMATRAIL1;

		gravity = 0.6 - fltcharge*0.004;
		// adjust bolt glow according to charge
		edict->s.color_r    = fltcharge*0.003;
		edict->s.color_g    = 1;
		edict->s.color_b    = fltcharge*0.003;
		if(charge > 100) // overloaded, so blow mid-air
			edict->s.radius = 200;
		else
			edict->s.radius = 150;
	}

	setSize( "-2 -2 -2", "2 2 2" );
	setOrigin( pos );
	worldorigin.copyTo( edict->s.old_origin );
}

//=================================================================
// plasmabow weapon class

Event EV_PlasmaBow_ReleaseCheck( "plasmabow_releasecheck" );
Event EV_PlasmaBow_Full( "plasmabow_full" );

CLASS_DECLARATION( Weapon, PlasmaBow, "weapon_plasmabow" );

ResponseDef PlasmaBow::Responses[] =
{
	// charge release check
	{&EV_PlasmaBow_ReleaseCheck, (Response)PlasmaBow::ReleaseCheck},
	// retain full charge
	{&EV_PlasmaBow_Full, (Response)PlasmaBow::FullRetain},
	// actual firing from button release
	{&EV_Weapon_Shoot, (Response)PlasmaBow::Shoot},
	{NULL, NULL}
};

PlasmaBow::PlasmaBow()
{
	SetModels( "crossbow_w.def", "view_crossbow.def" );
	modelIndex( "pbolt.def" );
	modelIndex( "sprites/cbshot.spr" );
	SetAmmo( "BulletPulse", 5, 90 );
	SetSecondaryAmmo( "BulletPulse", 10, 0 );
	SetRank( 65, 75 );
	SetType( WEAPON_2HANDED_HI );
	dualmode = true;
	primary_ammo_type = "BulletPulse";
	secondary_ammo_type = "BulletPulse";

	SetMinRange( 50 );
	SetProjectileSpeed( 500 );
}

void PlasmaBow::SecondaryUse( Event *ev )
{
	// switch to the shotgun
	owner->useWeapon( "Shotgun" );
}

// called when attack button is first pressed
void PlasmaBow::Fire( void )
{
	if (!ReadyToFire())
	{
		return;
	}

	if(!HasAmmoInClip())
	{
		CheckReload();
		return;
	}

	// charge fire mode
	if(weaponmode == PRIMARY)
	{
		UseAmmo( 5 );
		drainammo = 1;

		// init. charging state
		weaponstate = WEAPON_CHARGING;
		fullcycles = MAX_FULL_CYCLES;
		chargetime = level.time;

		CancelEventsOfType( EV_Weapon_DoneFiring );
		RandomAnimate( "charge", EV_PlasmaBow_Full );
		// check for button release every 0.1 seconds
		PostEvent( EV_PlasmaBow_ReleaseCheck, 0.1 );
		// this is just a precaution that we can re-trigger
		NextAttack( 5 );
	}
	else // instant fire mode
	{
		UseAmmo( secondary_ammorequired );

		weaponstate = WEAPON_FIRING;

		chargetime = level.time + 10;

		CancelEventsOfType( EV_Weapon_DoneFiring );
		RandomAnimate( "fire", EV_Weapon_DoneFiring );
		// this is just a precaution that we can re-trigger
		NextAttack( 5 );
	}

	last_attack_time = level.time;
}

void PlasmaBow::ReleaseCheck( Event *ev )
{
	// check for owner dying or getting on a hoverbike
	if((!owner) || (this != owner->CurrentWeapon()) || (((Player *)owner.Ptr())->GetHoverbike()) ||
	   (deathmatch->value == DEATHMATCH_MFD && owner->isClient() && owner->client->resp.informer))
	{
		//cancel weapon charging
		CancelEventsOfType( EV_PlasmaBow_ReleaseCheck );
		CancelEventsOfType( EV_PlasmaBow_Full );
		weaponstate = WEAPON_READY;
		StopAnimating();
		DetachFromOwner();
		return;
	}

	// check for attack button release
	// also fire if you run out of ammo to charge up
	if(!(((Player *)owner.Ptr())->Buttons() & BUTTON_ATTACK))
	{
		CancelEventsOfType( EV_PlasmaBow_Full );

		weaponstate = WEAPON_FIRING;
		RandomAnimate( "fire", EV_Weapon_DoneFiring );

		return;
	}

	PostEvent( EV_PlasmaBow_ReleaseCheck, 0.1 );
}

void PlasmaBow::FullRetain( Event *ev )
{
	// check for owner dying or getting on a hoverbike
	if((!owner) || (((Player *)owner.Ptr())->GetHoverbike()))
	{
		CancelEventsOfType( EV_PlasmaBow_ReleaseCheck );
		CancelEventsOfType( EV_PlasmaBow_Full );
		weaponstate = WEAPON_READY;
		StopAnimating();
		return;
	}

	RandomAnimate( "full", EV_PlasmaBow_Full );
}

// release button function
void PlasmaBow::Shoot( Event *ev )
{
	PBolt *bolt;
	Vector pos, dir, r, u;
	float boltcharge;

	assert( owner );
	if(!owner)
		return;

	// calc. charge amount
	boltcharge = level.time - chargetime;
	if(boltcharge < 0)
	{
		boltcharge = -1;
	}
	else if(boltcharge >= (CHARGE_TIME + MAX_FULL_CYCLES*2))
	{
		boltcharge = 100; //bolt is overloaded
	}
	else
	{
		boltcharge = floor((boltcharge / CHARGE_TIME) * 100 );
		if(boltcharge > 100)
			boltcharge = 100;
	}

	if(boltcharge >= 0)
	{
		// put owner into a firing animation
		if(owner->isClient())
		{
			str name;
			str prefix;
			Player *player;

			player = (Player *)owner.Ptr();

			prefix = player->AnimPrefixForPlayer();
			//
			// append the prefix based on which weapon we are holding
			//
			prefix += str( player->AnimPrefixForWeapon() );

			if ( ( player->GetXYspeed() > 20 ) )
			{
				int num;
				int numframes;

				if ( ( player->waterlevel > 2 ) || ( player->GetXYspeed() > 250 ) )
					name = prefix + str( "run_fire" );
				else
					name = prefix + str( "walk_fire" );
				num = gi.Anim_Random( player->edict->s.modelindex, name.c_str() );
				if ( num != -1 )
				{
					numframes = gi.Anim_NumFrames( player->edict->s.modelindex, num );
					if ( ( player->last_frame_in_anim + 1 ) == numframes )
						player->edict->s.anim = num;
					else
						player->TempAnim( name.c_str(), NULL );
				}
				else
				{
					name = prefix + str( "fire" );
					player->TempAnim( name.c_str(), NULL );
				}
			}
			else
			{
				name = prefix + str( "fire" );
				player->TempAnim( name.c_str(), NULL );
			}
		}
		else
		{
			owner->TempAnim( "fire", NULL );
		}
	}


	GetMuzzlePosition( &pos, &dir, &r, &u );
	pos -= (r*4 + u*4);

	bolt = new PBolt;
	bolt->Setup( owner, pos, dir, boltcharge );

	if(boltcharge < 0)
		NextAttack( 1.2 );
	else if(fullcycles == 0)
		NextAttack( 1.5 );
	else
		NextAttack( 0.7 );
}

//==================================================================
// And yae, from yonder, hither weapon doth seth fourth evil things...

class EXPORT_FROM_DLL EvilPlasmaBow : public PlasmaBow
{
public:
	CLASS_PROTOTYPE( EvilPlasmaBow );

	EvilPlasmaBow();
	virtual void Shoot( Event *ev );
	virtual void Fire( void );
};

CLASS_DECLARATION( PlasmaBow, EvilPlasmaBow, "weapon_evilplasmabow" );

ResponseDef EvilPlasmaBow::Responses[] =
{
	{&EV_Weapon_Shoot, (Response)EvilPlasmaBow::Shoot},
	{NULL, NULL}
};

EvilPlasmaBow::EvilPlasmaBow()
{}

void EvilPlasmaBow::Fire( void )
{
	if (!ReadyToFire())
	{
		return;
	}

	if(!HasAmmoInClip())
	{
		CheckReload();
		return;
	}

	UseAmmo( 5 );

	// init. charging state
	fullcycles = MAX_FULL_CYCLES;
	chargetime = level.time - CHARGE_TIME;

	CancelEventsOfType( EV_Weapon_DoneFiring );
	weaponstate = WEAPON_READY;
	RandomAnimate( "fire", EV_Weapon_DoneFiring );

	// this is just a precaution that we can re-trigger
	NextAttack( 5 );

	last_attack_time = level.time;
}

void EvilPlasmaBow::Shoot( Event *ev )
{
	PlasmaBow::Shoot( ev );
	NextAttack( 0.1 );
}
