//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/actor.cpp                        $
// $Revision:: 233                                                            $
//   $Author:: Markd                                                          $
//     $Date:: 2/08/99 5:12p                                                  $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /Quake 2 Engine/Sin/code/game/actor.cpp                             $
//
// 233   2/08/99 5:12p Markd
// fixed 2015 actor problem
//
// 232   1/29/99 6:14p Jimdose
// added 2015 changes
//
// 231   1/27/99 10:02p Markd
// Merged 2015 source into main code base
//
// 230   11/16/98 8:50p Markd
// increased chat times
//
// 229   11/16/98 7:28p Markd
// bullet proofed TargetEnemies so that dead bodies only spoke their secret
// once and then cleared who they were upset at, much like a soul being
// released from a dead body ;)
//
// 228   11/15/98 7:58p Jimdose
// Actors now exclusively use flags for movement types
// Made eyeposition max out at maxs.z
// Swim monsters will no longer leave water
// Air monsters will no longer enter water
//
// 227   11/15/98 2:58a Markd
// scale pain threshold by skill a bit less than what it is right now
//
// 226   11/15/98 1:24a Jimdose
// monsters don't play snd_sightenemy now unless DoAction( "sightenemy" )
// succeeds.
//
// 225   11/14/98 6:41p Jimdose
// guys no longer drown in moving water after the water has moved
//
// 224   11/13/98 11:55p Markd
// bodyimpact sound to goryimpact sound
//
// 223   11/13/98 11:00p Jimdose
// Took EV_CONSOLE off of fov
//
// 222   11/13/98 10:02p Jimdose
// bodies no longer float when they die in water
// guys will allow themselves to fall when they are standing on other setients
//
// 221   11/13/98 4:04p Markd
// increased min melee range
//
// 220   11/12/98 11:30p Jimdose
// Increased melee kick
//
// 219   11/10/98 5:47p Jimdose
// Made CanSee non-inline because it caused link problems
//
// 218   11/09/98 6:23p Markd
// Added turnspeed event
//
// 217   11/08/98 7:06p Markd
// Put in nochatter, made mutants not Like each other, added seenEnemy to
// actorinfo
//
// 216   11/07/98 10:16p Jimdose
// actors no longer get mad at scriptobjects that hurt them
//
// 215   11/07/98 3:34p Markd
// Added nodeathfade command
//
// 214   11/06/98 8:25p Jimdose
// made visiondistance adjustable from script
//
// 213   11/03/98 10:02p Aldie
// Drop inventory when being tesselated
//
// 212   10/27/98 9:06p Markd
// tweaked enemy aiming
//
// 211   10/27/98 7:52p Jimdose
// added IfEnemyWithinEvent
// made guys not lean if they're not moving very far
//
// 210   10/27/98 3:46p Markd
// Changed skill level on actors
//
// 209   10/27/98 3:51a Jimdose
// added kick to MeleeEvent
//
// 208   10/27/98 1:50a Markd
// Made JumpTo take in account water level
//
// 207   10/26/98 9:53p Markd
// Moved GotKill around, added gibfest sound
//
// 206   10/26/98 9:04p Markd
// Changed jumpto behavior
//
// 205   10/26/98 8:22p Markd
// Put CheckWater in Killed function
//
// 204   10/26/98 5:51p Jimdose
// actor saves off spawn args before processing init commands
//
// 203   10/26/98 2:19p Markd
// Added last_jump_time
//
// 202   10/26/98 1:20p Markd
// made guys be BestTargetable after having an enemy already
//
// 201   10/26/98 12:13p Markd
// fixed bug with blood preventing parent mode melee attacks
//
// 200   10/26/98 3:59a Jimdose
// made Trymove not return STEPMOVE_STUCK when opening doors
//
// 199   10/26/98 1:53a Jimdose
// TryMove was still using origin insteard of worldorigin, so the M_CheckBottom
// call was always checking where the monster currently was, and not where he
// was going to.
//
// 198   10/25/98 11:53p Aldie
// Another dead check to avoid bodies that are in the air not going dead
//
// 197   10/25/98 4:43a Markd
// Added animal event
//
// 196   10/25/98 4:28a Jimdose
// Made opendoor a forced action
// made CanShootFrom do extra tests to see if it should shoot damagable objects
//
// 195   10/25/98 1:52a Aldie
// Changed gibanim functionality to be location based
//
// 194   10/24/98 3:10p Markd
// Put in eyeoffset command
//
// 193   10/24/98 3:26a Markd
// Put in more debug info for actors
//
// 192   10/24/98 12:42a Markd
// changed origins to worldorigins where appropriate
//
// 191   10/23/98 6:46p Aldie
// Turn off shadows for death and do deathgib anim on location
//
// 190   10/22/98 9:28p Aldie
// Added ability for deathgib animation
//
// 189   10/22/98 3:05p Markd
// fixed movespeed on actors
//
// 188   10/22/98 1:40a Markd
// Added FL_STEALTH ability
//
// 187   10/21/98 11:15p Markd
// Put in FL_CLOAK ability for targetting from enemies
//
// 186   10/21/98 6:43p Markd
// made eyeposition the top of their bounding box
//
// 185   10/21/98 3:37p Markd
// put in different enemy alignment and put in support for skins
//
// 184   10/21/98 2:13a Markd
// Fixed jumping stuff
//
// 183   10/20/98 11:29p Markd
// Made CanShootFrom aim at the chest rather than the centroid
//
// 182   10/20/98 8:05p Aldie
// Added a DoGib function to ease readability
//
// 181   10/20/98 2:37a Markd
// Added additional JumpTo methods
//
// 180   10/20/98 12:43a Markd
// Put in scale for health, setsize, melee_range and melee_damage
//
// 179   10/19/98 11:53p Aldie
// Moved the solid_trigger setting to the setup function of touchfields
//
// 178   10/19/98 11:49p Aldie
// Fixed a bug where touch field was SOLID_BBOX.  Now it is forced to
// SOLID_TRIGGER.
//
// 177   10/19/98 9:55p Markd
// Made it so "state" event does a ProcessScript
//
// 176   10/19/98 8:58p Aldie
// Fix dead check for bodies in water
//
// 175   10/19/98 2:58a Markd
// Fixed CanShootFrom and Jumping
//
// 174   10/19/98 2:41a Markd
// made MakeEnemy not target the world
//
// 173   10/19/98 2:41a Aldie
// Removed MOVETYPE_TOSS when actors die
//
// 172   10/19/98 12:04a Jimdose
// made all code use fast checks for inheritance (no text lookups when
// possible)
// isSubclassOf no longer requires ::_classinfo()
//
// 171   10/18/98 6:43p Markd
// If no melee attack, make weapon not droppable
//
// 170   10/18/98 12:34a Markd
// Scaled shots_per_attack based on skill level
//
// 169   10/18/98 12:01a Markd
// Fixed AttackEntity and IfCanShoot
//
// 168   10/17/98 11:00p Markd
// Added IfCanShoot and not gettine mad at non-damageable things
//
// 167   10/17/98 9:41p Markd
// Made InvestigateSound not set nextsoundtime unless the Action responds
//
// 166   10/17/98 7:34p Jimdose
// deleted trig when actor deleted
//
// 165   10/17/98 4:59p Markd
// Added attackmode event
//
// 164   10/17/98 3:31p Markd
// Made firing more accurate over great distances
//
// 163   10/17/98 12:34a Markd
// Initialized chattime to random( 20 )
//
// 162   10/16/98 7:18p Markd
// Added a bunch of new commands, and added interfaces to the editor
//
// 161   10/16/98 3:11a Aldie
// Double the armor when actors die
//
// 160   10/16/98 1:41a Jimdose
// Added goto command for actors.  This helps the all too common mistake of
// typing "local.self goto label"
//
// 159   10/15/98 9:32p Markd
// Made inanimate objects not move, not bleed, and not gib
//
// 158   10/14/98 10:54p Jimdose
// Made Hates check for a null entity
// Added null check to investigate functions
//
// 157   10/14/98 9:00p Markd
// Fixed JumpTo command, speed wise
//
// 156   10/14/98 5:20p Markd
// Added jumpto event and jumpto function
//
// 155   10/14/98 2:17a Markd
// Added in ifcanmeleeattack event
//
// 154   10/14/98 1:27a Markd
// Added Has_weapon support
//
// 153   10/14/98 12:11a Markd
// Put extra reach into melee event
//
// 152   10/13/98 11:03p Markd
// Put scale in on all Anim_delta calls
//
// 151   10/13/98 7:34p Markd
// made gib code scalable
//
// 150   10/13/98 12:26a Markd
// Added support for flying creatures, also made it so that CanShootFrom fired
// from the centroid, and not from the gun position
//
// 149   10/11/98 8:17p Markd
// Working on nautic fixes
//
// 148   10/11/98 5:20p Markd
// Put in default pain_thresholds
//
// 147   10/11/98 5:00p Markd
// Fixed water drowning, changed ProcessCommands so that it doesn't overwrite
// the the "thread" variable
//
// 146   10/11/98 12:32a Markd
// Added Flight ability
//
// 145   10/10/98 9:12p Markd
// Fixed Chatter using an alias for SoundTimeLength
//
// 144   10/10/98 7:59p Markd
// Put in painthreshold and snd_pain_taunt
//
// 143   10/10/98 5:01p Markd
// Changed trace masks to edict->clipmasks
//
// 142   10/10/98 3:27p Markd
// Made EndBehavior always notify the actor script
//
// 141   10/10/98 1:24a Jimdose
// Did some bulletproofing against possible NULL actorthread
// Prethink kills actors with NULL threads
// added SetVariable for setting thread variables safely
//
// 140   10/09/98 5:24p Markd
// Fixed inanimate actor animations
//
// 139   10/09/98 4:59p Jimdose
// Working on savegames
//
// 138   10/07/98 11:32a Markd
// Made DamageThresholds targetable by actors
//
// 137   10/06/98 5:25p Markd
// Made MakeEnemy and DoAction take the force parameter
//
// 136   10/06/98 12:14p Markd
// Added ForceAction
//
// 135   10/05/98 10:33p Markd
// Added canStrafe and noStrafe, also added MinimumAttackRange
//
// 134   10/05/98 2:57p Markd
// Changed Chatter settings
//
// 133   10/04/98 10:36p Markd
// Put in attack_finished
//
// 132   10/04/98 8:45p Aldie
// Gib functions
//
// 131   10/04/98 2:49p Markd
// Fixed swimming and forward speed stuff
//
// 130   10/03/98 7:15p Markd
// got swimming characters working, in the process re-wrote a lot of the combat
// stuff
//
// 129   10/01/98 8:00p Markd
// Added melee capabilities
//
// 128   10/01/98 4:13p Jimdose
// Fixed some problems with CanShootFrom that prevented guys from being able to
// shoot the player when he's on ledges with railings
//
// 127   9/24/98 7:26p Markd
// made inanimate objects not set off triggers
//
// 126   9/24/98 7:10p Jimdose
// Added MyGunAngles
//
// 125   9/24/98 12:42a Jimdose
// attempting to fix dead guys hanging in air
//
// 124   9/22/98 10:19p Jimdose
// Initialized lastmove
// Fixed InFov
// Set fovdot in Fov event
//
// 123   9/22/98 5:19p Markd
// Put in new consolidated gib function
//
// 122   9/22/98 5:10p Jimdose
// Fixed inanimate objects
//
// 121   9/22/98 3:21p Markd
// put in parentmode lockout for blood and gibs
//
// 120   9/22/98 6:27a Jimdose
// Fixed bug when setting "attackmode"
//
// 119   9/22/98 5:37a Jimdose
// Made attackmode only set once when thread is created
//
// 118   9/22/98 5:31a Jimdose
// Added attackmode
//
// 117   9/22/98 1:58a Jimdose
// Added lastmove and forwardspeed
// Regrouped functions by functionality
// Removed reaction table stuff
//
// 116   9/19/98 7:49p Jimdose
// Fixed uninitialized pointer in ShowInfo and added more data
//
// 115   9/19/98 6:16p Jimdose
// Added AttackPlayer
// AttackEntity and AttackPlayer makes the actor attack the entity immediately
// by forcing a call to SightEnemy
//
// 114   9/19/98 4:34p Jimdose
// changed gi.dprintf's to gi.printf's in ShowInfo
//
// 113   9/18/98 10:55p Jimdose
// Added inanimate actor type
// started work on swimming monsters
//
// 112   9/16/98 8:38p Jimdose
// Made InFOV only test the fov in 2 dimensions
//
// 111   9/14/98 5:26p Jimdose
// Made EndBehavior set script variables
// Added some checks for NULL when getting nearest nodes
// SeenEnemy is now cleared when currentEnemy is killed
// CanShootFrom no longer assumes ScriptSlaves are shootable, but does shoot
// ScriptModel.  Gotta find some solution that allows the level designers to
// specify whats shootable by the AI.
//
// 110   9/08/98 9:48p Jimdose
// Enabled multi-size bounding box path code
//
// 109   9/03/98 9:05p Jimdose
// Guys will now shoot through scriptslaves and objects with the assumption
// that he can destroy it
//
// 108   8/31/98 7:46p Jimdose
// Spread out targeting a bit
//
// 107   8/31/98 5:42p Jimdose
// Added checks for NULL pointer in MakeEnemy and AttackEnemy
//
// 106   8/29/98 9:39p Jimdose
// Added call info to G_Trace
//
// 105   8/29/98 5:26p Markd
// added specialfx, replaced misc with specialfx where appropriate
//
// 104   8/29/98 1:11p Markd
// Made it so M_CheckBottom only checks if groundentity is not world
//
// 103   8/27/98 9:00p Jimdose
// Made centroid into a variable
// Made guys target enemies less often
// MakeEnemy checks if ent is the world (when a target is non-valid, the ent
// may be the world)
//
// 102   8/27/98 6:00p Aldie
// Drop inventory items if the actors are gibbed.
//
// 101   8/27/98 4:49p Jimdose
// Fixed bug with hidden actors being shown again on startup
// Made actors not target hidden enemies, or target while hidden (for
// cinematics)
//
// 100   8/27/98 4:27p Markd
// detach children
//
// 99    8/26/98 11:13p Jimdose
// Began strafe support
//
// 98    8/26/98 6:45p Jimdose
// Made DefineStateEvent prepend the value in ai_actorscript
//
// 97    8/24/98 6:55p Jimdose
// Added crouching pain and death animations
// Added path
//
// 96    8/20/98 8:38p Jimdose
// Actors now share enemy info between each other on sight
// CanShoot checks the head as well as the centroid
// InFOV now uses an eliptical cone for testing vision (better vertical
// peripheral vision).
//
// 95    8/19/98 4:57p Jimdose
// CanShootFrom now takes min and max weapon firing distances into account
//
// 94    8/19/98 3:59p Jimdose
// Added IfNearEvent and CopyStateEvent
//
// 93    8/19/98 2:30p Jimdose
// Can no longer alias "alert" to "idle" so I've added a hasalert variable
//
// 92    8/18/98 11:57p Aldie
// Fixed init variable warning
//
// 91    8/18/98 11:08p Markd
// Added new Alias System
//
// 90    8/18/98 9:59p Jimdose
// Made actors only react to some sounds when they're caused by an enemy.
// Hmmm, would be nice if they checked their "memory" to see if they expect
// that kind of sound from there.
//
// 89    8/15/98 5:29p Jimdose
// Fixed up the thread startup code.  No longer need to wait .1 in the script
// before sending commands.  Actor script is no longer run twice (because of
// the delayed model init commands.
// Got rid of !init label in scripts.  Now starts at beginning unless a label
// is given.
// CanShoot checks vision distance as an early exit.
//
// 88    8/14/98 11:34p Jimdose
// Got rid of "runtoarea" state.  Attack now just lets MakeEnemy call
// sightenemy.  Hmmm...probably should change to make sure that actor
// immediatly attacks the specified entity.
// don't set currentEnemy until sightenemy is responded to.  Gets rid of a lot
// of brain dead situations.
//
// 87    8/14/98 6:24p Jimdose
// Got rid of decelleration for steering
//
// 86    8/13/98 7:30p Jimdose
// actorThread is now persistant.  Threads don't get killed unless actor dies
// or is removed.
//
// 85    8/08/98 2:05p Markd
// Added entity who got the event to ForwardBehaviorEvent
//
// 84    8/07/98 7:57p Markd
// Added ForwardToBehavior event
//
// 83    8/07/98 6:01p Jimdose
// Rewrite AimAndShoot
//
// 82    8/06/98 6:59p Jimdose
// Added IfEnemyVisible
//
// 81    8/05/98 7:18p Jimdose
// Added definestate, ignoresounds, respondtosounds, respondtoall
//
// 80    8/03/98 6:49p Jimdose
// fixed thread bug
//
// 79    7/29/98 6:32p Jimdose
// Added MonsterStart
//
// 78    7/29/98 6:27p Jimdose
// Removed monster.h
//
// 77    7/29/98 2:30p Aldie
// Changed health to a float
//
// 76    7/26/98 3:40p Jimdose
// Fixed guys in water
// guys no longer open locked doors
//
// 75    7/26/98 11:43a Jimdose
// Added EnemyCanSeeMeFrom
//
// 74    7/26/98 6:44a Jimdose
// Overrode the setsize event so that def files don't change the hardcoded size
// (need to fix these damn paths one day).
//
// 73    7/26/98 3:48a Jimdose
// Modified aim based on skill
//
// 72    7/25/98 4:39p Jimdose
// Guys shoot at vehicles and shoot out windows
//
// 71    7/25/98 2:07a Jimdose
// Fixed bug with trig being delete without being NULLed out when gibbing
// Guys now attack any enemy
//
// 70    7/24/98 9:59p Aldie
// Gib stuff
//
// 69    7/24/98 6:16p Aldie
// Check for inventory items before removing dead guys
//
// 68    7/24/98 4:14p Aldie
// Cancel useless checks
//
// 67    7/24/98 3:46p Aldie
// Increased gibs
//
// 66    7/23/98 10:16p Jimdose
// Removed ignoreenemies stuff
// Fixed a bunch of anim screwups when actor is killed
// fixed "big gib" bug
//
// 65    7/23/98 6:55p Aldie
// Fun with gibs.
//
// 64    7/23/98 6:19p Jimdose
// ignoreEnemies was being set when character was targeted
//
// 63    7/22/98 7:02p Aldie
// Added ability to remove useless bodies.
//
// 62    7/22/98 2:32p Jimdose
// fixed bug where guys wouldn't sight the enemy if the went into pain
//
// 61    7/21/98 9:34p Jimdose
// Chatter now checks if the alias exists before playing the sound, thereby not
// generating warnings of missing sounds.
//
// 60    7/19/98 9:20p Jimdose
// Improved support for overriding the default script
//
// 59    7/16/98 6:21p Jimdose
// Fixed crash when actorthread was null
//
// 58    7/14/98 11:50p Jimdose
// Made PopState return whether a new state was popped off the stack
// StateDoneEvent now kills the calling thread if no previous state exists on
// the stack
// DoAction creates a new thread if no actor thread exists
//
// 57    7/14/98 6:57p Aldie
// Moved setsize before setmodel
//
// 56    7/13/98 4:59p Aldie
// Added dead player bodies with gibbing
//
// 55    7/09/98 10:40p Aldie
// Moved bodyparts to game
//
// 54    7/08/98 8:59p Jimdose
// Moved opening door to script
//
// 53    7/08/98 1:00p Jimdose
// added crouchsize and standsize
//
// 52    7/07/98 11:37p Jimdose
// Replaced priority-based system with full script-based state system
//
// 51    7/06/98 1:06p Jimdose
// working on ai
//
// 50    6/30/98 6:47p Aldie
// Added Gib Event
//
// 49    6/30/98 6:00p Jimdose
// Added more states and state detection code.
// Added range detection
// added health level detection
//
// 48    6/25/98 8:46p Markd
// Removed rotating doors
//
// 47    6/25/98 8:29p Aldie
// Updated killed and death
//
// 46    6/25/98 8:10p Jimdose
// Disabled some prints
//
// 45    6/25/98 8:10p Jimdose
// Using global actor script
//
// 44    6/24/98 4:26p Jimdose
// Fixed bugs in TurnTo
//
// 43    6/23/98 5:59p Aldie
// Fixed dropWeapon stuff.
//
// 42    6/20/98 7:47p Markd
// Added location based pain and death support
//
// 41    6/19/98 9:27p Jimdose
// Added GetGunOrientation
//
// 40    6/19/98 4:44p Jimdose
// Added util functions InFOV, etc.
//
// 39    6/18/98 8:45p Jimdose
// Removed string based SetBehavior and PushState functions
// Added source info to events
//
// 38    6/18/98 2:00p Markd
// rewrote tesselation code
//
// 37    6/17/98 10:00p Jimdose
// Improved door opening code
// Moved phyics move into class
//
// 36    6/17/98 3:15p Markd
// fixed arg issue
//
// 35    6/17/98 3:03p Markd
// Changed NumArgs back to previous behavior
//
// 34    6/17/98 1:19a Jimdose
// Moved setOwner to Item.
// Added EV_Item_Pickup
//
// 33    6/15/98 10:46p Jimdose
// Made neutral allias become enemies with pain
// shurnk bounding box temporarily
//
// 32    6/13/98 8:25p Jimdose
// Added leaning into turns (alright already Tom!)
// Added enemy visibility tests for FindCover
//
// 31    6/11/98 12:44a Jimdose
// behaviors now get info from the script at startup
//
// 30    6/10/98 10:24p Jimdose
// added priority based state system
//
// 29    6/09/98 4:18p Jimdose
// worked on ai
//
// 28    6/04/98 10:48p Jimdose
// Fixed a bunch of things that got broken just in time for E3.  Paths and
// scripting actually work now.
//
// 27    6/03/98 5:43p Jimdose
// Fixed spelling of behavior. :)
//
// 26    6/03/98 4:36p Markd
// Removed gun stuff from entity, it is now sent over as a separate object
//
// 25    6/02/98 7:46p Markd
// Took out currentweapon assignments for client-side stuff
//
// 24    5/28/98 6:01p Markd
// When actors die, they kill their dynamic light
//
// 23    5/28/98 2:11p Markd
// Made enemies drop weapons and only fire if their weapons are ready
//
// 22    5/28/98 1:50p Markd
// made actors drop weapons
//
// 21    5/27/98 4:56p Markd
// Only gib when inflictor is rocket
//
// 20    5/27/98 12:04p Jimdose
// fixed null pointer prob
//
// 19    5/27/98 11:59a Jimdose
// Gibbing is harder
//
// 18    5/27/98 11:50a Jimdose
// Added killtarget and target to killed
//
// 17    5/27/98 7:15a Jimdose
// took out dprintf
//
// 16    5/27/98 7:12a Jimdose
// ai ai ai
//
// 15    5/27/98 6:39a Jimdose
// working on ai
//
// 14    5/27/98 5:27a Aldie
// Added gibs
//
// 13    5/27/98 5:11a Jimdose
// working on ai
//
// 12    5/25/98 6:46p Jimdose
// Made animateframe, prethink and posthink into functions built into the base
// entity class
//
// 11    5/25/98 5:32p Jimdose
// Pathnodes are no longer a subclass of Entity.  This was done to save on
// edicts
//
// 10    5/25/98 1:06a Jimdose
// Added chatter
//
// 9     5/24/98 8:46p Jimdose
// Made a lot of functions more str-friendly.
// Got rid of a lot of char * based strings
// Cleaned up get spawn arg functions and sound functions
// sound functions now use consistant syntax
//
// 8     5/24/98 1:02a Jimdose
// Added monster hearing
//
// 7     5/23/98 6:27p Jimdose
// Improved steering and opening doors
//
// 6     5/22/98 9:44p Jimdose
// Disabled debug lines when checking to shoot
//
// 5     5/22/98 9:38p Jimdose
// Commented out prints
//
// 4     5/22/98 9:36p Jimdose
// AI Scripting works again (somewhat)
//
// 3     5/20/98 6:36p Jimdose
// Rewrite
//
// 2     5/18/98 8:15p Jimdose
// Created file
//
// DESCRIPTION:
// Base class for character AI.
//

#include "g_local.h"
#include "actor.h"
#include "behavior.h"
#include "scriptmaster.h"
#include "doors.h"
#include "gibs.h"
#include "vehicle.h"
#include "misc.h"
#include "specialfx.h"
#include "object.h"
#include "scriptslave.h"
#include "explosion.h"
#include "misc.h"

//#define DEBUG_PRINT

CLASS_DECLARATION( Class, ActorState, NULL );

ResponseDef ActorState::Responses[] =
{
	{ NULL, NULL }
};

cvar_t* ai_actorscript;

// used below for a slight movement tweak
// added as a global here to prevent constant re-allocation
const Vector movetweak( 0, 0, 2 );

#define TURN_SPEED      30

CLASS_DECLARATION( Sentient, Actor, "monster_generic" );

Event EV_Actor_CrouchSize( "crouchsize" );
Event EV_Actor_Fov( "fov" );
Event EV_Actor_VisionDistance( "visiondistance" );
Event EV_Actor_Start( "start" );
Event EV_Actor_Dead( "dead" );

Event EV_Actor_ClearState( "clearstate" );
Event EV_Actor_SetState( "state" );
Event EV_Actor_SetBehavior( "behavior" );

Event EV_Actor_Friend( "friend" );              // Allied to player
Event EV_Actor_Civilian( "civilian" );          // Nuetral to all, runs from danger
Event EV_Actor_Enemy( "enemy" );                    // Enemy to player
Event EV_Actor_Monster( "monster" );            // Enemy to player, civilians, and inanimate objects
Event EV_Actor_Animal( "animal" );          // neutral to all
Event EV_Actor_Inanimate( "inanimate" );        // Inanimate objects

Event EV_Actor_Swim( "swim" );                  // specifies actor as being able to swim
Event EV_Actor_Fly( "fly" );                       // specifies actor as being able to swim
Event EV_Actor_NotLand( "noland" );             // specifies actor as not being able to walk on land

Event EV_Actor_TargetEnemies( "targetenemies" );

// Scripting
Event EV_Actor_Script( "script" );
Event EV_Actor_Thread( "thread" );

Event EV_Actor_IfEnemyVisible( "ifenemyvisible" );
Event EV_Actor_IfNear( "ifnear" );

Event EV_Actor_ForwardSpeed( "forwardspeed" );

Event EV_Actor_Idle( "idle" );
Event EV_Actor_LookAt( "lookat" );
Event EV_Actor_TurnTo( "turnto" );
Event EV_Actor_FinishedBehavior( "finishedbehavior" );
Event EV_Actor_NotifyBehavior( "notifybehavior" );
Event EV_Actor_WalkTo( "walkto" );
Event EV_Actor_JumpTo( "jumpto" );
Event EV_Actor_RunTo( "runto" );
Event EV_Actor_Anim( "anim" );
Event EV_Actor_Attack( "attack" );
Event EV_Actor_AttackPlayer( "attackplayer" );

Event EV_Actor_ReserveNode( "reservenode" );
Event EV_Actor_ReleaseNode( "releasenode" );
Event EV_Actor_IfCanHideAt( "ifcanhideat" );
Event EV_Actor_IfCanStrafeAttack( "ifcanstrafeattack" );
Event EV_Actor_IfCanMeleeAttack( "ifcanmeleeattack" );
Event EV_Actor_IfCanShoot( "ifcanshoot" );
Event EV_Actor_IfEnemyWithin( "ifenemywithin" );

Event EV_Actor_IgnoreSounds( "ignoresounds" );
Event EV_Actor_RespondToSounds( "respondtosounds" );
Event EV_Actor_IgnoreAll( "ignoreall" );
Event EV_Actor_RespondToAll( "respondtoall" );
Event EV_Actor_RespondTo( "respondto" );
Event EV_Actor_Ignore( "ignore" );
Event EV_Actor_DefineState( "definestate" );
Event EV_Actor_CopyState( "copystate" );

Event EV_Actor_Remove( "remove_useless" );

Event EV_Actor_ForwardToBehavior( "forwardcommand" );
Event EV_Actor_Aim( "aim" );
Event EV_Actor_MeleeRange( "meleerange" );
Event EV_Actor_MeleeDamage( "meleedamage" );
Event EV_Actor_Melee( "melee" );
Event EV_Actor_AttackFinished( "attack_finished" );

Event EV_Actor_CanStrafe( "canstrafe" );
Event EV_Actor_NoStrafe( "nostrafe" );

Event EV_Actor_PainThreshold( "painthreshold" );
Event EV_Actor_SetKillThread( "killthread" );
Event EV_Actor_AttackRange( "attackrange" );
Event EV_Actor_ShotsPerAttack( "shotsperattack" );
Event EV_Actor_ClearEnemy( "clearenemy" );
Event EV_Actor_AttackMode( "attackmode" );
Event EV_Actor_EyePositionOffset( "eyeoffset" );
Event EV_Actor_NoDeathFade( "nodeathfade" );
Event EV_Actor_NoChatter( "nochatter" );
Event EV_Actor_TurnSpeed( "turnspeed" );

ResponseDef StateInfo::Responses[] =
{
	{ NULL, NULL }
};

CLASS_DECLARATION( Class, StateInfo, NULL );

StateInfo::StateInfo()
{
	ignore = true;
}

ResponseDef Actor::Responses[] =
{
	{ &EV_Activate, ( Response )& Actor::ActivateEvent },
	{ &EV_Use, ( Response )& Actor::UseEvent },

	{ &EV_Actor_Start, ( Response )& Actor::Start },
	{ &EV_Pain, ( Response )& Actor::Pain },
	{ &EV_Killed, ( Response )& Actor::Killed },
	{ &EV_Actor_Dead, ( Response )& Actor::Dead },

	{ &EV_Actor_TargetEnemies, ( Response )& Actor::TargetEnemies },

	{ &EV_Actor_SetBehavior, ( Response )& Actor::SetBehaviorEvent },
	{ &EV_Actor_ForwardSpeed, ( Response )& Actor::ForwardSpeedEvent },

	{ &EV_Actor_CrouchSize, ( Response )& Actor::CrouchSize },
	{ &EV_Actor_Fov, ( Response )& Actor::SetFov },
	{ &EV_Actor_VisionDistance, ( Response )& Actor::SetVisionDistance },
	{ &EV_Actor_Friend, ( Response )& Actor::FriendEvent },
	{ &EV_Actor_Civilian, ( Response )& Actor::CivilianEvent },
	{ &EV_Actor_Enemy, ( Response )& Actor::EnemyEvent },
	{ &EV_Actor_Monster, ( Response )& Actor::MonsterEvent },
	{ &EV_Actor_Animal, ( Response )& Actor::AnimalEvent },
	{ &EV_Actor_Inanimate, ( Response )& Actor::InanimateEvent },
	{ &EV_Actor_Swim, ( Response )& Actor::SwimEvent },
	{ &EV_Actor_Fly, ( Response )& Actor::FlyEvent },
	{ &EV_Actor_NotLand, ( Response )& Actor::NotLandEvent },
	{ &EV_Gib, ( Response )& Actor::GibEvent },

	// Scripting
	{ &EV_Actor_DefineState, ( Response )& Actor::DefineStateEvent },
	{ &EV_Actor_CopyState, ( Response )& Actor::CopyStateEvent },
	{ &EV_Actor_IgnoreAll, ( Response )& Actor::IgnoreAllEvent },
	{ &EV_Actor_RespondToAll, ( Response )& Actor::RespondToAllEvent },
	{ &EV_Actor_RespondTo, ( Response )& Actor::RespondToEvent },
	{ &EV_Actor_Ignore, ( Response )& Actor::IgnoreEvent },
	{ &EV_Actor_IgnoreSounds, ( Response )& Actor::IgnoreSoundsEvent },
	{ &EV_Actor_RespondToSounds, ( Response )& Actor::RespondToSoundsEvent },

	{ &EV_Actor_ClearState, ( Response )& Actor::ClearStateEvent },
	{ &EV_ScriptThread_End, ( Response )& Actor::StateDoneEvent },
	{ &EV_Actor_SetState, ( Response )& Actor::SetStateEvent },
	{ &EV_Actor_Script, ( Response )& Actor::SetScript },
	{ &EV_Actor_Thread, ( Response )& Actor::SetThread },
	{ &EV_Actor_IfEnemyVisible, ( Response )& Actor::IfEnemyVisibleEvent },
	{ &EV_Actor_IfNear, ( Response )& Actor::IfNearEvent },
	{ &EV_Actor_Idle, ( Response )& Actor::IdleEvent },
	{ &EV_ProcessCommands, ( Response )& Actor::StartMove },
	{ &EV_Actor_LookAt, ( Response )& Actor::LookAt },
	{ &EV_Actor_TurnTo, ( Response )& Actor::TurnToEvent },
	{ &EV_Actor_FinishedBehavior, ( Response )& Actor::FinishedBehavior },
	{ &EV_Actor_NotifyBehavior, ( Response )& Actor::NotifyBehavior },
	{ &EV_Actor_WalkTo, ( Response )& Actor::WalkTo },
	{ &EV_Actor_JumpTo, ( Response )& Actor::JumpToEvent },
	{ &EV_Actor_RunTo, ( Response )& Actor::RunTo },
	{ &EV_Actor_Anim, ( Response )& Actor::Anim },
	{ &EV_Actor_Attack, ( Response )& Actor::AttackEntity },
	{ &EV_Actor_AttackPlayer, ( Response )& Actor::AttackPlayer },
	{ &EV_Actor_Remove, ( Response )& Actor::RemoveUselessBody },
	{ &EV_Actor_ForwardToBehavior, ( Response )& Actor::ForwardBehaviorEvent },

	{ &EV_Actor_ReserveNode, ( Response )& Actor::ReserveNodeEvent },
	{ &EV_Actor_ReleaseNode, ( Response )& Actor::ReleaseNodeEvent },
	{ &EV_Actor_IfCanHideAt, ( Response )& Actor::IfCanHideAtEvent },
	{ &EV_Actor_IfCanStrafeAttack, ( Response )& Actor::IfCanStrafeAttackEvent },
	{ &EV_Actor_IfCanMeleeAttack, ( Response )& Actor::IfCanMeleeAttackEvent },
	{ &EV_Actor_IfCanShoot, ( Response )& Actor::IfCanShootEvent },
	{ &EV_Actor_IfEnemyWithin, ( Response )& Actor::IfEnemyWithinEvent },

	{ &EV_HeardWeapon, ( Response )& Actor::InvestigateWeaponSound },
	{ &EV_HeardMovement, ( Response )& Actor::InvestigateMovementSound },
	{ &EV_HeardPain, ( Response )& Actor::InvestigatePainSound },
	{ &EV_HeardDeath, ( Response )& Actor::InvestigateDeathSound },
	{ &EV_HeardBreaking, ( Response )& Actor::InvestigateBreakingSound },
	{ &EV_HeardDoor, ( Response )& Actor::InvestigateDoorSound },
	{ &EV_HeardMutant, ( Response )& Actor::InvestigateMutantSound },
	{ &EV_HeardVoice, ( Response )& Actor::InvestigateVoiceSound },
	{ &EV_HeardMachine, ( Response )& Actor::InvestigateMachineSound },
	{ &EV_HeardRadio, ( Response )& Actor::InvestigateRadioSound },

	{ &EV_Actor_Aim, ( Response )& Actor::SetAim },
	{ &EV_Actor_MeleeRange, ( Response )& Actor::SetMeleeRange },
	{ &EV_Actor_MeleeDamage, ( Response )& Actor::SetMeleeDamage },
	{ &EV_Actor_Melee, ( Response )& Actor::MeleeEvent },
	{ &EV_Actor_AttackFinished, ( Response )& Actor::AttackFinishedEvent },

	{ &EV_Actor_CanStrafe, ( Response )& Actor::CanStrafeEvent },
	{ &EV_Actor_NoStrafe, ( Response )& Actor::NoStrafeEvent },

	{ &EV_Actor_PainThreshold, ( Response )& Actor::SetPainThresholdEvent },
	{ &EV_Actor_SetKillThread, ( Response )& Actor::SetKillThreadEvent },
	{ &EV_Actor_AttackRange, ( Response )& Actor::AttackRangeEvent },
	{ &EV_Actor_ShotsPerAttack, ( Response )& Actor::ShotsPerAttackEvent },
	{ &EV_Actor_ClearEnemy, ( Response )& Actor::ClearEnemyEvent },
	{ &EV_Actor_AttackMode, ( Response )& Actor::AttackModeEvent },
	{ &EV_SetHealth, ( Response )& Actor::SetHealth },
	{ &EV_Actor_EyePositionOffset, ( Response )& Actor::EyeOffset },
	{ &EV_Actor_NoDeathFade, ( Response )& Actor::NoDeathFadeEvent },
	{ &EV_Actor_NoChatter, ( Response )& Actor::NoChatterEvent },
	{ &EV_Actor_TurnSpeed, ( Response )& Actor::TurnSpeedEvent },

	{ &EV_ScriptThread_Goto, ( Response )& Actor::GotoEvent },

	{ NULL, NULL }
};

//***********************************************************************************************
//
// Initialization functions
//
//***********************************************************************************************

Actor::Actor()
{
	const char* text;
	const char* skinname;
	Event* ev;
	SpawnArgs args;

	// don't spawn monsters in deathmatch
	if( deathmatch->value || nomonsters->value )
	{
		/* FS: newanimevent will bomb during deconstructor when attempting to load SP only maps (such as xeno1.bsp) since it will be filled with garbage */
		newanimnum = -1;
		newanim = "";
		newanimevent = NULL;

		PostEvent( EV_Remove, 0 );
		return;
	}

	totallen = 0;       /* Not initialized */
	has_melee = false;
	movespeed = 1;

	actortype = IS_ENEMY;
	setSolidType( SOLID_BBOX );
	setMoveType( MOVETYPE_STEP );

	health               = G_GetFloatArg( "health", 100 );
	max_health           = health;
	takedamage           = DAMAGE_AIM;
	mass                     = static_cast<int>( G_GetFloatArg( "mass", 200 ) );
	deadflag                 = DEAD_NO;
	edict->clipmask  = MASK_MONSTERSOLID;
	edict->svflags      |= SVF_MONSTER;

	forwardspeed = 0;
	checkStrafe = true;

	// No longer used
	movement = AI_CANWALK;

	lastmove = STEPMOVE_OK;

	gunoffset.setXYZ( 0, 0, 44 );

	numonstack = 0;
	behavior = NULL;
	path = NULL;

	newanimnum = -1;
	newanim = "";
	newanimevent = NULL;

	text = G_GetSpawnArg( "weapon" );
	if( text )
	{
		giveWeapon( text );
	}

	spawngroup = G_GetStringArg( "spawngroup" );

	attackmode = G_GetIntArg( "attackmode", 0 );

	attack_range = G_GetFloatArg( "attackrange", 8192 );

	fov = 150;
	fovdot = static_cast<float>( cos( fov * 0.5f * M_PI / 180.0f ) );

	vision_distance = G_GetFloatArg( "visiondistance", 1024 );

	eyeoffset = vec_zero;
	eyeposition.setXYZ( 0, 0, 64 );

	hasalert = false;

	lastEnemy = NULL;
	enemyRange = RANGE_FAR;
	seenEnemy = false;
	nodeathfade = false;
	nochatter = false;

	turnspeed = 60;

	if( !parentmode->value )
	{
		flags |= FL_BLOOD;
		flags |= FL_DIE_GIBS;
	}

	//
	// don't talk all at once initially
	//
	chattime = G_Random( 20 );
	nextsoundtime = 0;
	trig = NULL;
	deathgib = false;

	// set default crouchsize
	crouchsize_min.setXYZ( -16, -16, 0 );
	crouchsize_max.setXYZ( 16, 16, 32 );
	standsize_min = mins;
	standsize_max = maxs;

	// use a cvar to help with debugging
	ai_actorscript = gi.cvar( "ai_actorscript", "", 0 );
	actorscript = G_GetStringArg( "script", "global/enemy.scr" );
	actorstart = G_GetStringArg( "thread", "" );
	kill_thread = G_GetStringArg( "killthread", "" );

	// default melee characteristics
	melee_range = 100;
	melee_damage = 30;
	// default aim (normal)
	aim = G_GetFloatArg( "aim", 0 );
	// default pain_threshold is 10
	pain_threshold = G_GetFloatArg( "painthreshold", 10 * skill->value );
	// default shots per attack is 5 + ( 2 * skill->level )
	shots_per_attack = G_GetFloatArg( "shotsperattack", 3 + ( 2 * skill->value ) );

	startpos = worldorigin;

	next_drown_time = 0;
	air_finished = level.time + 5;
	last_jump_time = 0;

	CheckWater();

	setSize( Vector( -16, -16, 0 ), Vector( 16, 16, 76 ) );
	//setModel( G_GetSpawnArg( "model", "grunt.def" ) );
	showModel();

	if( !LoadingSavegame )
	{
		// save off our spawn args
		args.SetArgs();
		G_InitSpawnArguments();

		// force the init commands to be processed so that we start the right actor script immediately
		CancelEventsOfType( EV_ProcessInitCommands );
		ev = new Event( EV_ProcessInitCommands );
		ev->AddInteger( edict->s.modelindex );
		ProcessEvent( ev );

		SetupThread();

		if( eyeposition.z > maxs.z )
		{
			eyeposition.z = maxs.z;
		}

		// restore our args
		G_InitSpawnArguments();
		args.RestoreArgs();

		// wait until the script starts before thinking
		PostEvent( EV_Actor_Start, FRAMETIME );
	}
	//
	// I put this here, so that the initcommands would already be processed
	//
	skinname = G_GetSpawnArg( "skin" );
	if( skinname && skinname[ 0 ] )
	{
		int skinnum;

		skinnum = gi.Skin_NumForName( edict->s.modelindex, skinname );
		if( skinnum >= 0 )
			edict->s.skinnum = skinnum;
	}
}

Actor::~Actor()
{
	int n;
	int i;

	if( newanimevent )
	{
		delete newanimevent;
		newanimevent = 0;
	}
	if( actorthread )
	{
		actorthread->ProcessEvent( EV_ScriptThread_End );
		actorthread = NULL;
	}

	// delete the old action/response list
	n = actionList.NumObjects();
	for( i = n; i >= 1; i-- )
	{
		delete actionList.ObjectAt( i );
	}
	actionList.ClearObjectList();
	if( behavior )
	{
		delete behavior;
		behavior = NULL;
	}

	if( path )
	{
		delete path;
		path = NULL;
	}

	if( trig )
	{
		delete trig;
		trig = NULL;
	}
	ClearStateStack();
}

void Actor::Start( Event*  /* ev */)
{
	MonsterStart* start;

	// This is only used for choosing delay times for targeting enemies
	static int actornum = 0;

	hasalert = ( gi.Anim_Random( edict->s.modelindex, "alert" ) != -1 );

	start = MonsterStart::GetRandomSpot( spawngroup );
	if( start )
	{
		setOrigin( start->worldorigin );
		worldorigin.copyTo( edict->s.old_origin );
		setAngles( start->worldangles );
		if( strcmp( start->animname.c_str(), "" ) )
		{
			SetAnim( start->animname.c_str() );
		}
	}

	droptofloor( 16 );

	flags |= FL_PRETHINK;

	// see if we have any melee attacks
	if( HasAnim( "melee" ) )
	{
		has_melee = true;
	}
	else
	{
		//
		// make sure we can't knock the weapon out of this characters hands
		//
		if( currentWeapon )
		{
			Event* ev;

			ev = new Event( EV_Weapon_NotDroppable );
			currentWeapon->ProcessEvent( ev );
		}
		has_melee = false;
	}

	// spread their targeting about a bit
	PostEvent( EV_Actor_TargetEnemies, ( actornum++ % 10 ) * FRAMETIME );
}

//***********************************************************************************************
//
// Vision functions
//
//***********************************************************************************************

range_t Actor::Range( Entity* targ )
{
	float r;
	Vector delta;

	delta = centroid - targ->centroid;
	r = delta * delta;
	if( r < 120 * 120 )
	{
		return RANGE_MELEE;
	}

	if( r < 500 * 500 )
	{
		return RANGE_NEAR;
	}

	if( r < 1000 * 1000 )
	{
		return RANGE_MID;
	}

	return RANGE_FAR;
}

inline qboolean Actor::InFOV( const Vector& pos )
{
	Vector delta;
	float dot;

	delta = pos - EyePosition();
	if( !delta.x && !delta.y )
	{
		// special case for straight up and down
		return true;
	}
	// give better vertical vision
	delta.z = 0;

	delta.normalize();
	dot = DotProduct( orientation[ 0 ], delta.vec3() );

	return ( dot > fovdot );
}

inline qboolean Actor::InFOV( Entity* ent )
{
	return InFOV( ent->centroid );
}

inline qboolean Actor::CanSeeFOV( Entity* ent )
{
	return InFOV( ent ) && CanSeeFrom( worldorigin, ent );
}

qboolean Actor::CanSeeFrom( const Vector& pos, Entity* ent )
{
	trace_t trace;
	Vector p, tmp;

	p = ent->centroid;

	tmp = pos + eyeposition;

	// Check if he's visible
	trace = G_Trace( tmp, vec_zero, vec_zero, p, this, MASK_OPAQUE, "Actor::CanSeeFrom 1" );
	if( trace.fraction == 1.0 || trace.ent == ent->edict )
	{
		return true;
	}

	// Check if his head is visible
	p.z = ent->absmax.z;
	trace = G_Trace( tmp, vec_zero, vec_zero, p, this, MASK_OPAQUE, "Actor::CanSeeFrom 2" );
	if( trace.fraction == 1.0 || trace.ent == ent->edict )
	{
		return true;
	}
	return false;
}

qboolean Actor::CanSee( Entity* ent )
{
	return CanSeeFrom( worldorigin, ent );
}

int Actor::EnemyCanSeeMeFrom( const Vector& pos )
{
	Entity* ent;
	int i;
	int n;
	float rad;
	Vector d;
	Vector p1;
	Vector p2;
	int c;

	rad = max( size.x, size.y ) * 1.44f * 0.5f;

	c = 0;
	n = enemyList.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		ent = enemyList.ObjectAt( i );
		if( !ent || ent->deadflag || ( ent->flags & FL_NOTARGET ) )
		{
			continue;
		}

		if( WithinDistance( ent, vision_distance ) )
		{
			// To check if we're visible, I create a plane that intersects the actor
			// and is perpendicular to the delta vector between the actor and his enemy.
			// I place four points on this plane that "frame" the actor and check if
			// the enemy can see any of those points.
			d = ent->centroid - pos;
			d.z = 0;
			d.normalize();
			p1.x = -d.y;
			p1.y = d.x;
			p1 *= rad;
			p2 = p1;
			p1.z = mins.z;
			p2.z = maxs.z;

			if( CanSeeFrom( pos + p1, ent ) )
			{
				c++;
			}
			if( CanSeeFrom( pos + p2, ent ) )
			{
				c++;
			}
			p1.z = -p1.z;
			p2.z = -p2.z;
			if( CanSeeFrom( pos - p1, ent ) )
			{
				c++;
			}
			if( CanSeeFrom( pos - p2, ent ) )
			{
				c++;
			}
		}
	}

	return c;
}

qboolean Actor::CanSeeEnemyFrom( const Vector& pos )
{
	Entity* ent;
	int i;
	int n;

	n = enemyList.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		ent = enemyList.ObjectAt( i );
		if( !ent || ent->deadflag || ( ent->flags & FL_NOTARGET ) )
		{
			continue;
		}

		if( WithinDistance( ent, vision_distance ) && CanSeeFrom( pos, ent ) )
		{
			return true;
		}
	}

	return false;
}

//***********************************************************************************************
//
// Weapon functions
//
//***********************************************************************************************

qboolean Actor::WeaponReady( void )
{
	if( currentWeapon && currentWeapon->ReadyToFire() )
	{
		return true;
	}
	else if( !currentWeapon && has_melee )
	{
		return true;
	}

	return false;
}

void Actor::Attack( Event*  /* ev */
				  )
{
	Vector delta;
	Vector ang;
	Vector ang2;

	if( ( currentWeapon ) && currentWeapon->ReadyToFire() && currentWeapon->HasAmmo() )
	{
		if( currentEnemy )
		{
			ang = angles;
			delta = currentEnemy->centroid - GunPosition();
			ang2 = delta.toAngles();
			ang2[ 0 ] = -ang2[ 0 ];
			setAngles( ang2 );
			currentWeapon->Fire();
			setAngles( ang );
		}
		else
		{
			currentWeapon->Fire();
		}
	}
}

Vector Actor::GunPosition( void )
{
	vec3_t trans[ 3 ];
	vec3_t orient;
	int groupindex;
	int tri_num;
	Vector offset = vec_zero;
	Vector result;

	// get the gun position of the actor
	if( !gi.GetBoneInfo( edict->s.modelindex, "gun", &groupindex, &tri_num, orient ) )
	{
		// Gun doesn't have a barrel, just return the default
		return worldorigin + gunoffset;
	}

	gi.GetBoneTransform( edict->s.modelindex, groupindex, tri_num, orient, edict->s.anim,
						 edict->s.frame, edict->s.scale, trans, offset.vec3() );

	MatrixTransformVector( offset.vec3(), orientation, result.vec3() );
	result += worldorigin;

	return result;
}

inline Vector Actor::MyGunAngles( Vector muzzlepos, qboolean firing )
{
	Vector ang;
	Vector dir;


	if( currentEnemy && firing )
	{
		dir = currentEnemy->centroid - muzzlepos;
		dir.z += ( currentEnemy->absmax.z - currentEnemy->centroid.z ) * 0.75f;
		dir.normalize();

		ang = dir.toAngles();
		ang.x = -ang.x;
	}
	else
	{
		ang.x = -worldangles.x;
		ang.y = worldangles.y;
		ang.z = worldangles.z;
	}

	return ang;
}


#define MIN_AIM_DISTANCE 400
inline void Actor::GetGunOrientation( Vector muzzlepos, Vector* forward, Vector* right, Vector* up )
{
	Vector ang;
	float accuracy;
	float spread;
	float invaim;
	float skl;
	float enemydistance;

	ang = MyGunAngles( muzzlepos, true );

	if( currentEnemy )
	{
		enemydistance = ( currentEnemy->centroid - muzzlepos ).length();
		if( enemydistance < MIN_AIM_DISTANCE )
			enemydistance = MIN_AIM_DISTANCE;
	}
	else
	{
		enemydistance = MIN_AIM_DISTANCE;
	}

	// 0 is maximum accuracy
	invaim = 1.0f - aim;
	if( invaim < 0 )
		invaim = 0;
	skl = min( skill->value, 3 );
	if( skl < 1 )
		skl = 1;
	accuracy = 1 - ( min( skl, 3 ) * 0.18f );
	accuracy *= invaim;
	spread = ( 8 * MIN_AIM_DISTANCE * accuracy ) / enemydistance;
	ang.x += G_CRandom( spread );
	ang.y += G_CRandom( spread );

	ang.AngleVectors( forward, right, up );
}

qboolean Actor::CanShootFrom( Vector pos, Entity* ent, qboolean usecurrentangles )
{
	int mask;
	Vector delta;
	Vector start;
	Vector end;
	float len;
	trace_t trace;
	Vehicle*    v;
	Entity* t;
	Vector ang;

	if( !currentWeapon || !WithinDistance( ent, vision_distance ) )
	{
		if( !currentWeapon && !has_melee )
			return false;
	}

	if( usecurrentangles )
	{
		Vector dir;

		start = pos + GunPosition() - worldorigin;
//      start = pos + centroid - worldorigin;
		end = ent->centroid;
		end.z += ( ent->absmax.z - ent->centroid.z ) * 0.75f;
		delta = end - start;
		ang = delta.toAngles();
		ang.x = -ang.x;
		ang.y = angles.y;
		len = delta.length();
		ang.AngleVectors( &dir, NULL, NULL );
		dir *= len;
		end = start + dir;
	}
	else
	{
		start = pos + GunPosition() - worldorigin;
		end = ent->centroid;
		end.z += ( ent->absmax.z - ent->centroid.z ) * 0.75f;
		delta = end - start;
		len = delta.length();
	}

	// check if we're too far away, or too close
	if( currentWeapon )
	{
		if( ( len > attack_range ) || ( len > currentWeapon->GetMaxRange() ) || ( len < currentWeapon->GetMinRange() ) )
		{
			return false;
		}
		mask = MASK_SHOT;
	}
	else
	{
		if( ( len > attack_range ) || ( len > melee_range ) )
		{
			return false;
		}
		mask = MASK_PROJECTILE;
	}

	// shoot past the guy we're shooting at
	end += delta * 4;

#if 0
	if( usecurrentangles )
	{
		G_DebugLine( start, end, 1, 0, 0, 1 );
	}
	else
	{
		G_DebugLine( start, end, 1, 1, 0, 1 );
	}
#endif

	// Check if he's visible
	trace = G_Trace( start, vec_zero, vec_zero, end, this, mask, "Actor::CanShootFrom" );
	if( trace.startsolid )
	{
		return false;
	}

	// If we hit the guy we wanted, then shoot
	if( trace.ent == ent->edict )
	{
		return true;
	}

	// if we hit a vehicle, check if the driver is someone we want to hit
	t = trace.ent->entity;
	if( t && t->isSubclassOf( Vehicle ) )
	{
		v = ( Vehicle* )t;
		if( ( v->Driver() == ent ) || IsEnemy( v->Driver() ) )
		{
			return true;
		}
		return false;
	}

	// If we hit someone else we don't like, then shoot
	if( IsEnemy( t ) )
	{
		return true;
	}

	// if we hit something breakable, check if shooting it will
	// let us shoot someone.
	if( t->isSubclassOf( Shatter ) ||
			t->isSubclassOf( Object ) ||
			t->isSubclassOf( DamageThreshold ) ||
			t->isSubclassOf( ScriptModel ) )
	{
		trace = G_Trace( Vector( trace.endpos ), vec_zero, vec_zero, end, t, mask, "Actor::CanShootFrom 2" );
		if( trace.startsolid )
		{
			return false;
		}

		// If we hit the guy we wanted, then shoot
		if( trace.ent == ent->edict )
		{
			return true;
		}

		// If we hit someone else we don't like, then shoot
		if( IsEnemy( trace.ent->entity ) )
		{
			return true;
		}

		// Forget it then
		return false;
	}

	return false;
}

qboolean Actor::CanShoot( Entity* ent, qboolean usecurrentangles )
{
	return CanShootFrom( worldorigin, ent, usecurrentangles );
}

float Actor::AttackRange( void )
{
	if( !currentWeapon && !has_melee )
	{
		return 0;
	}

	if( currentWeapon )
	{
		return ( currentWeapon->GetMaxRange() );
	}
	else
	{
		return ( melee_range );
	}
}

float Actor::MinimumAttackRange( void )
{
	float range;
	float maxrange;

	if( !currentWeapon && !has_melee )
	{
		return 100;
	}

	range = melee_range * 0.75f;
	maxrange = melee_range;

	if( currentWeapon )
	{
		range = currentWeapon->GetMinRange();
		if( !range && melee_range )
			range = melee_range * 0.75f;
		maxrange = currentWeapon->GetMaxRange();
	}

	if( range > maxrange )
		range = maxrange;

	return range;
}

qboolean Actor::HasWeapon( void )
{
	return ( currentWeapon != NULL );
}

//***********************************************************************************************
//
// Actor type script commands
//
//***********************************************************************************************

void Actor::FriendEvent( Event*  /* ev */)
{
	actortype = IS_FRIEND;
}

void Actor::CivilianEvent( Event*  /* ev */)
{
	actortype = IS_CIVILIAN;
}

void Actor::EnemyEvent( Event* ev )
{
	actortype = IS_ENEMY;
}

void Actor::InanimateEvent( Event* ev )
{
	actortype = IS_INANIMATE;
	//
	// clear the monster flag so triggers are not triggered
	//
	edict->svflags  &= ~SVF_MONSTER;
	//
	// don't make them move
	//
	setMoveType( MOVETYPE_NONE );
	//
	// don't make it bleed
	//
	flags &= ~FL_BLOOD;
	//
	// don't make it gib
	//
	flags &= ~FL_DIE_GIBS;
}

void Actor::MonsterEvent( Event* ev )
{
	actortype = IS_MONSTER;
}

void Actor::AnimalEvent( Event* ev )
{
	actortype = IS_ANIMAL;
}

//***********************************************************************************************
//
// Enemy management
//
//***********************************************************************************************

qboolean Actor::HasEnemies( void )
{
	return ( enemyList.NumObjects() > 0 );
}

qboolean Actor::IsEnemy( Entity* ent )
{
	return enemyList.ObjectInList( EntityPtr( ent ) ) && seenEnemy;
}

void Actor::MakeEnemy( Entity* ent, qboolean force )
{
	// don't get mad at things that can't be hurt or the world
	if(
		ent &&
		( world != ent ) &&
		( this != ent ) &&
		!( ent->flags & FL_NOTARGET ) &&
		( ent->takedamage != DAMAGE_NO )
	)
	{
		if( !enemyList.ObjectInList( EntityPtr( ent ) ) )
		{
			enemyList.AddObject( EntityPtr( ent ) );
		}

		if( !currentEnemy && !seenEnemy )
		{
			currentEnemy = ent;
			if( DoAction( "sightenemy", force ) )
			{
				seenEnemy = true;
				Chatter( "snd_sightenemy", 5 );
			}
			else
			{
				currentEnemy = NULL;
			}
		}
	}
}

void Actor::ClearEnemies( void )
{
	currentEnemy = NULL;
	seenEnemy = false;
	enemyList.ClearObjectList();
}


qboolean Actor::Likes( Entity* ent )
{
	Actor* act;

	if( ent->isClient() )
	{
		return ( actortype == IS_FRIEND );
	}
	else if( actortype == IS_MONSTER )
	{
		// monsters don't like anyone, but they don't particular hate everyone
		return false;
	}
	else if( ent->isSubclassOf( Actor ) )
	{
		act = ( Actor* )ent;
		return ( act->actortype == actortype );
	}

	return false;
}

qboolean Actor::Hates( Entity* ent )
{
	Actor* act;

	assert( ent );
	if( !ent )
	{
		return false;
	}

	if( ent->isClient() )
	{
		if( ent->flags & FL_SP_MUTANT )
		{
			if( actortype == IS_ENEMY )
				return false;
			else
				return true;
		}
		else
		{
			return ( actortype != IS_CIVILIAN ) && ( actortype != IS_FRIEND );
		}
	}
	else if( ent->isSubclassOf( Actor ) && ( actortype != IS_INANIMATE ) )
	{
		act = ( Actor* )ent;
//      if ( act->actortype == IS_INANIMATE )
//         {
//         // heh... Mutants hate inanimate objects. :)
//         return ( actortype == IS_MONSTER );
//         }
		if( ( act->actortype <= IS_ENEMY ) && ( actortype <= IS_ENEMY ) )
		{
			return false;
		}
		if( ( act->actortype == IS_FRIEND ) && ( actortype <= IS_ENEMY ) )
		{
			return true;
		}
		if( ( act->actortype <= IS_ENEMY ) && ( actortype == IS_FRIEND ) )
		{
			return true;
		}
	}

	return false;
}

//***********************************************************************************************
//
// Targeting functions
//
//***********************************************************************************************

qboolean Actor::GetVisibleTargets( void )
{
	Sentient*   ent;
	Vector delta;
	int i;
	int n;

	targetList.ClearObjectList();
	nearbyList.ClearObjectList();

	n = SentientList.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		ent = SentientList.ObjectAt( i );

		//if ( ( ent == this ) || ent->deadflag || ( ent->flags & FL_NOTARGET ) || !Hates( ent ) )
		if( ( ent == this ) || ( ent->flags & FL_NOTARGET ) || ent->hidden() )
		{
			continue;
		}

		if( WithinDistance( ent, vision_distance ) && CanSeeFOV( ent ) )
		{
			targetList.AddObject( EntityPtr( ent ) );
			if( WithinDistance( ent, 96 ) )
			{
				nearbyList.AddObject( EntityPtr( ent ) );
			}
		}
	}

	return ( targetList.NumObjects() > 0 );
}

void Actor::TargetEnemies( Event* ev )
{
	int i;
	int n;
	Entity* ent;
	Entity* newtarget;
	Actor* act;

	if( actortype == IS_INANIMATE )
	{
		// inanimate objects don't need to worry about this kind of thing
		return;
	}

	if( enemyList.NumObjects() >= 1 )
	{
		// don't target new enemies as much while we've already got an enemy
		PostEvent( EV_Actor_TargetEnemies, 5 );
	}
	else
	{
		PostEvent( EV_Actor_TargetEnemies, 1 );
	}

	if( hidden() )
	{
		// don't target while hidden (for cinematic characters)
		//FIXME
		// probably should have a start/stop function
		return;
	}

	if( GetVisibleTargets() )
	{
		n = targetList.NumObjects();
		for( i = 1; i <= n; i++ )
		{
			ent = targetList.ObjectAt( i );
			if( ent )
			{
				if( ent->flags & ( FL_CLOAK | FL_STEALTH ) )
					continue;

				if( !ent->deadflag && Hates( ent ) && !IsEnemy( ent ) )
				{
					MakeEnemy( ent );
				}
				else if( ent->isSubclassOf( Actor ) && Likes( ent ) )
				{
					act = ( Actor* )ent;
					if( act->currentEnemy && Hates( act->currentEnemy ) && !IsEnemy( act->currentEnemy ) )
					{
						MakeEnemy( act->currentEnemy );
						if( act->deadflag )
						{
							//
							// we have passed on the post mortem message of our death, so let's clear it
							//
							act->ClearEnemies();
						}
					}
				}
			}
		}
	}

	newtarget = BestTarget();
	if( newtarget && ( newtarget != currentEnemy ) )
	{
		seenEnemy = false;
		currentEnemy = newtarget;
		if( DoAction( "sightenemy" ) )
		{
			seenEnemy = true;
			Chatter( "snd_sightenemy", 5 );
		}
		else
		{
			currentEnemy = NULL;
		}
	}
}

Entity* Actor::BestTarget( void )
{
	int i;
	int n;
	Entity* ent;
	Entity* bestent;
	float bestscore;
	float score;

	bestscore = 8192 * 8192 * 20;
	n = enemyList.NumObjects();
	if( n == 1 )
	{
		// don't waste our time when we only have one enemy
		return enemyList.ObjectAt( 1 );
	}

	bestent = NULL;
	for( i = 1; i <= n; i++ )
	{
		ent = enemyList.ObjectAt( i );
		if( !ent || ent->deadflag )
		{
			enemyList.RemoveObjectAt( i );
			i--;
			n--;
			continue;
		}

		score = Range( ent ) + 1;
		if( ent->health < WEAK_HEALTH )
		{
			// Try to kill off really weak enemies
			score *= WEAK_WEIGHT;
		}

		if( ent->health > health )
		{
			score *= STRONGER_WEIGHT;
		}

		if( CanSeeFOV( ent ) )
		{
			// We're more interested in guys we can see
			score *= VISIBLE_WEIGHT;
		}

		if( i == n )
		{
			// Favor the latest enemy
			score *= NEWENEMY_WEIGHT;
		}

		if( score < bestscore )
		{
			bestscore = score;
			bestent = ent;
		}
	}

	return bestent;
}

Sentient* Actor::NearFriend( void )
{
	int i;
	int num;
	Entity* ent;

	num = nearbyList.NumObjects();
	for( i = 1; i < num; i++ )
	{
		ent = nearbyList.ObjectAt( i );
		if( Likes( ent ) )
		{
			return ( Sentient* )ent;
		}
	}

	return NULL;
}

qboolean Actor::CloseToEnemy( const Vector& pos, float howclose )
{
	Entity* ent;
	int i;
	int n;

	n = enemyList.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		ent = enemyList.ObjectAt( i );
		if( !ent || ent->deadflag || ( ent->flags & FL_NOTARGET ) )
		{
			continue;
		}

		if( WithinDistance( ent, howclose ) )
		{
			return true;
		}
	}

	return false;
}

void Actor::EyeOffset( Event* ev )
{
	eyeposition -= eyeoffset;
	eyeoffset = ev->GetVector( 1 );
	eyeposition += eyeoffset;
}

//***********************************************************************************************
//
// State control functions
//
//***********************************************************************************************

void Actor::EnableState( str action )
{
	StateInfo* ptr;

	ptr = GetState( action );
	if( ptr )
	{
		ptr->ignore = false;
	}
}

void Actor::DisableState( str action )
{
	StateInfo* ptr;

	ptr = GetState( action );
	if( ptr )
	{
		ptr->ignore = true;
	}
}

StateInfo* Actor::SetResponse( str action, str response, qboolean ignore )
{
	StateInfo* ptr;
	ptr = GetState( action );
	if( !ptr )
	{
		ptr = new StateInfo;
		actionList.AddObject( ptr );
		ptr->action = action;
	}
	ptr->response = response;
	ptr->ignore = ignore;
	return ptr;
}

const char* Actor::GetResponse( str action, qboolean force )
{
	StateInfo* ptr;
	ptr = GetState( action );
	if( ptr && ( force || !ptr->ignore ) )
	{
		return ptr->response.c_str();
	}
	return "";
}

StateInfo* Actor::GetState( str action )
{
	int i;
	int n;
	StateInfo* ptr;

	n = actionList.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		ptr = actionList.ObjectAt( i );
		if( ptr->action == action )
		{
			return ptr;
		}
	}

	return NULL;
}

//***********************************************************************************************
//
// State stack management
//
//***********************************************************************************************

void Actor::ClearStateStack( void )
{
	ActorState* state;
	int n;
	int i;

	while( !stateStack.Empty() )
	{
		state = stateStack.Pop();

		if( state->animDoneEvent )
		{
			delete state->animDoneEvent;
		}

		// delete the old action/response list
		n = state->actionList.NumObjects();
		for( i = n; i >= 1; i-- )
		{
			delete state->actionList.ObjectAt( i );
		}
		state->actionList.ClearObjectList();

		if( state->behavior )
		{
			delete state->behavior;
		}

		if( state->path )
		{
			delete state->path;
		}

		delete state;
	}

	numonstack = 0;
}

qboolean Actor::PopState( void )
{
	ActorState* newstate;
	int n;
	int i;

#ifdef DEBUG_PRINT
	gi.dprintf( "%d Pop:", numonstack );
#endif
	if( !stateStack.Empty() )
	{
		newstate = stateStack.Pop();
		numonstack--;

		state = newstate->name;

#ifdef DEBUG_PRINT
		gi.dprintf( "state '%s' anim '%s'", state.c_str(), newstate->anim.c_str() );
#endif
		if( newstate->anim.length() )
		{
			SetAnim( newstate->anim, newstate->animDoneEvent );
			ChangeAnim();
		}

		SetPath( newstate->path );

#ifdef DEBUG_PRINT
		if( newstate->behavior )
		{
			gi.dprintf( "%s", newstate->behavior->getClassname() );
		}
		else
		{
			gi.dprintf( "NULL behavior" );
		}
		gi.dprintf( "\n" );
#endif

		// NULL out our current thread so that EndBehavior doesn't
		// signal the thread that the behavior ended.
		thread = NULL;
		EndBehavior();

		assert( !behavior );

		// Set the thread after ending the old behavior, but before the new behavior
		if( newstate->thread != -1 )
		{
			thread = Director.GetThread( newstate->thread );

			// Since PopState is called from the thread, we don't need to tell the thread to continue processing.
			// In fact, depending upon the state of the thread when we pushed it onto the stack, we may not want
			// the thread to continue processing (for example, if the last command was a waitFor).  Just restoring the
			// position here will tell the thread to continue executing or wait for an event to occur.
			// If we ever call PopState from outside of a thread, we MUST check to see if the thread should
			// continue execution.
			//FIXME
			// This is probably an actorthread that was removed
			// ADDENDUM: actorThread is now only removed once.  Thread should never be NULL.
			if( thread )
			{
				thread->Restore( &newstate->marker );
			}
		}
		else
		{
			thread = NULL;
		}

		// delete the old action/response list
		n = actionList.NumObjects();
		for( i = n; i >= 1; i-- )
		{
			delete actionList.ObjectAt( i );
		}
		actionList.ClearObjectList();

		// Copy the new action/response list
		n = newstate->actionList.NumObjects();
		for( i = 1; i <= n; i++ )
		{
			actionList.AddObject( newstate->actionList.ObjectAt( i ) );
		}

		assert( !behavior );

		SetBehavior( newstate->behavior, NULL, thread );

		delete newstate;
	}
	else
	{
#ifdef DEBUG_PRINT
		gi.dprintf( "\n" );
#endif
		EndBehavior();

		return false;
	}

	return true;
}

void Actor::PushState( const char* newstate, ScriptThread* newthread, ThreadMarker* marker )
{
	ActorState* oldstate;
	int i;
	int n;

	oldstate = new ActorState;

	// push the old state
#ifdef DEBUG_PRINT
	gi.dprintf( "%d : Pushing old state %s\n", numonstack, state.c_str() );
	if( behavior )
	{
		gi.dprintf( "old behavior %s\n", behavior->getClassname() );
	}
	else
	{
		gi.dprintf( "old behavior NULL\n" );
	}
	gi.dprintf( "new state %s\n", newstate );
#endif

	oldstate->name = state;
	oldstate->anim = animname;
	oldstate->animDoneEvent = animDoneEvent;
	animDoneEvent = NULL;

	oldstate->path = path;

	oldstate->behavior = behavior;

	// newthread must always be the old thread
	assert( newthread );
	oldstate->thread = newthread->ThreadNum();

	assert( marker );
	oldstate->marker = *marker;

	// Copy the action/response list
	n = actionList.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		StateInfo* ptr;
		StateInfo* newobj;

		ptr = actionList.ObjectAt( i );
		newobj = new StateInfo;
		newobj->action      = ptr->action;
		newobj->response    = ptr->response;
		newobj->ignore      = ptr->ignore;
		oldstate->actionList.AddObject( newobj );
	}

	numonstack++;
	stateStack.Push( oldstate );

	// Any SetBehavior following this will delete this behavior, so null it out so that it doesn't happen
	if( behavior )
	{
		behavior->End( *this );
		behavior = NULL;
	}

	state = newstate;
	thread = newthread;
}

//***********************************************************************************************
//
// State control script commands
//
//***********************************************************************************************

void Actor::DefineStateEvent( Event* ev )
{
	const char* action;
	str response;
	str Foo;
	ScriptThread* thread;
	int len;

	action = ev->GetString( 1 );
	response = ev->GetString( 2 );

	// check if we have a filename in the label
	if( !strstr( response.c_str(), "::" ) )
	{
		thread = ev->GetThread();
		if( thread )
		{
			// add filename to the label so that if we jump to another script, our labels are still valid
			Foo = thread->Filename();
			Foo += "::";
			Foo += response;
			response = Foo.c_str();
		}
	}
	else
	{
		// prepend our debug directory name
		Foo = ai_actorscript->string;
		len = Foo.length();

		// if we have a directory, make sure that it ends with a '/' or a '\'
		if( ( len > 0 ) && ( Foo[ len - 1 ] != '/' ) && ( Foo[ len - 1 ] != '\\' ) )
		{
			Foo += "/";
		}
		Foo += response;
		response = Foo.c_str();
	}
	SetResponse( action, response );
}

void Actor::CopyStateEvent( Event* ev )
{
	str action1;
	str action2;
	str response;
	StateInfo* ptr;

	action1 = ev->GetString( 1 );
	action2 = ev->GetString( 2 );

	ptr = GetState( action2 );
	if( ptr )
	{
		response = ptr->response;
	}
	SetResponse( action1, response );
}

void Actor::IgnoreAllEvent( Event* ev )
{
	int i;
	int n;

	n = actionList.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		actionList.ObjectAt( i )->ignore = true;
	}
}

void Actor::IgnoreEvent( Event* ev )
{
	int i;
	int n;

	n = ev->NumArgs();
	for( i = 1; i <= n; i++ )
	{
		DisableState( ev->GetString( i ) );
	}
}

void Actor::RespondToAllEvent( Event* ev )
{
	int i;
	int n;

	n = actionList.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		actionList.ObjectAt( i )->ignore = false;
	}
}

void Actor::RespondToEvent( Event* ev )
{
	int i;
	int n;

	n = ev->NumArgs();
	for( i = 1; i <= n; i++ )
	{
		EnableState( ev->GetString( i ) );
	}
}

void Actor::ClearStateEvent( Event* ev )
{
	ClearStateStack();
}

void Actor::StateDoneEvent( Event* ev )
{
	assert( actorthread == ev->GetThread() );

	if( !deadflag || ( actortype == IS_INANIMATE ) )
	{
		// if we're not dead, pop off the previous state
		PopState();
	}
	else
	{
		// if we're dead, kill off all the previous states
		ClearStateStack();

		// kill off the thread
		actorthread->ProcessEvent( EV_ScriptThread_End );
		actorthread = NULL;
	}
}

void Actor::SetStateEvent( Event* ev )
{
	str response;
	str name;
	StateInfo* ptr;

	if( ( deadflag == DEAD_DEAD ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	name = ev->GetString( 1 );

	// Don't check ignore flag
	ptr = GetState( name );
	if( ptr )
	{
		response = ptr->response;
	}
	else
	{
		response = "";
	}

#ifdef DEBUG_PRINT
	gi.dprintf( "%d : %d : Action: %s - %s\n",
				actorthread->ThreadNum(), ev->GetThread()->ThreadNum(),
				name.c_str(), response.c_str() );
#endif

	// check the existance of the actor's thread to prevent a crash
	if( !actorthread )
	{
		return;
	}

	ThreadMarker marker;
	actorthread->Mark( &marker );
	if( response != "" && actorthread->Goto( response.c_str() ) )
	{
		PushState( name.c_str(), actorthread, &marker );
		SetAnim( "idle" );
		animname = "idle";
		SetVariable( "state", name.c_str() );
		ProcessScript( actorthread );
	}
	else
	{
		ev->Error( "Could not find label '%s'", response.c_str() );
		actorthread->Goto( response.c_str() );
	}
}

void Actor::CanStrafeEvent( Event* ev )
{
	checkStrafe = true;
}

void Actor::NoStrafeEvent( Event* ev )
{
	checkStrafe = false;
}

//***********************************************************************************************
//
// Thread management
//
//***********************************************************************************************

void Actor::SetupThread( void )
{
	str script;
	int len;
	Event* event;

	// we should never have a thread at this point
	assert( !actorthread );

	// prepend our debug director name
	script = ai_actorscript->string;
	len = script.length();

	// if we have a directory, make sure that it ends with a '/' or a '\'
	if( ( len > 0 ) && ( script[ len - 1 ] != '/' ) && ( script[ len - 1 ] != '\\' ) )
	{
		script += "/";
	}
	script += actorscript;
	actorthread = Director.CreateThread( script.c_str(), MODEL_SCRIPT );

	if( actorthread )
	{
		// setup thread variables
		SetVariable( "attackmode", attackmode );

		if( actorstart.length() )
		{
			if( !actorthread->Goto( actorstart.c_str() ) )
			{
				gi.dprintf( "Label '%s' not found in %s.", actorstart.c_str(), script.c_str() );
			}
		}

		ProcessScript( actorthread );
	}

	assert( actorthread );
	if( !actorthread )
	{
		// not having a script is bad
		gi.dprintf( "Unable to start actor script '%s'.  Killing actor '%s'(%d).\n",
					script.c_str(), targetname.c_str(), entnum );

		// just kill him
		event = new Event( EV_Killed );
		event->AddEntity( this );
		event->AddInteger( 0 );
		event->AddEntity( this );
		event->AddString( "all" );
		ProcessEvent( event );

		return;
	}
}

qboolean Actor::DoAction( str name, qboolean force )
{
	str response;

	if( !actorthread )
	{
		return false;
	}

	if( ( deadflag == DEAD_DEAD ) && ( actortype != IS_INANIMATE ) )
	{
		return false;
	}

	response = GetResponse( name, force );

#ifdef DEBUG_PRINT
	gi.dprintf( "Action: %s - %s\n", name.c_str(), response.c_str() );
#endif

	ThreadMarker marker;
	actorthread->Mark( &marker );
	if( response != "" && actorthread->Goto( response.c_str() ) )
	{
		PushState( name.c_str(), actorthread, &marker );
		SetAnim( "idle" );
		animname = "idle";
		SetVariable( "state", name.c_str() );
		ProcessScript( actorthread );
		return true;
	}
	return false;
}

qboolean Actor::ForceAction( str name )
{
	return DoAction( name, true );
}

void Actor::ProcessScript( ScriptThread* thread, Event* ev )
{
	thread->Vars()->SetVariable( "self", this );
	thread->Vars()->SetVariable( "origin", worldorigin );
	thread->Vars()->SetVariable( "yaw", worldangles.y );
	thread->Vars()->SetVariable( "health", health );
	thread->Vars()->SetVariable( "startpos", startpos );
	thread->Vars()->SetVariable( "shots_per_attack", shots_per_attack );
	//
	// see if the enemy lost his weapon
	// i.e. it was shot out of his hands
	//
	if( currentWeapon )
	{
		thread->Vars()->SetVariable( "has_weapon", 1 );
	}
	else
	{
		thread->Vars()->SetVariable( "has_weapon", 0 );
	}


	if( currentEnemy )
	{
		thread->Vars()->SetVariable( "enemy", currentEnemy );
	}
	else
	{
		thread->Vars()->SetVariable( "enemy", "" );
	}

	if( ev )
	{
		thread->ProcessEvent( ev );
	}
	else
	{
		thread->ProcessEvent( EV_ScriptThread_Execute );
	}
}

void Actor::StartMove( Event* ev )
{
	if( deadflag )
	{
		return;
	}

//	thread = ev->GetThread();
}

inline ScriptVariable* Actor::SetVariable( const char* name, float value )
{
	if( actorthread )
	{
		return actorthread->Vars()->SetVariable( name, value );
	}

	return NULL;
}

inline ScriptVariable* Actor::SetVariable( const char* name, int value )
{
	if( actorthread )
	{
		return actorthread->Vars()->SetVariable( name, value );
	}

	return NULL;
}

/*inline*/ ScriptVariable* Actor::SetVariable( const char* name, const char* text )
{
	if( actorthread )
	{
		return actorthread->Vars()->SetVariable( name, text );
	}

	return NULL;
}

inline ScriptVariable* Actor::SetVariable( const char* name, Entity* ent )
{
	if( actorthread )
	{
		return actorthread->Vars()->SetVariable( name, ent );
	}

	return NULL;
}

inline ScriptVariable* Actor::SetVariable( const char* name, const Vector& vec )
{
	if( actorthread )
	{
		return actorthread->Vars()->SetVariable( name, vec );
	}

	return NULL;
}

//***********************************************************************************************
//
// Thread based script commands
//
//***********************************************************************************************

void Actor::SetScript( Event* ev )
{
	str script;
	str label;
	int len;

	actorscript = ev->GetString( 1 );

	// gotta start the thread again!
	if( actorthread )
	{
		// prepend our debug directory name
		script = ai_actorscript->string;
		len = script.length();

		// if we have a directory, make sure that it ends with a '/' or a '\'
		if( ( len > 0 ) && ( script[ len - 1 ] != '/' ) && ( script[ len - 1 ] != '\\' ) )
		{
			script += "/";
		}

		script += actorscript;
		if( !actorthread->SetScript( script.c_str() ) )
		{
			ev->Error( "Script '%s' not found", script.c_str() );
		}
		else
		{
			// setup thread variables
			SetVariable( "attackmode", attackmode );

			ProcessScript( actorthread );
		}
	}
}

void Actor::SetThread( Event* ev )
{
	ScriptThread* thread;

	if( ( deadflag == DEAD_DEAD ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	thread = ev->GetThread();
	actorstart = ev->GetString( 1 );

	// get the name of the script
	if( thread )
	{
		actorstart = str( thread->Filename() ) + "::" + actorstart;
	}

	// we may be getting this command from the .def file, so the thread may not have started yet
	if( actorthread && actorstart.length() )
	{
		// setup thread variables
		SetVariable( "attackmode", attackmode );

		if( actorthread->Goto( actorstart.c_str() ) )
		{
			ProcessScript( actorthread );
		}
		else
		{
			ev->Error( "Label '%s' not found.  Couldn't change thread.", actorstart.c_str() );
		}
	}
}

//***********************************************************************************************
//
// Behavior management
//
//***********************************************************************************************

void Actor::EndBehavior( void )
{
	ScriptThread* t;
	Event* event;

	if( behavior )
	{
		behavior->End( *this );
		delete behavior;
		behavior = NULL;

		if( thread )
		{
			t = thread;
			thread = NULL;
			event = new Event( EV_MoveDone );
			event->AddEntity( this );
#if 0
			if( t == actorthread )
			{
				ProcessScript( actorthread, event );
			}
			else
			{
				t->ProcessEvent( event );
			}
#else
			ProcessScript( actorthread, event );
			if( t != actorthread )
			{
				event = new Event( EV_MoveDone );
				event->AddEntity( this );
				t->ProcessEvent( event );
			}
#endif
		}
	}
}

void Actor::SetBehaviorEvent( Event* ev )
{
	ClassDef* cls;
	ScriptThread* thread;
	Event* e;
	str name;
	int i;
	int n;

	name = ev->GetString( 1 );
	if( !checkInheritance( &Behavior::ClassInfo, name.c_str() ) )
	{
		ev->Error( "%s is not a valid behavior\n", name.c_str() );
		return;
	}

	thread = ev->GetThread();

	e = new Event( EV_Behavior_Args );
	e->SetSource( EV_FROM_SCRIPT );
	e->SetThread( thread );
	e->SetLineNumber( ev->GetLineNumber() );

	e->AddEntity( this );

	n = ev->NumArgs();
	for( i = 2; i <= n; i++ )
	{
		e->AddToken( ev->GetToken( i ) );
	}

	cls = getClass( name.c_str() );
	SetBehavior( ( Behavior* )cls->newInstance(), e, thread );
}

void Actor::SetBehavior( Behavior* newbehavior, Event* startevent, ScriptThread* newthread )
{
	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	// End any previous behavior, but don't call EV_MoveDone if we're using the same thread,
	// or we'll end THIS behavior
	if( thread == newthread )
	{
		thread = NULL;
	}
	EndBehavior();

	behavior = newbehavior;
	if( behavior )
	{
		if( actortype == IS_INANIMATE )
		{
			// think while we have a behavior
			flags |= FL_PRETHINK;
		}

#ifdef DEBUG_PRINT
		gi.dprintf( "SetBehavior %s\n", behavior->getClassname() );
#endif
		if( startevent )
		{
			behavior->ProcessEvent( startevent );
		}
		currentBehavior = behavior->getClassname();
		behavior->Begin( *this );
		thread = newthread;
	}
}

void Actor::FinishedBehavior( Event* ev )
{
	EndBehavior();
}

void Actor::NotifyBehavior( Event* ev )
{
	if( behavior )
	{
		behavior->ProcessEvent( EV_Behavior_AnimDone );
	}
}

void Actor::ForwardBehaviorEvent( Event* ev )
{
	ScriptThread* thread;
	Event* e;
	str name;
	int i;
	int n;

	name = ev->GetString( 1 );

	thread = ev->GetThread();
	e = new Event( name );
	e->SetSource( EV_FROM_SCRIPT );
	e->SetThread( thread );
	e->SetLineNumber( ev->GetLineNumber() );

	// always add who got this event
	e->AddEntity( this );
	n = ev->NumArgs();
	for( i = 2; i <= n; i++ )
	{
		e->AddToken( ev->GetToken( i ) );
	}

	if( behavior )
		behavior->ProcessEvent( e );
	else
		warning( "ForwardBehaviorEvent", "no behavior defined" );
}

//***********************************************************************************************
//
// Path and node management
//
//***********************************************************************************************

void Actor::SetPath( Path* newpath )
{
	if( path && ( path != newpath ) )
	{
		delete path;
	}
	path = newpath;
}

void Actor::ReserveNodeEvent( Event* ev )
{
	PathNode* node;
	Vector pos;

	pos = ev->GetVector( 1 );
	node = PathManager.NearestNode( pos, this );

	if( node && ( !node->entnum || ( node->entnum == entnum ) || ( node->occupiedTime < level.time ) ) )
	{
		// Mark node as occupied for a short time
		node->occupiedTime = level.time + ev->GetFloat( 2 );
		node->entnum = entnum;
	}
}

void Actor::ReleaseNodeEvent( Event* ev )
{
	PathNode* node;
	Vector pos;

	pos = ev->GetVector( 1 );
	node = PathManager.NearestNode( pos, this );

	if( node && ( node->entnum == entnum ) )
	{
		node->occupiedTime = 0;
		node->entnum = 0;
	}
}

//***********************************************************************************************
//
// Animation control functions
//
//***********************************************************************************************

void Actor::ChangeAnim( void )
{
	float time;
	Vector totalmove;

	if( newanimnum == -1 )
	{
		return;
	}

	// If we're changing to the same anim, don't restart the animation
	if( animating && newanimnum == edict->s.anim )
	{
		if( animDoneEvent )
		{
			delete animDoneEvent;
		}

		animDoneEvent = newanimevent;
	}
	else
	{
		StopAnimating();

		animname = newanim;

		time = gi.Anim_Time( edict->s.modelindex, newanimnum );
		gi.Anim_Delta( edict->s.modelindex, newanimnum, totalmove.vec3() );

		totalmove[ 1 ] = -totalmove[ 1 ];
		totalmove *= edict->s.scale;
		totallen = totalmove.length();

		// always have a valid move direction
		if( totallen > 0.01 )
		{
			movespeed = totallen / time;
			animdir = totalmove * ( 1 / totallen );
		}
		else
		{
			if( forwardspeed )
			{
				movespeed = forwardspeed;
			}
			else
			{
				movespeed = 1;
			}
			animdir = Vector( 1, 0, 0 );
		}

		MatrixTransformVector( animdir.vec3(), orientation, movedir.vec3() );
		movevelocity = movedir * movespeed;

		NextAnim( newanimnum );
		animDoneEvent = newanimevent;
		StartAnimating();
	}

	// clear the new anim variables
	newanimnum = -1;
	newanim = "";
	newanimevent = NULL;
}

qboolean Actor::SetAnim( str anim, Event* ev )
{
	int num;

	// FIXME
	// HACK to make actors use alert when fighting instead of idle
	if( hasalert && currentEnemy && ( anim == "idle" ) )
	{
		anim = "alert";
	}

	num = gi.Anim_Random( edict->s.modelindex, anim.c_str() );
	if( num != -1 )
	{
		newanim = anim;
		newanimnum = num;
		newanimevent = ev;

		if( actortype == IS_INANIMATE )
		{
			// inanimate objects change anims immediately
			ChangeAnim();
		}
		return true;
	}

	// kill the event
	if( ev )
	{
		delete ev;
	}

	return false;
}

qboolean Actor::SetAnim( str anim, Event& ev )
{
	Event* event;

	event = new Event( ev );
	return SetAnim( anim, event );
}

void Actor::Anim( Event* ev )
{
	Event* e;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	e = new Event( EV_Behavior_Args );
	e->SetSource( EV_FROM_SCRIPT );
	e->SetThread( ev->GetThread() );
	e->SetLineNumber( ev->GetLineNumber() );

	e->AddEntity( this );
	e->AddToken( ev->GetToken( 1 ) );

	SetBehavior( new PlayAnim, e, ev->GetThread() );
}

//***********************************************************************************************
//
// Script commands
//
//***********************************************************************************************

void Actor::CrouchSize( Event* ev )
{
	crouchsize_min = ev->GetVector( 1 );
	crouchsize_max = ev->GetVector( 2 );
}

void Actor::SetFov( Event* ev )
{
	fov = ev->GetFloat( 1 );
	fovdot = cos( fov * 0.5 * M_PI / 180.0 );
}

void Actor::SetVisionDistance( Event* ev )
{
	vision_distance = ev->GetFloat( 1 );
}

void Actor::LookAt( Event* ev )
{
	Entity* ent;
	TurnTo* turnTo;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	ent = ev->GetEntity( 1 );
	if( ent && ent != world )
	{
		turnTo = new TurnTo;
		turnTo->SetTarget( ent );
		SetBehavior( turnTo, NULL, ev->GetThread() );
	}
}

void Actor::TurnToEvent( Event* ev )
{
	TurnTo* turnTo;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	turnTo = new TurnTo;
	turnTo->SetDirection( ev->GetFloat( 1 ) );

	SetBehavior( turnTo, NULL, ev->GetThread() );
}

void Actor::IdleEvent( Event* ev )
{
	Event* e;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	e = new Event( EV_Behavior_Args );
	e->SetSource( EV_FROM_SCRIPT );
	e->SetThread( ev->GetThread() );
	e->SetLineNumber( ev->GetLineNumber() );

	e->AddEntity( this );
	e->AddString( ev->GetToken( 1 ) );
	SetBehavior( new Idle, e, ev->GetThread() );
}

void Actor::WalkTo( Event* ev )
{
	Event* e;
	int i;
	int n;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	e = new Event( EV_Behavior_Args );
	e->SetSource( EV_FROM_SCRIPT );
	e->SetThread( ev->GetThread() );
	e->SetLineNumber( ev->GetLineNumber() );

	e->AddEntity( this );

	n = ev->NumArgs();

	e->AddString( "walk" );
	for( i = 1; i <= n; i++ )
	{
		e->AddToken( ev->GetToken( i ) );
	}

	SetBehavior( new GotoPathNode, e, ev->GetThread() );
}

void Actor::RunTo( Event* ev )
{
	Event* e;
	int i;
	int n;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	e = new Event( EV_Behavior_Args );
	e->SetSource( EV_FROM_SCRIPT );
	e->SetThread( ev->GetThread() );
	e->SetLineNumber( ev->GetLineNumber() );

	e->AddEntity( this );

	n = ev->NumArgs();

	e->AddString( "run" );
	for( i = 1; i <= n; i++ )
	{
		e->AddToken( ev->GetToken( i ) );
	}

	SetBehavior( new GotoPathNode, e, ev->GetThread() );
}

void Actor::AttackEntity( Event* ev )
{
	Entity* ent;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	ent = ev->GetEntity( 1 );
	// don't get mad at things that can't be hurt or the world
	if(
		ent &&
		( ent != world ) &&
		( ent->takedamage != DAMAGE_NO )
	)
	{
		ClearEnemies();
		MakeEnemy( ent, true );
	}
}

void Actor::AttackPlayer( Event* ev )
{
	int i;
	edict_t* ent;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	ClearEnemies();

	// make enemies of all the players
	for( i = 0; i < maxclients->value; i++ )
	{
		ent = &g_edicts[ i + 1 ];
		if( !ent->inuse || !ent->client || !ent->entity )
		{
			continue;
		}

		MakeEnemy( ent->entity, true );
	}

	currentEnemy = BestTarget();
	if( state != "sightenemy" )
	{
		if( ForceAction( "sightenemy" ) )
		{
			seenEnemy = true;
			Chatter( "snd_sightenemy", 5 );
		}
	}
}

void Actor::JumpToEvent( Event* ev )
{
	Event* e;
	int i;
	int n;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	e = new Event( EV_Behavior_Args );
	e->SetSource( EV_FROM_SCRIPT );
	e->SetThread( ev->GetThread() );
	e->SetLineNumber( ev->GetLineNumber() );

	e->AddEntity( this );

	n = ev->NumArgs();

	e->AddString( "jump" );
	for( i = 1; i <= n; i++ )
	{
		e->AddToken( ev->GetToken( i ) );
	}

	SetBehavior( new Jump, e, ev->GetThread() );
}

void Actor::GotoEvent( Event* ev )
{
	// This command was added because it was a common mistake when writing actor scripts
	// to say "local.self goto label".  Since we're such nice guys, we'll print a warning
	// and send it on the the calling thread. :)
	ScriptThread* thread;

	thread = ev->GetThread();
	if( thread )
	{
		gi.dprintf( "Actor recieved 'goto' command.  Passing to thread.\n" );
		thread->ProcessEvent( ev );
	}
	else
	{
		// should never happen, but say something anyways.
		gi.dprintf( "Actor recieved 'goto' command.  Thread is NULL.\n" );
	}
}

//***********************************************************************************************
//
// Script conditionals
//
//***********************************************************************************************

void Actor::IfEnemyVisibleEvent( Event* ev )
{
	ScriptThread* thread;

	thread = ev->GetThread();
	assert( thread );
	if( !thread )
	{
		return;
	}

	if( CanSeeEnemyFrom( worldorigin ) )
	{
		thread->ProcessCommandFromEvent( ev, 1 );
	}
}

void Actor::IfNearEvent( Event* ev )
{
	ScriptThread*   thread;
	Entity*         ent;
	Entity*         bestent;
	float bestdist;
	float dist;
	str name;
	Vector delta;
	float distance;
	TargetList*     tlist;
	int n;
	int i;

	thread = ev->GetThread();
	assert( thread );
	if( !thread )
	{
		return;
	}

	name = ev->GetString( 1 );
	distance = ev->GetFloat( 2 );

	if( name[ 0 ] == '*' )
	{
		ent = ev->GetEntity( 1 );
		if( WithinDistance( ent, distance ) )
		{
			SetVariable( "other", ent );
			thread->ProcessCommandFromEvent( ev, 3 );
		}
	}
	else if( name[ 0 ] == '$' )
	{
		bestent = NULL;
		bestdist = distance * distance;

		tlist = world->GetTargetList( str( &name[ 1 ] ) );
		n = tlist->list.NumObjects();
		for( i = 1; i <= n; i++ )
		{
			ent = tlist->list.ObjectAt( i );
			delta = centroid - ent->centroid;
			dist = delta * delta;
			if( dist <= bestdist )
			{
				bestent = ent;
				bestdist = dist;
			}
		}

		if( bestent )
		{
			SetVariable( "other", bestent );
			thread->ProcessCommandFromEvent( ev, 3 );
		}
	}
	else
	{
		bestent = NULL;
		bestdist = distance * distance;

		ent = NULL;
		while( ( ent = findradius( ent, worldorigin.vec3(), distance ) ) )
		{
			if( ent->inheritsFrom( name.c_str() ) )
			{
				delta = centroid - ent->centroid;
				dist = delta * delta;
				if( dist <= bestdist )
				{
					bestent = ent;
					bestdist = dist;
				}
			}
		}

		if( bestent )
		{
			SetVariable( "other", bestent );
			thread->ProcessCommandFromEvent( ev, 3 );
		}
	}
}

void Actor::IfCanHideAtEvent( Event* ev )
{
	PathNode* node;
	Vector pos;
	ScriptThread* thread;

	thread = ev->GetThread();
	assert( thread );
	if( !thread )
	{
		return;
	}

	pos = ev->GetVector( 1 );
	node = PathManager.NearestNode( pos, this );

	if( node && ( node->nodeflags & ( AI_DUCK | AI_COVER ) ) && !CanSeeEnemyFrom( pos ) )
	{
		if( !node->entnum || ( node->entnum == entnum ) || ( node->occupiedTime < level.time ) )
		{
			thread->ProcessCommandFromEvent( ev, 2 );
		}
	}
}

void Actor::IfCanStrafeAttackEvent( Event* ev )
{
	ScriptThread* thread;
	int num;
	Vector delta;
	Vector left;
	Vector pos;

	if( !checkStrafe )
		return;

	if( !currentEnemy )
	{
		return;
	}

	thread = ev->GetThread();
	assert( thread );
	if( !thread )
	{
		return;
	}

	delta = currentEnemy->worldorigin - worldorigin;
	left.x = -delta.y;
	left.y = delta.x;
	left.normalize();

	num = gi.Anim_Random( edict->s.modelindex, "step_left" );
	if( num != -1 )
	{
		gi.Anim_Delta( edict->s.modelindex, num, delta.vec3() );
		delta *= edict->s.scale;
		pos = worldorigin + left * delta.length();
		if( CanMoveTo( pos ) && CanShootFrom( pos, currentEnemy, false ) )
		{
			thread->ProcessCommandFromEvent( ev, 1 );
			return;
		}
	}

	num = gi.Anim_Random( edict->s.modelindex, "step_right" );
	if( num != -1 )
	{
		gi.Anim_Delta( edict->s.modelindex, num, delta.vec3() );
		delta *= edict->s.scale;
		pos = worldorigin - left * delta.length();
		if( CanMoveTo( pos ) && CanShootFrom( pos, currentEnemy, false ) )
		{
			thread->ProcessCommandFromEvent( ev, 1 );
			return;
		}
	}
}


void Actor::IfCanMeleeAttackEvent( Event* ev )
{
	ScriptThread* thread;
	Vector delta;
	float r;

	if( !currentEnemy || !has_melee )
	{
		return;
	}

	thread = ev->GetThread();
	assert( thread );
	if( !thread )
	{
		return;
	}

	delta = centroid - currentEnemy->centroid;
	r = delta.length();
	if( r <= melee_range )
	{
		thread->ProcessCommandFromEvent( ev, 1 );
	}
}

void Actor::IfCanShootEvent( Event* ev )
{
	ScriptThread* thread;

	if( !currentEnemy )
	{
		return;
	}

	thread = ev->GetThread();
	assert( thread );
	if( !thread )
	{
		return;
	}

	if( CanShoot( currentEnemy, false ) )
	{
		thread->ProcessCommandFromEvent( ev, 1 );
	}
}

void Actor::IfEnemyWithinEvent( Event* ev )
{
	ScriptThread* thread;

	if( !currentEnemy )
	{
		return;
	}

	thread = ev->GetThread();
	assert( thread );
	if( !thread )
	{
		return;
	}

	if( WithinDistance( currentEnemy, ev->GetFloat( 1 ) ) )
	{
		thread->ProcessCommandFromEvent( ev, 2 );
	}
}

//***********************************************************************************************
//
// Sound reaction functions
//
//***********************************************************************************************

void Actor::IgnoreSoundsEvent( Event* ev )
{
	DisableState( "weaponsound" );
	DisableState( "movementsound" );
	DisableState( "painsound" );
	DisableState( "deathsound" );
	DisableState( "breakingsound" );
	DisableState( "doorsound" );
	DisableState( "mutantsound" );
	DisableState( "voicesound" );
	DisableState( "machinesound" );
	DisableState( "radiosound" );
}

void Actor::RespondToSoundsEvent( Event* ev )
{
	EnableState( "weaponsound" );
	EnableState( "movementsound" );
	EnableState( "painsound" );
	EnableState( "deathsound" );
	EnableState( "breakingsound" );
	EnableState( "doorsound" );
	EnableState( "mutantsound" );
	EnableState( "voicesound" );
	EnableState( "machinesound" );
	EnableState( "radiosound" );
}

void Actor::InvestigateWeaponSound( Event* ev )
{
	Vector location;
	Entity* other;

	if( !currentEnemy && !deadflag && ( nextsoundtime < level.time ) )
	{
		other = ev->GetEntity( 1 );
		location = ev->GetVector( 2 );
		SetVariable( "other", other );
		SetVariable( "location", location );
		if( DoAction( "weaponsound" ) )
		{
			nextsoundtime = level.time + 2;
		}
	}
}

void Actor::InvestigateMovementSound( Event* ev )
{
	Vector location;
	Entity* other;

	other = ev->GetEntity( 1 );
	if( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) )
	{
		location = ev->GetVector( 2 );
		SetVariable( "other", other );
		SetVariable( "location", location );
		if( DoAction( "movementsound" ) )
			nextsoundtime = level.time + 2;
	}
}

void Actor::InvestigatePainSound( Event* ev )
{
	Vector location;
	Entity* other;

	if( !currentEnemy && !deadflag && ( nextsoundtime < level.time ) )
	{
		other = ev->GetEntity( 1 );
		location = ev->GetVector( 2 );
		SetVariable( "other", other );
		SetVariable( "location", location );
		if( DoAction( "painsound" ) )
			nextsoundtime = level.time + 2;
	}
}

void Actor::InvestigateDeathSound( Event* ev )
{
	Vector location;
	Entity* other;

	if( !currentEnemy && !deadflag && ( nextsoundtime < level.time ) )
	{
		other = ev->GetEntity( 1 );
		location = ev->GetVector( 2 );
		SetVariable( "other", other );
		SetVariable( "location", location );
		if( DoAction( "deathsound" ) )
			nextsoundtime = level.time + 2;
	}
}

void Actor::InvestigateBreakingSound( Event* ev )
{
	Vector location;
	Entity* other;

	if( !currentEnemy && !deadflag && ( nextsoundtime < level.time ) )
	{
		other = ev->GetEntity( 1 );
		location = ev->GetVector( 2 );
		SetVariable( "other", other );
		SetVariable( "location", location );
		if( DoAction( "breakingsound" ) )
			nextsoundtime = level.time + 2;
	}
}

void Actor::InvestigateDoorSound( Event* ev )
{
	Vector location;
	Entity* other;

	other = ev->GetEntity( 1 );
	if( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) )
	{
		location = ev->GetVector( 2 );
		SetVariable( "other", other );
		SetVariable( "location", location );
		if( DoAction( "doorsound" ) )
			nextsoundtime = level.time + 2;
	}
}

void Actor::InvestigateMutantSound( Event* ev )
{
	Vector location;
	Entity* other;

	other = ev->GetEntity( 1 );
	if( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) )
	{
		location = ev->GetVector( 2 );
		SetVariable( "other", other );
		SetVariable( "location", location );
		if( DoAction( "mutantsound" ) )
			nextsoundtime = level.time + 2;
	}
}

void Actor::InvestigateVoiceSound( Event* ev )
{
	Vector location;
	Entity* other;

	other = ev->GetEntity( 1 );
	if( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) )
	{
		location = ev->GetVector( 2 );
		SetVariable( "other", other );
		SetVariable( "location", location );
		if( DoAction( "voicesound" ) )
			nextsoundtime = level.time + 2;
	}
}

void Actor::InvestigateMachineSound( Event* ev )
{
	Vector location;
	Entity* other;

	other = ev->GetEntity( 1 );
	if( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) )
	{
		location = ev->GetVector( 2 );
		SetVariable( "other", other );
		SetVariable( "location", location );
		if( DoAction( "machinesound" ) )
			nextsoundtime = level.time + 2;
	}
}

void Actor::InvestigateRadioSound( Event* ev )
{
	Vector location;
	Entity* other;

	other = ev->GetEntity( 1 );
	if( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) )
	{
		location = ev->GetVector( 2 );
		SetVariable( "other", other );
		SetVariable( "location", location );
		if( DoAction( "radiosound" ) )
			nextsoundtime = level.time + 2;
	}
}

//***********************************************************************************************
//
// Pain and death related functions
//
//***********************************************************************************************

void Actor::Pain( Event* ev )
{
	float damage;
	Entity* ent;
	float oldhealth;
	float newhealth;

#ifdef DEBUG_PRINT
	gi.dprintf( "Pain\n" );
#endif

	damage = ev->GetFloat( 1 );
	ent = ev->GetEntity( 2 );

	// if it's a Sentient and not liked, attack 'em.
	if( ent && ent->isSubclassOf( Sentient ) && !Likes( ent ) )
	{
		MakeEnemy( ent );
		if( ent != currentEnemy )
		{
			currentEnemy = BestTarget();
		}
	}

	if( damage <= 0 )
	{
		return;
	}

	oldhealth = ( health + damage ) / max_health;
	newhealth = health / max_health;

	SetVariable( "other", ev->GetEntity( 2 ) );

	// If we pass more than one range,
	if( ( oldhealth > 0.75 ) && ( newhealth <= 0.75 ) )
	{
		DoAction( "health_ok" );
	}
	if( ( oldhealth > 0.5 ) && ( newhealth <= 0.5 ) )
	{
		DoAction( "health_med" );
	}
	if( ( oldhealth > 0.25 ) && ( newhealth <= 0.25 ) )
	{
		DoAction( "health_low" );
	}
	if( ( oldhealth > 0.1 ) && ( newhealth <= 0.1 ) )
	{
		DoAction( "health_danger" );
	}

	if( damage <= pain_threshold )
	{
		Chatter( "snd_pain_taunt", 5, true );
		return;
	}

	if( strncmp( animname.c_str(), "pain", 4 ) && strncmp( animname.c_str(), "crouch_pain", 11 ) )
	{
		str aname;
		int index;

		//
		// determine pain animation
		//
		if( !strncmp( animname.c_str(), "crouch", 6 ) )
		{
			aname = "crouch_";
		}
		aname += str( "pain_" );
		aname += str( ev->GetString( 3 ) );
		index = gi.Anim_Random( edict->s.modelindex, aname.c_str() );
		if( ( index == -1 ) && !strncmp( animname.c_str(), "crouch", 6 ) )
		{
			aname = "crouch_pain";
			index = gi.Anim_Random( edict->s.modelindex, aname.c_str() );
		}

		if( index == -1 )
		{
			aname = "pain";
		}

		SetVariable( "painanim", aname.c_str() );
		DoAction( "pain" );
	}
}

void Actor::Dead( Event* ev )
{
	Vector min, max;
	Event*    event;

	StopAnimating();
	if( !groundentity && ( velocity != vec_zero ) )
	{
		// wait until we hit the ground
		PostEvent( ev, FRAMETIME );
		return;
	}

	//
	// drop anything that might be attached to us
	//
	if( numchildren )
	{
		Entity* child;
		int i;
		//
		// detach all our children
		//
		for( i = 0; i < MAX_MODEL_CHILDREN; i++ )
		{
			if( children[ i ] )
			{
				child = ( Entity* )G_GetEntity( children[ i ] );
				child->ProcessEvent( EV_Detach );
			}
		}
	}

	deadflag = DEAD_DEAD;
	maxs[0] *= 2.0f;
	maxs[1] *= 2.0f;
	maxs[2] *= 0.3f;
	setSize( mins, maxs );
	edict->svflags |= SVF_DEADMONSTER;
	setMoveType( MOVETYPE_NONE );
	setOrigin( worldorigin );

	if( deathgib )
	{
		// Put down a bloodsplat
		Vector start, end, dir, norm, ang;
		float scale;
		BloodSplat*  splat;
		trace_t trace;

		dir   = Vector( 0, 0, -1 );
		start = centroid;
		end   = start + 100 * dir;

		trace = G_Trace( start, vec_zero, vec_zero, end, this, MASK_SOLIDNONFENCE, "Actor::Dead" );

		if( !( HitSky( &trace ) || ( trace.ent->solid != SOLID_BSP ) || ( trace.ent->s.number != 0 ) ) )
		{
			scale = G_Random( 1.2 );
			if( scale < 0.5 )
				scale = 0.5;
			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 );
		}
	}

	// If this body has nothing interesting, then remove it
	if( !NumInventoryItems() )
	{
		if( nodeathfade )
			return;

		PostEvent( EV_FadeOut, 10 );
		return;
	}

	// Spawn a touch field
	min.x = worldorigin.x - 32;
	min.y = worldorigin.y - 32;
	min.z = worldorigin.z - 32;
	max.x = worldorigin.x + 32;
	max.y = worldorigin.y + 32;
	max.z = worldorigin.z + 32;
	trig = new TouchField;
	trig->Setup( this, EV_Touch, min, max, TRIGGER_PLAYERS );

	assert( trig->edict->solid == SOLID_TRIGGER );

	if( !nodeathfade )
	{
		// Check in the future for uselessness
		event = new Event( EV_Sentient_UselessCheck );
		PostEvent( event, 1.0f + G_Random() );

		// If this guy has no InventoryItems, then fade him in 1 minute
		if( !HasInventoryOfType( "InventoryItem" ) )
		{
			PostEvent( EV_Actor_Remove, 60.0f );
		}
	}
}

void Actor::Killed( Event* ev )
{
	const char* name;
	Entity*     ent;
	int num;
	Entity*     attacker;
	Entity*     inflictor;
	Vector dir;
	Event*          event;
	int i;
	str dname;
	int meansofdeath;

	CheckWater();
	StopAnimating();
	CancelPendingEvents();

	// don't allow them to fly, think, or swim anymore
	flags &= ~( FL_PRETHINK | FL_SWIM | FL_FLY );

	deadflag = DEAD_DYING;
	takedamage = DAMAGE_YES;
	groundentity = NULL;

	attacker    = ev->GetEntity( 1 );
	inflictor = ev->GetEntity( 3 );
	meansofdeath = ev->GetInteger( 5 );

	// Double all the armor
	DoubleArmor();

	SetVariable( "other", ev->GetEntity( 1 ) );
	if( !DoAction( "killed" ) && actorthread )
	{
		actorthread->ProcessEvent( EV_ScriptThread_End );
	}


	// Turn off dlight and shadow
	edict->s.renderfx &= ~( RF_DLIGHT | RF_XFLIP );

	//
	// kill the killtargets
	//
	name = KillTarget();
	if( name && strcmp( name, "" ) )
	{
		num = 0;
		do
		{
			num = G_FindTarget( num, name );
			if( !num )
			{
				break;
			}

			ent = G_GetEntity( num );
			ent->PostEvent( EV_Remove, 0 );
		}
		while( 1 );
	}

	//
	// fire targets
	//
	name = Target();
	if( name && strcmp( name, "" ) )
	{
		num = 0;
		do
		{
			num = G_FindTarget( num, name );
			if( !num )
			{
				break;
			}

			ent = G_GetEntity( num );

			event = new Event( EV_Activate );
			event->AddEntity( attacker );
			ent->PostEvent( event, 0 );
		}
		while( 1 );
	}
	//
	// see if we have a kill_thread
	//
	if( kill_thread.length() > 1 )
	{
		ScriptThread* thread;

		//
		// create the thread, but don't start it yet
		//
		thread = ExecuteThread( kill_thread, false );
		if( thread )
		{
			ProcessScript( thread, NULL );
		}
		else
		{
			warning( "Killed", "could not process kill_thread" );
		}
	}

	if( flags & FL_DIE_EXPLODE )
	{
		CreateExplosion( worldorigin, 150 * edict->s.scale, edict->s.scale * 2, true, this, this, this );
	}

	if( flags & FL_DIE_TESSELATE )
	{
		float power;
		setSolidType( SOLID_NOT );
		hideModel();

		dir = worldorigin - attacker->worldorigin;

		if( meansofdeath == MOD_ION )
		{
			power = 1000;
		}
		else
		{
			power = ev->GetFloat( 2 );
		}

		TesselateModel
		(
			this,
			tess_min_size,
			tess_max_size,
			dir,
			power,
			1.0f,
			tess_thickness,
			vec3_origin
		);

		DropInventoryItems();
		// Tesselated models require the model to be on the client for 1 more frame
		PostEvent( EV_Remove, FRAMETIME );
		return;
	}

	if( DoGib( meansofdeath, inflictor ) )
	{
		deathgib = true;
	}

	if( currentWeapon )
	{
		DropWeapon( currentWeapon );
	}

	animOverride = false;

	//
	// determine death animation
	//
	if( !strncmp( animname.c_str(), "crouch", 6 ) )
	{
		dname = "crouch_";
	}

	if( deathgib )
	{
		str location;

		location = ev->GetString( 4 );

		// Check for location first otherwise randomize
		if( location == "torso_upper" )
			dname += str( "gibdeath_upper" );
		else if( location == "torso_lower" )
			dname += str( "gibdeath_lower" );
		else if( strstr( location.c_str(), "leg" ) )
			dname += str( "gibdeath_lower" );
		else if( strstr( location.c_str(), "arm" ) )
			dname += str( "gibdeath_upper" );
		else if( strstr( location.c_str(), "head" ) )
			dname += str( "gibdeath_upper" );
		else if( G_Random() > 0.5 )
			dname += str( "gibdeath_upper" );
		else
			dname += str( "gibdeath_lower" );
	}
	else
	{
		dname += str( "death_" ) + str( ev->GetString( 4 ) );
	}

	i = gi.Anim_Random( edict->s.modelindex, dname.c_str() );

	if( ( i == -1 ) && !strncmp( animname.c_str(), "crouch", 6 ) )
	{
		dname = "crouch_death";
		i = gi.Anim_Random( edict->s.modelindex, dname.c_str() );
	}

	if( i == -1 )
	{
		dname = "death";
	}

	if( ( i != -1 ) && ( !strncmp( dname.c_str(), "gibdeath", 7 ) ) )
	{
		Event* ev1;

		ev1 = new Event( EV_Gib );
		ev1->AddInteger( 1 );
		ProcessEvent( ev1 );
	}
	if( attacker )
	{
		str location;
		float damage;

		damage = ev->GetFloat( 2 );
		location = ev->GetString( 4 );

		event = new Event( EV_GotKill );
		event->AddEntity( this );
		event->AddInteger( damage );
		event->AddEntity( inflictor );
		event->AddString( location );
		event->AddInteger( meansofdeath );
		event->AddInteger( deathgib );
		attacker->ProcessEvent( event );
	}

	SetAnim( dname.c_str(), EV_Actor_Dead );

	// Call changeanim immediatly since we're no longer calling prethink
	ChangeAnim();

	//
	// moved this here so guys would not be solid right away
	//
	edict->svflags |= SVF_DEADMONSTER;
	edict->clipmask = MASK_DEADSOLID;

	if( velocity.z < 10 )
	{
		velocity.z += G_Random( 300 );
	}

	angles.x = 0;
	angles.z = 0;
	setAngles( angles );
}

void Actor::GibEvent( Event* ev )
{
	qboolean hidemodel;

	hidemodel = !ev->GetInteger( 1 );

	if( sv_gibs->value && !parentmode->value )
	{
		int numgibs;
		float gibsize;

		if( hidemodel )
		{
			takedamage = DAMAGE_NO;
			setSolidType( SOLID_NOT );
			hideModel();

			// If there is a touchfield, remove it.
			if( trig )
			{
				trig->PostEvent( EV_Remove, 0 );
				trig = NULL;
			}
		}

		gibsize = size.length() / 140;
		numgibs = gibsize * 6;
		if( numgibs > 4 )
			numgibs = 4;
		CreateGibs( this, health, gibsize, numgibs );
	}

	DropInventoryItems();

	if( hidemodel )
		PostEvent( EV_Remove, 0 );
}

void Actor::RemoveUselessBody( Event* ev )
{
	if( trig )
	{
		trig->PostEvent( EV_Remove, 0 );
		trig = NULL;
	}

	CancelEventsOfType( EV_Sentient_UselessCheck );
	PostEvent( EV_FadeOut, 5 );
}

void Actor::SetPainThresholdEvent( Event* ev )
{
	pain_threshold = ( ev->GetFloat( 1 ) ) * skill->value * 0.66f;
}

void Actor::SetKillThreadEvent( Event* ev )
{
	kill_thread = ev->GetString( 1 );
}

void Actor::AttackRangeEvent( Event* ev )
{
	attack_range = ev->GetFloat( 1 );
}

void Actor::AttackModeEvent( Event* ev )
{
	attackmode = ev->GetInteger( 1 );
	SetVariable( "attackmode", attackmode );
}

void Actor::ShotsPerAttackEvent( Event* ev )
{
	float shots;

	shots = ev->GetFloat( 1 );
	//
	// scale the shots based on skill
	//
	if( skill->value < 1 )
		shots_per_attack = shots * 0.4f;
	else if( skill->value < 2 )
		shots_per_attack = shots * 0.7f;
	else if( skill->value < 3 )
		shots_per_attack = shots;
	else
		shots_per_attack = shots * skill->value * 0.4f;
}

void Actor::ClearEnemyEvent( Event* ev )
{
	ClearEnemies();
}

void Actor::NoDeathFadeEvent( Event* ev )
{
	nodeathfade = true;
}

void Actor::NoChatterEvent( Event* ev )
{
	nochatter = true;
}

void Actor::TurnSpeedEvent( Event* ev )
{
	turnspeed = ev->GetFloat( 1 );
}


//***********************************************************************************************
//
// Movement functions
//
//***********************************************************************************************

void Actor::ForwardSpeedEvent( Event* ev )
{
	forwardspeed = ev->GetFloat( 1 );
}

void Actor::SwimEvent( Event* ev )
{
	// movement |= AI_CANSWIM;
	flags &= ~FL_FLY;
	flags |= FL_SWIM;
}

void Actor::FlyEvent( Event* ev )
{
	// movement |= AI_CANFLY;
	flags &= ~FL_SWIM;
	flags |= FL_FLY;
}

void Actor::NotLandEvent( Event* ev )
{
	//movement &= ~AI_CANWALK;
	flags &= FL_SWIM | FL_FLY;
}

/*inline*/ qboolean Actor::CanMoveTo( Vector pos )
{
	Vector min;
	trace_t trace;
	Vector start;
	Vector end;
	Vector s;

	s = Vector( 0, 0, STEPSIZE );
	start = worldorigin + s;
	end = pos + s;
	trace = G_Trace( start, mins, maxs, end, this, edict->clipmask, "Actor::CanMoveTo" );
	if( trace.fraction == 1 )
	{
		return true;
	}
	return false;
}

/*inline */ void Actor::CheckWater( void )
{
	Vector sample[3];
	int cont;

	//
	// get waterlevel and type
	//
	waterlevel = 0;
	watertype = 0;

	sample[ 0 ] = worldorigin;
	sample[ 2 ] = EyePosition();
	sample[ 1 ] = ( sample[ 0 ] + sample[ 2 ] ) * 0.5f;

	cont = gi.pointcontents( sample[ 0 ].vec3() );

	if( cont & MASK_WATER )
	{
		watertype = cont;
		waterlevel = 1;
		cont = gi.pointcontents( sample[ 2 ].vec3() );
		if( cont & MASK_WATER )
		{
			waterlevel = 3;
		}
		else
		{
			cont = gi.pointcontents( sample[ 1 ].vec3() );
			if( cont & MASK_WATER )
			{
				waterlevel = 2;
			}
		}
	}
}

#define MAX_PITCH 75
void Actor::Accelerate( Vector steering )
{
	// activate this to limit turnrate
#if 0
	if( steering.y > turnspeed )
		steering.y = turnspeed;
	else if( steering.y < -turnspeed )
		steering.y = -turnspeed;
#endif

	angles.y += steering.y;

	if( frame_delta.x > 4 )
	{
		// make him lean into the turn a bit
		angles.z = movespeed * ( 0.4f / 320.0f ) * steering.y;

		if( ( flags & FL_FLY ) || ( ( flags & FL_SWIM ) && waterlevel > 0 ) )
		{
			angles.z = bound( angles.z, -2, 2 );
		}
		else
		{
			angles.z = bound( angles.z, -5, 5 );
		}
	}
	else
	{
		angles.z = 0;
	}

	if( ( flags & FL_FLY ) || ( ( flags & FL_SWIM ) && waterlevel > 0 ) )
	{
		angles.x -= steering.x;
		//angles.x = bound( angles.x, -MAX_PITCH, MAX_PITCH );
	}

	setAngles( angles );
}

void Actor::CalcMove( void )
{
	if( total_delta != vec_zero )
	{
		// movement deltas have inverted Y axis
		total_delta[ 1 ] = -total_delta[ 1 ];
		MatrixTransformVector( total_delta.vec3(), orientation, move.vec3() );
		total_delta = vec_zero;
	}
	else
	{
		move = vec_zero;
	}

	// force movement if forwardspeed is set
	if( forwardspeed )
	{
		if( move == vec_zero )
		{
			move = orientation[ 0 ];
		}
		else
		{
			move.normalize();
		}

		animdir = move;
		movedir = move;
		movespeed = forwardspeed;
		move *= movespeed * FRAMETIME;
		totallen = forwardspeed;
		movevelocity = movedir * movespeed;
	}
}

void Actor::setAngles( Vector ang )
{
	Sentient::setAngles( ang );
	MatrixTransformVector( animdir.vec3(), orientation, movedir.vec3() );
	movevelocity = movedir * movespeed;
}

stepmoveresult_t Actor::WaterMove( void )
{
	Vector oldorg;
	Vector neworg;
	trace_t trace;
	int oldwater;

	if( ( totallen <= 0.01f ) || ( move == vec_zero ) )
	{
		return STEPMOVE_OK;
	}

	// try the move
	oldorg = worldorigin;
	neworg = worldorigin + move;

	trace = G_Trace( oldorg, mins, maxs, neworg, this, edict->clipmask, "Actor::WaterMove 1" );
	if( trace.fraction == 0 )
	{
		return STEPMOVE_STUCK;
	}

	oldwater = waterlevel;

	setOrigin( trace.endpos );

	CheckWater();

	// swim monsters don't exit water voluntarily
	if( ( oldwater > 1 ) && ( waterlevel < 2 ) )
	{
		waterlevel = oldwater;
		setOrigin( oldorg );
		return STEPMOVE_STUCK;
	}

	return STEPMOVE_OK;
}

stepmoveresult_t Actor::AirMove( void )
{
	Vector oldorg;
	Vector neworg;
	trace_t trace;
	int oldwater;

	if( ( totallen <= 0.01f ) || ( move == vec_zero ) )
	{
		return STEPMOVE_OK;
	}

	// try the move
	oldorg = worldorigin;
	neworg = worldorigin + move;

	trace = G_Trace( oldorg, mins, maxs, neworg, this, edict->clipmask, "Actor::AirMove 1" );
	if( trace.fraction == 0 )
	{
		return STEPMOVE_BLOCKED_BY_WATER;
	}

	oldwater = waterlevel;

	setOrigin( trace.endpos );

	CheckWater();

	// fly monsters don't enter water voluntarily
	if( !oldwater && waterlevel )
	{
		waterlevel = oldwater;
		setOrigin( oldorg );
		return STEPMOVE_STUCK;
	}

	return STEPMOVE_OK;
}

stepmoveresult_t Actor::TryMove( void )
{
	Vector oldorg;
	Vector neworg;
	Vector end;
	Entity* ent;
	trace_t trace;
	Door*     door;
#if 0
	vec3_t test;
	int contents;
#endif

	if( ( totallen <= 0.01f ) || ( move == vec_zero ) )
	{
		return STEPMOVE_OK;
	}

	// try the move
	oldorg = worldorigin;
	neworg = worldorigin + move;

	// push down from a step height above the wished position
	neworg[ 2 ] += STEPSIZE;
	end = neworg;
	end[ 2 ] -= STEPSIZE * 4; //2;

	trace = G_Trace( neworg, mins, maxs, end, this, edict->clipmask, "Actor::TryMove 1" );
	if( trace.allsolid )
	{
		ent = trace.ent->entity;
		if( ent && ent->isSubclassOf( Door ) )
		{
			if( !strcmp( state.c_str(), "opendoor" ) )
			{
				return STEPMOVE_OK;
			}

			door = ( Door* )ent;
			if( !door->locked && !door->isOpen() )
			{
				SetVariable( "other", ent );
				SetVariable( "dir", Vector( end - worldorigin ) );
				ForceAction( "opendoor" );

				return STEPMOVE_OK;
			}
		}

		return STEPMOVE_STUCK;
	}

	if( trace.startsolid )
	{
		neworg[ 2 ] -= STEPSIZE;
#if 0
		trace = G_Trace( neworg, mins, maxs, end, this, edict->clipmask, "Actor::TryMove 2" );
#else
		// 2015 - The following line altered to allow short actors to detect doors,
		// It doesn't seem to break anything else...
		if( maxs[ 2 ] > ( STEPSIZE * 3 ) )
		{
			trace = G_Trace( neworg, mins, maxs, end, this, edict->clipmask, "Actor::TryMove 2" );
		}
		else
		{
			trace = G_Trace( Vector( neworg + movetweak ), mins, Vector( maxs - movetweak ), end, this, edict->clipmask, "Actor::TryMove 2" );
		}
#endif
		if( trace.allsolid || trace.startsolid )
		{
			ent = trace.ent->entity;
			if( ent && ent->isSubclassOf( Door ) )
			{
				if( state == "opendoor" )
				{
					return STEPMOVE_OK;
				}

				door = ( Door* )ent;
				if( !door->locked && !door->isOpen() )
				{
					SetVariable( "other", ent );
					SetVariable( "dir", Vector( end - worldorigin ) );
					ForceAction( "opendoor" );

					return STEPMOVE_OK;
				}
			}

			return STEPMOVE_STUCK;
		}
	}

#if 0
	// don't go in to water
	if( waterlevel == 0 )
	{
		test[ 0 ] = trace.endpos[ 0 ];
		test[ 1 ] = trace.endpos[ 1 ];
		test[ 2 ] = trace.endpos[ 2 ] + mins[ 2 ] + 1;
		contents = gi.pointcontents( test );

		if( contents & MASK_WATER )
		{
			return STEPMOVE_BLOCKED_BY_WATER;
		}
	}
#endif

	if( trace.fraction == 1 )
	{
		// don't let guys get stuck standing on other guys
		// if monster had the ground pulled out, go ahead and fall
		if( ( flags & FL_PARTIALGROUND ) || ( groundentity && groundentity->entity &&
											  ( groundentity->entity->isSubclassOf( Sentient ) ) ) )
		{
			setOrigin( worldorigin + move );
			groundentity = NULL;
			return STEPMOVE_OK;
		}

		// walked off an edge
		return STEPMOVE_BLOCKED_BY_FALL;
	}

	// check point traces down for dangling corners
	worldorigin = trace.endpos;

	if( !M_CheckBottom( this ) )
	{
		// don't let guys get stuck standing on other guys
		if( ( flags & FL_PARTIALGROUND ) || ( groundentity && groundentity->entity &&
											  ( groundentity->entity->isSubclassOf( Sentient ) ) ) )
		{
			// entity had floor mostly pulled out from underneath it
			// and is trying to correct
			setOrigin( worldorigin );

			CheckWater();

			return STEPMOVE_OK;
		}

		setOrigin( oldorg );
		return STEPMOVE_BLOCKED_BY_FALL;
	}

	if( flags & FL_PARTIALGROUND )
	{
		flags &= ~FL_PARTIALGROUND;
	}

	groundentity = trace.ent;
	groundentity_linkcount = trace.ent->linkcount;
	groundplane = trace.plane;
	groundsurface = trace.surface;
	groundcontents = trace.contents;

	// the move is ok
	setOrigin( worldorigin );

	CheckWater();

	return STEPMOVE_OK;
}

void Actor::SetAim( Event* ev )
{
	aim = ev->GetFloat( 1 );
}

void Actor::SetMeleeRange( Event* ev )
{
	melee_range = ev->GetFloat( 1 );
	melee_range *= edict->s.scale;
	if( melee_range < 70 )
		melee_range = 70;
}

void Actor::SetMeleeDamage( Event* ev )
{
	melee_damage = ev->GetFloat( 1 );
	melee_damage *= edict->s.scale;
}

void Actor::MeleeEvent( Event* ev )
{
	trace_t trace;
	Vector start;
	Vector end;
	float damage;
	float extra_reach;
	Vector org;
	Vector ang;
	Vector dir;
	float kick;

	if( !has_melee )
	{
		warning( "MeleeEvent", "Melee being called without animation" );
		return;
	}

	damage = G_Random( melee_damage * 0.5f ) + G_Random( melee_damage * 0.5f );
	if( ev->NumArgs() > 0 )
		extra_reach = ev->GetFloat( 1 );
	else
		extra_reach = 0;

	if( ev->NumArgs() > 1 )
	{
		kick = ev->GetFloat( 2 );
	}
	else
	{
		kick = damage * 3;
	}

	// get the position of the attack
	start = GunPosition();
	// get the attack_dir
	ang = MyGunAngles( start, true );
	ang.AngleVectors( &dir, NULL, NULL );

	end = start + dir * ( melee_range + extra_reach );

	trace = G_FullTrace( start, vec_zero, vec_zero, end, 15, this, MASK_PROJECTILE, "Actor::Melee" );

	dir = Vector( trace.endpos ) - start;
	dir.normalize();

	org = Vector( trace.endpos ) - dir;

	if( ( trace.fraction < 1.0f ) )
	{
		if( trace.ent->entity && trace.ent->entity->takedamage )
		{
			if( trace.ent->entity->flags & FL_BLOOD )
				SpawnBlood( org, trace.plane.normal, damage );

			RandomGlobalSound( "impact_goryimpact" );
			if( trace.intersect.valid )
			{
				// We hit a valid group so send in location based damage
				trace.ent->entity->Damage( this,
										   this,
										   damage,
										   trace.endpos,
										   dir,
										   trace.plane.normal,
										   kick,
										   0,
										   MOD_FISTS,
										   trace.intersect.parentgroup,
										   -1,
										   trace.intersect.damage_multiplier );
			}
			else
			{
				// take the ground out so that the kick works
				trace.ent->entity->groundentity = NULL;

				// We didn't hit any groups, so send in generic damage
				trace.ent->entity->Damage( this,
										   this,
										   damage,
										   trace.endpos,
										   dir,
										   trace.plane.normal,
										   kick,
										   0,
										   MOD_FISTS,
										   -1,
										   -1,
										   1 );
			}
		}
	}
}

void Actor::AttackFinishedEvent( Event*  /* ev */)
{
	if( currentWeapon )
	{
		currentWeapon->PostEvent( EV_Weapon_FinishAttack, 0 );
	}
}

float Actor::JumpTo( Vector targ, float speed )
{
	float traveltime;
	float vertical_speed;
	Vector target;
	Vector dir;
	Vector xydir;

	CheckWater();
	//
	// if we got a jump, go into that mode
	//
	traveltime = 0;
	if( speed <= 0 )
	{
		speed = movespeed * 1.5f;
		if( speed < ( 400 * gravity ) )
			speed = ( 400 * gravity );
	}

	target = targ;
	dir = target - worldorigin;
	xydir = dir;
	xydir.z = 0;
	Vector temp;
	temp = xydir.toAngles();
	setAngles( temp );
	traveltime = xydir.length() / speed;
	//
	// we add 16 to allow for a little bit higher
	//
	if( waterlevel > 2 )
	{
		vertical_speed = ( ( dir.z + 16 ) / traveltime ) + ( 0.5f * gravity * 60 * traveltime );
	}
	else
	{
		vertical_speed = ( ( dir.z + 16 ) / traveltime ) + ( 0.5f * gravity * sv_gravity->value * traveltime );
	}
	xydir.normalize();

	velocity = speed * xydir;
	velocity.z = vertical_speed;

	return traveltime;
}

float Actor::JumpTo( PathNode* goal, float speed )
{
	if( goal )
		return JumpTo( goal->worldorigin, speed );
	else
		return 0;
}

float Actor::JumpTo( Entity* goal, float speed )
{
	if( goal )
		return JumpTo( goal->worldorigin, speed );
	else
		return 0;
}

void Actor::setSize( const Vector& min, const Vector& max )
{
	Vector min2, max2;
	float scale;
	scale = edict->s.scale;
	min2.x = min.x * scale;
	min2.y = min.y * scale;
	min2.z = min.z * scale;
	max2.x = max.x * scale;
	max2.y = max.y * scale;
	max2.z = max.z * scale;
	Sentient::setSize( min2, max2 );
}

void Actor::SetHealth( Event* ev )
{
	health = ev->GetFloat( 1 ) * edict->s.scale;
	max_health = health;
}

//***********************************************************************************************
//
// Debug functions
//
//***********************************************************************************************

void Actor::ShowInfo( void )
{
	Entity* ent;
	int i;
	int n;
	StateInfo* ptr;

	gi.printf( "\nEntity #   : %d\n", entnum );
	gi.printf( "Class ID   : %s\n", getClassID() );
	gi.printf( "Classname  : %s\n", getClassname() );
	gi.printf( "Targetname : %s\n", TargetName() );
	gi.printf( "Origin     : ( %f, %f, %f )\n", worldorigin.x, worldorigin.y, worldorigin.z );
	gi.printf( "Bounds     : Mins( %.2f, %.2f, %.2f ) Maxs( %.2f, %.2f, %.2f )\n", mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z );
	gi.printf( "State      : %s\n", state.c_str() );

	if( behavior )
	{
		gi.printf( "Behavior   : %s\n", behavior->getClassname() );
	}
	else
	{
		gi.printf( "Behavior   : NULL -- was '%s'\n", currentBehavior.c_str() );
	}

	if( actorthread )
	{
		gi.printf( "Thread     : %s(%d)\n", actorthread->Filename(), actorthread->CurrentLine() );
	}
	else
	{
		gi.printf( "Thread     : NULL\n" );
	}
	gi.printf( "Actortype  : %d\n", actortype );
	gi.printf( "Attackmode : %d\n", attackmode );

	gi.printf( "Model      : %s\n", model.c_str() );
	gi.printf( "Anim       : %s\n", animname.c_str() );
	gi.printf( "Movespeed  : %.2f\n", movespeed );
	gi.printf( "Health     : %f\n", health );

	gi.printf( "\nResponses:\n" );
	n = actionList.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		ptr = actionList.ObjectAt( i );
		gi.printf( "%s : ", ptr->action.c_str() );
		if( ptr->ignore )
		{
			gi.printf( "ignored - " );
		}

		gi.printf( "%s\n", ptr->response.c_str() );
	}

	n = enemyList.NumObjects();
	if( n )
	{
		gi.printf( "\nEnemies:\n" );

		for( i = 1; i <= n; i++ )
		{
			ent = enemyList.ObjectAt( i );
			if( ent )
			{
				if( currentEnemy == ent )
				{
					gi.printf( "*" );
				}
				else
				{
					gi.printf( " " );
				}
				gi.printf( "%d : '%s'", ent->entnum, ent->targetname.c_str() );
				gi.printf( "\n" );
			}
		}
	}

	gi.printf( "seenEnemy: %d\n", seenEnemy );
	gi.printf( "actortype: %d\n", actortype );
	gi.printf( "deadflag: %d\n", deadflag );

	gi.printf( "\n" );
	if( behavior )
	{
		gi.printf( "Behavior Info:\n" );
		gi.printf( "Game time: %f\n", level.time );
		behavior->ShowInfo( *this );
		gi.printf( "\n" );
	}
}

//***********************************************************************************************
//
// General functions
//
//***********************************************************************************************

void Actor::Chatter( const char* snd, float chance, float volume, int channel )
{
	str realname;

	if( nochatter || chattime > level.time )
	{
		return;
	}

	if( G_Random( 10 ) > chance )
	{
		chattime = level.time + 1 + G_Random( 2 );
		return;
	}

	realname = GetRandomAlias( snd );
	if( realname.length() > 1 )
	{
		float delay;

		delay = gi.SoundLength( realname.c_str() );
		chattime = level.time + delay + 4 + G_Random( 5 );
		sound( realname, volume, channel );
	}
	else
	{
		// set it into the future, so we don't check it again right away
		chattime = level.time + 1;
	}
}

void Actor::ActivateEvent( Event* ev )
{
	Entity* ent;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	ent = ev->GetEntity( 1 );
	SetVariable( "other", ent );

	DoAction( "activate" );
}

void Actor::UseEvent( Event* ev )
{
	Entity* ent;

	if( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	ent = ev->GetEntity( 1 );
	SetVariable( "other", ent );

	DoAction( "use" );
}

void Actor::Prethink( void )
{
	range_t range;
	Event* event;

	assert( actorthread );
	if( !actorthread )
	{
		// not having a script is bad
		gi.dprintf( "Null actorthread.  Killing actor '%s'(%d).\n", targetname.c_str(), entnum );

		// just kill him
		event = new Event( EV_Killed );
		event->AddEntity( this );
		event->AddInteger( 0 );
		event->AddEntity( this );
		event->AddString( "all" );
		ProcessEvent( event );

		return;
	}

	if( hidden() )
	{
		// Don't think while hidden, you're just a cinematic.
		return;
	}

	if( actortype == IS_INANIMATE )
	{
		if( behavior && !behavior->Evaluate( *this ) )
		{
			// stop thinking
			flags &= ~FL_PRETHINK;
			EndBehavior();
		}
		return;
	}

	if( currentEnemy )
	{
		if( currentEnemy->deadflag )
		{
			DoAction( "enemydead" );
			currentEnemy = NULL;
			seenEnemy = false;
		}
		else
		{
			range = Range( currentEnemy );
			if( ( lastEnemy != currentEnemy ) || ( range != enemyRange ) )
			{
				lastEnemy = currentEnemy;
				enemyRange = range;

				SetVariable( "other", currentEnemy );
				switch( range )
				{
					case RANGE_MELEE:
						DoAction( "range_melee" );
						break;

					case RANGE_NEAR:
						DoAction( "range_near" );
						break;

					case RANGE_MID:
						DoAction( "range_mid" );
						break;

					case RANGE_FAR:
						DoAction( "range_far" );
						break;
				}
			}
		}
	}
	else
	{
		lastEnemy = NULL;
		enemyRange = RANGE_FAR;
	}

	eyeposition[ 2 ] = maxs[ 2 ] + eyeoffset[ 2 ];
	angles.z = 0;

#ifdef DEBUG_PRINT
	gi.dprintf( "stack %d : %s : %s\n", numonstack, behavior ? behavior->getClassname() : "", animname.c_str() );
#endif
	if( behavior && !behavior->Evaluate( *this ) )
	{
		EndBehavior();
	}

	if( newanimnum != -1 )
	{
		ChangeAnim();
	}

	CalcMove();
	lastmove = STEPMOVE_STUCK;

	if( flags & FL_SWIM )
	{
		lastmove = WaterMove();
	}
	else if( flags & FL_FLY )
	{
		lastmove = AirMove();
	}
	else
	{
		lastmove = TryMove();
	}
	//
	// see if we should damage the actor because of waterlevel
	//
	if( waterlevel == 3 && !( flags & FL_SWIM ) )
	{
		// if out of air, start drowning
		if( air_finished < level.time )
		{
			// we may have been in a water brush when we spawned, so check our water level again to be sure
			CheckWater();
			if( waterlevel < 3 )
			{
				// we're ok, so reset our air
				air_finished = level.time + 5;
			}
			else if( next_drown_time < level.time && health > 0 )
			{
				// drown!
				next_drown_time = level.time + 1;

				RandomGlobalSound( "snd_uwchoke", 1, CHAN_VOICE, ATTN_NORM );
				ProcessEvent( EV_PainSound );

				Damage( world, world, 15, worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_DROWN, -1, -1, 1.0f );
			}
		}
	}
	else
	{
		air_finished = level.time + 5;
	}

	G_TouchTriggers( this );
	if( groundentity && ( groundentity->entity != world ) && !M_CheckBottom( this ) )
	{
		// G_FixCheckBottom( this );
		flags |= FL_PARTIALGROUND;
	}
}

/*****************************************************************************/
/*SINED info_monster_spawnspot (1 0 0) (-16 -16 0) (16 16 64)

   Potential spawn location for a monster.

   "spawngroup" - the name of the group of spawn spots that this one belongs to.
   Monsters use spawngroup to choose which spawn spots to spawn at.

   "angle"  - the orientation for the monster to spawn in.
   "angles" - the full ( x, y, z ) orientation for the monster to spawn in.
   "anim"  - the animation that the monster should use when spawning here.

 *****************************************************************************/

CLASS_DECLARATION( PathNode, MonsterStart, "info_monster_spawnspot" );

ResponseDef MonsterStart::Responses[] =
{
	{ NULL, NULL }
};

MonsterStart::MonsterStart()
{
	spawngroup = G_GetSpawnArg( "spawngroup", "" );

	leader = NULL;
	leader = GetGroupLeader( spawngroup );
	if( !leader )
	{
		leader = this;
		groupchain = NULL;
	}
	else
	{
		groupchain = leader->groupchain;
		leader->groupchain = this;
	}
}

MonsterStart* MonsterStart::GetGroupLeader( str& groupname )
{
	Entity* ent;
	MonsterStart* m;

	for( ent = world; ent; ent = G_NextEntity( ent ) )
	{
		if( ent->isSubclassOf( MonsterStart ) )
		{
			m = ( MonsterStart* )ent;
			if( ( m->spawngroup == groupname ) && m->leader )
			{
				return m->leader;
			}
		}
	}
	return NULL;
}

MonsterStart* MonsterStart::GetRandomSpot( str& groupname )
{
	MonsterStart* leader;
	MonsterStart* m;
	int num;
	int i;
	int j;

	leader = GetGroupLeader( groupname );
	if( !leader )
	{
		return NULL;
	}

	num = 0;
	for( m = leader; m != NULL; m = m->groupchain )
	{
		num++;
	}

	j = ( int )G_Random( num );

	for( m = leader, i = 0; m != NULL; m = m->groupchain, i++ )
	{
		if( i == j )
		{
			break;
		}
	}

	return m;
}
