//----------------------------------------------------------------------------- // // $Logfile:: /Quake 2 Engine/Sin/code/game/entity.cpp $ // $Revision:: 296 $ // $Author:: Jimdose $ // $Date:: 11/18/98 5:22a $ // // Copyright (C) 1997 by Ritual Entertainment, Inc. // All rights reserved. // // This source is may not be distributed and/or modified without // expressly written permission by Ritual Entertainment, Inc. // // $Log:: /Quake 2 Engine/Sin/code/game/entity.cpp $ // // 296 11/18/98 5:22a Jimdose // CheckGround properly checks normal an alternate grav axis // // 295 11/18/98 3:03a Jimdose // made EV_Mutate a cheat // // 294 11/18/98 12:22a Jimdose // fixed fullmins and fullmaxs when gravaxis is non-0 in setSize // // 293 11/15/98 11:31p Markd // make sure all our children are deleted when removing // // 292 11/14/98 5:29p Jimdose // Made BroadcastSound search through the SentientList instead of using // findradius. This way, fewer entities are checked. // // 291 11/13/98 10:20p Aldie // Fix crash bug in attachevent // // 290 11/13/98 1:47a Markd // Don't set gravaxis if the same as before // // 289 11/10/98 8:03p Jimdose // made setSize and SetGravityAxis change fullmins and fullmaxs appropriately // // 288 11/08/98 6:33p Jimdose // added FLAG_IGNORE to the flag modifaction functions, making sure that // illegal values don't cause flags to be modified // // 287 11/04/98 8:40p Jimdose // Made it so the Flags event will not screw the flags if the string is not // recognized. // // 286 10/26/98 9:41p Markd // Fixed gotkill message not passing in gibbed parameter // // 285 10/26/98 4:26p Aldie // Added ghost command for models that are notsolid, and notvisible, but still // need to get sent over. // // 284 10/25/98 4:38a Aldie // Moved link() // // 283 10/25/98 12:01a Markd // put in censored support // // 282 10/24/98 5:43p Markd // Fixed IfSkillEvent // // 281 10/24/98 12:42a Markd // changed origins to worldorigins where appropriate // // 280 10/23/98 11:50p Jimdose // fixed usagage of model as a temporary variable in AttachModelEvent // // 279 10/23/98 5:38a Jimdose // Added SetMassEvent // // 278 10/22/98 4:57p Aldie // Removed blastscale_z values // // 277 10/22/98 1:40a Markd // Added stealth mode // // 276 10/21/98 1:29a Jimdose // fixed crash bug with binding/unbinding and teams // // 275 10/20/98 11:29p Markd // Revamped Broadcast sound // // 274 10/20/98 4:00p Aldie // Put in teammaster checkwhen unbinding // // 273 10/20/98 3:32a Jimdose // Made setSolidType not error out when loading savegames // added isBoundTo to test is an entity is affected by another entity via // binding // binding is no longer order dependent // // 272 10/19/98 8:55p Markd // Put check in setSolidType // // 271 10/19/98 5:29p Aldie // Zero out total_delta when stop animating is called // // 270 10/19/98 12:07a Jimdose // made all code use fast checks for inheritance (no text lookups when // possible) // isSubclassOf no longer requires ::_classinfo() // // 269 10/18/98 8:40p Jimdose // Added GetEntName // Made setModel check if it's SOLID_BSP when it has no model // // 268 10/17/98 11:02p Markd // Added ifskill // // 267 10/17/98 8:11p Jimdose // Changed Damage to DamgeEvent // // 266 10/15/98 3:39p Markd // added forcefield ability // // 265 10/14/98 10:21p Markd // Added debug code for AnimateFrame // // 264 10/13/98 11:13p Markd // Added hurt and mutate support // // 263 10/13/98 5:25p Markd // Added UseBoundingBoxEvent // // 262 10/11/98 8:50p Jimdose // Added RandomGlobalEntitySound and RandomGlobalEntitySoundEvent // // 261 10/10/98 10:37p Markd // made it so that targetnames with "$" could not be entered // // 260 10/10/98 9:13p Markd // Took out SetAliasPrefix // // 259 10/10/98 3:24a Jimdose // changed team to moveteam // // 258 10/09/98 10:18p Jimdose // made setSize check mins and maxs in the edict instead of the shadow // variables in Entity to check if it should change the size // // 257 10/09/98 8:58p Aldie // Move air_finished to player // // 256 10/09/98 8:02p Jimdose // made SetModel only post processinitcommands when LoadingSavegame is false // // 255 10/08/98 7:40p Aldie // Added minlight // // 254 10/08/98 7:25p Aldie // minlight, gravity, lightoffset // // 253 10/07/98 11:46p Jimdose // Disabled ProcessInitCommands when loading savegames // made setModel use str instead of char array // // 252 10/06/98 9:59p Aldie // Added an oxygenator // // 251 10/06/98 9:39p Markd // removed last_origin // // 250 10/05/98 11:30p Markd // Changed sound radius to header // // 249 10/04/98 10:28p Aldie // Added multiple weapon changes. Damage, flashes, quantum stuff // // 248 10/03/98 1:09p Aldie // Added rendereffects flag // // 247 10/02/98 10:59p Jimdose // Added SetEntNum // // 246 10/01/98 7:59p Markd // Made dialog use NO_PHS // // 245 9/23/98 11:00p Markd // put in some garbage collection on stuff that wasn't freed up // // 244 9/22/98 5:11p Jimdose // Changed the radius of most of the sound events // // 243 9/22/98 3:21p Markd // put in parentmode lockout for blood and gibs // // 242 9/22/98 2:59p Aldie // Added effects command // // 241 9/20/98 7:11p Aldie // Added flags to particles // // 240 9/20/98 3:45p Markd // Changed default dialog channel from CHAN_DIALOG to CHAN_DIALOG_SECONDARY // // 239 9/17/98 6:19p Jimdose // Made BroadcastSound use centroid instead of worldorigin for basing it's // radius and phs checks // // 238 9/17/98 10:50a Markd // fixed gun entity not being shown // // 237 9/15/98 6:37p Markd // Added RotatedBounds flag support // // 236 9/12/98 11:27a Markd // fixed check in Animateframe to go to the next animation // // 235 9/11/98 11:25p Markd // cleared out last_animation_time when stopanimating is called // // 234 9/09/98 6:49p Markd // forgot to check to see if num_frames_in_gun_anim was greater than one // // 233 9/09/98 6:45p Markd // put in world weapon model animations // // 232 9/08/98 11:31p Jimdose // Added AnimEvent // // 231 9/08/98 8:11p Markd // Fixed Animation timing so frames weren't skipped // // 230 9/07/98 8:28p Markd // Added fullradius // // 229 8/29/98 9:40p Jimdose // Added call info to G_Trace // // 228 8/29/98 5:27p Markd // added specialfx, replaced misc with specialfx where appropriate // // 227 8/28/98 7:03p Markd // Fixed Broadcast Sound // // 226 8/28/98 4:13p Markd // Increased VoiceSound radius // // 225 8/27/98 9:04p Jimdose // Moved a lot of small functions to the header as inline // Made Centroid a variable // // 224 8/24/98 6:51p Jimdose // Added SetGravityAxis // // 223 8/22/98 9:36p Jimdose // Renamed spawn arg // // 222 8/22/98 8:55p Jimdose // Added support for alternate gravity axis // // 221 8/21/98 3:48p Markd // Added new group "all" behavior // // 220 8/19/98 8:48p Aldie // Reduce lag for weapons and fixed assertion // // 219 8/18/98 11:08p Markd // Added new Alias System // // 218 8/18/98 11:12a Markd // Added "skin" event // // 217 8/15/98 5:30p Jimdose // Made RegisterAlias and RegisterAliasAndCache use a str for storing the name // so we don't have any name length crashes // // 216 8/09/98 5:52p Markd // Attached models are ThrowObjects by default // // 215 8/08/98 8:36p Markd // forgot to return a value in attach function // // 214 8/08/98 8:27p Markd // Added attach and bind check for self binding/attaching // // 213 8/08/98 7:50p Jimdose // changed realWorld to world // // 212 8/07/98 4:21p Aldie // Allow kill from the console. // // 211 8/06/98 10:53p Aldie // Added weapon tweaks and kickback. Also modified blast radius damage and // rocket jumping. // // 210 8/04/98 6:05p Aldie // Added RF_DETAIL flag and removed some dead code // // 209 7/31/98 8:08p Jimdose // Script commands now include flags to indicate cheats and console commands // // 208 7/29/98 2:31p Aldie // Changed health to a float // // 207 7/26/98 1:14a Markd // rename entityflags to flags because we wern't using entityflags in any def // files // // 206 7/25/98 8:18p Markd // Fixed animation bug // // 205 7/25/98 3:57p Markd // Added EV_GotKill // // 204 7/24/98 6:18p Markd // changed some CHAN_AUTO's to CHAN_BODY, changed CHAN_VOICE in dialog to // CHAN_DIALOG // // 203 7/24/98 6:17p Aldie // Dialog checking // // 202 7/23/98 6:17p Aldie // Updated damage system and fixed some damage related bugs. Also put tracers // back to the way they were, and added gib event to funcscriptmodels // // 201 7/22/98 10:41p Aldie // Fixed tracers // // 200 7/21/98 10:42p Markd // Fixed entity tesselation // // 199 7/21/98 10:04p Markd // Added DIE_EXPLODE stuff to entity flags // // 198 7/21/98 9:34p Jimdose // Added AliasExists and PrefixAliasExists // // 197 7/21/98 1:10p Aldie // Added meansofdeath to obituaries // // 196 7/20/98 3:52p Aldie // Made a ProcessInitCommands which is called from the event. // // 195 7/19/98 8:27p Jimdose // Made setModel cancel any EV_ProcessInitCommands events // // 194 7/18/98 11:16p Markd // Added takedamage and nodamage events // // 193 7/18/98 8:05p Markd // Fixed bug with attached models // // 192 7/18/98 4:05p Markd // Added movetype events to attach, detach and attachmodel // // 191 7/18/98 3:52p Markd // Added attach, detach and attach model commands // // 190 7/17/98 3:50p Markd // Added HasAnim method // // 189 7/15/98 11:22p Markd // Added ProcessInitCommands when setting model // // 188 7/14/98 11:35p Markd // Added PHSSound and RandomPHSSound // // 187 7/14/98 4:35p Markd // fixed animation bug // // 186 7/14/98 3:53p Markd // Added proper frame 0 animation if immediately animating when calling // RandomAnimate // // 185 7/13/98 4:59p Aldie // Added dead player bodies with gibbing // // 184 7/11/98 2:48p Markd // Added dialog event // // 183 7/11/98 2:25p Markd // Removed dialog event // // 182 7/10/98 11:11p Markd // Added DialogEvent // // 181 7/09/98 11:52p Markd // fixed greater than 10FPS support // // 180 7/09/98 9:37p Jimdose // Added getParentVector, since some calls to getLocalVector needed the // transform in terms of the parent, and some to the local vector. phew... // hope that's that! :) // // 179 7/09/98 12:55a Markd // put in arbitrary frames per second support for models // // 178 7/09/98 12:41a Markd // Added support for greater than 10FPS // // 177 7/09/98 12:18a Jimdose // As soon as I checked in the file, I realized that getLocalVector was only // incorrect in returning the vector untouched when it didn't have a // bindmaster. getLocalVector is independent of binding, so I fixed that and // returned the bind call to getLocalVector to its original (correct) state. // For binding, either way is correct, but getLocalVector is used in the // physics for calculating a delta move in the coordinate system of the object, // so this is more correct. // // 176 7/09/98 12:11a Jimdose // fixed getLocalVector to do the dot products agains the bindmaster's // orientation instead of the object's. This fixes a bug with binding to // objects that are oriented. // // 175 7/08/98 12:55p Jimdose // Added classname event to clear up warnings when loading def files // // 174 7/03/98 12:00p Aldie // Fixed random animate to post endevent frametime in the future if anim not // found // // 173 6/27/98 2:17p Aldie // Updated CanDamage // // 172 6/25/98 4:58p Markd // Fixed tesselation bug // // 171 6/24/98 12:39p Markd // Added default tesselation percentage // // 170 6/20/98 7:49p Markd // Added location to Killed and Pain events // // 169 6/20/98 3:42p Markd // Changed default damage tesselation // // 168 6/19/98 7:24p Markd // took out initialization of groups by setModel // // 167 6/19/98 4:45p Jimdose // Added Centroid, DistanceTo, and WithinDistance // // 166 6/19/98 10:56a Markd // re-ordered tesselation event // // 165 6/18/98 8:46p Jimdose // Added better event error handling // Added source info to events // // 164 6/18/98 6:14p Markd // forgot to remove a warning // // 163 6/18/98 2:00p Markd // rewrote tesselation code // // 162 6/17/98 7:34p Markd // Fixed weird bug when world has targetname set! // // 161 6/17/98 6:16p Markd // Fixed broadcast sound NumArgs bug // // 160 6/17/98 3:03p Markd // Changed NumArgs back to previous behavior // // 159 6/15/98 8:04p Markd // put in Group_Flags support // // 158 6/13/98 7:32p Markd // put in default tesselation of 10 thick // // 157 6/10/98 7:53p Markd // Made NumArgs behave correctly like argc // // 156 6/10/98 2:10p Aldie // Updated damage function. // // 155 6/09/98 6:41p Markd // made static sounds default to ATTN_IDLE instead of ATTN_STATIC // // 154 6/09/98 4:40p Markd // Added additonal environment mapped effect // // 153 6/08/98 8:20p Markd // When RandomAnimate fails, the animdone event is still posted // // 152 6/08/98 7:21p Aldie // Fixed group command parsing // // 151 6/08/98 5:18p Markd // made group events use '+' and '-' behavior // // 150 6/08/98 5:17p Aldie // Moved defines to qshared. // // 149 6/08/98 4:58p Markd // Added GroupModelEvent for model dynamic modifications // // 148 6/08/98 2:37p Markd // changed some static sound stuff // // 147 6/08/98 1:54p Jimdose // Made sure that animdoneevent was freed if the animation was stopped or // changed // // 146 6/05/98 6:23p Aldie // Added a location string to Damage // // 145 6/04/98 4:36p Markd // made all script sounds not use PHS so that sounds would be sent over no // matter what // // 144 5/27/98 8:35p Markd // decreased health quotient // // 143 5/27/98 7:34p Markd // reduce damage on player by 50% // // 142 5/27/98 4:55p Markd // Added Inflictor message to Killed // // 141 5/26/98 10:54p Markd // Made sound flags SOUND_SYNCH default // // 140 5/26/98 9:40p Markd // Made damage appear instantly // // 139 5/26/98 9:25p Aldie // Added kill event // // 138 5/26/98 8:45p Markd // Initilialize number of groups for model // // 137 5/26/98 4:22p Markd // Added Target registration stuff // // 136 5/26/98 1:29a Markd // fixed rotated bounding boxes // // 135 5/25/98 12:22p Aldie // Inited waterlevel and watertype // // 134 5/25/98 8:50p Markd // Increased damage momentum for E3 // // 133 5/25/98 7:59p Markd // Added RandomPositionedSound // // 132 5/25/98 6:46p Jimdose // Made animateframe, prethink and posthink into functions built into the base // entity class // // 131 5/25/98 4:43p Markd // Added SpawnParticles to entity // // 130 5/24/98 8:55p Jimdose // Changed classname to a const char * // // 129 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 // // 128 5/24/98 4:48p Jimdose // Made char *'s const // // 127 5/24/98 2:47p Markd // Made char *'s into const char *'s // // 126 5/24/98 1:06a Jimdose // Added sound events for ai // // 125 5/23/98 6:47p Aldie // Fixed FADE_OUT // // 124 5/20/98 1:42p Markd // Added proper stopsound behavior // // 123 5/20/98 11:11a Markd // removed char * dependency // // 122 5/15/98 6:46p Markd // patched FLAGS command, also set max_health // // 121 5/13/98 4:44p Jimdose // Changed remove time in FadeOut // Damage checks if inflictor or attacker are NULL. // // 120 5/11/98 5:53p Markd // Added aliascache command // // 119 5/11/98 2:19p Markd // Fixed randomsound stuff // // 118 5/09/98 7:11p Markd // Removed sound parameter from tesselate command // // 117 5/08/98 7:00p Markd // Added FL_DARKEN Support // // 116 5/08/98 2:55p Markd // Put in an additional randomsound method // // 115 5/07/98 11:32p Markd // Removed footstep event and command // // 114 5/06/98 11:22a Markd // Fixed up some sound stuff // // 113 5/04/98 8:31p Markd // Removed cachemodel and cachesound // // 112 5/03/98 8:09p Markd // Fixed calculate bounds bug in creator and also added precaching to // modelindex and soundindex // // 111 5/03/98 7:13p Markd // Added precache to alias event // // 110 5/03/98 4:31p Jimdose // Changed Vector class. No longer includes PointsTo // // 109 5/03/98 2:41p Jimdose // no change // // 107 5/02/98 8:41p Markd // Added cachemodel and cachesound and also entityflags events // // 106 5/02/98 12:49a Jimdose // added scale event // // 105 5/01/98 8:32p Markd // Added some precache comments // // 104 5/01/98 8:17p Jimdose // Init groundsurface to NULL // // 103 5/01/98 7:39p Markd // changed entsound and related functions to ambientsound etc. // // 102 5/01/98 7:32p Jimdose // Added groundplane, groundsurface, groundcontents // // 101 5/01/98 5:02p Markd // temporarily commented out setting of children in setorigin // // 100 5/01/98 11:09a Markd // Added sound to tesselation event // // 99 4/29/98 10:44p Markd // Added positioned_sound and random_sound with more parameters // // 98 4/29/98 5:55p Jimdose // Added "bind" spawn key to allow binding without using scripts // // 97 4/16/98 1:59p Jimdose // Added EndAnimEvent and PrevFrameEvent // // 96 4/14/98 6:55p Markd // Moved SetModel in Entity Constructor, added thickness to tesselation // parameter // // 95 4/14/98 5:25p Markd // Fixed setsize support // // 94 4/10/98 4:55p Jimdose // fixed bug in tesselate // damage no longer affects people in noclip mode // // 93 4/10/98 1:22a Markd // Added FL_TESSELATE damage func // // 92 4/10/98 12:34a Jimdose // RandomSound now uses CHAN_BODY // Got rid of damage_inflictor // // 91 4/09/98 8:45p Jimdose // Made channel non-specific sound functions use CHAN_AUTO instead of // CHAN_VOICE // // 90 4/09/98 1:40p Markd // Added a NextAnim(0) which I accidentally took out before, needed for single // animation stuff // // 89 4/08/98 6:03p Jimdose // Changed momentum from damage // // 88 4/07/98 8:00p Markd // removed defhandle, changed all SINMDL calls to modelindex calls, removed // SINMDL prefix // // 87 4/07/98 5:40p Jimdose // Made animdone events be posted 0 time in future instead of FRAMETIME / 2 // // 86 4/06/98 8:25p Markd // Can't use centroid fix with B-models, only A-models // // 85 4/06/98 7:21p Markd // Fixed PVS tesselation bug, fixed it by sending over centroid instead. // // 84 4/06/98 6:40p Jimdose // BecomeSolid and BecomeNonSolid no longer change the movetype // BecomeSolid now handles a-model and b-model entities // // 83 4/06/98 5:44p Jimdose // Moved the "angles" stuff to Object and Sentient // // 82 4/06/98 12:02a Markd // Grabbed a float as an integer instead // // 81 4/05/98 10:42p Markd // Added Tesselate Event // // 80 4/05/98 10:17p Jimdose // added lastorigin // // 79 4/05/98 9:41p Markd // Added "angles" and "angle" support // // 78 4/05/98 7:19p Aldie // Added dynamic lights. // // 77 4/05/98 1:55a Jimdose // Added SetModelEvent // // 76 4/04/98 7:51p Jimdose // Oops! trace->surface->surfaceinfo is NULL if it's an a-model! :) // // 75 4/04/98 7:29p Jimdose // Added HitSky that takes a generic trace // // 74 4/04/98 6:03p Jimdose // Added HitSky and RandomSound // // 73 4/02/98 4:51p Jimdose // Added animation control events // Made RandomSound default volume to 1 // Made droptofloor accept the max distance to fall // // 72 3/31/98 5:40p Markd // Added StartAnimatingEvent // // 71 3/31/98 4:21p Jimdose // Fixed angle mod // // 70 3/31/98 3:03p Markd // Fixed CalculateBounds and set proper solidtype for models // // 69 3/31/98 2:26p Jimdose // Got rid of some unneeded R_ConcatRotations // // 68 3/31/98 2:04p Jimdose // Fixed binding bug // // 67 3/30/98 11:39p Markd // Added modelIndex function // // 66 3/30/98 11:20p Markd // Added scale support to entity // // 65 3/30/98 11:17p Markd // Added sound and random sound support // // 64 3/30/98 9:14p Markd // Fixed attenuation of footstep sounds // // 63 3/30/98 9:54p Jimdose // Setmodel now prepends "models/" to model names. // // 62 3/30/98 7:30p Markd // Added FootStep method // // 61 3/30/98 3:09p Jimdose // made total_delta based on the entity's scale // // 60 3/29/98 9:01p Markd // Added Frame events and Init Events // // 59 3/29/98 9:38p Jimdose // Changed Killed and Pain to events // Added damage event // last_frame_in_anim wasn't being initialized when the entity was allocated // // 58 3/27/98 7:01p Markd // Added vieworigin and viewangle // // 57 3/26/98 8:24p Jimdose // Added GetBone (not working yet) // Changed groundentity to an edict_t * // // 56 3/25/98 7:13p Markd // Added detach to free event and made sure that parents exist when detaching // // 55 3/25/98 3:24p Markd // Added attach, detach, model binding variables and modified setorigin // // 54 3/24/98 4:54p Jimdose // Changed usage of GetToken to GetString so that script variables can be used // // 53 3/23/98 1:31p Jimdose // Revamped event and command system // // 52 3/18/98 7:49p Jimdose // Updated sound for new sound system // // 51 3/18/98 7:19p Jimdose // Added RandomAnimate // Tweaked animation code // // 50 3/11/98 6:51p Aldie // Added alpha to the render state for an entity. // // 49 3/11/98 11:32a Markd // Added total_delta variable // // 48 3/09/98 2:57p Aldie // Fixed some alpha calculations in entity. // // 47 3/09/98 2:49p Jimdose // Working on euler-quat stuff // // 46 3/07/98 2:04p Markd // Got animation system up and running (no pun intended) // // 45 3/05/98 2:49p Jimdose // Made playsound command work again. // // 44 3/02/98 8:49p Jimdose // Changed the classid parameter of CLASS_DECLARATION to a quoted string so // that you could have a NULL classid. // // 43 3/02/98 5:51p Jimdose // Added entname to edict_t. // Copy classname into entname // No longer set default targetname to edict# // // 42 2/18/98 8:08p Jimdose // Changed calls to getEntityFromScript to GetObject calls // // 41 2/17/98 6:58p Jimdose // Gave entities a default target name. // no longer pass script into interpretCommand // // 40 2/16/98 2:33p Jimdose // Fixed bug in bind where an uninitialized pointer was referenced. // // 39 2/16/98 1:58p Jimdose // Added object teams // Added true hierarchial binding // Fixed bug with spawning using specific edicts. // // 38 2/06/98 5:47p Jimdose // Added link and unlink // Removed touch and think functions // Removed Spawn (all spawning done in constructor) // Added client pointer // No longer initialize mins and maxs to '0 0 0' since it screws up bmodels. // // 37 2/03/98 10:57a Jimdose // Updated to work with Quake 2 engine // Moved initialization to constructor and removed Init function // // 35 12/15/97 11:56a Markd // zeroed out velocity in "notsolid" // // 34 12/15/97 1:34a Jimdose // Decreased the momentum added from damage // // 33 12/13/97 5:42p Markd // Made it so that ProcessNoteCommands no longer uses a static Script variable // // 32 12/12/97 7:19p Markd // Fixed model spawning stuff for decals and *models // // 31 12/12/97 4:27p Markd // Added "soundprefix" // // 30 12/12/97 2:10p Markd // Only calls animcallback if animate is still true // // 29 12/12/97 1:19p Markd // Moved model setArg to before setModel // // 28 12/12/97 1:00p Jimdose // Added warning to AnimateThink when no animation is set // // 27 12/11/97 7:41p Markd // moved note processing into setmodel // // 26 12/11/97 3:34p Markd // Fixed SetArg for "model", set "model" to "" when it was processed // checked for global_loading in registersound // // 25 12/08/97 4:28p Aldie // Changed FadeOut factors so that the blood splats look ok. // // 24 12/06/97 4:54p Markd // Added default spawing behavior // Added alpha, target, targetname, spawnflags, model, origin, angle, angles to // interpretCommand // // 23 12/05/97 3:03p Jimdose // Now responds to EVENT_REMOVE. // Made it so that if "model" is set when spawned then it precaches that model. // // 22 12/03/97 6:40p Markd // changed how alias stuff works for sounds // // 21 11/24/97 6:54p Markd // Added Register Sound and Random Sound, added Setsize // // 20 11/18/97 5:30p Markd // Added weighting in RandomAnimate, also commented out warnings in // InterpretCommands (they were debug messages) // // 19 11/17/97 5:45p Markd // Added $owner command to ProcessFrameNotes routine // // 18 11/15/97 6:53p Markd // Added ProcessNoteCommands, Added RandomAnimate, added animloop_ variables // for animation loop processing // // 17 11/15/97 2:48p Jimdose // Added ProcessEvent call which calls System->ProcessEvent // // 16 11/14/97 4:44p Jimdose // Added PostEvent // // 15 10/30/97 7:42p Jimdose // In Damage, now only add momentum if not a bsp model // // 14 10/29/97 4:19p Jimdose // Added FadeOut. // // 13 10/28/97 4:13p Jimdose // Added interpretCommand to make Entity be controllable by scripts via // ScriptMaster. // // 12 10/27/97 3:30p Jimdose // Removed dependency on quakedef.h // // 11 10/08/97 8:52p Jimdose // Added EVENT_USE to Event() // // 10 10/08/97 6:03p Jimdose // Began vehicle support. // // 9 10/01/97 10:27a Markd // forgot to put break statement next to zero time animation // // 8 10/01/97 10:23a Markd // Fixed Animate bug, some animations had ZERO length animation times, still // investigating that problem // // 7 9/30/97 9:59p Markd // Fixed AnimTime stuff // // 6 9/30/97 5:55p Jimdose // Damaged entities now get velocity from inflictor // // 5 9/30/97 2:39p Markd // Fixed Animate stuff // // 4 9/29/97 6:18p Markd // working on animate // // 3 9/26/97 6:13p Jimdose // Added standard Ritual headers // // DESCRIPTION: // Base class for all enities that are controlled by Sin. If you have any // object that should be called on a periodic basis and it is not an entity, // then you have to have an dummy entity that calls it. // // An entity in Sin is any object that is not part of the world. Any non-world // object that is visible in Sin is an entity, although it is not required that // all entities be visible to the player. Some objects are basically just virtual // constructs that act as an instigator of certain actions, for example, some // triggers are invisible and cannot be touched, but when activated by other // objects can cause things to happen. // // All entities are capable of receiving messages from Sin or from other entities. // Messages received by an entity may be ignored, passed on to their superclass, // or acted upon by the entity itself. The programmer must decide on the proper // action for the entity to take to any message. There will be many messages // that are completely irrelevant to an entity and should be ignored. Some messages // may require certain states to exist and if they are received by an entity when // it these states don't exist may indicate a logic error on the part of the // programmer or map designer and should be reported as warnings (if the problem is // not severe enough for the game to be halted) or as errors (if the problem should // not be ignored at any cost). // #include "entity.h" #include "worldspawn.h" #include "scriptmaster.h" #include "sentient.h" #include "misc.h" #include "specialfx.h" #include "object.h" #include "player.h" CLASS_DECLARATION( Listener, Entity, NULL ); // Player events Event EV_ClientConnect( "client_connect" ); Event EV_ClientDisconnect( "client_disconnect" ); Event EV_ClientKill( "client_kill" ); Event EV_ClientMove( "client_move" ); Event EV_ClientEndFrame( "client_endframe" ); // Generic entity events Event EV_GetEntName( "getentname" ); Event EV_Classname( "classname" ); Event EV_Activate( "doActivate" ); Event EV_Use( "doUse" ); //Event EV_Footstep( "footstep" ); Event EV_FadeOut( "fadeout" ); Event EV_Fade( "fade" ); Event EV_Killed( "killed" ); Event EV_GotKill( "gotkill" ); Event EV_Pain( "pain" ); Event EV_Damage( "damage" ); Event EV_Kill( "kill", EV_CONSOLE ); Event EV_Gib( "gib" ); Event EV_Hurt( "hurt" ); Event EV_CourseAngles( "courseangles" ); Event EV_SmoothAngles( "smoothangles" ); Event EV_TakeDamage( "takedamage" ); Event EV_NoDamage( "nodamage" ); // Physics events Event EV_MoveDone( "movedone" ); Event EV_Touch( "doTouch" ); Event EV_Blocked( "doBlocked" ); Event EV_UseBoundingBox( "usebbox" ); // Animation events Event EV_NewAnim( "animChanged" ); Event EV_LastFrame( "lastFrame" ); Event EV_NextAnim( "nextanim" ); Event EV_NextFrame( "nextframe" ); Event EV_PrevFrame( "prevframe" ); Event EV_SetFrame( "setframe" ); Event EV_StopAnim( "stopanim" ); Event EV_EndAnim( "endanim" ); Event EV_ProcessInitCommands( "processinit" ); Event EV_Attach( "attach" ); Event EV_AttachModel( "attachmodel" ); Event EV_Detach( "detach" ); // script stuff Event EV_Model( "model" ); Event EV_Hide( "hide" ); Event EV_Show( "show" ); Event EV_BecomeSolid( "solid" ); Event EV_BecomeNonSolid( "notsolid" ); Event EV_Ghost( "ghost" ); Event EV_PlaySound( "playsound" ); Event EV_PHSSound( "phssound" ); Event EV_StopSound( "stopsound" ); Event EV_GravityAxis( "gravityaxis", EV_CHEAT ); Event EV_Bind( "bind" ); Event EV_Unbind( "unbind" ); Event EV_JoinTeam( "joinTeam" ); Event EV_QuitTeam( "quitTeam" ); Event EV_SetHealth( "health", EV_CHEAT ); Event EV_SetScale( "scale" ); Event EV_SetSize( "setsize" ); Event EV_SetAlpha( "alpha" ); Event EV_SetOrigin( "origin" ); Event EV_SetTargetName( "targetname" ); Event EV_SetTarget( "target" ); Event EV_SetKillTarget( "killtarget" ); Event EV_SetAngles( "angles" ); Event EV_RegisterAlias( "alias" ); Event EV_RegisterAliasAndCache( "aliascache" ); Event EV_RandomSound( "randomsound" ); Event EV_RandomPHSSound( "randomphssound" ); Event EV_Tesselate( "shatter" ); Event EV_SetMass( "mass" ); //HACK HACK Event EV_EntitySound( "ambientsound" ); Event EV_RandomGlobalEntitySound( "randomglobalambientsound" ); Event EV_RandomEntitySound( "randomambientsound" ); Event EV_StopEntitySound( "stopambientsound" ); Event EV_Anim( "anim" ); Event EV_StartAnimating( "animate" ); Event EV_GroupModelEvent( "group" ); Event EV_DialogEvent( "dialog" ); Event EV_SetSkin( "skin" ); // AI sound events Event EV_WeaponSound( "weaponsound" ); Event EV_MovementSound( "movementsound" ); Event EV_PainSound( "painsound" ); Event EV_DeathSound( "deathsound" ); Event EV_BreakingSound( "breakingsound" ); Event EV_DoorSound( "doorsound" ); Event EV_MutantSound( "mutantsound" ); Event EV_VoiceSound( "voicesound" ); Event EV_MachineSound( "machinesound" ); Event EV_RadioSound( "radiosound" ); Event EV_HeardWeapon( "heardweapon" ); Event EV_HeardMovement( "heardmovement" ); Event EV_HeardPain( "heardpain" ); Event EV_HeardDeath( "hearddeath" ); Event EV_HeardBreaking( "heardbreaking" ); Event EV_HeardDoor( "hearddoor" ); Event EV_HeardMutant( "heardmutant" ); Event EV_HeardVoice( "heardvoice" ); Event EV_HeardMachine( "heardmachine" ); Event EV_HeardRadio( "heardradio" ); // Conditionals Event EV_IfSkill( "ifskill" ); // Lighting Event EV_SetLight( "light" ); Event EV_LightOn( "lightOn" ); Event EV_LightOff( "lightOff" ); Event EV_LightRed( "lightRed" ); Event EV_LightGreen( "lightGreen" ); Event EV_LightBlue( "lightBlue" ); Event EV_LightRadius( "lightRadius" ); Event EV_Lightoffset( "lightoffset" ); Event EV_Minlight( "minlight" ); Event EV_Gravity( "gravity" ); // Entity flag specific Event EV_EntityFlags( "flags" ); Event EV_EntityRenderEffects( "rendereffects" ); Event EV_EntityEffects( "effects" ); // Special Effects Event EV_SpawnParticles( "sparks" ); // Tesselation setup events Event EV_Shatter_MinSize( "shatter_minsize" ); Event EV_Shatter_MaxSize( "shatter_maxsize" ); Event EV_Shatter_Thickness( "shatter_thickness" ); Event EV_Shatter_Percentage( "shatter_percentage" ); Event EV_Mutate( "mutate", EV_CHEAT ); Event EV_Censor( "censor" ); ResponseDef Entity::Responses[] = { { &EV_Damage, ( Response )Entity::DamageEvent }, { &EV_Kill, ( Response )Entity::Kill }, { &EV_FadeOut, ( Response )Entity::FadeOut }, { &EV_Fade, ( Response )Entity::Fade }, { &EV_Hide, ( Response )Entity::EventHideModel }, { &EV_Show, ( Response )Entity::EventShowModel }, { &EV_BecomeSolid, ( Response )Entity::BecomeSolid }, { &EV_BecomeNonSolid, ( Response )Entity::BecomeNonSolid }, { &EV_Ghost, ( Response )Entity::Ghost }, { &EV_PlaySound, ( Response )Entity::PlaySound }, { &EV_StopSound, ( Response )Entity::StopSound }, { &EV_GravityAxis, ( Response )Entity::GravityAxisEvent }, { &EV_Bind, ( Response )Entity::BindEvent }, { &EV_Unbind, ( Response )Entity::EventUnbind }, { &EV_JoinTeam, ( Response )Entity::JoinTeam }, { &EV_QuitTeam, ( Response )Entity::EventQuitTeam }, { &EV_SetHealth, ( Response )Entity::SetHealth }, { &EV_SetSize, ( Response )Entity::SetSize }, { &EV_SetScale, ( Response )Entity::SetScale }, { &EV_SetAlpha, ( Response )Entity::SetAlpha }, { &EV_SetOrigin, ( Response )Entity::SetOrigin }, { &EV_SetTargetName, ( Response )Entity::SetTargetName }, { &EV_SetTarget, ( Response )Entity::SetTarget }, { &EV_SetKillTarget, ( Response )Entity::SetKillTarget }, { &EV_SetAngles, ( Response )Entity::SetAngles }, { &EV_SetMass, ( Response )Entity::SetMassEvent }, { &EV_CourseAngles, ( Response )Entity::CourseAnglesEvent }, { &EV_SmoothAngles, ( Response )Entity::SmoothAnglesEvent }, { &EV_RegisterAlias, ( Response )Entity::RegisterAlias }, { &EV_RegisterAliasAndCache, ( Response )Entity::RegisterAliasAndCache }, { &EV_RandomSound, ( Response )Entity::RandomSound }, { &EV_EntitySound, ( Response )Entity::EntitySound }, { &EV_RandomEntitySound,( Response )Entity::RandomEntitySound }, { &EV_RandomGlobalEntitySound, ( Response )Entity::RandomGlobalEntitySoundEvent }, { &EV_StopEntitySound, ( Response )Entity::StopEntitySound }, { &EV_Anim, ( Response )Entity::AnimEvent }, { &EV_StartAnimating, ( Response )Entity::StartAnimatingEvent }, { &EV_NextAnim, ( Response )Entity::NextAnimEvent }, { &EV_NextFrame, ( Response )Entity::NextFrameEvent }, { &EV_PrevFrame, ( Response )Entity::PrevFrameEvent }, { &EV_SetFrame, ( Response )Entity::SetFrameEvent }, { &EV_StopAnim, ( Response )Entity::StopAnimatingEvent }, { &EV_EndAnim, ( Response )Entity::EndAnimEvent }, { &EV_Model, ( Response )Entity::SetModelEvent }, { &EV_SetLight, ( Response )Entity::SetLight }, { &EV_LightOn, ( Response )Entity::LightOn }, { &EV_LightOff, ( Response )Entity::LightOff }, { &EV_LightRed, ( Response )Entity::LightRed }, { &EV_LightGreen, ( Response )Entity::LightGreen }, { &EV_LightBlue, ( Response )Entity::LightBlue }, { &EV_LightRadius, ( Response )Entity::LightRadius }, { &EV_Tesselate, ( Response )Entity::Tesselate }, { &EV_EntityFlags, ( Response )Entity::Flags }, { &EV_EntityEffects, ( Response )Entity::Effects }, { &EV_EntityRenderEffects, ( Response )Entity::RenderEffects }, { &EV_RandomPHSSound, ( Response )Entity::RandomPHSSound }, { &EV_PHSSound, ( Response )Entity::PHSSound }, { &EV_WeaponSound, ( Response )Entity::WeaponSound }, { &EV_MovementSound, ( Response )Entity::MovementSound }, { &EV_PainSound, ( Response )Entity::PainSound }, { &EV_DeathSound, ( Response )Entity::DeathSound }, { &EV_BreakingSound, ( Response )Entity::BreakingSound }, { &EV_DoorSound, ( Response )Entity::DoorSound }, { &EV_MutantSound, ( Response )Entity::MutantSound }, { &EV_VoiceSound, ( Response )Entity::VoiceSound }, { &EV_MachineSound, ( Response )Entity::MachineSound }, { &EV_RadioSound, ( Response )Entity::RadioSound }, { &EV_SpawnParticles, ( Response )Entity::SpawnParticles }, { &EV_GroupModelEvent, ( Response )Entity::GroupModelEvent }, { &EV_DialogEvent, ( Response )Entity::DialogEvent }, { &EV_ProcessInitCommands,( Response )Entity::ProcessInitCommandsEvent }, { &EV_Attach, ( Response )Entity::AttachEvent }, { &EV_AttachModel, ( Response )Entity::AttachModelEvent }, { &EV_Detach, ( Response )Entity::DetachEvent }, { &EV_TakeDamage, ( Response )Entity::TakeDamageEvent }, { &EV_NoDamage, ( Response )Entity::NoDamageEvent }, { &EV_SetSkin, ( Response )Entity::SetSkinEvent }, { &EV_Lightoffset, ( Response )Entity::Lightoffset }, { &EV_Minlight, ( Response )Entity::Minlight }, { &EV_Gravity, ( Response )Entity::Gravity }, { &EV_Shatter_MinSize, ( Response )Entity::SetShatterMinSize }, { &EV_Shatter_MaxSize, ( Response )Entity::SetShatterMaxSize }, { &EV_Shatter_Thickness,( Response )Entity::SetShatterThickness }, { &EV_Shatter_Percentage,( Response )Entity::SetShatterPercentage }, { &EV_UseBoundingBox, ( Response )Entity::UseBoundingBoxEvent }, { &EV_Hurt, ( Response )Entity::HurtEvent }, { &EV_IfSkill, ( Response )Entity::IfSkillEvent }, { &EV_GetEntName, ( Response )Entity::GetEntName }, { &EV_Censor, ( Response )Entity::Censor }, { NULL, NULL } }; Entity::Entity() { const char *m; Event *ev; int minlight; classname = this->getClassname(); if ( game.force_entnum ) { game.force_entnum = false; edict = &g_edicts[ game.spawn_entnum ]; LL_Remove( edict, next, prev ); G_InitEdict( edict ); LL_Add( &active_edicts, edict, next, prev ); } else { edict = G_Spawn (); } client = edict->client; edict->entity = this; entnum = edict->s.number; m = G_GetSpawnArg( "classname" ); if ( m ) { strncpy( edict->entname, m, sizeof( edict->entname ) - 1 ); } // spawning variables spawnflags = G_GetIntArg( "spawnflags" ); if ( spawnflags & SPAWNFLAG_DETAIL ) { edict->s.renderfx |= RF_DETAIL; } // rendering variables setAlpha( G_GetFloatArg( "alpha", 1.0f ) ); setScale( G_GetFloatArg( "scale", 1.0f ) ); minlight = G_GetIntArg( "minlight", 0 ); if ( minlight ) edict->s.renderfx |= RF_MINLIGHT; edict->s.lightofs = G_GetFloatArg( "lightoffset", 0 ); if ( edict->s.lightofs ) edict->s.renderfx |= RF_LIGHTOFFSET; viewheight = 0; light_level = 0; // Animation variables next_anim = -1; next_frame = -1; frame_delta = "0 0 0"; next_anim_delta = "0 0 0"; next_anim_time = 0; total_delta = "0 0 0"; animDoneEvent = NULL; animating = false; last_frame_in_anim = 0; last_animation_time = -1; num_frames_in_gun_anim = 0; // team variables teamchain = NULL; teammaster = NULL; m = G_GetSpawnArg( "team" ); if ( m ) { moveteam = str( m ); } // physics variables contents = 0; mass = 0; gravity = 1.0; groundentity = NULL; groundsurface = NULL; groundentity_linkcount = 0; bindmaster = NULL; velocity = vec_zero; avelocity = vec_zero; SetGravityAxis( G_GetIntArg( "gravityaxis", 0 ) ); // model binding variables numchildren = 0; memset( &children, 0, sizeof( children ) ); setOrigin( G_GetSpawnArg( "origin" ) ); worldorigin.copyTo( edict->s.old_origin ); setMoveType( MOVETYPE_NONE ); setSolidType( SOLID_NOT ); // targeting variables SetTargetName( G_GetSpawnArg( "targetname" ) ); SetTarget( G_GetSpawnArg( "target" ) ); // Character state health = 0; max_health = 0; deadflag = DEAD_NO; flags = 0; // underwater variables watertype = 0; waterlevel = 0; // Pain and damage variables takedamage = DAMAGE_NO; enemy = NULL; pain_finished = 0; damage_debounce_time = 0; m = G_GetSpawnArg( "model" ); if ( m ) { setModel( m ); } // // see if we have a mins and maxs set for this model // if ( gi.IsModel( edict->s.modelindex ) && !mins.length() && !maxs.length()) { vec3_t tempmins, tempmaxs; gi.CalculateBounds( edict->s.modelindex, edict->s.scale, tempmins, tempmaxs ); setSize( tempmins, tempmaxs ); } // // get the number of groups for this model // edict->s.numgroups = gi.NumGroups( edict->s.modelindex ); m = G_GetSpawnArg( "bind" ); if ( m ) { str name; ev = new Event( EV_Bind ); // construct an object name name = "$"; name += m; ev->AddString( name ); // Wait until all entities are spawned. PostEvent( ev, 0 ); } // // initialize tesselation variables // tess_max_size = size.length() / 4; tess_min_size = tess_max_size / 3; if ( tess_min_size < 8 ) { tess_min_size = 8; } if ( tess_max_size <= tess_min_size ) { tess_max_size = tess_min_size * 2; } tess_thickness = tess_min_size; tess_percentage = TESS_DEFAULT_PERCENT; } Entity::~Entity() { Container bindlist; Entity *ent; int num; int i; // unbind any entities that are bound to me // can't unbind within this loop, so make an array // and unbind them outside of it. num = 0; for( ent = teamchain; ent; ent = ent->teamchain ) { if ( ent->bindmaster == this ) { bindlist.AddObject( ent ); } } num = bindlist.NumObjects(); for( i = 1; i <= num; i++ ) { bindlist.ObjectAt( i )->unbind(); } bindlist.FreeObjectList(); unbind(); quitTeam(); detach(); // // go through and set our children // num = numchildren; for( i = 0; ( i < MAX_MODEL_CHILDREN ) && num; i++ ) { if ( !children[ i ] ) { continue; } ent = ( Entity * )G_GetEntity( children[ i ] ); ent->PostEvent( EV_Remove, 0 ); num--; } if ( targetname.length() && world ) { world->RemoveTargetEntity( targetname, this ); } G_FreeEdict( edict ); } EXPORT_FROM_DLL void Entity::SetEntNum ( int num ) { if ( edict ) { G_FreeEdict( edict ); } edict = &g_edicts[ num ]; LL_Remove( edict, next, prev ); G_InitEdict( edict ); LL_Add( &active_edicts, edict, next, prev ); client = edict->client; edict->entity = this; entnum = num; } EXPORT_FROM_DLL void Entity::GetEntName ( Event *ev ) { strncpy( edict->entname, getClassname(), sizeof( edict->entname ) - 1 ); } EXPORT_FROM_DLL void Entity::SetTarget ( const char *text ) { if ( text ) { target = text; } else { target = ""; } } EXPORT_FROM_DLL void Entity::SetTargetName ( const char *text ) { if ( targetname.length() && world ) { world->RemoveTargetEntity( targetname, this ); } if ( text ) { if ( text[ 0 ] == '$' ) text++; targetname = text; } else { targetname = ""; } if ( targetname.length() && world ) { world->AddTargetEntity( targetname, this ); } } EXPORT_FROM_DLL void Entity::SetKillTarget ( const char *text ) { if ( text ) { killtarget = text; } else { killtarget = ""; } } EXPORT_FROM_DLL int Entity::modelIndex ( const char *mdl ) { str name; assert( mdl ); if ( !mdl ) { return 0; } // Prepend 'models/' to make things easier if ( !strchr( mdl, '*' ) && !strchr( mdl, '\\' ) && !strchr( mdl, '/' ) ) { name = "models/"; } name += mdl; return gi.modelindex( name.c_str() ); } EXPORT_FROM_DLL void Entity::setModel ( const char *mdl ) { str temp; if ( !mdl ) { mdl = ""; } // Prepend 'models/' to make things easier temp = ""; if ( !strchr( mdl, '*' ) && !strchr( mdl, '\\' ) && !strchr( mdl, '/' ) ) { temp = "models/"; } temp += mdl; // we use a temp string so that if model was passed into here, we don't // accidentally free up the string that we're using in the process. model = temp; gi.setmodel( edict, model.c_str() ); if ( gi.IsModel( edict->s.modelindex ) ) { Event *ev; edict->s.numgroups = gi.NumGroups( edict->s.modelindex ); if ( !LoadingSavegame ) { CancelEventsOfType( EV_ProcessInitCommands ); ev = new Event( EV_ProcessInitCommands ); ev->AddInteger( edict->s.modelindex ); PostEvent( ev, 0 ); } } // Sanity check to see if we're expecting a B-Model assert( !( ( edict->solid == SOLID_BSP ) && !edict->s.modelindex ) ); if ( ( edict->solid == SOLID_BSP ) && !edict->s.modelindex ) { const char *name; name = getClassID(); if ( !name ) { name = getClassname(); } gi.dprintf( "%s with SOLID_BSP and no model - '%s'(%d)\n", name, targetname.c_str(), entnum ); // Make it non-solid so that the collision code doesn't kick us out. setSolidType( SOLID_NOT ); } mins = edict->mins; maxs = edict->maxs; size = edict->size; } EXPORT_FROM_DLL void Entity::ProcessInitCommands ( int index ) { sinmdl_cmd_t *cmds; if ( LoadingSavegame ) { // Don't process init commands when loading a savegame since // it will cause items to be added to inventories unnecessarily. // All variables affected by the init commands will be set // by the unarchive functions. return; } cmds = gi.InitCommands( index ); if (cmds) { int i, j; Event *event; for (i=0;inum_cmds;i++) { event = new Event( cmds->cmds[i].args[0] ); for(j=1;jcmds[i].num_args;j++) { event->AddToken( cmds->cmds[i].args[j] ); } ProcessEvent( event ); } } } EXPORT_FROM_DLL void Entity::ProcessInitCommandsEvent ( Event *ev ) { int index; index = ev->GetInteger( 1 ); ProcessInitCommands( index ); } EXPORT_FROM_DLL void Entity::EventHideModel ( Event *ev ) { hideModel(); } EXPORT_FROM_DLL void Entity::EventShowModel ( Event *ev ) { showModel(); } EXPORT_FROM_DLL void Entity::setAlpha ( float alpha ) { if ( alpha > 1.0f ) { alpha = 1.0f; } if ( alpha < 0 ) { alpha = 0; } translucence = 1.0f - alpha; edict->s.alpha = alpha; edict->s.renderfx &= ~RF_TRANSLUCENT; if ( ( translucence > 0 ) && ( translucence <= 1.0 ) ) { edict->s.renderfx |= RF_TRANSLUCENT; } } EXPORT_FROM_DLL void Entity::setScale ( float scale ) { if ( scale > 255.0f ) { scale = 255.0f; } if ( scale < 0.004f ) { scale = 0.004f; } edict->s.scale = scale; } EXPORT_FROM_DLL void Entity::setSolidType ( solid_t type ) { if ( ( !LoadingSavegame ) && ( type == SOLID_BSP ) && ( this != world ) && ( !model.length() || ( ( model[ 0 ] != '*' ) && ( !strstr( model.c_str(), ".bsp" ) ) ) ) ) { error( "setSolidType", "SOLID_BSP entity at x%.2f y%.2f z%.2f with no BSP model", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] ); } edict->solid = type; link(); edict->svflags &= ~SVF_NOCLIENT; if ( hidden() ) { edict->svflags |= SVF_NOCLIENT; } } EXPORT_FROM_DLL void Entity::setSize ( Vector min, Vector max ) { Vector delta; if ( flags & FL_ROTATEDBOUNDS ) { vec3_t tempmins, tempmaxs; float mat[3][3]; // // rotate the mins and maxs for the model // min.copyTo( tempmins ); max.copyTo( tempmaxs ); VectorCopy( orientation[ 0 ], mat[ 0 ] ); VectorNegate( orientation[ 1 ], mat[ 1 ] ); VectorCopy( orientation[ 2 ], mat[ 2 ] ); CalculateRotatedBounds2( mat, tempmins, tempmaxs ); mins = Vector( tempmins ); maxs = Vector( tempmaxs ); size = max - min; mins.copyTo( edict->mins ); maxs.copyTo( edict->maxs ); size.copyTo( edict->size ); mins.copyTo( edict->fullmins ); maxs.copyTo( edict->fullmaxs ); edict->fullradius = size.length() * 0.5; } else { if ( ( min == edict->mins ) && ( max == edict->maxs ) ) { return; } mins = min; maxs = max; size = max - min; mins.copyTo( edict->mins ); maxs.copyTo( edict->maxs ); size.copyTo( edict->size ); // // get the full mins and maxs for this model // if ( gi.IsModel( edict->s.modelindex ) ) { Vector delta; vec3_t fmins; vec3_t fmaxs; const gravityaxis_t &grav = gravity_axis[ gravaxis ]; gi.CalculateBounds( edict->s.modelindex, edict->s.scale, fmins, fmaxs ); edict->fullmins[ 0 ] = fmins[ grav.x ]; edict->fullmaxs[ 0 ] = fmaxs[ grav.x ]; if ( grav.sign > 0 ) { edict->fullmins[ 1 ] = fmins[ grav.y ]; edict->fullmins[ 2 ] = fmins[ grav.z ]; edict->fullmaxs[ 1 ] = fmaxs[ grav.y ]; edict->fullmaxs[ 2 ] = fmaxs[ grav.z ]; } else { edict->fullmins[ 1 ] = -fmaxs[ grav.y ]; edict->fullmins[ 2 ] = -fmaxs[ grav.z ]; edict->fullmaxs[ 1 ] = -fmins[ grav.y ]; edict->fullmaxs[ 2 ] = -fmins[ grav.z ]; } delta = Vector( edict->fullmaxs ) - Vector( edict->fullmins ); edict->fullradius = delta.length() * 0.5f; } else { mins.copyTo( edict->fullmins ); maxs.copyTo( edict->fullmaxs ); edict->fullradius = size.length() * 0.5; } } link(); } EXPORT_FROM_DLL Vector Entity::getLocalVector ( Vector vec ) { Vector pos; pos[ 0 ] = vec * orientation[ 0 ]; pos[ 1 ] = vec * orientation[ 1 ]; pos[ 2 ] = vec * orientation[ 2 ]; return pos; } EXPORT_FROM_DLL Vector Entity::getParentVector ( Vector vec ) { Vector pos; if ( !bindmaster ) { return vec; } pos[ 0 ] = vec * bindmaster->orientation[ 0 ]; pos[ 1 ] = vec * bindmaster->orientation[ 1 ]; pos[ 2 ] = vec * bindmaster->orientation[ 2 ]; return pos; } EXPORT_FROM_DLL void Entity::link ( void ) { gi.linkentity( edict ); absmin = edict->absmin; absmax = edict->absmax; centroid = ( absmin + absmax ) * 0.5; centroid.copyTo( edict->centroid ); // If this has a parent, then set the areanum the same // as the parent's if ( edict->s.parent ) { edict->areanum = g_edicts[ edict->s.parent ].areanum; } } EXPORT_FROM_DLL void Entity::setOrigin ( Vector org ) { Entity * ent; int i, num; origin = org; if ( bindmaster ) { MatrixTransformVector( origin.vec3(), bindmaster->orientation, worldorigin.vec3() ); worldorigin += bindmaster->worldorigin; worldorigin.copyTo( edict->s.vieworigin ); } else if ( edict->s.parent ) { org.copyTo( edict->s.vieworigin ); ent = ( Entity * )G_GetEntity( edict->s.parent ); worldorigin = ent->centroid; } else { worldorigin = origin; worldorigin.copyTo( edict->s.vieworigin ); } worldorigin.copyTo( edict->s.origin ); link(); // // go through and set our children // num = numchildren; for( i = 0; ( i < MAX_MODEL_CHILDREN ) && num; i++ ) { if ( !children[ i ] ) { continue; } ent = ( Entity * )G_GetEntity( children[ i ] ); ent->setOrigin( ent->origin ); num--; } } EXPORT_FROM_DLL qboolean Entity::GetBone ( const char *name, Vector *pos, Vector *forward, Vector *right, Vector *up ) { vec3_t trans[ 3 ]; vec3_t p1, p2; vec3_t orient; int groupindex; int tri_num; // get the bone information if ( !gi.GetBoneInfo( edict->s.modelindex, name, &groupindex, &tri_num, orient) ) { return false; } if ( !gi.GetBoneTransform( edict->s.modelindex, groupindex, tri_num, orient, edict->s.anim, edict->s.frame, edict->s.scale, trans, p1 ) ) { return false; } if ( forward || right || up ) { R_ConcatRotations( trans, orientation, trans ); } if ( pos ) { MatrixTransformVector( p1, orientation, p2 ); *pos = Vector( p2 ); } if ( forward ) { *forward = Vector( trans[ 0 ] ); } if ( right ) { *right = Vector( trans[ 1 ] ); } if ( up ) { *up = Vector( trans[ 2 ] ); } return true; } EXPORT_FROM_DLL void Entity::setAngles ( Vector ang ) { Entity * ent; float mat[3][3]; int num,i; angles[ 0 ] = angmod( ang[ 0 ] ); angles[ 1 ] = angmod( ang[ 1 ] ); angles[ 2 ] = angmod( ang[ 2 ] ); if ( bindmaster ) { AnglesToMat( angles.vec3(), mat ); R_ConcatRotations( mat, bindmaster->orientation, orientation ); MatrixToEulerAngles( orientation, worldangles.vec3() ); worldangles.copyTo( edict->s.viewangles ); } else if (edict->s.parent) { float trans[3][3]; float local_trans[3][3]; vec3_t p1; ent = ( Entity * )G_GetEntity( edict->s.parent ); ang.copyTo( edict->s.viewangles ); if ( gi.GetBoneTransform( ent->edict->s.modelindex, edict->s.bone.group_num, edict->s.bone.tri_num, edict->s.bone.orientation, ent->edict->s.anim, ent->edict->s.frame, ent->edict->s.scale, trans, p1 ) ) { AnglesToMat( angles.vec3(), mat ); R_ConcatRotations( mat, trans, local_trans ); R_ConcatRotations( local_trans, ent->orientation, orientation ); MatrixToEulerAngles( orientation, worldangles.vec3() ); } } else { worldangles = angles; AnglesToMat( worldangles.vec3(), orientation ); worldangles.copyTo( edict->s.viewangles ); } worldangles.copyTo( edict->s.angles ); // Fill the edicts matrix VectorCopy( orientation[ 0 ], edict->s.mat[ 0 ] ); VectorCopy( orientation[ 1 ], edict->s.mat[ 1 ] ); VectorCopy( orientation[ 2 ], edict->s.mat[ 2 ] ); // // go through and set our children // num = numchildren; for (i=0;(i < MAX_MODEL_CHILDREN) && num;i++) { if (!children[i]) continue; ent = ( Entity * )G_GetEntity( children[i] ); ent->setAngles( ent->angles ); num--; } } EXPORT_FROM_DLL qboolean Entity::droptofloor ( float maxfall ) { trace_t trace; Vector end; end = origin; end[ 2 ]-= maxfall; origin += "0 0 1"; trace = G_Trace( origin, mins, maxs, end, this, MASK_SOLID, "Entity::droptofloor" ); if ( trace.fraction == 1 || trace.allsolid ) { groundentity = NULL; return false; } setOrigin( trace.endpos ); groundentity = trace.ent; groundentity_linkcount = trace.ent->linkcount; return true; } void Entity::Damage ( Entity *inflictor, Entity *attacker, int damage, Vector position, Vector direction, Vector normal, int knockback, int dflags, int meansofdeath, int groupnum, int trinum, float damage_multiplier ) { Event *ev; if ( !attacker ) { attacker = world; } if ( !inflictor ) { inflictor = world; } ev = new Event( EV_Damage ); ev->AddInteger( damage ); ev->AddEntity ( inflictor ); ev->AddEntity ( attacker ); ev->AddVector ( position ); ev->AddVector ( direction ); ev->AddVector ( normal ); ev->AddInteger( knockback ); ev->AddInteger( dflags ); ev->AddInteger( meansofdeath ); ev->AddInteger( groupnum ); ev->AddInteger( trinum ); ev->AddFloat ( damage_multiplier ); ProcessEvent ( ev ); } void Entity::DamageEvent ( Event *ev ) { Entity *inflictor; Entity *attacker; int damage; Vector dir; Vector momentum; Event *event; float m; if ( ( takedamage == DAMAGE_NO ) || ( movetype == MOVETYPE_NOCLIP ) ) { return; } damage = ev->GetInteger( 1 ); inflictor = ev->GetEntity( 2 ); attacker = ev->GetEntity( 3 ); // figure momentum add if ( ( inflictor != world ) && ( movetype != MOVETYPE_NONE ) && ( movetype != MOVETYPE_BOUNCE ) && ( movetype != MOVETYPE_PUSH ) && ( movetype != MOVETYPE_STOP ) ) { dir = worldorigin - ( inflictor->worldorigin + ( inflictor->mins + inflictor->maxs ) * 0.5 ); dir.normalize(); if ( mass < 50) { m = 50; } else { m = mass; } momentum = dir * damage * ( 1700.0 / m ); velocity += momentum; } // check for godmode or invincibility if ( flags & FL_GODMODE ) { return; } // Forcefields make objects invulnerable if ( flags & FL_FORCEFIELD ) { float alpha; float radius; Entity *forcefield; // // spawn forcefield // forcefield = new Entity; radius = ( centroid - worldorigin ).length(); forcefield->setModel( "sphere2.def" ); forcefield->setOrigin( centroid ); forcefield->worldorigin.copyTo(forcefield->edict->s.old_origin); forcefield->setMoveType( MOVETYPE_NONE ); forcefield->setSolidType( SOLID_NOT ); forcefield->edict->s.scale = radius / 16; alpha = damage / 100; if ( alpha > 1 ) alpha = 1; if ( alpha < 0.15f ) alpha = 0.15f; forcefield->edict->s.alpha = alpha; forcefield->edict->s.renderfx |= RF_TRANSLUCENT; forcefield->PostEvent( EV_Remove, 0.1f ); return; } // team play damage avoidance //if ( ( global->teamplay == 1 ) && ( edict->team > 0 ) && ( edict->team == attacker->edict->team ) ) // { // return; // } if ( !deathmatch->value && isSubclassOf( Player ) ) { damage *= 0.15; } if ( deadflag ) { // Check for gib. if ( inflictor->isSubclassOf( Projectile ) ) { Event *gibEv; health -= damage; gibEv = new Event( EV_Gib ); gibEv->AddEntity( this ); gibEv->AddFloat( health ); ProcessEvent( gibEv ); } return; } // do the damage health -= damage; if ( health <= 0 ) { if ( attacker ) { event = new Event( EV_GotKill ); event->AddEntity( this ); event->AddInteger( damage ); event->AddEntity( inflictor ); // location based damage event->AddString( ev->GetString( 4 ) ); event->AddInteger( ev->GetInteger( 9 ) ); event->AddInteger( 0 ); attacker->ProcessEvent( event ); } event = new Event( EV_Killed ); event->AddEntity( attacker ); event->AddInteger( damage ); event->AddEntity( inflictor ); // location based damage event->AddString( ev->GetString( 4 ) ); ProcessEvent( event ); return; } if (flags & FL_TESSELATE) { TesselateModel ( this, tess_min_size, tess_max_size, dir, damage, tess_percentage*0.5f, tess_thickness, ev->GetVector( 5 ) ); } if (flags & FL_DARKEN) { edict->s.renderfx |= RF_LIGHTOFFSET; if ( max_health ) { edict->s.lightofs = - ( 40.0f * ( (float)(max_health - health) / (float)max_health ) ); } else { edict->s.lightofs -= damage; } if ( edict->s.lightofs < -127 ) edict->s.lightofs = -127; if ( edict->s.lightofs > 127 ) edict->s.lightofs = 127; } event = new Event( EV_Pain ); event->AddFloat( damage ); event->AddEntity( attacker ); // location based damage event->AddString( ev->GetString( 4 ) ); ProcessEvent( event ); } /* ============ CanDamage Returns true if the inflictor can directly damage the target. Used for explosions and melee attacks. ============ */ qboolean Entity::CanDamage ( Entity *target ) { trace_t trace; Vector pos; // bmodels need special checking because their origin is 0,0,0 if ( target->getMoveType() == MOVETYPE_PUSH ) { pos = ( target->absmin + target->absmax ) * 0.5; trace = G_Trace( origin, vec_origin, vec_origin, pos, this, MASK_SHOT, "Entity::CanDamage 1" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } return false; } trace = G_Trace( origin, vec_origin, vec_origin, target->centroid, this, MASK_SHOT, "Entity::CanDamage 2" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } pos = target->centroid + Vector( 15, 15, 0 ); trace = G_Trace( origin, vec_origin, vec_origin, pos, this, MASK_SHOT, "Entity::CanDamage 3" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } pos = target->centroid + Vector( -15, 15, 0 ); trace = G_Trace( origin, vec_zero, vec_zero, pos, this, MASK_SHOT, "Entity::CanDamage 4" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } pos = target->centroid + Vector( 15, -15, 0 ); trace = G_Trace( origin, vec_zero, vec_zero, pos, this, MASK_SHOT, "Entity::CanDamage 5" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } pos = target->centroid + Vector( -15, -15, 0 ); trace = G_Trace( origin, vec_zero, vec_zero, pos, this, MASK_SHOT, "Entity::CanDamage 6" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } return false; } EXPORT_FROM_DLL qboolean Entity::IsTouching ( Entity *e1 ) { if ( e1->absmin.x > absmax.x ) { return false; } if ( e1->absmin.y > absmax.y ) { return false; } if ( e1->absmin.z > absmax.z ) { return false; } if ( e1->absmax.x < absmin.x ) { return false; } if ( e1->absmax.y < absmin.y ) { return false; } if ( e1->absmax.z < absmin.z ) { return false; } return true; } void Entity::StopAnimating ( void ) { // Cancel all animating events last_animation_time = -1; animating = false; total_delta = vec_zero; if ( animDoneEvent ) { CancelEventsOfType( animDoneEvent ); delete animDoneEvent; animDoneEvent = NULL; } } void Entity::StartAnimating ( void ) { // start animating AnimateFrame(); animating = true; } void Entity::NextAnim ( int animnum ) { if ( ( animnum >= 0 ) && ( animnum < gi.NumAnims( edict->s.modelindex ) ) ) { next_anim = animnum; } else { // bad value return; } // get the next anim delta gi.Anim_Delta( edict->s.modelindex, next_anim, next_anim_delta.vec3() ); next_anim_delta *= edict->s.scale; // get the next anim time next_anim_time = gi.Anim_Time( edict->s.modelindex, next_anim ); NextFrame( 0 ); } void Entity::NextFrame ( int framenum ) { if ( ( framenum >= 0 ) && ( framenum <= last_frame_in_anim ) ) { next_frame = framenum; } else { // bad value return; } } void Entity::AnimateFrame ( void ) { float delta; sinmdl_cmd_t * cmds; Event *ev; int i; int j; // // see if we have already animated this frame // if ( ( level.time == last_animation_time ) && ( next_anim < 0 ) && ( next_frame == edict->s.frame + 1 ) ) { return; } // see if we have an anim change pending if (next_anim >= 0) { edict->s.anim = next_anim; last_frame_in_anim = gi.Anim_NumFrames( edict->s.modelindex, edict->s.anim ) - 1; next_anim = -1; if ( edict->s.gunmodelindex ) { const char * animname; animname = gi.Anim_NameForNum( edict->s.modelindex, edict->s.anim ); // // see if the anim exists in the world model // edict->s.gunanim = gi.Anim_Random( edict->s.gunmodelindex, animname ); if ( edict->s.gunanim < 0 ) { // // see if at least we have an idle // edict->s.gunanim = gi.Anim_Random( edict->s.gunmodelindex, "idle" ); } if ( edict->s.gunanim >= 0 ) { num_frames_in_gun_anim = gi.Anim_NumFrames( edict->s.gunmodelindex, edict->s.gunanim ); } else { edict->s.gunanim = 0; num_frames_in_gun_anim = 0; } } } // see if we have a frame change pending if (next_frame >= 0) { edict->s.frame = next_frame; next_frame = -1; } #if 0 { const char * animname; animname = gi.Anim_NameForNum( edict->s.modelindex, edict->s.anim ); warning( "aframe", "%d anim %s frame %d", entnum, animname, edict->s.frame ); } #endif delta = gi.Frame_Time( edict->s.modelindex, edict->s.anim, edict->s.frame ); if ( !delta ) { delta = FRAMETIME; } next_frame = edict->s.frame + ( int )( ( float )FRAMETIME / delta ); // should never be greater...but just in case if ( ( edict->s.frame >= last_frame_in_anim ) || ( next_frame > last_frame_in_anim ) ) { PostEvent( EV_LastFrame, 0 ); if ( animDoneEvent ) { PostEvent( animDoneEvent, 0 ); animDoneEvent = NULL; } next_frame -= last_frame_in_anim+1; } // get the current frame delta gi.Frame_Delta( edict->s.modelindex, edict->s.anim, edict->s.frame, frame_delta.vec3() ); total_delta += frame_delta * edict->s.scale; cmds = gi.Frame_Commands( edict->s.modelindex, edict->s.anim, edict->s.frame ); if ( cmds ) { for( i = 0; i < cmds->num_cmds; i++ ) { ev = new Event( cmds->cmds[ i ].args[ 0 ] ); for( j = 1; j < cmds->cmds[ i ].num_args; j++ ) { ev->AddToken( cmds->cmds[ i ].args[ j ] ); } ProcessEvent( ev ); } } last_animation_time = level.time; // // check to see if we have a secondary animation system going on here // if ( edict->s.gunmodelindex ) { if ( num_frames_in_gun_anim > 1 ) { edict->s.gunframe = ( edict->s.frame * num_frames_in_gun_anim ) / ( last_frame_in_anim + 1 ); } else { edict->s.gunframe = 0; } } } void Entity::RandomAnimate ( const char *animname, Event *endevent ) { int num; num = gi.Anim_Random( edict->s.modelindex, animname ); // // if we have an event that hasn't been processed, kill the current one // if ( animDoneEvent ) { CancelEventsOfType( animDoneEvent ); delete animDoneEvent; animDoneEvent = NULL; } // // see if we even have a valid animation at all // if ( num == -1 ) { if ( endevent ) { PostEvent( endevent, FRAMETIME ); } animDoneEvent = NULL; return; } NextAnim( num ); animDoneEvent = endevent; if ( !animating ) { StartAnimating(); } last_animation_time = -1; } EXPORT_FROM_DLL void Entity::joinTeam ( Entity *teammember ) { Entity *ent; Entity *master; Entity *prev; Entity *next; if ( teammaster && ( teammaster != this ) ) { quitTeam(); } assert( teammember ); if ( !teammember ) { warning( "joinTeam", "Null entity" ); return; } master = teammember->teammaster; if ( !master ) { master = teammember; teammember->teammaster = teammember; teammember->teamchain = this; // make anyone who's bound to me part of the new team for( ent = teamchain; ent != NULL; ent = ent->teamchain ) { ent->teammaster = master; } } else { // skip past the chain members bound to the entity we're teaming up with prev = teammember; next = teammember->teamchain; if ( bindmaster ) { // if we have a bindmaster, joing after any entities bound to the entity // we're joining while( next && next->isBoundTo( teammember ) ) { prev = next; next = next->teamchain; } } else { // if we're not bound to someone, then put us at the end of the team while( next ) { prev = next; next = next->teamchain; } } // make anyone who's bound to me part of the new team and // also find the last member of my team for( ent = this; ent->teamchain != NULL; ent = ent->teamchain ) { ent->teamchain->teammaster = master; } prev->teamchain = this; ent->teamchain = next; } teammaster = master; flags |= FL_TEAMSLAVE; } EXPORT_FROM_DLL void Entity::quitTeam ( void ) { Entity *ent; if ( !teammaster ) { return; } if ( teammaster == this ) { if ( !teamchain->teamchain ) { teamchain->teammaster = NULL; } else { // make next teammate the teammaster for( ent = teamchain; ent; ent = ent->teamchain ) { ent->teammaster = teamchain; } } teamchain->flags &= ~FL_TEAMSLAVE; } else { assert( flags & FL_TEAMSLAVE ); assert( teammaster->teamchain ); ent = teammaster; while( ent->teamchain != this ) { // this should never happen assert( ent->teamchain ); ent = ent->teamchain; } ent->teamchain = teamchain; if ( !teammaster->teamchain ) { teammaster->teammaster = NULL; } } teammaster = NULL; teamchain = NULL; flags &= ~FL_TEAMSLAVE; } EXPORT_FROM_DLL void Entity::EventQuitTeam ( Event *ev ) { quitTeam(); } qboolean Entity::isBoundTo ( Entity *master ) { Entity *ent; for( ent = bindmaster; ent != NULL; ent = ent->bindmaster ) { if ( ent == master ) { return true; } } return false; } EXPORT_FROM_DLL void Entity::bind ( Entity *master ) { float mat[ 3 ][ 3 ]; float local[ 3 ][ 3 ]; Vector ang; assert( master ); if ( !master ) { warning( "bind", "Null master entity" ); return; } if ( master == this ) { warning( "bind", "Trying to bind to oneself." ); return; } // unbind myself from my master unbind(); bindmaster = master; // We are now separated from our previous team and are either // an individual, or have a team of our own. Now we can join // the new bindmaster's team. Bindmaster must be set before // joining the team, or we will be placed in the wrong position // on the team. joinTeam( master ); // calculate local angles TransposeMatrix( bindmaster->orientation, mat ); R_ConcatRotations( mat, orientation, local ); MatrixToEulerAngles( local, ang.vec3() ); setAngles( ang ); setOrigin( getParentVector( origin - bindmaster->worldorigin ) ); return; } EXPORT_FROM_DLL void Entity::unbind ( void ) { Entity *prev; Entity *next; Entity *last; Entity *ent; if ( !bindmaster ) { return; } //bindmaster = NULL; origin = Vector( edict->s.origin ); angles = Vector( edict->s.angles ); if ( !teammaster ) { bindmaster = NULL; //Teammaster already has been freed return; } // We're still part of a team, so that means I have to extricate myself // and any entities that are bound to me from the old team. // Find the node previous to me in the team prev = teammaster; for( ent = teammaster->teamchain; ent && ( ent != this ); ent = ent->teamchain ) { prev = ent; } // If ent is not pointing to me, then something is very wrong. assert( ent ); if ( !ent ) { error( "unbind", "corrupt team chain\n" ); } // Find the last node in my team that is bound to me. // Also find the first node not bound to me, if one exists. last = this; for( next = teamchain; next != NULL; next = next->teamchain ) { if ( !next->isBoundTo( this ) ) { break; } // Tell them I'm now the teammaster next->teammaster = this; last = next; } // disconnect the last member of our team from the old team last->teamchain = NULL; // connect up the previous member of the old team to the node that // follow the last node bound to me (if one exists). if ( teammaster != this ) { prev->teamchain = next; if ( !next && ( teammaster == prev ) ) { prev->teammaster = NULL; } } else if ( next ) { // If we were the teammaster, then the nodes that were not bound to me are now // a disconnected chain. Make them into their own team. for( ent = next; ent->teamchain != NULL; ent = ent->teamchain ) { ent->teammaster = next; } next->teammaster = next; next->flags &= ~FL_TEAMSLAVE; } // If we don't have anyone on our team, then clear the team variables. if ( teamchain ) { // make myself my own team teammaster = this; } else { // no longer a team teammaster = NULL; } flags &= ~FL_TEAMSLAVE; bindmaster = NULL; } EXPORT_FROM_DLL void Entity::EventUnbind ( Event *ev ) { unbind(); } EXPORT_FROM_DLL void Entity::FadeOut ( Event *ev ) { PostEvent( EV_FadeOut, 0.1f ); edict->s.renderfx |= RF_TRANSLUCENT; translucence += 0.03f; if ( translucence >= 0.96f ) { PostEvent( EV_Remove, 0 ); } setAlpha( 1.0f - translucence ); } EXPORT_FROM_DLL void Entity::Fade ( Event *ev ) { float rate = ev->GetFloat( 1 ); edict->s.renderfx |= RF_TRANSLUCENT; translucence += rate; setAlpha( 1.0f - translucence ); if ( translucence <= 1 ) PostEvent( EV_FadeOut, 0.1f ); } EXPORT_FROM_DLL void Entity::SetMassEvent ( Event *ev ) { mass = ev->GetFloat( 1 ); } void Entity::CheckGround ( void ) { Vector point; trace_t trace; const gravityaxis_t &grav = gravity_axis[ gravaxis ]; if ( flags & ( FL_SWIM | FL_FLY ) ) { return; } if ( velocity[ grav.z ] > 100 ) { groundentity = NULL; return; } // if the hull point one-quarter unit down is solid the entity is on ground point = worldorigin; point[ grav.z ] -= 0.25 * grav.sign; trace = G_Trace( worldorigin, mins, maxs, point, this, MASK_MONSTERSOLID, "Entity::CheckGround" ); // check steepness if ( ( ( trace.plane.normal[ grav.z ] * grav.sign ) <= 0.7 ) && !trace.startsolid ) { groundentity = NULL; return; } groundentity = trace.ent; groundentity_linkcount = trace.ent->linkcount; groundplane = trace.plane; groundsurface = trace.surface; groundcontents = trace.contents; if ( !trace.startsolid && !trace.allsolid ) { setOrigin( trace.endpos ); velocity[ grav.z ] = 0; } } EXPORT_FROM_DLL void Entity::BecomeSolid ( Event *ev ) { if ( ( model.length() ) && ( ( model[ 0 ] == '*' ) || ( strstr( model.c_str(), ".bsp" ) ) ) ) { setSolidType( SOLID_BSP ); } else { setSolidType( SOLID_BBOX ); } } EXPORT_FROM_DLL void Entity::BecomeNonSolid ( Event *ev ) { setSolidType( SOLID_NOT ); } EXPORT_FROM_DLL void Entity::Ghost ( Event *ev ) { // Make not solid, but send still send over whether it is hidden or not setSolidType( SOLID_NOT ); edict->svflags &= ~SVF_NOCLIENT; } EXPORT_FROM_DLL void Entity::PlaySound ( Event *ev ) { char name[ 128 ]; float volume; int channel; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; // // set defaults // name[0] = 0; volume = 1.0f; channel = CHAN_BODY; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = SOUND_SYNCH; for ( i = 1 ; i <= ev->NumArgs() ; i++ ) { switch (i-1) { case 0: strcpy( name, ev->GetString( i ) ); break; case 1: volume = ev->GetFloat( i ); break; case 2: channel = ev->GetInteger( i ); break; case 3: attenuation = ev->GetFloat( i ); break; case 4: pitch = ev->GetFloat( i ); break; case 5: timeofs = ev->GetFloat( i ); break; case 6: fadetime = ev->GetFloat( i ); break; case 7: flags = ev->GetInteger( i ); break; default: break; } } channel |= CHAN_NO_PHS_ADD; sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } EXPORT_FROM_DLL void Entity::StopSound ( Event *ev ) { if (ev->NumArgs() < 1) stopsound( CHAN_BODY ); else stopsound( ev->GetInteger( 1 ) ); } EXPORT_FROM_DLL void Entity::SetGravityAxis ( int axis ) { Vector min; Vector max; if ( ( axis < 0 ) || ( axis > 5 ) ) { axis = 0; } // don't do anything if the axis has been already set if ( axis == gravaxis ) return; edict->s.effects &= ~( EF_GRAVITY_AXIS_0 | EF_GRAVITY_AXIS_1 | EF_GRAVITY_AXIS_2 ); edict->s.effects |= GRAVITYAXIS_TO_EFFECTS( axis ); gravaxis = EFFECTS_TO_GRAVITYAXIS( edict->s.effects ); groundentity = NULL; const gravityaxis_t &grav = gravity_axis[ gravaxis ]; min[ grav.x ] = mins[ 0 ]; min[ grav.y ] = mins[ 1 ] * grav.sign; min[ grav.z ] = mins[ 2 ] * grav.sign; max[ grav.x ] = maxs[ 0 ]; max[ grav.y ] = maxs[ 1 ] * grav.sign; max[ grav.z ] = maxs[ 2 ] * grav.sign; setSize( min, max ); } EXPORT_FROM_DLL void Entity::GravityAxisEvent ( Event *ev ) { SetGravityAxis( ev->GetInteger( 1 ) ); } EXPORT_FROM_DLL void Entity::BindEvent ( Event *ev ) { Entity *ent; ent = ev->GetEntity( 1 ); if ( ent ) { bind( ent ); } } EXPORT_FROM_DLL void Entity::JoinTeam ( Event *ev ) { Entity *ent; ent = ev->GetEntity( 1 ); if ( ent ) { joinTeam( ent ); } } EXPORT_FROM_DLL void Entity::SetLight ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; edict->s.color_r = ev->GetFloat( 1 ); edict->s.color_g = ev->GetFloat( 2 ); edict->s.color_b = ev->GetFloat( 3 ); edict->s.radius = ev->GetFloat( 4 ); } EXPORT_FROM_DLL void Entity::LightOn ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; } EXPORT_FROM_DLL void Entity::LightOff ( Event *ev ) { edict->s.renderfx &= ~RF_DLIGHT; } EXPORT_FROM_DLL void Entity::LightRed ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; edict->s.color_r = ev->GetFloat( 1 ); } EXPORT_FROM_DLL void Entity::LightGreen ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; edict->s.color_g = ev->GetFloat( 1 ); } EXPORT_FROM_DLL void Entity::LightBlue ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; edict->s.color_b = ev->GetFloat( 1 ); } EXPORT_FROM_DLL void Entity::LightRadius ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; edict->s.radius = ev->GetFloat( 1 ); } EXPORT_FROM_DLL void Entity::SetHealth ( Event *ev ) { health = ev->GetFloat( 1 ); max_health = health; } EXPORT_FROM_DLL void Entity::SetSize ( Event *ev ) { Vector min, max; min = ev->GetVector( 1 ); max = ev->GetVector( 2 ); setSize( min, max ); } EXPORT_FROM_DLL void Entity::SetScale ( Event *ev ) { setScale( ev->GetFloat( 1 ) ); } EXPORT_FROM_DLL void Entity::SetAlpha ( Event *ev ) { setAlpha( ev->GetFloat( 1 ) ); } EXPORT_FROM_DLL void Entity::SetOrigin ( Event *ev ) { setOrigin( ev->GetVector( 1 ) ); } EXPORT_FROM_DLL void Entity::SetTargetName ( Event *ev ) { SetTargetName( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetTarget ( Event *ev ) { SetTarget( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetKillTarget ( Event *ev ) { SetKillTarget( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetAngles ( Event *ev ) { setAngles( ev->GetVector( 1 ) ); } EXPORT_FROM_DLL void Entity::CourseAnglesEvent ( Event *ev ) { // Angles will be sent over the net as 8-bit values (default) edict->s.effects &= ~EF_SMOOTHANGLES; } EXPORT_FROM_DLL void Entity::SmoothAnglesEvent ( Event *ev ) { // Angles will be sent over the net as 16-bit values for smoother rotation (or slow rotation) edict->s.effects |= EF_SMOOTHANGLES; } EXPORT_FROM_DLL void Entity::RegisterAlias ( Event *ev ) { if ( ev->NumArgs() < 3 ) { gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), ev->GetString( 2 ), 1 ); } else { gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), ev->GetString( 2 ), ev->GetInteger( 3 ) ); } } EXPORT_FROM_DLL void Entity::RegisterAliasAndCache ( Event *ev ) { int length; str realname; const char * ptr; realname = ev->GetString( 2 ); if ( ev->NumArgs() < 3 ) { gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), realname.c_str(), 1 ); } else { gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), realname.c_str(), ev->GetInteger( 3 ) ); } length = realname.length(); ptr = realname.c_str(); ptr += length - 4; if ( ( length > 4 ) && ( !strcmpi( ptr, ".wav" ) ) ) { gi.soundindex( realname.c_str() ); } else if ( ( length > 4 ) && ( !strcmpi( ptr, ".def" ) ) ) { gi.modelindex( realname.c_str() ); } } EXPORT_FROM_DLL void Entity::positioned_sound ( Vector origin, str soundname, float volume, int channel, int attenuation, float pitch, float timeofs, float fadetime, int flags ) { if ( soundname.length() ) { gi.positioned_sound( worldorigin.vec3(), edict, channel, gi.soundindex( soundname.c_str() ), volume, attenuation, timeofs, pitch, fadetime, flags ); } else { warning( "sound", "Null sample pointer" ); } } EXPORT_FROM_DLL void Entity::RandomPositionedSound ( Vector origin, str soundname, float volume, int channel, int attenuation, float pitch, float timeofs, float fadetime, int flags ) { const char * name; name = gi.GlobalAlias_FindRandom( soundname.c_str() ); if ( name ) { positioned_sound( worldorigin, name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } else { warning( "RandomPositionedSound", "Couldn't find alias for %s", soundname.c_str() ); } } EXPORT_FROM_DLL void Entity::sound ( str soundname, float volume, int channel, int attenuation, float pitch, float timeofs, float fadetime, int flags ) { if ( soundname.length() ) { gi.sound( edict, channel, gi.soundindex( soundname.c_str() ), volume, attenuation, timeofs, pitch, fadetime, flags ); } else { warning( "sound", "Null sample pointer" ); } } EXPORT_FROM_DLL void Entity::RandomGlobalSound ( str soundname, float volume, int channel, int attenuation, float pitch, float timeofs, float fadetime, int flags ) { const char * name; name = gi.GlobalAlias_FindRandom( soundname.c_str() ); if ( name ) { sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } else { warning( "RandomGlobalSound", "Couldn't find alias for %s", soundname.c_str() ); } } EXPORT_FROM_DLL void Entity::RandomSound ( str soundname, float volume, int channel, int attenuation, float pitch, float timeofs, float fadetime, int flags ) { str realname; realname = GetRandomAlias( soundname ); sound( realname, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } EXPORT_FROM_DLL void Entity::RandomSound ( Event *ev ) { str name; float volume; int channel; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; // // set defaults // volume = 1.0f; channel = CHAN_BODY; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = SOUND_SYNCH; for ( i = 1 ; i <= ev->NumArgs() ; i++ ) { switch (i-1) { case 0: name = ev->GetString( i ); break; case 1: volume = ev->GetFloat( i ); break; case 2: channel = ev->GetInteger( i ); break; case 3: attenuation = ev->GetFloat( i ); break; case 4: pitch = ev->GetFloat( i ); break; case 5: timeofs = ev->GetFloat( i ); break; case 6: fadetime = ev->GetFloat( i ); break; case 7: flags = ev->GetInteger( i ); break; default: break; } } channel |= CHAN_NO_PHS_ADD; RandomSound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } EXPORT_FROM_DLL void Entity::RandomPHSSound ( Event *ev ) { str name; float volume; int channel; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; // // set defaults // volume = 1.0f; channel = CHAN_BODY; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = SOUND_SYNCH; for ( i = 1 ; i <= ev->NumArgs() ; i++ ) { switch (i-1) { case 0: name = ev->GetString( i ); break; case 1: volume = ev->GetFloat( i ); break; case 2: channel = ev->GetInteger( i ); break; case 3: attenuation = ev->GetFloat( i ); break; case 4: pitch = ev->GetFloat( i ); break; case 5: timeofs = ev->GetFloat( i ); break; case 6: fadetime = ev->GetFloat( i ); break; case 7: flags = ev->GetInteger( i ); break; default: break; } } RandomSound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } EXPORT_FROM_DLL void Entity::PHSSound ( Event *ev ) { char name[ 128 ]; float volume; int channel; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; // // set defaults // name[0] = 0; volume = 1.0f; channel = CHAN_BODY; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = SOUND_SYNCH; for ( i = 1 ; i <= ev->NumArgs() ; i++ ) { switch (i-1) { case 0: strcpy( name, ev->GetString( i ) ); break; case 1: volume = ev->GetFloat( i ); break; case 2: channel = ev->GetInteger( i ); break; case 3: attenuation = ev->GetFloat( i ); break; case 4: pitch = ev->GetFloat( i ); break; case 5: timeofs = ev->GetFloat( i ); break; case 6: fadetime = ev->GetFloat( i ); break; case 7: flags = ev->GetInteger( i ); break; default: break; } } sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } EXPORT_FROM_DLL void Entity::EntitySound ( Event *ev ) { edict->s.sound = gi.soundindex( ev->GetString( 1 ) ); if ( ev->NumArgs() > 1 ) { int attenuation; attenuation = ev->GetInteger( 2 ); if (attenuation > 3) attenuation = 3; if (attenuation < 0) attenuation = 0; edict->s.sound |= attenuation<<14; } else { edict->s.sound |= ATTN_IDLE<<14; } } EXPORT_FROM_DLL void Entity::StopEntitySound ( Event *ev ) { edict->s.sound = 0; } EXPORT_FROM_DLL void Entity::RandomEntitySound ( Event *ev ) { const char * alias; const char * soundname; alias = ev->GetString( 1 ); soundname = gi.Alias_FindRandom( edict->s.modelindex, alias ); edict->s.sound = gi.soundindex( soundname ); if ( ev->NumArgs() > 1 ) { int attenuation; attenuation = ev->GetInteger( 2 ); if (attenuation > 3) attenuation = 3; if (attenuation < 0) attenuation = 0; edict->s.sound |= attenuation<<14; } else { edict->s.sound |= ATTN_IDLE<<14; } } EXPORT_FROM_DLL void Entity::RandomGlobalEntitySound ( str soundname, int attenuation ) { const char * name; name = gi.GlobalAlias_FindRandom( soundname.c_str() ); if ( name ) { edict->s.sound = gi.soundindex( name ); bound( attenuation, 0, 3 ); edict->s.sound |= attenuation<<14; } else { warning( "RandomGlobalEntitySound", "Couldn't find alias for %s", soundname.c_str() ); } } EXPORT_FROM_DLL void Entity::RandomGlobalEntitySoundEvent ( Event *ev ) { const char *alias; int attenuation; alias = ev->GetString( 1 ); attenuation = ATTN_IDLE; if ( ev->NumArgs() > 1 ) { attenuation = ev->GetInteger( 2 ); } RandomGlobalEntitySound( alias, attenuation ); } EXPORT_FROM_DLL qboolean Entity::attach ( int parent_entity_num, int group_num, int tri_num, Vector orient ) { int i; Entity * parent; if ( entnum == parent_entity_num ) { warning("attach","Trying to attach to oneself." ); return false; } if (edict->s.parent) detach(); // // get the parent // parent = ( Entity * )G_GetEntity( parent_entity_num ); if (parent->numchildren < MAX_MODEL_CHILDREN) { // // find a free spot in the parent // for ( i=0; i < MAX_MODEL_CHILDREN; i++ ) if (!parent->children[i]) { break; } parent->children[i] = entnum; parent->numchildren++; edict->s.parent = parent_entity_num; edict->s.bone.group_num = group_num; edict->s.bone.tri_num = tri_num; VectorCopy( orient.vec3(), edict->s.bone.orientation ); setOrigin( origin ); return true; } return false; } EXPORT_FROM_DLL void Entity::detach ( void ) { int i; int num; Entity * parent; if (!edict->s.parent) return; parent = ( Entity * )G_GetEntity( edict->s.parent ); if (!parent) return; for ( i=0,num = parent->numchildren; i < MAX_MODEL_CHILDREN; i++ ) { if (!parent->children[i]) { continue; } if (parent->children[i] == entnum) { parent->children[i] = 0; parent->numchildren--; break; } num--; if (!num) break; } edict->s.parent = 0; // // i don't think we want to do this automatically, we might later, but for right now lets not // //setOrigin( edict->origin + parent->origin ); } void Entity::AnimEvent ( Event *ev ) { int num; num = gi.Anim_Random( edict->s.modelindex, ev->GetString( 1 ) ); NextAnim( num ); if ( !animating ) { StartAnimating(); } } void Entity::StartAnimatingEvent ( Event *ev ) { StartAnimating(); } void Entity::StopAnimatingEvent ( Event *ev ) { StopAnimating(); } void Entity::EndAnimEvent ( Event *ev ) { PostEvent( EV_LastFrame, 0 ); if ( animDoneEvent ) { PostEvent( animDoneEvent, 0 ); animDoneEvent = NULL; } next_frame = 0; } void Entity::NextAnimEvent ( Event *ev ) { int num; num = gi.Anim_Random( edict->s.modelindex, ev->GetString( 1 ) ); NextAnim( num ); } void Entity::NextFrameEvent ( Event *ev ) { NextFrame( ev->GetInteger( 1 ) ); } void Entity::PrevFrameEvent ( Event *ev ) { edict->s.prevframe = ev->GetInteger( 1 ); } void Entity::SetFrameEvent ( Event *ev ) { int framenum; framenum = ev->GetInteger( 1 ); edict->s.frame = framenum; NextFrame( framenum + 1 ); } void Entity::Tesselate(Event *ev) { Vector origin, dir, temp; Entity * ent; int i, power, min_size, max_size, thickness; float percentage; // dir is 1 // power is 2 // minsize is 3 // maxsize is 4 // percentage is 5 // thickness 6 // entity is 7 // origin 8 // // initialize some variables // ent = this; min_size = TESS_DEFAULT_MIN_SIZE; max_size = TESS_DEFAULT_MIN_SIZE; thickness = min_size; percentage = TESS_DEFAULT_PERCENT; VectorCopy( vec3_origin, origin ); VectorCopy( vec3_origin, dir ); for ( i = 1; i <= ev->NumArgs(); i++ ) { switch( i ) { case 1: temp = ev->GetVector( i ); temp.AngleVectors( &dir, NULL, NULL ); break; case 2: power = ev->GetInteger( i ); break; case 3: min_size = ev->GetInteger( i ); break; case 4: max_size = ev->GetInteger( i ); break; case 5: percentage = ev->GetFloat( i ); break; case 6: thickness = ev->GetInteger( i ); break; case 7: ent = ev->GetEntity( i ); break; case 8: origin = ev->GetVector( i ); break; } } TesselateModel ( ent, min_size, max_size, dir, power, percentage, thickness, origin ); } void Entity::SetShatterMinSize( Event *ev ) { tess_min_size = ev->GetInteger( 1 ); } void Entity::SetShatterMaxSize( Event *ev ) { tess_max_size = ev->GetInteger( 1 ); } void Entity::SetShatterThickness( Event *ev ) { tess_thickness = ev->GetInteger( 1 ); } void Entity::SetShatterPercentage( Event *ev ) { tess_percentage = ev->GetFloat( 1 ) / 100.0f;; } void Entity::Flags( Event *ev ) { const char *flag; int mask; int action; int i; #define FLAG_IGNORE 0 #define FLAG_SET 1 #define FLAG_CLEAR 2 #define FLAG_ADD 3 for ( i = 1; i <= ev->NumArgs(); i++ ) { action = FLAG_IGNORE; flag = ev->GetString( i ); switch( flag[0] ) { case '+': action = FLAG_ADD; flag++; break; case '-': action = FLAG_CLEAR; flag++; break; default: action = FLAG_SET; break; } if ( !stricmp( flag, "blood" ) ) mask = FL_BLOOD; else if ( !stricmp( flag, "sparks" ) ) mask = FL_SPARKS; else if ( !stricmp( flag, "shatter" ) ) mask = FL_TESSELATE; else if ( !stricmp( flag, "blast" ) ) mask = FL_BLASTMARK; else if ( !stricmp( flag, "die_shatter" ) ) mask = FL_DIE_TESSELATE; else if ( !stricmp( flag, "explode" ) ) mask = FL_DIE_EXPLODE; else if ( !stricmp( flag, "die_gibs" ) ) mask = FL_DIE_GIBS; else if ( !stricmp( flag, "darken" ) ) mask = FL_DARKEN; else if ( !stricmp( flag, "forcefield" ) ) mask = FL_FORCEFIELD; else if ( !stricmp( flag, "stealth" ) ) mask = FL_STEALTH; else { action = FLAG_IGNORE; ev->Error( "Unknown flag '%s'", flag ); } switch (action) { case FLAG_SET: // preserver non-configurable bits flags &= (FL_BLOOD - 1); case FLAG_ADD: flags |= mask; break; case FLAG_CLEAR: flags &= ~mask; break; case FLAG_IGNORE: break; } } if ( parentmode->value ) { if ( flags & (FL_BLOOD|FL_DIE_GIBS) ) { flags &= ~FL_BLOOD; flags &= ~FL_DIE_GIBS; } } } void Entity::Effects( Event *ev ) { const char *flag; int mask=0; int action; int i; #define FLAG_IGNORE 0 #define FLAG_SET 1 #define FLAG_CLEAR 2 #define FLAG_ADD 3 for ( i = 1; i <= ev->NumArgs(); i++ ) { action = 0; flag = ev->GetString( i ); switch( flag[0] ) { case '+': action = FLAG_ADD; flag++; break; case '-': action = FLAG_CLEAR; flag++; break; default: action = FLAG_SET; break; } if ( !stricmp( flag, "rotate" ) ) mask = EF_ROTATE; else if ( !stricmp( flag, "rocket" ) ) mask = EF_ROCKET; else if ( !stricmp( flag, "gib" ) ) mask = EF_GIB; else if ( !stricmp( flag, "pulse" ) ) mask = EF_PULSE; else if ( !stricmp( flag, "everyframe" ) ) mask = EF_EVERYFRAME; else if ( !stricmp( flag, "autoanimate" ) ) mask = EF_AUTO_ANIMATE; else { action = FLAG_IGNORE; ev->Error( "Unknown token %s.", flag ); } switch (action) { case FLAG_SET: case FLAG_ADD: edict->s.effects |= mask; break; case FLAG_CLEAR: edict->s.effects &= ~mask; break; case FLAG_IGNORE: break; } } } void Entity::RenderEffects( Event *ev ) { const char *flag; int mask=0; int action; int i; #define FLAG_IGNORE 0 #define FLAG_SET 1 #define FLAG_CLEAR 2 #define FLAG_ADD 3 for ( i = 1; i <= ev->NumArgs(); i++ ) { action = 0; flag = ev->GetString( i ); switch( flag[0] ) { case '+': action = FLAG_ADD; flag++; break; case '-': action = FLAG_CLEAR; flag++; break; default: action = FLAG_SET; break; } if ( !stricmp( flag, "minlight" ) ) mask = RF_MINLIGHT; else if ( !stricmp( flag, "fullbright" ) ) mask = RF_FULLBRIGHT; else if ( !stricmp( flag, "envmapped" ) ) mask = RF_ENVMAPPED; else if ( !stricmp( flag, "glow" ) ) mask = RF_GLOW; else if ( !stricmp( flag, "dontdraw" ) ) mask = RF_DONTDRAW; else { action = FLAG_IGNORE; ev->Error( "Unknown token %s.", flag ); } switch (action) { case FLAG_SET: case FLAG_ADD: edict->s.renderfx |= mask; break; case FLAG_CLEAR: edict->s.renderfx &= ~mask; break; case FLAG_IGNORE: break; } } } void Entity::BroadcastSound ( Event *soundevent, int channel, Event &event, float radius ) { Sentient *ent; Vector delta; Event *ev; str name; float r2; float dist2; float volume; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; int n; #if 0 int count; count = 0; #endif if ( ( ( int )event != ( int )NullEvent ) && !( this->flags & FL_NOTARGET ) ) { r2 = radius * radius; n = SentientList.NumObjects(); for( i = 1; i <= n; i++ ) { ent = SentientList.ObjectAt( i ); if ( ent->deadflag || ( ent == this ) ) { continue; } delta = centroid - ent->centroid; // dot product returns length squared dist2 = delta * delta; if ( ( dist2 <= r2 ) && ( ( edict->areanum == ent->edict->areanum ) || ( gi.AreasConnected( edict->areanum, ent->edict->areanum ) ) ) ) { ev = new Event( event ); ev->AddEntity( this ); ev->AddVector( worldorigin ); ent->PostEvent( ev, 0 ); #if 0 count++; #endif } } } #if 0 gi.dprintf( "Broadcast sound to %d entities\n", count ); #endif if ( !soundevent->NumArgs() ) { return; } // // set defaults // volume = 1.0f; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = 0; for ( i = 1 ; i <= soundevent->NumArgs() ; i++ ) { switch (i-1) { case 0: name = soundevent->GetString( i ); break; case 1: volume = soundevent->GetFloat( i ); break; case 2: attenuation = soundevent->GetFloat( i ); break; case 3: pitch = soundevent->GetFloat( i ); break; case 4: timeofs = soundevent->GetFloat( i ); break; case 5: fadetime = soundevent->GetFloat( i ); break; case 6: flags = soundevent->GetInteger( i ); break; default: break; } } RandomSound( name.c_str(), volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } void Entity::WeaponSound ( Event *ev ) { BroadcastSound( ev, CHAN_WEAPON, EV_HeardWeapon, SOUND_WEAPON_RADIUS ); } void Entity::MovementSound ( Event *ev ) { static int movement_count = 0; // // movement sounds now happen very infrequently, unless this is a client // if ( isClient() || ( movement_count++ > 15 ) ) { BroadcastSound( ev, CHAN_BODY, EV_HeardMovement, SOUND_MOVEMENT_RADIUS ); if ( movement_count > 15 ) movement_count = 0; } } void Entity::PainSound ( Event *ev ) { BroadcastSound( ev, CHAN_VOICE, EV_HeardPain, SOUND_PAIN_RADIUS ); } void Entity::DeathSound ( Event *ev ) { BroadcastSound( ev, CHAN_VOICE, EV_HeardDeath, SOUND_DEATH_RADIUS ); } void Entity::BreakingSound ( Event *ev ) { BroadcastSound( ev, CHAN_AUTO, EV_HeardBreaking, SOUND_BREAKING_RADIUS ); } void Entity::DoorSound ( Event *ev ) { BroadcastSound( ev, CHAN_AUTO, EV_HeardDoor, SOUND_DOOR_RADIUS ); } void Entity::MutantSound ( Event *ev ) { BroadcastSound( ev, CHAN_VOICE, EV_HeardMutant, SOUND_MUTANT_RADIUS ); } void Entity::VoiceSound ( Event *ev ) { BroadcastSound( ev, CHAN_VOICE, EV_HeardVoice, SOUND_VOICE_RADIUS ); } void Entity::MachineSound ( Event *ev ) { BroadcastSound( ev, CHAN_AUTO, EV_HeardMachine, SOUND_MACHINE_RADIUS ); } void Entity::RadioSound ( Event *ev ) { BroadcastSound( ev, CHAN_VOICE, EV_HeardRadio, SOUND_RADIO_RADIUS ); } void Entity::SpawnParticles ( Event *ev ) { int i; Vector norm; int count; int lightstyle; norm = orientation[0]; count = 4; lightstyle = 122; for ( i = 1 ; i <= ev->NumArgs() ; i++ ) { switch( i ) { case 1: norm = ev->GetVector( 1 ); break; case 2: count = ev->GetInteger( 2 ); break; case 3: lightstyle = ev->GetInteger( 3 ); break; case 4: flags = ev->GetInteger( 4 ); default: break; } } Particles( worldorigin, norm, count, lightstyle, flags ); } void Entity::Prethink ( void ) { } void Entity::Postthink ( void ) { } void Entity::SetWaterType ( void ) { qboolean isinwater; watertype = gi.pointcontents( worldorigin.vec3() ); isinwater = watertype & MASK_WATER; if ( isinwater ) { waterlevel = 1; } else { waterlevel = 0; } } void Entity::DamageSkin ( trace_t * trace, float damage ) { int group; group = trace->intersect.parentgroup; if ( !edict->s.groups[ group ] ) { edict->s.groups[ group ]++; } } void Entity::Kill ( Event *ev ) { health = 0; Damage( this, this, 10, worldorigin, vec_zero, vec_zero, 0, 0, MOD_SUICIDE, -1, -1, 1.0f ); } void Entity::GroupModelEvent ( Event *ev ) { const char * group_name; const char * token; int i, group_num, argnum, flags; int mask; int action; qboolean do_all; #define FLAG_IGNORE 0 #define FLAG_SET 1 #define FLAG_CLEAR 2 #define FLAG_ADD 3 do_all = false; // "group" is first group_name = ev->GetString( 1 ); if ( str( group_name ) != str( "all" ) ) { group_num = gi.Group_NameToNum( edict->s.modelindex, group_name ); if (group_num < 0) { ev->Error( "group %s not found.", group_name ); } } else { group_num = 0; do_all = true; } flags = 0; argnum = 2; for ( i = argnum; i <= ev->NumArgs() ; i++ ) { token = ev->GetString( i ); action = 0; switch( token[0] ) { case '+': action = FLAG_ADD; token++; break; case '-': action = FLAG_CLEAR; token++; break; default: action = FLAG_SET; break; } if (!strcmpi( token, "skin1")) { mask = MDL_GROUP_SKINOFFSET_BIT0; } else if (!strcmpi (token, "skin2")) { mask = MDL_GROUP_SKINOFFSET_BIT1; } else if (!strcmpi (token, "nodraw")) { mask = MDL_GROUP_NODRAW; } else if (!strcmpi (token, "envmapped")) { mask = MDL_GROUP_ENVMAPPED_SILVER; } else if (!strcmpi (token, "goldenvmapped")) { mask = MDL_GROUP_ENVMAPPED_GOLD; } else if (!strcmpi (token, "translucent33")) { mask = MDL_GROUP_TRANSLUCENT_33; } else if (!strcmpi (token, "translucent66")) { mask = MDL_GROUP_TRANSLUCENT_66; } else if (!strcmpi (token, "fullbright")) { mask = MDL_GROUP_FULLBRIGHT; } else { ev->Error( "Unknown token %s.", token ); action = FLAG_IGNORE; } for( ; group_num < edict->s.numgroups ; group_num++ ) { switch (action) { case FLAG_SET: // clear out group edict->s.groups[ group_num ] = 0; case FLAG_ADD: edict->s.groups[ group_num ] |= mask; break; case FLAG_CLEAR: edict->s.groups[ group_num ] &= ~mask; break; case FLAG_IGNORE: break; } if ( !do_all ) break; } } } void Entity::DialogEvent ( Event * ev ) { str name; float volume; int channel; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; str icon_name; str dialog_text; if ( ( dialog->value == 1 ) || ( dialog->value == 3 ) ) { icon_name = ev->GetString( 1 ); dialog_text = ev->GetString( 2 ); SendDialog( icon_name.c_str(), dialog_text.c_str() ); } if ( ( dialog->value == 0 ) || ( dialog->value == 1 ) ) return; // // set defaults // volume = 1.0f; channel = CHAN_DIALOG_SECONDARY | CHAN_NO_PHS_ADD; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = SOUND_SYNCH; for ( i = 3 ; i <= ev->NumArgs() ; i++ ) { switch (i-3) { case 0: name = ev->GetString( i ); break; case 1: volume = ev->GetFloat( i ); break; case 2: channel = ev->GetInteger( i ); break; case 3: attenuation = ev->GetFloat( i ); break; case 4: pitch = ev->GetFloat( i ); break; case 5: timeofs = ev->GetFloat( i ); break; case 6: fadetime = ev->GetFloat( i ); break; case 7: flags = ev->GetInteger( i ); break; default: break; } } sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } void Entity::AttachEvent ( Event * ev ) { Entity * parent; const char * bone; int groupindex; int tri_num; vec3_t orient; parent = ev->GetEntity( 1 ); bone = ev->GetString( 2 ); if ( !parent ) return; if ( gi.GetBoneInfo( parent->edict->s.modelindex, bone, &groupindex, &tri_num, orient ) ) { attach( parent->entnum, groupindex, tri_num, Vector(orient) ); } else { warning( "AttachEvent", "GetBoneInfo failed for bone %s", bone ); } } void Entity::AttachModelEvent ( Event * ev ) { ThrowObject * tobj; const char * bone; int groupindex; int tri_num; vec3_t orient; str modelname; tobj = new ThrowObject; modelname = ev->GetString( 1 ); bone = ev->GetString( 2 ); if ( ev->NumArgs() > 2 ) { tobj->SetTargetName( ev->GetString( 3 ) ); } tobj->setModel( modelname ); if ( gi.GetBoneInfo( edict->s.modelindex, bone, &groupindex, &tri_num, orient ) ) { tobj->attach( this->entnum, groupindex, tri_num, Vector(orient) ); } else { warning( "AttachModelEvent", "GetBoneInfo failed for bone %s", bone ); } } void Entity::DetachEvent ( Event * ev ) { float trans[ 3 ][ 3 ]; Entity * ent; vec3_t p1, p2; if ( edict->s.parent <= 0 ) { return; } ent = ( Entity * )G_GetEntity( edict->s.parent ); if ( gi.GetBoneTransform( ent->edict->s.modelindex, edict->s.bone.group_num, edict->s.bone.tri_num, edict->s.bone.orientation, ent->edict->s.anim, ent->edict->s.frame, ent->edict->s.scale, trans, p1 ) ) { VectorAdd( p1, origin.vec3(), p1 ); MatrixTransformVector( p1, ent->orientation, p2 ); VectorAdd( p2, ent->worldorigin, p2 ); } detach(); setOrigin( p2 ); worldorigin.copyTo( edict->s.old_origin ); } void Entity::TakeDamageEvent ( Event * ev ) { takedamage = DAMAGE_YES; } void Entity::NoDamageEvent ( Event * ev ) { takedamage = DAMAGE_NO; } void Entity::SetSkinEvent ( Event *ev ) { int num; num = gi.Skin_NumForName( edict->s.modelindex, ev->GetString( 1 ) ); if ( num >= 0 ) { edict->s.skinnum = num; } else { ev->Error( "Could not find %s as a skin name.\n", ev->GetString( 1 ) ); } } void Entity::Lightoffset ( Event *ev ) { edict->s.lightofs = ev->GetFloat( 1 ); if ( edict->s.lightofs != 0 ) edict->s.renderfx |= RF_LIGHTOFFSET; else edict->s.renderfx &= ~RF_LIGHTOFFSET; } void Entity::Gravity ( Event *ev ) { gravity = ev->GetFloat( 1 ); } void Entity::Minlight ( Event *ev ) { if ( ev->GetInteger( 1 ) ) edict->s.renderfx |= RF_MINLIGHT; else edict->s.renderfx &= ~RF_MINLIGHT; } void Entity::UseBoundingBoxEvent ( Event *ev ) { edict->svflags |= SVF_USEBBOX; } void Entity::HurtEvent ( Event *ev ) { Vector normal; float dmg; if ( ev->NumArgs() < 1 ) { dmg = 50; } else { dmg = ev->GetFloat( 1 ); } normal = Vector( orientation[ 0 ] ); Damage( world, world, dmg, worldorigin, vec_zero, normal, dmg, 0, MOD_CRUSH, -1, -1, 1.0f ); } void Entity::IfSkillEvent ( Event *ev ) { float skilllevel; skilllevel = ev->GetFloat( 1 ); if ( skill->value == skilllevel ) { int argc; int numargs; Event *event; int i; numargs = ev->NumArgs(); argc = numargs - 2 + 1; event = new Event( ev->GetToken( 2 ) ); for( i = 1; i < argc; i++ ) { event->AddToken( ev->GetToken( 2 + i ) ); } ProcessEvent( event ); } } void Entity::Censor ( Event *ev ) { Vector delta; float oldsize; float newsize; if ( !parentmode->value ) return; oldsize = size.length(); setSolidType( SOLID_NOT ); setModel( "censored.def" ); gi.CalculateBounds( edict->s.modelindex, 1, mins.vec3(), maxs.vec3() ); delta = maxs - mins; newsize = delta.length(); edict->s.scale = oldsize / newsize; mins *= edict->s.scale; maxs *= edict->s.scale; setSize( mins, maxs ); setOrigin( origin ); }