//===========================================================================
//
// Name:			ai_cast_characters.c
// Function:		Wolfenstein AI Characters
// Programmer:		Ridah
// Tab Size:		4 (real tabs)
//===========================================================================

#include "g_local.h"
#include "../game/botlib.h"		//bot lib interface
#include "../game/be_aas.h"
#include "../game/be_ea.h"
#include "../game/be_ai_gen.h"
#include "../game/be_ai_goal.h"
#include "../game/be_ai_move.h"
#include "../botai/botai.h"			//bot ai interface

#include "ai_cast.h"

//---------------------------------------------------------------------------
// Character specific attributes (defaults, these can be altered in the editor (TODO!))
AICharacterDefaults_t aiDefaults[NUM_CHARACTERS] = {
	//AICHAR_NONE
	{0},
	//AICHAR_SOLDIER
	{
		"Soldier",
		{
			220,		// running speed
			90,			// walking speed
			80,			// crouching speed
			90,			// Field of View
			200,		// Yaw Speed	// RF change
			0.0,		// leader
			0.5,		// aim skill
			0.5,		// aim accuracy
			0.75,		// attack skill
			0.5,		// reaction time
			0.4,		// attack crouch
			0.0,		// idle crouch
			0.5,		// aggression
			0.8,		// tactical
			0.0,		// camper
			16000,		// alertness
			100,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"infantrySightPlayer",
		"infantryAttackPlayer",
		"infantryOrders",
		"infantryDeath",
		"infantrySilentDeath",	//----(SA)	added
		"infantryPain",
		"infantryStay",			// stay - you're told to stay put
		"infantryFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"infantryOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,						// team
		"infantryss/default",					// default model/skin
		{WP_MP40,WP_GRENADE_LAUNCHER},		// starting weapons
		BBOX_SMALL, {32,48},				// bbox, crouch/stand height
		AIFL_CATCH_GRENADE|AIFL_STAND_IDLE2,// flags
		NULL, NULL, NULL,					// special attack routine
		NULL,								// looping sound
		AISTATE_RELAXED
	},
	//AICHAR_AMERICAN
	{
		"American",
		{
			220,		// running speed
			90,			// walking speed
			80,			// crouching speed
			90,			// Field of View
			200,		// Yaw Speed	// RF change
			0.0,		// leader
			0.70,		// aim skill
			0.70,		// aim accuracy
			0.75,		// attack skill
			0.5,		// reaction time
			0.3,		// attack crouch
			0.0,		// idle crouch
			0.5,		// aggression
			0.8,		// tactical
			0.0,		// camper
			16000,		// alertness
			100,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"americanSightPlayer",
		"americanAttackPlayer",
		"americanOrders",
		"americanDeath",
		"americanDeath",		//----(SA)	added
		"americanPain",
		"americanStay",			// stay - you're told to stay put
		"americanFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"americanOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_ALLIES,
		"american/default",
		{WP_THOMPSON,WP_GRENADE_PINEAPPLE},
		BBOX_SMALL, {32,48},
		AIFL_CATCH_GRENADE|AIFL_STAND_IDLE2,
		NULL, NULL, NULL,
		NULL,
		AISTATE_RELAXED
	},
	//AICHAR_ZOMBIE
	{
		"Zombie",
		{
			200,		// running speed		//----(SA)	DK requested change
			60,			// walking speed		//----(SA)	DK requested change
			80,			// crouching speed
			90,			// Field of View
			350,		// Yaw Speed
			0.0,		// leader
			0.70,		// aim skill
			0.70,		// aim accuracy
			0.75,		// attack skill
			0.1,		// reaction time
			0.0,		// attack crouch
			0.0,		// idle crouch
			1.0,		// aggression
			0.0,		// tactical
			0.0,		// camper
			16000,		// alertness
			180,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"zombieSightPlayer",
		"zombieAttackPlayer",
		"zombieOrders",
		"zombieDeath",
		"zombieDeath",		//----(SA)	added
		"zombiePain",
		"zombieStay",		// stay - you're told to stay put
		"zombieFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"zombieOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_MONSTER,
		"zombie/default",
		{WP_GAUNTLET,WP_MONSTER_ATTACK2},
		BBOX_SMALL, {32,48},
		/*AIFL_NOPAIN|*/AIFL_WALKFORWARD|AIFL_NO_RELOAD,
		AIFunc_ZombieFlameAttackStart, AIFunc_ZombieAttack2Start, NULL,
		NULL,
		AISTATE_ALERT
	},

//----(SA)	added
	//AICHAR_WARZOMBIE
	{
		"WarriorZombie",
		{
			250,		// running speed	(SA) upped from 200->250 per Mike/DK
			60,			// walking speed
			80,			// crouching speed
			90,			// Field of View
			350,		// Yaw Speed
			0.0,		// leader
			0.70,		// aim skill
			0.70,		// aim accuracy
			0.75,		// attack skill
			0.1,		// reaction time
			0.0,		// attack crouch
			0.0,		// idle crouch
			1.0,		// aggression
			0.0,		// tactical
			0.0,		// camper
			16000,		// alertness
			180,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"warzombieSightPlayer",
		"warzombieAttackPlayer",
		"warzombieOrders",
		"warzombieDeath",
		"warzombieDeath",		//----(SA)	added
		"warzombiePain",
		"sound/weapons/melee/fstatck.wav",		// stay - you're told to stay put
		"warzombieFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"warzombieOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_MONSTER,
		"warrior/default",
		{WP_MONSTER_ATTACK1,WP_MONSTER_ATTACK2,WP_MONSTER_ATTACK3},
		BBOX_SMALL, {10,48},	// very low defense position
		AIFL_NO_RELOAD,
		AIFunc_WarriorZombieMeleeStart, /*AIFunc_WarriorZombieSightStart*/NULL, AIFunc_WarriorZombieDefenseStart,
		NULL,
		AISTATE_ALERT
	},
//----(SA)	end

	//AICHAR_FEMZOMBIE
	{
		"FemZombie",
		{
			90,			// running speed
			30,			// walking speed
			80,			// crouching speed
			90,			// Field of View
			200,		// Yaw Speed	// RF change
			0.0,		// leader
			0.5,		// aim skill
			0.5,		// aim accuracy
			0.75,		// attack skill
			0.8,		// reaction time
			0.0,		// attack crouch
			0.0,		// idle crouch
			1.0,		// aggression
			0.8,		// tactical
			0.0,		// camper
			16000,		// alertness
			180,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"zombieFemSightPlayer",
		"zombieFemAttackPlayer",
		"zombieFemOrders",
		"zombieFemDeath",
		"zombieFemDeath",		//----(SA)	added
		"zombieFemPain",
		"zombieFemStay",		// stay - you're told to stay put
		"zombieFemFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"zombieFemOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_MONSTER,						// team
		"femzombie/default",					// default model/skin
		{WP_GAUNTLET,WP_MONSTER_ATTACK2},		// starting weapons
		BBOX_SMALL, {32,48},					// bbox, crouch/stand height
		AIFL_NO_FLAME_DAMAGE|AIFL_STAND_IDLE2|AIFL_WALKFORWARD|AIFL_NO_RELOAD,		// flags
		AIFunc_FZombie_LightningAttackStart, AIFunc_FZombie_HandLightningAttackStart, NULL,						// special attack routine
		NULL,
		AISTATE_ALERT
	},

	
//----(SA)	end

	//AICHAR_UNDEAD
	{
		"Undead",
		{
			70,			// running speed
			70,			// walking speed
			80,			// crouching speed
			90,			// Field of View
			100,		// Yaw Speed
			0.0,		// leader
			0.70,		// aim skill
			0.70,		// aim accuracy
			0.75,		// attack skill
			0.8,		// reaction time
			0.0,		// attack crouch
			0.0,		// idle crouch
			1.0,		// aggression
			0.0,		// tactical
			0.0,		// camper
			16000,		// alertness
			100,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"undeadSightPlayer",
		"undeadAttackPlayer",
		"undeadOrders",
		"undeadDeath",
		"undeadDeath",		//----(SA)	added
		"undeadPain",
		"undeadStay",		// stay - you're told to stay put
		"undeadFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"undeadOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_MONSTER,
		"undead/default",
		{WP_GAUNTLET},
		BBOX_SMALL, {32,48},
		AIFL_WALKFORWARD|AIFL_NO_RELOAD,
		NULL, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},
	//AICHAR_VENOM
	{
		"Venom",
		{
			110,		// running speed
			100,		// walking speed
			80,			// crouching speed
			90,			// Field of View
			200,		// Yaw Speed
			0.0,		// leader
			0.70,		// aim skill
			0.70,		// aim accuracy
			0.75,		// attack skill
			0.5,		// reaction time
			0.05,		// attack crouch
			0.0,		// idle crouch
			0.9,		// aggression
			0.2,		// tactical
			0.0,		// camper
			16000,		// alertness
			240,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"venomSightPlayer",
		"venomAttackPlayer",
		"venomOrders",
		"venomDeath",
		"venomDeath",		//----(SA)	added
		"venomPain",
		"venomStay",		// stay - you're told to stay put
		"venomFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"venomOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,
		"venom/default",
		{WP_VENOM,WP_VENOM_FULL,WP_FLAMETHROWER},
		BBOX_SMALL, {32,48},
		AIFL_NO_FLAME_DAMAGE|AIFL_WALKFORWARD|AIFL_NO_RELOAD|AIFL_NO_HEADSHOT_DMG,
		NULL, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},
	//AICHAR_LOPER
	{
		"Loper",
		{
			220,		// running speed
			70,			// walking speed
			220,		// crouching speed
			90,			// Field of View
			200,		// Yaw Speed
			0.0,		// leader
			0.70,		// aim skill
			0.70,		// aim accuracy
			0.75,		// attack skill
			0.8,		// reaction time
			0.05,		// attack crouch
			0.0,		// idle crouch
			1.0,		// aggression
			0.0,		// tactical
			0.0,		// camper
			16000,		// alertness
			500,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"loperSightPlayer",
		"loperAttackPlayer",
		"loperOrders",
		"loperDeath",
		"loperDeath",		//----(SA)	added
		"loperPain",
		"loperStay",		// stay - you're told to stay put
		"loperFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"loperOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_MONSTER,
		"loper/default",
		{WP_MONSTER_ATTACK1,WP_MONSTER_ATTACK2,WP_MONSTER_ATTACK3},
		BBOX_LARGE, {48,48},		// large is for wide characters
		AIFL_NO_RELOAD,
		AIFunc_LoperAttack1Start, AIFunc_LoperAttack2Start, AIFunc_LoperAttack3Start,
		"sound/world/electloop.wav",
		AISTATE_ALERT
	},
	//AICHAR_SEALOPER

	//----(SA)	no 'correct' values/weapons/etc.  have been set.  only adding character basics
	{
		"SeaLoper",
		{
			220,		// running speed
			70,			// walking speed
			220,		// crouching speed
			90,			// Field of View
			200,		// Yaw Speed
			0.0,		// leader
			0.70,		// aim skill
			0.70,		// aim accuracy
			0.75,		// attack skill
			0.8,		// reaction time
			0.05,		// attack crouch
			0.0,		// idle crouch
			1.0,		// aggression
			0.0,		// tactical
			0.0,		// camper
			16000,		// alertness
			500,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"sealoperSightPlayer",
		"sealoperAttackPlayer",
		"sealoperOrders",
		"sealoperDeath",
		"sealoperDeath",		//----(SA)	added
		"sealoperPain",
		"sealoperStay",			// stay - you're told to stay put
		"sealoperFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"sealoperOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_MONSTER,
		"sealoper/default",

		{WP_MONSTER_ATTACK1,WP_MONSTER_ATTACK2,WP_MONSTER_ATTACK3},
		BBOX_LARGE, {48,48},		// large is for wide characters
		AIFL_NO_RELOAD,
		AIFunc_LoperAttack1Start, AIFunc_LoperAttack2Start, AIFunc_LoperAttack3Start,
		"sound/world/electloop.wav",
		AISTATE_ALERT

	},

	//AICHAR_ELITEGUARD
	{
		"Elite Guard",
		{
			230,		// running speed
			90,			// walking speed
			100,		// crouching speed
			90,			// Field of View
			200,		// Yaw Speed	// RF change
			0.0,		// leader
			0.5,		// aim skill
			1.0,		// aim accuracy
			0.9,		// attack skill
			0.3,		// reaction time
			0.4,		// attack crouch
			0.0,		// idle crouch
			0.5,		// aggression
			1.0,		// tactical
			0.0,		// camper
			16000,		// alertness
			120,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"eliteGuardSightPlayer",
		"eliteGuardAttackPlayer",
		"eliteGuardOrders",
		"eliteGuardDeath",
		"eliteGuardDeath",		//----(SA)	added
		"eliteGuardPain",
		"eliteGuardStay",		// stay - you're told to stay put
		"eliteGuardFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"eliteGuardOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,
		"eliteguard/default",
		{WP_SILENCER},		//----(SA)	TODO: replace w/ "silenced luger"
		BBOX_SMALL, {32,48},
		AIFL_CATCH_GRENADE|AIFL_STAND_IDLE2,
		NULL, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},

	//AICHAR_STIMSOLDIER1
	{
		"Stim Soldier",
		{
			170,		// running speed
			100,		// walking speed
			90,			// crouching speed
			90,			// Field of View
			150,		// Yaw Speed
			0.0,		// leader
			0.7,		// aim skill
			1.0,		// aim accuracy
			0.9,		// attack skill
			0.6,		// reaction time
			0.05,		// attack crouch
			0.0,		// idle crouch
			0.9,		// aggression
			0.1,		// tactical
			0.0,		// camper
			16000,		// alertness
			300,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"stimSoldierSightPlayer",
		"stimSoldierAttackPlayer",
		"stimSoldierOrders",
		"stimSoldierDeath",
		"stimSoldierDeath",		//----(SA)	added
		"stimSoldierPain",
		"stimSoldierStay",			// stay - you're told to stay put
		"stimSoldierFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"stimSoldierOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,
		"stim/default",
		{WP_MONSTER_ATTACK2},	// TODO: dual machinegun attack
		BBOX_LARGE, {48,64},
		AIFL_NO_RELOAD,
		NULL, AIFunc_StimSoldierAttack2Start, NULL,
		NULL,
		AISTATE_ALERT
	},
	//AICHAR_STIMSOLDIER2
	{
		"Stim Soldier",
		{
			170,		// running speed
			100,		// walking speed
			90,			// crouching speed
			90,			// Field of View
			150,		// Yaw Speed
			0.0,		// leader
			0.7,		// aim skill
			1.0,		// aim accuracy
			0.9,		// attack skill
			0.6,		// reaction time
			0.05,		// attack crouch
			0.0,		// idle crouch
			0.9,		// aggression
			0.1,		// tactical
			0.0,		// camper
			16000,		// alertness
			300,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"stimSoldierSightPlayer",
		"stimSoldierAttackPlayer",
		"stimSoldierOrders",
		"stimSoldierDeath",
		"stimSoldierDeath",		//----(SA)	added
		"stimSoldierPain",
		"stimSoldierStay",			// stay - you're told to stay put
		"stimSoldierFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"stimSoldierOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,
		"stim/default",
		{WP_MP40, WP_ROCKET_LAUNCHER, WP_MONSTER_ATTACK1},	// attack1 is leaping rocket attack
		BBOX_LARGE, {48,64},
		AIFL_NO_RELOAD,
		AIFunc_StimSoldierAttack1Start, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},
	//AICHAR_STIMSOLDIER3
	{
		"Stim Soldier",
		{
			170,		// running speed
			100,		// walking speed
			90,			// crouching speed
			90,			// Field of View
			150,		// Yaw Speed
			0.0,		// leader
			0.7,		// aim skill
			1.0,		// aim accuracy
			0.9,		// attack skill
			0.6,		// reaction time
			0.05,		// attack crouch
			0.0,		// idle crouch
			0.9,		// aggression
			0.1,		// tactical
			0.0,		// camper
			16000,		// alertness
			300,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"stimSoldierSightPlayer",
		"stimSoldierAttackPlayer",
		"stimSoldierOrders",
		"stimSoldierDeath",
		"stimSoldierDeath",		//----(SA)	added
		"stimSoldierPain",
		"stimSoldierStay",			// stay - you're told to stay put
		"stimSoldierFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"stimSoldierOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,
		"stim/default",
		{WP_MP40, WP_TESLA},	// no monster_attack1, since that's only used for the jumping rocket attack
		BBOX_LARGE, {48,64},
		AIFL_NO_RELOAD,
		AIFunc_StimSoldierAttack1Start, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},
	//AICHAR_SUPERSOLDIER
	{
		"Super Soldier",
		{
			170,		// running speed
			100,		// walking speed
			90,			// crouching speed
			90,			// Field of View
			150,		// Yaw Speed
			0.0,		// leader
			0.7,		// aim skill
			1.0,		// aim accuracy
			0.9,		// attack skill
			0.6,		// reaction time
			0.05,		// attack crouch
			0.0,		// idle crouch
			0.9,		// aggression
			0.1,		// tactical
			0.0,		// camper
			16000,		// alertness
			300,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			2.0,		// pain threshold multiplier
		},
		"superSoldierSightPlayer",
		"superSoldierAttackPlayer",
		"superSoldierOrders",
		"superSoldierDeath",
		"superSoldierDeath",		//----(SA)	added
		"superSoldierPain",
		"superSoldierStay",			// stay - you're told to stay put
		"superSoldierFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"superSoldierOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,
		"supersoldier/default",
		{WP_VENOM},
		BBOX_LARGE, {48,64},
		AIFL_NO_RELOAD|AIFL_NO_FLAME_DAMAGE|AIFL_NO_TESLA_DAMAGE,
		NULL, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},
	//AICHAR_BLACKGUARD
	{
		"Black Guard",
		{
			220,		// running speed
			90,			// walking speed
			100,		// crouching speed
			90,			// Field of View
			300,		// Yaw Speed
			0.0,		// leader
			0.5,		// aim skill
			0.8,		// aim accuracy
			0.9,		// attack skill
			0.3,		// reaction time
			0.4,		// attack crouch
			0.0,		// idle crouch
			0.5,		// aggression
			1.0,		// tactical
			0.0,		// camper
			16000,		// alertness
			120,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"blackGuardSightPlayer",
		"blackGuardAttackPlayer",
		"blackGuardOrders",
		"blackGuardDeath",
		"blackGuardDeath",		//----(SA)	added
		"blackGuardPain",
		"blackGuardStay",		// stay - you're told to stay put
		"blackGuardFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"blackGuardOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,
		"blackguard/default",
//		{WP_MP40, WP_GRENADE_LAUNCHER, WP_MONSTER_ATTACK1},	// attack1 is melee kick
		{WP_FG42, WP_FG42SCOPE, WP_GRENADE_LAUNCHER, WP_MONSTER_ATTACK1},	// attack1 is melee kick
		BBOX_SMALL, {32,48},
		AIFL_CATCH_GRENADE|AIFL_FLIP_ANIM|AIFL_STAND_IDLE2,
		AIFunc_BlackGuardAttack1Start, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},
	//AICHAR_PROTOSOLDIER
	{
		"Protosoldier",
		{
			170,		// running speed
			100,		// walking speed
			90,			// crouching speed
			90,			// Field of View
			230,		// Yaw Speed
			0.0,		// leader
			0.7,		// aim skill
			1.0,		// aim accuracy
			0.9,		// attack skill
			0.2,		// reaction time
			0.05,		// attack crouch
			0.0,		// idle crouch
			0.9,		// aggression
			0.1,		// tactical
			0.0,		// camper
			16000,		// alertness
			300,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			2.0,		// pain threshold multiplier
		},
		"protoSoldierSightPlayer",
		"protoSoldierAttackPlayer",
		"protoSoldierOrders",
		"protoSoldierDeath",
		"protoSoldierDeath",		//----(SA)	added
		"protoSoldierPain",
		"protoSoldierStay",			// stay - you're told to stay put
		"protoSoldierFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"protoSoldierOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,
		"protosoldier/default",
//		{WP_TESLA},
		{WP_VENOM_FULL},
		BBOX_LARGE, {48,64},
		AIFL_NO_TESLA_DAMAGE|AIFL_NO_FLAME_DAMAGE|AIFL_WALKFORWARD|AIFL_NO_RELOAD,
		NULL, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},
	//AICHAR_REJECTX
	{
		"Reject X Creature",
		{
			// (SA) trying to set basics up per DM's requests
			300,		// running speed	// 220 * 1.35 (DM number)
			150,		// walking speed
			100,		// crouching speed
			90,			// Field of View
			150,		// Yaw Speed
			0.0,		// leader
			0.7,		// aim skill
			1.0,		// aim accuracy
			0.9,		// attack skill
			0.6,		// reaction time
			0.0,		// attack crouch
			0.2,		// idle crouch
			0.6,		// aggression
			0.02,		// tactical
			0.0,		// camper
			16000,		// alertness
			500,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"rejectXSightPlayer",
		"rejectXAttackPlayer",
		"rejectXOrders",
		"rejectXDeath",
		"rejectXDeath",		//----(SA)	added
		"rejectXPain",
		"rejectXStay",
		"rejectXFollow",
		"rejectXOrdersDeny",
		AITEAM_NAZI,
		"rejectx/default",
		{WP_MONSTER_ATTACK1},	// ,WP_FLAMETHROWER},
		BBOX_SMALL, {32, 48},
		AIFL_STAND_IDLE2|AIFL_NO_RELOAD,
		AIFunc_RejectAttack1Start, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},
// AICHAR_FROGMAN
	{
		"Frogman",
		{
			170,		// running speed
			100,		// walking speed
			90,			// crouching speed
			90,			// Field of View
			150,		// Yaw Speed
			0.0,		// leader
			0.7,		// aim skill
			1.0,		// aim accuracy
			0.9,		// attack skill
			0.6,		// reaction time
			0.05,		// attack crouch
			0.0,		// idle crouch
			0.9,		// aggression
			0.1,		// tactical
			0.0,		// camper
			16000,		// alertness
			200,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"frogmanSightPlayer",
		"frogmanAttackPlayer",
		"frogmanOrders",
		"frogmanDeath",
		"frogmanDeath",		//----(SA)	added
		"frogmanPain",
		"frogmanStay",			// stay - you're told to stay put
		"frogmanFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"frogmanOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,
		"frogman/default",
		{WP_SPEARGUN},
		BBOX_SMALL, {32,48},	// bbox, crouch/stand height
		0,
		NULL, NULL, NULL,
		NULL,
		AISTATE_RELAXED
	},
	//AICHAR_HELGA
	{
		"Helga",
		{
			140,		// running speed
			90,			// walking speed
			80,			// crouching speed
			90,			// Field of View
			300,		// Yaw Speed
			0.0,		// leader
			0.5,		// aim skill
			0.5,		// aim accuracy
			0.75,		// attack skill
			0.5,		// reaction time
			0.0,		// attack crouch
			0.0,		// idle crouch
			0.5,		// aggression
			0.8,		// tactical
			0.0,		// camper
			16000,		// alertness
			100,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"helgaSightPlayer",
		"helgaAttackPlayer",
		"helgaOrders",
		"helgaDeath",
		"helgaDeath",		//----(SA)	added
		"helgaPain",
		"helgaStay",		// stay - you're told to stay put
		"helgaFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"helgaOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_MONSTER,						// team
		"helga/default",					// default model/skin
		{WP_LUGER},							// starting weapons
		BBOX_SMALL, {32,48},				// bbox, crouch/stand height
//		AIFL_STAND_IDLE2,					// flags
		0,
		NULL, NULL, NULL,					// special attack routine
		NULL,
		AISTATE_ALERT
	},
	//AICHAR_HEINRICH
	{
		"Heinrich",
		{
			170,		// running speed
			100,		// walking speed
			90,			// crouching speed
			90,			// Field of View
			230,		// Yaw Speed
			0.0,		// leader
			0.7,		// aim skill
			1.0,		// aim accuracy
			0.9,		// attack skill
			0.2,		// reaction time
			0.05,		// attack crouch
			0.0,		// idle crouch
			0.9,		// aggression
			0.1,		// tactical
			0.0,		// camper
			16000,		// alertness
			300,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"heinrichSightPlayer",
		"heinrichAttackPlayer",
		"heinrichOrders",
		"heinrichDeath",
		"heinrichDeath",
		"heinrichPain",
		"heinrichStay",			// stay - you're told to stay put
		"heinrichFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
		"heinrichOrdersDeny",	// deny - refuse orders (doing something else)
		AITEAM_NAZI,
		"heinrich/default",
		{WP_VENOM_FULL},
		BBOX_LARGE, {110,140},	// (SA) height is not exact.  just eyeballed.
		AIFL_WALKFORWARD|AIFL_NO_RELOAD,
		NULL, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},
	//AICHAR_PARTISAN
	{
		"Partisan",
		{
			220,		// running speed
			90,			// walking speed
			80,			// crouching speed
			90,			// Field of View
			300,		// Yaw Speed
			0.0,		// leader
			0.70,		// aim skill
			0.70,		// aim accuracy
			0.75,		// attack skill
			0.5,		// reaction time
			0.3,		// attack crouch
			0.0,		// idle crouch
			0.5,		// aggression
			0.8,		// tactical
			0.0,		// camper
			16000,		// alertness
			100,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"partisanSightPlayer",
		"partisanAttackPlayer",
		"partisanOrders",
		"partisanDeath",
		"partisanDeath",		//----(SA)	added
		"partisanPain",
		"partisanStay",
		"partisanFollow",
		"partisanOrdersDeny",
		AITEAM_ALLIES,	//----(SA)	changed affiliation for DK
		"partisan/default",
		{WP_THOMPSON},
		BBOX_SMALL, {32,48},
		AIFL_CATCH_GRENADE|AIFL_STAND_IDLE2,
		NULL, NULL, NULL,
		NULL,
		AISTATE_RELAXED
	},
	//AICHAR_CIVILIAN
	{
		"Civilian",
		{
			220,		// running speed
			90,			// walking speed
			80,			// crouching speed
			90,			// Field of View
			300,		// Yaw Speed
			0.0,		// leader
			0.70,		// aim skill
			0.70,		// aim accuracy
			0.75,		// attack skill
			0.5,		// reaction time
			0.3,		// attack crouch
			0.0,		// idle crouch
			0.5,		// aggression
			0.8,		// tactical
			0.0,		// camper
			16000,		// alertness
			100,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"civilianSightPlayer",
		"civilianAttackPlayer",
		"civilianOrders",
		"civilianDeath",
		"civilianDeath",		//----(SA)	added
		"civilianPain",
		"civilianStay",
		"civilianFollow",
		"civilianOrdersDeny",
		AITEAM_NEUTRAL,	//----(SA)	changed affiliation for DK
		"civilian/default",
		{0},
		BBOX_SMALL, {32,48},
		AIFL_CATCH_GRENADE|AIFL_STAND_IDLE2,
		NULL, NULL, NULL,
		NULL,
		AISTATE_RELAXED
	},
	//AICHAR_CHIMP
	{
		"Chimp",
		{
			220,		// running speed
			90,			// walking speed
			80,			// crouching speed
			90,			// Field of View
			300,		// Yaw Speed
			0.0,		// leader
			0.70,		// aim skill
			0.70,		// aim accuracy
			0.75,		// attack skill
			0.5,		// reaction time
			0.3,		// attack crouch
			0.0,		// idle crouch
			0.5,		// aggression
			0.8,		// tactical
			0.0,		// camper
			16000,		// alertness
			100,		// starting health
			1.0,		// hearing range
			512,		// relaxec detection radius
			1.0,		// pain threshold multiplier
		},
		"chimpSightPlayer",
		"chimpAttackPlayer",
		"chimpOrders",
		"chimpDeath",
		"chimpDeath",		//----(SA)	added
		"chimpPain",
		"chimpStay",
		"chimpFollow",
		"chimpOrdersDeny",
		AITEAM_ALLIES,	//----(SA)	changed affiliation for DK
		"chimp/default",
		{0},
		BBOX_SMALL, {16,24},
		AIFL_CATCH_GRENADE|AIFL_STAND_IDLE2|AIFL_NO_RELOAD,
		NULL, NULL, NULL,
		NULL,
		AISTATE_ALERT
	},
};
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Bounding boxes
static vec3_t bbmins[2] = {{-18, -18, -24},{-32,-32,-24}};
static vec3_t bbmaxs[2] = {{ 18,  18,  48},{ 32, 32, 68}};
// TTimo unused
//static float crouchMaxZ[2] = {32,48};	// same as player, will head be ok?
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Weapon info
cast_weapon_info_t weaponInfo;
//---------------------------------------------------------------------------

/*
============
AIChar_SetBBox

  FIXME: pass a maxZ into this so we can tailor the height for each character,
  since height isn't important for the AAS routing (whereas width is very important)
============
*/
void AIChar_SetBBox( gentity_t *ent, cast_state_t *cs )
{
	VectorCopy( bbmins[cs->aasWorldIndex], ent->client->ps.mins );
	VectorCopy( bbmaxs[cs->aasWorldIndex], ent->client->ps.maxs );
	ent->client->ps.maxs[2] = aiDefaults[cs->aiCharacter].crouchstandZ[1];
	VectorCopy( ent->client->ps.mins, ent->r.mins );
	VectorCopy( ent->client->ps.maxs, ent->r.maxs );
	ent->client->ps.crouchMaxZ = aiDefaults[cs->aiCharacter].crouchstandZ[0];
	ent->s.density = cs->aasWorldIndex;
}

/*
============
AIChar_Death
============
*/
void AIChar_Death(gentity_t *ent, gentity_t *attacker, int damage, int mod)	//----(SA)	added mod
{
	// need this check otherwise sound will overwrite gib message
	if ( ent->health > GIB_HEALTH  ) {
		if(ent->client->ps.eFlags & EF_HEADSHOT) {
			G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].quietDeathSoundScript ) );
		} else {
			switch(mod) {				//----(SA)	modified to add 'quiet' deaths
				case MOD_KNIFE_STEALTH:
				case MOD_SNIPERRIFLE:
				case MOD_SNOOPERSCOPE:
					G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].quietDeathSoundScript ) );
					break;
				default:
					G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].deathSoundScript ) );
					break;
			}
		}
	}
}

/*
=============
AIChar_GetPainLocation
=============
*/
int AIChar_GetPainLocation( gentity_t *ent, vec3_t point )
{
	static char *painTagNames[] = {
		"tag_head",
		"tag_chest",
		"tag_torso",
		"tag_groin",
		"tag_armright",
		"tag_armleft",
		"tag_legright",
		"tag_legleft",
		NULL,
	};

	int tagIndex, bestTag;
	float bestDist, dist;
	orientation_t or;

	// first make sure the client is able to retrieve tag information
  // TTimo gcc: warning: comparison is always false due to limited range of data type
  // initial line: if (trap_GetTag( ent->s.number, painTagNames[0], &or ) < 0)
	if (!trap_GetTag( ent->s.number, painTagNames[0], &or )) {
		return 0;
	}

	// find a correct animation to play, based on the body orientation at previous frame
	for (tagIndex=0, bestDist=0, bestTag=-1; painTagNames[tagIndex]; tagIndex++) {
		// grab the tag with this name
    // TTimo gcc: warning: comparison is always true due to limited range of data type
    // initial line: if (trap_GetTag( ent->s.number, painTagNames[tagIndex], &or ) >= 0) 
		if (trap_GetTag( ent->s.number, painTagNames[tagIndex], &or )) {
			dist = VectorDistance( or.origin, point );
			if (!bestDist || dist < bestDist) {
				bestTag = tagIndex;
				bestDist = dist;
			}
		}
	}

	if (bestTag >= 0) {
		return bestTag+1;
	}

	return 0;
}

/*
============
AIChar_Pain
============
*/
void AIChar_Pain (gentity_t *ent, gentity_t *attacker, int damage, vec3_t point)
{
	#define	PAIN_THRESHOLD		25
	#define STUNNED_THRESHOLD	30
	cast_state_t	*cs;
	float		dist;
	qboolean forceStun=qfalse;
	float	painThreshold, stunnedThreshold;

	cs = AICast_GetCastState( ent->s.number );

	if (g_testPain.integer == 1) {
		ent->health = ent->client->pers.maxHealth;	// debugging
	}

	if (g_testPain.integer != 2) {
		if ( level.time < cs->painSoundTime)
		{
			return;
		}
	}

	painThreshold = PAIN_THRESHOLD * cs->attributes[PAIN_THRESHOLD_SCALE];
	stunnedThreshold = STUNNED_THRESHOLD * cs->attributes[PAIN_THRESHOLD_SCALE];

	// if they are already playing another animation, we might get confused and cut it off, so don't play a pain
	if ( ent->client->ps.torsoTimer || ent->client->ps.legsTimer ) {
		return;
	}

	// if we are waiting for our weapon to fire (throwing a grenade)
	if (ent->client->ps.weaponDelay) {
		return;
	}

	// HACK: if the attacker is using the flamethrower, don't do any special pain anim or sound
	// FIXME: we should pass in the MOD here, since they could have fired a grenade, then switched weapons
	if (attacker->s.weapon == WP_FLAMETHROWER) {
		return;
	}

	if (!Q_stricmp(attacker->classname, "props_statue")) {
		damage = 99999;	// try and force a stun
		forceStun = qtrue;
	}

	// now check the damageQuota to see if we should play a pain animation
	// first reduce the current damageQuota with time
	if (cs->damageQuotaTime && cs->damageQuota > 0) {
		cs->damageQuota -= (int)((1.0 + (g_gameskill.value/GSKILL_MAX)) * ((float)(level.time - cs->damageQuotaTime)/1000) * (7.5 + cs->attributes[ATTACK_SKILL]*10.0));
		if (cs->damageQuota < 0)
			cs->damageQuota = 0;
	}

	// if it's been a long time since our last pain, scale it up
	if (cs->painSoundTime < level.time - 1000) {
		float scale;
		scale = (float)(level.time - cs->painSoundTime - 1000) / 1000.0;
		if (scale > 4.0)
			scale = 4.0;
		damage = (int)((float)damage * (1.0 + (scale * (1.0 - 0.5*g_gameskill.value/GSKILL_MAX))));
	}

	// adjust the new damage with distance, if they are really close, scale it down, to make it
	// harder to get through the game by continually rushing the enemies
	if ((dist = VectorDistance( ent->r.currentOrigin, attacker->r.currentAngles )) < 384) {
		damage -= (int)((float)damage * (1.0 - (dist/384.0)) * (0.5 + 0.5*g_gameskill.value/GSKILL_MAX));
	}

	// add the new damage
	cs->damageQuota += damage;
	cs->damageQuotaTime = level.time;

	if (forceStun) {
		damage = 99999;	// try and force a stun
		cs->damageQuota = painThreshold+1;
	}

	// if it's over the threshold, play a pain

	// don't do this if crouching, or we might clip through the world

	if (g_testPain.integer == 2 || (cs->damageQuota > painThreshold)) {
		int delay;

		// stunned?
		if (damage > stunnedThreshold && (forceStun || (rand()%2))) {	// stunned
			BG_UpdateConditionValue( ent->s.number, ANIM_COND_STUNNED, qtrue, qfalse );
		}
		// enemy weapon
		if (attacker->client) {
			BG_UpdateConditionValue( ent->s.number, ANIM_COND_ENEMY_WEAPON, attacker->s.weapon, qtrue );
		}
		if (point) {
			// location
			BG_UpdateConditionValue( ent->s.number, ANIM_COND_IMPACT_POINT, AIChar_GetPainLocation(ent, point), qtrue );
		} else {
			BG_UpdateConditionValue( ent->s.number, ANIM_COND_IMPACT_POINT, 0, qfalse );
		}

		// pause while we play a pain
		delay = BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_PAIN, qfalse, qtrue );

		// turn off temporary conditions
		BG_UpdateConditionValue( ent->s.number, ANIM_COND_STUNNED, 0, qfalse );
		BG_UpdateConditionValue( ent->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse );
		BG_UpdateConditionValue( ent->s.number, ANIM_COND_IMPACT_POINT, 0, qfalse );

		if (delay >= 0) {
			// setup game stuff to handle the character movements, etc
			cs->pauseTime = level.time + delay + 250;
			cs->lockViewAnglesTime = cs->pauseTime;
			// make sure we stop crouching
			cs->bs->attackcrouch_time = 0;
			// don't fire while in pain?
			cs->triggerReleaseTime = cs->pauseTime;
			// stay crouching if we were before the pain
			if (cs->bs->cur_ps.viewheight > cs->bs->cur_ps.crouchViewHeight) {
				cs->bs->attackcrouch_time = trap_AAS_Time() + (float)(cs->pauseTime - level.time)/1000.0 + 0.5;
			}
		}

		// if we didnt just play a scripted sound, then play one of the default sounds
		if (cs->lastScriptSound < level.time) {
			G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].painSoundScript ) );
		}

		// reset the quota
		cs->damageQuota = 0;
		cs->damageQuotaTime = 0;
		//
		cs->painSoundTime = cs->pauseTime + (int)(1000*(g_gameskill.value/GSKILL_MAX));		// add a bit more of a buffer before the next one
	}

}

/*
============
AIChar_Sight
============
*/
void AIChar_Sight( gentity_t *ent, gentity_t *other, int lastSight )
{
	cast_state_t	*cs;

	cs = AICast_GetCastState( ent->s.number );

	// if we are in noattack mode, don't make sounds
	if (cs->castScriptStatus.scriptNoAttackTime >= level.time) {
		return;
	}
	if (cs->noAttackTime >= level.time) {
		return;
	}

	// if they have recently played a script sound, then ignore this
	if (cs->lastScriptSound > level.time - 4000) {
		return;
	}

	if (!AICast_SameTeam( cs, other->s.number )) {
		if (!cs->firstSightTime || cs->firstSightTime < (level.time - 15000))
		{
			//G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].sightSoundScript ) );
		}
		cs->firstSightTime = level.time;
	}
	
}

/*
=====================
AIChar_AttackSND

  NOTE: this should just lookup a sound script for this character/weapon combo
=====================
*/
void AIChar_AttackSound (cast_state_t *cs) {
	
	gentity_t *ent;

	ent = &g_entities [cs->entityNum];

	if (cs->attackSNDtime > level.time)
		return;

	// if we are in noattack mode, don't make sounds
	if (cs->castScriptStatus.scriptNoAttackTime >= level.time) {
		return;
	}
	if (cs->noAttackTime >= level.time) {
		return;
	}

	// Ridah, only yell when throwing grenades every now and then, since it's not very "stealthy"
	if (cs->bs->weaponnum == WP_GRENADE_LAUNCHER && rand()%5)
		return;

	cs->attackSNDtime = level.time + 5000 + (1000 * rand()%10);

	AICast_ScriptEvent( cs, "attacksound", ent->aiName );
	if (cs->aiFlags & AIFL_DENYACTION) {
		return;
	}
	
	if (cs->bs->weaponnum == WP_LUGER)
		G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].ordersSoundScript ) );
	else
		G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].attackSoundScript ) );
		
}

/*
============
AIChar_spawn
============
*/
void AIChar_spawn( gentity_t *ent )
{
	gentity_t		*newent;
	cast_state_t	*cs;
	AICharacterDefaults_t *aiCharDefaults;
	int i;
	static int lastCall;
	static int numCalls;

	// if there are other cast's waiting to spawn before us, wait for them
	for (i=MAX_CLIENTS, newent=&g_entities[MAX_CLIENTS]; i<MAX_GENTITIES; i++, newent++) {
		if (!newent->inuse)
			continue;
		if (newent->think != AIChar_spawn)
			continue;
		if (newent == ent)
			break;		// we are the first in line
		// still waiting for someone else
		ent->nextthink = level.time + FRAMETIME;
		return;
	}

	// if the client hasn't connected yet, wait around
	if (!AICast_FindEntityForName("player")) {
		ent->nextthink = level.time + FRAMETIME;
		return;
	}

	if (lastCall == level.time) {
		if (numCalls++ > 2) {
			ent->nextthink = level.time + FRAMETIME;
			return;		// spawned enough this frame already
		}
	} else {
		numCalls = 0;
	}
	lastCall = level.time;

	aiCharDefaults = &aiDefaults[ent->aiCharacter];

	// ............................
	// setup weapon info
	//
	// starting weapons/ammo
	memset( &weaponInfo, 0, sizeof(weaponInfo) );
	for (i=0; aiCharDefaults->weapons[i]; i++) {
		//weaponInfo.startingWeapons[(aiCharDefaults->weapons[i] / 32)] |= ( 1 << aiCharDefaults->weapons[i] );
		//weaponInfo.startingWeapons[0] |= ( 1 << aiCharDefaults->weapons[i] );
		COM_BitSet( weaponInfo.startingWeapons, aiCharDefaults->weapons[i] );
		if (aiCharDefaults->weapons[i] == WP_GRENADE_LAUNCHER)	// give them a bunch of grenades, but not an unlimited supply
			weaponInfo.startingAmmo[BG_FindAmmoForWeapon(aiCharDefaults->weapons[i])] = 6;
		else
			weaponInfo.startingAmmo[BG_FindAmmoForWeapon(aiCharDefaults->weapons[i])] = 999;
	}
	//
	// use the default skin if nothing specified
	if (!ent->aiSkin || !strlen(ent->aiSkin))
		ent->aiSkin = aiCharDefaults->skin;
	// ............................
	//
	// create the character

	// (there will always be an ent->aiSkin (SA))
	newent = AICast_CreateCharacter( ent, aiCharDefaults->attributes, &weaponInfo, aiCharDefaults->name, ent->aiSkin, ent->aihSkin, "m", "7", "100" );
	
	if (!newent)
	{
		G_FreeEntity(ent);
		return;
	}
	// copy any character-specific information to the new entity (like editor fields, etc)
	//
	// copy this across so killing ai can trigger a target
	newent->target = ent->target;
	//
	newent->classname = ent->classname;
	newent->r.svFlags |= (ent->r.svFlags & SVF_NOFOOTSTEPS);
	newent->aiCharacter = ent->aiCharacter;
	newent->client->ps.aiChar = ent->aiCharacter;
	newent->spawnflags = ent->spawnflags;
	newent->aiTeam = ent->aiTeam;
	if (newent->aiTeam < 0) {
		newent->aiTeam = aiCharDefaults->aiTeam;
	}
	newent->client->ps.teamNum = newent->aiTeam;
	if (newent->aiCharacter == AICHAR_FEMZOMBIE)
		newent->flags |= FL_NO_KNOCKBACK;
	//
	// kill the old entity
	G_FreeEntity(ent);
	// attach to the new entity
	ent = newent;
	//
	// precache any specific sounds
	//
	// ...
	//
	// get the cast state
	cs = AICast_GetCastState( ent->s.number );
	//
	// setup any character specific cast_state variables
	cs->deathfunc = AIChar_Death;
	cs->painfunc = AIChar_Pain;
	cs->aiFlags |= aiCharDefaults->aiFlags;
	cs->aiState = aiCharDefaults->aiState;
	//
	cs->queryCountValidTime = -1;
	//
	// randomly choose idle animation
	if (cs->aiFlags & AIFL_STAND_IDLE2) {
		newent->client->ps.eFlags |= EF_STAND_IDLE2;
	}
	//
	// attach any event specific functions (pain, death, etc)
	//
	//cs->getDeathAnim = AIChar_getDeathAnim;
	cs->sightfunc = AIChar_Sight;
	if (ent->aiTeam == AITEAM_ALLIES || ent->aiTeam == AITEAM_NEUTRAL)	// friendly
		cs->activate = AICast_ProcessActivate;
	else
		cs->activate = NULL;
	cs->aifuncAttack1 = aiCharDefaults->aifuncAttack1;
	cs->aifuncAttack2 = aiCharDefaults->aifuncAttack2;
	cs->aifuncAttack3 = aiCharDefaults->aifuncAttack3;
	//
	// looping sound?
	if (aiCharDefaults->loopingSound) {
		ent->s.loopSound = G_SoundIndex( aiCharDefaults->loopingSound );
	}
	//
	// precache sounds for this character
	G_SoundIndex( aiDefaults[ent->aiCharacter].attackSoundScript );
	G_SoundIndex( aiDefaults[ent->aiCharacter].sightSoundScript );
	G_SoundIndex( aiDefaults[ent->aiCharacter].ordersSoundScript );
	G_SoundIndex( aiDefaults[ent->aiCharacter].deathSoundScript );
	G_SoundIndex( aiDefaults[ent->aiCharacter].quietDeathSoundScript );	//----(SA)	added
	G_SoundIndex( aiDefaults[ent->aiCharacter].painSoundScript );
	G_SoundIndex( aiDefaults[ent->aiCharacter].staySoundScript );
	G_SoundIndex( aiDefaults[ent->aiCharacter].followSoundScript );
	G_SoundIndex( aiDefaults[ent->aiCharacter].ordersDenySoundScript );
	//
	// special spawnflag stuff
	if (ent->spawnflags & 2) {
		cs->secondDeadTime = qtrue;
	}
	//
	// init scripting
	cs->castScriptStatus.castScriptEventIndex = -1;
	cs->castScriptStatus.scriptAttackEnt = -1;
	//
	// set crouch move speed
	ent->client->ps.crouchSpeedScale = cs->attributes[CROUCHING_SPEED] / cs->attributes[RUNNING_SPEED];
	//
	// check for some anims which we can use for special behaviours
	if (BG_GetAnimScriptEvent( &ent->client->ps, ANIM_ET_ROLL ) >= 0) {
		cs->aiFlags |= AIFL_ROLL_ANIM;
	}
	if (BG_GetAnimScriptEvent( &ent->client->ps, ANIM_ET_FLIP ) >= 0) {
		cs->aiFlags |= AIFL_FLIP_ANIM;
	}
	if (BG_GetAnimScriptEvent( &ent->client->ps, ANIM_ET_DIVE ) >= 0) {
		cs->aiFlags |= AIFL_DIVE_ANIM;
	}
	//
	// check for no headshot damage
	if (cs->aiFlags & AIFL_NO_HEADSHOT_DMG) {
		ent->headshotDamageScale = 0.0;
	}
	//
	if (!ent->aiInactive)
	{
		// trigger a spawn script event
		AICast_ScriptEvent( cs, "spawn", "" );
	} else {
		trap_UnlinkEntity(ent);
	}

}
	
//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_soldier (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
soldier entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'infantryss/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/
/*
-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY --------
model="models\mapobjects\characters\test\nazi.md3"
*/
/*
============
SP_ai_soldier
============
*/
void SP_ai_soldier( gentity_t *ent )
{
		AICast_DelayedSpawnCast( ent, AICHAR_SOLDIER );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_american (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
american entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'american/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_american
============
*/
void SP_ai_american( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_AMERICAN );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_zombie (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive PortalZombie
zombie entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'zombie/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_zombie
============
*/
void SP_ai_zombie( gentity_t *ent )
{
	ent->r.svFlags |= SVF_NOFOOTSTEPS;
	AICast_DelayedSpawnCast( ent, AICHAR_ZOMBIE );
}


//----(SA)	added
//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_warzombie (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive PortalZombie
warrior zombie entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'warrior/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_zombie
============
*/
void SP_ai_warzombie( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_WARZOMBIE );
}


//----(SA)	end



//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_femzombie (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
zombie entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'femzombie/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_femzombie
============
*/
void SP_ai_femzombie( gentity_t *ent )
{
	ent->r.svFlags |= SVF_NOFOOTSTEPS;
	AICast_DelayedSpawnCast( ent, AICHAR_FEMZOMBIE );
}




//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_undead (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
undead entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'undead/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_undead
============
*/
void SP_ai_undead( gentity_t *ent )
{
	ent->r.svFlags |= SVF_NOFOOTSTEPS;
	AICast_DelayedSpawnCast( ent, AICHAR_UNDEAD );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_venom (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
venom entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'venom/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_venom
============
*/
void SP_ai_venom( gentity_t *ent )
{
	ent->r.svFlags |= SVF_NOFOOTSTEPS;
	AICast_DelayedSpawnCast( ent, AICHAR_VENOM );
}


//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_loper (1 0.25 0) (-32 -32 -24) (32 32 48) TriggerSpawn NoRevive
loper entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'loper/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_loper
============
*/
void SP_ai_loper( gentity_t *ent )
{
	ent->r.svFlags |= SVF_NOFOOTSTEPS;
	AICast_DelayedSpawnCast( ent, AICHAR_LOPER );
	//
	level.loperZapSound = G_SoundIndex("loperZap");
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_sealoper (1 0.25 0) (-32 -32 -24) (32 32 48) TriggerSpawn NoRevive
loper entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'sealoper/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_sealoper
============
*/
void SP_ai_sealoper( gentity_t *ent )
{
	ent->r.svFlags |= SVF_NOFOOTSTEPS;
	AICast_DelayedSpawnCast( ent, AICHAR_SEALOPER );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_boss_helga (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
helga entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'helga/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_boss_helga
============
*/
void SP_ai_boss_helga( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_HELGA );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_boss_heinrich (1 0.25 0) (-32 -32 -24) (32 32 156) TriggerSpawn NoRevive
heinrich entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'helga/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_boss_heinrich
============
*/
void SP_ai_boss_heinrich( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_HEINRICH );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_partisan (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'partisan/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_partisan
============
*/
void SP_ai_partisan( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_PARTISAN );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_civilian (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'civilian/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_civilian
============
*/
void SP_ai_civilian( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_CIVILIAN );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_chimp (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'chimp/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_chimp
============
*/
void SP_ai_chimp( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_CHIMP );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_eliteguard (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
elite guard entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'eliteguard/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_eliteguard
============
*/
void SP_ai_eliteguard( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_ELITEGUARD );
}


//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_frogman (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
elite guard entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'frogman/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_frogman
============
*/
void SP_ai_frogman( gentity_t *ent )
{
	ent->r.svFlags |= SVF_NOFOOTSTEPS;
	AICast_DelayedSpawnCast( ent, AICHAR_FROGMAN );
}


//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_stimsoldier_dual (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive
stim soldier entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'stim/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_stimsoldier_dual
============
*/
void SP_ai_stimsoldier_dual( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_STIMSOLDIER1 );
	//
	level.stimSoldierFlySound = G_SoundIndex("sound/stimsoldier/flyloop.wav");
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_stimsoldier_rocket (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive
stim soldier entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'stim/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_stimsoldier_rocket
============
*/
void SP_ai_stimsoldier_rocket( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_STIMSOLDIER2 );
	//
	level.stimSoldierFlySound = G_SoundIndex("sound/stimsoldier/flyloop.wav");
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_stimsoldier_tesla (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive
stim soldier entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'stim/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_stimsoldier_tesla
============
*/
void SP_ai_stimsoldier_tesla( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_STIMSOLDIER3 );
	//
	level.stimSoldierFlySound = G_SoundIndex("sound/stimsoldier/flyloop.wav");
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_supersoldier (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive
supersoldier entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'supersoldier/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_supersoldier
============
*/
void SP_ai_supersoldier( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_SUPERSOLDIER );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_protosoldier (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive
protosoldier entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'protosoldier/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_protosoldier
============
*/
void SP_ai_protosoldier( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_PROTOSOLDIER );
}


//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_rejectxcreature (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
Reject X creature
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'rejectx/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

void SP_ai_rejectxcreature( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_REJECTX );
}

//----------------------------------------------------------------------------------------------------------------------------
/*QUAKED ai_blackguard (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
black guard entity
"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'blackguard/default' is used)
"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
"ainame" name of AI
*/

/*
============
SP_ai_blackguard
============
*/
void SP_ai_blackguard( gentity_t *ent )
{
	AICast_DelayedSpawnCast( ent, AICHAR_BLACKGUARD );
}
