//-----------------------------------------------------------------------------
//
//  $Logfile:: /Code/DLLs/game/player_combat.cpp                             $
// $Revision:: 40                                                             $
//   $Author:: Steven                                                         $
//     $Date:: 5/17/03 3:09p                                                  $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
//
// DESCRIPTION:
// Player combat system and combat utility functions
//

#include "_pch_cpp.h"
#include "player.h"
#include "weaputils.h"
#include <qcommon/gameplaymanager.h>
#include "mp_manager.hpp"


Entity * Player::FindClosestEntityInRadius( const float horizontalFOVDegrees, const float verticalFOVDegrees, const float maxDistance )
{
	Vector torsoForward;
	Vector torsoRight;
	Vector torsoUp;

	torsoAngles.AngleVectors( &torsoForward, &torsoRight, &torsoUp );

	const float horizontalFOVComponent = static_cast<float>( sin( DEG2RAD( horizontalFOVDegrees / 2.0f ) ) );
	const float verticalFOVComponent = static_cast<float>( sin( DEG2RAD( verticalFOVDegrees / 2.0f ) ) );

	Entity   *bestEntity=NULL;
	float    bestDistance = maxDistance;
	
	// Find closest enemy in radius
	Entity   *currentEntity = NULL;
	
	while( ( currentEntity = findradius( currentEntity, centroid, maxDistance ) ) != NULL )
	{
		bool validEntity = false;
		
		if ( currentEntity->flags & FL_AUTOAIM )
		{
			validEntity = true;
		}
		else if ( currentEntity->isSubclassOf( Actor ) && !currentEntity->deadflag )
		{
			Actor *actor = static_cast<Actor *>( currentEntity );
			
			if ( ( actor->actortype == IS_ENEMY ) && actor->CanTarget() && !( actor->bind_info && actor->bind_info->bindmaster ) && ( actor->edict->s.parent == ENTITYNUM_NONE ) )
				validEntity = true;
		}
		
		if ( validEntity )
		{
			// Check to see if the enemy is closest to us
			Vector delta( currentEntity->centroid - centroid );

			const float dist = delta.length();
			
			if ( dist < bestDistance )
            {
				delta.normalize();
				
				// It's close, now check to see if it's in our FOV.
				const float forwardDot = DotProduct( torsoForward, delta );
				const float horizontalDot = DotProduct( torsoRight, delta );
				const float verticalDot = DotProduct( torsoUp, delta );
				
				if ( 
					forwardDot > 0.0f &&
					fabs(horizontalDot) < horizontalFOVComponent &&
					( (fabs(verticalDot) < verticalFOVComponent) || ( dist < 96.0f ) )
					)
				{
					trace_t trace;
					// Do a trace to see if we can get to it
					trace = G_Trace( centroid,
						vec_zero,
						vec_zero,
						currentEntity->centroid,
						NULL,
						MASK_OPAQUE,
						false,
						"FindClosestEntityInRadius" );
					
					if ( trace.ent || ( trace.fraction == 1 ) )
					{
						// dir = delta;
						bestEntity  = currentEntity;
						bestDistance = dist;
					}
				}
            }
		}
	}
	return bestEntity;
}

const bool IsValidHeadTarget( const Entity &entity)
{
	if ( entity.isSubclassOf( Actor ) && !entity.deadflag )
	{
		const Actor &actor = static_cast<const Actor &>( entity );
		if ( ( actor.actortype == IS_ENEMY ) && actor.CanTarget() && ! ( actor.bind_info && actor.bind_info->bindmaster ) && ( actor.edict->s.parent == ENTITYNUM_NONE ) )
		{
			return true;
		}
	}
	else if ( entity.isSubclassOf( Item ) )
	{
		const Item &item = static_cast<const Item &>( entity );
		
		if ( !item.GetOwner() && !item.has_been_looked_at && entity.look_at_me)
		{
			return true;
		}
	}
	return false;
}

Entity* Player::FindHeadTarget( const Vector &origin, const Vector &forward, const float fov, const float maxdist ) 
{
	const int maximumNumberOfCandidates = 10;
	int numberOfCandidates=0;
	float validTargetRadiusSquared = maxdist * maxdist;
	float fovdot = cos( DEG2RAD( fov * 0.5f) );

	if ( multiplayerManager.inMultiplayer() )
		return NULL;

	Container<Entity *> possibleHeadTargets;
	finishableList.FreeObjectList();

	for( gentity_t *currentEdict = active_edicts.next; currentEdict != &active_edicts && numberOfCandidates < maximumNumberOfCandidates; currentEdict = currentEdict->next )
	{
		Entity &currentEntity = *currentEdict->entity;
		if ( IsValidHeadTarget( currentEntity ) )
		{
      		Vector delta = ( currentEntity.centroid ) - origin;
			float lengthOfDeltaSquared = delta.lengthSquared();
			if ( lengthOfDeltaSquared < validTargetRadiusSquared )
			{
				// Headwatch Stuff
				delta.normalize();
				float dot = DotProduct( forward, delta );
				if ( dot > fovdot )
				{
					int insertionPoint = 1;
					const float distanceSquaredToCurrentEntity = Vector::DistanceSquared( origin, currentEntity.centroid );
					for ( ; insertionPoint < possibleHeadTargets.NumObjects(); insertionPoint++ )
					{
						if ( distanceSquaredToCurrentEntity < Vector::DistanceSquared( origin, possibleHeadTargets.ObjectAt( insertionPoint )->centroid ) )
						{
							break;
						}
					}
					numberOfCandidates++;
					possibleHeadTargets.InsertObjectAt( insertionPoint, &currentEntity );
				}

				// Finishable List -- If there's a finishable guy within 5 feet,
				// add him to the finishable list.
				if ( lengthOfDeltaSquared < 6400.0f ) // 5 feet
				{
					if ( currentEntity.isSubclassOf(Actor) )
					{
						Actor *act = (Actor*)&currentEntity;
						if ( act->IsFinishable() )
							finishableList.AddObject(act);
					}
				}
			}
		}
	}

	Entity *closestValidEntity = NULL;

	for (int i = 1; i <= possibleHeadTargets.NumObjects(); i++ )
	{
		Entity *currentCandidate = possibleHeadTargets.ObjectAt( i );
		trace_t trace = G_Trace( origin, vec_zero, vec_zero, currentCandidate->centroid, NULL, MASK_OPAQUE, false, "FindHeadTarget" );
		if ( ( trace.ent && trace.entityNum == currentCandidate->entnum ) || ( trace.fraction == 1.0f ) )
		{
			closestValidEntity = currentCandidate;
			break;
		}
	}
	return closestValidEntity;
}

//====================
//ActivateNewWeapon
//====================
void Player::ActivateNewWeapon( Event *ev )
{
	// Change the weapon to the currently active weapon as specified by useWeapon
	ChangeWeapon( newActiveWeapon.weapon, newActiveWeapon.hand );
	
	// Clear out the newActiveWeapon
	ClearNewActiveWeapon();
	
	// Clear out the holstered weapons
	holsteredWeapons[WEAPON_LEFT]  = NULL;
	holsteredWeapons[WEAPON_RIGHT] = NULL;
	holsteredWeapons[WEAPON_DUAL]  = NULL;
	
	// let the player know that our weapons are not holstered
	WeaponsNotHolstered();
}

//====================
//DeactivateWeapon
//====================
void Player::DeactivateWeapon( Event *ev )
{
	// Deactivate the weapon
	weaponhand_t hand;
	str   side;
	
	side = ev->GetString( 1 );
	
	hand = WeaponHandNameToNum( side );
	
	if ( hand == WEAPON_ERROR )
		return;
	
	Sentient::DeactivateWeapon( hand );
	
	if ( !GetActiveWeapon( WEAPON_LEFT ) && !GetActiveWeapon( WEAPON_RIGHT ) && !GetActiveWeapon( WEAPON_DUAL ) )
	{
		// let the player know our weapons are holstered
		WeaponsHolstered();
	}
}

//====================
//PutawayWeapon
//====================
void Player::PutawayWeapon( Event *ev )
{
	Weapon * weapon;
	weaponhand_t hand;
	str   side;
	
	side = ev->GetString( 1 );
	
	hand = WeaponHandNameToNum( side );
	
	if ( hand == WEAPON_ERROR )
		return;
	
	weapon = GetActiveWeapon( hand );
	if ( !weapon ) return;
	
	if ( weapon->isSubclassOf( Weapon ) )
	{
		weapon->targetidleflag = false;
		CancelEventsOfType( EV_Weapon_TargetIdleThink );

		weapon->PutAway();

		if ( gi.Anim_NumForName( weapon->edict->s.modelindex, "putaway" ) != -1 )
			weapon->SetAnim( "putaway", EV_Weapon_DonePutaway );
		else
			weapon->PostEvent( EV_Weapon_DonePutaway, 0.0f );
	}
}


//--------------------------------------------------------------
//
// Name:			useWeapon
// Class:			Player
//
// Description:		Find the weapon by name and use it in the specifed
//					hand.
//
// Parameters:		const char *weaponname -- name of the weapon to use
//					weaponhand_t hand -- Hand to use it in
//
// Returns:			None
//
//--------------------------------------------------------------
bool Player::useWeapon( const char *weaponname, weaponhand_t hand )
{
	Weapon *weapon;
	str name(weaponname);

	GameplayManager *gpm = GameplayManager::getTheGameplayManager();
	if ( gpm )
	{
		str objectName("CurrentPlayer.");
		objectName += name ;
		if ( gpm->hasProperty( objectName, "name" ) )
		{
			name = gpm->getStringValue( objectName, "name" );
		}
	}

	weapon = ( Weapon * )FindItem( name );

	// Check to see if player has the weapon
	if ( !weapon )
	{
		warning( "Player::useWeapon", "Player does not have weapon %s", weaponname );
		return false;
	}

	return useWeapon( weapon, hand );
}

bool Player::useWeapon( Weapon *weapon, weaponhand_t hand )
{
	Weapon * activeWeapon;
	
	if ( !weapon )
	{
		warning( "Player::useWeapon", "Null weapon used.\n" );
		return false;
	}
	
	// Check to see if we are already in the process of using a new weapon.
	//if ( newActiveWeapon.weapon )
	//{
	//	return false;
	//}
	
	// Check to see if weapon has ammo and if useNoAmmo is allowed
	if ( !weapon->HasAmmo( FIRE_MODE1 ) && !weapon->HasAmmo( FIRE_MODE2 ) && !weapon->GetUseNoAmmo() )
	{
		Sound( "snd_noammo" );
		return false;
	}
	
	// Check to see if the hand is allowed to have that weapon
	
	// WEAPON_ANY can be used in WEAPON_LEFT or WEAPON_RIGHT but not as a WEAPON_DUAL, so check for that first
	if ( ( hand == WEAPON_DUAL ) && ( weapon->GetHand() != hand ) )
    {
		warning( "Player::useWeapon", "Weapon %s is not allowed in %s", weapon->getName().c_str(), WeaponHandNumToName( hand ) );
		return false;
    }
	else if ( ( weapon->GetHand() != WEAPON_ANY ) && ( weapon->GetHand() != hand ) )
    {
		warning( "Player::useWeapon", "Weapon %s is not allowed in %s", weapon->getName().c_str(), WeaponHandNumToName( hand ) );
		return false;
    }
	
	// If the weapon we are wielding is a WEAPON_DUAL, then put away the left and right ones
	if ( weapon->GetHand() == WEAPON_DUAL )
	{
		activeWeapon = GetActiveWeapon( WEAPON_LEFT );
		if ( activeWeapon )
			activeWeapon->PutAway();
		activeWeapon = GetActiveWeapon( WEAPON_RIGHT );
		if ( activeWeapon )
			activeWeapon->PutAway();
	}
	
	// Check to see if a WEAPON_DUAL is being used and put it away if needed
	activeWeapon = GetActiveWeapon( WEAPON_DUAL );
	
	if ( activeWeapon && activeWeapon != weapon )
	{
		activeWeapon->PutAway();
		// we just want to put the dual handed weapon away
		if ( activeWeapon == weapon )
		{
			return false;
		}
	}
	
	// Now get the active weapon in the specified hand
	activeWeapon = GetActiveWeapon( hand );
	
	// Check to see if this weapon is already being used in this hand and just put it away and return
	if ( ( activeWeapon == weapon ) && ( !newActiveWeapon.weapon || newActiveWeapon.weapon == weapon ) )
	{
		// Set the putaway flag to true.  The state machine will then play the correct animation to put away the active weapon
		//activeWeapon->PutAway();
		return true;
	}
	
	// If activeWeapon is set, and it's not == weapon then put away this weapon
	/* 
	if ( activeWeapon )
	{
		// Set the putaway flag to true.  The state machine will then play the correct animation to put away the active weapon
		activeWeapon->PutAway();
	} 
	*/
	
	// Check to see if this weapon is being used in a different hand and put it away as well (if it's in a different hand)
	if ( IsActiveWeapon( weapon ) )
	{
		weapon->PutAway();
	}
	
	// Set the newActiveWeapon as the weapon specified, the state machine will play the appropriate animation and
	// trigger when to attach it to the player model.
	newActiveWeapon.weapon = weapon;
	newActiveWeapon.hand   = hand;
	return true;
}

//====================
//ActivateShield
//====================
void Player::ActivateShield( Event *ev )
{
	shield_active = true;
}

//====================
//DeactivateShield
//====================
void Player::DeactivateShield( Event *ev )
{
	shield_active = false;
}

//====================
//ShieldActive
//====================
qboolean Player::ShieldActive( void )
{
	return shield_active;
}

//====================
//LargeShieldActive
//====================
qboolean Player::LargeShieldActive( void )
{
	Weapon *weapon;
	qboolean large_shield_active=false;
	
	weapon = GetActiveWeapon( WEAPON_LEFT );
	if ( weapon && !str::icmp( weapon->item_name, "LargeShield" ) )
		large_shield_active = true;
	
	return shield_active && large_shield_active;
}

void Player::AcquireHeadTarget( void )
{
	vec3_t mat[3];
	Entity *new_head_target;
	Entity *ent;
	Item	 *item;
	
	// Find a good target
	
	if ( targetEnemy )
		head_target = targetEnemy;
	else
	{
		AnglesToAxis( headAngles, mat );
		
		// Make sure not to look at items too long
		
		if ( ( look_at_time <= level.time ) && head_target && head_target->isSubclassOf( Item ) )
		{
			item = (Item *)(Entity *)head_target;
			item->has_been_looked_at = true;
		}
		
		// Get the new head target
		new_head_target = FindHeadTarget( this->centroid, mat[0], 160.0f, 1000.0f );
		
		if ( !new_head_target )
		{
			head_target = NULL;
			return;
		}
		
		if ( new_head_target != head_target )
		{
			// If we were looking at an item and are not now, mark it as has been looked at
			
			if ( head_target && head_target->isSubclassOf( Item ) )
			{
				item = (Item *)(Entity *)head_target;
				item->has_been_looked_at = true;
			}
			
			// Set up new head target
			
			head_target = new_head_target;
			
			look_at_time = level.time + 5.0f;
			
			// Mark items near this one as looked at
			
			if ( head_target && head_target->isSubclassOf( Item ) )
			{
				ent = findradius( NULL, head_target->origin, 50.0f );
				
				while( ent )
				{
					if ( ent != head_target && ent->isSubclassOf( Item ) )
					{
						item = (Item *)ent;
						item->has_been_looked_at = true;
					}
					
					ent = findradius( ent, head_target->origin, 50.0f );
				}
			}
		}
	}
}
