/*
================================================================
NUKE LAUNCHER aka IP36
================================================================

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 "explosion.h"
#include "nuke.h"
#include "worldspawn.h"
#include "misc.h"
#include "surface.h"
#include "jitter.h"
#include "ctf.h"

#define NUKE_SPEED 700
#define NUKE_RADIUS 800

//=================================================================
// nuke flash maker

CLASS_DECLARATION( Entity, NukeFlash, NULL );

Event EV_NukeFlash_Flash( "nukeflash_flash" );

ResponseDef NukeFlash::Responses[] =
{
	{&EV_NukeFlash_Flash, (Response)&NukeFlash::Flash},
	{NULL, NULL}
};

void NukeFlash::Flash( Event *ev )
{
	Entity  *ent;

	ent = G_NextEntity( NULL );
	while(ent)
	{
		// only apply view flash to players
		if(ent->isSubclassOf( Player ))
		{
			if(CanDamage( ent ))
			{
				((Player *)ent)->StartNukeFlash( fblend, falpha );
			}
		}

		ent = G_NextEntity( ent );
	}

	// do fade in
	if(falpha > 1)
	{
		//fade in the flash
		falpha += 0.4;
		// switch to fade out when we reach full opacity
		if(falpha >= 2)
			falpha = 1;
	}
	else // do fade out
	{
		// decay color to red
		fblend[1] -= 0.02;
		fblend[2] = fblend[1];

		// decay alpha
		if(falpha > 0.6)
			falpha -= 0.1;
		else
		{
			falpha -= 0.035;
			if(falpha < 0)
				falpha = 0;
		}
	}

	if(falpha > 0)
		PostEvent( EV_NukeFlash_Flash, 0.1 );
	else
		ProcessEvent( EV_Remove );
}

void NukeFlash::Setup( Vector pos, float delay )
{
	setMoveType( MOVETYPE_NONE );
	setSolidType( SOLID_NOT );

	setOrigin( pos );
	worldorigin.copyTo( edict->s.old_origin );

	fblend[0] = fblend[1] = fblend[2] = 1;
	falpha = 1.01;

	if(delay)
		PostEvent( EV_NukeFlash_Flash, delay );
	else
		ProcessEvent( EV_NukeFlash_Flash );
}
//=================================================================
// nuke fireball

CLASS_DECLARATION( Projectile, NukeFireball, NULL );

Event EV_NukeFireball_Animate( "fireball_animate" );
Event EV_NukeFireball_Damage( "fireball_damage" );
Event EV_NukeFireball_Fade( "fireball_fade" );

ResponseDef NukeFireball::Responses[] =
{
	{&EV_NukeFireball_Animate, (Response)&NukeFireball::Animate},
	{&EV_NukeFireball_Damage, (Response)&NukeFireball::DoDamage},
	{&EV_NukeFireball_Fade, (Response)&NukeFireball::Fade},
	{NULL, NULL}
};

void NukeFireball::DoDamage( Event *ev )
{
	Entity  *ownerent;
	float points, dist;
	Entity  *ent;
	Vector org;
	Vector v;
	float rad, minrad;
	int damage;

	if(deathmatch->value)
		damage = 80;
	else
		damage = 120;

	rad = edict->s.scale*64 + 128;
	minrad = rad - 256;

	ownerent = G_GetEntity( owner );

	ent = findradius( NULL, origin.vec3(), rad );
	while(ent)
	{
		if(ent->takedamage)
		{
			org = ent->origin + (ent->mins + ent->maxs)*0.5;
			v = origin - org;
			dist = v.length();

			if(dist > minrad)
			{
				points =  dist*(float)0.04;
				if(points < 0)
				{
					points = 0;
				}
				points = damage - points;
				if(ent == ownerent)
				{
					points *= 0.75;
				}

				if(points > 0)
				{
					if(CanDamage( ent ))
					{
						points += 0.5;
						ent->Damage( this, ownerent, (int)points, ent->origin, v*(-1), vec_zero, points*0.1, DAMAGE_RADIUS, MOD_NUKEEXPLOSION, -1, -1, 1.0f );
					}
				}
			}
		}
		ent = findradius( ent, origin.vec3(), rad );
	}

	// continue doing damage every 0.1 seconds
	PostEvent( EV_NukeFireball_Damage, 0.1 );
}

void NukeFireball::Fade( Event *ev )
{
	PostEvent( EV_NukeFireball_Fade, 0.1 );

	edict->s.renderfx |= RF_TRANSLUCENT;
	translucence += 0.15;
	if(translucence >= 0.98)
	{
		ProcessEvent( EV_Remove );
		return;
	}
	setAlpha( 1.0 - translucence );
}

void NukeFireball::Animate( Event *ev )
{
	edict->s.scale += 0.4;
	PostEvent( EV_NukeFireball_Animate, 0.1 );
}

void NukeFireball::Setup( Entity *owner, Vector pos, float life, int type )
{
	this->owner = owner->entnum;
	edict->owner = owner->edict;

	setMoveType( MOVETYPE_FLY );
	setSolidType( SOLID_NOT );

	edict->s.frame = 0;

	angles[PITCH] = 0;
	angles[YAW] = random()*360;
	angles[ROLL] = 0;
	setAngles( angles );

	setModel( "sprites/nukering.spr" );
	edict->s.scale = 0.1;
	// setup it's transparency
	edict->s.renderfx |= RF_TRANSLUCENT;
	translucence = 0;
	setAlpha( 1.0f - translucence );
	// the outward most fireball does the expanding damage
	PostEvent( EV_NukeFireball_Damage, 0.1 );
	PostEvent( EV_NukeFireball_Animate, 0.2 );

	setOrigin( pos );
	worldorigin.copyTo( edict->s.old_origin );

	PostEvent( EV_NukeFireball_Fade, life );
}

//=================================================================
// nuke energyball

CLASS_DECLARATION( Projectile, NukeBall, NULL );

Event EV_NukeBall_Animate( "nuke_animate" );
Event EV_NukeBall_Collapse( "nuke_collapse" );
Event EV_NukeBall_Explode( "nuke_explode" );
Event EV_NukeBall_ThrowRing( "nuke_throwring" );
Event EV_NukeBall_ExplosionBall( "nuke_explosionball" );

ResponseDef NukeBall::Responses[] =
{
	{&EV_Touch, (Response)&NukeBall::Collapse},
	{&EV_NukeBall_Animate, (Response)&NukeBall::Animate},
	{&EV_NukeBall_Collapse, (Response)&NukeBall::Collapse},
	{&EV_NukeBall_Explode, (Response)&NukeBall::Explode},
	{&EV_NukeBall_ThrowRing, (Response)&NukeBall::ThrowRing},
	{&EV_NukeBall_ExplosionBall, (Response)&NukeBall::ExplosionBall},
	{NULL, NULL}
};

void NukeBall::Animate( Event *ev )
{
	edict->s.frame++;
	if(edict->s.frame >= 10)
		edict->s.frame = 0;

	PostEvent( EV_NukeBall_Animate, FRAMETIME );
}

void NukeBall::Collapse( Event *ev )
{
	Entity  *other;
	Entity  *owner;
	int damg;

	other = ev->GetEntity( 1 );

	owner =  G_GetEntity( this->owner );

	if(deathmatch->value)
		damg = 250;
	else
		damg = 300;

	// touched something that triggered exploding
	if(ballstate < NBS_COLLAPSING)
	{
		NukeFlash *flash;
		qboolean antizone;
		Entity *ent;

		assert( other );

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

		// whak touched entity
		if (other->takedamage)
			other->Damage( this, owner, damg, origin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_NUKE, -1, -1, 1.0f );

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

		// make it collapse
		CancelEventsOfType( EV_NukeBall_Animate );

		setMoveType( MOVETYPE_FLY );
		setSolidType( SOLID_NOT );
		velocity.normalize();
		setOrigin( origin - velocity*24 );
		velocity = vec_zero;
		edict->s.scale = 0.2;

		TempModel( NULL, origin, vec_zero, "sprites/implosion.spr", 0, 1, 1.0f, TEMPMODEL_ANIMATE_ONCE|TEMPMODEL_ANIMATE_FAST, 2 );

		// post explosion event for end of collapsing
		PostEvent( EV_NukeBall_Explode, 0.4 );

		//create flash entity
		flash = new NukeFlash;
		flash->Setup( origin, 0.2 );

		// check for touching NoNukeZone
		antizone = false;
		ent = G_NextEntity( NULL );
		while(ent)
		{
			if(ent->isSubclassOf( NoNukeZone ))
			{
				if((origin.x < ent->absmax.x) && (origin.x > ent->absmin.x) &&
				   (origin.y < ent->absmax.y) && (origin.y > ent->absmin.y) &&
				   (origin.z < ent->absmax.z) && (origin.z > ent->absmin.z))
				{ // we're inside one
					antizone = true;
					break;
				}
			}

			ent = G_NextEntity( ent );
		}

		// check for evilness...
		if(ballstate == NBS_EVIL && antizone)
		{
			// evilness, here we come...
			int evilcount;
			Vector evildirection;
			NukeBall *ball;
			Vector pos, dir;
			Vector right, up;
			Vector tmpvec;

			for(evilcount = 0; evilcount < 6; evilcount++)
			{
				switch(evilcount)
				{
				case 0:
					evildirection = Vector( 1, 0, 0 );
					break;
				case 1:
					evildirection = Vector( -1, 0, 0 );
					break;
				case 2:
					evildirection = Vector( 0, 1, 0 );
					break;
				case 3:
					evildirection = Vector( 0, -1, 0 );
					break;
				case 4:
					evildirection = Vector( 0, 0, 1 );
					break;
				case 5:
				default:
					evildirection = Vector( 0, 0, -1 );
					break;
				}

				tmpvec = worldorigin + evildirection*24;

				ball = new NukeBall;
				ball->Setup( owner, tmpvec, evildirection );
				ball->takedamage = DAMAGE_NO;
			}
		}

		//setup imploding stuff
		ballstate = NBS_COLLAPSING;
	}
	// always check if something was touched
	else if(other)
	{
		if (other->takedamage)
			other->Damage( this, owner, damg, origin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_NUKE, -1, -1, 1.0f );

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

	// collapse & animate the model
	if(edict->s.scale > 0.2)
	{
		edict->s.scale -= 0.2;
		if(edict->s.scale <= 0.2)
		{
			edict->s.scale = 0.2;
			edict->s.frame = 0;
			stopsound( CHAN_VOICE );
			hideModel();
		}
		else
		{
			edict->s.frame++;
			if(edict->s.frame >= 10)
				edict->s.frame = 0;
		}
	}
}

void NukeBall::Explode( Event *ev )
{
	NukeFireball *fball;
	Event *event;
	RadiusJitter *jitter;
	Entity *owner;
	qboolean antizone;
	Entity *ent;

	CancelEventsOfType( EV_NukeBall_Animate );
	CancelEventsOfType( EV_NukeBall_Collapse );

	takedamage = DAMAGE_NO;

	owner = G_GetEntity( this->owner );

	// check for touching NoNukeZone
	antizone = false;
	ent = G_NextEntity( NULL );
	while(ent)
	{
		if(ent->isSubclassOf( NoNukeZone ))
		{
			if((origin.x < ent->absmax.x) && (origin.x > ent->absmin.x) &&
			   (origin.y < ent->absmax.y) && (origin.y > ent->absmin.y) &&
			   (origin.z < ent->absmax.z) && (origin.z > ent->absmin.z))
			{ // we're inside one
				antizone = true;
				break;
			}
		}

		ent = G_NextEntity( ent );
	}

	// small amount of damage to everyone visible from initial flash
	RadiusDamage( this, owner, 20, NULL, MOD_NUKEEXPLOSION, 0.002 );

	// alota damage to anything near the initial blast
	if(antizone) // don't do as much explosive damage
		RadiusDamage( this, owner, 200, NULL, MOD_NUKEEXPLOSION, 0.35 );
	else if(deathmatch->value)
		RadiusDamage( this, owner, 250, NULL, MOD_NUKEEXPLOSION, 0.4 );
	else
		RadiusDamage( this, owner, 300, NULL, MOD_NUKEEXPLOSION, 0.45 );

	// post the particle explosion events
	if(antizone)
	{ // much smaller explosion effect when in an anti-nuke zone
		event = new Event( EV_NukeBall_ExplosionBall );
		event->AddInteger( 40 );
		ProcessEvent( event );

		event = new Event( EV_NukeBall_ExplosionBall );
		event->AddInteger( 64 );
		PostEvent( event, 0.4 );

		// no expanding fireball of death, just a visual effect
		TempModel( NULL, origin, Vector( 90,0,G_Random( 360 )), "sprites/nukering2.spr", 0, 5.0f, 1.0f, TEMPMODEL_ANIMATE_SCALE|TEMPMODEL_ALPHAFADE, 15.0f );
	}
	else
	{
		event = new Event( EV_NukeBall_ExplosionBall );
		event->AddInteger( 64 );
		ProcessEvent( event );

		event = new Event( EV_NukeBall_ExplosionBall );
		event->AddInteger( 128 );
		PostEvent( event, 0.6 );

		event = new Event( EV_NukeBall_ExplosionBall );
		event->AddInteger( 240 );
		PostEvent( event, 1.1 );

		fball = new NukeFireball;
		fball->Setup( owner, origin, 1.8, 1 );

		TempModel( NULL, origin, Vector( 90,0,G_Random( 360 )), "sprites/nukering2.spr", 0, 10.0f, 1.0f, TEMPMODEL_ANIMATE_SCALE|TEMPMODEL_ALPHAFADE, 20.0f );
	}

	// nuke explosion sounds
	/* FIXME: RandomPositionedSound() takes 'int attenuation'
	 *	  not a float: 0.75 becomes 0 (ATTN_NONE) here */
	RandomPositionedSound( origin, "impact_nukeexplosion", 1, CHAN_AUTO, 0.75 );

	// add in the ass whoopin' view jitter
	jitter = new RadiusJitter;
	jitter->Setup( origin, 1600, 0.03,
	               0.8, 15, 4,
	               1.5, 10, 5 );

	PostEvent( EV_Remove, 1.5 );

	//do a bit of dialog for the first time firing it in single player
	if(owner->isClient() && !deathmatch->value)
	{
		ScriptVariable  *var;

		var = gameVars.GetVariable( "firstnukefire" );

		if(!var) // hasn't been fired yet this game
		{
			var = gameVars.CreateVariable( "firstnukefire", 1 );

			// call the dialog thread for it
			ExecuteThread( "global/nukeweapon.scr::nuke_first_fire", true );
		}
	}
}

void NukeBall::ThrowRing( Event *ev )
{
	TempModel( NULL, origin, Vector( 90, G_Random( 360 ), 0 ), "sprites/nukering2.spr",
	           0, 8.0f, 1.0f, TEMPMODEL_ANIMATE_SCALE|TEMPMODEL_ALPHAFADE, 15.0f );
	TempModel( NULL, origin, Vector( -90, G_Random( 360 ), 0 ), "sprites/nukering2.spr",
	           0, 8.0f, 1.0f, TEMPMODEL_ANIMATE_SCALE|TEMPMODEL_ALPHAFADE, 15.0f );
}

void NukeBall::ExplosionBall( Event *ev )
{
	int size;

	size = ev->GetInteger( 1 );
	SpawnNukeExplosion( origin, size, 153 );
}

void NukeBall::Setup( Entity *owner, Vector pos, Vector dir )
{
	Event *ev;

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

	setMoveType( MOVETYPE_FLYMISSILE );
	setSolidType( SOLID_BBOX );
	edict->clipmask = MASK_PROJECTILE;

	edict->s.frame = 0;

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

	speed = NUKE_SPEED;
	velocity = dir * NUKE_SPEED;

	// set nuke duration
	PostEvent( EV_NukeBall_Animate, FRAMETIME );
	ev = new Event( EV_NukeBall_Collapse );
	ev->AddEntity( world );
	PostEvent( ev, 1.5 );

	ballstate = NBS_FLYING;

	setModel( "sprites/nukeball.spr" );
	edict->s.renderfx |= RF_DLIGHT;
	edict->s.effects  |= EF_NUKETRAIL;
	gravity = 0;
	edict->s.color_r = 0.8;
	edict->s.color_g = 0.2;
	edict->s.color_b = 0.3;
	edict->s.radius = 350;

	// setup ambient thrust
	edict->s.sound = gi.soundindex( "weapons/nuke/ballsound.wav" );
	edict->s.sound |= ATTN_IDLE<<14;

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

// for devious purposes...
void NukeBall::EvilSetup( Entity *owner, Vector pos, Vector dir )
{
	Setup( owner, pos, dir );
	ballstate = NBS_EVIL;
}

//=================================================================
// nuke launcher

CLASS_DECLARATION( Weapon, IP36, "weapon_ip36" );

Event EV_Nuke_Launch( "nuke_launch" );

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

IP36::IP36()
{
	SetModels( "nuke_w.def", "view_nuke.def" );
	modelIndex( "sprites/nukeball.spr" );
	modelIndex( "sprites/implosion.spr" );
	modelIndex( "sprites/nukering.spr" );
	modelIndex( "sprites/nukering2.spr" );
	SetAmmo( "IlludiumModules", 1, 1 );
	SetRank( 110, 0 );
	SetType( WEAPON_2HANDED_LO );

	SetMinRange( NUKE_RADIUS );
	SetProjectileSpeed( NUKE_SPEED );

	// precache the first fire dialog if needed
	if(!deathmatch->value)
	{
		ScriptVariable  *var;

		var = gameVars.GetVariable( "firstnukefire" );

		// only bother to precache if hasn't been fired yet
		if(!var)
		{
			G_LoadAndExecScript( "global/nukeweapon.scr", "nuke_first_fire_precache" );
		}
	}
}

void IP36::Shoot( Event *ev )
{
	NukeBall *ball;
	Vector pos, dir;
	Vector right, up;
	Vector tmpvec;

	if((!owner) || (owner->IsOnBike()))
		return;

	GetMuzzlePosition( &pos, &dir, &right, &up );
	tmpvec = pos - right*8;

	ball = new NukeBall;
	ball->Setup( owner, tmpvec, dir );

	NextAttack( 3 );
}

void IP36::Launch( Event *ev )
{
	NukeBall *ball;
	Vector pos, dir;
	Vector right, up;
	Vector tmpvec;

	if((!owner) || (owner->IsOnBike()))
		return;

	GetMuzzlePosition( &pos, &dir, &right, &up );
	tmpvec = pos - right*8;

	ball = new NukeBall;
	ball->Setup( owner, tmpvec, dir );
}

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

class EvilIP36 : public IP36
{
public:
	CLASS_PROTOTYPE( EvilIP36 );

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

CLASS_DECLARATION( IP36, EvilIP36, "weapon_evilip36" );

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

EvilIP36::EvilIP36()
{}

void EvilIP36::Shoot( Event *ev )
{
	NukeBall *ball;
	Vector pos, dir;
	Vector right, up;
	Vector tmpvec;

	if((!owner) || (owner->IsOnBike()))
		return;

	GetMuzzlePosition( &pos, &dir, &right, &up );
	tmpvec = pos - right*8;

	ball = new NukeBall;
	ball->EvilSetup( owner, tmpvec, dir );
	ball->takedamage = DAMAGE_NO;

	NextAttack( 2 );
}
