/*
================================================================
CONCUSSION GUN
================================================================

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 "g_local.h"
#include "concussion.h"
#include "worldspawn.h"
#include "player.h"

#define MAX_CONCUSSION_AMMO 100
#define CONCUSSION_REGEN_TIME_DM 0.5
#define CONCUSSION_REGEN_TIME 0.6

//==================================================================

CLASS_DECLARATION( Entity, ConcussionRing, NULL );

Event EV_ConcussionRing_Animate2( "concussionring_animate2" );
Event EV_ConcussionRing_Animate3( "concussionring_animate3" );

ResponseDef ConcussionRing::Responses[] =
{
	{&EV_ConcussionRing_Animate2, (Response)&ConcussionRing::Animate2},
	{&EV_ConcussionRing_Animate3, (Response)&ConcussionRing::Animate3},
	{NULL, NULL}
};

void ConcussionRing::Animate2( Event *ev )
{
	edict->s.scale += 0.4;
	edict->s.alpha *= 0.6;
	edict->s.renderfx |= RF_TRANSLUCENT;
	PostEvent( EV_ConcussionRing_Animate2, FRAMETIME );
}

void ConcussionRing::Animate3( Event *ev )
{
	edict->s.scale += 0.1;
	edict->s.alpha *= 0.5;
	edict->s.renderfx |= RF_TRANSLUCENT;
	PostEvent( EV_ConcussionRing_Animate3, FRAMETIME );
}

void ConcussionRing::Setup( Vector pos, Vector dir )
{
	setMoveType( MOVETYPE_FLY );
	setSolidType( SOLID_NOT );
	setOrigin( pos );
	worldorigin.copyTo( edict->s.old_origin );

	angles = dir.toAngles();
	angles[PITCH] = -angles[PITCH];
	angles[ROLL] = rand() % 360;
	avelocity[2] = crandom()*120;
	setAngles( angles );

	setModel( "sprites/concussion.spr" );

	edict->s.renderfx |= RF_TRANSLUCENT;
}

//==================================================================

Event EV_Concussion_Effect( "concussion_effect" );
Event EV_Concussion_Regen( "concussion_regen" );

CLASS_DECLARATION( Weapon, ConcussionGun, "weapon_concussiongun" );

ResponseDef ConcussionGun::Responses[] =
{
	{&EV_Weapon_Shoot, (Response)&ConcussionGun::Shoot},
	{&EV_Concussion_Effect, (Response)&ConcussionGun::BlastEffect},
	{&EV_Concussion_Regen, (Response)&ConcussionGun::BatteryRegen},
	{NULL, NULL}
};

ConcussionGun::ConcussionGun()
{
	SetModels( "concussion_w.def", "view_concussion.def" );
	modelIndex( "sprites/concussion.spr" );
	SetAmmo( "ConcussionBattery", 10, MAX_CONCUSSION_AMMO );
	SetRank( 75, 25 );
	SetType( WEAPON_2HANDED_LO );

	SetMinRange( 0 );
	SetProjectileSpeed( 0 );
}

void ConcussionGun::SecondaryUse( Event *ev )
{
	// switch to the fists
	owner->useWeapon( "Fists" );
}

void ConcussionGun::BatteryRegen( Event *ev )
{
	// don't regen battery if owner is dead
	if(!owner)
		return;

	// only do for players
	if(!owner->isClient())
		return;

	// first make sure that we still have a living owner
	if(!owner || owner->health < 1)
	{
		CancelEventsOfType( EV_Concussion_Regen );
		return;
	}

	if(AmmoAvailable() < MAX_CONCUSSION_AMMO)
	{
		owner->giveItem( ammotype.c_str(), 2 );

		// if still below max, then need to do it again
		if(AmmoAvailable() < MAX_CONCUSSION_AMMO)
		{
			if(deathmatch->value)
				PostEvent( EV_Concussion_Regen, CONCUSSION_REGEN_TIME_DM );
			else
				PostEvent( EV_Concussion_Regen, CONCUSSION_REGEN_TIME );
		}
	}
}

// start the concussion gun's firing sequence
void ConcussionGun::Shoot( Event *ev )
{
	ConcussionRing *ring;
	Vector targvec, vec;
	Vector pos, dir, right;
	Vector end;
	trace_t trace;
	float f;

	assert( owner );

	GetMuzzlePosition( &pos, &dir, &right );
	end = pos - dir*40;
	trace = G_Trace( pos, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), end, owner, MASK_PLAYERSOLID, "ConcussionGun::Shoot" );
	pos = trace.endpos;

	// do kick back
	end = pos + (dir*CONCUSSION_DIST*0.5);
	trace = G_Trace( pos, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ), end, owner, MASK_PLAYERSOLID, "ConcussionGun::Shoot" );
	f = (1 - trace.fraction)*CONCUSSION_KICKBACK;
	if(f < 400)
		f = 400;
	// reduce upwards kickback in singleplayer
	if(!deathmatch->value)
	{
		owner->velocity.x -= dir.x*f;
		owner->velocity.y -= dir.y*f;
		if(dir.z < 0)
			owner->velocity.z -= dir.z*f*0.3;
		else
			owner->velocity.z -= dir.z*f*0.5;
	}
	else // in DM, let 'em fly
	{
		owner->velocity -= dir*f;
	}

	// store firing position & dir for later
	lastfirepos = pos;
	lastfiredir = dir;

// make a narrow moving ring at the start of the blast
	pos += dir*2 + right*4;
	ring = new ConcussionRing;
	ring->Setup( pos, lastfiredir );
	ring->velocity = lastfiredir*500;
	ring->edict->s.scale = 0.05;
	ring->edict->s.alpha = 0.75;
	ring->PostEvent( EV_ConcussionRing_Animate2, FRAMETIME );
	ring->PostEvent( EV_Remove, 1 );

	NextAttack( 1 );

	blastcount = 0;
	ProcessEvent( EV_Concussion_Effect );

	// post the battery regenerating event
	if(owner->isClient())
	{
		CancelEventsOfType( EV_Concussion_Regen );
		PostEvent( EV_Concussion_Regen, CONCUSSION_REGEN_TIME );
	}
}

void ConcussionGun::BlastEffect( Event *ev )
{
	Entity *tmpent = NULL;
	ConcussionRing *ring;
	Vector targvec, vec;
	Vector end;
	trace_t trace;
	float d, f, targdist;

	if(!owner)
	{
		weaponstate = WEAPON_READY;
		return;
	}

	tmpent = NULL;
	while((tmpent = findradius( tmpent, lastfirepos, CONCUSSION_DIST ))!=0)
	{
		if(tmpent == owner)
			continue;
		// only effect damagable things and projectiles
		// don't effect BSP model entities
		if(
			((!tmpent->takedamage) &&
			 (!tmpent->isSubclassOf( Projectile ))) ||
			(tmpent->edict->solid == SOLID_BSP)
			)
			continue;

		targvec = tmpent->origin + (tmpent->mins + tmpent->maxs)*0.5;

		trace = G_Trace( lastfirepos, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ), targvec, owner, MASK_SOLIDNONFENCE, "ConcussionGun::BlastEffect" );
		if(trace.fraction < 1)
			continue;

		vec = targvec - lastfirepos;
		targdist = vec.normalize2();

		d = DotProduct( vec, lastfiredir );
		if(d < CONCUSSION_ARC)
			continue;

		if(!deathmatch->value) // don't make extra rings in DM, too much net traffic
		{
			if(tmpent->concussion_debounce < level.time)
			{
				// make a visible indication of a hit
				end = targvec - lastfiredir*32;
				ring = new ConcussionRing;
				ring->Setup( end, lastfiredir );
				ring->velocity = lastfiredir*50;
				ring->edict->s.scale = 0.02;
				ring->edict->s.alpha = 0.75;
				ring->PostEvent( EV_ConcussionRing_Animate3, FRAMETIME );
				ring->PostEvent( EV_Remove, 0.4 );
			}

			tmpent->concussion_debounce = level.time + 0.3;
		}

		f = CONCUSSION_PUSH - (targdist * CONCUSSION_PUSH_DECAY);
		// scale the force by the directional dot product
		f *= d;
		// scale the force by the mass of the target
		f *= 1/(tmpent->mass*0.005);
		if(f < 0)
			f = 0;

		//apply force, but not to script objects and models
		if((f > 0) && (!tmpent->isSubclassOf( ScriptSlave )))
		{
			tmpent->velocity += vec * f;

			if(!deathmatch->value)
			{
				if(tmpent->velocity[2] <= 0)
					tmpent->velocity[2] = 50;
				else
					tmpent->velocity[2] += 50;
			}
			else
			{
				if(tmpent->velocity[2] <= 0)
					tmpent->velocity[2] = 75;
				else
					tmpent->velocity[2] += 75;
			}

			// set info for wall splatting damage
			tmpent->concussioner = owner;

			if(tmpent->isSubclassOf( Player ))
			{
				Player *player;

				player = (Player *)tmpent;
				player->concussion_timer = level.time + 1;
				// jitter the poor sucker's view
				f = f/CONCUSSION_PUSH;
				player->SetAngleJitter( f*10, f*30, level.time );
				player->SetOffsetJitter( f*5, f*20, level.time + 0.1 );
			}
		}

		// do some damage if applicable
		f = CONCUSSION_DAMG - (targdist * CONCUSSION_DAMG_DECAY);
		// scale the damage by the directional dot product
		f *= d;
		if(f < 0)
			f = 0;
		if(tmpent->takedamage)
			tmpent->Damage( this, owner, f, tmpent->origin, lastfiredir, level.impact_trace.plane.normal, 32, 0, MOD_CONCUSSION, -1, -1, 1.0f );
	}


	// set effect counter as needed
	if(blastcount < CONCUSSION_COUNT)
	{
		// do effect again
		PostEvent( EV_Concussion_Effect, 0.1 );
		blastcount++;
	}
}

// this stuff was added to prevent changing weapons
// when doesn't have enough ammo to fire

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

	if(!(((ammo_clip_size && ammo_in_clip >= ammorequired) || AmmoAvailable() >= ammorequired)))
	{
		return;
	}

	UseAmmo( ammorequired );

	weaponstate = WEAPON_FIRING;

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

	RandomAnimate( "fire", EV_Weapon_DoneFiring );

	last_attack_time = level.time;
}

qboolean ConcussionGun::HasAmmo( void )
{
	if ( !owner )
	{
		return false;
	}

	// always return true from this
	return true;
}

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

class EvilConcussionGun : public ConcussionGun
{
public:
	CLASS_PROTOTYPE( EvilConcussionGun );

	EvilConcussionGun();
	virtual void Shoot( Event *ev );
};

CLASS_DECLARATION( ConcussionGun, EvilConcussionGun, "weapon_evilconcussiongun" );

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

EvilConcussionGun::EvilConcussionGun()
{}

void EvilConcussionGun::Shoot( Event *ev )
{
	ConcussionGun::Shoot( ev );
	NextAttack( 0.5 );
	weaponstate = WEAPON_READY;
}
