//-----------------------------------------------------------------------------
//
//  $Logfile:: /EF2/Code/DLLs/game/equipment.cpp                             $
// $Revision:: 53                                                             $
//   $Author:: Singlis                                                        $
//     $Date:: 9/26/03 2:36p                                                  $
//
// Copyright (C) 2002 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
//
// DESCRIPTION:

#include "_pch_cpp.h"
#include "equipment.h"
#include "player.h"
#include "weaputils.h"

#define DETAILED_SCAN_TIME 1.5
#define MAX_TARGET_DISTANCE			600.0f
#define MAX_TARGET_DISTANCE_SQUARED	MAX_TARGET_DISTANCE * MAX_TARGET_DISTANCE

Event EV_AirStrike
(
	"airstrike",
	EV_CODEONLY,
	NULL,
	NULL,
	"call airstrike from equipment"
);
Event EV_ScanStart
(
	"scanstart",
	EV_TIKIONLY,
	NULL,
	NULL,
	"The start of the scan"
);
Event EV_ScanEnd
(
	"scanend",
	EV_TIKIONLY,
	NULL,
	NULL,
	"The end of the scan"
);
Event EV_Scanner
(
	"hasscanner",
	EV_TIKIONLY,
	NULL,
	NULL,
	"The equipment has a scanner"
);
Event EV_Radar
(
	"hasradar",
	EV_TIKIONLY,
	NULL,
	NULL,
	"The equipment has a radar"
);
Event EV_Equipment_HasModes
(
	"hasmodes",
	EV_TIKIONLY,
	"sSSSSSSSSS",
	"mode1 mode2 mode3 mode4 mode5 mode6 mode7 mode8 mode9 mode10",
	"Specify the modes this equipment has."
);
Event EV_Equipment_ChangeMode
(
	"changemode",
	EV_TIKIONLY,
	NULL,
	NULL,
	"Change to the next mode."
);
Event EV_Scan
(
	"scan",
	EV_TIKIONLY,
	NULL,
	NULL,
	"Scan"
);
Event EV_Equipment_SetTypeName
(
	"typeName",
	EV_TIKIONLY,
	"s",
	"typeName",
	"Sets the type name of this equipment"
);

CLASS_DECLARATION( Weapon, Equipment, NULL)
{
	{ &EV_AirStrike,					&Equipment::airStrike },
	{ &EV_ScanStart,					&Equipment::scanStart },
	{ &EV_ScanEnd,						&Equipment::scanEnd },
	{ &EV_Scanner,						&Equipment::setScanner },
	{ &EV_Radar,						&Equipment::setRadar },
	{ &EV_Equipment_HasModes,			&Equipment::hasModes },
	{ &EV_Equipment_ChangeMode,			&Equipment::changeMode },
	{ &EV_Equipment_SetTypeName,		&Equipment::setTypeName },
	{ &EV_Scan,							&Equipment::scan },

	{ NULL, NULL }
};


Equipment::Equipment()
{
	init();
}


Equipment::Equipment( const char* file ) :
	Weapon(file)
{
	init();
}

void Equipment::init( void )
{
	scanning = false;
	scanner = false;
	radar = false;
	_active = false;

	scanTime = 0;
	_nextUseTime = 0.0f;

	_scannedEntity = NULL;

	_currentMode = 1;
	_lastMode    = 1;

	_scanEndFrame = -1;

	turnThinkOn();
}


Equipment::~Equipment()
{
	_modes.FreeObjectList();
}

void Equipment::Think()
{		
	if ( _modes.NumObjects() > 0 )
	{
		str *modeName;

		// Make sure this mode is still available

		modeName = &_modes.ObjectAt( _currentMode );
			
		if ( _currentMode != 1 && world->isAnyViewModeAvailable() && !world->isViewModeAvailable( modeName->c_str() ) )
		{
			Event *newEvent;
		
			newEvent = new Event( EV_Equipment_ChangeMode );
			newEvent->AddInteger( 1 );
			ProcessEvent( newEvent );
		}
	}
	
	Player* player = 0;
	if( owner && owner->isSubclassOf(Player) )
	{
		player= (Player*)(Sentient*)owner;
	}
	
	if ( player && _active == qtrue)
	{
		player->client->ps.pm_flags &= ~( PMF_RADAR_MODE | PMF_SCANNER );
		if( hasRadar() )
		{
			player->client->ps.pm_flags |= ( PMF_RADAR_MODE );
		}

		if( hasScanner() )
		{
			player->client->ps.pm_flags |= PMF_SCANNER;
		}
	}
}

//----------------------------------------------------------------
// Name:			airStrike
// Class:			Equipment
//
// Description:		does a few traces to find a sane location, places a model 
//					against the edge of the skybox, and starts it up to do an air strike
//
// Parameters:		none
//
// Returns:			none
//----------------------------------------------------------------
void Equipment::airStrike(Event *ev)
{
	str modeName;
	Player* player;
	str airStrikeSound;


	// Make sure the scanning stuff is ok

	if ( !scanner || !scanning )
		return;

	// Make sure we are still in the correct mode

	if ( ( _currentMode > _modes.NumObjects() ) || ( _currentMode < 1 ) )
		return;

	modeName = _modes.ObjectAt( _currentMode );

	if ( stricmp( modeName, "torpedostrike") != 0 )
		return;

	// Make sure our owner is a player

	assert( owner );	

	if ( !owner || !owner->isSubclassOf( Player ) )
		return;
		
	player = (Player*)(Sentient*)owner;
							
	// Try to do the torpedo strike

	trace_t trace;
	Vector start;
	Vector forward,right;
	Vector end;
	int i;
	Vector angles;
	Vector dir;
	bool hitSky;
	Vector mins;
	Vector maxs;
	float bestFraction = 0.0f;
	Vector skyPosition;
	Vector bestSkyPosition;

	player->GetViewTrace( trace, MASK_PROJECTILE, 5000.0f );

	start = trace.endpos;
	hitSky = false;

	mins = Vector( -5.0f, -5.0f, -5.0f );
	maxs = Vector( 5.0f, 5.0f, 5.0f );

	for ( i = 0 ; i <= 360 ; i += 45 )
	{
		end = start + Vector( 0.0f, 0.0f, 10000.0f );

		if ( i != 360 )
		{
			angles = vec_zero;
			angles[ YAW ] = i;
			angles.AngleVectors( &dir );

			end += dir * 7500.0f;
		}

		// Try to trace for sky (that means we're outside and in range)

		trace = G_Trace( start, vec_zero, vec_zero, end, player, MASK_PROJECTILE, false, "airStrike::skyspot1" );

		/* gentity_t *loopent = NULL;

		// if we hit a bmodel, move to the top of its box and try again

		while ( (trace.ent) && (trace.ent->bmodel) && (loopent != trace.ent) ) 
		{
			loopent = trace.ent;
			start[2] = loopent->absmax[2] + 5.0f;
			trace = G_Trace( start, vec_zero,vec_zero, end, player, MASK_PROJECTILE, false, "airStrike::skyspot1" );
		} */

		if ( trace.surfaceFlags & SURF_SKY )
		{
			skyPosition = trace.endpos;

			trace = G_Trace( skyPosition, mins, maxs, start, player, MASK_PROJECTILE, false, "airStrike::skyspot2" );

			if ( ( bestFraction == 0.0f ) || ( trace.fraction > bestFraction ) )
			{
				bestFraction = trace.fraction;
				bestSkyPosition = skyPosition;
			}

			hitSky = true;
			//break;
		}
	}

	// Show effects and play sound based on whether or not we hit

	if ( hitSky )
	{
		right = bestSkyPosition;
		forward = right - start;
		
		if (forward.normalize() > 4000)
			right = start + (forward * 4000); // abusing right for final start point
		else
			right = trace.endpos;
		
		Projectile		*airstrike;
		airstrike = new Projectile;		//this needs to be projectile so triggers respond properly (they won't respond to Entity)
		airstrike->setModel("models/weapons/projectile_airstrike.tik");
		airstrike->setOrigin(right);
		forward *= -1.0f; // airstrike model tag is backward, easier to just reverse the direction here
		airstrike->setAngles(forward.toAngles());
		airstrike->PostEvent(EV_Remove,30);

		airStrikeSound = GetRandomAlias( "snd_strikeincoming" );	// play this sound at beginning of airstrike

		_nextUseTime = level.time + 30.0f;

	}
	else
	{
		airStrikeSound = GetRandomAlias( "snd_strikeblocked" );		// play this sound if we can't see sky
	}

	Sound( airStrikeSound );
}

void Equipment::scanStart(Event* ev)
{
	if(scanner == true)
		scanning = qtrue;

	//if we end scan and start scan on the same frame, 
	//then ignore the end scan
	if(_scanEndFrame == level.framenum)
	{
		PostEvent( EV_Scan, 0.05f );
		return;
	}

	if ( shootingSkin )
		ChangeSkin( shootingSkin, true );

	PostEvent( EV_Scan, 0.25f );

	if ( level.time > _nextUseTime )
	{
		CancelEventsOfType( EV_AirStrike );
		PostEvent(EV_AirStrike,5.0f);
	}
}


void Equipment::scanEnd(Event* ev)
{
	if(scanner == true)
		scanning = false;

	_scanEndFrame = level.framenum;

	if ( shootingSkin )
		ChangeSkin( shootingSkin, false );

	CancelEventsOfType( EV_Scan );
}

void Equipment::scan( Event *ev )
{
	
	if ( owner && owner->isSubclassOf( Player ) )
	{
		Event *newEvent;
		Player *player = (Player *)(Entity *)owner;
		
		newEvent = new Event( EV_Player_DoUse );
		newEvent->AddEntity( this );
		player->ProcessEvent( newEvent );
	}
	
	CancelEventsOfType( EV_Scan );
	PostEvent( EV_Scan, 0.05f );
	
}


void Equipment::setScanner(Event* ev)
{
	if(ev == 0)
		return;

	scanner = true;
}


void Equipment::setRadar(Event* ev)
{
	if(ev == 0)
		return;

	radar = true;
}

void Equipment::PutAway(void)
{
	Weapon::PutAway();
}


void Equipment::ProcessTargetedEntity(EntityPtr entity)
{
	if(_scannedEntity != entity && entity != 0)
	{
		Player* player = (Player*)(Sentient*)owner;
		float distanceSquared = DistanceSquared(entity->origin, player->client->ps.origin);
		if(distanceSquared > MAX_TARGET_DISTANCE_SQUARED)
			scanTime = (int) level.time;
	}
	
	if(_scannedEntity != entity)
		scanTime = (int) level.time;
	
	_scannedEntity = entity;
	if(_scannedEntity == 0)
		return;
	
	Weapon::ProcessTargetedEntity(entity);
	
	if(isScanning())
	{
		entity->edict->s.eFlags &= ~EF_DISPLAY_DESC1;	//turn off description 1 if displaying description 2.
		entity->edict->s.eFlags |= EF_DISPLAY_DESC2;
		
		long timeElapsed = (long)(level.time - scanTime);
		if(timeElapsed >= DETAILED_SCAN_TIME)
			entity->edict->s.eFlags |= EF_DISPLAY_DESC3;
	}
	else 
	{
		scanTime = (int)level.time;
		entity->edict->s.eFlags &= ~EF_DISPLAY_DESC2;
		entity->edict->s.eFlags &= ~EF_DISPLAY_DESC3;
	}	
}

void Equipment::AttachToOwner(weaponhand_t hand)
{
	_active = true;
	Weapon::AttachToOwner(hand);

	if ( _lastMode > 1 )
	{
		changeMode( _lastMode );
	}
}

void Equipment::hasModes( Event *ev )
{
	int i;
	str newMode;
	
	for ( i = 1 ; i <= ev->NumArgs() ; i++ )
	{
		newMode = ev->GetString( i );
		
		_modes.AddObject( newMode );
	}
}

void Equipment::changeMode( Event *ev )
{
	int newMode;
	
	if ( ev->NumArgs() > 0 )
	{
		newMode = ev->GetInteger( 1 );
	}
	else
	{
		newMode = _currentMode + 1;
	}

	changeMode( newMode );
}

void Equipment::updateMode( void )
{
	changeMode( _currentMode );
}

void Equipment::changeMode( int newMode )
{
	int numModes;
	Event *newEvent;
	str *modeName;
	
	numModes = _modes.NumObjects();
	
	if ( !numModes )
		return;
	
	// Set the desired mode
	
	_currentMode = newMode;
	
	if ( _currentMode > numModes )
		_currentMode = 1;
	
	// Continue until we have a valid mode set
	
	while( 1 )
	{
		if (_currentMode == 1) // if mode is unset, query player to see if we want nightvision instead (divorced from equipment)
		{
			assert(owner);	
			Player* player;
			
			if ( owner->isSubclassOf(Player))
				player = (Player*)(Sentient*)owner;
			else
				player = NULL;
			
			if ( (player) && (player->client->ps.pm_flags & PMF_NIGHTVISION) )
			{
				newEvent = new Event( EV_Sentient_SetViewMode );
				newEvent->AddString( "nightvision" );
				owner->ProcessEvent( newEvent );
				
				return;
			}
		}
		
		modeName = &_modes.ObjectAt( _currentMode );
		
		// Make sure this mode is available
		
		if ( _currentMode != 1 && world->isAnyViewModeAvailable() && !world->isViewModeAvailable( modeName->c_str() ) )
		{
			// This mode isn't allowed
			
			_currentMode++;
			
			if ( _currentMode > numModes )
				_currentMode = 1;
			
			continue;
		}
		
		// We have a valid mode so set it
		
		newEvent = new Event( EV_Sentient_SetViewMode );
		newEvent->AddString( modeName->c_str() );
		owner->ProcessEvent( newEvent );
		
		return;
	}
}

void Equipment::resetMode( void )
{	
	changeMode( 1 );
}

void Equipment::Uninitialize( void )
{
	if ( !_active )
		return;

	Weapon::Uninitialize();

	if ( !owner )
		return;

	if ( !owner->isSubclassOf( Player ) )
		return;

	Player* player = (Player*)(Sentient*)owner;
	if ( player )
	{
		player->client->ps.pm_flags &= ~(PMF_SCANNER | PMF_RADAR_MODE);
	}

	_lastMode = _currentMode;
	
	resetMode();

	_active = false;
}

void Equipment::Archive( Archiver &arc )
{
	Weapon::Archive( arc );
	
	arc.ArchiveBool( &scanner );
	arc.ArchiveBool( &scanning );
	arc.ArchiveInteger( &_scanEndFrame );
	arc.ArchiveInteger( &scanTime );
	arc.ArchiveBool( &radar );
	
	_modes.Archive( arc );
	
	arc.ArchiveInteger( &_currentMode );
	arc.ArchiveInteger( &_lastMode );
	
	arc.ArchiveString( &_typeName );

	arc.ArchiveSafePointer( &_scannedEntity );

	arc.ArchiveBool( &_active );

	arc.ArchiveFloat( &_nextUseTime );
}

void Equipment::setTypeName( Event *ev )
{
	_typeName = ev->GetString( 1 );
}

int Equipment::getStat( int statNum )
{
	if ( statNum == STAT_WEAPON_GENERIC1 )
	{
		// Return the locking percentage for the torpedo strike

		str modeName;

		if ( !scanner || !scanning )
			return 0;

		if ( level.time < _nextUseTime )
			return 0;

		modeName = _modes.ObjectAt( _currentMode );

		if ( modeName == "torpedostrike" )
		{
			return (int)( ( ( level.time - scanTime ) / 5.0f ) * 100.0f );
		}
	}
	else if ( statNum == STAT_WEAPON_GENERIC2 )
	{
		if ( level.time > _nextUseTime )
			return 100;

		return (int)( 100.0f - ( ( _nextUseTime - level.time ) / 30.0f ) * 100.0f );
	}

	return 0;
}
