sin-sdk/actor.cpp

6258 lines
131 KiB
C++
Raw Permalink Normal View History

1998-12-20 00:00:00 +00:00
//-----------------------------------------------------------------------------
//
// $Logfile:: /Quake 2 Engine/Sin/code/game/actor.cpp $
1999-03-20 00:00:00 +00:00
// $Revision:: 233 $
1998-12-20 00:00:00 +00:00
// $Author:: Markd $
1999-03-20 00:00:00 +00:00
// $Date:: 2/08/99 5:12p $
1998-12-20 00:00:00 +00:00
//
// 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 $
//
1999-03-20 00:00:00 +00:00
// 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
//
1998-12-20 00:00:00 +00:00
// 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;
1999-03-20 00:00:00 +00:00
// used below for a slight movement tweak
// added as a global here to prevent constant re-allocation
const Vector movetweak = "0 0 2";
1998-12-20 00:00:00 +00:00
#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;
str script;
const char * skinname;
Event *ev;
SpawnArgs args;
// don't spawn monsters in deathmatch
if ( deathmatch->value || nomonsters->value )
{
PostEvent( EV_Remove, 0 );
return;
}
actortype = IS_ENEMY;
setSolidType( SOLID_BBOX );
setMoveType( MOVETYPE_STEP );
health = G_GetFloatArg( "health", 100 );
max_health = health;
takedamage = DAMAGE_AIM;
mass = 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 = "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 = cos( fov * 0.5 * M_PI / 180.0 );
vision_distance = G_GetFloatArg( "visiondistance", 1024 );
eyeoffset = "0 0 0";
eyeposition = "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 = "-16 -16 0";
crouchsize_max = "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( "-16 -16 0", "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 ( 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 ( start->animname != "" )
{
SetAnim( start->animname );
}
}
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
(
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 );
}
inline qboolean Actor::CanSeeFrom
(
Vector pos,
Entity *ent
)
{
trace_t trace;
Vector p;
p = ent->centroid;
// Check if he's visible
trace = G_Trace( pos + eyeposition, 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( pos + eyeposition, 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
(
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.44 * 0.5;
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
(
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.18 );
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 &&
( ent != world ) &&
( ent != this ) &&
!( 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
(
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;
ScriptThread *thread;
str script;
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
response = str( thread->Filename() ) + "::" + response;
}
}
else
{
// 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 += "/";
}
response = script + response;
}
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;
ThreadMarker marker;
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;
}
#ifdef DEBUG_PRINT
gi.dprintf( "%d : %d : Action: %s - %s\n",
actorthread->ThreadNum(), ev->GetThread()->ThreadNum(),
name.c_str(), response.c_str() );
#endif
1999-03-20 00:00:00 +00:00
// check the existance of the actor's thread to prevent a crash
if ( !actorthread )
{
return;
}
1998-12-20 00:00:00 +00:00
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;
ThreadMarker marker;
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
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,
str &text
)
{
if ( actorthread )
{
return actorthread->Vars()->SetVariable( name, text.c_str() );
}
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,
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_") + 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 = worldorigin - "32 32 32";
max = worldorigin + "32 32 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
)
{
1999-03-20 00:00:00 +00:00
// activate this to limit turnrate
1998-12-20 00:00:00 +00:00
#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 ( state == "opendoor" )
{
return STEPMOVE_OK;
}
door = ( Door * )ent;
if ( !door->locked && !door->isOpen() )
{
SetVariable( "other", ent );
SetVariable( "dir", end - worldorigin );
ForceAction( "opendoor" );
return STEPMOVE_OK;
}
}
return STEPMOVE_STUCK;
}
if ( trace.startsolid )
{
neworg[ 2 ] -= STEPSIZE;
1999-03-20 00:00:00 +00:00
#if 0
1998-12-20 00:00:00 +00:00
trace = G_Trace( neworg, mins, maxs, end, this, edict->clipmask, "Actor::TryMove 2" );
1999-03-20 00:00:00 +00:00
#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( neworg + movetweak, mins, maxs - movetweak, end, this, edict->clipmask, "Actor::TryMove 2" );
}
#endif
1998-12-20 00:00:00 +00:00
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", 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;
setAngles( xydir.toAngles() );
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;
}
EXPORT_FROM_DLL void Actor::setSize
(
Vector min,
Vector max
)
{
min *= edict->s.scale;
max *= edict->s.scale;
Sentient::setSize( min, max );
}
EXPORT_FROM_DLL 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;
}