// Copyright (C) 1997 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:
// Base class of entity that can carry other entities, and use weapons.
//

#include "g_local.h"
#include "entity.h"
#include "sentient.h"
#include "weapon.h"
#include "scriptmaster.h"
#include "ammo.h"
#include "armor.h"
#include "misc.h"
#include "inventoryitem.h"
#include "player.h"
#include "actor.h"
#include "hoverweap.h" //###

CLASS_DECLARATION( Entity, Sentient, NULL );

Event EV_Sentient_Attack( "fire" );
Event EV_Sentient_ReleaseAttack( "releasefire" );
Event EV_Sentient_GiveWeapon( "weapon" );
Event EV_Sentient_GiveAmmo( "ammo" );
Event EV_Sentient_GiveArmor( "armor" );
Event EV_Sentient_GiveItem( "item" );
Event EV_Sentient_GiveTargetname( "give" );
Event EV_Sentient_GiveInventoryItem( "invitem" );
Event EV_Sentient_GiveHealth( "health", EV_CHEAT );
Event EV_Sentient_TakeWeapon( "take_weapon" );
Event EV_Sentient_TakeAmmo( "take_ammo" );
Event EV_Sentient_TakeArmor( "take_armor" );
Event EV_Sentient_TakeItem( "take_item" );
Event EV_Sentient_WeaponPutAway( "weapon_putaway" );
Event EV_Sentient_WeaponReady( "weapon_ready" );
Event EV_Sentient_WeaponDoneFiring( "weapon_donefiring" );
Event EV_Sentient_AnimLoop( "animloop" );
Event EV_Sentient_UselessCheck( "useless_check" );
Event EV_Sentient_TurnOffShadow( "noshadow" );
Event EV_Sentient_Freeze( "freeze" );
Event EV_Sentient_UnFreeze( "unfreeze" );
Event EV_Sentient_ImpactDamage( "impact_damage" );
Event EV_Sentient_WeaponUse( "weaponuse", EV_CONSOLE );
Event EV_Sentient_SetDropWeapon( "dropweapon" );
Event EV_Sentient_DropWeaponNow( "dropweaponnow" );
//###
Event EV_Sentient_HurtFlame( "hurtflame" );
//###

ResponseDef Sentient::Responses[] =
{
	{ &EV_Sentient_Attack,               ( Response )Sentient::FireWeapon },
	{ &EV_Sentient_ReleaseAttack,     ( Response )Sentient::ReleaseFireWeapon },
	{ &EV_Sentient_GiveWeapon,           ( Response )Sentient::EventGiveWeapon },
	{ &EV_Sentient_TakeWeapon,            ( Response )Sentient::EventTakeWeapon },
	{ &EV_Sentient_GiveAmmo,         ( Response )Sentient::EventGiveAmmo },
	{ &EV_Sentient_TakeAmmo,          ( Response )Sentient::EventTakeAmmo },
	{ &EV_Sentient_GiveArmor,         ( Response )Sentient::EventGiveArmor },
	{ &EV_Sentient_TakeArmor,         ( Response )Sentient::EventTakeArmor },
	{ &EV_Sentient_GiveItem,         ( Response )Sentient::EventGiveItem },
	{ &EV_Sentient_GiveInventoryItem,( Response )Sentient::EventGiveInventoryItem },
	{ &EV_Sentient_GiveHealth,       ( Response )Sentient::EventGiveHealth },
	{ &EV_Sentient_GiveTargetname,   ( Response )Sentient::EventGiveTargetname },
	{ &EV_Sentient_WeaponPutAway,     ( Response )Sentient::WeaponPutAway },
	{ &EV_Sentient_WeaponReady,      ( Response )Sentient::WeaponReady },
	{ &EV_Sentient_WeaponDoneFiring,    ( Response )Sentient::WeaponDoneFiring },
	{ &EV_Sentient_AnimLoop,            ( Response )Sentient::AnimLoop },
	{ &EV_Damage,                    ( Response )Sentient::ArmorDamage },
	{ &EV_Touch,                     ( Response )Sentient::SearchBody },
	{ &EV_Sentient_UselessCheck,        ( Response )Sentient::UselessCheck },
	{ &EV_Sentient_TurnOffShadow,       ( Response )Sentient::TurnOffShadow },
	{ &EV_Sentient_Freeze,              ( Response )Sentient::Freeze },
	{ &EV_Sentient_UnFreeze,        ( Response )Sentient::UnFreeze },
	{ &EV_Sentient_ImpactDamage,     ( Response )Sentient::ImpactDamage },
	{ &EV_Sentient_WeaponUse,        ( Response )Sentient::WeaponUse },
	{ &EV_Sentient_SetDropWeapon,    ( Response )Sentient::SetDropWeapon },
	{ &EV_Sentient_DropWeaponNow,    ( Response )Sentient::DropWeaponNowEvent },
	{ &EV_Sentient_TakeItem,        ( Response )Sentient::EventTakeItem },
	//###
	{ &EV_Sentient_HurtFlame,           ( Response )Sentient::HurtFlame},
	//###
	{ NULL, NULL }
};

Container<Sentient *> SentientList;

Sentient::Sentient()
{
	Vector defangles;

	SentientList.AddObject( ( Sentient * )this );

	inventory.ClearObjectList();
	currentWeapon = NULL;
	currentItem = NULL;
	newWeapon = NULL;
	animOverride = false;
	dropweapon = true;
	tempAnimEvent = NULL;

	eyeposition = "0 0 64";
	gunoffset = "0 0 56";

	firedown = false;
	firedowntime = 0;

	// angles
	defangles = Vector( 0, G_GetFloatArg( "angle", 0 ), 0 );
	if (defangles.y == -1)
	{
		defangles = Vector( -90, 0, 0 );
	}
	else if (defangles.y == -2)
	{
		defangles = Vector( 90, 0, 0 );
	}
	angles = G_GetVectorArg( "angles", defangles );
	setAngles( angles );
	stopanimating_tillchange = false;

	poweruptype    = 0;
	poweruptimer   = 0;
	sentientFrozen = false;

	autoweaponswitch = 1; //### default weapon switching on
	weaponoverride = 1; //### default weapon select override on

	// HACK FIXME
	//
	// temporary shadow flag
	//
	edict->s.renderfx |= RF_XFLIP;
}

Sentient::~Sentient()
{
	SentientList.RemoveObject( ( Sentient * )this );
	FreeInventory();
}

Vector Sentient::EyePosition( void )
{
	Vector pos;

	pos = worldorigin;
	pos[ gravity_axis[ gravaxis ].x ] += eyeposition[ 0 ];
	pos[ gravity_axis[ gravaxis ].y ] += eyeposition[ 1 ] * gravity_axis[ gravaxis ].sign;
	pos[ gravity_axis[ gravaxis ].z ] += eyeposition[ 2 ] * gravity_axis[ gravaxis ].sign;

	return pos;
}

Vector Sentient::GunPosition( void )
{
	Vector pos;

	pos = worldorigin;
	pos[ gravity_axis[ gravaxis ].x ] += gunoffset[ 0 ];
	pos[ gravity_axis[ gravaxis ].y ] += gunoffset[ 1 ] * gravity_axis[ gravaxis ].sign;
	pos[ gravity_axis[ gravaxis ].z ] += gunoffset[ 2 ] * gravity_axis[ gravaxis ].sign;

	return pos;
}

inline void Sentient::GetGunOrientation( Vector pos,Vector *forward,Vector *right,Vector *up )
{
	const gravityaxis_t &grav = gravity_axis[ gravaxis ];

	if ( forward )
	{
		( *forward )[ grav.x ] = orientation[ 0 ][ 0 ];
		( *forward )[ grav.y ] = orientation[ 0 ][ 1 ] * grav.sign;
		( *forward )[ grav.z ] = orientation[ 0 ][ 2 ] * grav.sign;
	}

	if ( right )
	{
		( *right )[ grav.x ] = -orientation[ 1 ][ 0 ];
		( *right )[ grav.y ] = -orientation[ 1 ][ 1 ] * grav.sign;
		( *right )[ grav.z ] = -orientation[ 1 ][ 2 ] * grav.sign;
	}

	if ( up )
	{
		( *up )[ grav.x ] = orientation[ 2 ][ 0 ];
		( *up )[ grav.y ] = orientation[ 2 ][ 1 ] * grav.sign;
		( *up )[ grav.z ] = orientation[ 2 ][ 2 ] * grav.sign;
	}
}

void Sentient::GetMuzzlePositionAndDirection( Vector *pos,Vector *dir )
{
	*pos = Vector( "0 0 0" );
	*dir = Vector( "0 0 0" );

	if ( currentWeapon )
		currentWeapon->GetMuzzlePosition( pos, dir );
}

void Sentient::EventGiveHealth( Event *ev )
{
	int group_num;

	health = ev->GetFloat( 1 );

	if ( isClient() )
	{
		for( group_num = 0; group_num < edict->s.numgroups; group_num++ )
		{
			edict->s.groups[ group_num ] &= ~MDL_GROUP_SKINOFFSET_BIT0;
		}
	}
}

//### had to modify for hoverbike's multiple firing modes
void Sentient::FireWeapon( Event *ev )
{
/*	if ( ( currentWeapon ) && currentWeapon->ReadyToFire() && currentWeapon->HasAmmo() )
        {
        currentWeapon->Fire();
        }
 */
	if ( ( currentWeapon ) && currentWeapon->ReadyToFire() && currentWeapon->HasAmmo() )
	{
		if(this->isSubclassOf( Player ))
		{
			if(((Player *)this)->GetHoverbike())
			{
				((HoverWeap *)currentWeapon)->Fire();
			}
			else
			{
				currentWeapon->Fire();
			}
		}
		else
		{
			currentWeapon->Fire();
		}
	}
}
//###

void Sentient::ReleaseFireWeapon( Event *ev )
{
	// Only need to check if currentWeapon because FireWeapon checks for
	// the weapon ready and if it has ammo.
	if ( currentWeapon )
	{
		float fireholdtime = ev->GetFloat( 1 );
		currentWeapon->ReleaseFire( fireholdtime );
	}
}

void Sentient::AddItem( Item *object )
{
	inventory.AddObject( object->entnum );

	if ( object->isSubclassOf( InventoryItem ) )
	{
		currentItem = (InventoryItem *)object;
	}
	//
	// set our global game variables if client
	//
	if ( isClient() )
	{
		str fullname;
		ScriptVariable * var;

		fullname = str( "playeritem_" ) + object->getClassname();
		var = gameVars.GetVariable( fullname.c_str() );
		if ( !var )
		{
			gameVars.SetVariable( fullname.c_str(), 1 );
		}
		else
		{
			int amount;

			amount = var->intValue() + 1;
			var->setIntValue( amount );
		}

		var = levelVars.GetVariable( fullname.c_str() );
		if ( !var )
		{
			levelVars.SetVariable( fullname.c_str(), 1 );
		}
		else
		{
			int amount;

			amount = var->intValue() + 1;
			var->setIntValue( amount );
		}
	}
}

void Sentient::RemoveItem( Item *object )
{
	Weapon *weapon;

	inventory.RemoveObject( object->entnum );
	if ( currentWeapon == object )
	{
		currentWeapon = NULL;
		if ( newWeapon == object )
		{
			newWeapon = NULL;
			weapon = BestWeapon();
		}
		else
		{
			weapon = newWeapon;
		}

		SetCurrentWeapon( weapon );
	}

	//
	// set our global game variables if client
	//
	if ( isClient() )
	{
		str fullname;
		ScriptVariable * var;

		fullname = str( "playeritem_" ) + object->getClassname();

		var = levelVars.GetVariable( fullname.c_str() );
		if ( var )
		{
			int amount;

			amount = var->intValue() - 1;
			if ( amount < 0 )
				amount = 0;
			var->setIntValue( amount );
		}
	}

	if ( currentItem == (InventoryItem *) object )
	{
		currentItem = NULL;
		// Try to set the the current item

		currentItem = (InventoryItem *)NextItem( NULL );
		if (!currentItem)
		{
			currentItem = (InventoryItem *)PrevItem( NULL );
		}
	}
}

Item *Sentient::FindItem( const char *itemname )
{
	int num;
	int i;
	Item    *item;

	num = inventory.NumObjects();
	for( i = 1; i <= num; i++ )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		if ( !Q_stricmp( item->getClassname(), itemname ) )
		{
			return item;
		}
	}

	return NULL;
}

void Sentient::FreeInventory( void )
{
	int num;
	int i;
	Item    *item;

	if ( currentWeapon )
		currentWeapon->DetachFromOwner();

	SetCurrentWeapon( NULL );
	newWeapon = NULL;

	num = inventory.NumObjects();
	for( i = num; i > 0; i-- )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		delete item;
	}

	inventory.ClearObjectList();
	currentItem = NULL;
}

void Sentient::FreeInventoryOfType( const char *type )
{
	int num;
	int i;
	Item    *item;

	num = inventory.NumObjects();
	for( i = num; i > 0; i-- )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		if ( checkInheritance( type, item->getClassname() ) )
		{
			inventory.RemoveObject( item->entnum );

			// If the current item is about to be deleted, then look for
			// another item to be current
			if ( currentItem == item )
			{
				currentItem = NULL;
				// Try to set the the current item

				currentItem = (InventoryItem *)NextItem( NULL );
				if (!currentItem)
				{
					currentItem = (InventoryItem *)PrevItem( NULL );
				}
			}
			delete item;
		}
	}
}

qboolean Sentient::HasItem( const char *itemname )
{
	return ( FindItem( itemname ) != NULL );
}

int Sentient::NumWeapons( void )
{
	int num;
	int i;
	Item    *item;
	int numweaps;

	numweaps = 0;

	num = inventory.NumObjects();
	for( i = 1; i <= num; i++ )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		if ( checkInheritance( &Weapon::ClassInfo, item->getClassname() ) )
		{
			numweaps++;
		}
	}

	return numweaps;
}

Weapon * Sentient::WeaponNumber( int weaponnum )
{
	int num;
	int i;
	Item    *item;

	// make it so that 0 is our first weapon rather than 1
	weaponnum++;
	num = inventory.NumObjects();
	for( i = 1; i <= num; i++ )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		if ( item->isSubclassOf( Weapon ) )
		{
			weaponnum--;
			if ( !weaponnum )
			{
				return ( Weapon * )item;
			}
		}
	}

	return NULL;
}

void Sentient::ChangeWeapon( Weapon *weapon )
{
	if ( weapon == currentWeapon )
	{
		return;
	}

	if ( currentWeapon )
	{
		if ( !weapon->ReadyToUse() )
		{
			return;
		}
		newWeapon = weapon;
		if ( !currentWeapon->ReadyToChange() )
		{
			return;
		}
		currentWeapon->PutAway();
	}
	else
	{
		SetCurrentWeapon( weapon );
	}
}

void Sentient::ForceChangeWeapon( Weapon *weapon )
{
	if ( newWeapon == weapon )
	{
		return;
	}

	if ( weapon == currentWeapon )
	{
		return;
	}

	newWeapon = NULL;

	if ( currentWeapon )
	{
		currentWeapon->DetachFromOwner();
	}
	SetCurrentWeapon( weapon );
}

qboolean Sentient::CanChangeWeapons( void )
{
	return !(
		( newWeapon != NULL ) ||
		( currentWeapon && !currentWeapon->ReadyToChange() ) ||
		( flags & FL_MUTANT )
		);
}

void Sentient::SetCurrentWeapon( Weapon *weapon )
{
	const char *name;

	newWeapon = NULL;
	currentWeapon = weapon;
	if ( currentWeapon )
	{
		currentWeapon->ReadyWeapon();

		// get the name of the group that the gun bone belongs to
		name = gi.GetBoneGroupName( edict->s.modelindex, "gun" );
		if ( name )
		{
			gun_bone_group_name = name;
		}
		else
		{
			gun_bone_group_name = "";
		}
	}
}

Weapon *Sentient::CurrentWeapon( void )
{
	return currentWeapon;
}

Weapon *Sentient::BestWeapon( Weapon *ignore )
{
	Item        *next;
	int n;
	int j;
	int bestrank;
	Weapon  *bestweapon;

	n = inventory.NumObjects();

	// Search forewards until we find a weapon
	bestweapon = NULL;
	bestrank = -999999;
	for( j = 1; j <= n; j++ )
	{
		next = ( Item * )G_GetEntity( inventory.ObjectAt( j ) );

		assert( next );

		if ( ( next != ignore ) && next->isSubclassOf( Weapon ) && ( ( ( Weapon * )next )->Rank() > bestrank ) &&
		     ( ( ( Weapon * )next )->HasAmmo() ) )
		{
			bestweapon = ( Weapon * )next;
			bestrank = bestweapon->Rank();
		}
	}

	return bestweapon;
}

Weapon *Sentient::NextWeapon( Weapon *weapon )
{
	Item        *item;
	int i;
	int n;
	int weaponorder;
	Weapon  *choice;
	int choiceorder;
	Weapon  *bestchoice;
	int bestorder;
	Weapon  *worstchoice;
	int worstorder;

	if ( !inventory.ObjectInList( weapon->entnum ) )
	{
		error( "NextWeapon", "Weapon not in list" );
	}

	weaponorder = weapon->Order();
	bestchoice = weapon;
	bestorder = 65535;
	worstchoice = weapon;
	worstorder = weaponorder;

	n = inventory.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );

		assert( item );

		if ( item->isSubclassOf( Weapon ) )
		{
			choice = ( Weapon * )item;
			if ( !choice->HasAmmo() || !choice->AutoChange() )
			{
				continue;
			}

			choiceorder = choice->Order();
			if ( ( choiceorder > weaponorder ) && ( choiceorder < bestorder ) )
			{
				bestorder = choiceorder;
				bestchoice = choice;
			}

			if ( choiceorder < worstorder )
			{
				worstorder = choiceorder;
				worstchoice = choice;
			}
		}
	}

	if ( bestchoice == weapon )
	{
		return worstchoice;
	}

	return bestchoice;
}

Weapon *Sentient::PreviousWeapon( Weapon *weapon )
{
	Item        *item;
	int i;
	int n;
	int weaponorder;
	Weapon  *choice;
	int choiceorder;
	Weapon  *bestchoice;
	int bestorder;
	Weapon  *worstchoice;
	int worstorder;

	if ( !inventory.ObjectInList( weapon->entnum ) )
	{
		error( "PreviousWeapon", "Weapon not in list" );
	}

	weaponorder = weapon->Order();
	bestchoice = weapon;
	bestorder = -65535;
	worstchoice = weapon;
	worstorder = weaponorder;

	n = inventory.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );

		assert( item );

		if ( item->isSubclassOf( Weapon ) )
		{
			choice = ( Weapon * )item;
			if ( !choice->HasAmmo() || !choice->AutoChange() )
			{
				continue;
			}

			choiceorder = choice->Order();
			if ( ( choiceorder < weaponorder ) && ( choiceorder > bestorder ) )
			{
				bestorder = choiceorder;
				bestchoice = choice;
			}

			if ( choiceorder > worstorder )
			{
				worstorder = choiceorder;
				worstchoice = choice;
			}
		}
	}

	if ( bestchoice == weapon )
	{
		return worstchoice;
	}

	return bestchoice;
}

qboolean Sentient::WeaponReady( void )
{
	Weapon *best;

	if ( !currentWeapon )
	{
		return false;
	}

	if (!currentWeapon->ReadyToUse() )
	{
		best = BestWeapon();
		if ( best )
		{
			ChangeWeapon( best );
		}
		return false;
	}

	if ( !currentWeapon->ReadyToFire() )
	{
		return false;
	}

	if ( !currentWeapon->HasAmmo() )
	{
		best = BestWeapon();
		if ( best )
		{
			ChangeWeapon( best );
		}
		return false;
	}

	return true;
}

void Sentient::DropWeapon( Weapon *weapon )
{
	if ( weapon == currentWeapon )
	{
		if ( deadflag )
		{
			SetCurrentWeapon( NULL );
		}
		else
		{
			// Choose the best weapon that's not the current
			SetCurrentWeapon( BestWeapon( currentWeapon ) );
		}
	}

	// check if the sentient can drop the weapon
	if ( dropweapon )
	{
		// This one checks if the weapon is droppable
		if ( !weapon->Drop() && deadflag )
		{
			// can't drop it, probably because there's no worldmodel
			// since we're dead, just delete the weapon.
			weapon->PostEvent( EV_Remove, 0 );
		}
	}
	else
	{
		weapon->PostEvent( EV_Remove, 0 );
	}
}

void Sentient::DropCurrentWeapon( void )
{
	if ( currentWeapon )
		DropWeapon( currentWeapon );
}

void Sentient::takeWeapon( const char *weaponname )
{
	Weapon *weapon;

	assert( weaponname );

	weapon = ( Weapon * )FindItem( weaponname );
	if ( !weapon )
	{
		return;
	}

	if(weapon == newWeapon)
	{
		newWeapon = NULL;
	}

	if ( weapon == currentWeapon )
	{
		//### added check for the newWeapon value
		if(newWeapon && newWeapon != weapon)
		{
			SetCurrentWeapon( newWeapon );
		}
		else
		{
			SetCurrentWeapon( BestWeapon( weapon ) );
		}
		//###
	}

	weapon->DetachFromOwner();
	weapon->PostEvent( EV_Remove, 0 );
}

void Sentient::EventGiveTargetname( Event *ev )
{
	str name;
	Item     *item=NULL;
	edict_t  *from;

	name = ev->GetString( 1 );

	for ( from = &g_edicts[ 1 ]; from < &g_edicts[ globals.num_edicts ]; from++ )
	{
		if ( !from->inuse )
			continue;

		if ( from->entity->isSubclassOf( Item ) )
		{
			item = ( Item * ) from->entity;
			if ( item->itemname == name )
				break;
		}
	}

	// Item should be found
	assert( item );

	if ( item )
	{
		item->SetOwner( this );
		item->ProcessPendingEvents();
		AddItem( item );
	}
	else
	{
		ev->Error( "Could not give item %s to %s.\n", name, targetname );
	}
}

Item *Sentient::giveItem( const char * itemname,int amount,int icon_index )
{
	ClassDef        *cls;
	Item        *item;
	int index;

	item = ( Item * )FindItem( itemname );

	if ( item )
	{
		item->Add( amount );
	}
	else
	{
		cls = getClass( itemname );
		if ( !cls )
		{
			gi.dprintf( "No item named '%s'\n", itemname );
			return NULL;
		}

		item = ( Item * )cls->newInstance();

		item->SetOwner( this );

		item->ProcessPendingEvents();

		item->Set( amount );
		AddItem( item );
	}

	// Send icon to the player
	if ( this->isSubclassOf( Player ) )
	{
		if ( icon_index >= 0 )
			index = icon_index;
		else
			index = item->Icon();

		if ( index >= 0 )
		{
			gi.WriteByte( svc_console_command );
			gi.WriteString( va( "pl %d %d", index, amount ) );
			gi.unicast( edict, NULL );
		}
	}
	return item;
}

void Sentient::takeItem( const char *itemname,int amount )
{
	Item        *item;

	item = ( Item * )FindItem( itemname );

	if ( item )
	{
		item->Remove( amount );
		if ( !item->Amount() )
			item->ProcessEvent( EV_Remove );
	}
}

void Sentient::EventTakeItem( Event *ev )
{
	int amount;

	if ( ev->NumArgs() > 1 )
		amount = ev->GetInteger( 2 );
	else
		amount = 1;
	takeItem( ev->GetString( 1 ), amount );
}


//### modified for hoverbike
Weapon *Sentient::giveWeapon( const char *weaponname )
{
	Weapon  *weapon;

	assert( weaponname );

	if ( !checkInheritance( &Weapon::ClassInfo, weaponname ) )
	{
		gi.dprintf( "A %s is not usable as a weapon\n", weaponname );
		return NULL;
	}

	weapon = ( Weapon * )giveItem( weaponname, 1 );

	//### don't change weapons if on a hoverbike
//			or if autoweapon switching is turned off
//	if ( !currentWeapon || ( weapon->Rank() > currentWeapon->Rank() ) )
	if  (
		autoweaponswitch &&
		(!currentWeapon || (weapon->Rank() > currentWeapon->Rank()))
		&& !IsOnBike()
		)
	{
		ChangeWeapon( weapon );
	}
	//###
	else
	{
		NonAutoChangeWeapon( weapon );
	}
	//###

	return weapon;
}

Weapon *Sentient::useWeapon( const char *weaponname )
{
	Weapon *weapon;

	assert( weaponname );

	weapon = ( Weapon * )FindItem( weaponname );
	if ( weapon )
	{
		ChangeWeapon( weapon );
	}

	return currentWeapon;
}

void Sentient::EventGiveWeapon( Event *ev )
{
	ChangeWeapon( giveWeapon( ev->GetString( 1 ) ) );
}


void Sentient::EventTakeWeapon( Event *ev )
{
	takeWeapon( ev->GetString( 1 ) );
}

void Sentient::EventTakeAmmo( Event *ev )
{
	int amount;
	const char  *type;
	Ammo            *ammo;
	Weapon      *best;

	type = ev->GetString( 1 );
	amount = ev->GetInteger( 2 );

	ammo = ( Ammo * )FindItem( type );
	if ( ammo )
	{
		ammo->Remove( amount );
	}

	if ( currentWeapon && !currentWeapon->HasAmmo() )
	{
		best = BestWeapon();
		if ( best )
		{
			ChangeWeapon( best );
		}
	}
}

void Sentient::EventGiveAmmo( Event *ev )
{
	int amount;
	const char  *type;

	type = ev->GetString( 1 );
	amount = ev->GetInteger( 2 );

	if ( !checkInheritance( &Ammo::ClassInfo, type ) )
	{
		gi.dprintf( "%s is not usable as ammo\n", type );
		return;
	}

	giveItem( type, amount );

	if ( currentWeapon && !currentWeapon->HasAmmo() )
	{
		Weapon      *best;
		best = BestWeapon();
		if ( best )
		{
			ChangeWeapon( best );
		}
	}
}

void Sentient::EventTakeArmor( Event *ev )
{
	int amount;
	const char  *type;
	Armor           *armor;

	type = ev->GetString( 1 );
	amount = ev->GetInteger( 2 );

	armor = ( Armor * )FindItem( type );
	if ( armor )
	{
		armor->Remove( amount );
	}
}

void Sentient::EventGiveArmor( Event *ev )
{
	int amount;
	const char  *type;
	Armor           *armor;

	type     = ev->GetString( 1 );
	amount   = ev->GetInteger( 2 );

	if ( !checkInheritance( &Armor::ClassInfo, type ) )
	{
		gi.dprintf( "%s is not usable as armor\n", type );
		return;
	}

	armor    = ( Armor * )FindItem( type );

	if ( armor )
	{
		armor->Set( amount );
	}
	else
	{
		giveItem( type, amount );
	}
}

void Sentient::EventGiveItem( Event *ev )
{
	const char  *type;
	float amount;

	type     = ev->GetString( 1 );
	amount   = ev->GetInteger( 2 );

	giveItem( type, amount );
}

void Sentient::EventGiveInventoryItem( Event *ev )
{
	const char           *type;
	int amount;

	type     = ev->GetString( 1 );
	amount   = ev->GetInteger( 2 );

	if ( !checkInheritance( &InventoryItem::ClassInfo, type ) )
	{
		gi.dprintf( "item '%s' is not a InventoryItem\n", type );
		return;
	}

	giveItem( type, amount );
}

void Sentient::WeaponPutAway( Event *ev )
{
	Weapon *best;

	if(newWeapon)
	{
		SetCurrentWeapon( newWeapon );
	}
	else
	{
		best = BestWeapon();
		if(best)
		{
			SetCurrentWeapon( best );
		}
	}
}

void Sentient::WeaponReady( Event *ev )
{
	if ( newWeapon )
	{
		ChangeWeapon( newWeapon );
	}
}

void Sentient::WeaponDoneFiring( Event *ev )
{
	Weapon *best;

	if ( newWeapon )
	{
		ChangeWeapon( newWeapon );
	}
	else if ( !currentWeapon || !currentWeapon->HasAmmo() || !currentWeapon->ReadyToUse() )
	{
		best = BestWeapon();
		if ( best )
		{
			ChangeWeapon( best );
		}
	}
}

EXPORT_FROM_DLL void Sentient::AnimLoop( Event *ev )
{
	Event *t;

	animOverride = false;

	if ( deadflag )
	{
		StopAnimating();
	}
	else if ( animating )
	{
		RandomAnimate( currentAnim.c_str(), NULL );
	}
	else
	{
		stopanimating_tillchange = true;
	}

	if ( tempAnimEvent )
	{
		t = tempAnimEvent;
		tempAnimEvent = NULL;
		ProcessEvent( t );
	}
}

EXPORT_FROM_DLL void Sentient::SetAnim( const char *anim )
{
	assert( anim );

	assert ( !deadflag );

	if ( str( anim ) == currentAnim )
	{
		if (!animating && !stopanimating_tillchange)
			StartAnimating();
		return;
	}

	stopanimating_tillchange = false;
	currentAnim = str( anim );

	if ( animOverride )
	{
		return;
	}

	RandomAnimate( currentAnim.c_str(), EV_Sentient_AnimLoop );

	if (!animating)
		StartAnimating();
}

EXPORT_FROM_DLL void Sentient::TempAnim( const char *anim,Event *event )
{
	assert ( !deadflag );

	assert( anim );

	animOverride = true;

	tempAnimEvent = event;

	RandomAnimate( anim, EV_Sentient_AnimLoop );
}

void Sentient::TempAnim( const char *anim,Event &event )
{
	Event *ev;

	ev = new Event( event );
	TempAnim( anim, ev );
}

void Sentient::PrintDamageLocationToAttacker( edict_s     *attacker,const char  *victim_name,const char  *location )
{
	const char *expanded_location;

	expanded_location = ExpandLocation( location );

	if ( expanded_location )
		gi.cprintf ( attacker, PRINT_MEDIUM, "You hit %s in the %s.\n", victim_name, expanded_location );
}

void Sentient::PrintDamageLocationToVictim( edict_s     *victim,const char  *location )
{
	const char *expanded_location;

	expanded_location = ExpandLocation( location );

	if ( expanded_location )
		gi.cprintf ( victim, PRINT_MEDIUM, "%s damage\n", expanded_location );
}

qboolean Sentient::DoGib( int meansofdeath,Entity *inflictor )
{
	if ( !( flags & FL_DIE_GIBS ) || parentmode->value )
	{
		return false;
	}

	if (
		( meansofdeath == MOD_TELEFRAG ) ||
		( meansofdeath == MOD_MUTANTHANDS ) ||
		( meansofdeath == MOD_HELIGUN ) ||
		( meansofdeath == MOD_LAVA ) ||
		( ( meansofdeath == MOD_FISTS ) && ( inflictor->flags & FL_ADRENALINE ) ) ||
		( ( meansofdeath == MOD_SHOTGUN ) && ( G_Random() < 0.5 ) ) ||
		//### added gib deaths from IP36 and Hoverbike run-overs
		( meansofdeath == MOD_NUKE) ||
		( meansofdeath == MOD_NUKEEXPLOSION) ||
		( meansofdeath == MOD_HOVERBIKE)
		//###
		)
	{
		return true;
	}

	if ( health > -75 )
	{
		return false;
	}

	// Impact and Crush < -75 health
	if ( ( meansofdeath == MOD_IMPACT ) ||
	     ( meansofdeath == MOD_CRUSH ) ||
	     ( meansofdeath == MOD_VEHICLE )
	     )
	{
		return true;
	}

	// Any projectiles except spear
	if ( inflictor->isSubclassOf( Projectile ) )
	{
		if ( meansofdeath != MOD_SPEARGUN )
		{
			return true;
		}
	}

	// Shotgun only 10% of the time
	if ( meansofdeath == MOD_SHOTGUN )
	{
		if ( G_Random() > 0.5 )
		{
			return true;
		}
	}

	return false;
}

void Sentient::ArmorDamage( Event *ev )
{
	Armor    *armor=0;
	Entity  *inflictor;
	Entity  *attacker;
	float damage;
	float armor_damage=0;
	Vector momentum;
	Vector position;
	Vector normal;
	Vector direction;
	Event        *event;
	int dflags;
	int meansofdeath;
	int knockback;
	float damage_mult;
	int groupnum;
	int trinum;
	const char *location="";
	float unmodified_damage=0;

	if ( ( takedamage == DAMAGE_NO ) || ( movetype == MOVETYPE_NOCLIP ) )
	{
		if ( isClient () )
		{
			Player *player;

			player = ( Player * )this;
			if ( !player->GetVehicle() )
				return;
		}
		else
		{
			return;
		}
	}

	damage          = ev->GetFloat  ( 1 );
	inflictor      = ev->GetEntity ( 2 );
	attacker           = ev->GetEntity ( 3 );
	position       = ev->GetVector ( 4 );
	direction      = ev->GetVector ( 5 );
	normal         = ev->GetVector ( 6 );
	knockback      = ev->GetInteger( 7 );
	dflags         = ev->GetInteger( 8 );
	meansofdeath   = ev->GetInteger( 9 );
	groupnum       = ev->GetInteger( 10 );
	trinum         = ev->GetInteger( 11 );
	damage_mult    = ev->GetFloat  ( 12 );

	// make sure attacker os there
	if(!attacker)
	{
		return;
	}

	// Forcefields make objects invulnerable
	if ( flags & FL_FORCEFIELD )
	{
		float alpha;
		float radius;
		Vector delta;
		Vector org;
		Entity   *forcefield;
		//
		// spawn forcefield
		//
		forcefield = new Entity;

		delta = centroid - worldorigin;
		radius = delta.length();

		//
		// transform the centroid around by the current orientation
		//
		MatrixTransformVector( delta.vec3(), orientation, org.vec3() );
		org += worldorigin;

		forcefield->setModel( "sphere2.def" );
		forcefield->setOrigin( org );
		forcefield->worldorigin.copyTo( forcefield->edict->s.old_origin );
		forcefield->setMoveType( MOVETYPE_NONE );
		forcefield->setSolidType( SOLID_NOT );
		forcefield->edict->s.scale = radius / 16;
		alpha = ( damage * damage_mult ) / 100;
		if ( alpha > 1 )
			alpha = 1;
		if ( alpha < 0.15f )
			alpha = 0.15f;
		forcefield->edict->s.alpha = alpha;
		forcefield->edict->s.renderfx |= RF_TRANSLUCENT;
		forcefield->PostEvent( EV_Remove, 0.1f );
		return;
	}

//### MFD
	if(deathmatch->value == DEATHMATCH_MFD)
	{
		// the mobsters can't be damaged in MFD
		if(isClient() && !client->resp.informer)
			damage = 0;
	}
	else if(deathmatch->value == DEATHMATCH_MOB)
	{
		// people can't hit people that are on the same team
		if(OnSameTeam( this, attacker ) || !attacker->isClient())
			damage = 0;
	}
	// if enabled you can't hurt teammates (but you can hurt yourself)
	// knockback still occurs
	else if ( ( attacker != this ) && ( DM_FLAG( DF_MODELTEAMS | DF_SKINTEAMS ) ) )
//###
	{
		if ( OnSameTeam ( this, attacker ) )
		{
			// check for friendly fire
			if ( DM_FLAG( DF_FRIENDLY_FIRE ) || (dflags & DAMAGE_NO_PROTECTION))
				meansofdeath = MOD_FRIENDLY_FIRE;
			else
				damage = 0;
		}
	}

	if (groupnum == -1)
		location = "all";
	else
		location = gi.Group_NumToName( edict->s.modelindex, groupnum );

	// Adjust damage for skill level
	if ( isClient() && !deathmatch->value && !(dflags & DAMAGE_NO_SKILL))
	{
		// Clamp the damage multiplier
		if ( damage_mult > 2 )
			damage_mult = 2;

		switch( ( int )skill->value )
		{
		case 0:
			damage *= 0.2f;
			break;
		case 1:
			damage *= 0.4f;
			break;
		case 2:
			damage *= 0.6f;
			break;
		default:
			damage *= 0.8f;
			break;
		}
	}

	// Shields reduce damage by 75%
	if ( flags & FL_SHIELDS )
	{
		damage *= 0.25f;
	}
	else if ( flags & FL_MUTANT )
	{
		damage *= 0.33f;
	}

	// If in deathmatch, client takes a minimum of 33% damage from source
	if ( deathmatch->value )
	{
		damage *= 0.66f;
		unmodified_damage = damage * 0.33f;
	}

	// check to see if we have armor in this location to reduce the damage
	if (location[0]  && !(dflags & DAMAGE_NO_ARMOR) )
	{
		if ( !strncmp( location,"all",3 ) )
		{
			char *armortypes[] = {"RiotHelmet", "FlakJacket", "FlakPants", NULL};
			int i = 0;
			float reduced_damage = 0;

			// Go through all the armor types
			while (armortypes[i])
			{
				float partial_damage = damage / 3;
				float odam = partial_damage;

				armor = ( Armor * )FindItem( armortypes[i] );

				// Check for armor in this spot
				if ( armor && ( armor->Amount() > 0 ) )
				{
					// Reduce the parital damage by the amount of armor
					partial_damage -= armor->Amount();

					if ( partial_damage < 0 )
						partial_damage = 0;

					// Remove the amount absorbed by the armor from the armor.
					armor->Remove( odam - partial_damage );

					// Keep track of the damage done to the armor for testing purposes
					armor_damage += (odam - partial_damage );
				}
				// Keep track of damage done after armor has been applied
				reduced_damage += partial_damage;
				i++;
			}
			// This is the damage after all armor is done
			damage = reduced_damage;
		}
		// else check for specific location based
		else if ( !strncmp( location,"head",4 ) )
			armor = ( Armor * )FindItem( "RiotHelmet" );
		else if ( !strncmp( location,"torso",4 ) )
			armor = ( Armor * )FindItem( "FlakJacket" );
//      else if ( !strncmp( location,"arm",3 ) )
//         armor = ( Armor * )FindItem( "FlakJacket" );
		else if ( !strncmp( location,"leg",3 ) )
			armor = ( Armor * )FindItem( "FlakPants" );

		// If armor is there, remove the appropriate amount
		if ( armor && ( armor->Amount() > 0 ) && !(dflags & DAMAGE_NO_ARMOR) )
		{
			float odam = damage;
			damage -= armor->Amount();

			if ( damage < 0 )
				damage = 0;

			armor->Remove( odam-damage );
			armor_damage += (odam - damage );
		}
	}

	damage += unmodified_damage;

	// Damage multiplier
	damage *= damage_mult;

	// Damage skins if armor didn't absorb all damage
	if ( ( damage > 0 ) && ( groupnum != -1 ) && ( flags & FL_BLOOD ) )
		if ( !edict->s.groups[ groupnum ] )
			edict->s.groups[ groupnum ]++;

	// Show location based damage.
	if ( sv_showdamagelocation->value && deathmatch->value )
	{
		// Send message to attacker
		if ( attacker->isClient() )
		{
			if ( this->client )
			{
				PrintDamageLocationToAttacker( attacker->edict, this->client->pers.netname, location );
			}
		}

		// Send message to victim
		if ( this->isClient() && attacker != this )
		{
			PrintDamageLocationToVictim( this->edict, location );
		}
	}

	// Shows detailed damage messages
	if ( sv_showdamage->value )
	{
		// Send message to attacker
		if ( attacker->isClient() )
		{
			if ( this->client )
			{
				gi.cprintf ( attacker->edict, PRINT_HIGH, "TARG:%s ARMOR_DAM:%0.2f TOT_DAM:%0.2f LOC:%s\n",
				             this->client->pers.netname, armor_damage, damage, location );
			}
			else
			{
				gi.cprintf ( attacker->edict, PRINT_HIGH, "TARG:%s ARMOR_DAM:%0.2f TOT_DAM:%0.2f LOC:%s\n",
				             getClassname(), armor_damage, damage, location );
			}
		}

		// Send message to victim
		if ( this->isClient() && attacker != this )
		{
			gi.cprintf ( this->edict, PRINT_HIGH, "TARG:%s ARMOR_DAM:%0.2f TOT_DAM:%0.2f LOC:%s\n",
			             this->client->pers.netname, armor_damage, damage, location );
		}
	}


	// Blood and sparks effects
	if (
		( damage > 0 ) &&
		( meansofdeath != MOD_DROWN ) &&
		( meansofdeath != MOD_MUTANT_DRAIN )
		)
	{
		// Blood particles
		if ( flags & FL_BLOOD )
		{
			//### tripled blood amount
//         Particles( position, normal, __min( 150,damage ), 127, PARTICLE_RANDOM );
			Particles( position, normal, __min( 150, (damage*3)) + 10, 127, PARTICLE_RANDOM );

			// Blood splat
			if ( dflags & DAMAGE_BULLET )
				SprayBlood( position, direction, damage );
		}
		// Sparks from metal
		else if ( flags & FL_SPARKS )
		{
			SpawnSparks ( position, normal, __min( damage, 75 ) );
		}
	}
	else if (  armor_damage > 0 )
	{
		// Sparks off armor
		Particles( position, normal, __min( armor_damage, 75 ), 122, 0 );
	}

	// Gib if we are dead and get hit by a rocket or shotgun
	if ( deadflag == DEAD_DEAD )
	{
		health -= damage;
		if ( DoGib( meansofdeath, inflictor ) )
		{
			Event *gibEv;

			gibEv = new Event( EV_Gib );
			gibEv->AddInteger( 0 );
			ProcessEvent( gibEv );
		}
		return;
	}
	else if ( deadflag )
	{
		health -= damage;
		return;
	}

	// Do the kick
	if (!(dflags & DAMAGE_NO_KNOCKBACK))
	{
		if ((knockback) &&
		    (movetype != MOVETYPE_NONE) &&
		    (movetype != MOVETYPE_BOUNCE) &&
		    (movetype != MOVETYPE_PUSH) &&
		    (movetype != MOVETYPE_STOP))
		{
			float m;

			if (mass < 50)
				m = 50;
			else
				m = mass;

			direction.normalize();
			if ( isClient() && ( attacker == this ) && deathmatch->value )
				momentum = direction * ( 1700.0f * ( float )knockback / m );  // the rocket jump hack...
			else
				momentum = direction * ( 500.0f * ( float )knockback / m );

			if ( dflags & DAMAGE_BULLET )
			{
				// Clip the z velocity for bullet weapons
				if ( momentum.z > 75)
					momentum.z = 75;
			}
			velocity += momentum;
		}
	}

	// check for godmode or invincibility
	if ( flags & FL_GODMODE )
	{
		return;
	}

	// do the damage
	health -= damage;

	// He's dead jim, send out a bunch of events
	if ( health <= 0 )
	{
		if ( ( meansofdeath == MOD_ION ) && !( flags & FL_NOION ) )
		{
			flags |= FL_DIE_TESSELATE;
			RandomGlobalSound( "snd_tesselate" );
		}

		event = new Event( EV_Killed );
		event->AddEntity( attacker );
		event->AddInteger( damage );
		event->AddEntity( inflictor );
		event->AddString( location );
		event->AddInteger( meansofdeath );
		ProcessEvent( event );
		return;
	}

	if ( ( damage > 0 ) && ( meansofdeath == MOD_ION ) && !( flags & FL_NOION ) )
	{
		// Do the ion particlizer effect
		TesselateModel
		(
			this,
			1,
			1000,
			direction,
			damage,
			1.0f,
			0,
			vec3_origin,
			TESSELATE_EXPANDANDSHRINK,
			126
		);
	}

	// Tesselate from damage
	if (flags & FL_TESSELATE)
	{
		TesselateModel
		(
			this,
			tess_min_size,
			tess_max_size,
			direction,
			damage,
			1.0f,
			tess_thickness,
			vec3_origin
		);
	}

	// Darken if we are hurt
	if (flags & FL_DARKEN)
	{
		edict->s.renderfx |= RF_LIGHTOFFSET;
		if ( max_health )
		{
			edict->s.lightofs = -( 40.0f * ( (float)(max_health - health) / (float)max_health ) );
		}
		else
		{
			edict->s.lightofs -= damage;
		}
		if ( edict->s.lightofs < -127 )
			edict->s.lightofs = -127;
		if ( edict->s.lightofs > 127 )
			edict->s.lightofs = 127;
	}

	// Drop your weapon if you get hit in the hands
	if ( location == gun_bone_group_name && ( ( damage > 50 ) || ( G_Random( 1 ) > 0.97f ) ) )
	{
		// don't drop weapons when in deathmatch and DF_NO_DROP_WEAPONS is set
		if ( currentWeapon && currentWeapon->IsDroppable() && ( !deathmatch->value || !DM_FLAG( DF_NO_DROP_WEAPONS ) ) )
		{
			DropCurrentWeapon();
			WeaponKnockedFromHands();
		}
	}

	if ( meansofdeath == MOD_MUTANT_DRAIN )
		return;

	// Send pain event
	event = new Event( EV_Pain );
	event->AddFloat( damage );
	event->AddEntity( attacker );
	event->AddString( location );
	event->AddInteger( meansofdeath );
	ProcessEvent( event );
}

void Sentient::UpdateSilencedWeapons( void )
{
	// If the current weapon is silencable, then change it.
	if ( currentWeapon && currentWeapon->IsSilenced() )
	{
		currentWeapon->ProcessEvent( EV_Weapon_PutAwayAndRaise );
	}
}

int Sentient::NumInventoryItems( void )
{

	return inventory.NumObjects();

}

Item *Sentient::NextItem( Item *item )
{
	Item        *next_item;
	int i;
	int n;
	qboolean item_found = false;

	if ( !item )
	{
		item_found = true;
	}
	else if ( !inventory.ObjectInList( item->entnum ) )
	{
		error( "NextItem", "Item not in list" );
	}

	n = inventory.NumObjects();

	for( i = 1; i <= n; i++ )
	{
		next_item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		assert( next_item );

		if ( next_item->isSubclassOf( InventoryItem ) && item_found )
			return next_item;

		if ( next_item == item )
			item_found = true;
	}

	return NULL;
}


Item *Sentient::PrevItem( Item *item )
{
	Item        *prev_item;
	int i;
	int n;
	qboolean item_found = false;

	if ( !item )
	{
		item_found = true;
	}
	else if ( !inventory.ObjectInList( item->entnum ) )
	{
		error( "NextItem", "Item not in list" );
	}

	n = inventory.NumObjects();

	for( i = n; i >= 1; i-- )
	{
		prev_item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		assert( prev_item );

		if ( prev_item->isSubclassOf( InventoryItem ) && item_found)
			return prev_item;

		if ( prev_item == item )
			item_found = true;
	}

	return NULL;
}


// This will search a dead body and create a floating inventory for that player
// which will be displayed on the client as a list of icons.  If the user presses
// use, then he will pick up the items in that floating inventory, and the body
// will be faded out.
void Sentient::SearchBody( Event *ev )
{
	int i,n;
	Item *item;
	Entity *other;
	Player *player;
	Event *event;
	qboolean inventory_created = false;

	other = ev->GetEntity( 1 );
	assert( other );

	// Only players can touch this
	if ( !other->isSubclassOf( Player ) )
		return;

	// Can't touch yourself
	if ( other == this )
		return;

	// Make sure the body is dead
	if ( !deadflag )
		return;

	n = inventory.NumObjects();

	// No items, get out of here
	if ( n == 0 )
	{
		return;
	}

	player = ( Player * )other;

	// Check to see if this inventory is already being displayed for
	// this body.
	if ( player->GetFloatingOwner() == this )
	{
		// Still touching the body, so extend the clear time
		player->CancelEventsOfType( EV_Player_ClearFloatingInventory );
		event = new Event( EV_Player_ClearFloatingInventory );
		player->PostEvent( event, 0.4f );
		return;
	}
	else if ( player->GetFloatingOwner() )
	{
		// Currently looking at an inventory, so don't bother with this one.
		return;
	}
	else
	{
		// Make sure the inventory is cleared out before making a new one.
		// Could have some left over items in there from other bodies
		player->ProcessEvent( EV_Player_ClearFloatingInventory );
	}

	// Create the inventory
	for( i = 1; i <= n; i++ )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		assert( item );
		if ( item->Amount() )
		{
			player->AddItemToFloatingInventory( item );
			inventory_created = true;
		}
	}

	if (!inventory_created)
		return;

	// Set the current owner of the inventory
	player->SetFloatingOwner( this );

	// Send the inventory to the client
	player->SendFloatingInventory();

	// Clear the inventory in the future
	event = new Event( EV_Player_ClearFloatingInventory );
	player->PostEvent( event, 0.3f );
}

void Sentient::UselessCheck( Event *ev )
{
	Item     *item;
	int n;
	int i;
	Event    *event;

	// Check to see if we have any inventory and
	// remove if we don't

	n = inventory.NumObjects();
	for ( i = 1; i <= n; i++ )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		assert( item );
		if ( item->Amount() )
		{
			PostEvent ( EV_Sentient_UselessCheck, 1.0f + G_Random() );
			return;
		}
	}

	event = new Event( "remove_useless" );
	ProcessEvent( event );
}

qboolean Sentient::HasInventoryOfType( const char *type )
{
	int num;
	int i;
	Item    *item;

	num = inventory.NumObjects();
	for( i = num; i > 0; i-- )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		if ( checkInheritance( type, item->getClassname() ) )
		{
			return true;
		}
	}
	return false;
}

void Sentient::DropInventoryItems( void )
{
	int num;
	int i;
	Item  *item;

	// Drop any inventory items
	num = inventory.NumObjects();
	for( i = num; i >= 1; i-- )
	{
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );
		if ( item->isSubclassOf( InventoryItem ) )
		{
			item->Drop();
		}
	}
}

qboolean Sentient::PowerupActive( void )
{
	if ( poweruptype && this->client )
	{
		gi.cprintf( edict, PRINT_HIGH, "You are already using a powerup\n" );
	}

	return poweruptype;
}

void Sentient::SprayBlood( Vector src,Vector dir,float damage )
{
	trace_t trace;
	Vector norm;
	Vector end;
	Vector ang;
	float dist;
	float scale;
	BloodSplat *splat;

	dir.normalize();
	end = src + dir * 2048;

	trace = G_Trace( src, vec_zero, vec_zero, end, this, MASK_SOLIDNONFENCE, "Sentient::SprayBlood" );

	if ( HitSky( &trace ) || ( trace.ent->solid != SOLID_BSP ) || ( trace.ent->s.number != 0 ) )
	{
		return;
	}

	dist = ( Vector( trace.endpos ) - src ).length();
	scale = damage / ( dist * 0.3 );
	if ( scale > 0.6 )
	{
		scale = 0.6;
	}

	if ( scale < 0.02 )
	{
		return;
	}

	norm = trace.plane.normal;
	norm.x = -norm.x;
	norm.y = -norm.y;
	ang = norm.toAngles();
	ang.z = G_Random( 360 );
	end = trace.endpos + Vector( trace.plane.normal ) * 0.2;

	splat = new BloodSplat( end, ang, scale );
}

EXPORT_FROM_DLL void Sentient::setModel( const char *mdl )
{
	if ( currentWeapon )
	{
		// rebind the current gun
		currentWeapon->DetachFromOwner();
		Entity::setModel( mdl );
		currentWeapon->AttachToOwner();
	}
	else
	{
		Entity::setModel( mdl );
	}
}

void Sentient::TurnOffShadow( Event *ev )
{
	// HACK FIXME
	//
	// temporary shadow flag
	//
	edict->s.renderfx &= ~RF_XFLIP;
}

EXPORT_FROM_DLL void Sentient::Archive( Archiver &arc )
{
	int i;
	int num;

	Entity::Archive( arc );

	num = inventory.NumObjects();
	arc.WriteInteger( num );
	for( i = 1; i <= num; i++ )
	{
		arc.WriteInteger( inventory.ObjectAt( i ) );
	}
	arc.WriteObjectPointer( currentWeapon );
	arc.WriteObjectPointer( currentItem );
	arc.WriteObjectPointer( newWeapon );
	arc.WriteString( currentAnim );
	arc.WriteBoolean( animOverride );
	arc.WriteEvent( *tempAnimEvent );
	arc.WriteString( gun_bone_group_name );
	arc.WriteBoolean( stopanimating_tillchange );
	arc.WriteInteger( poweruptype );
	arc.WriteInteger( poweruptimer );
	arc.WriteBoolean( sentientFrozen );
	arc.WriteBoolean( dropweapon );
	arc.WriteVector( gunoffset );
	arc.WriteVector( eyeposition );
	arc.WriteFloat( firedowntime );
	arc.WriteBoolean( firedown );
	arc.WriteString( saveskin );
	arc.WriteString( savemodel );

	//###
	arc.WriteSafePointer( rope_grabbed );
	arc.WriteFloat( rope_debounce_time );
	arc.WriteBoolean( ropesound );
	arc.WriteInteger( autoweaponswitch );
	arc.WriteInteger( weaponoverride );
	//###
}

EXPORT_FROM_DLL void Sentient::Unarchive( Archiver &arc )
{
	int i;
	int num;
	int temp;

	Entity::Unarchive( arc );

	inventory.FreeObjectList();
	num = arc.ReadInteger();
	for( i = 1; i <= num; i++ )
	{
		temp = arc.ReadInteger();
		inventory.AddObject( temp );
	}

	arc.ReadObjectPointer( ( Class ** )&currentWeapon );
	arc.ReadObjectPointer( ( Class ** )&currentItem );
	arc.ReadObjectPointer( ( Class ** )&newWeapon );
	arc.ReadString( &currentAnim );
	arc.ReadBoolean( &animOverride );
	//FIXME
	// make a ReadEventPointer function
	tempAnimEvent = new Event( arc.ReadEvent() );
	arc.ReadString( &gun_bone_group_name );
	arc.ReadBoolean( &stopanimating_tillchange );
	arc.ReadInteger( &poweruptype );
	arc.ReadInteger( &poweruptimer );
	arc.ReadBoolean( &sentientFrozen );
	arc.ReadBoolean( &dropweapon );
	arc.ReadVector( &gunoffset );
	arc.ReadVector( &eyeposition );
	arc.ReadFloat( &firedowntime );
	arc.ReadBoolean( &firedown );
	arc.ReadString( &saveskin );
	arc.ReadString( &savemodel );

	//###
	arc.ReadSafePointer( &rope_grabbed );
	arc.ReadFloat( &rope_debounce_time );
	arc.ReadBoolean( &ropesound );
	arc.ReadInteger( &autoweaponswitch );
	arc.ReadInteger( &weaponoverride );
	//###
}

void Sentient::WritePersistantData( SpawnArgGroup &group )
{
	int i;
	int num;
	Entity *ent;

	G_SetSpawnArg( "classname", getClassname() );
	if ( currentWeapon )
	{
		G_SetSpawnArg( "current_weapon", currentWeapon->getClassname() );
	}
	if ( currentItem )
	{
		G_SetSpawnArg( "current_item", currentItem->getClassname() );
	}

	G_SetIntArg( "health", health );
	G_SetIntArg( "max_health", max_health );
	G_SetIntArg( "savegame", 1 );

	group.AddArgs();

	num = inventory.NumObjects();
	for( i = 1; i <= num; i++ )
	{
		ent = G_GetEntity( inventory.ObjectAt( i ) );
		if ( ent && ent->isSubclassOf( Item ) )
		{
			G_InitSpawnArguments();
			( ( Item * )ent )->CreateSpawnArgs();
			G_SetSpawnArg( "classname", ent->getClassname() );
			G_SetIntArg( "savegame", 1 );
			group.AddArgs();
		}
	}

	G_InitSpawnArguments();
}

void Sentient::RestorePersistantData( SpawnArgGroup &group )
{
	str weap;
	str item;
	Entity *ent;
	int i;
	int num;
	Item *it;

	num = group.NumInGroup();
	for( i = 2; i <= num; i++ )
	{
		group.RestoreArgs( i );

		game.force_entnum = false;
		ent = G_CallSpawn();
		if ( !ent->isSubclassOf( Item ) )
		{
			error( "RestorePersistantData", "Loaded non-Item object of class %s\n", ent->getClassname() );
		}

		// G_CallSpawn clears all the spawnargs
		group.RestoreArgs( i );

		it = ( Item * )ent;
		it->SetOwner( this );
		it->ProcessPendingEvents();
		AddItem( it );
	}

	group.RestoreArgs( 1 );

	weap = G_GetStringArg( "current_weapon" );
	item = G_GetStringArg( "current_item" );

	health = G_GetIntArg( "health", 100 );
	max_health = G_GetIntArg( "max_health", 100 );

	if ( weap.length() )
	{
		it = FindItem( weap.c_str() );
		assert( it );
		if ( it )
		{
			if ( !it->isSubclassOf( Weapon ) )
			{
				error( "RestorePersistantData", "Tried to set non-weapon of type %s as current weapon\n", it->getClassname() );
			}
			SetCurrentWeapon( ( Weapon * )it );
		}
	}

	if ( item.length() )
	{
		it = FindItem( item.c_str() );
		assert( it );
		if ( it )
		{
			if ( !it->isSubclassOf( InventoryItem ) )
			{
				error( "RestorePersistantData", "Tried to set non-inventory item of type %s as current item\n", it->getClassname() );
			}
			currentItem = ( InventoryItem * )it;
		}
	}

}

EXPORT_FROM_DLL void Sentient::Freeze( Event *ev )
{
	sentientFrozen = true;
}

EXPORT_FROM_DLL void Sentient::UnFreeze( Event *ev )
{
	sentientFrozen = false;
}

void Sentient::ImpactDamage( Event *ev )
{
	float damg;

	if ( deathmatch->value )
	{
		damg  = velocity.length();
		damg /= 20.0f;
	}
	else
	{
		damg = 100;
	}

	setMoveType( MOVETYPE_STEP );
	Damage( world, enemy, damg,
	        worldorigin, vec_zero, vec_zero, 0,
	        0, MOD_IMPACT,
	        -1, -1, 1.0f );
}

void Sentient::WeaponUse( Event *ev )
{
	Event *event;

	if ( !currentWeapon )
		return;

	currentWeapon->ForceState( WEAPON_READY );

	event = new Event( EV_Weapon_SecondaryUse );
	event->AddEntity( this );
	currentWeapon->ProcessEvent( event );
}

void Sentient::DoubleArmor( void )
{
	int i,n;

	n = inventory.NumObjects();

	for( i = 1; i <= n; i++ )
	{
		Item  *item;
		item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) );

		if ( item->isSubclassOf( Armor ) )
			item->Set( item->Amount() * 2 );
	}
}

void Sentient::WeaponKnockedFromHands( void )
{
	str realname;

	realname = GetRandomAlias( "snd_lostweapon" );
	if ( realname.length() > 1 )
	{
		sound( realname, 1, CHAN_VOICE );
	}
	else
	{
		RandomSound( "snd_pain", 1, CHAN_VOICE );
	}
}

void Sentient::SetDropWeapon( Event *ev )
{
	dropweapon = ev->GetInteger( 1 );
}

void Sentient::DropWeaponNowEvent( Event *ev )
{
	DropCurrentWeapon();
}

//### ====================================================
// added 2015 stuff

// flamethrower stop the hurting
void Sentient::HurtFlame( Event *ev )
{
	edict->s.effects &= ~EF_FLAMES;
	// turn off the actor's heat signature if dead
	if(deadflag)
		edict->s.effects &= ~EF_WARM;
}

// to tell if it's on a bike
qboolean Sentient::IsOnBike( void )
{
	if(this->isSubclassOf( Player ))
	{
		if(((Player *)this)->GetHoverbike())
			return true;
		else
			return false;
	}
	else
	{
		return false;
	}
}

// this allows for some special case weapon switching
// when auto weapon switching is off
void Sentient::NonAutoChangeWeapon( Weapon *weapon )
{
	if ( weapon == currentWeapon )
	{
		return;
	}

	if ( currentWeapon )
	{
		if ( !weapon->ReadyToUse() )
		{
			return;
		}

		if ( !currentWeapon->ReadyToChange() )
		{
			return;
		}

		// if using a magnum, switch to dual magnum
		if(!stricmp( currentWeapon->getClassname(), "Magnum" ) &&
		   !stricmp( weapon->getClassname(), "DualMagnum" ))
		{
			newWeapon = weapon;

			currentWeapon->PutAway();
		}
	}
	else
	{
		SetCurrentWeapon( weapon );
	}
}
//###