commit 09e039d47e90b2cb48268b990281b686b5cdcd60 Author: archive Date: Sun Dec 20 00:00:00 1998 +0000 as released 1998-12-20 diff --git a/actor.cpp b/actor.cpp new file mode 100644 index 0000000..ca32e6c --- /dev/null +++ b/actor.cpp @@ -0,0 +1,6225 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/actor.cpp $ +// $Revision:: 230 $ +// $Author:: Markd $ +// $Date:: 11/16/98 8:50p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/actor.cpp $ +// +// 230 11/16/98 8:50p Markd +// increased chat times +// +// 229 11/16/98 7:28p Markd +// bullet proofed TargetEnemies so that dead bodies only spoke their secret +// once and then cleared who they were upset at, much like a soul being +// released from a dead body ;) +// +// 228 11/15/98 7:58p Jimdose +// Actors now exclusively use flags for movement types +// Made eyeposition max out at maxs.z +// Swim monsters will no longer leave water +// Air monsters will no longer enter water +// +// 227 11/15/98 2:58a Markd +// scale pain threshold by skill a bit less than what it is right now +// +// 226 11/15/98 1:24a Jimdose +// monsters don't play snd_sightenemy now unless DoAction( "sightenemy" ) +// succeeds. +// +// 225 11/14/98 6:41p Jimdose +// guys no longer drown in moving water after the water has moved +// +// 224 11/13/98 11:55p Markd +// bodyimpact sound to goryimpact sound +// +// 223 11/13/98 11:00p Jimdose +// Took EV_CONSOLE off of fov +// +// 222 11/13/98 10:02p Jimdose +// bodies no longer float when they die in water +// guys will allow themselves to fall when they are standing on other setients +// +// 221 11/13/98 4:04p Markd +// increased min melee range +// +// 220 11/12/98 11:30p Jimdose +// Increased melee kick +// +// 219 11/10/98 5:47p Jimdose +// Made CanSee non-inline because it caused link problems +// +// 218 11/09/98 6:23p Markd +// Added turnspeed event +// +// 217 11/08/98 7:06p Markd +// Put in nochatter, made mutants not Like each other, added seenEnemy to +// actorinfo +// +// 216 11/07/98 10:16p Jimdose +// actors no longer get mad at scriptobjects that hurt them +// +// 215 11/07/98 3:34p Markd +// Added nodeathfade command +// +// 214 11/06/98 8:25p Jimdose +// made visiondistance adjustable from script +// +// 213 11/03/98 10:02p Aldie +// Drop inventory when being tesselated +// +// 212 10/27/98 9:06p Markd +// tweaked enemy aiming +// +// 211 10/27/98 7:52p Jimdose +// added IfEnemyWithinEvent +// made guys not lean if they're not moving very far +// +// 210 10/27/98 3:46p Markd +// Changed skill level on actors +// +// 209 10/27/98 3:51a Jimdose +// added kick to MeleeEvent +// +// 208 10/27/98 1:50a Markd +// Made JumpTo take in account water level +// +// 207 10/26/98 9:53p Markd +// Moved GotKill around, added gibfest sound +// +// 206 10/26/98 9:04p Markd +// Changed jumpto behavior +// +// 205 10/26/98 8:22p Markd +// Put CheckWater in Killed function +// +// 204 10/26/98 5:51p Jimdose +// actor saves off spawn args before processing init commands +// +// 203 10/26/98 2:19p Markd +// Added last_jump_time +// +// 202 10/26/98 1:20p Markd +// made guys be BestTargetable after having an enemy already +// +// 201 10/26/98 12:13p Markd +// fixed bug with blood preventing parent mode melee attacks +// +// 200 10/26/98 3:59a Jimdose +// made Trymove not return STEPMOVE_STUCK when opening doors +// +// 199 10/26/98 1:53a Jimdose +// TryMove was still using origin insteard of worldorigin, so the M_CheckBottom +// call was always checking where the monster currently was, and not where he +// was going to. +// +// 198 10/25/98 11:53p Aldie +// Another dead check to avoid bodies that are in the air not going dead +// +// 197 10/25/98 4:43a Markd +// Added animal event +// +// 196 10/25/98 4:28a Jimdose +// Made opendoor a forced action +// made CanShootFrom do extra tests to see if it should shoot damagable objects +// +// 195 10/25/98 1:52a Aldie +// Changed gibanim functionality to be location based +// +// 194 10/24/98 3:10p Markd +// Put in eyeoffset command +// +// 193 10/24/98 3:26a Markd +// Put in more debug info for actors +// +// 192 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 191 10/23/98 6:46p Aldie +// Turn off shadows for death and do deathgib anim on location +// +// 190 10/22/98 9:28p Aldie +// Added ability for deathgib animation +// +// 189 10/22/98 3:05p Markd +// fixed movespeed on actors +// +// 188 10/22/98 1:40a Markd +// Added FL_STEALTH ability +// +// 187 10/21/98 11:15p Markd +// Put in FL_CLOAK ability for targetting from enemies +// +// 186 10/21/98 6:43p Markd +// made eyeposition the top of their bounding box +// +// 185 10/21/98 3:37p Markd +// put in different enemy alignment and put in support for skins +// +// 184 10/21/98 2:13a Markd +// Fixed jumping stuff +// +// 183 10/20/98 11:29p Markd +// Made CanShootFrom aim at the chest rather than the centroid +// +// 182 10/20/98 8:05p Aldie +// Added a DoGib function to ease readability +// +// 181 10/20/98 2:37a Markd +// Added additional JumpTo methods +// +// 180 10/20/98 12:43a Markd +// Put in scale for health, setsize, melee_range and melee_damage +// +// 179 10/19/98 11:53p Aldie +// Moved the solid_trigger setting to the setup function of touchfields +// +// 178 10/19/98 11:49p Aldie +// Fixed a bug where touch field was SOLID_BBOX. Now it is forced to +// SOLID_TRIGGER. +// +// 177 10/19/98 9:55p Markd +// Made it so "state" event does a ProcessScript +// +// 176 10/19/98 8:58p Aldie +// Fix dead check for bodies in water +// +// 175 10/19/98 2:58a Markd +// Fixed CanShootFrom and Jumping +// +// 174 10/19/98 2:41a Markd +// made MakeEnemy not target the world +// +// 173 10/19/98 2:41a Aldie +// Removed MOVETYPE_TOSS when actors die +// +// 172 10/19/98 12:04a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 171 10/18/98 6:43p Markd +// If no melee attack, make weapon not droppable +// +// 170 10/18/98 12:34a Markd +// Scaled shots_per_attack based on skill level +// +// 169 10/18/98 12:01a Markd +// Fixed AttackEntity and IfCanShoot +// +// 168 10/17/98 11:00p Markd +// Added IfCanShoot and not gettine mad at non-damageable things +// +// 167 10/17/98 9:41p Markd +// Made InvestigateSound not set nextsoundtime unless the Action responds +// +// 166 10/17/98 7:34p Jimdose +// deleted trig when actor deleted +// +// 165 10/17/98 4:59p Markd +// Added attackmode event +// +// 164 10/17/98 3:31p Markd +// Made firing more accurate over great distances +// +// 163 10/17/98 12:34a Markd +// Initialized chattime to random( 20 ) +// +// 162 10/16/98 7:18p Markd +// Added a bunch of new commands, and added interfaces to the editor +// +// 161 10/16/98 3:11a Aldie +// Double the armor when actors die +// +// 160 10/16/98 1:41a Jimdose +// Added goto command for actors. This helps the all too common mistake of +// typing "local.self goto label" +// +// 159 10/15/98 9:32p Markd +// Made inanimate objects not move, not bleed, and not gib +// +// 158 10/14/98 10:54p Jimdose +// Made Hates check for a null entity +// Added null check to investigate functions +// +// 157 10/14/98 9:00p Markd +// Fixed JumpTo command, speed wise +// +// 156 10/14/98 5:20p Markd +// Added jumpto event and jumpto function +// +// 155 10/14/98 2:17a Markd +// Added in ifcanmeleeattack event +// +// 154 10/14/98 1:27a Markd +// Added Has_weapon support +// +// 153 10/14/98 12:11a Markd +// Put extra reach into melee event +// +// 152 10/13/98 11:03p Markd +// Put scale in on all Anim_delta calls +// +// 151 10/13/98 7:34p Markd +// made gib code scalable +// +// 150 10/13/98 12:26a Markd +// Added support for flying creatures, also made it so that CanShootFrom fired +// from the centroid, and not from the gun position +// +// 149 10/11/98 8:17p Markd +// Working on nautic fixes +// +// 148 10/11/98 5:20p Markd +// Put in default pain_thresholds +// +// 147 10/11/98 5:00p Markd +// Fixed water drowning, changed ProcessCommands so that it doesn't overwrite +// the the "thread" variable +// +// 146 10/11/98 12:32a Markd +// Added Flight ability +// +// 145 10/10/98 9:12p Markd +// Fixed Chatter using an alias for SoundTimeLength +// +// 144 10/10/98 7:59p Markd +// Put in painthreshold and snd_pain_taunt +// +// 143 10/10/98 5:01p Markd +// Changed trace masks to edict->clipmasks +// +// 142 10/10/98 3:27p Markd +// Made EndBehavior always notify the actor script +// +// 141 10/10/98 1:24a Jimdose +// Did some bulletproofing against possible NULL actorthread +// Prethink kills actors with NULL threads +// added SetVariable for setting thread variables safely +// +// 140 10/09/98 5:24p Markd +// Fixed inanimate actor animations +// +// 139 10/09/98 4:59p Jimdose +// Working on savegames +// +// 138 10/07/98 11:32a Markd +// Made DamageThresholds targetable by actors +// +// 137 10/06/98 5:25p Markd +// Made MakeEnemy and DoAction take the force parameter +// +// 136 10/06/98 12:14p Markd +// Added ForceAction +// +// 135 10/05/98 10:33p Markd +// Added canStrafe and noStrafe, also added MinimumAttackRange +// +// 134 10/05/98 2:57p Markd +// Changed Chatter settings +// +// 133 10/04/98 10:36p Markd +// Put in attack_finished +// +// 132 10/04/98 8:45p Aldie +// Gib functions +// +// 131 10/04/98 2:49p Markd +// Fixed swimming and forward speed stuff +// +// 130 10/03/98 7:15p Markd +// got swimming characters working, in the process re-wrote a lot of the combat +// stuff +// +// 129 10/01/98 8:00p Markd +// Added melee capabilities +// +// 128 10/01/98 4:13p Jimdose +// Fixed some problems with CanShootFrom that prevented guys from being able to +// shoot the player when he's on ledges with railings +// +// 127 9/24/98 7:26p Markd +// made inanimate objects not set off triggers +// +// 126 9/24/98 7:10p Jimdose +// Added MyGunAngles +// +// 125 9/24/98 12:42a Jimdose +// attempting to fix dead guys hanging in air +// +// 124 9/22/98 10:19p Jimdose +// Initialized lastmove +// Fixed InFov +// Set fovdot in Fov event +// +// 123 9/22/98 5:19p Markd +// Put in new consolidated gib function +// +// 122 9/22/98 5:10p Jimdose +// Fixed inanimate objects +// +// 121 9/22/98 3:21p Markd +// put in parentmode lockout for blood and gibs +// +// 120 9/22/98 6:27a Jimdose +// Fixed bug when setting "attackmode" +// +// 119 9/22/98 5:37a Jimdose +// Made attackmode only set once when thread is created +// +// 118 9/22/98 5:31a Jimdose +// Added attackmode +// +// 117 9/22/98 1:58a Jimdose +// Added lastmove and forwardspeed +// Regrouped functions by functionality +// Removed reaction table stuff +// +// 116 9/19/98 7:49p Jimdose +// Fixed uninitialized pointer in ShowInfo and added more data +// +// 115 9/19/98 6:16p Jimdose +// Added AttackPlayer +// AttackEntity and AttackPlayer makes the actor attack the entity immediately +// by forcing a call to SightEnemy +// +// 114 9/19/98 4:34p Jimdose +// changed gi.dprintf's to gi.printf's in ShowInfo +// +// 113 9/18/98 10:55p Jimdose +// Added inanimate actor type +// started work on swimming monsters +// +// 112 9/16/98 8:38p Jimdose +// Made InFOV only test the fov in 2 dimensions +// +// 111 9/14/98 5:26p Jimdose +// Made EndBehavior set script variables +// Added some checks for NULL when getting nearest nodes +// SeenEnemy is now cleared when currentEnemy is killed +// CanShootFrom no longer assumes ScriptSlaves are shootable, but does shoot +// ScriptModel. Gotta find some solution that allows the level designers to +// specify whats shootable by the AI. +// +// 110 9/08/98 9:48p Jimdose +// Enabled multi-size bounding box path code +// +// 109 9/03/98 9:05p Jimdose +// Guys will now shoot through scriptslaves and objects with the assumption +// that he can destroy it +// +// 108 8/31/98 7:46p Jimdose +// Spread out targeting a bit +// +// 107 8/31/98 5:42p Jimdose +// Added checks for NULL pointer in MakeEnemy and AttackEnemy +// +// 106 8/29/98 9:39p Jimdose +// Added call info to G_Trace +// +// 105 8/29/98 5:26p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 104 8/29/98 1:11p Markd +// Made it so M_CheckBottom only checks if groundentity is not world +// +// 103 8/27/98 9:00p Jimdose +// Made centroid into a variable +// Made guys target enemies less often +// MakeEnemy checks if ent is the world (when a target is non-valid, the ent +// may be the world) +// +// 102 8/27/98 6:00p Aldie +// Drop inventory items if the actors are gibbed. +// +// 101 8/27/98 4:49p Jimdose +// Fixed bug with hidden actors being shown again on startup +// Made actors not target hidden enemies, or target while hidden (for +// cinematics) +// +// 100 8/27/98 4:27p Markd +// detach children +// +// 99 8/26/98 11:13p Jimdose +// Began strafe support +// +// 98 8/26/98 6:45p Jimdose +// Made DefineStateEvent prepend the value in ai_actorscript +// +// 97 8/24/98 6:55p Jimdose +// Added crouching pain and death animations +// Added path +// +// 96 8/20/98 8:38p Jimdose +// Actors now share enemy info between each other on sight +// CanShoot checks the head as well as the centroid +// InFOV now uses an eliptical cone for testing vision (better vertical +// peripheral vision). +// +// 95 8/19/98 4:57p Jimdose +// CanShootFrom now takes min and max weapon firing distances into account +// +// 94 8/19/98 3:59p Jimdose +// Added IfNearEvent and CopyStateEvent +// +// 93 8/19/98 2:30p Jimdose +// Can no longer alias "alert" to "idle" so I've added a hasalert variable +// +// 92 8/18/98 11:57p Aldie +// Fixed init variable warning +// +// 91 8/18/98 11:08p Markd +// Added new Alias System +// +// 90 8/18/98 9:59p Jimdose +// Made actors only react to some sounds when they're caused by an enemy. +// Hmmm, would be nice if they checked their "memory" to see if they expect +// that kind of sound from there. +// +// 89 8/15/98 5:29p Jimdose +// Fixed up the thread startup code. No longer need to wait .1 in the script +// before sending commands. Actor script is no longer run twice (because of +// the delayed model init commands. +// Got rid of !init label in scripts. Now starts at beginning unless a label +// is given. +// CanShoot checks vision distance as an early exit. +// +// 88 8/14/98 11:34p Jimdose +// Got rid of "runtoarea" state. Attack now just lets MakeEnemy call +// sightenemy. Hmmm...probably should change to make sure that actor +// immediatly attacks the specified entity. +// don't set currentEnemy until sightenemy is responded to. Gets rid of a lot +// of brain dead situations. +// +// 87 8/14/98 6:24p Jimdose +// Got rid of decelleration for steering +// +// 86 8/13/98 7:30p Jimdose +// actorThread is now persistant. Threads don't get killed unless actor dies +// or is removed. +// +// 85 8/08/98 2:05p Markd +// Added entity who got the event to ForwardBehaviorEvent +// +// 84 8/07/98 7:57p Markd +// Added ForwardToBehavior event +// +// 83 8/07/98 6:01p Jimdose +// Rewrite AimAndShoot +// +// 82 8/06/98 6:59p Jimdose +// Added IfEnemyVisible +// +// 81 8/05/98 7:18p Jimdose +// Added definestate, ignoresounds, respondtosounds, respondtoall +// +// 80 8/03/98 6:49p Jimdose +// fixed thread bug +// +// 79 7/29/98 6:32p Jimdose +// Added MonsterStart +// +// 78 7/29/98 6:27p Jimdose +// Removed monster.h +// +// 77 7/29/98 2:30p Aldie +// Changed health to a float +// +// 76 7/26/98 3:40p Jimdose +// Fixed guys in water +// guys no longer open locked doors +// +// 75 7/26/98 11:43a Jimdose +// Added EnemyCanSeeMeFrom +// +// 74 7/26/98 6:44a Jimdose +// Overrode the setsize event so that def files don't change the hardcoded size +// (need to fix these damn paths one day). +// +// 73 7/26/98 3:48a Jimdose +// Modified aim based on skill +// +// 72 7/25/98 4:39p Jimdose +// Guys shoot at vehicles and shoot out windows +// +// 71 7/25/98 2:07a Jimdose +// Fixed bug with trig being delete without being NULLed out when gibbing +// Guys now attack any enemy +// +// 70 7/24/98 9:59p Aldie +// Gib stuff +// +// 69 7/24/98 6:16p Aldie +// Check for inventory items before removing dead guys +// +// 68 7/24/98 4:14p Aldie +// Cancel useless checks +// +// 67 7/24/98 3:46p Aldie +// Increased gibs +// +// 66 7/23/98 10:16p Jimdose +// Removed ignoreenemies stuff +// Fixed a bunch of anim screwups when actor is killed +// fixed "big gib" bug +// +// 65 7/23/98 6:55p Aldie +// Fun with gibs. +// +// 64 7/23/98 6:19p Jimdose +// ignoreEnemies was being set when character was targeted +// +// 63 7/22/98 7:02p Aldie +// Added ability to remove useless bodies. +// +// 62 7/22/98 2:32p Jimdose +// fixed bug where guys wouldn't sight the enemy if the went into pain +// +// 61 7/21/98 9:34p Jimdose +// Chatter now checks if the alias exists before playing the sound, thereby not +// generating warnings of missing sounds. +// +// 60 7/19/98 9:20p Jimdose +// Improved support for overriding the default script +// +// 59 7/16/98 6:21p Jimdose +// Fixed crash when actorthread was null +// +// 58 7/14/98 11:50p Jimdose +// Made PopState return whether a new state was popped off the stack +// StateDoneEvent now kills the calling thread if no previous state exists on +// the stack +// DoAction creates a new thread if no actor thread exists +// +// 57 7/14/98 6:57p Aldie +// Moved setsize before setmodel +// +// 56 7/13/98 4:59p Aldie +// Added dead player bodies with gibbing +// +// 55 7/09/98 10:40p Aldie +// Moved bodyparts to game +// +// 54 7/08/98 8:59p Jimdose +// Moved opening door to script +// +// 53 7/08/98 1:00p Jimdose +// added crouchsize and standsize +// +// 52 7/07/98 11:37p Jimdose +// Replaced priority-based system with full script-based state system +// +// 51 7/06/98 1:06p Jimdose +// working on ai +// +// 50 6/30/98 6:47p Aldie +// Added Gib Event +// +// 49 6/30/98 6:00p Jimdose +// Added more states and state detection code. +// Added range detection +// added health level detection +// +// 48 6/25/98 8:46p Markd +// Removed rotating doors +// +// 47 6/25/98 8:29p Aldie +// Updated killed and death +// +// 46 6/25/98 8:10p Jimdose +// Disabled some prints +// +// 45 6/25/98 8:10p Jimdose +// Using global actor script +// +// 44 6/24/98 4:26p Jimdose +// Fixed bugs in TurnTo +// +// 43 6/23/98 5:59p Aldie +// Fixed dropWeapon stuff. +// +// 42 6/20/98 7:47p Markd +// Added location based pain and death support +// +// 41 6/19/98 9:27p Jimdose +// Added GetGunOrientation +// +// 40 6/19/98 4:44p Jimdose +// Added util functions InFOV, etc. +// +// 39 6/18/98 8:45p Jimdose +// Removed string based SetBehavior and PushState functions +// Added source info to events +// +// 38 6/18/98 2:00p Markd +// rewrote tesselation code +// +// 37 6/17/98 10:00p Jimdose +// Improved door opening code +// Moved phyics move into class +// +// 36 6/17/98 3:15p Markd +// fixed arg issue +// +// 35 6/17/98 3:03p Markd +// Changed NumArgs back to previous behavior +// +// 34 6/17/98 1:19a Jimdose +// Moved setOwner to Item. +// Added EV_Item_Pickup +// +// 33 6/15/98 10:46p Jimdose +// Made neutral allias become enemies with pain +// shurnk bounding box temporarily +// +// 32 6/13/98 8:25p Jimdose +// Added leaning into turns (alright already Tom!) +// Added enemy visibility tests for FindCover +// +// 31 6/11/98 12:44a Jimdose +// behaviors now get info from the script at startup +// +// 30 6/10/98 10:24p Jimdose +// added priority based state system +// +// 29 6/09/98 4:18p Jimdose +// worked on ai +// +// 28 6/04/98 10:48p Jimdose +// Fixed a bunch of things that got broken just in time for E3. Paths and +// scripting actually work now. +// +// 27 6/03/98 5:43p Jimdose +// Fixed spelling of behavior. :) +// +// 26 6/03/98 4:36p Markd +// Removed gun stuff from entity, it is now sent over as a separate object +// +// 25 6/02/98 7:46p Markd +// Took out currentweapon assignments for client-side stuff +// +// 24 5/28/98 6:01p Markd +// When actors die, they kill their dynamic light +// +// 23 5/28/98 2:11p Markd +// Made enemies drop weapons and only fire if their weapons are ready +// +// 22 5/28/98 1:50p Markd +// made actors drop weapons +// +// 21 5/27/98 4:56p Markd +// Only gib when inflictor is rocket +// +// 20 5/27/98 12:04p Jimdose +// fixed null pointer prob +// +// 19 5/27/98 11:59a Jimdose +// Gibbing is harder +// +// 18 5/27/98 11:50a Jimdose +// Added killtarget and target to killed +// +// 17 5/27/98 7:15a Jimdose +// took out dprintf +// +// 16 5/27/98 7:12a Jimdose +// ai ai ai +// +// 15 5/27/98 6:39a Jimdose +// working on ai +// +// 14 5/27/98 5:27a Aldie +// Added gibs +// +// 13 5/27/98 5:11a Jimdose +// working on ai +// +// 12 5/25/98 6:46p Jimdose +// Made animateframe, prethink and posthink into functions built into the base +// entity class +// +// 11 5/25/98 5:32p Jimdose +// Pathnodes are no longer a subclass of Entity. This was done to save on +// edicts +// +// 10 5/25/98 1:06a Jimdose +// Added chatter +// +// 9 5/24/98 8:46p Jimdose +// Made a lot of functions more str-friendly. +// Got rid of a lot of char * based strings +// Cleaned up get spawn arg functions and sound functions +// sound functions now use consistant syntax +// +// 8 5/24/98 1:02a Jimdose +// Added monster hearing +// +// 7 5/23/98 6:27p Jimdose +// Improved steering and opening doors +// +// 6 5/22/98 9:44p Jimdose +// Disabled debug lines when checking to shoot +// +// 5 5/22/98 9:38p Jimdose +// Commented out prints +// +// 4 5/22/98 9:36p Jimdose +// AI Scripting works again (somewhat) +// +// 3 5/20/98 6:36p Jimdose +// Rewrite +// +// 2 5/18/98 8:15p Jimdose +// Created file +// +// DESCRIPTION: +// Base class for character AI. +// + +#include "g_local.h" +#include "actor.h" +#include "behavior.h" +#include "scriptmaster.h" +#include "doors.h" +#include "gibs.h" +#include "vehicle.h" +#include "misc.h" +#include "specialfx.h" +#include "object.h" +#include "scriptslave.h" +#include "explosion.h" +#include "misc.h" + +//#define DEBUG_PRINT + +CLASS_DECLARATION( Class, ActorState, NULL ); + +ResponseDef ActorState::Responses[] = + { + { NULL, NULL } + }; + +cvar_t *ai_actorscript; + +#define TURN_SPEED 30 + +CLASS_DECLARATION( Sentient, Actor, "monster_generic" ); + +Event EV_Actor_CrouchSize( "crouchsize" ); +Event EV_Actor_Fov( "fov" ); +Event EV_Actor_VisionDistance( "visiondistance" ); +Event EV_Actor_Start( "start" ); +Event EV_Actor_Dead( "dead" ); + +Event EV_Actor_ClearState( "clearstate" ); +Event EV_Actor_SetState( "state" ); +Event EV_Actor_SetBehavior( "behavior" ); + +Event EV_Actor_Friend( "friend" ); // Allied to player +Event EV_Actor_Civilian( "civilian" ); // Nuetral to all, runs from danger +Event EV_Actor_Enemy( "enemy" ); // Enemy to player +Event EV_Actor_Monster( "monster" ); // Enemy to player, civilians, and inanimate objects +Event EV_Actor_Animal( "animal" ); // neutral to all +Event EV_Actor_Inanimate( "inanimate" ); // Inanimate objects + +Event EV_Actor_Swim( "swim" ); // specifies actor as being able to swim +Event EV_Actor_Fly( "fly" ); // specifies actor as being able to swim +Event EV_Actor_NotLand( "noland" ); // specifies actor as not being able to walk on land + +Event EV_Actor_TargetEnemies( "targetenemies" ); + +// Scripting +Event EV_Actor_Script( "script" ); +Event EV_Actor_Thread( "thread" ); + +Event EV_Actor_IfEnemyVisible( "ifenemyvisible" ); +Event EV_Actor_IfNear( "ifnear" ); + +Event EV_Actor_ForwardSpeed( "forwardspeed" ); + +Event EV_Actor_Idle( "idle" ); +Event EV_Actor_LookAt( "lookat" ); +Event EV_Actor_TurnTo( "turnto" ); +Event EV_Actor_FinishedBehavior( "finishedbehavior" ); +Event EV_Actor_NotifyBehavior( "notifybehavior" ); +Event EV_Actor_WalkTo( "walkto" ); +Event EV_Actor_JumpTo( "jumpto" ); +Event EV_Actor_RunTo( "runto" ); +Event EV_Actor_Anim( "anim" ); +Event EV_Actor_Attack( "attack" ); +Event EV_Actor_AttackPlayer( "attackplayer" ); + +Event EV_Actor_ReserveNode( "reservenode" ); +Event EV_Actor_ReleaseNode( "releasenode" ); +Event EV_Actor_IfCanHideAt( "ifcanhideat" ); +Event EV_Actor_IfCanStrafeAttack( "ifcanstrafeattack" ); +Event EV_Actor_IfCanMeleeAttack( "ifcanmeleeattack" ); +Event EV_Actor_IfCanShoot( "ifcanshoot" ); +Event EV_Actor_IfEnemyWithin( "ifenemywithin" ); + +Event EV_Actor_IgnoreSounds( "ignoresounds" ); +Event EV_Actor_RespondToSounds( "respondtosounds" ); +Event EV_Actor_IgnoreAll( "ignoreall" ); +Event EV_Actor_RespondToAll( "respondtoall" ); +Event EV_Actor_RespondTo( "respondto" ); +Event EV_Actor_Ignore( "ignore" ); +Event EV_Actor_DefineState( "definestate" ); +Event EV_Actor_CopyState( "copystate" ); + +Event EV_Actor_Remove( "remove_useless" ); + +Event EV_Actor_ForwardToBehavior( "forwardcommand" ); +Event EV_Actor_Aim( "aim" ); +Event EV_Actor_MeleeRange( "meleerange" ); +Event EV_Actor_MeleeDamage( "meleedamage" ); +Event EV_Actor_Melee( "melee" ); +Event EV_Actor_AttackFinished( "attack_finished" ); + +Event EV_Actor_CanStrafe( "canstrafe" ); +Event EV_Actor_NoStrafe( "nostrafe" ); + +Event EV_Actor_PainThreshold( "painthreshold" ); +Event EV_Actor_SetKillThread( "killthread" ); +Event EV_Actor_AttackRange( "attackrange" ); +Event EV_Actor_ShotsPerAttack( "shotsperattack" ); +Event EV_Actor_ClearEnemy( "clearenemy" ); +Event EV_Actor_AttackMode( "attackmode" ); +Event EV_Actor_EyePositionOffset( "eyeoffset" ); +Event EV_Actor_NoDeathFade( "nodeathfade" ); +Event EV_Actor_NoChatter( "nochatter" ); +Event EV_Actor_TurnSpeed( "turnspeed" ); + +ResponseDef StateInfo::Responses[] = + { + { NULL, NULL } + }; + +CLASS_DECLARATION( Class, StateInfo, NULL ); + +StateInfo::StateInfo() + { + ignore = true; + } + +ResponseDef Actor::Responses[] = + { + { &EV_Activate, ( Response )Actor::ActivateEvent }, + { &EV_Use, ( Response )Actor::UseEvent }, + + { &EV_Actor_Start, ( Response )Actor::Start }, + { &EV_Pain, ( Response )Actor::Pain }, + { &EV_Killed, ( Response )Actor::Killed }, + { &EV_Actor_Dead, ( Response )Actor::Dead }, + + { &EV_Actor_TargetEnemies, ( Response )Actor::TargetEnemies }, + + { &EV_Actor_SetBehavior, ( Response )Actor::SetBehaviorEvent }, + { &EV_Actor_ForwardSpeed, ( Response )Actor::ForwardSpeedEvent }, + + { &EV_Actor_CrouchSize, ( Response )Actor::CrouchSize }, + { &EV_Actor_Fov, ( Response )Actor::SetFov }, + { &EV_Actor_VisionDistance, ( Response )Actor::SetVisionDistance }, + { &EV_Actor_Friend, ( Response )Actor::FriendEvent }, + { &EV_Actor_Civilian, ( Response )Actor::CivilianEvent }, + { &EV_Actor_Enemy, ( Response )Actor::EnemyEvent }, + { &EV_Actor_Monster, ( Response )Actor::MonsterEvent }, + { &EV_Actor_Animal, ( Response )Actor::AnimalEvent }, + { &EV_Actor_Inanimate, ( Response )Actor::InanimateEvent }, + { &EV_Actor_Swim, ( Response )Actor::SwimEvent }, + { &EV_Actor_Fly, ( Response )Actor::FlyEvent }, + { &EV_Actor_NotLand, ( Response )Actor::NotLandEvent }, + { &EV_Gib, ( Response )Actor::GibEvent }, + + // Scripting + { &EV_Actor_DefineState, ( Response )Actor::DefineStateEvent }, + { &EV_Actor_CopyState, ( Response )Actor::CopyStateEvent }, + { &EV_Actor_IgnoreAll, ( Response )Actor::IgnoreAllEvent }, + { &EV_Actor_RespondToAll, ( Response )Actor::RespondToAllEvent }, + { &EV_Actor_RespondTo, ( Response )Actor::RespondToEvent }, + { &EV_Actor_Ignore, ( Response )Actor::IgnoreEvent }, + { &EV_Actor_IgnoreSounds, ( Response )Actor::IgnoreSoundsEvent }, + { &EV_Actor_RespondToSounds, ( Response )Actor::RespondToSoundsEvent }, + + { &EV_Actor_ClearState, ( Response )Actor::ClearStateEvent }, + { &EV_ScriptThread_End, ( Response )Actor::StateDoneEvent }, + { &EV_Actor_SetState, ( Response )Actor::SetStateEvent }, + { &EV_Actor_Script, ( Response )Actor::SetScript }, + { &EV_Actor_Thread, ( Response )Actor::SetThread }, + { &EV_Actor_IfEnemyVisible, ( Response )Actor::IfEnemyVisibleEvent }, + { &EV_Actor_IfNear, ( Response )Actor::IfNearEvent }, + { &EV_Actor_Idle, ( Response )Actor::IdleEvent }, + { &EV_ProcessCommands, ( Response )Actor::StartMove }, + { &EV_Actor_LookAt, ( Response )Actor::LookAt }, + { &EV_Actor_TurnTo, ( Response )Actor::TurnToEvent }, + { &EV_Actor_FinishedBehavior, ( Response )Actor::FinishedBehavior }, + { &EV_Actor_NotifyBehavior, ( Response )Actor::NotifyBehavior }, + { &EV_Actor_WalkTo, ( Response )Actor::WalkTo }, + { &EV_Actor_JumpTo, ( Response )Actor::JumpToEvent }, + { &EV_Actor_RunTo, ( Response )Actor::RunTo }, + { &EV_Actor_Anim, ( Response )Actor::Anim }, + { &EV_Actor_Attack, ( Response )Actor::AttackEntity }, + { &EV_Actor_AttackPlayer, ( Response )Actor::AttackPlayer }, + { &EV_Actor_Remove, ( Response )Actor::RemoveUselessBody }, + { &EV_Actor_ForwardToBehavior,( Response )Actor::ForwardBehaviorEvent }, + + { &EV_Actor_ReserveNode, ( Response )Actor::ReserveNodeEvent }, + { &EV_Actor_ReleaseNode, ( Response )Actor::ReleaseNodeEvent }, + { &EV_Actor_IfCanHideAt, ( Response )Actor::IfCanHideAtEvent }, + { &EV_Actor_IfCanStrafeAttack,( Response )Actor::IfCanStrafeAttackEvent }, + { &EV_Actor_IfCanMeleeAttack, ( Response )Actor::IfCanMeleeAttackEvent }, + { &EV_Actor_IfCanShoot, ( Response )Actor::IfCanShootEvent }, + { &EV_Actor_IfEnemyWithin, ( Response )Actor::IfEnemyWithinEvent }, + + { &EV_HeardWeapon, ( Response )Actor::InvestigateWeaponSound }, + { &EV_HeardMovement, ( Response )Actor::InvestigateMovementSound }, + { &EV_HeardPain, ( Response )Actor::InvestigatePainSound }, + { &EV_HeardDeath, ( Response )Actor::InvestigateDeathSound }, + { &EV_HeardBreaking, ( Response )Actor::InvestigateBreakingSound }, + { &EV_HeardDoor, ( Response )Actor::InvestigateDoorSound }, + { &EV_HeardMutant, ( Response )Actor::InvestigateMutantSound }, + { &EV_HeardVoice, ( Response )Actor::InvestigateVoiceSound }, + { &EV_HeardMachine, ( Response )Actor::InvestigateMachineSound }, + { &EV_HeardRadio, ( Response )Actor::InvestigateRadioSound }, + + { &EV_Actor_Aim, ( Response )Actor::SetAim }, + { &EV_Actor_MeleeRange, ( Response )Actor::SetMeleeRange }, + { &EV_Actor_MeleeDamage, ( Response )Actor::SetMeleeDamage }, + { &EV_Actor_Melee, ( Response )Actor::MeleeEvent }, + { &EV_Actor_AttackFinished, ( Response )Actor::AttackFinishedEvent }, + + { &EV_Actor_CanStrafe, ( Response )Actor::CanStrafeEvent }, + { &EV_Actor_NoStrafe, ( Response )Actor::NoStrafeEvent }, + + { &EV_Actor_PainThreshold, ( Response )Actor::SetPainThresholdEvent }, + { &EV_Actor_SetKillThread, ( Response )Actor::SetKillThreadEvent }, + { &EV_Actor_AttackRange, ( Response )Actor::AttackRangeEvent }, + { &EV_Actor_ShotsPerAttack, ( Response )Actor::ShotsPerAttackEvent }, + { &EV_Actor_ClearEnemy, ( Response )Actor::ClearEnemyEvent }, + { &EV_Actor_AttackMode, ( Response )Actor::AttackModeEvent }, + { &EV_SetHealth, ( Response )Actor::SetHealth }, + { &EV_Actor_EyePositionOffset,( Response )Actor::EyeOffset }, + { &EV_Actor_NoDeathFade, ( Response )Actor::NoDeathFadeEvent }, + { &EV_Actor_NoChatter, ( Response )Actor::NoChatterEvent }, + { &EV_Actor_TurnSpeed, ( Response )Actor::TurnSpeedEvent }, + + { &EV_ScriptThread_Goto, ( Response )Actor::GotoEvent }, + + { NULL, NULL } + }; + +//*********************************************************************************************** +// +// Initialization functions +// +//*********************************************************************************************** + +Actor::Actor() + { + const char *text; + str script; + const char * skinname; + Event *ev; + SpawnArgs args; + + // don't spawn monsters in deathmatch + if ( deathmatch->value || nomonsters->value ) + { + PostEvent( EV_Remove, 0 ); + return; + } + + actortype = IS_ENEMY; + + setSolidType( SOLID_BBOX ); + setMoveType( MOVETYPE_STEP ); + + health = G_GetFloatArg( "health", 100 ); + max_health = health; + takedamage = DAMAGE_AIM; + mass = G_GetFloatArg( "mass", 200 ); + deadflag = DEAD_NO; + edict->clipmask = MASK_MONSTERSOLID; + edict->svflags |= SVF_MONSTER; + + forwardspeed = 0; + checkStrafe = true; + + // No longer used + movement = AI_CANWALK; + + lastmove = STEPMOVE_OK; + + gunoffset = "0 0 44"; + + numonstack = 0; + behavior = NULL; + path = NULL; + + newanimnum = -1; + newanim = ""; + newanimevent = NULL; + + text = G_GetSpawnArg( "weapon" ); + if ( text ) + { + giveWeapon( text ); + } + + spawngroup = G_GetStringArg( "spawngroup" ); + + attackmode = G_GetIntArg( "attackmode", 0 ); + + attack_range = G_GetFloatArg( "attackrange", 8192 ); + + fov = 150; + fovdot = cos( fov * 0.5 * M_PI / 180.0 ); + + vision_distance = G_GetFloatArg( "visiondistance", 1024 ); + + eyeoffset = "0 0 0"; + eyeposition = "0 0 64"; + + hasalert = false; + + lastEnemy = NULL; + enemyRange = RANGE_FAR; + seenEnemy = false; + nodeathfade = false; + nochatter = false; + + turnspeed = 60; + + if ( !parentmode->value ) + { + flags |= FL_BLOOD; + flags |= FL_DIE_GIBS; + } + + // + // don't talk all at once initially + // + chattime = G_Random( 20 ); + nextsoundtime = 0; + trig = NULL; + deathgib = false; + + // set default crouchsize + crouchsize_min = "-16 -16 0"; + crouchsize_max = "16 16 32"; + standsize_min = mins; + standsize_max = maxs; + + // use a cvar to help with debugging + ai_actorscript = gi.cvar( "ai_actorscript", "", 0 ); + actorscript = G_GetStringArg( "script", "global/enemy.scr" ); + actorstart = G_GetStringArg( "thread", "" ); + kill_thread = G_GetStringArg( "killthread", "" ); + + // default melee characteristics + melee_range = 100; + melee_damage = 30; + // default aim (normal) + aim = G_GetFloatArg( "aim", 0 ); + // default pain_threshold is 10 + pain_threshold = G_GetFloatArg( "painthreshold", 10 * skill->value ); + // default shots per attack is 5 + ( 2 * skill->level ) + shots_per_attack = G_GetFloatArg( "shotsperattack", 3 + ( 2 * skill->value ) ); + + startpos = worldorigin; + + next_drown_time = 0; + air_finished = level.time + 5; + last_jump_time = 0; + + CheckWater(); + + setSize( "-16 -16 0", "16 16 76" ); + //setModel( G_GetSpawnArg( "model", "grunt.def" ) ); + showModel(); + + if ( !LoadingSavegame ) + { + // save off our spawn args + args.SetArgs(); + G_InitSpawnArguments(); + + // force the init commands to be processed so that we start the right actor script immediately + CancelEventsOfType( EV_ProcessInitCommands ); + ev = new Event( EV_ProcessInitCommands ); + ev->AddInteger( edict->s.modelindex ); + ProcessEvent( ev ); + + SetupThread(); + + if ( eyeposition.z > maxs.z ) + { + eyeposition.z = maxs.z; + } + + // restore our args + G_InitSpawnArguments(); + args.RestoreArgs(); + + // wait until the script starts before thinking + PostEvent( EV_Actor_Start, FRAMETIME ); + } + + // + // I put this here, so that the initcommands would already be processed + // + skinname = G_GetSpawnArg( "skin" ); + if ( skinname && skinname[ 0 ] ) + { + int skinnum; + + skinnum = gi.Skin_NumForName( edict->s.modelindex, skinname ); + if (skinnum >= 0) + edict->s.skinnum = skinnum; + } + + } + +Actor::~Actor() + { + int n; + int i; + + if ( actorthread ) + { + actorthread->ProcessEvent( EV_ScriptThread_End ); + actorthread = NULL; + } + + // delete the old action/response list + n = actionList.NumObjects(); + for( i = n; i >= 1; i-- ) + { + delete actionList.ObjectAt( i ); + } + actionList.ClearObjectList(); + if ( behavior ) + { + delete behavior; + behavior = NULL; + } + + if ( path ) + { + delete path; + path = NULL; + } + + if ( trig ) + { + delete trig; + trig = NULL; + } + + ClearStateStack(); + } + +void Actor::Start + ( + Event *ev + ) + + { + MonsterStart *start; + + // This is only used for choosing delay times for targeting enemies + static int actornum = 0; + + hasalert = ( gi.Anim_Random( edict->s.modelindex, "alert" ) != -1 ); + + start = MonsterStart::GetRandomSpot( spawngroup ); + if ( start ) + { + setOrigin( start->worldorigin ); + worldorigin.copyTo( edict->s.old_origin ); + setAngles( start->worldangles ); + if ( start->animname != "" ) + { + SetAnim( start->animname ); + } + } + + droptofloor( 16 ); + + flags |= FL_PRETHINK; + + // see if we have any melee attacks + if ( HasAnim( "melee" ) ) + { + has_melee = true; + } + else + { + // + // make sure we can't knock the weapon out of this characters hands + // + if ( currentWeapon ) + { + Event * ev; + + ev = new Event( EV_Weapon_NotDroppable ); + currentWeapon->ProcessEvent( ev ); + } + has_melee = false; + } + + // spread their targeting about a bit + PostEvent( EV_Actor_TargetEnemies, ( actornum++ % 10 ) * FRAMETIME ); + } + +//*********************************************************************************************** +// +// Vision functions +// +//*********************************************************************************************** + +range_t Actor::Range + ( + Entity *targ + ) + + { + float r; + Vector delta; + + delta = centroid - targ->centroid; + r = delta * delta; + if ( r < 120 * 120 ) + { + return RANGE_MELEE; + } + + if ( r < 500 * 500 ) + { + return RANGE_NEAR; + } + + if ( r < 1000 * 1000 ) + { + return RANGE_MID; + } + + return RANGE_FAR; + } + +inline qboolean Actor::InFOV + ( + Vector pos + ) + + { + Vector delta; + float dot; + + delta = pos - EyePosition(); + if ( !delta.x && !delta.y ) + { + // special case for straight up and down + return true; + } + + // give better vertical vision + delta.z = 0; + + delta.normalize(); + dot = DotProduct( orientation[ 0 ], delta.vec3() ); + + return ( dot > fovdot ); + } + +inline qboolean Actor::InFOV + ( + Entity *ent + ) + + { + return InFOV( ent->centroid ); + } + +inline qboolean Actor::CanSeeFOV + ( + Entity *ent + ) + + { + return InFOV( ent ) && CanSeeFrom( worldorigin, ent ); + } + +inline qboolean Actor::CanSeeFrom + ( + Vector pos, + Entity *ent + ) + + { + trace_t trace; + Vector p; + + p = ent->centroid; + + // Check if he's visible + trace = G_Trace( pos + eyeposition, vec_zero, vec_zero, p, this, MASK_OPAQUE, "Actor::CanSeeFrom 1" ); + if ( trace.fraction == 1.0 || trace.ent == ent->edict ) + { + return true; + } + + // Check if his head is visible + p.z = ent->absmax.z; + trace = G_Trace( pos + eyeposition, vec_zero, vec_zero, p, this, MASK_OPAQUE, "Actor::CanSeeFrom 2" ); + if ( trace.fraction == 1.0 || trace.ent == ent->edict ) + { + return true; + } + + return false; + } + +qboolean Actor::CanSee + ( + Entity *ent + ) + + { + return CanSeeFrom( worldorigin, ent ); + } + +int Actor::EnemyCanSeeMeFrom + ( + Vector pos + ) + + { + Entity *ent; + int i; + int n; + float rad; + Vector d; + Vector p1; + Vector p2; + int c; + + rad = max( size.x, size.y ) * 1.44 * 0.5; + + c = 0; + n = enemyList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ent = enemyList.ObjectAt( i ); + if ( !ent || ent->deadflag || ( ent->flags & FL_NOTARGET ) ) + { + continue; + } + + if ( WithinDistance( ent, vision_distance ) ) + { + // To check if we're visible, I create a plane that intersects the actor + // and is perpendicular to the delta vector between the actor and his enemy. + // I place four points on this plane that "frame" the actor and check if + // the enemy can see any of those points. + d = ent->centroid - pos; + d.z = 0; + d.normalize(); + p1.x = -d.y; + p1.y = d.x; + p1 *= rad; + p2 = p1; + p1.z = mins.z; + p2.z = maxs.z; + + if ( CanSeeFrom( pos + p1, ent ) ) + { + c++; + } + if ( CanSeeFrom( pos + p2, ent ) ) + { + c++; + } + p1.z = -p1.z; + p2.z = -p2.z; + if ( CanSeeFrom( pos - p1, ent ) ) + { + c++; + } + if ( CanSeeFrom( pos - p2, ent ) ) + { + c++; + } + } + } + + return c; + } + +qboolean Actor::CanSeeEnemyFrom + ( + Vector pos + ) + + { + Entity *ent; + int i; + int n; + + n = enemyList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ent = enemyList.ObjectAt( i ); + if ( !ent || ent->deadflag || ( ent->flags & FL_NOTARGET ) ) + { + continue; + } + + if ( WithinDistance( ent, vision_distance ) && CanSeeFrom( pos, ent ) ) + { + return true; + } + } + + return false; + } + +//*********************************************************************************************** +// +// Weapon functions +// +//*********************************************************************************************** + +qboolean Actor::WeaponReady + ( + void + ) + + { + if ( currentWeapon && currentWeapon->ReadyToFire() ) + { + return true; + } + else if ( !currentWeapon && has_melee ) + { + return true; + } + + return false; + } + +void Actor::Attack + ( + Event *ev + ) + + { + Vector delta; + Vector ang; + Vector ang2; + + if ( ( currentWeapon ) && currentWeapon->ReadyToFire() && currentWeapon->HasAmmo() ) + { + if ( currentEnemy ) + { + ang = angles; + delta = currentEnemy->centroid - GunPosition(); + ang2 = delta.toAngles(); + ang2[ 0 ] = -ang2[ 0 ]; + setAngles( ang2 ); + currentWeapon->Fire(); + setAngles( ang ); + } + else + { + currentWeapon->Fire(); + } + } + } + +Vector Actor::GunPosition + ( + void + ) + + { + vec3_t trans[ 3 ]; + vec3_t orient; + int groupindex; + int tri_num; + Vector offset = vec_zero; + Vector result; + + // get the gun position of the actor + if ( !gi.GetBoneInfo( edict->s.modelindex, "gun", &groupindex, &tri_num, orient ) ) + { + // Gun doesn't have a barrel, just return the default + return worldorigin + gunoffset; + } + + gi.GetBoneTransform( edict->s.modelindex, groupindex, tri_num, orient, edict->s.anim, + edict->s.frame, edict->s.scale, trans, offset.vec3() ); + + MatrixTransformVector( offset.vec3(), orientation, result.vec3() ); + result += worldorigin; + + return result; + } + +inline Vector Actor::MyGunAngles + ( + Vector muzzlepos, + qboolean firing + ) + + { + Vector ang; + Vector dir; + + + if ( currentEnemy && firing ) + { + dir = currentEnemy->centroid - muzzlepos; + dir.z += ( currentEnemy->absmax.z - currentEnemy->centroid.z ) * 0.75f; + dir.normalize(); + + ang = dir.toAngles(); + ang.x = -ang.x; + } + else + { + ang.x = -worldangles.x; + ang.y = worldangles.y; + ang.z = worldangles.z; + } + + return ang; + } + + +#define MIN_AIM_DISTANCE 400 +inline void Actor::GetGunOrientation + ( + Vector muzzlepos, + Vector *forward, + Vector *right, + Vector *up + ) + + { + Vector ang; + float accuracy; + float spread; + float invaim; + float skl; + float enemydistance; + + ang = MyGunAngles( muzzlepos, true ); + + if ( currentEnemy ) + { + enemydistance = ( currentEnemy->centroid - muzzlepos ).length(); + if ( enemydistance < MIN_AIM_DISTANCE ) + enemydistance = MIN_AIM_DISTANCE; + } + else + { + enemydistance = MIN_AIM_DISTANCE; + } + + // 0 is maximum accuracy + invaim = 1.0f - aim; + if ( invaim < 0 ) + invaim = 0; + skl = min( skill->value, 3 ); + if ( skl < 1 ) + skl = 1; + accuracy = 1 - ( min( skl, 3 ) * 0.18 ); + accuracy *= invaim; + spread = ( 8 * MIN_AIM_DISTANCE * accuracy ) / enemydistance; + ang.x += G_CRandom( spread ); + ang.y += G_CRandom( spread ); + + ang.AngleVectors( forward, right, up ); + } + +qboolean Actor::CanShootFrom + ( + Vector pos, + Entity *ent, + qboolean usecurrentangles + ) + + { + int mask; + Vector delta; + Vector start; + Vector end; + float len; + trace_t trace; + Vehicle *v; + Entity *t; + Vector ang; + + if ( !currentWeapon || !WithinDistance( ent, vision_distance ) ) + { + if (!currentWeapon && !has_melee ) + return false; + } + + if ( usecurrentangles ) + { + Vector dir; + + start = pos + GunPosition() - worldorigin; +// start = pos + centroid - worldorigin; + end = ent->centroid; + end.z += ( ent->absmax.z - ent->centroid.z ) * 0.75f; + delta = end - start; + ang = delta.toAngles(); + ang.x = -ang.x; + ang.y = angles.y; + len = delta.length(); + ang.AngleVectors( &dir, NULL, NULL ); + dir *= len; + end = start + dir; + } + else + { + start = pos + GunPosition() - worldorigin; + end = ent->centroid; + end.z += ( ent->absmax.z - ent->centroid.z ) * 0.75f; + delta = end - start; + len = delta.length(); + } + + // check if we're too far away, or too close + if ( currentWeapon ) + { + if ( ( len > attack_range ) || ( len > currentWeapon->GetMaxRange() ) || ( len < currentWeapon->GetMinRange() ) ) + { + return false; + } + mask = MASK_SHOT; + } + else + { + if ( ( len > attack_range ) || ( len > melee_range ) ) + { + return false; + } + mask = MASK_PROJECTILE; + } + + // shoot past the guy we're shooting at + end += delta * 4; + +#if 0 + if ( usecurrentangles ) + { + G_DebugLine( start, end, 1, 0, 0, 1 ); + } + else + { + G_DebugLine( start, end, 1, 1, 0, 1 ); + } +#endif + + // Check if he's visible + trace = G_Trace( start, vec_zero, vec_zero, end, this, mask, "Actor::CanShootFrom" ); + if ( trace.startsolid ) + { + return false; + } + + // If we hit the guy we wanted, then shoot + if ( trace.ent == ent->edict ) + { + return true; + } + + // if we hit a vehicle, check if the driver is someone we want to hit + t = trace.ent->entity; + if ( t && t->isSubclassOf( Vehicle ) ) + { + v = ( Vehicle * )t; + if ( ( v->Driver() == ent ) || IsEnemy( v->Driver() ) ) + { + return true; + } + return false; + } + + // If we hit someone else we don't like, then shoot + if ( IsEnemy( t ) ) + { + return true; + } + + // if we hit something breakable, check if shooting it will + // let us shoot someone. + if ( t->isSubclassOf( Shatter ) || + t->isSubclassOf( Object ) || + t->isSubclassOf( DamageThreshold ) || + t->isSubclassOf( ScriptModel ) ) + { + trace = G_Trace( Vector( trace.endpos ), vec_zero, vec_zero, end, t, mask, "Actor::CanShootFrom 2" ); + if ( trace.startsolid ) + { + return false; + } + + // If we hit the guy we wanted, then shoot + if ( trace.ent == ent->edict ) + { + return true; + } + + // If we hit someone else we don't like, then shoot + if ( IsEnemy( trace.ent->entity ) ) + { + return true; + } + + // Forget it then + return false; + } + + return false; + } + +qboolean Actor::CanShoot + ( + Entity *ent, + qboolean usecurrentangles + ) + + { + return CanShootFrom( worldorigin, ent, usecurrentangles ); + } + +float Actor::AttackRange + ( + void + ) + + { + if ( !currentWeapon && !has_melee ) + { + return 0; + } + + if ( currentWeapon ) + { + return ( currentWeapon->GetMaxRange() ); + } + else + { + return ( melee_range ); + } + } + +float Actor::MinimumAttackRange + ( + void + ) + + { + float range; + float maxrange; + + if ( !currentWeapon && !has_melee ) + { + return 100; + } + + range = melee_range * 0.75f; + maxrange = melee_range; + + if ( currentWeapon ) + { + range = currentWeapon->GetMinRange(); + if ( !range && melee_range ) + range = melee_range * 0.75f; + maxrange = currentWeapon->GetMaxRange(); + } + + if ( range > maxrange ) + range = maxrange; + + return range; + } + +qboolean Actor::HasWeapon + ( + void + ) + { + return ( currentWeapon != NULL ); + } + +//*********************************************************************************************** +// +// Actor type script commands +// +//*********************************************************************************************** + +void Actor::FriendEvent + ( + Event *ev + ) + + { + actortype = IS_FRIEND; + } + +void Actor::CivilianEvent + ( + Event *ev + ) + + { + actortype = IS_CIVILIAN; + } + +void Actor::EnemyEvent + ( + Event *ev + ) + + { + actortype = IS_ENEMY; + } + +void Actor::InanimateEvent + ( + Event *ev + ) + + { + actortype = IS_INANIMATE; + // + // clear the monster flag so triggers are not triggered + // + edict->svflags &= ~SVF_MONSTER; + // + // don't make them move + // + setMoveType( MOVETYPE_NONE ); + // + // don't make it bleed + // + flags &= ~FL_BLOOD; + // + // don't make it gib + // + flags &= ~FL_DIE_GIBS; + } + +void Actor::MonsterEvent + ( + Event *ev + ) + + { + actortype = IS_MONSTER; + } + +void Actor::AnimalEvent + ( + Event *ev + ) + + { + actortype = IS_ANIMAL; + } + +//*********************************************************************************************** +// +// Enemy management +// +//*********************************************************************************************** + +qboolean Actor::HasEnemies + ( + void + ) + + { + return ( enemyList.NumObjects() > 0 ); + } + +qboolean Actor::IsEnemy + ( + Entity *ent + ) + + { + return enemyList.ObjectInList( EntityPtr( ent ) ) && seenEnemy; + } + +void Actor::MakeEnemy + ( + Entity *ent, + qboolean force + ) + + { + // don't get mad at things that can't be hurt or the world + if ( + ent && + ( ent != world ) && + ( ent != this ) && + !( ent->flags & FL_NOTARGET ) && + ( ent->takedamage != DAMAGE_NO ) + ) + { + if ( !enemyList.ObjectInList( EntityPtr( ent ) ) ) + { + enemyList.AddObject( EntityPtr( ent ) ); + } + + if ( !currentEnemy && !seenEnemy ) + { + currentEnemy = ent; + if ( DoAction( "sightenemy", force ) ) + { + seenEnemy = true; + Chatter( "snd_sightenemy", 5 ); + } + else + { + currentEnemy = NULL; + } + } + } + } + +void Actor::ClearEnemies + ( + void + ) + + { + currentEnemy = NULL; + seenEnemy = false; + enemyList.ClearObjectList(); + } + + +qboolean Actor::Likes + ( + Entity *ent + ) + + { + Actor *act; + + if ( ent->isClient() ) + { + return ( actortype == IS_FRIEND ); + } + else if ( actortype == IS_MONSTER ) + { + // monsters don't like anyone, but they don't particular hate everyone + return false; + } + else if ( ent->isSubclassOf( Actor ) ) + { + act = ( Actor * )ent; + return ( act->actortype == actortype ); + } + + return false; + } + +qboolean Actor::Hates + ( + Entity *ent + ) + + { + Actor *act; + + assert( ent ); + if ( !ent ) + { + return false; + } + + if ( ent->isClient() ) + { + if ( ent->flags & FL_SP_MUTANT ) + { + if ( actortype == IS_ENEMY ) + return false; + else + return true; + } + else + { + return ( actortype != IS_CIVILIAN ) && ( actortype != IS_FRIEND ); + } + } + else if ( ent->isSubclassOf( Actor ) && ( actortype != IS_INANIMATE ) ) + { + act = ( Actor * )ent; +// if ( act->actortype == IS_INANIMATE ) +// { +// // heh... Mutants hate inanimate objects. :) +// return ( actortype == IS_MONSTER ); +// } + if ( ( act->actortype <= IS_ENEMY ) && ( actortype <= IS_ENEMY ) ) + { + return false; + } + if ( ( act->actortype == IS_FRIEND ) && ( actortype <= IS_ENEMY ) ) + { + return true; + } + if ( ( act->actortype <= IS_ENEMY ) && ( actortype == IS_FRIEND ) ) + { + return true; + } + } + + return false; + } + +//*********************************************************************************************** +// +// Targeting functions +// +//*********************************************************************************************** + +qboolean Actor::GetVisibleTargets + ( + void + ) + + { + Sentient *ent; + Vector delta; + int i; + int n; + + targetList.ClearObjectList(); + nearbyList.ClearObjectList(); + + n = SentientList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ent = SentientList.ObjectAt( i ); + + //if ( ( ent == this ) || ent->deadflag || ( ent->flags & FL_NOTARGET ) || !Hates( ent ) ) + if ( ( ent == this ) || ( ent->flags & FL_NOTARGET ) || ent->hidden() ) + { + continue; + } + + if ( WithinDistance( ent, vision_distance ) && CanSeeFOV( ent ) ) + { + targetList.AddObject( EntityPtr( ent ) ); + if ( WithinDistance( ent, 96 ) ) + { + nearbyList.AddObject( EntityPtr( ent ) ); + } + } + } + + return ( targetList.NumObjects() > 0 ); + } + +void Actor::TargetEnemies + ( + Event *ev + ) + + { + int i; + int n; + Entity *ent; + Entity *newtarget; + Actor *act; + + if ( actortype == IS_INANIMATE ) + { + // inanimate objects don't need to worry about this kind of thing + return; + } + + if ( enemyList.NumObjects() >= 1 ) + { + // don't target new enemies as much while we've already got an enemy + PostEvent( EV_Actor_TargetEnemies, 5 ); + } + else + { + PostEvent( EV_Actor_TargetEnemies, 1 ); + } + + if ( hidden() ) + { + // don't target while hidden (for cinematic characters) + //FIXME + // probably should have a start/stop function + return; + } + + if ( GetVisibleTargets() ) + { + n = targetList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ent = targetList.ObjectAt( i ); + if ( ent ) + { + if ( ent->flags & (FL_CLOAK|FL_STEALTH) ) + continue; + + if ( !ent->deadflag && Hates( ent ) && !IsEnemy( ent ) ) + { + MakeEnemy( ent ); + } + else if ( ent->isSubclassOf( Actor ) && Likes( ent ) ) + { + act = ( Actor * )ent; + if ( act->currentEnemy && Hates( act->currentEnemy ) && !IsEnemy( act->currentEnemy ) ) + { + MakeEnemy( act->currentEnemy ); + if ( act->deadflag ) + { + // + // we have passed on the post mortem message of our death, so let's clear it + // + act->ClearEnemies(); + } + } + } + } + } + } + + newtarget = BestTarget(); + if ( newtarget && ( newtarget != currentEnemy ) ) + { + seenEnemy = false; + currentEnemy = newtarget; + if ( DoAction( "sightenemy" ) ) + { + seenEnemy = true; + Chatter( "snd_sightenemy", 5 ); + } + else + { + currentEnemy = NULL; + } + } + } + +Entity *Actor::BestTarget + ( + void + ) + + { + int i; + int n; + Entity *ent; + Entity *bestent; + float bestscore; + float score; + + bestscore = 8192 * 8192 * 20; + n = enemyList.NumObjects(); + if ( n == 1 ) + { + // don't waste our time when we only have one enemy + return enemyList.ObjectAt( 1 ); + } + + bestent = NULL; + for( i = 1; i <= n; i++ ) + { + ent = enemyList.ObjectAt( i ); + if ( !ent || ent->deadflag ) + { + enemyList.RemoveObjectAt( i ); + i--; + n--; + continue; + } + + score = Range( ent ) + 1; + if ( ent->health < WEAK_HEALTH ) + { + // Try to kill off really weak enemies + score *= WEAK_WEIGHT; + } + + if ( ent->health > health ) + { + score *= STRONGER_WEIGHT; + } + + if ( CanSeeFOV( ent ) ) + { + // We're more interested in guys we can see + score *= VISIBLE_WEIGHT; + } + + if ( i == n ) + { + // Favor the latest enemy + score *= NEWENEMY_WEIGHT; + } + + if ( score < bestscore ) + { + bestscore = score; + bestent = ent; + } + } + + return bestent; + } + +Sentient *Actor::NearFriend + ( + void + ) + + { + int i; + int num; + Entity *ent; + + num = nearbyList.NumObjects(); + for( i = 1; i < num; i++ ) + { + ent = nearbyList.ObjectAt( i ); + if ( Likes( ent ) ) + { + return ( Sentient * )ent; + } + } + + return NULL; + } + +qboolean Actor::CloseToEnemy + ( + Vector pos, + float howclose + ) + + { + Entity *ent; + int i; + int n; + + n = enemyList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ent = enemyList.ObjectAt( i ); + if ( !ent || ent->deadflag || ( ent->flags & FL_NOTARGET ) ) + { + continue; + } + + if ( WithinDistance( ent, howclose ) ) + { + return true; + } + } + + return false; + } + +void Actor::EyeOffset + ( + Event *ev + ) + { + eyeposition -= eyeoffset; + eyeoffset = ev->GetVector( 1 ); + eyeposition += eyeoffset; + } + +//*********************************************************************************************** +// +// State control functions +// +//*********************************************************************************************** + +void Actor::EnableState + ( + str action + ) + + { + StateInfo *ptr; + + ptr = GetState( action ); + if ( ptr ) + { + ptr->ignore = false; + } + } + +void Actor::DisableState + ( + str action + ) + + { + StateInfo *ptr; + + ptr = GetState( action ); + if ( ptr ) + { + ptr->ignore = true; + } + } + +StateInfo *Actor::SetResponse + ( + str action, + str response, + qboolean ignore + ) + + { + StateInfo *ptr; + + ptr = GetState( action ); + if ( !ptr ) + { + ptr = new StateInfo; + + actionList.AddObject( ptr ); + ptr->action = action; + } + + ptr->response = response; + ptr->ignore = ignore; + + return ptr; + } + +const char *Actor::GetResponse + ( + str action, + qboolean force + ) + + { + StateInfo *ptr; + + ptr = GetState( action ); + if ( ptr && ( force || !ptr->ignore ) ) + { + return ptr->response.c_str(); + } + + return ""; + } + +StateInfo *Actor::GetState + ( + str action + ) + + { + int i; + int n; + StateInfo *ptr; + + n = actionList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ptr = actionList.ObjectAt( i ); + if ( ptr->action == action ) + { + return ptr; + } + } + + return NULL; + } + +//*********************************************************************************************** +// +// State stack management +// +//*********************************************************************************************** + +void Actor::ClearStateStack + ( + void + ) + + { + ActorState *state; + int n; + int i; + + while( !stateStack.Empty() ) + { + state = stateStack.Pop(); + + if ( state->animDoneEvent ) + { + delete state->animDoneEvent; + } + + // delete the old action/response list + n = state->actionList.NumObjects(); + for( i = n; i >= 1; i-- ) + { + delete state->actionList.ObjectAt( i ); + } + state->actionList.ClearObjectList(); + + if ( state->behavior ) + { + delete state->behavior; + } + + if ( state->path ) + { + delete state->path; + } + + delete state; + } + + numonstack = 0; + } + +qboolean Actor::PopState + ( + void + ) + + { + ActorState *newstate; + int n; + int i; + +#ifdef DEBUG_PRINT + gi.dprintf( "%d Pop:", numonstack ); +#endif + if ( !stateStack.Empty() ) + { + newstate = stateStack.Pop(); + numonstack--; + + state = newstate->name; + +#ifdef DEBUG_PRINT + gi.dprintf( "state '%s' anim '%s'", state.c_str(), newstate->anim.c_str() ); +#endif + if ( newstate->anim.length() ) + { + SetAnim( newstate->anim, newstate->animDoneEvent ); + ChangeAnim(); + } + + SetPath( newstate->path ); + +#ifdef DEBUG_PRINT + if ( newstate->behavior ) + { + gi.dprintf( "%s", newstate->behavior->getClassname() ); + } + else + { + gi.dprintf( "NULL behavior" ); + } + gi.dprintf( "\n" ); +#endif + + // NULL out our current thread so that EndBehavior doesn't + // signal the thread that the behavior ended. + thread = NULL; + EndBehavior(); + + assert( !behavior ); + + // Set the thread after ending the old behavior, but before the new behavior + if ( newstate->thread != -1 ) + { + thread = Director.GetThread( newstate->thread ); + + // Since PopState is called from the thread, we don't need to tell the thread to continue processing. + // In fact, depending upon the state of the thread when we pushed it onto the stack, we may not want + // the thread to continue processing (for example, if the last command was a waitFor). Just restoring the + // position here will tell the thread to continue executing or wait for an event to occur. + // If we ever call PopState from outside of a thread, we MUST check to see if the thread should + // continue execution. + //FIXME + // This is probably an actorthread that was removed + // ADDENDUM: actorThread is now only removed once. Thread should never be NULL. + if ( thread ) + { + thread->Restore( &newstate->marker ); + } + } + else + { + thread = NULL; + } + + // delete the old action/response list + n = actionList.NumObjects(); + for( i = n; i >= 1; i-- ) + { + delete actionList.ObjectAt( i ); + } + actionList.ClearObjectList(); + + // Copy the new action/response list + n = newstate->actionList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + actionList.AddObject( newstate->actionList.ObjectAt( i ) ); + } + + assert( !behavior ); + + SetBehavior( newstate->behavior, NULL, thread ); + + delete newstate; + } + else + { +#ifdef DEBUG_PRINT + gi.dprintf( "\n" ); +#endif + EndBehavior(); + + return false; + } + + return true; + } + +void Actor::PushState + ( + const char *newstate, + ScriptThread *newthread, + ThreadMarker *marker + ) + + { + ActorState *oldstate; + int i; + int n; + + oldstate = new ActorState; + + // push the old state +#ifdef DEBUG_PRINT + gi.dprintf( "%d : Pushing old state %s\n", numonstack, state.c_str() ); + if ( behavior ) + { + gi.dprintf( "old behavior %s\n", behavior->getClassname() ); + } + else + { + gi.dprintf( "old behavior NULL\n" ); + } + gi.dprintf( "new state %s\n", newstate ); +#endif + + oldstate->name = state; + oldstate->anim = animname; + oldstate->animDoneEvent = animDoneEvent; + animDoneEvent = NULL; + + oldstate->path = path; + + oldstate->behavior = behavior; + + // newthread must always be the old thread + assert( newthread ); + oldstate->thread = newthread->ThreadNum(); + assert( marker ); + oldstate->marker = *marker; + + // Copy the action/response list + n = actionList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + StateInfo *ptr; + StateInfo *newobj; + + ptr = actionList.ObjectAt( i ); + newobj = new StateInfo; + newobj->action = ptr->action; + newobj->response = ptr->response; + newobj->ignore = ptr->ignore; + oldstate->actionList.AddObject( newobj ); + } + + numonstack++; + stateStack.Push( oldstate ); + + // Any SetBehavior following this will delete this behavior, so null it out so that it doesn't happen + if ( behavior ) + { + behavior->End( *this ); + behavior = NULL; + } + + state = newstate; + thread = newthread; + } + +//*********************************************************************************************** +// +// State control script commands +// +//*********************************************************************************************** + +void Actor::DefineStateEvent + ( + Event *ev + ) + + { + const char *action; + str response; + ScriptThread *thread; + str script; + int len; + + action = ev->GetString( 1 ); + response = ev->GetString( 2 ); + + // check if we have a filename in the label + if ( !strstr( response.c_str(), "::" ) ) + { + thread = ev->GetThread(); + if ( thread ) + { + // add filename to the label so that if we jump to another script, our labels are still valid + response = str( thread->Filename() ) + "::" + response; + } + } + else + { + // prepend our debug directory name + script = ai_actorscript->string; + len = script.length(); + + // if we have a directory, make sure that it ends with a '/' or a '\' + if ( ( len > 0 ) && ( script[ len - 1 ] != '/' ) && ( script[ len - 1 ] != '\\' ) ) + { + script += "/"; + } + + response = script + response; + } + + SetResponse( action, response ); + } + +void Actor::CopyStateEvent + ( + Event *ev + ) + + { + str action1; + str action2; + str response; + StateInfo *ptr; + + action1 = ev->GetString( 1 ); + action2 = ev->GetString( 2 ); + + ptr = GetState( action2 ); + if ( ptr ) + { + response = ptr->response; + } + + SetResponse( action1, response ); + } + +void Actor::IgnoreAllEvent + ( + Event *ev + ) + + { + int i; + int n; + + n = actionList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + actionList.ObjectAt( i )->ignore = true; + } + } + +void Actor::IgnoreEvent + ( + Event *ev + ) + + { + int i; + int n; + + n = ev->NumArgs(); + for( i = 1; i <= n; i++ ) + { + DisableState( ev->GetString( i ) ); + } + } + +void Actor::RespondToAllEvent + ( + Event *ev + ) + + { + int i; + int n; + + n = actionList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + actionList.ObjectAt( i )->ignore = false; + } + } + +void Actor::RespondToEvent + ( + Event *ev + ) + + { + int i; + int n; + + n = ev->NumArgs(); + for( i = 1; i <= n; i++ ) + { + EnableState( ev->GetString( i ) ); + } + } + +void Actor::ClearStateEvent + ( + Event *ev + ) + + { + ClearStateStack(); + } + +void Actor::StateDoneEvent + ( + Event *ev + ) + + { + assert( actorthread == ev->GetThread() ); + + if ( !deadflag || ( actortype == IS_INANIMATE ) ) + { + // if we're not dead, pop off the previous state + PopState(); + } + else + { + // if we're dead, kill off all the previous states + ClearStateStack(); + + // kill off the thread + actorthread->ProcessEvent( EV_ScriptThread_End ); + actorthread = NULL; + } + } + +void Actor::SetStateEvent + ( + Event *ev + ) + + { + str response; + str name; + ThreadMarker marker; + StateInfo *ptr; + + if ( ( deadflag == DEAD_DEAD ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + name = ev->GetString( 1 ); + + // Don't check ignore flag + ptr = GetState( name ); + if ( ptr ) + { + response = ptr->response; + } + +#ifdef DEBUG_PRINT + gi.dprintf( "%d : %d : Action: %s - %s\n", + actorthread->ThreadNum(), ev->GetThread()->ThreadNum(), + name.c_str(), response.c_str() ); +#endif + + actorthread->Mark( &marker ); + if ( response != "" && actorthread->Goto( response.c_str() ) ) + { + PushState( name.c_str(), actorthread, &marker ); + SetAnim( "idle" ); + animname = "idle"; + SetVariable( "state", name.c_str() ); + ProcessScript( actorthread ); + } + else + { + ev->Error( "Could not find label '%s'", response.c_str() ); + actorthread->Goto( response.c_str() ); + } + } + +void Actor::CanStrafeEvent + ( + Event *ev + ) + + { + checkStrafe = true; + } + +void Actor::NoStrafeEvent + ( + Event *ev + ) + + { + checkStrafe = false; + } + +//*********************************************************************************************** +// +// Thread management +// +//*********************************************************************************************** + +void Actor::SetupThread + ( + void + ) + + { + str script; + int len; + Event *event; + + // we should never have a thread at this point + assert( !actorthread ); + + // prepend our debug director name + script = ai_actorscript->string; + len = script.length(); + + // if we have a directory, make sure that it ends with a '/' or a '\' + if ( ( len > 0 ) && ( script[ len - 1 ] != '/' ) && ( script[ len - 1 ] != '\\' ) ) + { + script += "/"; + } + script += actorscript; + actorthread = Director.CreateThread( script.c_str(), MODEL_SCRIPT ); + + if ( actorthread ) + { + // setup thread variables + SetVariable( "attackmode", attackmode ); + + if ( actorstart.length() ) + { + if ( !actorthread->Goto( actorstart.c_str() ) ) + { + gi.dprintf( "Label '%s' not found in %s.", actorstart.c_str(), script.c_str() ); + } + } + + ProcessScript( actorthread ); + } + + assert( actorthread ); + if ( !actorthread ) + { + // not having a script is bad + gi.dprintf( "Unable to start actor script '%s'. Killing actor '%s'(%d).\n", + script.c_str(), targetname.c_str(), entnum ); + + // just kill him + event = new Event( EV_Killed ); + event->AddEntity( this ); + event->AddInteger( 0 ); + event->AddEntity( this ); + event->AddString( "all" ); + ProcessEvent( event ); + + return; + } + } + +qboolean Actor::DoAction + ( + str name, + qboolean force + ) + + { + str response; + ThreadMarker marker; + + if ( !actorthread ) + { + return false; + } + + if ( ( deadflag == DEAD_DEAD ) && ( actortype != IS_INANIMATE ) ) + { + return false; + } + + response = GetResponse( name, force ); + +#ifdef DEBUG_PRINT + gi.dprintf( "Action: %s - %s\n", name.c_str(), response.c_str() ); +#endif + + actorthread->Mark( &marker ); + if ( response != "" && actorthread->Goto( response.c_str() ) ) + { + PushState( name.c_str(), actorthread, &marker ); + SetAnim( "idle" ); + animname = "idle"; + SetVariable( "state", name.c_str() ); + ProcessScript( actorthread ); + return true; + } + + return false; + } + +qboolean Actor::ForceAction + ( + str name + ) + + { + return DoAction( name, true ); + } + +void Actor::ProcessScript + ( + ScriptThread *thread, + Event *ev + ) + + { + thread->Vars()->SetVariable( "self", this ); + thread->Vars()->SetVariable( "origin", worldorigin ); + thread->Vars()->SetVariable( "yaw", worldangles.y ); + thread->Vars()->SetVariable( "health", health ); + thread->Vars()->SetVariable( "startpos", startpos ); + thread->Vars()->SetVariable( "shots_per_attack", shots_per_attack ); + // + // see if the enemy lost his weapon + // i.e. it was shot out of his hands + // + if ( currentWeapon ) + { + thread->Vars()->SetVariable( "has_weapon", 1 ); + } + else + { + thread->Vars()->SetVariable( "has_weapon", 0 ); + } + + + if ( currentEnemy ) + { + thread->Vars()->SetVariable( "enemy", currentEnemy ); + } + else + { + thread->Vars()->SetVariable( "enemy", "" ); + } + + if ( ev ) + { + thread->ProcessEvent( ev ); + } + else + { + thread->ProcessEvent( EV_ScriptThread_Execute ); + } + } + +void Actor::StartMove + ( + Event *ev + ) + + { + if ( deadflag ) + { + return; + } + +// thread = ev->GetThread(); + } + +inline ScriptVariable *Actor::SetVariable + ( + const char *name, + float value + ) + + { + if ( actorthread ) + { + return actorthread->Vars()->SetVariable( name, value ); + } + + return NULL; + } + +inline ScriptVariable *Actor::SetVariable + ( + const char *name, + int value + ) + + { + if ( actorthread ) + { + return actorthread->Vars()->SetVariable( name, value ); + } + + return NULL; + } + +inline ScriptVariable *Actor::SetVariable + ( + const char *name, + const char *text + ) + + { + if ( actorthread ) + { + return actorthread->Vars()->SetVariable( name, text ); + } + + return NULL; + } + +inline ScriptVariable *Actor::SetVariable + ( + const char *name, + str &text + ) + + { + if ( actorthread ) + { + return actorthread->Vars()->SetVariable( name, text.c_str() ); + } + + return NULL; + } + +inline ScriptVariable *Actor::SetVariable + ( + const char *name, + Entity *ent + ) + + { + if ( actorthread ) + { + return actorthread->Vars()->SetVariable( name, ent ); + } + + return NULL; + } + +inline ScriptVariable *Actor::SetVariable + ( + const char *name, + Vector &vec + ) + + { + if ( actorthread ) + { + return actorthread->Vars()->SetVariable( name, vec ); + } + + return NULL; + } + +//*********************************************************************************************** +// +// Thread based script commands +// +//*********************************************************************************************** + +void Actor::SetScript + ( + Event *ev + ) + + { + str script; + str label; + int len; + + actorscript = ev->GetString( 1 ); + + // gotta start the thread again! + if ( actorthread ) + { + // prepend our debug directory name + script = ai_actorscript->string; + len = script.length(); + + // if we have a directory, make sure that it ends with a '/' or a '\' + if ( ( len > 0 ) && ( script[ len - 1 ] != '/' ) && ( script[ len - 1 ] != '\\' ) ) + { + script += "/"; + } + + script += actorscript; + if ( !actorthread->SetScript( script.c_str() ) ) + { + ev->Error( "Script '%s' not found", script.c_str() ); + } + else + { + // setup thread variables + SetVariable( "attackmode", attackmode ); + + ProcessScript( actorthread ); + } + } + } + +void Actor::SetThread + ( + Event *ev + ) + + { + ScriptThread *thread; + + if ( ( deadflag == DEAD_DEAD ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + thread = ev->GetThread(); + actorstart = ev->GetString( 1 ); + + // get the name of the script + if ( thread ) + { + actorstart = str( thread->Filename() ) + "::" + actorstart; + } + + // we may be getting this command from the .def file, so the thread may not have started yet + if ( actorthread && actorstart.length() ) + { + // setup thread variables + SetVariable( "attackmode", attackmode ); + + if ( actorthread->Goto( actorstart.c_str() ) ) + { + ProcessScript( actorthread ); + } + else + { + ev->Error( "Label '%s' not found. Couldn't change thread.", actorstart.c_str() ); + } + } + } + +//*********************************************************************************************** +// +// Behavior management +// +//*********************************************************************************************** + +void Actor::EndBehavior + ( + void + ) + + { + ScriptThread *t; + Event *event; + + if ( behavior ) + { + behavior->End( *this ); + delete behavior; + behavior = NULL; + + if ( thread ) + { + t = thread; + thread = NULL; + event = new Event( EV_MoveDone ); + event->AddEntity( this ); +#if 0 + if ( t == actorthread ) + { + ProcessScript( actorthread, event ); + } + else + { + t->ProcessEvent( event ); + } +#else + ProcessScript( actorthread, event ); + if ( t != actorthread ) + { + event = new Event( EV_MoveDone ); + event->AddEntity( this ); + t->ProcessEvent( event ); + } +#endif + } + } + } + +void Actor::SetBehaviorEvent + ( + Event *ev + ) + + { + ClassDef *cls; + ScriptThread *thread; + Event *e; + str name; + int i; + int n; + + name = ev->GetString( 1 ); + if ( !checkInheritance( &Behavior::ClassInfo, name.c_str() ) ) + { + ev->Error( "%s is not a valid behavior\n", name.c_str() ); + return; + } + + thread = ev->GetThread(); + + e = new Event( EV_Behavior_Args ); + e->SetSource( EV_FROM_SCRIPT ); + e->SetThread( thread ); + e->SetLineNumber( ev->GetLineNumber() ); + + e->AddEntity( this ); + + n = ev->NumArgs(); + for( i = 2; i <= n; i++ ) + { + e->AddToken( ev->GetToken( i ) ); + } + + cls = getClass( name.c_str() ); + SetBehavior( ( Behavior * )cls->newInstance(), e, thread ); + } + +void Actor::SetBehavior + ( + Behavior *newbehavior, + Event *startevent, + ScriptThread *newthread + ) + + { + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + // End any previous behavior, but don't call EV_MoveDone if we're using the same thread, + // or we'll end THIS behavior + if ( thread == newthread ) + { + thread = NULL; + } + EndBehavior(); + + behavior = newbehavior; + if ( behavior ) + { + if ( actortype == IS_INANIMATE ) + { + // think while we have a behavior + flags |= FL_PRETHINK; + } + +#ifdef DEBUG_PRINT + gi.dprintf( "SetBehavior %s\n", behavior->getClassname() ); +#endif + if ( startevent ) + { + behavior->ProcessEvent( startevent ); + } + currentBehavior = behavior->getClassname(); + behavior->Begin( *this ); + thread = newthread; + } + } + +void Actor::FinishedBehavior + ( + Event *ev + ) + + { + EndBehavior(); + } + +void Actor::NotifyBehavior + ( + Event *ev + ) + + { + if ( behavior ) + { + behavior->ProcessEvent( EV_Behavior_AnimDone ); + } + } + +void Actor::ForwardBehaviorEvent + ( + Event *ev + ) + + { + ScriptThread *thread; + Event *e; + str name; + int i; + int n; + + name = ev->GetString( 1 ); + + thread = ev->GetThread(); + e = new Event( name ); + e->SetSource( EV_FROM_SCRIPT ); + e->SetThread( thread ); + e->SetLineNumber( ev->GetLineNumber() ); + + // always add who got this event + e->AddEntity( this ); + n = ev->NumArgs(); + for( i = 2; i <= n; i++ ) + { + e->AddToken( ev->GetToken( i ) ); + } + + if ( behavior ) + behavior->ProcessEvent( e ); + else + warning( "ForwardBehaviorEvent", "no behavior defined" ); + } + +//*********************************************************************************************** +// +// Path and node management +// +//*********************************************************************************************** + +void Actor::SetPath + ( + Path *newpath + ) + + { + if ( path && ( path != newpath ) ) + { + delete path; + } + path = newpath; + } + +void Actor::ReserveNodeEvent + ( + Event *ev + ) + + { + PathNode *node; + Vector pos; + + pos = ev->GetVector( 1 ); + node = PathManager.NearestNode( pos, this ); + + if ( node && ( !node->entnum || ( node->entnum == entnum ) || ( node->occupiedTime < level.time ) ) ) + { + // Mark node as occupied for a short time + node->occupiedTime = level.time + ev->GetFloat( 2 ); + node->entnum = entnum; + } + } + +void Actor::ReleaseNodeEvent + ( + Event *ev + ) + + { + PathNode *node; + Vector pos; + + pos = ev->GetVector( 1 ); + node = PathManager.NearestNode( pos, this ); + + if ( node && ( node->entnum == entnum ) ) + { + node->occupiedTime = 0; + node->entnum = 0; + } + } + +//*********************************************************************************************** +// +// Animation control functions +// +//*********************************************************************************************** + +void Actor::ChangeAnim + ( + void + ) + + { + float time; + Vector totalmove; + + if ( newanimnum == -1 ) + { + return; + } + + // If we're changing to the same anim, don't restart the animation + if ( animating && newanimnum == edict->s.anim ) + { + if ( animDoneEvent ) + { + delete animDoneEvent; + } + + animDoneEvent = newanimevent; + } + else + { + StopAnimating(); + + animname = newanim; + + time = gi.Anim_Time( edict->s.modelindex, newanimnum ); + gi.Anim_Delta( edict->s.modelindex, newanimnum, totalmove.vec3() ); + + totalmove[ 1 ] = -totalmove[ 1 ]; + totalmove *= edict->s.scale; + totallen = totalmove.length(); + + // always have a valid move direction + if ( totallen > 0.01 ) + { + movespeed = totallen / time; + animdir = totalmove * ( 1 / totallen ); + } + else + { + if ( forwardspeed ) + { + movespeed = forwardspeed; + } + else + { + movespeed = 1; + } + animdir = Vector( 1, 0, 0 ); + } + + MatrixTransformVector( animdir.vec3(), orientation, movedir.vec3() ); + movevelocity = movedir * movespeed; + + NextAnim( newanimnum ); + animDoneEvent = newanimevent; + StartAnimating(); + } + + // clear the new anim variables + newanimnum = -1; + newanim = ""; + newanimevent = NULL; + } + +qboolean Actor::SetAnim + ( + str anim, + Event *ev + ) + + { + int num; + + // FIXME + // HACK to make actors use alert when fighting instead of idle + if ( hasalert && currentEnemy && ( anim == "idle" ) ) + { + anim = "alert"; + } + + num = gi.Anim_Random( edict->s.modelindex, anim.c_str() ); + if ( num != -1 ) + { + newanim = anim; + newanimnum = num; + newanimevent = ev; + + if ( actortype == IS_INANIMATE ) + { + // inanimate objects change anims immediately + ChangeAnim(); + } + + return true; + } + + // kill the event + if ( ev ) + { + delete ev; + } + + return false; + } + +qboolean Actor::SetAnim + ( + str anim, + Event &ev + ) + + { + Event * event; + + event = new Event( ev ); + return SetAnim( anim, event ); + } + +void Actor::Anim + ( + Event *ev + ) + + { + Event *e; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + e = new Event( EV_Behavior_Args ); + e->SetSource( EV_FROM_SCRIPT ); + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + + e->AddEntity( this ); + e->AddToken( ev->GetToken( 1 ) ); + + SetBehavior( new PlayAnim, e, ev->GetThread() ); + } + +//*********************************************************************************************** +// +// Script commands +// +//*********************************************************************************************** + +void Actor::CrouchSize + ( + Event *ev + ) + + { + crouchsize_min = ev->GetVector( 1 ); + crouchsize_max = ev->GetVector( 2 ); + } + +void Actor::SetFov + ( + Event *ev + ) + + { + fov = ev->GetFloat( 1 ); + fovdot = cos( fov * 0.5 * M_PI / 180.0 ); + } + +void Actor::SetVisionDistance + ( + Event *ev + ) + + { + vision_distance = ev->GetFloat( 1 ); + } + +void Actor::LookAt + ( + Event *ev + ) + + { + Entity *ent; + TurnTo *turnTo; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + ent = ev->GetEntity( 1 ); + if ( ent && ent != world ) + { + turnTo = new TurnTo; + turnTo->SetTarget( ent ); + SetBehavior( turnTo, NULL, ev->GetThread() ); + } + } + +void Actor::TurnToEvent + ( + Event *ev + ) + + { + TurnTo *turnTo; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + turnTo = new TurnTo; + turnTo->SetDirection( ev->GetFloat( 1 ) ); + + SetBehavior( turnTo, NULL, ev->GetThread() ); + } + +void Actor::IdleEvent + ( + Event *ev + ) + + { + Event *e; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + e = new Event( EV_Behavior_Args ); + e->SetSource( EV_FROM_SCRIPT ); + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + + e->AddEntity( this ); + e->AddString( ev->GetToken( 1 ) ); + SetBehavior( new Idle, e, ev->GetThread() ); + } + +void Actor::WalkTo + ( + Event *ev + ) + + { + Event *e; + int i; + int n; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + e = new Event( EV_Behavior_Args ); + e->SetSource( EV_FROM_SCRIPT ); + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + + e->AddEntity( this ); + + n = ev->NumArgs(); + + e->AddString( "walk" ); + for( i = 1; i <= n; i++ ) + { + e->AddToken( ev->GetToken( i ) ); + } + + SetBehavior( new GotoPathNode, e, ev->GetThread() ); + } + +void Actor::RunTo + ( + Event *ev + ) + + { + Event *e; + int i; + int n; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + e = new Event( EV_Behavior_Args ); + e->SetSource( EV_FROM_SCRIPT ); + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + + e->AddEntity( this ); + + n = ev->NumArgs(); + + e->AddString( "run" ); + for( i = 1; i <= n; i++ ) + { + e->AddToken( ev->GetToken( i ) ); + } + + SetBehavior( new GotoPathNode, e, ev->GetThread() ); + } + +void Actor::AttackEntity + ( + Event *ev + ) + + { + Entity *ent; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + ent = ev->GetEntity( 1 ); + // don't get mad at things that can't be hurt or the world + if ( + ent && + ( ent != world ) && + ( ent->takedamage != DAMAGE_NO ) + ) + { + ClearEnemies(); + MakeEnemy( ent, true ); + } + } + +void Actor::AttackPlayer + ( + Event *ev + ) + + { + int i; + edict_t *ent; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + ClearEnemies(); + + // make enemies of all the players + for( i = 0; i < maxclients->value; i++ ) + { + ent = &g_edicts[ i + 1 ]; + if ( !ent->inuse || !ent->client || !ent->entity ) + { + continue; + } + + MakeEnemy( ent->entity, true ); + } + + currentEnemy = BestTarget(); + if ( state != "sightenemy" ) + { + if ( ForceAction( "sightenemy" ) ) + { + seenEnemy = true; + Chatter( "snd_sightenemy", 5 ); + } + } + } + +void Actor::JumpToEvent + ( + Event *ev + ) + + { + Event *e; + int i; + int n; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + e = new Event( EV_Behavior_Args ); + e->SetSource( EV_FROM_SCRIPT ); + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + + e->AddEntity( this ); + + n = ev->NumArgs(); + + e->AddString( "jump" ); + for( i = 1; i <= n; i++ ) + { + e->AddToken( ev->GetToken( i ) ); + } + + SetBehavior( new Jump, e, ev->GetThread() ); + } + +void Actor::GotoEvent + ( + Event *ev + ) + + { + // This command was added because it was a common mistake when writing actor scripts + // to say "local.self goto label". Since we're such nice guys, we'll print a warning + // and send it on the the calling thread. :) + ScriptThread *thread; + + thread = ev->GetThread(); + if ( thread ) + { + gi.dprintf( "Actor recieved 'goto' command. Passing to thread.\n" ); + thread->ProcessEvent( ev ); + } + else + { + // should never happen, but say something anyways. + gi.dprintf( "Actor recieved 'goto' command. Thread is NULL.\n" ); + } + } + +//*********************************************************************************************** +// +// Script conditionals +// +//*********************************************************************************************** + +void Actor::IfEnemyVisibleEvent + ( + Event *ev + ) + + { + ScriptThread *thread; + + thread = ev->GetThread(); + assert( thread ); + if ( !thread ) + { + return; + } + + if ( CanSeeEnemyFrom( worldorigin ) ) + { + thread->ProcessCommandFromEvent( ev, 1 ); + } + } + +void Actor::IfNearEvent + ( + Event *ev + ) + + { + ScriptThread *thread; + Entity *ent; + Entity *bestent; + float bestdist; + float dist; + str name; + Vector delta; + float distance; + TargetList *tlist; + int n; + int i; + + thread = ev->GetThread(); + assert( thread ); + if ( !thread ) + { + return; + } + + name = ev->GetString( 1 ); + distance = ev->GetFloat( 2 ); + + if ( name[ 0 ] == '*' ) + { + ent = ev->GetEntity( 1 ); + if ( WithinDistance( ent, distance ) ) + { + SetVariable( "other", ent ); + thread->ProcessCommandFromEvent( ev, 3 ); + } + } + else if ( name[ 0 ] == '$' ) + { + bestent = NULL; + bestdist = distance * distance; + + tlist = world->GetTargetList( str( &name[ 1 ] ) ); + n = tlist->list.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ent = tlist->list.ObjectAt( i ); + delta = centroid - ent->centroid; + dist = delta * delta; + if ( dist <= bestdist ) + { + bestent = ent; + bestdist = dist; + } + } + + if ( bestent ) + { + SetVariable( "other", bestent ); + thread->ProcessCommandFromEvent( ev, 3 ); + } + } + else + { + bestent = NULL; + bestdist = distance * distance; + + ent = NULL; + while( ent = findradius( ent, worldorigin.vec3(), distance ) ) + { + if ( ent->inheritsFrom( name.c_str() ) ) + { + delta = centroid - ent->centroid; + dist = delta * delta; + if ( dist <= bestdist ) + { + bestent = ent; + bestdist = dist; + } + } + } + + if ( bestent ) + { + SetVariable( "other", bestent ); + thread->ProcessCommandFromEvent( ev, 3 ); + } + } + } + +void Actor::IfCanHideAtEvent + ( + Event *ev + ) + + { + PathNode *node; + Vector pos; + ScriptThread *thread; + + thread = ev->GetThread(); + assert( thread ); + if ( !thread ) + { + return; + } + + pos = ev->GetVector( 1 ); + node = PathManager.NearestNode( pos, this ); + + if ( node && ( node->nodeflags & ( AI_DUCK | AI_COVER ) ) && !CanSeeEnemyFrom( pos ) ) + { + if ( !node->entnum || ( node->entnum == entnum ) || ( node->occupiedTime < level.time ) ) + { + thread->ProcessCommandFromEvent( ev, 2 ); + } + } + } + +void Actor::IfCanStrafeAttackEvent + ( + Event *ev + ) + + { + ScriptThread *thread; + int num; + Vector delta; + Vector left; + Vector pos; + + if ( !checkStrafe ) + return; + + if ( !currentEnemy ) + { + return; + } + + thread = ev->GetThread(); + assert( thread ); + if ( !thread ) + { + return; + } + + delta = currentEnemy->worldorigin - worldorigin; + left.x = -delta.y; + left.y = delta.x; + left.normalize(); + + num = gi.Anim_Random( edict->s.modelindex, "step_left" ); + if ( num != -1 ) + { + gi.Anim_Delta( edict->s.modelindex, num, delta.vec3() ); + delta *= edict->s.scale; + pos = worldorigin + left * delta.length(); + if ( CanMoveTo( pos ) && CanShootFrom( pos, currentEnemy, false ) ) + { + thread->ProcessCommandFromEvent( ev, 1 ); + return; + } + } + + num = gi.Anim_Random( edict->s.modelindex, "step_right" ); + if ( num != -1 ) + { + gi.Anim_Delta( edict->s.modelindex, num, delta.vec3() ); + delta *= edict->s.scale; + pos = worldorigin - left * delta.length(); + if ( CanMoveTo( pos ) && CanShootFrom( pos, currentEnemy, false ) ) + { + thread->ProcessCommandFromEvent( ev, 1 ); + return; + } + } + } + + +void Actor::IfCanMeleeAttackEvent + ( + Event *ev + ) + + { + ScriptThread *thread; + Vector delta; + float r; + + if ( !currentEnemy || !has_melee ) + { + return; + } + + thread = ev->GetThread(); + assert( thread ); + if ( !thread ) + { + return; + } + + delta = centroid - currentEnemy->centroid; + r = delta.length(); + if ( r <= melee_range ) + { + thread->ProcessCommandFromEvent( ev, 1 ); + } + } + +void Actor::IfCanShootEvent + ( + Event *ev + ) + + { + ScriptThread *thread; + + if ( !currentEnemy ) + { + return; + } + + thread = ev->GetThread(); + assert( thread ); + if ( !thread ) + { + return; + } + + if ( CanShoot( currentEnemy, false ) ) + { + thread->ProcessCommandFromEvent( ev, 1 ); + } + } + +void Actor::IfEnemyWithinEvent + ( + Event *ev + ) + + { + ScriptThread *thread; + + if ( !currentEnemy ) + { + return; + } + + thread = ev->GetThread(); + assert( thread ); + if ( !thread ) + { + return; + } + + if ( WithinDistance( currentEnemy, ev->GetFloat( 1 ) ) ) + { + thread->ProcessCommandFromEvent( ev, 2 ); + } + } + +//*********************************************************************************************** +// +// Sound reaction functions +// +//*********************************************************************************************** + +void Actor::IgnoreSoundsEvent + ( + Event *ev + ) + + { + DisableState( "weaponsound" ); + DisableState( "movementsound" ); + DisableState( "painsound" ); + DisableState( "deathsound" ); + DisableState( "breakingsound" ); + DisableState( "doorsound" ); + DisableState( "mutantsound" ); + DisableState( "voicesound" ); + DisableState( "machinesound" ); + DisableState( "radiosound" ); + } + +void Actor::RespondToSoundsEvent + ( + Event *ev + ) + + { + EnableState( "weaponsound" ); + EnableState( "movementsound" ); + EnableState( "painsound" ); + EnableState( "deathsound" ); + EnableState( "breakingsound" ); + EnableState( "doorsound" ); + EnableState( "mutantsound" ); + EnableState( "voicesound" ); + EnableState( "machinesound" ); + EnableState( "radiosound" ); + } + +void Actor::InvestigateWeaponSound + ( + Event *ev + ) + + { + Vector location; + Entity *other; + + if ( !currentEnemy && !deadflag && ( nextsoundtime < level.time ) ) + { + other = ev->GetEntity( 1 ); + location = ev->GetVector( 2 ); + SetVariable( "other", other ); + SetVariable( "location", location ); + if ( DoAction( "weaponsound" ) ) + { + nextsoundtime = level.time + 2; + } + } + } + +void Actor::InvestigateMovementSound + ( + Event *ev + ) + + { + Vector location; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) ) + { + location = ev->GetVector( 2 ); + SetVariable( "other", other ); + SetVariable( "location", location ); + if ( DoAction( "movementsound" ) ) + nextsoundtime = level.time + 2; + } + } + +void Actor::InvestigatePainSound + ( + Event *ev + ) + + { + Vector location; + Entity *other; + + if ( !currentEnemy && !deadflag && ( nextsoundtime < level.time ) ) + { + other = ev->GetEntity( 1 ); + location = ev->GetVector( 2 ); + SetVariable( "other", other ); + SetVariable( "location", location ); + if ( DoAction( "painsound" ) ) + nextsoundtime = level.time + 2; + } + } + +void Actor::InvestigateDeathSound + ( + Event *ev + ) + + { + Vector location; + Entity *other; + + if ( !currentEnemy && !deadflag && ( nextsoundtime < level.time ) ) + { + other = ev->GetEntity( 1 ); + location = ev->GetVector( 2 ); + SetVariable( "other", other ); + SetVariable( "location", location ); + if ( DoAction( "deathsound" ) ) + nextsoundtime = level.time + 2; + } + } + +void Actor::InvestigateBreakingSound + ( + Event *ev + ) + + { + Vector location; + Entity *other; + + if ( !currentEnemy && !deadflag && ( nextsoundtime < level.time ) ) + { + other = ev->GetEntity( 1 ); + location = ev->GetVector( 2 ); + SetVariable( "other", other ); + SetVariable( "location", location ); + if ( DoAction( "breakingsound" ) ) + nextsoundtime = level.time + 2; + } + } + +void Actor::InvestigateDoorSound + ( + Event *ev + ) + + { + Vector location; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) ) + { + location = ev->GetVector( 2 ); + SetVariable( "other", other ); + SetVariable( "location", location ); + if ( DoAction( "doorsound" ) ) + nextsoundtime = level.time + 2; + } + } + +void Actor::InvestigateMutantSound + ( + Event *ev + ) + + { + Vector location; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) ) + { + location = ev->GetVector( 2 ); + SetVariable( "other", other ); + SetVariable( "location", location ); + if ( DoAction( "mutantsound" ) ) + nextsoundtime = level.time + 2; + } + } + +void Actor::InvestigateVoiceSound + ( + Event *ev + ) + + { + Vector location; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) ) + { + location = ev->GetVector( 2 ); + SetVariable( "other", other ); + SetVariable( "location", location ); + if ( DoAction( "voicesound" ) ) + nextsoundtime = level.time + 2; + } + } + +void Actor::InvestigateMachineSound + ( + Event *ev + ) + + { + Vector location; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) ) + { + location = ev->GetVector( 2 ); + SetVariable( "other", other ); + SetVariable( "location", location ); + if ( DoAction( "machinesound" ) ) + nextsoundtime = level.time + 2; + } + } + +void Actor::InvestigateRadioSound + ( + Event *ev + ) + + { + Vector location; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( other && !currentEnemy && !deadflag && ( nextsoundtime < level.time ) && Hates( other ) ) + { + location = ev->GetVector( 2 ); + SetVariable( "other", other ); + SetVariable( "location", location ); + if ( DoAction( "radiosound" ) ) + nextsoundtime = level.time + 2; + } + } + +//*********************************************************************************************** +// +// Pain and death related functions +// +//*********************************************************************************************** + +void Actor::Pain + ( + Event *ev + ) + + { + float damage; + Entity *ent; + float oldhealth; + float newhealth; + +#ifdef DEBUG_PRINT + gi.dprintf( "Pain\n" ); +#endif + + damage = ev->GetFloat( 1 ); + ent = ev->GetEntity( 2 ); + + // if it's a Sentient and not liked, attack 'em. + if ( ent && ent->isSubclassOf( Sentient ) && !Likes( ent ) ) + { + MakeEnemy( ent ); + if ( ent != currentEnemy ) + { + currentEnemy = BestTarget(); + } + } + + if ( damage <= 0 ) + { + return; + } + + oldhealth = ( health + damage ) / max_health; + newhealth = health / max_health; + + SetVariable( "other", ev->GetEntity( 2 ) ); + + // If we pass more than one range, + if ( ( oldhealth > 0.75 ) && ( newhealth <= 0.75 ) ) + { + DoAction( "health_ok" ); + } + if ( ( oldhealth > 0.5 ) && ( newhealth <= 0.5 ) ) + { + DoAction( "health_med" ); + } + if ( ( oldhealth > 0.25 ) && ( newhealth <= 0.25 ) ) + { + DoAction( "health_low" ); + } + if ( ( oldhealth > 0.1 ) && ( newhealth <= 0.1 ) ) + { + DoAction( "health_danger" ); + } + + if ( damage <= pain_threshold ) + { + Chatter( "snd_pain_taunt", 5, true ); + return; + } + + if ( strncmp( animname.c_str(), "pain", 4 ) && strncmp( animname.c_str(), "crouch_pain", 11 ) ) + { + str aname; + int index; + + // + // determine pain animation + // + if ( !strncmp( animname.c_str(), "crouch", 6 ) ) + { + aname = "crouch_"; + } + aname += str("pain_") + str( ev->GetString( 3 ) ); + index = gi.Anim_Random( edict->s.modelindex, aname.c_str() ); + if ( ( index == -1 ) && !strncmp( animname.c_str(), "crouch", 6 ) ) + { + aname = "crouch_pain"; + index = gi.Anim_Random( edict->s.modelindex, aname.c_str() ); + } + + if ( index == -1 ) + { + aname = "pain"; + } + + SetVariable( "painanim", aname.c_str() ); + DoAction( "pain" ); + } + } + +void Actor::Dead + ( + Event *ev + ) + + { + Vector min, max; + Event *event; + + StopAnimating(); + if ( !groundentity && ( velocity != vec_zero ) ) + { + // wait until we hit the ground + PostEvent( ev, FRAMETIME ); + return; + } + + // + // drop anything that might be attached to us + // + if ( numchildren ) + { + Entity * child; + int i; + // + // detach all our children + // + for ( i = 0; i < MAX_MODEL_CHILDREN; i++ ) + { + if ( children[ i ] ) + { + child = ( Entity * )G_GetEntity( children[ i ] ); + child->ProcessEvent( EV_Detach ); + } + } + } + + deadflag = DEAD_DEAD; + maxs[0] *= 2.0f; + maxs[1] *= 2.0f; + maxs[2] *= 0.3f; + setSize(mins,maxs); + edict->svflags |= SVF_DEADMONSTER; + setMoveType( MOVETYPE_NONE ); + setOrigin( worldorigin ); + + if ( deathgib ) + { + // Put down a bloodsplat + Vector start, end, dir, norm, ang; + float scale; + BloodSplat *splat; + trace_t trace; + + dir = Vector( 0, 0, -1 ); + start = centroid; + end = start + 100 * dir; + + trace = G_Trace( start, vec_zero, vec_zero, end, this, MASK_SOLIDNONFENCE, "Actor::Dead" ); + + if ( !( HitSky( &trace ) || ( trace.ent->solid != SOLID_BSP ) || ( trace.ent->s.number != 0 ) ) ) + { + scale = G_Random( 1.2 ); + if ( scale < 0.5 ) + scale = 0.5; + norm = trace.plane.normal; + norm.x = -norm.x; + norm.y = -norm.y; + ang = norm.toAngles(); + ang.z = G_Random( 360 ); + end = trace.endpos + Vector( trace.plane.normal ) * 0.2; + splat = new BloodSplat( end, ang, scale ); + } + } + + // If this body has nothing interesting, then remove it + if ( !NumInventoryItems() ) + { + if ( nodeathfade ) + return; + + PostEvent( EV_FadeOut, 10 ); + return; + } + + // Spawn a touch field + min = worldorigin - "32 32 32"; + max = worldorigin + "32 32 32"; + trig = new TouchField; + trig->Setup( this, EV_Touch, min, max, TRIGGER_PLAYERS ); + + assert( trig->edict->solid == SOLID_TRIGGER ); + + if ( !nodeathfade ) + { + // Check in the future for uselessness + event = new Event ( EV_Sentient_UselessCheck ); + PostEvent ( event, 1.0f + G_Random() ); + + // If this guy has no InventoryItems, then fade him in 1 minute + if ( !HasInventoryOfType( "InventoryItem" ) ) + { + PostEvent( EV_Actor_Remove, 60.0f ); + } + } + } + +void Actor::Killed + ( + Event *ev + ) + + { + const char *name; + Entity *ent; + int num; + Entity *attacker; + Entity *inflictor; + Vector dir; + Event *event; + int i; + str dname; + int meansofdeath; + + CheckWater(); + StopAnimating(); + CancelPendingEvents(); + + // don't allow them to fly, think, or swim anymore + flags &= ~( FL_PRETHINK | FL_SWIM | FL_FLY ); + + deadflag = DEAD_DYING; + takedamage = DAMAGE_YES; + groundentity = NULL; + + attacker = ev->GetEntity( 1 ); + inflictor = ev->GetEntity( 3 ); + meansofdeath = ev->GetInteger( 5 ); + + // Double all the armor + DoubleArmor(); + + SetVariable( "other", ev->GetEntity( 1 ) ); + if ( !DoAction( "killed" ) && actorthread ) + { + actorthread->ProcessEvent( EV_ScriptThread_End ); + } + + + // Turn off dlight and shadow + edict->s.renderfx &= ~( RF_DLIGHT|RF_XFLIP ); + + // + // kill the killtargets + // + name = KillTarget(); + if ( name && strcmp( name, "" ) ) + { + num = 0; + do + { + num = G_FindTarget( num, name ); + if ( !num ) + { + break; + } + + ent = G_GetEntity( num ); + ent->PostEvent( EV_Remove, 0 ); + } + while ( 1 ); + } + + // + // fire targets + // + name = Target(); + if ( name && strcmp( name, "" ) ) + { + num = 0; + do + { + num = G_FindTarget( num, name ); + if ( !num ) + { + break; + } + + ent = G_GetEntity( num ); + + event = new Event( EV_Activate ); + event->AddEntity( attacker ); + ent->PostEvent( event, 0 ); + } + while ( 1 ); + } + // + // see if we have a kill_thread + // + if ( kill_thread.length() > 1 ) + { + ScriptThread * thread; + + // + // create the thread, but don't start it yet + // + thread = ExecuteThread( kill_thread, false ); + if ( thread ) + { + ProcessScript( thread, NULL ); + } + else + { + warning( "Killed", "could not process kill_thread" ); + } + } + + if (flags & FL_DIE_EXPLODE) + { + CreateExplosion( worldorigin, 150*edict->s.scale, edict->s.scale * 2, true, this, this, this ); + } + + if ( flags & FL_DIE_TESSELATE ) + { + float power; + setSolidType( SOLID_NOT ); + hideModel(); + + dir = worldorigin - attacker->worldorigin; + + if ( meansofdeath == MOD_ION ) + { + power = 1000; + } + else + { + power = ev->GetFloat( 2 ); + } + + TesselateModel + ( + this, + tess_min_size, + tess_max_size, + dir, + power, + 1.0f, + tess_thickness, + vec3_origin + ); + + DropInventoryItems(); + // Tesselated models require the model to be on the client for 1 more frame + PostEvent( EV_Remove, FRAMETIME ); + return; + } + + if ( DoGib( meansofdeath, inflictor ) ) + { + deathgib = true; + } + + if ( currentWeapon ) + { + DropWeapon( currentWeapon ); + } + + animOverride = false; + + // + // determine death animation + // + if ( !strncmp( animname.c_str(), "crouch", 6 ) ) + { + dname = "crouch_"; + } + + if ( deathgib ) + { + str location; + + location = ev->GetString( 4 ); + + // Check for location first otherwise randomize + if ( location == "torso_upper" ) + dname += str( "gibdeath_upper" ); + else if ( location == "torso_lower" ) + dname += str( "gibdeath_lower" ); + else if ( strstr( location.c_str(), "leg" ) ) + dname += str( "gibdeath_lower" ); + else if ( strstr( location.c_str(), "arm" ) ) + dname += str( "gibdeath_upper" ); + else if ( strstr( location.c_str(), "head" ) ) + dname += str( "gibdeath_upper" ); + else if ( G_Random() > 0.5 ) + dname += str( "gibdeath_upper" ); + else + dname += str( "gibdeath_lower" ); + } + else + { + dname += str( "death_" ) + str( ev->GetString( 4 ) ); + } + + i = gi.Anim_Random( edict->s.modelindex, dname.c_str() ); + + if ( ( i == -1 ) && !strncmp( animname.c_str(), "crouch", 6 ) ) + { + dname = "crouch_death"; + i = gi.Anim_Random( edict->s.modelindex, dname.c_str() ); + } + + if ( i == -1 ) + { + dname = "death"; + } + + if ( ( i != -1 ) && ( !strncmp( dname.c_str(), "gibdeath", 7 ) ) ) + { + Event *ev1; + + ev1 = new Event( EV_Gib ); + ev1->AddInteger( 1 ); + ProcessEvent( ev1 ); + } + if ( attacker ) + { + str location; + float damage; + + damage = ev->GetFloat( 2 ); + location = ev->GetString( 4 ); + + event = new Event( EV_GotKill ); + event->AddEntity( this ); + event->AddInteger( damage ); + event->AddEntity( inflictor ); + event->AddString( location ); + event->AddInteger( meansofdeath ); + event->AddInteger( deathgib ); + attacker->ProcessEvent( event ); + } + + SetAnim( dname.c_str(), EV_Actor_Dead ); + + // Call changeanim immediatly since we're no longer calling prethink + ChangeAnim(); + + // + // moved this here so guys would not be solid right away + // + edict->svflags |= SVF_DEADMONSTER; + edict->clipmask = MASK_DEADSOLID; + + if ( velocity.z < 10 ) + { + velocity.z += G_Random( 300 ); + } + + angles.x = 0; + angles.z = 0; + setAngles( angles ); + } + +void Actor::GibEvent + ( + Event *ev + ) + + { + qboolean hidemodel; + + hidemodel = !ev->GetInteger( 1 ); + + if ( sv_gibs->value && !parentmode->value ) + { + int numgibs; + float gibsize; + + if ( hidemodel ) + { + takedamage = DAMAGE_NO; + setSolidType( SOLID_NOT ); + hideModel(); + + // If there is a touchfield, remove it. + if ( trig ) + { + trig->PostEvent( EV_Remove, 0 ); + trig = NULL; + } + } + + gibsize = size.length() / 140; + numgibs = gibsize * 6; + if ( numgibs > 4 ) + numgibs = 4; + CreateGibs( this, health, gibsize, numgibs ); + } + + DropInventoryItems(); + + if ( hidemodel ) + PostEvent( EV_Remove, 0 ); + } + +void Actor::RemoveUselessBody + ( + Event *ev + ) + + { + if ( trig ) + { + trig->PostEvent( EV_Remove, 0 ); + trig = NULL; + } + + CancelEventsOfType( EV_Sentient_UselessCheck ); + PostEvent( EV_FadeOut, 5); + } + +void Actor::SetPainThresholdEvent + ( + Event *ev + ) + + { + pain_threshold = ( ev->GetFloat( 1 ) ) * skill->value * 0.66f; + } + +void Actor::SetKillThreadEvent + ( + Event *ev + ) + + { + kill_thread = ev->GetString( 1 ); + } + +void Actor::AttackRangeEvent + ( + Event *ev + ) + + { + attack_range = ev->GetFloat( 1 ); + } + +void Actor::AttackModeEvent + ( + + Event *ev + ) + + { + attackmode = ev->GetInteger( 1 ); + SetVariable( "attackmode", attackmode ); + } + +void Actor::ShotsPerAttackEvent + ( + Event *ev + ) + + { + float shots; + + shots = ev->GetFloat( 1 ); + // + // scale the shots based on skill + // + if ( skill->value < 1 ) + shots_per_attack = shots * 0.4f; + else if ( skill->value < 2 ) + shots_per_attack = shots * 0.7f; + else if ( skill->value < 3 ) + shots_per_attack = shots; + else + shots_per_attack = shots * skill->value * 0.4f; + } + +void Actor::ClearEnemyEvent + ( + Event *ev + ) + + { + ClearEnemies(); + } + +void Actor::NoDeathFadeEvent + ( + Event *ev + ) + + { + nodeathfade = true; + } + +void Actor::NoChatterEvent + ( + Event *ev + ) + + { + nochatter = true; + } + +void Actor::TurnSpeedEvent + ( + Event *ev + ) + + { + turnspeed = ev->GetFloat( 1 ); + } + + +//*********************************************************************************************** +// +// Movement functions +// +//*********************************************************************************************** + +void Actor::ForwardSpeedEvent + ( + Event *ev + ) + + { + forwardspeed = ev->GetFloat( 1 ); + } + +void Actor::SwimEvent + ( + Event *ev + ) + + { + // movement |= AI_CANSWIM; + flags &= ~FL_FLY; + flags |= FL_SWIM; + } + +void Actor::FlyEvent + ( + Event *ev + ) + + { + // movement |= AI_CANFLY; + flags &= ~FL_SWIM; + flags |= FL_FLY; + } + +void Actor::NotLandEvent + ( + Event *ev + ) + + { + //movement &= ~AI_CANWALK; + flags &= FL_SWIM | FL_FLY; + } + +inline qboolean Actor::CanMoveTo + ( + Vector pos + ) + + { + Vector min; + trace_t trace; + Vector start; + Vector end; + Vector s; + + s = Vector( 0, 0, STEPSIZE ); + start = worldorigin + s; + end = pos + s; + trace = G_Trace( start, mins, maxs, end, this, edict->clipmask, "Actor::CanMoveTo" ); + if ( trace.fraction == 1 ) + { + return true; + } + return false; + } + +inline void Actor::CheckWater + ( + void + ) + { + Vector sample[3]; + int cont; + + // + // get waterlevel and type + // + waterlevel = 0; + watertype = 0; + + sample[ 0 ] = worldorigin; + sample[ 2 ] = EyePosition(); + sample[ 1 ] = ( sample[ 0 ] + sample[ 2 ] ) * 0.5f; + + cont = gi.pointcontents( sample[ 0 ].vec3() ); + + if (cont & MASK_WATER) + { + watertype = cont; + waterlevel = 1; + cont = gi.pointcontents( sample[ 2 ].vec3() ); + if (cont & MASK_WATER) + { + waterlevel = 3; + } + else + { + cont = gi.pointcontents( sample[ 1 ].vec3() ); + if (cont & MASK_WATER) + { + waterlevel = 2; + } + } + } + } + +#define MAX_PITCH 75 +void Actor::Accelerate + ( + Vector steering + ) + + { + //### activate this to limit turnrate +#if 0 + if ( steering.y > turnspeed ) + steering.y = turnspeed; + else if ( steering.y < -turnspeed ) + steering.y = -turnspeed; +#endif + + angles.y += steering.y; + + if ( frame_delta.x > 4 ) + { + // make him lean into the turn a bit + angles.z = movespeed * ( 0.4f / 320.0f ) * steering.y; + + if ( ( flags & FL_FLY ) || ( ( flags & FL_SWIM ) && waterlevel > 0 ) ) + { + angles.z = bound( angles.z, -2, 2 ); + } + else + { + angles.z = bound( angles.z, -5, 5 ); + } + } + else + { + angles.z = 0; + } + + if ( ( flags & FL_FLY ) || ( ( flags & FL_SWIM ) && waterlevel > 0 ) ) + { + angles.x -= steering.x; + //angles.x = bound( angles.x, -MAX_PITCH, MAX_PITCH ); + } + + setAngles( angles ); + } + +void Actor::CalcMove + ( + void + ) + + { + if ( total_delta != vec_zero ) + { + // movement deltas have inverted Y axis + total_delta[ 1 ] = -total_delta[ 1 ]; + MatrixTransformVector( total_delta.vec3(), orientation, move.vec3() ); + total_delta = vec_zero; + } + else + { + move = vec_zero; + } + + // force movement if forwardspeed is set + if ( forwardspeed ) + { + if ( move == vec_zero ) + { + move = orientation[ 0 ]; + } + else + { + move.normalize(); + } + + animdir = move; + movedir = move; + movespeed = forwardspeed; + move *= movespeed * FRAMETIME; + totallen = forwardspeed; + movevelocity = movedir * movespeed; + } + } + +void Actor::setAngles + ( + Vector ang + ) + + { + Sentient::setAngles( ang ); + MatrixTransformVector( animdir.vec3(), orientation, movedir.vec3() ); + movevelocity = movedir * movespeed; + } + +stepmoveresult_t Actor::WaterMove + ( + void + ) + + { + Vector oldorg; + Vector neworg; + trace_t trace; + int oldwater; + + if ( ( totallen <= 0.01f ) || ( move == vec_zero ) ) + { + return STEPMOVE_OK; + } + + // try the move + oldorg = worldorigin; + neworg = worldorigin + move; + + trace = G_Trace( oldorg, mins, maxs, neworg, this, edict->clipmask, "Actor::WaterMove 1" ); + if ( trace.fraction == 0 ) + { + return STEPMOVE_STUCK; + } + + oldwater = waterlevel; + + setOrigin( trace.endpos ); + + CheckWater(); + + // swim monsters don't exit water voluntarily + if ( ( oldwater > 1 ) && ( waterlevel < 2 ) ) + { + waterlevel = oldwater; + setOrigin( oldorg ); + return STEPMOVE_STUCK; + } + + return STEPMOVE_OK; + } + +stepmoveresult_t Actor::AirMove + ( + void + ) + + { + Vector oldorg; + Vector neworg; + trace_t trace; + int oldwater; + + if ( ( totallen <= 0.01f ) || ( move == vec_zero ) ) + { + return STEPMOVE_OK; + } + + // try the move + oldorg = worldorigin; + neworg = worldorigin + move; + + trace = G_Trace( oldorg, mins, maxs, neworg, this, edict->clipmask, "Actor::AirMove 1" ); + if ( trace.fraction == 0 ) + { + return STEPMOVE_BLOCKED_BY_WATER; + } + + oldwater = waterlevel; + + setOrigin( trace.endpos ); + + CheckWater(); + + // fly monsters don't enter water voluntarily + if ( !oldwater && waterlevel ) + { + waterlevel = oldwater; + setOrigin( oldorg ); + return STEPMOVE_STUCK; + } + + return STEPMOVE_OK; + } + +stepmoveresult_t Actor::TryMove + ( + void + ) + + { + Vector oldorg; + Vector neworg; + Vector end; + Entity *ent; + trace_t trace; + Door *door; +#if 0 + vec3_t test; + int contents; +#endif + + if ( ( totallen <= 0.01f ) || ( move == vec_zero ) ) + { + return STEPMOVE_OK; + } + + // try the move + oldorg = worldorigin; + neworg = worldorigin + move; + + // push down from a step height above the wished position + neworg[ 2 ] += STEPSIZE; + end = neworg; + end[ 2 ] -= STEPSIZE * 4;//2; + + trace = G_Trace( neworg, mins, maxs, end, this, edict->clipmask, "Actor::TryMove 1" ); + if ( trace.allsolid ) + { + ent = trace.ent->entity; + if ( ent && ent->isSubclassOf( Door ) ) + { + if ( state == "opendoor" ) + { + return STEPMOVE_OK; + } + + door = ( Door * )ent; + if ( !door->locked && !door->isOpen() ) + { + SetVariable( "other", ent ); + SetVariable( "dir", end - worldorigin ); + ForceAction( "opendoor" ); + + return STEPMOVE_OK; + } + } + + return STEPMOVE_STUCK; + } + + if ( trace.startsolid ) + { + neworg[ 2 ] -= STEPSIZE; + trace = G_Trace( neworg, mins, maxs, end, this, edict->clipmask, "Actor::TryMove 2" ); + if ( trace.allsolid || trace.startsolid ) + { + ent = trace.ent->entity; + if ( ent && ent->isSubclassOf( Door ) ) + { + if ( state == "opendoor" ) + { + return STEPMOVE_OK; + } + + door = ( Door * )ent; + if ( !door->locked && !door->isOpen() ) + { + SetVariable( "other", ent ); + SetVariable( "dir", end - worldorigin ); + ForceAction( "opendoor" ); + + return STEPMOVE_OK; + } + } + + return STEPMOVE_STUCK; + } + } + +#if 0 + // don't go in to water + if ( waterlevel == 0 ) + { + test[ 0 ] = trace.endpos[ 0 ]; + test[ 1 ] = trace.endpos[ 1 ]; + test[ 2 ] = trace.endpos[ 2 ] + mins[ 2 ] + 1; + contents = gi.pointcontents( test ); + + if ( contents & MASK_WATER ) + { + return STEPMOVE_BLOCKED_BY_WATER; + } + } +#endif + + if ( trace.fraction == 1 ) + { + // don't let guys get stuck standing on other guys + // if monster had the ground pulled out, go ahead and fall + if ( ( flags & FL_PARTIALGROUND ) || ( groundentity && groundentity->entity && + ( groundentity->entity->isSubclassOf( Sentient ) ) ) ) + { + setOrigin( worldorigin + move ); + groundentity = NULL; + return STEPMOVE_OK; + } + + // walked off an edge + return STEPMOVE_BLOCKED_BY_FALL; + } + + // check point traces down for dangling corners + worldorigin = trace.endpos; + + if ( !M_CheckBottom( this ) ) + { + // don't let guys get stuck standing on other guys + if ( ( flags & FL_PARTIALGROUND ) || ( groundentity && groundentity->entity && + ( groundentity->entity->isSubclassOf( Sentient ) ) ) ) + { + // entity had floor mostly pulled out from underneath it + // and is trying to correct + setOrigin( worldorigin ); + + CheckWater(); + + return STEPMOVE_OK; + } + + setOrigin( oldorg ); + return STEPMOVE_BLOCKED_BY_FALL; + } + + if ( flags & FL_PARTIALGROUND ) + { + flags &= ~FL_PARTIALGROUND; + } + + groundentity = trace.ent; + groundentity_linkcount = trace.ent->linkcount; + groundplane = trace.plane; + groundsurface = trace.surface; + groundcontents = trace.contents; + + // the move is ok + setOrigin( worldorigin ); + + CheckWater(); + + return STEPMOVE_OK; + } + +void Actor::SetAim + ( + Event *ev + ) + { + aim = ev->GetFloat( 1 ); + } + +void Actor::SetMeleeRange + ( + Event *ev + ) + { + melee_range = ev->GetFloat( 1 ); + melee_range *= edict->s.scale; + if ( melee_range < 70 ) + melee_range = 70; + } + +void Actor::SetMeleeDamage + ( + Event *ev + ) + { + melee_damage = ev->GetFloat( 1 ); + melee_damage *= edict->s.scale; + } + +void Actor::MeleeEvent + ( + Event *ev + ) + { + trace_t trace; + Vector start; + Vector end; + float damage; + float extra_reach; + Vector org; + Vector ang; + Vector dir; + float kick; + + if ( !has_melee ) + { + warning( "MeleeEvent","Melee being called without animation" ); + return; + } + + damage = G_Random( melee_damage*0.5f )+ G_Random( melee_damage*0.5f ); + if ( ev->NumArgs() > 0 ) + extra_reach = ev->GetFloat( 1 ); + else + extra_reach = 0; + + if ( ev->NumArgs() > 1 ) + { + kick = ev->GetFloat( 2 ); + } + else + { + kick = damage * 3; + } + + // get the position of the attack + start = GunPosition(); + // get the attack_dir + ang = MyGunAngles( start, true ); + ang.AngleVectors( &dir, NULL, NULL ); + + end = start + dir * ( melee_range + extra_reach ); + + trace = G_FullTrace( start, vec_zero, vec_zero, end, 15, this, MASK_PROJECTILE, "Actor::Melee" ); + + dir = Vector(trace.endpos) - start; + dir.normalize(); + + org = Vector(trace.endpos) - dir; + + if ( (trace.fraction < 1.0f) ) + { + if ( trace.ent->entity && trace.ent->entity->takedamage ) + { + if ( trace.ent->entity->flags & FL_BLOOD ) + SpawnBlood( org, trace.plane.normal, damage ); + + RandomGlobalSound("impact_goryimpact"); + if ( trace.intersect.valid ) + { + // We hit a valid group so send in location based damage + trace.ent->entity->Damage( this, + this, + damage, + trace.endpos, + dir, + trace.plane.normal, + kick, + 0, + MOD_FISTS, + trace.intersect.parentgroup, + -1, + trace.intersect.damage_multiplier ); + } + else + { + // take the ground out so that the kick works + trace.ent->entity->groundentity = NULL; + + // We didn't hit any groups, so send in generic damage + trace.ent->entity->Damage( this, + this, + damage, + trace.endpos, + dir, + trace.plane.normal, + kick, + 0, + MOD_FISTS, + -1, + -1, + 1 ); + } + } + } + } + +void Actor::AttackFinishedEvent + ( + Event *ev + ) + { + if ( currentWeapon ) + { + currentWeapon->PostEvent( EV_Weapon_FinishAttack, 0 ); + } + } + +float Actor::JumpTo + ( + Vector targ, + float speed + ) + { + float traveltime; + float vertical_speed; + Vector target; + Vector dir; + Vector xydir; + + CheckWater(); + // + // if we got a jump, go into that mode + // + traveltime = 0; + if ( speed <= 0 ) + { + speed = movespeed * 1.5f; + if ( speed < (400 * gravity) ) + speed = (400 * gravity); + } + + target = targ; + dir = target - worldorigin; + xydir = dir; + xydir.z = 0; + setAngles( xydir.toAngles() ); + traveltime = xydir.length() / speed; + // + // we add 16 to allow for a little bit higher + // + if ( waterlevel > 2 ) + { + vertical_speed = ( ( dir.z + 16 ) / traveltime ) + ( 0.5f * gravity * 60 * traveltime ); + } + else + { + vertical_speed = ( ( dir.z + 16 ) / traveltime ) + ( 0.5f * gravity * sv_gravity->value * traveltime ); + } + xydir.normalize(); + + velocity = speed * xydir; + velocity.z = vertical_speed; + + return traveltime; + } + +float Actor::JumpTo + ( + PathNode *goal, + float speed + ) + { + if ( goal ) + return JumpTo( goal->worldorigin, speed ); + else + return 0; + } + +float Actor::JumpTo + ( + Entity *goal, + float speed + ) + { + if ( goal ) + return JumpTo( goal->worldorigin, speed ); + else + return 0; + } + +EXPORT_FROM_DLL void Actor::setSize + ( + Vector min, + Vector max + ) + { + min *= edict->s.scale; + max *= edict->s.scale; + Sentient::setSize( min, max ); + } + +EXPORT_FROM_DLL void Actor::SetHealth + ( + Event *ev + ) + + { + health = ev->GetFloat( 1 ) * edict->s.scale; + max_health = health; + } + +//*********************************************************************************************** +// +// Debug functions +// +//*********************************************************************************************** + +void Actor::ShowInfo + ( + void + ) + + { + Entity *ent; + int i; + int n; + StateInfo *ptr; + + gi.printf( "\nEntity # : %d\n", entnum ); + gi.printf( "Class ID : %s\n", getClassID() ); + gi.printf( "Classname : %s\n", getClassname() ); + gi.printf( "Targetname : %s\n", TargetName() ); + gi.printf( "Origin : ( %f, %f, %f )\n", worldorigin.x, worldorigin.y, worldorigin.z ); + gi.printf( "Bounds : Mins( %.2f, %.2f, %.2f ) Maxs( %.2f, %.2f, %.2f )\n", mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z ); + gi.printf( "State : %s\n", state.c_str() ); + + if ( behavior ) + { + gi.printf( "Behavior : %s\n", behavior->getClassname() ); + } + else + { + gi.printf( "Behavior : NULL -- was '%s'\n", currentBehavior.c_str() ); + } + + if ( actorthread ) + { + gi.printf( "Thread : %s(%d)\n", actorthread->Filename(), actorthread->CurrentLine() ); + } + else + { + gi.printf( "Thread : NULL\n" ); + } + gi.printf( "Actortype : %d\n", actortype ); + gi.printf( "Attackmode : %d\n", attackmode ); + + gi.printf( "Model : %s\n", model.c_str() ); + gi.printf( "Anim : %s\n", animname.c_str() ); + gi.printf( "Movespeed : %.2f\n", movespeed ); + gi.printf( "Health : %f\n", health ); + + gi.printf( "\nResponses:\n" ); + n = actionList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ptr = actionList.ObjectAt( i ); + gi.printf( "%s : ", ptr->action.c_str() ); + if ( ptr->ignore ) + { + gi.printf( "ignored - " ); + } + + gi.printf( "%s\n", ptr->response.c_str() ); + } + + n = enemyList.NumObjects(); + if ( n ) + { + gi.printf( "\nEnemies:\n" ); + + for( i = 1; i <= n; i++ ) + { + ent = enemyList.ObjectAt( i ); + if ( ent ) + { + if ( currentEnemy == ent ) + { + gi.printf( "*" ); + } + else + { + gi.printf( " " ); + } + gi.printf( "%d : '%s'", ent->entnum, ent->targetname.c_str() ); + gi.printf( "\n" ); + } + } + } + + gi.printf( "seenEnemy: %d\n", seenEnemy ); + gi.printf( "actortype: %d\n", actortype ); + gi.printf( "deadflag: %d\n", deadflag ); + + gi.printf( "\n" ); + if ( behavior ) + { + gi.printf( "Behavior Info:\n" ); + gi.printf( "Game time: %f\n", level.time ); + behavior->ShowInfo( *this ); + gi.printf( "\n" ); + } + } + +//*********************************************************************************************** +// +// General functions +// +//*********************************************************************************************** + +void Actor::Chatter + ( + const char *snd, + float chance, + float volume, + int channel + ) + + { + str realname; + + if ( nochatter || chattime > level.time ) + { + return; + } + + if ( G_Random( 10 ) > chance ) + { + chattime = level.time + 1 + G_Random( 2 ); + return; + } + + realname = GetRandomAlias( snd ); + if ( realname.length() > 1 ) + { + float delay; + + delay = gi.SoundLength( realname.c_str() ); + chattime = level.time + delay + 4 + G_Random( 5 ); + sound( realname, volume, channel ); + } + else + { + // set it into the future, so we don't check it again right away + chattime = level.time + 1; + } + } + +void Actor::ActivateEvent + ( + Event *ev + ) + + { + Entity *ent; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + ent = ev->GetEntity( 1 ); + SetVariable( "other", ent ); + + DoAction( "activate" ); + } + +void Actor::UseEvent + ( + Event *ev + ) + + { + Entity *ent; + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + ent = ev->GetEntity( 1 ); + SetVariable( "other", ent ); + + DoAction( "use" ); + } + +void Actor::Prethink + ( + void + ) + + { + range_t range; + Event *event; + + assert( actorthread ); + if ( !actorthread ) + { + // not having a script is bad + gi.dprintf( "Null actorthread. Killing actor '%s'(%d).\n", targetname.c_str(), entnum ); + + // just kill him + event = new Event( EV_Killed ); + event->AddEntity( this ); + event->AddInteger( 0 ); + event->AddEntity( this ); + event->AddString( "all" ); + ProcessEvent( event ); + + return; + } + + if ( hidden() ) + { + // Don't think while hidden, you're just a cinematic. + return; + } + + if ( actortype == IS_INANIMATE ) + { + if ( behavior && !behavior->Evaluate( *this ) ) + { + // stop thinking + flags &= ~FL_PRETHINK; + EndBehavior(); + } + return; + } + + if ( currentEnemy ) + { + if ( currentEnemy->deadflag ) + { + DoAction( "enemydead" ); + currentEnemy = NULL; + seenEnemy = false; + } + else + { + range = Range( currentEnemy ); + if ( ( lastEnemy != currentEnemy ) || ( range != enemyRange ) ) + { + lastEnemy = currentEnemy; + enemyRange = range; + + SetVariable( "other", currentEnemy ); + switch( range ) + { + case RANGE_MELEE : + DoAction( "range_melee" ); + break; + + case RANGE_NEAR : + DoAction( "range_near" ); + break; + + case RANGE_MID : + DoAction( "range_mid" ); + break; + + case RANGE_FAR : + DoAction( "range_far" ); + break; + } + } + } + } + else + { + lastEnemy = NULL; + enemyRange = RANGE_FAR; + } + + eyeposition[ 2 ] = maxs[ 2 ] + eyeoffset[ 2 ]; + angles.z = 0; + +#ifdef DEBUG_PRINT + gi.dprintf( "stack %d : %s : %s\n", numonstack, behavior ? behavior->getClassname() : "", animname.c_str() ); +#endif + if ( behavior && !behavior->Evaluate( *this ) ) + { + EndBehavior(); + } + + if ( newanimnum != -1 ) + { + ChangeAnim(); + } + + CalcMove(); + lastmove = STEPMOVE_STUCK; + + if ( flags & FL_SWIM ) + { + lastmove = WaterMove(); + } + else if ( flags & FL_FLY ) + { + lastmove = AirMove(); + } + else + { + lastmove = TryMove(); + } + // + // see if we should damage the actor because of waterlevel + // + if ( waterlevel == 3 && !( flags & FL_SWIM ) ) + { + // if out of air, start drowning + if ( air_finished < level.time ) + { + // we may have been in a water brush when we spawned, so check our water level again to be sure + CheckWater(); + if ( waterlevel < 3 ) + { + // we're ok, so reset our air + air_finished = level.time + 5; + } + else if ( next_drown_time < level.time && health > 0 ) + { + // drown! + next_drown_time = level.time + 1; + + RandomGlobalSound( "snd_uwchoke", 1, CHAN_VOICE, ATTN_NORM ); + ProcessEvent( EV_PainSound ); + + Damage( world, world, 15, worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_DROWN, -1, -1, 1.0f ); + } + } + } + else + { + air_finished = level.time + 5; + } + + G_TouchTriggers( this ); + if ( groundentity && ( groundentity->entity != world ) && !M_CheckBottom( this ) ) + { + // G_FixCheckBottom( this ); + flags |= FL_PARTIALGROUND; + } + } + +/*****************************************************************************/ +/*SINED info_monster_spawnspot (1 0 0) (-16 -16 0) (16 16 64) + +Potential spawn location for a monster. + +"spawngroup" - the name of the group of spawn spots that this one belongs to. +Monsters use spawngroup to choose which spawn spots to spawn at. + +"angle" - the orientation for the monster to spawn in. +"angles" - the full ( x, y, z ) orientation for the monster to spawn in. +"anim" - the animation that the monster should use when spawning here. + +/*****************************************************************************/ + +CLASS_DECLARATION( PathNode, MonsterStart, "info_monster_spawnspot" ); + +ResponseDef MonsterStart::Responses[] = + { + { NULL, NULL } + }; + +MonsterStart::MonsterStart() + { + spawngroup = G_GetSpawnArg( "spawngroup", "" ); + + leader = NULL; + leader = GetGroupLeader( spawngroup ); + if ( !leader ) + { + leader = this; + groupchain = NULL; + } + else + { + groupchain = leader->groupchain; + leader->groupchain = this; + } + } + +MonsterStart *MonsterStart::GetGroupLeader + ( + str& groupname + ) + + { + Entity *ent; + MonsterStart *m; + + for( ent = world; ent; ent = G_NextEntity( ent ) ) + { + if ( ent->isSubclassOf( MonsterStart ) ) + { + m = ( MonsterStart * )ent; + if ( ( m->spawngroup == groupname ) && m->leader ) + { + return m->leader; + } + } + } + + return NULL; + } + +MonsterStart *MonsterStart::GetRandomSpot + ( + str& groupname + ) + + { + MonsterStart *leader; + MonsterStart *m; + int num; + int i; + int j; + + leader = GetGroupLeader( groupname ); + if ( !leader ) + { + return NULL; + } + + num = 0; + for( m = leader; m != NULL; m = m->groupchain ) + { + num++; + } + + j = ( int )G_Random( num ); + + for( m = leader, i = 0; m != NULL; m = m->groupchain, i++ ) + { + if ( i == j ) + { + break; + } + } + + return m; + } diff --git a/actor.h b/actor.h new file mode 100644 index 0000000..0d9bd2b --- /dev/null +++ b/actor.h @@ -0,0 +1,1333 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/actor.h $ +// $Revision:: 89 $ +// $Author:: Markd $ +// $Date:: 11/09/98 6:23p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/actor.h $ +// +// 89 11/09/98 6:23p Markd +// Added turnspeed event +// +// 88 11/08/98 7:07p Markd +// added nochatter command +// +// 87 11/08/98 6:57p Jimdose +// reordered some variables in archive and unarchive so that they match the +// order they're defined in the class +// +// 86 11/07/98 3:34p Markd +// Added nodeathfade command +// +// 85 11/06/98 8:25p Jimdose +// made visiondistance adjustable from script +// +// 84 10/27/98 9:41p Jimdose +// made gun orienting functions virtual +// +// 83 10/27/98 7:53p Jimdose +// added IfEnemyWithinEvent +// +// 82 10/27/98 1:50a Markd +// Made Chatter a virtual void function +// +// 81 10/26/98 2:19p Markd +// Added last_jump_time +// +// 80 10/25/98 11:52p Jimdose +// added EXPORT_TEMPLATE +// +// 79 10/25/98 4:43a Markd +// Added animal events +// +// 78 10/24/98 3:10p Markd +// Put in eyeoffset command +// +// 77 10/22/98 9:29p Aldie +// Added support for deathgib animations +// +// 76 10/20/98 2:37a Markd +// Added additional JumpTo methods +// +// 75 10/20/98 12:44a Markd +// Added setSize and setHealth +// +// 74 10/17/98 11:02p Markd +// Added ifcanshoot +// +// 73 10/17/98 4:59p Markd +// Added Attackmode event +// +// 72 10/16/98 7:19p Markd +// Added a bunch of new events +// +// 71 10/16/98 1:41a Jimdose +// Added goto command for actors. This helps the all too common mistake of +// typing "local.self goto label" +// +// 70 10/14/98 5:22p Markd +// Added jumpto and jumpto function +// +// 69 10/14/98 2:17a Markd +// Added in ifcanmeleeattack event +// +// 68 10/14/98 1:27a Markd +// Added HasWeapon method +// +// 67 10/13/98 9:11p Markd +// Externed Actor_AttackEntity Actor_AttackPlayer +// +// 66 10/11/98 5:01p Markd +// Added variables to keep track of drowning and lack of oxygen +// +// 65 10/11/98 12:32a Markd +// Added AirMove +// +// 64 10/10/98 7:59p Markd +// Added painthreshold and snd_pain_taunt +// +// 63 10/10/98 6:08p Markd +// Added FleeAndRemove, FindFlee, fixed max_ainode bug +// +// 62 10/10/98 1:25a Jimdose +// Did some bulletproofing against possible NULL actorthread +// Prethink kills actors with NULL threads +// added SetVariable for setting thread variables safely +// stateStack is now properly archived without destroying its contents +// +// 61 10/06/98 5:26p Markd +// Made MakeEnemy and DoAction take the force parameter +// +// 60 10/06/98 12:14p Markd +// Added ForceAction +// +// 59 10/05/98 10:33p Markd +// Added nostrafe and canstrafe +// +// 58 10/05/98 2:25a Jimdose +// Made archive functions properly handle NULL behaviors +// +// 57 10/04/98 10:26p Markd +// Added AttackFinishedEvent +// +// 56 10/03/98 7:27p Markd +// working on swimming character and redid some weapon aiming stuff +// +// 55 10/01/98 8:01p Markd +// Added melee variables +// +// 54 9/30/98 1:18p Markd +// Added actor melee variables +// +// 53 9/29/98 5:58p Markd +// put in archive and unarchive +// +// 52 9/24/98 7:10p Jimdose +// Added MyGunAngles +// +// 51 9/22/98 5:11p Jimdose +// made the anim variables public +// +// 50 9/22/98 5:31a Jimdose +// Added attackmode +// +// 49 9/22/98 1:58a Jimdose +// Added lastmove and forwardspeed +// Regrouped functions by functionality +// +// 48 9/19/98 6:16p Jimdose +// Added AttackPlayer +// +// 47 9/18/98 10:55p Jimdose +// Added inanimate actor type +// started work on swimming monsters +// added showinfo +// +// 46 9/14/98 5:26p Jimdose +// ProcessScript now allows events to be passed in +// +// 45 8/26/98 11:13p Jimdose +// Began strafe support +// +// 44 8/24/98 6:56p Jimdose +// Added crouching pain and death animations +// Added path +// Moved hueristics for path finding to actor +// +// 43 8/19/98 3:59p Jimdose +// Added IfNearEvent and CopyStateEvent +// +// 42 8/19/98 2:30p Jimdose +// Can no longer alias "alert" to "idle" so I've added a hasalert variable +// +// 41 8/14/98 11:34p Jimdose +// added seenenemy +// +// 40 8/14/98 6:24p Jimdose +// Got rid of decelleration for steering +// +// 39 8/07/98 7:57p Markd +// Added ForwardToBehavior event +// +// 38 8/07/98 6:02p Jimdose +// Added NotifyBehavior +// +// 37 8/06/98 6:59p Jimdose +// Added IfEnemyVisible +// +// 36 8/05/98 7:19p Jimdose +// Added definestate, ignoresounds, respondtosounds, respondtoall +// Added StateInfo +// +// 35 7/29/98 6:32p Jimdose +// Added MonsterStart +// +// 34 7/26/98 11:43a Jimdose +// Added EnemyCanSeeMeFrom +// +// 33 7/26/98 3:49a Jimdose +// Modified aim based on skill +// +// 32 7/25/98 2:12a Jimdose +// Made trig a TouchFieldPtr +// +// 31 7/24/98 3:10p Jimdose +// Removed ignore enemies stuff +// +// 30 7/22/98 7:03p Aldie +// Remove useless bodies +// +// 29 7/14/98 11:49p Jimdose +// Made PopState return whether a new state was popped off the stack +// +// 28 7/08/98 12:58p Jimdose +// added min and max for crouch and stand sizes +// +// 27 7/08/98 12:56p Jimdose +// added crouchsize and standsize +// +// 26 7/07/98 11:37p Jimdose +// Replaced priority-based system with full script-based state system +// +// 25 7/06/98 1:06p Jimdose +// working on ai +// +// 24 7/06/98 1:06p Jimdose +// working on ai +// +// 23 6/30/98 6:49p Aldie +// Added Gib event +// +// 22 6/30/98 6:00p Jimdose +// Added more states and state detection code. +// Added range detection +// added health level detection +// +// 21 6/25/98 8:11p Jimdose +// Added global actor scripts +// +// 20 6/19/98 9:29p Jimdose +// Added GetGunOrientation +// +// 19 6/19/98 5:53p Jimdose +// Added InFOV +// +// 18 6/18/98 8:45p Jimdose +// Removed string based SetBehavior and PushState functions +// +// 17 6/17/98 1:16a Jimdose +// Moved setOwner to Item. +// Added EV_Item_Pickup +// +// 16 6/13/98 8:22p Jimdose +// Added some enemy visibility functions for FindCover +// +// 15 6/11/98 12:44a Jimdose +// behaviors now get info from the script at startup +// +// 14 6/10/98 10:25p Jimdose +// Added priority based state system +// +// 13 6/09/98 4:24p Jimdose +// worked on ai +// +// 12 6/04/98 10:48p Jimdose +// Fixed a bunch of things that got broken just in time for E3. Paths and +// scripting actually work now. +// +// 11 6/03/98 5:44p Jimdose +// Fixed spelling of behavior. :) +// +// 10 5/27/98 5:11a Jimdose +// working on ai +// +// 9 5/25/98 6:47p Jimdose +// Made animateframe, prethink and posthink into functions built into the base +// entity class +// +// 8 5/25/98 5:31p Jimdose +// Pathnodes are no longer a subclass of Entity. This was done to save on +// edicts +// +// 7 5/25/98 1:06a Jimdose +// Added chatter +// +// 6 5/24/98 1:02a Jimdose +// Added monster hearing +// +// 5 5/23/98 6:29p Jimdose +// changed waitfordoor to a float +// +// 4 5/22/98 9:39p Jimdose +// Worked on ai +// +// 3 5/20/98 6:38p Jimdose +// working on ai +// +// 2 5/18/98 8:15p Jimdose +// Created file +// +// DESCRIPTION: +// Base class for character AI. +// + +#ifndef __ACTOR_H__ +#define __ACTOR_H__ + +#include "g_local.h" +#include "weapon.h" +#include "sentient.h" +#include "container.h" +#include "stack.h" +#include "navigate.h" +#include "behavior.h" +#include "scriptmaster.h" +#include "prioritystack.h" + +extern Event EV_Actor_Start; +extern Event EV_Actor_Dead; +extern Event EV_Actor_PopAnim; + +extern Event EV_Actor_LookAt; +extern Event EV_Actor_TurnTo; +extern Event EV_Actor_FinishedBehavior; +extern Event EV_Actor_NotifyBehavior; +extern Event EV_Actor_FinishedMove; +extern Event EV_Actor_FinishedAnim; +extern Event EV_Actor_WalkTo; +extern Event EV_Actor_RunTo; +extern Event EV_Actor_Anim; +extern Event EV_Actor_Attack; +extern Event EV_Actor_InPain; +extern Event EV_Actor_Gib; +extern Event EV_Actor_ForwardToBehavior; +extern Event EV_Actor_Aim; +extern Event EV_Actor_MeleeRange; +extern Event EV_Actor_MeleeDamage; +extern Event EV_Actor_AttackFinished; +extern Event EV_Actor_Attack; +extern Event EV_Actor_AttackPlayer; + +typedef enum + { + RANGE_MELEE, + RANGE_NEAR, + RANGE_MID, + RANGE_FAR + } range_t; + +typedef enum + { + ON_SIGHT, + ON_ATTACK + }; + +typedef enum + { + IGNORE, + ATTACK, + FLEE, + } reaction_t; + +typedef enum + { + LIKES, // Will not fire at, even if attacked + TOLERATES, // Will attack when attacked + HATES, // Will attack on sight + WARY, // Will flee when attacked + FEARS, // Will flee on sight + NUM_DISPOSITIONS + } disposition_t; + +typedef enum + { + IS_INANIMATE, + IS_MONSTER, + IS_ENEMY, + IS_CIVILIAN, + IS_FRIEND, + IS_ANIMAL, + NUM_ACTORTYPES + } actortype_t; + +#define AI_CANWALK 0x00000001 +#define AI_CANSWIM 0x00000002 +#define AI_CANFLY 0x00000004 + +#define WEAK_WEIGHT 0.5 +#define WEAK_HEALTH 20 +#define STRONGER_WEIGHT 0.9 +#define VISIBLE_WEIGHT 0.5 +#define NEWENEMY_WEIGHT 0.75 // if he's a new enemy + +//#define DAMAGE_WEIGHT 0.5 // If he's done a lot of damage to you +//#define WEAPON_WEIGHT 1.5 // How much the weapon influences you + +class EXPORT_FROM_DLL StateInfo : public Class + { + public: + CLASS_PROTOTYPE( StateInfo ); + + StateInfo(); + + str action; + str response; + qboolean ignore; + + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void StateInfo::Archive + ( + Archiver &arc + ) + { + Class::Archive( arc ); + + arc.WriteString( action ); + arc.WriteString( response ); + arc.WriteBoolean( ignore ); + } + +inline EXPORT_FROM_DLL void StateInfo::Unarchive + ( + Archiver &arc + ) + { + Class::Unarchive( arc ); + + arc.ReadString( &action ); + arc.ReadString( &response ); + arc.ReadBoolean( &ignore ); + } + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL ActorState : public Class + { + public: + CLASS_PROTOTYPE( ActorState ); + + str name; + str anim; + Event *animDoneEvent; + Behavior *behavior; + PathPtr path; + int thread; + ThreadMarker marker; + Container actionList; + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void ActorState::Archive + ( + Archiver &arc + ) + { + int i, num; + + Class::Archive( arc ); + + arc.WriteString( name ); + arc.WriteString( anim ); + arc.WriteEvent( *animDoneEvent ); + //FIXME + arc.WriteBoolean( behavior != NULL ); + if ( behavior ) + { + arc.WriteObject( behavior ); + } + arc.WriteSafePointer( path ); + arc.WriteInteger( thread ); + arc.WriteObject( &marker ); + num = actionList.NumObjects(); + arc.WriteInteger( num ); + for ( i = 1; i <= num; i++ ) + { + arc.WriteObject( actionList.ObjectAt( i ) ); + } + } + +inline EXPORT_FROM_DLL void ActorState::Unarchive + ( + Archiver &arc + ) + { + Event * ev; + int i, num; + + Class::Unarchive( arc ); + + arc.ReadString( &name ); + arc.ReadString( &anim ); + ev = new Event; + arc.ReadEvent( ev ); + animDoneEvent = ev; + + //FIXME + behavior = NULL; + if ( arc.ReadBoolean() ) + { + behavior = ( Behavior * )arc.ReadObject(); + } + + arc.ReadSafePointer( &path ); + arc.ReadInteger( &thread ); + arc.ReadObject( &marker ); + actionList.FreeObjectList(); + arc.ReadInteger( &num ); + for ( i = 1; i <= num; i++ ) + { + StateInfo * info; + + info = new StateInfo; + arc.ReadObject( info ); + actionList.AddObject( info ); + } + } + +// +// Exported templated classes must be explicitly instantiated +// +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +template class EXPORT_FROM_DLL Stack; +#endif + +class EXPORT_FROM_DLL Actor : public Sentient + { + public: + str newanim; + Event *newanimevent; + int newanimnum; + + str spawngroup; + + int movement; + stepmoveresult_t lastmove; + float forwardspeed; + + actortype_t actortype; + int attackmode; + + Vector standsize_min; + Vector standsize_max; + Vector crouchsize_min; + Vector crouchsize_max; + + str state; + str animname; + Container actionList; + int numonstack; + Stack stateStack; + + BehaviorPtr behavior; + str currentBehavior; + + PathPtr path; + + Container targetList; + Container nearbyList; + Container enemyList; + EntityPtr currentEnemy; + qboolean seenEnemy; + range_t enemyRange; + EntityPtr lastEnemy; + + float fov; + float fovdot; + float vision_distance; + + Vector startpos; + + Vector move; + Vector movedir; + float movespeed; + Vector movevelocity; + float totallen; + float turnspeed; + Vector animdir; + + float chattime; + float nextsoundtime; + + qboolean hasalert; + + PathNodePtr movegoal; + PathNodePtr soundnode; + ThreadPtr thread; + + ThreadPtr actorthread; + str actorscript; + str actorstart; + TouchFieldPtr trig; + + qboolean has_melee; + float melee_damage; + float melee_range; + float aim; + float pain_threshold; + + qboolean checkStrafe; + + float next_drown_time; + float air_finished; + float attack_range; + float shots_per_attack; + qboolean deathgib; + + str kill_thread; + Vector eyeoffset; + float last_jump_time; + qboolean nodeathfade; + qboolean nochatter; + + CLASS_PROTOTYPE( Actor ); + + // Initialization functions + Actor(); + ~Actor(); + void Start( Event *ev ); + + // Vision functions + range_t Range( Entity *ent ); + qboolean InFOV( Vector pos ); + qboolean InFOV( Entity *ent ); + qboolean CanSeeFOV( Entity *ent ); + qboolean CanSeeFrom( Vector pos, Entity *ent ); + qboolean CanSee( Entity *ent ); + int EnemyCanSeeMeFrom( Vector pos ); + qboolean CanSeeEnemyFrom( Vector pos ); + + // Weapon functions + qboolean WeaponReady( void ); + void Attack( Event *ev ); + virtual Vector GunPosition( void ); + virtual Vector MyGunAngles( Vector muzzlepos, qboolean firing ); + virtual void GetGunOrientation( Vector muzzlepos, Vector *forward, Vector *right, Vector *up ); + virtual qboolean CanShootFrom( Vector pos, Entity *ent, qboolean usecurrentangles ); + virtual qboolean CanShoot( Entity *ent, qboolean usecurrentangles ); + + // Actor type script commands + void FriendEvent( Event *ev ); + void CivilianEvent( Event *ev ); + void EnemyEvent( Event *ev ); + void InanimateEvent( Event *ev ); + void MonsterEvent( Event *ev ); + void AnimalEvent( Event *ev ); + + // Enemy management + qboolean HasEnemies( void ); + qboolean IsEnemy( Entity *ent ); + void MakeEnemy( Entity *ent, qboolean force = false ); + qboolean Likes( Entity *ent ); + qboolean Hates( Entity *ent ); + + // Targeting functions + qboolean GetVisibleTargets( void ); + void TargetEnemies( Event *ev ); + Entity *BestTarget( void ); + Sentient *NearFriend( void ); + qboolean CloseToEnemy( Vector pos, float howclose ); + float AttackRange( void ); + float MinimumAttackRange( void ); + + // State control functions + void EnableState( str action ); + void DisableState( str action ); + StateInfo *SetResponse( str action, str response, qboolean ignore = false ); + const char *GetResponse( str action, qboolean force = false ); + StateInfo *GetState( str action ); + + // State stack management + void ClearStateStack( void ); + qboolean PopState( void ); + void PushState( const char *newstate, ScriptThread *newthread = NULL, ThreadMarker *marker = NULL ); + + // State control script commands + void DefineStateEvent( Event *ev ); + void CopyStateEvent( Event *ev ); + void IgnoreAllEvent( Event *ev ); + void IgnoreEvent( Event *ev ); + void RespondToAllEvent( Event *ev ); + void RespondToEvent( Event *ev ); + void ClearStateEvent( Event *ev ); + void StateDoneEvent( Event *ev ); + void SetStateEvent( Event *ev ); + + // Thread management + void SetupThread( void ); + qboolean DoAction( str name, qboolean force = false ); + qboolean ForceAction( str name ); + void ProcessScript( ScriptThread *thread, Event *ev = NULL ); + void StartMove( Event *ev ); + ScriptVariable *SetVariable( const char *name, float value ); + ScriptVariable *SetVariable( const char *name, int value ); + ScriptVariable *SetVariable( const char *name, const char *text ); + ScriptVariable *SetVariable( const char *name, str &text ); + ScriptVariable *SetVariable( const char *name, Entity *ent ); + ScriptVariable *SetVariable( const char *name, Vector &vec ); + + // Thread based script commands + void SetScript( Event *ev ); + void SetThread( Event *ev ); + + // Behavior management + void EndBehavior( void ); + void SetBehaviorEvent( Event *ev ); + void SetBehavior( Behavior *newbehavior, Event *argevent = NULL, ScriptThread *thread = NULL ); + void FinishedBehavior( Event *ev ); + void NotifyBehavior( Event *ev ); + void ForwardBehaviorEvent( Event *ev ); + + // Path and node management + void SetPath( Path *newpath ); + void ReserveNodeEvent( Event *ev ); + void ReleaseNodeEvent( Event *ev ); + + // Animation control functions + void ChangeAnim( void ); + qboolean SetAnim( str anim, Event *ev = NULL ); + qboolean SetAnim( str anim, Event &ev ); + void Anim( Event *ev ); + + // Script commands + void CrouchSize( Event *ev ); + void SetFov( Event *ev ); + void SetVisionDistance( Event *ev ); + void LookAt( Event *ev ); + void TurnToEvent( Event *ev ); + void IdleEvent( Event *ev ); + void WalkTo( Event *ev ); + void RunTo( Event *ev ); + void AttackEntity( Event *ev ); + void AttackPlayer( Event *ev ); + void JumpToEvent( Event *ev ); + void GotoEvent( Event *ev ); + + // Script conditionals + void IfEnemyVisibleEvent( Event *ev ); + void IfNearEvent( Event *ev ); + void IfCanHideAtEvent( Event *ev ); + void IfCanStrafeAttackEvent( Event *ev ); + void IfCanMeleeAttackEvent( Event *ev ); + void IfCanShootEvent( Event *ev ); + void IfEnemyWithinEvent( Event *ev ); + + // Sound reaction functions + void IgnoreSoundsEvent( Event *ev ); + void RespondToSoundsEvent( Event *ev ); + void InvestigateWeaponSound( Event *ev ); + void InvestigateMovementSound( Event *ev ); + void InvestigatePainSound( Event *ev ); + void InvestigateDeathSound( Event *ev ); + void InvestigateBreakingSound( Event *ev ); + void InvestigateDoorSound( Event *ev ); + void InvestigateMutantSound( Event *ev ); + void InvestigateVoiceSound( Event *ev ); + void InvestigateMachineSound( Event *ev ); + void InvestigateRadioSound( Event *ev ); + + // Pain and death related functions + void Pain( Event *ev ); + void Dead( Event *ev ); + void Killed( Event *ev ); + void GibEvent( Event *ev ); + void RemoveUselessBody( Event *ev ); + + // Movement functions + void ForwardSpeedEvent( Event *ev ); + void SwimEvent( Event *ev ); + void FlyEvent( Event *ev ); + void NotLandEvent( Event *ev ); + qboolean CanMoveTo( Vector pos ); + void Accelerate( Vector steering ); + void CalcMove( void ); + void setAngles( Vector ang ); + stepmoveresult_t WaterMove( void ); + stepmoveresult_t AirMove( void ); + stepmoveresult_t TryMove( void ); + void CheckWater( void ); + virtual void setSize( Vector min, Vector max ); + + // Debug functions + void ShowInfo( void ); + + // General functions + virtual void Chatter( const char *sound, float chance = 10, float volume = 1.0f, int channel = CHAN_VOICE ); + void ActivateEvent( Event *ev ); + void UseEvent( Event *ev ); + void Prethink( void ); + + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + + void SetAim( Event *ev ); + void SetMeleeRange( Event *ev ); + void SetMeleeDamage( Event *ev ); + void MeleeEvent( Event *ev ); + void AttackFinishedEvent( Event *ev ); + + void CanStrafeEvent( Event *ev ); + void NoStrafeEvent( Event *ev ); + void SetPainThresholdEvent( Event *ev ); + void SetKillThreadEvent( Event *ev ); + void AttackRangeEvent( Event *ev ); + void AttackModeEvent( Event *ev ); + void ShotsPerAttackEvent( Event *ev ); + void ClearEnemyEvent( Event *ev ); + void SetHealth( Event *ev ); + void ClearEnemies( void ); + void EyeOffset( Event *ev ); + void NoDeathFadeEvent( Event *ev ); + void NoChatterEvent( Event *ev ); + void TurnSpeedEvent( Event *ev ); + + qboolean HasWeapon( void ); + float JumpTo( PathNode * goal, float speed ); + float JumpTo( Entity * goal, float speed ); + float JumpTo( Vector targ, float speed ); + }; + +inline EXPORT_FROM_DLL void Actor::Archive + ( + Archiver &arc + ) + { + int i, num; + Stack tempStack; + ActorState *s; + + Sentient::Archive( arc ); + + arc.WriteString( newanim ); + arc.WriteEvent( *newanimevent ); + arc.WriteInteger( newanimnum ); + + arc.WriteString( spawngroup ); + + arc.WriteInteger( movement ); + // cast as stepmoveresult_t on read + arc.WriteInteger( ( int )lastmove ); + arc.WriteFloat( forwardspeed ); + + // cast as actortype_t on read + arc.WriteInteger( ( int )actortype ); + arc.WriteInteger( attackmode ); + + arc.WriteVector( standsize_min ); + arc.WriteVector( standsize_max ); + arc.WriteVector( crouchsize_min ); + arc.WriteVector( crouchsize_max ); + + arc.WriteString( state ); + arc.WriteString( animname ); + + num = actionList.NumObjects(); + arc.WriteInteger( num ); + for ( i = 1; i <= num; i++ ) + { + arc.WriteObject( actionList.ObjectAt( i ) ); + } + + arc.WriteInteger( numonstack ); + + // We have to push all the states onto another stack in order to + // save them out in the proper order and to restore them after + // saving the game. + for( i = 1; i <= numonstack; i++ ) + { + tempStack.Push( stateStack.Pop() ); + } + + // put them back on the stateStack as we write them out. + for( i = 1; i <= numonstack; i++ ) + { + s = tempStack.Pop(); + stateStack.Push( s ); + arc.WriteObject( s ); + } + + // FIXME + arc.WriteBoolean( behavior != NULL ); + if ( behavior ) + { + arc.WriteObject( behavior ); + } + + arc.WriteString( currentBehavior ); + + arc.WriteSafePointer( path ); + + num = targetList.NumObjects(); + arc.WriteInteger( num ); + for ( i = 1; i <= num; i++ ) + { + arc.WriteSafePointer( targetList.ObjectAt( i ) ); + } + + num = nearbyList.NumObjects(); + arc.WriteInteger( num ); + for ( i = 1; i <= num; i++ ) + { + arc.WriteSafePointer( nearbyList.ObjectAt( i ) ); + } + + num = enemyList.NumObjects(); + arc.WriteInteger( num ); + for ( i = 1; i <= num; i++ ) + { + arc.WriteSafePointer( enemyList.ObjectAt( i ) ); + } + + arc.WriteSafePointer( currentEnemy ); + arc.WriteBoolean( seenEnemy ); + // cast as range_t on read + arc.WriteInteger( ( int )enemyRange ); + arc.WriteSafePointer( lastEnemy ); + + arc.WriteFloat( fov ); + arc.WriteFloat( fovdot ); + arc.WriteFloat( vision_distance ); + + arc.WriteVector( startpos ); + + arc.WriteVector( move ); + arc.WriteVector( movedir ); + arc.WriteFloat( movespeed ); + arc.WriteVector( movevelocity ); + arc.WriteFloat( totallen ); + arc.WriteFloat( turnspeed ); + arc.WriteVector( animdir ); + + arc.WriteFloat( chattime ); + arc.WriteFloat( nextsoundtime ); + + arc.WriteBoolean( hasalert ); + + arc.WriteSafePointer( movegoal ); + arc.WriteSafePointer( soundnode ); + arc.WriteSafePointer( thread ); + + arc.WriteSafePointer( actorthread ); + arc.WriteString( actorscript ); + arc.WriteString( actorstart ); + arc.WriteSafePointer( trig ); + + arc.WriteBoolean( has_melee ); + arc.WriteFloat( melee_damage ); + arc.WriteFloat( melee_range ); + arc.WriteFloat( aim ); + arc.WriteFloat( pain_threshold ); + + arc.WriteBoolean( checkStrafe ); + + arc.WriteFloat( next_drown_time ); + arc.WriteFloat( air_finished ); + arc.WriteFloat( attack_range ); + arc.WriteFloat( shots_per_attack ); + arc.WriteBoolean( deathgib ); + + arc.WriteString( kill_thread ); + arc.WriteVector( eyeoffset ); + arc.WriteFloat( last_jump_time ); + arc.WriteBoolean( nodeathfade ); + arc.WriteBoolean( nochatter ); + } + +inline EXPORT_FROM_DLL void Actor::Unarchive + ( + Archiver &arc + ) + { + Event * ev; + int i, num, temp; + + Sentient::Unarchive( arc ); + + arc.ReadString( &newanim ); + ev = new Event; + arc.ReadEvent( ev ); + newanimevent = ev; + arc.ReadInteger( &newanimnum ); + + arc.ReadString( &spawngroup ); + + arc.ReadInteger( &movement ); + // cast as stepmoveresult_t on read + temp = arc.ReadInteger(); + lastmove = ( stepmoveresult_t )temp; + arc.ReadFloat( &forwardspeed ); + + // cast as actortype_t on read + temp = arc.ReadInteger(); + actortype = ( actortype_t )temp; + arc.ReadInteger( &attackmode ); + + arc.ReadVector( &standsize_min ); + arc.ReadVector( &standsize_max ); + arc.ReadVector( &crouchsize_min ); + arc.ReadVector( &crouchsize_max ); + + arc.ReadString( &state ); + arc.ReadString( &animname ); + + arc.ReadInteger( &num ); + for( i = 1; i <= num; i++ ) + { + StateInfo * info; + + info = new StateInfo; + arc.ReadObject( info ); + actionList.AddObject( info ); + } + + arc.ReadInteger( &numonstack ); + + for( i = 1; i <= numonstack; i++ ) + { + ActorState * state; + + state = new ActorState; + arc.ReadObject( state ); + stateStack.Push( state ); + } + + behavior = NULL; + if ( arc.ReadBoolean() ) + { + behavior = ( Behavior * )arc.ReadObject(); + } + + arc.ReadString( ¤tBehavior ); + + arc.ReadSafePointer( &path ); + + arc.ReadInteger( &num ); + targetList.FreeObjectList(); + targetList.Resize( num ); + for( i = 1; i <= num; i++ ) + { + EntityPtr tmp, *ptr; + + targetList.AddObject( tmp ); + ptr = targetList.AddressOfObjectAt( i ); + arc.ReadSafePointer( ptr ); + } + + arc.ReadInteger( &num ); + nearbyList.FreeObjectList(); + nearbyList.Resize( num ); + for( i = 1; i <= num; i++ ) + { + EntityPtr tmp, *ptr; + + nearbyList.AddObject( tmp ); + ptr = nearbyList.AddressOfObjectAt( i ); + arc.ReadSafePointer( ptr ); + } + + arc.ReadInteger( &num ); + enemyList.FreeObjectList(); + enemyList.Resize( num ); + for( i = 1; i <= num; i++ ) + { + EntityPtr tmp, *ptr; + + enemyList.AddObject( tmp ); + ptr = enemyList.AddressOfObjectAt( i ); + arc.ReadSafePointer( ptr ); + } + + arc.ReadSafePointer( ¤tEnemy ); + arc.ReadBoolean( &seenEnemy ); + // cast as range_t on read + temp = arc.ReadInteger(); + enemyRange = ( range_t )temp; + arc.ReadSafePointer( &lastEnemy ); + + arc.ReadFloat( &fov ); + arc.ReadFloat( &fovdot ); + arc.ReadFloat( &vision_distance ); + + arc.ReadVector( &startpos ); + + arc.ReadVector( &move ); + arc.ReadVector( &movedir ); + arc.ReadFloat( &movespeed ); + arc.ReadVector( &movevelocity ); + arc.ReadFloat( &totallen ); + arc.ReadFloat( &turnspeed ); + arc.ReadVector( &animdir ); + + arc.ReadFloat( &chattime ); + arc.ReadFloat( &nextsoundtime ); + + arc.ReadBoolean( &hasalert ); + + arc.ReadSafePointer( &movegoal ); + arc.ReadSafePointer( &soundnode ); + arc.ReadSafePointer( &thread ); + + arc.ReadSafePointer( &actorthread ); + arc.ReadString( &actorscript ); + arc.ReadString( &actorstart ); + arc.ReadSafePointer( &trig ); + + arc.ReadBoolean( &has_melee ); + arc.ReadFloat( &melee_damage ); + arc.ReadFloat( &melee_range ); + arc.ReadFloat( &aim ); + arc.ReadFloat( &pain_threshold ); + + arc.ReadBoolean( &checkStrafe ); + + arc.ReadFloat( &next_drown_time ); + arc.ReadFloat( &air_finished ); + arc.ReadFloat( &attack_range ); + arc.ReadFloat( &shots_per_attack ); + arc.ReadBoolean( &deathgib ); + + arc.ReadString( &kill_thread ); + arc.ReadVector( &eyeoffset ); + arc.ReadFloat( &last_jump_time ); + arc.ReadBoolean( &nodeathfade ); + arc.ReadBoolean( &nochatter ); + } + + +class EXPORT_FROM_DLL MonsterStart : public PathNode + { + private: + str spawngroup; + MonsterStart *groupchain; + MonsterStart *leader; + static MonsterStart *GetGroupLeader( str& groupname ); + + public: + CLASS_PROTOTYPE( MonsterStart ); + + MonsterStart(); + static MonsterStart *GetRandomSpot( str& groupname ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void MonsterStart::Archive + ( + Archiver &arc + ) + { + PathNode::Archive( arc ); + + arc.WriteString( spawngroup ); + arc.WriteObjectPointer( groupchain ); + arc.WriteObjectPointer( leader ); + } + +inline EXPORT_FROM_DLL void MonsterStart::Unarchive + ( + Archiver &arc + ) + { + PathNode::Unarchive( arc ); + + arc.ReadString( &spawngroup ); + arc.ReadObjectPointer( ( Class ** )&groupchain ); + arc.ReadObjectPointer( ( Class ** )&leader ); + } + +// Set destination to node with duck or cover set. Class will find a path to that node, or a closer one. +class EXPORT_FROM_DLL FindCoverMovement : public StandardMovement + { + public: + Actor *self; + + inline qboolean validpath + ( + PathNode *node, + int i + ) + + { + pathway_t *path; + PathNode *n; + + path = &node->Child[ i ]; + + if ( !StandardMovement::validpath( node, i ) ) + { + return false; + } + + n = AI_GetNode( path->node ); + if ( !n || self->CloseToEnemy( n->worldorigin, 128 ) ) + { + return false; + } + + return true; + } + + inline qboolean done + ( + PathNode *node, + PathNode *end + ) + + { + if ( node == end ) + { + return true; + } + + //if ( node->reject ) + if ( node->reject || !( node->nodeflags & ( AI_DUCK | AI_COVER ) ) ) + { + return false; + } + + if ( self ) + { + node->reject = self->CanSeeEnemyFrom( node->worldorigin ); + return !node->reject; + } + + return false; + } + }; + +// Set destination to node with flee set. Class will find a path to that node, or a closer one. +class EXPORT_FROM_DLL FindFleeMovement : public StandardMovement + { + public: + Actor *self; + + inline qboolean validpath + ( + PathNode *node, + int i + ) + + { + pathway_t *path; + PathNode *n; + + path = &node->Child[ i ]; + + if ( !StandardMovement::validpath( node, i ) ) + { + return false; + } + + n = AI_GetNode( path->node ); + if ( !n || self->CloseToEnemy( n->worldorigin, 128 ) ) + { + return false; + } + + return true; + } + + inline qboolean done + ( + PathNode *node, + PathNode *end + ) + + { + if ( node == end ) + { + return true; + } + + //if ( node->reject ) + if ( node->reject || !( node->nodeflags & AI_FLEE ) ) + { + return false; + } + + if ( self ) + { + node->reject = self->CanSeeEnemyFrom( node->worldorigin ); + return !node->reject; + } + + return false; + } + }; + +class EXPORT_FROM_DLL FindEnemyMovement : public StandardMovement + { + public: + Actor *self; + + inline qboolean done + ( + PathNode *node, + PathNode *end + ) + + { + if ( node == end ) + { + return true; + } + + if ( node->reject ) + { + return false; + } + + if ( self ) + { + node->reject = !self->CanShootFrom( node->worldorigin, self->currentEnemy, false ); + return !node->reject; + } + + return false; + } + }; + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL PathFinder; +template class EXPORT_FROM_DLL PathFinder; +template class EXPORT_FROM_DLL PathFinder; +#endif + +typedef PathFinder FindCoverPath; +typedef PathFinder FindFleePath; +typedef PathFinder FindEnemyPath; + +#endif /* actor.h */ diff --git a/ammo.cpp b/ammo.cpp new file mode 100644 index 0000000..f5603d7 --- /dev/null +++ b/ammo.cpp @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/ammo.cpp $ +// $Revision:: 28 $ +// $Author:: Aldie $ +// $Date:: 10/24/98 2:07p $ +// +// 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/ammo.cpp $ +// +// 28 10/24/98 2:07p Aldie +// Moved pickupable function out. +// +// 27 10/21/98 9:05p Aldie +// Added spear ammo +// +// 26 10/21/98 5:24p Aldie +// Don't let mutants pickup ammo +// +// 25 10/14/98 1:01a Jimdose +// change Ammo::Ammo so that it used Set to set amount so that spawnarg values +// are not cleared out +// +// 24 10/05/98 11:59p Aldie +// Fix spidermines +// +// 23 6/28/98 4:13p Markd +// simplified setup +// +// 22 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// 21 6/24/98 1:35p Aldie +// Implementation of inventory system and picking stuff up +// +// 20 6/17/98 1:19a Jimdose +// Moved setOwner to Item. +// Added EV_Item_Pickup +// +// 19 6/10/98 5:10p Markd +// Using ExpandAlias method +// +// 18 6/10/98 1:19p Markd +// Added SetAmmoName and SetAmmoAmount, removed 357 ammo +// +// 17 6/08/98 7:21p Aldie +// Added SpiderMine ammo +// +// 16 5/26/98 4:44p Aldie +// Added remove ammo +// +// 15 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 +// +// 14 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 13 5/08/98 2:55p Markd +// Put in Ammo pickup sounds +// +// 12 4/28/98 5:28p Jimdose +// Changed rocket and sniper ammo from 20 to 10 +// +// 11 4/18/98 3:30p Markd +// Added new shotgun clip type +// +// 10 4/07/98 9:18p Jimdose +// Added Rockets +// +// 9 4/07/98 6:42p Jimdose +// Turned off respawn in singleplayer +// +// 8 4/04/98 6:00p Jimdose +// Made response to EV_Touch be response to EV_Trigger_Effect +// +// 7 3/31/98 4:21p Jimdose +// Changed model names +// +// 6 3/30/98 9:54p Jimdose +// Changed location of .def files +// +// 5 3/30/98 2:31p Jimdose +// Created file +// +// DESCRIPTION: +// Base class for all ammunition for entities derived from the Weapon class. +// + +#include "g_local.h" +#include "item.h" +#include "ammo.h" + +CLASS_DECLARATION( Item, Ammo, NULL ); + +ResponseDef Ammo::Responses[] = + { + { NULL, NULL } + }; + +Ammo::Ammo() + { + Set( 0 ); + } + +void Ammo::Setup + ( + const char *model + ) + + { + const char * m; + m = G_GetSpawnArg( "model" ); + if ( m ) + { + setModel( m ); + } + else + { + assert( model ); + setModel( model ); + } + } + +CLASS_DECLARATION( Ammo, Bullet10mm, "ammo_10mm" ); + +ResponseDef Bullet10mm::Responses[] = + { + { NULL, NULL } + }; + +Bullet10mm::Bullet10mm() + { + Setup( "357.def" ); + } + +CLASS_DECLARATION( Ammo, Bullet50mm, "ammo_50mm" ); + +ResponseDef Bullet50mm::Responses[] = + { + { NULL, NULL } + }; + +Bullet50mm::Bullet50mm() + { + Setup( "50mm.def" ); + } + +CLASS_DECLARATION( Ammo, BulletPulse, "ammo_pulserifle" ); + +ResponseDef BulletPulse::Responses[] = + { + { NULL, NULL } + }; + +BulletPulse::BulletPulse() + { + Setup( "pulse_ammo.def" ); + } + +CLASS_DECLARATION( Ammo, BulletSniper, "ammo_sniperrifle" ); + +ResponseDef BulletSniper::Responses[] = + { + { NULL, NULL } + }; + +BulletSniper::BulletSniper() + { + Setup( "sniper_ammo.def" ); + } + +CLASS_DECLARATION( Ammo, Rockets, "ammo_rockets" ); + +ResponseDef Rockets::Responses[] = + { + { NULL, NULL } + }; + +Rockets::Rockets() + { + Setup( "rockets.def" ); + } + +CLASS_DECLARATION( Ammo, Spears, "ammo_speargun" ); + +ResponseDef Spears::Responses[] = + { + { NULL, NULL } + }; + +Spears::Spears() + { + Setup( "spear_ammo.def" ); + } + +CLASS_DECLARATION( Ammo, ShotgunClip, "ammo_shotgunclip" ); + +ResponseDef ShotgunClip::Responses[] = + { + { NULL, NULL } + }; + +ShotgunClip::ShotgunClip() + { + Setup( "shotgunclip.def" ); + } + +CLASS_DECLARATION( Ammo, SpiderMines, "ammo_spidermines" ); + +ResponseDef SpiderMines::Responses[] = + { + { NULL, NULL } + }; + +SpiderMines::SpiderMines() + { + Setup( "mine.def" ); + } + diff --git a/ammo.h b/ammo.h new file mode 100644 index 0000000..1dec4e5 --- /dev/null +++ b/ammo.h @@ -0,0 +1,149 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/ammo.h $ +// $Revision:: 19 $ +// $Author:: Aldie $ +// $Date:: 10/24/98 2:09p $ +// +// 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/ammo.h $ +// +// 19 10/24/98 2:09p Aldie +// Moved pickupable out of ammo +// +// 18 10/21/98 10:39p Aldie +// Added spears +// +// 17 10/21/98 5:29p Aldie +// Added a pickupable function to ammo which restricts mutants +// +// 16 6/28/98 4:13p Markd +// simplified setup +// +// 15 6/25/98 8:48p Markd +// Rewrote Item class, added keyed items to triggers, cleaned up item system +// +// 14 6/24/98 1:38p Aldie +// Implementation of inventory system and picking stuff up +// +// 13 6/17/98 1:16a Jimdose +// Moved setOwner to Item. +// Added EV_Item_Pickup +// +// 12 6/10/98 1:18p Markd +// Added SetAmmoAmount and SetAmmoName, also removed 357 bullets +// +// 11 6/08/98 7:22p Aldie +// Added spidermines +// +// 10 5/26/98 4:44p Aldie +// Added remove ammo +// +// 9 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 8 5/08/98 2:57p Markd +// Added pickup sound +// +// 7 4/18/98 3:29p Markd +// Added new shotgunclip ammo +// +// 6 4/07/98 9:19p Jimdose +// Added rockets +// +// 5 3/30/98 2:31p Jimdose +// Created file +// +// DESCRIPTION: +// Base class for all ammunition for entities derived from the Weapon class. +// + +#ifndef __AMMO_H__ +#define __AMMO_H__ + +#include "g_local.h" +#include "item.h" +#include "sentient.h" +#include "item.h" + +class EXPORT_FROM_DLL Ammo : public Item + { + protected: + virtual void Setup( const char *model ); + + public: + CLASS_PROTOTYPE( Ammo ); + + Ammo(); + }; + +class EXPORT_FROM_DLL Bullet10mm : public Ammo + { + public: + CLASS_PROTOTYPE( Bullet10mm ); + + Bullet10mm(); + }; + +class EXPORT_FROM_DLL Bullet50mm : public Ammo + { + public: + CLASS_PROTOTYPE( Bullet50mm ); + + Bullet50mm(); + }; + +class EXPORT_FROM_DLL BulletPulse : public Ammo + { + public: + CLASS_PROTOTYPE( BulletPulse ); + + BulletPulse(); + }; + +class EXPORT_FROM_DLL BulletSniper : public Ammo + { + public: + CLASS_PROTOTYPE( BulletSniper ); + + BulletSniper(); + }; + +class EXPORT_FROM_DLL Rockets : public Ammo + { + public: + CLASS_PROTOTYPE( Rockets ); + + Rockets(); + }; + +class EXPORT_FROM_DLL Spears : public Ammo + { + public: + CLASS_PROTOTYPE( Spears ); + + Spears(); + }; + +class EXPORT_FROM_DLL ShotgunClip : public Ammo + { + public: + CLASS_PROTOTYPE( ShotgunClip ); + + ShotgunClip(); + }; + +class EXPORT_FROM_DLL SpiderMines : public Ammo + { + public: + CLASS_PROTOTYPE( SpiderMines ); + + SpiderMines(); + }; + +#endif /* ammo.h */ diff --git a/animals.cpp b/animals.cpp new file mode 100644 index 0000000..a38ea40 --- /dev/null +++ b/animals.cpp @@ -0,0 +1,289 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/animals.cpp $ +// $Revision:: 8 $ +// $Author:: Markd $ +// $Date:: 11/16/98 11:15p $ +// +// Copyright (C) 1998 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/animals.cpp $ +// +// 8 11/16/98 11:15p Markd +// Added fish +// +// 7 10/25/98 9:10p Markd +// made bats be more like rats but with wings +// +// 6 10/19/98 12:04a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 5 10/13/98 11:13p Markd +// Redid mouse, allowed mutation +// +// 4 9/22/98 5:19p Markd +// Put in new consolidated gib function +// +// 3 9/22/98 3:21p Markd +// put in parentmode lockout for blood and gibs +// +// 2 8/10/98 6:51p Aldie +// First version of rat +// +// DESCRIPTION: +// Animals behavior + +#include "actor.h" +#include "gibs.h" + +class EXPORT_FROM_DLL Rat : public Actor + { + + public: + CLASS_PROTOTYPE( Rat ); + + void Killed( Event *ev ); + void Touched( Event *ev ); + void Mutate( Event *ev ); + }; + +CLASS_DECLARATION( Actor, Rat, "animals_rat" ); + +ResponseDef Rat::Responses[] = + { + { &EV_Killed, ( Response )Rat::Killed }, + { &EV_Touch, ( Response )Rat::Touched }, + { &EV_Mutate, ( Response )Rat::Mutate }, + { NULL, NULL } + }; + + +void Rat::Touched + ( + Event *ev + ) + + { + Event *event; + Entity *other; + + other = ev->GetEntity( 1 ); + + assert( other != this ); + + if ( other->isSubclassOf( Sentient ) ) + { + event = new Event( EV_Actor_Attack ); + event->AddEntity( other ); + ProcessEvent( event ); + } + } + +void Rat::Mutate + ( + Event *ev + ) + + { + Vector tempmins; + Vector tempmaxs; + Event *event; + + assert( !( flags & FL_MUTATED ) ); + + flags |= FL_MUTATED; + + edict->s.scale = 3; + health *= edict->s.scale; + max_health *= edict->s.scale; + tempmins = mins * edict->s.scale; + tempmaxs = maxs * edict->s.scale; + setSize( tempmins, tempmaxs ); + + unlink(); + KillBox( this ); + link(); + + event = new Event( EV_Actor_MeleeDamage ); + event->AddFloat( melee_damage * edict->s.scale ); + ProcessEvent( event ); + event = new Event( EV_Actor_MeleeRange ); + event->AddFloat( melee_range * edict->s.scale ); + ProcessEvent( event ); + } + +void Rat::Killed + ( + Event *ev + ) + + { + takedamage = DAMAGE_NO; + setSolidType( SOLID_NOT ); + hideModel(); + + if ( sv_gibs->value && !parentmode->value ) + { + int numgibs; + float gibsize; + + gibsize = size.length() / 175; + numgibs = gibsize * 6; + if ( numgibs > 4 ) + numgibs = 4; + CreateGibs( this, health, gibsize, numgibs ); + } + + PostEvent( EV_Remove, 0 ); + } + +class EXPORT_FROM_DLL Bat : public Actor + { + + public: + CLASS_PROTOTYPE( Bat ); + + void Killed( Event *ev ); + void Touched( Event *ev ); + void Mutate( Event *ev ); + }; + +CLASS_DECLARATION( Actor, Bat, "animals_bat" ); + +ResponseDef Bat::Responses[] = + { + { &EV_Killed, ( Response )Bat::Killed }, + { &EV_Touch, ( Response )Bat::Touched }, + { &EV_Mutate, ( Response )Bat::Mutate }, + { NULL, NULL } + }; + + +void Bat::Touched + ( + Event *ev + ) + + { + Event *event; + Entity *other; + + other = ev->GetEntity( 1 ); + + assert( other != this ); + + if ( other->isSubclassOf( Sentient ) ) + { + event = new Event( EV_Actor_Attack ); + event->AddEntity( other ); + ProcessEvent( event ); + } + } + +void Bat::Mutate + ( + Event *ev + ) + + { + Vector tempmins; + Vector tempmaxs; + Event *event; + + assert( !( flags & FL_MUTATED ) ); + + flags |= FL_MUTATED; + + edict->s.scale = 5; + health *= edict->s.scale; + max_health *= edict->s.scale; + tempmins = mins * edict->s.scale; + tempmaxs = maxs * edict->s.scale; + setSize( tempmins, tempmaxs ); + + unlink(); + KillBox( this ); + link(); + + event = new Event( EV_Actor_MeleeDamage ); + event->AddFloat( melee_damage * edict->s.scale ); + ProcessEvent( event ); + event = new Event( EV_Actor_MeleeRange ); + event->AddFloat( melee_range * edict->s.scale ); + ProcessEvent( event ); + } + +void Bat::Killed + ( + Event *ev + ) + + { + takedamage = DAMAGE_NO; + setSolidType( SOLID_NOT ); + hideModel(); + + if ( sv_gibs->value && !parentmode->value ) + { + int numgibs; + float gibsize; + + gibsize = size.length() / 175; + numgibs = gibsize * 6; + if ( numgibs > 4 ) + numgibs = 4; + CreateGibs( this, health, gibsize, numgibs ); + } + + PostEvent( EV_Remove, 0 ); + } + + +class EXPORT_FROM_DLL Fish : public Actor + { + + public: + CLASS_PROTOTYPE( Fish ); + + void Killed( Event *ev ); + }; + +CLASS_DECLARATION( Actor, Fish, "animals_fish" ); + +ResponseDef Fish::Responses[] = + { + { &EV_Killed, ( Response )Fish::Killed }, + { NULL, NULL } + }; + + +void Fish::Killed + ( + Event *ev + ) + + { + takedamage = DAMAGE_NO; + setSolidType( SOLID_NOT ); + hideModel(); + + if ( sv_gibs->value && !parentmode->value ) + { + int numgibs; + float gibsize; + + gibsize = size.length() / 175; + numgibs = gibsize * 6; + if ( numgibs > 4 ) + numgibs = 4; + CreateGibs( this, health, gibsize, numgibs ); + } + + PostEvent( EV_Remove, 0 ); + } diff --git a/arcade_comm.cpp b/arcade_comm.cpp new file mode 100644 index 0000000..129ba96 --- /dev/null +++ b/arcade_comm.cpp @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/arcade_comm.cpp $ +// $Revision:: 4 $ +// $Author:: Aldie $ +// $Date:: 12/14/98 8:16p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/arcade_comm.cpp $ +// +// 4 12/14/98 8:16p Aldie +// Added a disablecom command +// +// 3 12/14/98 5:23p Aldie +// Added generic COM ports +// +// 2 12/08/98 7:04p Aldie +// First version of serial comm for arcade +// +// DESCRIPTION: +// Sin Arcade Serial Communications + +#ifdef SIN_ARCADE + +#include "arcade_comm.h" +#include + +static LPDCB lpDCB=NULL; +static HANDLE COMHANDLE=NULL; +static cvar_t *disable_com; + +void ARCADE_ComError + ( + void + ) + + { + LPVOID lpMsgBuf; + + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR)&lpMsgBuf, + 0, + NULL ); + + // Display the string. + gi.error( (const char *)lpMsgBuf ); + + // Free the buffer. + LocalFree( lpMsgBuf ); + } + + +qboolean ARCADE_ComWriteByte + ( + byte b + ) + + { + DWORD bytesWritten; + LPBYTE lpByte; + BOOL retval; + + if ( disable_com->value ) + return( true ); + + lpByte = (BYTE *)"UB"; + + if ( !lpDCB || !COMHANDLE ) + { + return false; + } + + retval = WriteFile( COMHANDLE, + lpByte, + 2, + &bytesWritten, + NULL ); + if (!retval) + { + return false; + } + + // 50 millisecond wait for the board + Sleep( 50 ); + + // Byte command to send to the Universal Board + lpByte = &b; + + // Send over the byte command + retval = WriteFile( COMHANDLE, + lpByte, + 2, + &bytesWritten, + NULL ); + + if (!retval) + { + return false; + } + + return true; + } + +void ARCADE_SetupCommunications + ( + void + ) + + { + LPCTSTR lpDef; + BOOL retval; + cvar_t *comport; + const char *comstring; + + if ( lpDCB ) // Already setup + return; + + disable_com = gi.cvar( "disablecom", "0", 0 ); + + if ( disable_com->value ) + return; + + comport = gi.cvar( "comport", "1", 0 ); + + if ( comport->value == 2 ) + { + lpDef = "COM2: baud=9600 parity=N data=8 stop=1"; + comstring = "COM2"; + } + else + { + lpDef = "COM1: baud=9600 parity=N data=8 stop=1"; + comstring = "COM1"; + } + + lpDCB = new DCB; + + retval = BuildCommDCB( lpDef, lpDCB ); + + if ( !retval ) + { + // An error occurred creating the device + ARCADE_ComError(); + return; + } + + if ( ( COMHANDLE = ( CreateFile( comstring, + GENERIC_WRITE, + 0, // exclusive access + NULL, // no security attrs + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL + ) ) ) == (HANDLE) -1 ) + { + gi.error( "Could not create COM I/O file\n"); + } + else + { + PurgeComm( COMHANDLE, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ); + } + } + + +void ARCADE_CloseCommunications + ( + void + ) + + { + CloseHandle( COMHANDLE ); + free( lpDCB ); + } +#endif \ No newline at end of file diff --git a/arcade_comm.h b/arcade_comm.h new file mode 100644 index 0000000..b979ead --- /dev/null +++ b/arcade_comm.h @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/arcade_comm.h $ +// $Revision:: 2 $ +// $Author:: Aldie $ +// $Date:: 12/14/98 5:55p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/arcade_comm.h $ +// +// 2 12/14/98 5:55p Aldie +// First version of arcade communications +// +// DESCRIPTION: +// Arcade Communications Functions + +#include "g_local.h" + +void ARCADE_SetupCommunications( void ); +void ARCADE_CloseCommunications( void ); +qboolean ARCADE_ComWriteByte( byte b ); diff --git a/archive.cpp b/archive.cpp new file mode 100644 index 0000000..bcf2713 --- /dev/null +++ b/archive.cpp @@ -0,0 +1,1164 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/archive.cpp $ +// $Revision:: 19 $ +// $Author:: Aldie $ +// $Date:: 11/12/98 3:46p $ +// +// 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/archive.cpp $ +// +// 19 11/12/98 3:46p Aldie +// Fixed length check for files not found. +// +// 18 11/12/98 2:41p Markd +// added assert for pos in Read +// +// 17 11/12/98 12:19p Markd +// archive file was being loaded as TAG_GAME this was a VERY BAD thing. +// +// 16 11/12/98 2:31a Jimdose +// Added ReadFile. Archives now read from pak files +// +// 15 10/19/98 12:04a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 14 10/10/98 1:26a Jimdose +// ReadObject no longer cancels objects events +// +// 13 10/07/98 11:41p Jimdose +// Got savegames working!!! +// Rewrote event archiving +// +// 12 9/22/98 7:18p Markd +// forgot to free up a list of objects when closing the file +// +// 11 9/22/98 3:58a Jimdose +// Incremented the archive version number +// +// 10 9/21/98 10:15p Markd +// Putting archiving and unarchiving functions in +// +// 9 9/21/98 4:21p Markd +// Put in archive functions and rewrote all archive routines +// +// 8 7/26/98 2:15a Jimdose +// ReadObject was casting a Class * as a Entity *. Not bad, but wrong. +// +// 7 6/11/98 7:18p Jimdose +// FileError now closes the file before exiting. This prevents an assertion +// when the object is deleted but the file is still open. +// +// 6 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 +// +// 5 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 4 5/09/98 8:04p Jimdose +// Create now creates the path if it doesn't exist +// +// 3 5/08/98 2:50p Jimdose +// fixed bugs +// +// 2 5/07/98 10:39p Jimdose +// created file +// +// 1 5/06/98 8:19p Jimdose +// +// DESCRIPTION: +// Class for archiving objects +// +#include "g_local.h" +#include "archive.h" + +#define ARCHIVE_WRITE 0 +#define ARCHIVE_READ 1 + +enum + { + ARC_NULL, ARC_Vector, ARC_Integer, ARC_Unsigned, ARC_Byte, ARC_Char, ARC_Short, ARC_UnsignedShort, + ARC_Float, ARC_Double, ARC_Boolean, ARC_String, ARC_Raw, ARC_Object, ARC_ObjectPointer, + ARC_SafePointer, ARC_Event, ARC_Quat, ARC_Entity, + ARC_NUMTYPES + }; + +static const char *typenames[] = + { + "NULL", "vector", "int", "unsigned", "byte", "char", "short", "unsigned short", + "float", "double", "qboolean", "string", "raw data", "object", "objectpointer", + "safepointer", "event", "quaternion", "entity" + }; + +#define ArchiveHeader ( *( int * )"SIN\0" ) +#define ArchiveVersion 2 // This must be changed any time the format changes! +#define ArchiveInfo "Sin Archive Version 2" // This must be changed any time the format changes! + +CLASS_DECLARATION( Class, ReadFile, NULL ); + +ResponseDef ReadFile::Responses[] = + { + { NULL, NULL } + }; + +ReadFile::ReadFile() + { + length = 0; + buffer = NULL; + pos = 0; + } + +ReadFile::~ReadFile() + { + Close(); + } + +void ReadFile::Close + ( + void + ) + + { + if ( buffer ) + { + gi.TagFree( ( void * )buffer ); + buffer = NULL; + } + + filename = ""; + length = 0; + pos = 0; + } + +const char *ReadFile::Filename + ( + void + ) + + { + return filename.c_str(); + } + +size_t ReadFile::Length + ( + void + ) + + { + return length; + } + +size_t ReadFile::Pos + ( + void + ) + + { + return pos - buffer; + } + +qboolean ReadFile::Seek + ( + size_t newpos + ) + + { + if ( !buffer ) + { + return false; + } + + if ( newpos < 0 ) + { + return false; + } + + if ( newpos > length ) + { + return false; + } + + pos = buffer + newpos; + + return true; + } + +qboolean ReadFile::Open + ( + const char *name + ) + + { + assert( name ); + + assert( !buffer ); + Close(); + + if ( !name ) + { + return false; + } + + length = gi.LoadFile( name, ( void ** )&buffer, 0 ); + if ( length == ( size_t )( -1 ) ) + { + return false; + } + + filename = name; + pos = buffer; + + return true; + } + +qboolean ReadFile::Read + ( + void *dest, + size_t size + ) + + { + assert( dest ); + assert( buffer ); + assert( pos ); + + if ( !dest ) + { + return false; + } + + if ( size <= 0 ) + { + return false; + } + + if ( ( pos + size ) > ( buffer + length ) ) + { + return false; + } + + memcpy( dest, pos, size ); + pos += size; + + return true; + } + +CLASS_DECLARATION( Class, Archiver, NULL ); + +ResponseDef Archiver::Responses[] = + { + { NULL, NULL } + }; + +Archiver::Archiver() + { + file = NULL; + fileerror = false; + assert( ( sizeof( typenames ) / sizeof( typenames[ 0 ] ) ) == ARC_NUMTYPES ); + } + +Archiver::~Archiver() + { + if ( file ) + { + Close(); + } + + readfile.Close(); + } + +void Archiver::FileError + ( + const char *fmt, + ... + ) + + { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + fileerror = true; + Close(); + if ( archivemode == ARCHIVE_READ ) + { + gi.error( "Error while loading %s : %s\n", filename.c_str(), text ); + } + else + { + gi.error( "Error while writing to %s : %s\n", filename.c_str(), text ); + } + } + +void Archiver::Close + ( + void + ) + + { + if ( file ) + { + if ( archivemode == ARCHIVE_WRITE ) + { + // write out the number of classpointers + fseek( file, numclassespos, SEEK_SET ); + numclassespos = ftell( file ); + WriteInteger( classpointerList.NumObjects() ); + } + + fclose( file ); + file = NULL; + } + + readfile.Close(); + + if ( archivemode == ARCHIVE_READ ) + { + int i, num; + Class * classptr; + pointer_fixup_t *fixup; + + num = fixupList.NumObjects(); + for( i = 1; i <= num; i++ ) + { + fixup = fixupList.ObjectAt( i ); + classptr = classpointerList.ObjectAt( fixup->index ); + if ( fixup->type == pointer_fixup_normal ) + { + Class ** fixupptr; + fixupptr = ( Class ** )fixup->ptr; + *fixupptr = classptr; + } + else if ( fixup->type == pointer_fixup_safe ) + { + SafePtrBase * fixupptr; + fixupptr = ( SafePtrBase * )fixup->ptr; + fixupptr->InitSafePtr( classptr ); + } + delete fixup; + } + fixupList.FreeObjectList(); + classpointerList.FreeObjectList(); + } + } + +/**************************************************************************************** + + File Read functions + +*****************************************************************************************/ + +void Archiver::Read + ( + const char *name + ) + + { + unsigned header; + unsigned version; + str info; + int num; + int i; + Class *null; + + assert( name ); + if ( !name ) + { + gi.error( "NULL pointer for filename in Archiver::Read.\n" ); + } + + fileerror = false; + + archivemode = ARCHIVE_READ; + + filename = name; + + if ( !readfile.Open( filename.c_str() ) ) + { + FileError( "Couldn't open file." ); + } + + header = ReadUnsigned(); + if ( header != ArchiveHeader ) + { + readfile.Close(); + FileError( "Not a valid Sin archive." ); + } + + version = ReadUnsigned(); + if ( version > ArchiveVersion ) + { + readfile.Close(); + FileError( "Archive is from version %.2f. Check http://www.ritual.com for an update.", version ); + } + + if ( version < ArchiveVersion ) + { + readfile.Close(); + FileError( "Archive is out of date." ); + } + + info = ReadString(); + gi.dprintf( "%s\n", info.c_str() ); + + // setup out class pointers + num = ReadInteger(); + classpointerList.Resize( num ); + null = NULL; + for( i = 1; i <= num; i++ ) + { + classpointerList.AddObject( null ); + } + } + +inline void Archiver::CheckRead + ( + void + ) + + { + assert( archivemode == ARCHIVE_READ ); + if ( !fileerror && ( archivemode != ARCHIVE_READ ) ) + { + FileError( "File read during a write operation." ); + } + } + +inline int Archiver::ReadType + ( + void + ) + + { + int t; + + if ( !fileerror ) + { + readfile.Read( &t, sizeof( t ) ); + + return t; + } + + return ARC_NULL; + } + +inline void Archiver::CheckType + ( + int type + ) + + { + int t; + + assert( ( type >= 0 ) && ( type < ARC_NUMTYPES ) ); + + if ( !fileerror ) + { + t = ReadType(); + if ( t != type ) + { + FileError( "Expecting %s", typenames[ type ] ); + } + } + } + +inline size_t Archiver::ReadSize + ( + void + ) + + { + size_t s; + + s = 0; + if ( !fileerror ) + { + readfile.Read( &s, sizeof( s ) ); + } + + return s; + } + +inline void Archiver::CheckSize + ( + int type, + size_t size + ) + + { + size_t s; + + if ( !fileerror ) + { + s = ReadSize(); + + if ( size != s ) + { + FileError( "Invalid data size of %d on %s.", s, typenames[ type ] ); + } + } + } + +inline void Archiver::ReadData + ( + int type, + void *data, + size_t size + ) + + { + CheckRead(); + CheckType( type ); + CheckSize( type, size ); + + if ( !fileerror && size ) + { + readfile.Read( data, size ); + } + } + +#define READ( func, type ) \ +type Archiver::Read##func \ + ( \ + void \ + ) \ + \ + { \ + type v; \ + \ + ReadData( ARC_##func, &v, sizeof( type ) ); \ + \ + return v; \ + } + + READ( Vector, Vector ); + READ( Integer, int ); + READ( Unsigned, unsigned ); + READ( Byte, byte ); + READ( Char, char ); + READ( Short, short ); + READ( UnsignedShort, unsigned short ); + READ( Float, float ); + READ( Double, double ); + READ( Boolean, qboolean ); + READ( Quat, Quat ); + +#define READPTR( func, type ) \ +void Archiver::Read##func \ + ( \ + type * v \ + ) \ + \ + { \ + ReadData( ARC_##func, v, sizeof( type ) ); \ + } + + READPTR( Vector, Vector ); + READPTR( Integer, int ); + READPTR( Unsigned, unsigned ); + READPTR( Byte, byte ); + READPTR( Char, char ); + READPTR( Short, short ); + READPTR( UnsignedShort, unsigned short ); + READPTR( Float, float ); + READPTR( Double, double ); + READPTR( Boolean, qboolean ); + READPTR( Quat, Quat ); + +void Archiver::ReadObjectPointer + ( + Class ** ptr + ) + + { + int index; + pointer_fixup_t *fixup; + + ReadData( ARC_ObjectPointer, &index, sizeof( index ) ); + + // Check for a NULL pointer + assert( ptr ); + if ( !ptr ) + { + FileError( "NULL pointer in ReadObjectPointer." ); + } + + // + // see if the variable was NULL + // + if ( index == ARCHIVE_NULL_POINTER ) + { + *ptr = NULL; + } + else + { + // init the pointer with NULL until we can fix it + *ptr = NULL; + + fixup = new pointer_fixup_t; + fixup->ptr = ( void ** )ptr; + fixup->index = index; + fixup->type = pointer_fixup_normal; + fixupList.AddObject( fixup ); + } + } + +void Archiver::ReadSafePointer + ( + SafePtrBase * ptr + ) + + { + int index; + pointer_fixup_t *fixup; + + ReadData( ARC_SafePointer, &index, sizeof( &index ) ); + + // Check for a NULL pointer + assert( ptr ); + if ( !ptr ) + { + FileError( "NULL pointer in ReadSafePointer." ); + } + + // + // see if the variable was NULL + // + if ( index == ARCHIVE_NULL_POINTER ) + { + ptr->InitSafePtr( NULL ); + } + else + { + // init the pointer with NULL until we can fix it + ptr->InitSafePtr( NULL ); + + // Add new fixup + fixup = new pointer_fixup_t; + fixup->ptr = ( void ** )ptr; + fixup->index = index; + fixup->type = pointer_fixup_safe; + fixupList.AddObject( fixup ); + } + } + +Event Archiver::ReadEvent + ( + void + ) + + { + Event ev; + + CheckRead(); + CheckType( ARC_Event ); + + if ( !fileerror ) + { + ev.Unarchive( *this ); + } + + return ev; + } + +void Archiver::ReadEvent + ( + Event * ev + ) + + { + CheckRead(); + CheckType( ARC_Event ); + + if ( !fileerror ) + { + ev->Unarchive( *this ); + } + } + +void Archiver::ReadRaw + ( + void *data, + size_t size + ) + + { + ReadData( ARC_Raw, data, size ); + } + +str Archiver::ReadString + ( + void + ) + + { + size_t s; + char *data; + str string; + + CheckRead(); + CheckType( ARC_String ); + + if ( !fileerror ) + { + s = ReadSize(); + if ( !fileerror ) + { + data = new char[ s + 1 ]; + if ( s ) + { + readfile.Read( data, s ); + } + data[ s ] = 0; + + string = data; + + delete [] data; + } + } + + return string; + } + +void Archiver::ReadString + ( + str * string + ) + + { + *string = ReadString(); + } + +Class *Archiver::ReadObject + ( + void + ) + + { + ClassDef *cls; + Class *obj; + str classname; + long objstart; + long endpos; + int index; + size_t size; + qboolean isent; + int type; + + CheckRead(); + + type = ReadType(); + if ( ( type != ARC_Object ) && ( type != ARC_Entity ) ) + { + FileError( "Expecting %s or %s", typenames[ ARC_Object ], typenames[ ARC_Entity ] ); + } + + size = ReadSize(); + classname = ReadString(); + + cls = getClass( classname.c_str() ); + if ( !cls ) + { + FileError( "Invalid class %s.", classname.c_str() ); + } + + isent = checkInheritance( &Entity::ClassInfo, cls ); + if ( type == ARC_Entity ) + { + if ( !isent ) + { + FileError( "Non-Entity class object '%s' saved as an Entity based object.", classname.c_str() ); + } + + game.force_entnum = true; + game.spawn_entnum = ReadInteger(); + } + else if ( isent ) + { + FileError( "Entity class object '%s' saved as non-Entity based object.", classname.c_str() ); + } + + index = ReadInteger(); + objstart = readfile.Pos(); + + obj = ( Class * )cls->newInstance(); + if ( !obj ) + { + FileError( "Failed to on new instance of class %s.", classname.c_str() ); + } + else + { + obj->Unarchive( *this ); + } + + if ( isent ) + { + game.force_entnum = false; + } + + if ( !fileerror ) + { + endpos = readfile.Pos(); + if ( ( endpos - objstart ) > size ) + { + FileError( "Object read past end of object's data" ); + } + else if ( ( endpos - objstart ) < size ) + { + FileError( "Object didn't read entire data from file" ); + } + } + + // + // register this pointer with our list + // + classpointerList.AddObjectAt( index, obj ); + + return obj; + } + +Class *Archiver::ReadObject + ( + Class *obj + ) + + { + ClassDef *cls; + str classname; + long objstart; + long endpos; + int index; + size_t size; + int type; + qboolean isent; + + CheckRead(); + type = ReadType(); + if ( ( type != ARC_Object ) && ( type != ARC_Entity ) ) + { + FileError( "Expecting %s or %s", typenames[ ARC_Object ], typenames[ ARC_Entity ] ); + } + + size = ReadSize(); + classname = ReadString(); + + cls = getClass( classname.c_str() ); + if ( !cls ) + { + FileError( "Invalid class %s.", classname.c_str() ); + } + + if ( obj->classinfo() != cls ) + { + FileError( "Archive has a '%s' object, but was expecting a '%s' object.", classname.c_str(), obj->getClassname() ); + } + + isent = obj->isSubclassOf( Entity ); + if ( type == ARC_Entity ) + { + if ( !isent ) + { + FileError( "Non-Entity class object '%s' saved as an Entity based object.", classname.c_str() ); + } + + ( ( Entity * )obj )->SetEntNum( ReadInteger() ); + } + else if ( isent ) + { + FileError( "Entity class object '%s' saved as non-Entity based object.", classname.c_str() ); + } + + index = ReadInteger(); + objstart = readfile.Pos(); + + obj->Unarchive( *this ); + + if ( !fileerror ) + { + endpos = readfile.Pos(); + if ( ( endpos - objstart ) > size ) + { + FileError( "Object read past end of object's data" ); + } + else if ( ( endpos - objstart ) < size ) + { + FileError( "Object didn't read entire data from file" ); + } + } + + // + // register this pointer with our list + // + classpointerList.AddObjectAt( index, obj ); + + return obj; + } + +/**************************************************************************************** + + File Write functions + +*****************************************************************************************/ + +void Archiver::Create + ( + const char *name + ) + + { + assert( name ); + if ( !name ) + { + gi.error( "NULL pointer for filename in Archiver::Create.\n" ); + } + + fileerror = false; + + archivemode = ARCHIVE_WRITE; + + filename = name; + + gi.CreatePath( filename.c_str() ); + file = fopen( filename.c_str(), "wb" ); + if ( !file ) + { + FileError( "Couldn't open file." ); + } + + WriteUnsigned( ArchiveHeader ); + WriteUnsigned( ArchiveVersion ); + WriteString( str( ArchiveInfo ) ); + + numclassespos = ftell( file ); + WriteInteger( 0 ); + } + +inline void Archiver::CheckWrite + ( + void + ) + + { + assert( archivemode == ARCHIVE_WRITE ); + if ( !fileerror && ( archivemode != ARCHIVE_WRITE ) ) + { + FileError( "File write during a read operation." ); + } + } + +inline void Archiver::WriteType + ( + int type + ) + + { + fwrite( &type, sizeof( type ), 1, file ); + } + +inline void Archiver::WriteSize + ( + size_t size + ) + + { + fwrite( &size, sizeof( size ), 1, file ); + } + +inline void Archiver::WriteData + ( + int type, + const void *data, + size_t size + ) + + { + CheckWrite(); + WriteType( type ); + WriteSize( size ); + + if ( !fileerror && size ) + { + fwrite( data, size, 1, file ); + } + } + +#define WRITE( func, type ) \ +void Archiver::Write##func \ + ( \ + type v \ + ) \ + \ + { \ + WriteData( ARC_##func, &v, sizeof( type ) ); \ + } + + WRITE( Vector, Vector & ); + WRITE( Quat, Quat & ); + WRITE( Integer, int ); + WRITE( Unsigned, unsigned ); + WRITE( Byte, byte ); + WRITE( Char, char ); + WRITE( Short, short ); + WRITE( UnsignedShort, unsigned short ); + WRITE( Float, float ); + WRITE( Double, double ); + WRITE( Boolean, qboolean ); + +void Archiver::WriteRaw + ( + const void *data, + size_t size + ) + + { + WriteData( ARC_Raw, data, size ); + } + +void Archiver::WriteString + ( + str &string + ) + + { + WriteData( ARC_String, string.c_str(), string.length() ); + } + +void Archiver::WriteObject + ( + Class *obj + ) + + { + str classname; + long sizepos; + long objstart; + long endpos; + int index; + size_t size; + qboolean isent; + + assert( obj ); + if ( !obj ) + { + FileError( "NULL object in WriteObject" ); + } + + isent = obj->isSubclassOf( Entity ); + + CheckWrite(); + if ( isent ) + { + WriteType( ARC_Entity ); + } + else + { + WriteType( ARC_Object ); + } + + sizepos = ftell( file ); + size = 0; + WriteSize( size ); + + classname = obj->getClassname(); + WriteString( classname ); + + if ( isent ) + { + // Write out the entity number + WriteInteger( ( ( Entity * )obj )->entnum ); + } + + // write out pointer index for this class pointer + index = classpointerList.AddUniqueObject( obj ); + WriteInteger( index ); + + if ( !fileerror ) + { + objstart = ftell( file ); + obj->Archive( *this ); + } + + if ( !fileerror ) + { + endpos = ftell( file ); + size = endpos - objstart; + fseek( file, sizepos, SEEK_SET ); + WriteSize( size ); + + if ( !fileerror ) + { + fseek( file, endpos, SEEK_SET ); + } + } + } + +void Archiver::WriteObjectPointer + ( + Class * ptr + ) + + { + int index; + + if ( ptr ) + { + index = classpointerList.AddUniqueObject( ptr ); + } + else + { + index = ARCHIVE_NULL_POINTER; + } + WriteData( ARC_ObjectPointer, &index, sizeof( index ) ); + } + +void Archiver::WriteSafePointer + ( + Class * ptr + ) + + { + int index; + + if ( ptr ) + { + index = classpointerList.AddUniqueObject( ptr ); + } + else + { + index = ARCHIVE_NULL_POINTER; + } + WriteData( ARC_SafePointer, &index, sizeof( index ) ); + } + +void Archiver::WriteEvent + ( + Event &ev + ) + + { + CheckWrite(); + WriteType( ARC_Event ); + + //FIXME!!!! Make this handle null events + if ( &ev == NULL ) + { + NullEvent.Archive( *this ); + } + else + { + ev.Archive( *this ); + } + } diff --git a/archive.h b/archive.h new file mode 100644 index 0000000..3a87bef --- /dev/null +++ b/archive.h @@ -0,0 +1,213 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/archive.h $ +// $Revision:: 9 $ +// $Author:: Jimdose $ +// $Date:: 11/12/98 2:31a $ +// +// 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/archive.h $ +// +// 9 11/12/98 2:31a Jimdose +// Added ReadFile. Archives now read from pak files +// +// 8 10/25/98 11:52p Jimdose +// added EXPORT_TEMPLATE +// +// 7 10/07/98 11:41p Jimdose +// Got savegames working!!! +// Rewrote event archiving +// +// 6 9/21/98 10:15p Markd +// Putting archiving and unarchiving functions in +// +// 5 9/21/98 4:21p Markd +// Put in archive functions and rewrote all archive routines +// +// 4 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 3 5/08/98 2:50p Jimdose +// Base archivable class is now Class instead of Listener +// +// 2 5/07/98 10:39p Jimdose +// created file +// +// 1 5/06/98 8:19p Jimdose +// +// DESCRIPTION: +// Class for archiving objects +// + +#ifndef __ARCHIVE_H__ +#define __ARCHIVE_H__ + +#include "g_local.h" +#include "str.h" + +#define ARCHIVE_NULL_POINTER ( -654321 ) +#define ARCHIVE_POINTER_VALID ( 0 ) +#define ARCHIVE_POINTER_NULL ( ARCHIVE_NULL_POINTER ) +#define ARCHIVE_POINTER_SELF_REFERENTIAL ( -123456 ) + +enum + { + pointer_fixup_normal, + pointer_fixup_safe + }; + +typedef struct + { + void **ptr; + int index; + int type; + } pointer_fixup_t; + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL ReadFile : public Class + { + protected: + str filename; + size_t length; + byte *buffer; + byte *pos; + + public: + CLASS_PROTOTYPE( ReadFile ); + + ReadFile(); + ~ReadFile(); + void Close( void ); + const char *Filename( void ); + size_t Length( void ); + size_t Pos( void ); + qboolean Seek( size_t newpos ); + qboolean Open( const char *name ); + qboolean Read( void *dest, size_t size ); + }; + +class EXPORT_FROM_DLL Archiver : public Class + { + private: + Container classpointerList; + Container fixupList; + + protected: + str filename; + qboolean fileerror; + FILE *file; + ReadFile readfile; + int archivemode; + int numclassespos; + + void CheckRead( void ); + void CheckType( int type ); + int ReadType( void ); + size_t ReadSize( void ); + void CheckSize( int type, size_t size ); + void ReadData( int type, void *data, size_t size ); + + void CheckWrite( void ); + void WriteType( int type ); + void WriteSize( size_t size ); + void WriteData( int type, const void *data, size_t size ); + + public: + CLASS_PROTOTYPE( Archiver ); + + Archiver(); + ~Archiver(); + void FileError( const char *fmt, ... ); + void Close( void ); + + void Read( str &name ); + void Read( const char *name ); + + // + // return methods + // + Vector ReadVector( void ); + Quat ReadQuat( void ); + int ReadInteger( void ); + unsigned ReadUnsigned( void ); + byte ReadByte( void ); + char ReadChar( void ); + short ReadShort( void ); + unsigned short ReadUnsignedShort( void ); + float ReadFloat( void ); + double ReadDouble( void ); + qboolean ReadBoolean( void ); + str ReadString( void ); + Event ReadEvent( void ); + // + // ptr methods + // + void ReadVector( Vector * vec ); + void ReadQuat( Quat * quat ); + void ReadInteger( int * num ); + void ReadUnsigned( unsigned * unum); + void ReadByte( byte * num ); + void ReadChar( char * ch ); + void ReadShort( short * num ); + void ReadUnsignedShort( unsigned short * num ); + void ReadFloat( float * num ); + void ReadDouble( double * num ); + void ReadBoolean( qboolean * bool ); + void ReadString( str * string ); + void ReadObjectPointer( Class ** ptr ); + void ReadSafePointer( SafePtrBase * ptr ); + void ReadEvent( Event * ev ); + + void ReadRaw( void *data, size_t size ); + Class *ReadObject( void ); + Class *ReadObject( Class *obj ); + + void Create( str &name ); + void Create( const char *name ); + void WriteVector( Vector &v ); + void WriteQuat( Quat &quat ); + void WriteInteger( int v ); + void WriteUnsigned( unsigned v ); + void WriteByte( byte v ); + void WriteChar( char v ); + void WriteShort( short v ); + void WriteUnsignedShort( unsigned short v ); + void WriteFloat( float v ); + void WriteDouble( double v ); + void WriteBoolean( qboolean v ); + void WriteRaw( const void *data, size_t size ); + void WriteString( str &string ); + void WriteObject( Class *obj ); + void WriteObjectPointer( Class * ptr ); + void WriteSafePointer( Class * ptr ); + void WriteEvent( Event &ev ); + }; + +inline EXPORT_FROM_DLL void Archiver::Read + ( + str &name + ) + + { + Read( name.c_str() ); + } + +inline EXPORT_FROM_DLL void Archiver::Create + ( + str &name + ) + + { + Create( name.c_str() ); + } + +#endif /* archive.h */ diff --git a/areaportal.cpp b/areaportal.cpp new file mode 100644 index 0000000..0de2b49 --- /dev/null +++ b/areaportal.cpp @@ -0,0 +1,212 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/areaportal.cpp $ +// $Revision:: 8 $ +// $Author:: Jimdose $ +// $Date:: 10/19/98 6:08p $ +// +// Copyright (C) 1998 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/areaportal.cpp $ +// +// 8 10/19/98 6:08p Jimdose +// SetAreaPortals now cancels any waiting open or close portal events +// +// 7 8/28/98 7:14p Markd +// added world to trigger event +// +// 6 8/28/98 2:54p Markd +// Added Targets to AreaPortals +// +// 5 5/24/98 8:55p Jimdose +// Removed the char * cast from Q_stricmp call +// +// 4 5/20/98 11:11a Markd +// removed char * dependency +// +// 3 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 2 3/11/98 2:25p Jimdose +// Created file +// +// DESCRIPTION: +// + +#include "g_local.h" +#include "entity.h" +#include "areaportal.h" + +Event EV_AreaPortal_Open( "open" ); +Event EV_AreaPortal_Close( "close" ); + +void SetAreaPortals + ( + const char *name, + qboolean open + ) + + { + int t; + Entity *ent; + float time; + Event event; + + if ( !name ) + { + return; + } + + // delay turning a portal off so that lerping models are in place when the portal goes off + if ( open ) + { + time = 0; + event = EV_AreaPortal_Open; + } + else + { + time = FRAMETIME; + event = EV_AreaPortal_Close; + } + + t = 0; + while( t = G_FindTarget( t, name ) ) + { + ent = G_GetEntity( t ); + assert( ent ); + if ( Q_stricmp( ent->getClassID(), "func_areaportal" ) == 0 ) + { + // Cancel any waiting portal events + ent->CancelEventsOfType( EV_AreaPortal_Open ); + ent->CancelEventsOfType( EV_AreaPortal_Close ); + ent->PostEvent( event, time ); + } + } + } + +/*****************************************************************************/ +/*SINED func_areaportal (0 0 0) ? + +This is a non-visible object that divides the world into +areas that are seperated when this portal is not activated. +Usually enclosed in the middle of a door. + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, AreaPortal, "func_areaportal" ); + +ResponseDef AreaPortal::Responses[] = + { + { &EV_AreaPortal_Open, ( Response )AreaPortal::Open }, + { &EV_AreaPortal_Close, ( Response )AreaPortal::Close }, + { NULL, NULL } + }; + +void AreaPortal::SetPortalState + ( + qboolean state + ) + + { + portalstate = state; + gi.SetAreaPortalState( portalnum, portalstate ); + } + +qboolean AreaPortal::PortalOpen + ( + void + ) + + { + return portalstate; + } + +void AreaPortal::Open + ( + Event *ev + ) + + { + const char *name; + + SetPortalState( true ); + + // + // fire targets + // + name = Target(); + if ( name && strcmp( name, "" ) ) + { + int num; + Event *event; + Entity *ent; + num = 0; + do + { + num = G_FindTarget( num, name ); + if ( !num ) + { + break; + } + + ent = G_GetEntity( num ); + + event = new Event( EV_Activate ); + event->AddEntity( world ); + ent->ProcessEvent( event ); + } + while ( 1 ); + } + } + +void AreaPortal::Close + ( + Event *ev + ) + + { + const char *name; + SetPortalState( false ); + + // + // fire targets + // + name = Target(); + if ( name && strcmp( name, "" ) ) + { + int num; + Event *event; + Entity *ent; + num = 0; + do + { + num = G_FindTarget( num, name ); + if ( !num ) + { + break; + } + + ent = G_GetEntity( num ); + + event = new Event( EV_Activate ); + event->AddEntity( world ); + ent->ProcessEvent( event ); + } + while ( 1 ); + } + } + +AreaPortal::AreaPortal() + { + portalnum = G_GetIntArg( "style" ); + + if ( !LoadingSavegame ) + { + // always start closed, except during savegames + SetPortalState( false ); + } + } diff --git a/areaportal.h b/areaportal.h new file mode 100644 index 0000000..43301ea --- /dev/null +++ b/areaportal.h @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/areaportal.h $ +// $Revision:: 5 $ +// $Author:: Markd $ +// $Date:: 9/29/98 5:58p $ +// +// Copyright (C) 1998 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/areaportal.h $ +// +// 5 9/29/98 5:58p Markd +// put in archive and unarchive +// +// 4 5/20/98 11:12a Markd +// removed char * dependency +// +// 3 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 2 3/11/98 2:25p Jimdose +// Created file +// +// DESCRIPTION: +// + +#ifndef __AREAPORTAL_H__ +#define __AREAPORTAL_H__ + +#include "g_local.h" +#include "entity.h" + +extern Event EV_AreaPortal_Open; +extern Event EV_AreaPortal_Close; + +void SetAreaPortals( const char *name, qboolean open ); + +class EXPORT_FROM_DLL AreaPortal : public Entity + { + private: + int portalstate; + int portalnum; + + public: + CLASS_PROTOTYPE( AreaPortal ); + + AreaPortal(); + void SetPortalState( qboolean state ); + qboolean PortalOpen( void ); + void Open( Event *ev ); + void Close( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void AreaPortal::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteInteger( portalstate ); + arc.WriteInteger( portalnum ); + } + +inline EXPORT_FROM_DLL void AreaPortal::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadInteger( &portalstate ); + arc.ReadInteger( &portalnum ); + } + +#endif diff --git a/armor.cpp b/armor.cpp new file mode 100644 index 0000000..714bab7 --- /dev/null +++ b/armor.cpp @@ -0,0 +1,202 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/armor.cpp $ +// $Revision:: 21 $ +// $Author:: Aldie $ +// $Date:: 10/24/98 2:07p $ +// +// 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/armor.cpp $ +// +// 21 10/24/98 2:07p Aldie +// Mutants can't pickup armor +// +// 20 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 19 10/09/98 2:05a Aldie +// Updated DMFLAGS +// +// 18 7/24/98 5:02p Aldie +// Armor never adds, only replaces +// +// 17 7/24/98 3:46p Aldie +// dmflags - armor +// +// 16 7/11/98 5:55p Aldie +// Fix pickup for armor +// +// 15 6/30/98 6:47p Aldie +// If no amount in armor, then you can't pick it up. +// +// 14 6/28/98 4:13p Markd +// simplified setup +// +// 13 6/25/98 8:58p Markd +// made Pickupable comparison check for >= +// +// 12 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// 11 6/25/98 7:31p Aldie +// Changed the icon names +// +// 10 6/24/98 1:35p Aldie +// Implementation of inventory system and picking stuff up +// +// 9 6/17/98 1:19a Jimdose +// Moved setOwner to Item. +// Added EV_Item_Pickup +// +// 8 6/15/98 3:35p Aldie +// Updated picking up of armor to have a maxout value +// +// 7 6/05/98 6:22p Aldie +// +// 6 6/05/98 2:45p Aldie +// New version of armor +// +// 4 11/07/97 5:59p Markd +// Removed QUAKE specific sound effects +// +// 3 10/27/97 3:30p Jimdose +// Removed dependency on quakedef.h +// +// 2 9/26/97 5:30p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Standard armor that prevents a percentage of damage per hit. +// + +#include "g_local.h" +#include "armor.h" + +/* +======== +ARMOR +======== +*/ + +CLASS_DECLARATION( Item, Armor, NULL ) + +ResponseDef Armor::Responses[] = + { + { NULL, NULL } + }; + +Armor::Armor + ( + ) + { + if ( DM_FLAG( DF_NO_ARMOR ) ) + { + PostEvent( EV_Remove, 0 ); + return; + } + + Set( 0 ); + } + +void Armor::Setup + ( + const char *model, + int amount + ) + + { + assert( model ); + setModel( model ); + Set( amount ); + } + +void Armor::Add + ( + int num + ) + + { + // Armor never adds, it only replaces + amount = num; + if ( amount >= MaxAmount() ) + amount = MaxAmount(); + } + +qboolean Armor::Pickupable + ( + Entity *other + ) + + { + if ( !other->isSubclassOf( Sentient ) ) + { + return false; + } + else + { + Sentient * sent; + Item * item; + + sent = ( Sentient * )other; + + // Mutants don't get armor + if ( sent->flags & ( FL_MUTANT | FL_SP_MUTANT ) ) + { + return false; + } + + item = sent->FindItem( getClassname() ); + // If our armor is > than our current armor or armor has no value, then leave it alone. + if ( item ) + if ( ( item->Amount() >= this->Amount() ) || !this->Amount() ) + { + return false; + } + } + return true; + } + +CLASS_DECLARATION( Armor, RiotHelmet, "armor_riothelmet" ); + +ResponseDef RiotHelmet::Responses[] = + { + { NULL, NULL } + }; + +RiotHelmet::RiotHelmet() + { + Setup( "riothelm.def", MAX_ARMOR ); + } + +CLASS_DECLARATION( Armor, FlakJacket, "armor_flakjacket" ); + +ResponseDef FlakJacket::Responses[] = + { + { NULL, NULL } + }; + +FlakJacket::FlakJacket() + { + Setup( "flakjack.def", MAX_ARMOR ); + } + +CLASS_DECLARATION( Armor, FlakPants, "armor_flakpants" ); + +ResponseDef FlakPants::Responses[] = + { + { NULL, NULL } + }; + +FlakPants::FlakPants() + { + Setup( "flakpants.def", MAX_ARMOR ); + } + diff --git a/armor.h b/armor.h new file mode 100644 index 0000000..f1a0f33 --- /dev/null +++ b/armor.h @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/armor.h $ +// $Revision:: 11 $ +// $Author:: Aldie $ +// $Date:: 7/24/98 5:03p $ +// +// 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/armor.h $ +// +// 11 7/24/98 5:03p Aldie +// Armor never adds, only replaces +// +// 10 6/28/98 4:13p Markd +// simplified setup +// +// 9 6/25/98 8:48p Markd +// Rewrote Item class, added keyed items to triggers, cleaned up item system +// +// 8 6/24/98 1:38p Aldie +// Implementation of inventory system and picking stuff up +// +// 7 6/17/98 1:16a Jimdose +// Moved setOwner to Item. +// Added EV_Item_Pickup +// +// 6 6/15/98 3:36p Aldie +// Updated armor for maxout values +// +// 5 6/05/98 2:45p Aldie +// New version of armor +// +// 3 10/27/97 2:48p Jimdose +// +// 2 9/26/97 5:30p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Standard armor that prevents a percentage of damage per hit. +// + +#ifndef __ARMOR_H__ +#define __ARMOR_H__ + +#include "item.h" + +#define MAX_ARMOR 100 + +class EXPORT_FROM_DLL Armor : public Item + { + protected: + + virtual void Setup( const char *model, int amount ); + virtual void Add( int amount ); + public: + CLASS_PROTOTYPE( Armor ); + Armor( ); + virtual qboolean Pickupable( Entity *other ); + }; + +class EXPORT_FROM_DLL RiotHelmet : public Armor + { + public: + CLASS_PROTOTYPE( RiotHelmet ); + RiotHelmet(); + }; + +class EXPORT_FROM_DLL FlakJacket : public Armor + { + public: + CLASS_PROTOTYPE( FlakJacket ); + FlakJacket(); + }; + +class EXPORT_FROM_DLL FlakPants : public Armor + { + public: + CLASS_PROTOTYPE( FlakPants ); + FlakPants(); + }; + +#endif /* armor.h */ diff --git a/assaultrifle.cpp b/assaultrifle.cpp new file mode 100644 index 0000000..6de995a --- /dev/null +++ b/assaultrifle.cpp @@ -0,0 +1,151 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/assaultrifle.cpp $ +// $Revision:: 33 $ +// $Author:: Markd $ +// $Date:: 11/15/98 9:12p $ +// +// Copyright (C) 1998 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/assaultrifle.cpp $ +// +// 33 11/15/98 9:12p Markd +// Put in more precaching for models and sprites +// +// 32 11/13/98 3:30p Markd +// put in more precaching on weapons +// +// 31 10/05/98 10:18p Aldie +// Covnverted over to new silencer methods +// +// 30 8/29/98 5:26p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 29 8/19/98 6:37p Markd +// Moved Assault rifle tracer into def file +// +// 28 8/06/98 10:53p Aldie +// Added weapon tweaks and kickback. Also modified blast radius damage and +// rocket jumping. +// +// 27 8/04/98 11:57a Aldie +// Increased spread +// +// 26 8/01/98 3:24p Aldie +// Added server effects flag for specific weapons +// +// 25 8/01/98 3:03p Aldie +// Client side muzzle flash (dynamic light) +// +// 24 7/26/98 4:06p Aldie +// +// 23 7/26/98 3:59p Aldie +// Less muzzleflashes +// +// 22 7/22/98 10:40p Aldie +// Fixed tracers +// +// 21 7/22/98 9:57p Markd +// Defined weapon type +// +// 20 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 19 7/12/98 5:48p Jimdose +// changed timing on tracers to once every 5 frames +// +// 18 7/12/98 5:37p Jimdose +// Recoded the tracer fire based on framenum. Frametime was being used as if +// it had millisecond precision. +// +// 17 6/19/98 9:29p Jimdose +// Moved gun orientation code to Weapon +// +// 16 6/18/98 3:56p Aldie +// Made a tracer for the assaultrifle. +// +// 15 6/15/98 9:08p Aldie +// Added SilencedBullet class for silencers +// +// 14 6/10/98 2:10p Aldie +// Updated damage function. +// +// 13 6/08/98 7:21p Aldie +// Updated attack time +// +// 12 4/20/98 1:56p Markd +// SINED decelration is now in def file +// +// 11 4/18/98 3:08p Markd +// Changed view weapon naming convention +// +// 10 4/09/98 3:29p Jimdose +// Removed sound from shoot since anim plays it +// +// 9 4/07/98 6:42p Jimdose +// Rewrote weapon code. +// Added order to rank +// +// 8 4/05/98 2:58a Jimdose +// changed attack time (again) +// +// 7 4/04/98 6:01p Jimdose +// Changed attack time +// +// 6 4/02/98 4:20p Jimdose +// Tweaked for DM +// +// 5 3/30/98 9:54p Jimdose +// Changed location of .def files +// +// 4 3/30/98 2:34p Jimdose +// Moved firing to BulletWeapon to make more general +// Added Ammo +// Added world models +// +// 3 3/27/98 11:04p Jimdose +// added muzzle flash +// +// 2 3/27/98 6:34p Jimdose +// created file +// +// DESCRIPTION: +// Assault rifle +// + +#include "g_local.h" +#include "assaultrifle.h" + +CLASS_DECLARATION( BulletWeapon, AssaultRifle, "weapon_assaultrifle" ); + +ResponseDef AssaultRifle::Responses[] = + { + { &EV_Weapon_Shoot, ( Response )AssaultRifle::Shoot }, + { NULL, NULL } + }; + +AssaultRifle::AssaultRifle() + { + SetModels( "asrifle.def", "view_asrifle.def" ); + SetAmmo( "Bullet10mm", 1, 30 ); + SetRank( 40, 40 ); + SetType( WEAPON_2HANDED_LO ); + modelIndex( "10mm.def" ); + modelIndex( "sprites/gunblast.spr" ); + modelIndex( "shell.def" ); + silenced = true; + } + +void AssaultRifle::Shoot + ( + Event *ev + ) + + { + FireBullets( 1, "120 120 120", 8, 14, DAMAGE_BULLET, MOD_ASSRIFLE, false ); + NextAttack( 0 ); + } diff --git a/assaultrifle.h b/assaultrifle.h new file mode 100644 index 0000000..ef6afbf --- /dev/null +++ b/assaultrifle.h @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/assaultrifle.h $ +// $Revision:: 7 $ +// $Author:: Aldie $ +// $Date:: 10/05/98 10:38p $ +// +// Copyright (C) 1998 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/assaultrifle.h $ +// +// 7 10/05/98 10:38p Aldie +// Converted over to new silencer methods +// +// 6 7/22/98 10:41p Aldie +// Fixed tracers +// +// 5 6/18/98 3:57p Aldie +// Made a fire tracer function. +// +// 4 6/15/98 9:11p Aldie +// Added SilencedBullet calss for silencer +// +// 3 3/30/98 2:35p Jimdose +// Changed from subclass of Magnum to subclass of BulletWeapon +// +// 2 3/27/98 6:36p Jimdose +// Created file +// +// DESCRIPTION: +// Assault rifle +// + +#ifndef __ASSAULTRIFLE_H__ +#define __ASSAULTRIFLE_H__ + +#include "g_local.h" +#include "item.h" +#include "weapon.h" +#include "bullet.h" + +class EXPORT_FROM_DLL AssaultRifle : public BulletWeapon + { + public: + CLASS_PROTOTYPE( AssaultRifle ); + + AssaultRifle::AssaultRifle(); + virtual void Shoot( Event *ev ); + }; + +#endif /* assaultrifle.h */ diff --git a/bacrodai.cpp b/bacrodai.cpp new file mode 100644 index 0000000..cce86c6 --- /dev/null +++ b/bacrodai.cpp @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/bacrodai.cpp $ +// $Revision:: 3 $ +// $Author:: Markd $ +// $Date:: 10/22/98 7:57p $ +// +// Copyright (C) 1998 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/bacrodai.cpp $ +// +// 3 10/22/98 7:57p Markd +// put in proper pre-caching in all the classes +// +// 2 10/20/98 3:25a Markd +// first time +// +// 1 10/20/98 3:10a Markd +// +// DESCRIPTION: +// Bacrodai +// + +#include "g_local.h" +#include "actor.h" +#include "bacrodai.h" + +CLASS_DECLARATION( Actor, Bacrodai, "monster_bachrodai" ); + +Event EV_Bacrodai_SpawnBat( "spawnbat" ); + +ResponseDef Bacrodai::Responses[] = + { + { &EV_Bacrodai_SpawnBat, ( Response )Bacrodai::SpawnBat }, + { NULL, NULL } + }; + +Bacrodai::Bacrodai() + { + setModel( "bacrodai.def" ); + modelIndex( "bat.def" ); + } + +void Bacrodai::SpawnBat + ( + Event *ev + ) + + { + Actor * ent; + str text; + Vector pos; + + if ( !currentEnemy ) + return; + + pos = centroid - ( Vector( orientation[ 0 ] ) * 48 ); + + // create a new entity + G_InitSpawnArguments(); + + G_SetSpawnArg( "model", "bat.def" ); + + text = va( "%f %f %f", pos[ 0 ], pos[ 1 ], pos[ 2 ] ); + G_SetSpawnArg( "origin", text.c_str() ); + + ent = new Actor; + + G_InitSpawnArguments(); + + ent->MakeEnemy( currentEnemy, true ); + } diff --git a/bacrodai.h b/bacrodai.h new file mode 100644 index 0000000..28f336e --- /dev/null +++ b/bacrodai.h @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/bacrodai.h $ +// $Revision:: 2 $ +// $Author:: Markd $ +// $Date:: 10/20/98 3:25a $ +// +// Copyright (C) 1998 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/bacrodai.h $ +// +// 2 10/20/98 3:25a Markd +// first time +// +// 1 10/20/98 3:10a Markd +// +// DESCRIPTION: +// Bacrodai +// + +#ifndef __BACRODAI_H__ +#define __BACRODAI_H__ + +#include "g_local.h" +#include "actor.h" + +class EXPORT_FROM_DLL Bacrodai : public Actor + { + public: + CLASS_PROTOTYPE( Bacrodai ); + + Bacrodai::Bacrodai(); + virtual void SpawnBat( Event *ev ); + }; + +#endif /* assaultrifle.h */ diff --git a/behavior.cpp b/behavior.cpp new file mode 100644 index 0000000..47c3b42 --- /dev/null +++ b/behavior.cpp @@ -0,0 +1,5186 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/behavior.cpp $ +// $Revision:: 117 $ +// $Author:: Markd $ +// $Date:: 11/20/98 7:17p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/behavior.cpp $ +// +// 117 11/20/98 7:17p Markd +// Fixed Hide behavior +// +// 116 11/18/98 8:53p Markd +// Made nearestnode check for one without bounding box for thrall +// +// 115 11/18/98 7:47p Markd +// made it so FindNearEnemy doesn't use bounding box mainly for THrall +// +// 114 11/15/98 8:02p Jimdose +// Swimming monsters no longer choose random directions that are in air +// Flying monsters no longer choose random directions that are in water +// Actors using FindEnemy no longer get stuck when the nearest node to them is +// the same as the nearest node to the enemy. +// +// 113 11/15/98 1:24a Jimdose +// monsters don't play snd_sightenemy now unless DoAction( "sightenemy" ) +// succeeds. +// +// 112 11/13/98 3:29p Markd +// Fixed some ChooseRandomDirection stuff +// +// 111 11/07/98 8:14p Jimdose +// Made it so Hide and FleeAndRemove ensure the guy either runs away or +// crouches down +// +// 110 10/27/98 7:48p Jimdose +// Added PlayAnimSeekEnemy +// +// 109 10/27/98 3:52a Markd +// put in state = 0 if can't resolve FindEnemy +// +// 108 10/27/98 12:42a Jimdose +// added animprefix to AimAndShoot +// +// 107 10/26/98 2:18p Markd +// Put in last_jump_time support +// +// 106 10/25/98 4:56a Jimdose +// Fixed bug with chase where guys would do a spin at the end of a path +// Changed wait time in opendoor +// made FindEnemy check if the enemy has moved far before doing another search +// +// 105 10/23/98 4:44a Markd +// made jump early exit if no land animation +// +// 104 10/23/98 12:30a Markd +// Fixed up Flee behavior +// +// 103 10/22/98 11:36p Jimdose +// Added GetCloseToEnemy +// Changed FindEnemy's seektime +// +// 102 10/20/98 11:29p Markd +// guys won't get stuck aiming anymore +// +// 101 10/20/98 4:05a Markd +// made snd_idle more frequent in wander +// +// 100 10/20/98 3:52a Markd +// Added idle chatter to Wander +// +// 99 10/20/98 2:37a Markd +// Put in ClearEnemies and JumpTo modifications +// +// 98 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 97 10/18/98 4:11p Markd +// Made FindEnemy not use FindClosestSightNode +// +// 96 10/18/98 12:34a Markd +// Added randomness to AimAndShoot and AimAndMelee +// +// 95 10/17/98 11:01p Markd +// Added code to AimAndShoot +// +// 94 10/17/98 4:43p Markd +// Added curious time and xydist to Investigate +// +// 93 10/17/98 4:05p Markd +// replaced OpenDOor initial trace with vec_zero's again +// +// 92 10/17/98 3:30p Markd +// Fixed OpenDoor code not taking into account bounding boxes +// +// 91 10/17/98 12:34a Markd +// Commented out warning for the time being +// +// 90 10/16/98 7:20p Markd +// put in numshots for AimAndMelee, also tweaked PlayAnim warning reporting +// +// 89 10/14/98 11:53p Markd +// If PlayAnim doesn't resolve an anim, make sure and post and EndBehavior +// +// 88 10/14/98 9:02p Markd +// Made the Jump behavior more robust, and added landing +// +// 87 10/14/98 5:20p Markd +// Updated Jump behavior +// +// 86 10/14/98 2:17a Markd +// Took out can_melee code +// +// 85 10/14/98 1:27a Markd +// Put in can_melee in FindEnemy +// +// 84 10/13/98 11:13p Markd +// Scaled anim_deltas and fixed closeattacks not waiting long enough to reach +// player +// +// 83 10/13/98 9:11p Markd +// Fixed RandomChooseDirection bug +// +// 82 10/13/98 7:38p Markd +// Created behaviors for Wander, Fly, FLyCloseAttack, and WanderCloseAttack +// +// 81 10/13/98 12:25a Markd +// Fixed swimming creatures AGAIN! +// +// 80 10/10/98 6:08p Markd +// Added FleeAndRemove, FindFlee, fixed max_ainode bug +// +// 79 10/10/98 5:01p Markd +// Changed trace masks to edict->clipmasks +// +// 78 10/10/98 3:27p Markd +// Fixed FindEnemy behavior +// +// 77 10/09/98 11:57p Markd +// Fixed up melee close range attacks +// +// 76 10/06/98 5:25p Markd +// Added ForceAction, hopefully fixed some sightEnemy issues +// +// 75 10/05/98 10:32p Markd +// Made Strafe Attack use a random strafe direction, also tweaked +// SwimCloseAttack +// +// 74 10/05/98 4:36p Markd +// Clear out seenEnemy +// +// 73 10/05/98 3:04p Markd +// Added miss detection to AimAndShoot +// +// 72 10/05/98 12:29a Jimdose +// moved angledist to q_shared +// +// 71 10/04/98 7:49p Markd +// When done GotoPathNode, play the idle animation if no animation exists at +// the movegoal +// +// 70 10/04/98 5:32p Markd +// Fixed ChooseRandomDirection problems +// +// 69 10/04/98 3:47p Markd +// fixed swimming getting stuck +// +// 68 10/04/98 3:01p Markd +// fixed swimming behavior +// +// 67 10/03/98 7:19p Markd +// Added SwimCloseAttack and Melee behaviors +// +// 66 10/01/98 8:00p Markd +// Added AimAndMelee +// +// 65 10/01/98 4:14p Jimdose +// Improved Aim::Evaluate so that it properly targets partially obscured guys. +// +// 64 9/22/98 4:16a Jimdose +// Made GotoPathNode check new paths every 4 seconds instead of every 20. +// +// 63 9/22/98 1:54a Jimdose +// Completed ShowInfo for all behaviors +// Added StrafeTo +// +// 62 9/18/98 10:56p Jimdose +// Separated steering behaviors into their own file +// Made AimAndShoot exit when enemy is killed while aiming +// +// 61 9/14/98 5:29p Jimdose +// Added SetPathRate to Chase so that actors can choose how often they search +// for a new path. +// NearestNode now requires that you pass in the entity that is going to use +// the path. +// +// 60 8/31/98 7:48p Jimdose +// Removed unused variables from FollowPath +// +// 59 8/31/98 7:46p Jimdose +// Simplified FollowPath +// +// 58 8/29/98 9:39p Jimdose +// Added call info to G_Trace +// +// 57 8/26/98 11:13p Jimdose +// Added StrafeAttack +// +// 56 8/24/98 6:57p Jimdose +// Moved hueristics for path finding to actor +// Modified how closely actors follow paths +// +// 55 8/19/98 8:47p Jimdose +// Added Jump behavior +// +// 54 8/19/98 7:58p Jimdose +// took out debug print +// starting jump node support +// +// 53 8/19/98 4:00p Jimdose +// Made PickupAndThrow exit if it has a NULL pickup_target +// +// 52 8/19/98 2:31p Jimdose +// Changed Chase so that if the target is an entity, it ends when it touches +// that entity. +// +// 51 8/18/98 10:00p Jimdose +// Actors follow paths tighter when near doors +// +// 50 8/15/98 11:18p Jimdose +// Extended reach for opening doors +// +// 49 8/14/98 8:30p Jimdose +// TurnTo now doesn't return false when turning to ents. (need to change +// eventually). +// FindEnemy now searches based on CanShoot rather than CanSee +// +// 48 8/14/98 6:26p Jimdose +// Got rid of decelleration for steering +// Added GoalEnt to GotoPathNode +// Added check for arrival to seek. Reduces running in circles +// +// 47 8/12/98 2:12p Markd +// fixed initialization of bestnode parameter in FindWanderNode +// +// 46 8/10/98 6:51p Aldie +// Modified wander behavior a little to make it less intensive +// +// 45 8/09/98 5:49p Markd +// Rewrote ThrowObjects pickup and throw +// +// 44 8/08/98 8:39p Markd +// fixed throwing bug +// +// 43 8/08/98 8:41p Jimdose +// Fixed oscillating problem in Aim +// +// 42 8/08/98 8:24p Markd +// working on pickupandthrow behavior +// +// 41 8/07/98 8:47p Markd +// working on pickupandthrow +// +// 40 8/07/98 6:01p Jimdose +// Rewrote AimAndShoot +// +// 39 8/06/98 6:57p Jimdose +// Removed FireFromCover (now done in script) +// Added SetArgs to AimAndShoot +// +// 38 8/05/98 7:18p Jimdose +// Renamed states +// +// 37 8/03/98 7:58p Aldie +// Added a first attempt at wander behavior +// +// 36 7/26/98 11:43a Jimdose +// Tweaked hiding behavior +// +// 35 7/26/98 6:39a Jimdose +// Avoiding occupied nodes now works +// +// 34 7/26/98 3:48a Jimdose +// Modified aim based on skill +// +// 33 7/26/98 2:45a Jimdose +// Changed pathway_t structure +// +// 32 7/25/98 2:09a Jimdose +// FindCoverNode now uses the closest cover node to search from, rather than +// choosing a random node and letting the search algorithm find a closer one. +// +// 31 7/22/98 10:51p Jimdose +// Added repel behavior +// +// 30 7/21/98 4:14p Jimdose +// disabled the - operator on Vectors +// +// 29 7/19/98 9:19p Jimdose +// Fixed bug in FindCoverNode where the actor would try searching for a path +// to every cover node in the level. +// +// 28 7/08/98 9:00p Jimdose +// Made OpenDoor script callable +// +// 27 7/06/98 1:06p Jimdose +// working on ai +// +// 26 6/30/98 6:03p Jimdose +// Chase and GotoPathNode now can use vectors for goals +// Investigate no longer needs currentEnemy to be set +// +// 25 6/25/98 8:11p Jimdose +// Changed Idle stuff +// +// 24 6/24/98 4:26p Jimdose +// Fixed bugs in TurnTo +// +// 23 6/17/98 9:59p Jimdose +// Changed path radius +// +// 22 6/17/98 3:03p Markd +// Changed NumArgs back to previous behavior +// +// 21 6/17/98 1:19a Jimdose +// Removed TargetEnemies. Moved it back into actor +// Working on weapon firing and sighting enemies +// +// 20 6/15/98 10:47p Jimdose +// Added Hide, and CoverFire +// +// 19 6/13/98 8:23p Jimdose +// Added FindCover +// +// 18 6/11/98 12:44a Jimdose +// behaviors now get info from the script at startup +// +// 17 6/10/98 10:25p Jimdose +// Added priority based state system +// +// 16 6/09/98 5:34p Jimdose +// made flee better at finding a random node. +// +// 15 6/09/98 4:18p Jimdose +// worked on ai +// +// 14 6/04/98 10:48p Jimdose +// Fixed a bunch of things that got broken just in time for E3. Paths and +// scripting actually work now. +// +// 13 6/03/98 5:43p Jimdose +// Fixed spelling of behavior. :) +// +// 12 5/27/98 7:12a Jimdose +// ai ai ai +// +// 11 5/27/98 6:39a Jimdose +// working on ai +// +// 10 5/27/98 5:11a Jimdose +// working on ai +// +// 9 5/25/98 5:31p Jimdose +// Pathnodes are no longer a subclass of Entity. This was done to save on +// edicts +// +// 8 5/25/98 1:06a Jimdose +// Added chatter +// +// 7 5/24/98 1:02a Jimdose +// added Investigate +// +// 6 5/23/98 6:27p Jimdose +// improved steering code +// +// 5 5/22/98 9:45p Jimdose +// Disabled debug lines for forces +// +// 4 5/22/98 9:36p Jimdose +// AI Scripting works again (somewhat) +// +// 3 5/20/98 6:36p Jimdose +// Working on ai +// +// 2 5/18/98 8:15p Jimdose +// Created file +// +// DESCRIPTION: +// Behaviors used by the AI. +// + +#include "g_local.h" +#include "behavior.h" +#include "actor.h" +#include "doors.h" +#include "object.h" + +Event EV_Behavior_Args( "args" ); +Event EV_Behavior_AnimDone( "animdone" ); + +/**************************************************************************** + + Behavior Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Listener, Behavior, NULL ); + +ResponseDef Behavior::Responses[] = + { + { NULL, NULL } + }; + +Behavior::Behavior() + { + } + +void Behavior::ShowInfo + ( + Actor &self + ) + + { + if ( movegoal ) + { + gi.printf( "movegoal: ( %f, %f, %f ) - '%s'\n", + movegoal->worldorigin.x, movegoal->worldorigin.y, movegoal->worldorigin.z, movegoal->targetname.c_str() ); + } + else + { + gi.printf( "movegoal: NULL\n" ); + } + } + +void Behavior::Begin + ( + Actor &self + ) + + { + } + +qboolean Behavior::Evaluate + ( + Actor &self + ) + + { + return false; + } + +void Behavior::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + Idle Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Idle, NULL ); + +ResponseDef Idle::Responses[] = + { + { &EV_Behavior_Args, ( Response )Idle::SetArgs }, + { NULL, NULL } + }; + +void Idle::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +void Idle::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nnexttwitch : %f\n", nexttwitch ); + gi.printf( "anim : %s\n", anim.c_str() ); + } + +void Idle::Begin + ( + Actor &self + ) + + { + self.currentEnemy = NULL; + self.seenEnemy = false; + nexttwitch = level.time + 10 + G_Random( 30 ); + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + } + +qboolean Idle::Evaluate + ( + Actor &self + ) + + { + if ( self.currentEnemy ) + { + if ( self.DoAction( "sightenemy" ) ) + { + self.seenEnemy = true; + self.Chatter( "snd_sightenemy", 5 ); + } + else + { + self.currentEnemy = NULL; + } + return true; + } + + if ( nexttwitch < level.time ) + { + self.chattime += 10; + self.DoAction( "twitch" ); + return true; + } + else + { + self.Chatter( "snd_idle", 1 ); + } + + return true; + } + +void Idle::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + Aim Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Aim, NULL ); + +ResponseDef Aim::Responses[] = + { + { NULL, NULL } + }; + +void Aim::SetTarget + ( + Entity *ent + ) + + { + target = ent; + } + +void Aim::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nseek:\n" ); + seek.ShowInfo( self ); + if ( target ) + { + gi.printf( "\ntarget : #%d '%s'\n", target->entnum, target->targetname.c_str() ); + } + else + { + gi.printf( "\ntarget : NULL\n" ); + } + } + +void Aim::Begin + ( + Actor &self + ) + + { + seek.Begin( self ); + } + +qboolean Aim::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector ang; + Vector pos; + + if ( !target ) + { + return false; + } + + // + // get my gun pos + // + pos = self.GunPosition(); + + ang = self.MyGunAngles( pos, false ); + + // + // invert PITCH + // + ang[ PITCH ] = -ang[ PITCH ]; + + ang.AngleVectors( &dir, NULL, NULL ); + + seek.SetTargetPosition( target->centroid ); + seek.SetTargetVelocity( target->velocity ); + seek.SetPosition( self.centroid ); + seek.SetDir( dir ); + seek.SetMaxSpeed( 1400 + skill->value * 600 ); + seek.Evaluate( self ); + if ( ( fabs( seek.steeringforce.y ) > 5 ) && ( self.enemyRange > RANGE_MELEE ) ) + { + seek.steeringforce.y *= 2; + } + + self.Accelerate( seek.steeringforce ); + if ( seek.steeringforce.y < 0.25f ) + { + // dead on + return false; + } + + return true; + } + +void Aim::End + ( + Actor &self + ) + + { + seek.End( self ); + } + +/**************************************************************************** + + FireOnSight Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FireOnSight, NULL ); + +ResponseDef FireOnSight::Responses[] = + { + { &EV_Behavior_Args, ( Response )FireOnSight::SetArgs }, + { NULL, NULL } + }; + +void FireOnSight::SetArgs + ( + Event *ev + ) + + { + if ( ev->NumArgs() > 1 ) + { + anim = ev->GetString( 1 ); + } + } + +void FireOnSight::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nchase:\n" ); + chase.ShowInfo( self ); + + gi.printf( "\naim:\n" ); + aim.ShowInfo( self ); + gi.printf( "\nmode : %d\n", mode ); + gi.printf( "anim : %s\n", anim.c_str() ); + } + +void FireOnSight::Begin + ( + Actor &self + ) + + { + mode = 0; + if ( !anim.length() ) + { + anim = "run"; + } + } + +qboolean FireOnSight::Evaluate + ( + Actor &self + ) + + { + if ( !self.currentEnemy || self.currentEnemy->deadflag || self.currentEnemy->health <= 0 ) + { + return false; + } + + switch( mode ) + { + case 0 : + // Start chasing + self.SetAnim( anim ); + chase.Begin( self ); + mode = 1; + + case 1 : + // Chasing + if ( self.WeaponReady() && self.CanShoot( self.currentEnemy, false ) ) + { + chase.End( self ); + self.SetAnim( "readyfire" ); + aim.Begin( self ); + mode = 2; + break; + } + else + { + self.Chatter( "snd_pursuit", 1 ); + } + + chase.SetTarget( self.currentEnemy ); + chase.Evaluate( self ); + break; + + case 2 : + // Aiming + aim.SetTarget( self.currentEnemy ); + aim.Evaluate( self ); + + if ( self.WeaponReady() && self.CanShoot( self.currentEnemy, true ) ) + { + self.Chatter( "snd_inmysights", 5 ); + self.SetAnim( "fire" ); + mode = 3; + } + else if ( !self.WeaponReady() || !self.CanShoot( self.currentEnemy, false ) ) + { + aim.End( self ); + mode = 0; + break; + } + break; + + case 3 : + // Fire + aim.SetTarget( self.currentEnemy ); + aim.Evaluate( self ); + if ( !self.CanShoot( self.currentEnemy, true ) ) + { + self.SetAnim( "aim" ); + mode = 2; + } + else + { + self.Chatter( "snd_attacktaunt", 4 ); + } + break; + } + + return true; + } + +void FireOnSight::End + ( + Actor &self + ) + + { + chase.End( self ); + aim.End( self ); + } + +/**************************************************************************** + + TurnTo Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, TurnTo, NULL ); + +ResponseDef TurnTo::Responses[] = + { + { NULL, NULL } + }; + +TurnTo::TurnTo() + { + dir = Vector( 1, 0, 0 ); + mode = 0; + ent = NULL; + yaw = 0; + } + +void TurnTo::SetDirection + ( + float yaw + ) + + { + Vector ang; + + ang = Vector( 0, yaw, 0 ); + this->yaw = anglemod( yaw ); + ang.AngleVectors( &dir, NULL, NULL ); + mode = 1; + } + +void TurnTo::SetTarget + ( + Entity *ent + ) + + { + this->ent = ent; + mode = 2; + } + +void TurnTo::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nseek:\n" ); + seek.ShowInfo( self ); + + if ( ent ) + { + gi.printf( "\nent: #%d '%s'\n", ent->entnum, ent->targetname.c_str() ); + } + else + { + gi.printf( "\nent: NULL\n" ); + } + + gi.printf( "dir: ( %f, %f, %f )\n", dir.x, dir.y, dir.z ); + gi.printf( "yaw: %f\n", yaw ); + gi.printf( "mode: %d\n", mode ); + } + +void TurnTo::Begin + ( + Actor &self + ) + + { + seek.Begin( self ); + } + +qboolean TurnTo::Evaluate + ( + Actor &self + ) + + { + Vector delta; + float ang; + + switch( mode ) + { + case 1 : + ang = angledist( yaw - self.angles.yaw() ); + if ( fabs( ang ) < 1 ) + { + self.Accelerate( Vector( 0, ang, 0 ) ); + return false; + } + + seek.SetTargetPosition( self.worldorigin + dir ); + seek.SetTargetVelocity( vec_zero ); + break; + + case 2 : + if ( !ent ) + { + return false; + } + + delta = ent->worldorigin - self.worldorigin; + yaw = delta.toYaw(); + //if ( self.angles.yaw() == yaw ) + // { + // return false; + // } + + seek.SetTargetPosition( ent->worldorigin ); + seek.SetTargetVelocity( vec_zero ); + break; + + default : + return false; + } + + seek.SetPosition( self.worldorigin ); + seek.SetDir( self.movedir ); + seek.SetMaxSpeed( self.movespeed ); + seek.Evaluate( self ); + //seek.DrawForces(); + + self.Accelerate( seek.steeringforce ); + + return true; + } + +void TurnTo::End + ( + Actor &self + ) + + { + seek.End( self ); + } + +/**************************************************************************** + + GotoPathNode Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, GotoPathNode, NULL ); + +ResponseDef GotoPathNode::Responses[] = + { + { &EV_Behavior_Args, ( Response )GotoPathNode::SetArgs }, + { NULL, NULL } + }; + +GotoPathNode::GotoPathNode() + { + usevec = false; + movegoal = NULL; + goal = vec_zero; + goalent = NULL; + } + +void GotoPathNode::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + + if ( ev->IsVectorAt( 3 ) ) + { + goal = ev->GetVector( 3 ); + usevec = true; + } + else + { + usevec = false; + movegoal = AI_FindNode( ev->GetString( 3 ) ); + if ( !movegoal ) + { + goalent = ev->GetEntity( 3 ); + } + } + } + +void GotoPathNode::SetGoal + ( + PathNode *node + ) + + { + usevec = false; + movegoal = node; + } + +void GotoPathNode::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nturnto:\n" ); + turnto.ShowInfo( self ); + + gi.printf( "\nchase:\n" ); + chase.ShowInfo( self ); + + gi.printf( "\nstate: %d\n", state ); + gi.printf( "usevec: %d\n", usevec ); + gi.printf( "time: %f\n", time ); + gi.printf( "anim: %s\n", anim.c_str() ); + + if ( goalent ) + { + gi.printf( "\ngoalent: #%d '%s'\n", goalent->entnum, goalent->targetname.c_str() ); + } + else + { + gi.printf( "\ngoalent: NULL\n" ); + } + + gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z ); + } + +void GotoPathNode::Begin + ( + Actor &self + ) + + { + state = 0; + chase.Begin( self ); + turnto.Begin( self ); + if ( goalent ) + { + chase.SetTarget( goalent ); + } + else if ( movegoal ) + { + chase.SetGoal( movegoal ); + } + else + { + chase.SetGoalPos( goal ); + } + + // don't check for new paths as often + chase.SetPathRate( 4 ); + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + } + +qboolean GotoPathNode::Evaluate + ( + Actor &self + ) + + { + float yaw; + + if ( !usevec && !goalent && !movegoal ) + { + return false; + } + + switch( state ) + { + case 0 : + if ( chase.Evaluate( self ) ) + { + break; + } + + state = 1; + self.SetAnim( "idle" ); + + // cascade down to case 1 + case 1 : + if ( !movegoal ) + { + return false; + } + + if ( movegoal->setangles ) + { + yaw = movegoal->worldangles.yaw(); + turnto.SetDirection( yaw ); + if ( turnto.Evaluate( self ) ) + { + break; + } + } + + if ( movegoal->animname == "" ) + { + self.SetAnim( "idle" ); + return false; + } + + self.SetAnim( movegoal->animname, EV_Actor_FinishedBehavior ); + state = 2; + break; + + case 2 : + break; + } + + return true; + } + +void GotoPathNode::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + Investigate Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Investigate, NULL ); + +ResponseDef Investigate::Responses[] = + { + { &EV_Behavior_Args, ( Response )Investigate::SetArgs }, + { NULL, NULL } + }; + +void Investigate::SetArgs + ( + Event *ev + ) + + { + Entity *ent; + Actor *self; + + ent = ev->GetEntity( 1 ); + if ( ent && ent->isSubclassOf( Actor ) ) + { + self = ( Actor * )ent; + } + + anim = ev->GetString( 2 ); + goal = ev->GetVector( 3 ); + } + +void Investigate::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nchase:\n" ); + chase.ShowInfo( self ); + gi.printf( "\nanim: %s\n", anim.c_str() ); + gi.printf( "curioustime: %f\n", curioustime ); + gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z ); + } + +void Investigate::Begin + ( + Actor &self + ) + + { + // + // we are only interested for about 10 seconds, if we can't get there, lets go back to what we were doing + // + curioustime = level.time + 10; + self.Chatter( "snd_investigate", 10 ); + chase.Begin( self ); + chase.SetGoalPos( goal ); + + // Don't allow guys to change their anim if we're already close enough to the goal + if ( !Done( self ) && anim.length() ) + { + self.SetAnim( anim ); + } + } + +qboolean Investigate::Done + ( + Actor &self + ) + + { + Vector delta; + float xydist; + + if ( curioustime < level.time ) + { + return true; + } + + if ( self.CanSeeEnemyFrom( self.worldorigin ) ) + { + return true; + } + + if ( self.lastmove == STEPMOVE_STUCK ) + { + return true; + } + delta = goal - self.worldorigin; + // + // get rid of Z variance + // + delta[ 2 ] = 0; + xydist = delta.length(); + if ( xydist < 100 ) + { + return true; + } + + return false; + } + +qboolean Investigate::Evaluate + ( + Actor &self + ) + + { + if ( Done( self ) || !chase.Evaluate( self ) ) + { + return false; + } + + return true; + } + +void Investigate::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + Flee Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Flee, NULL ); + +ResponseDef Flee::Responses[] = + { + { &EV_Behavior_Args, ( Response )Flee::SetArgs }, + { NULL, NULL } + }; + +void Flee::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +void Flee::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nfollow:\n" ); + follow.ShowInfo( self ); + + if ( path ) + { + gi.printf( "\npath : ( %f, %f, %f ) to ( %f, %f, %f )\n", + path->Start()->worldorigin.x, path->Start()->worldorigin.y, path->Start()->worldorigin.z, + path->End()->worldorigin.x, path->End()->worldorigin.y, path->End()->worldorigin.z ); + } + else + { + gi.printf( "\npath : NULL\n" ); + } + + gi.printf( "\navoid:\n" ); + avoid.ShowInfo( self ); + + gi.printf( "\navoidtime: %f\n", avoidtime ); + gi.printf( "anim: %s\n", anim.c_str() ); + } + +void Flee::Begin + ( + Actor &self + ) + + { + follow.Begin( self ); + avoid.AvoidWalls( false ); + avoid.Begin( self ); + avoidtime = 0; + + path = NULL; + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + } + +qboolean Flee::Evaluate + ( + Actor &self + ) + + { + PathNode *node; + int i; + + self.Chatter( "snd_panic", 3 ); + + if ( path && follow.DoneWithPath( self ) ) + { + path = NULL; + + if ( !self.currentEnemy || !self.CanSee( self.currentEnemy ) ) + { + return false; + } + } + + if ( !path ) + { + for( i = 0; i < 5; i++ ) + { + node = AI_GetNode( ( int )G_Random( ai_maxnode + 1 ) ); + if ( node ) + { + break; + } + } + + if ( node ) + { + path = follow.SetPath( self, self.worldorigin, node->worldorigin ); + } + else + { + return false; + } + } + + follow.SetPosition( self.worldorigin ); + follow.SetDir( self.movedir ); + follow.SetMaxSpeed( self.movespeed ); + follow.Evaluate( self ); + + if ( avoidtime < level.time ) + { + avoidtime = level.time + 0.4; + + avoid.SetMaxSpeed( self.movespeed ); + avoid.SetPosition( self.worldorigin ); + avoid.SetDir( self.movedir ); + avoid.Evaluate( self ); + + follow.steeringforce += avoid.steeringforce; + } + + self.Accelerate( follow.steeringforce ); + + return true; + } + +void Flee::End + ( + Actor &self + ) + + { + avoid.End( self ); + follow.End( self ); + path = NULL; + } + +/**************************************************************************** + + OpenDoor Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, OpenDoor, NULL ); + +ResponseDef OpenDoor::Responses[] = + { + { NULL, NULL } + }; + +OpenDoor::OpenDoor() + { + usedir = false; + } + +void OpenDoor::SetArgs + ( + Event *ev + ) + + { + if ( ev->NumArgs() > 1 ) + { + dir = ev->GetVector( 2 ); + //usedir = true; + } + } + +void OpenDoor::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\ntime: %f\n", time ); + gi.printf( "endtime: %f\n", endtime ); + gi.printf( "usedir: %d\n", usedir ); + gi.printf( "dir: ( %f, %f, %f )\n", dir.x, dir.y, dir.z ); + } + +void OpenDoor::Begin + ( + Actor &self + ) + + { + Event *e; + trace_t trace; + Entity *ent; + Vector pos; + Vector end; + + endtime = 0; + + pos = self.worldorigin + self.eyeposition; + if ( usedir ) + { + end = pos + dir; + } + else + { + end = pos + Vector( self.orientation[ 0 ] ) * 64; + } + + trace = G_Trace( pos, vec_zero, vec_zero, end, &self, self.edict->clipmask, "OpenDoor 1" ); + + ent = trace.ent->entity; + if ( ent && ent->isSubclassOf( Door ) ) + { + self.SetAnim( "idle" ); + + time = level.time + 0.1; + endtime = time + 1; + + e = new Event( EV_Use ); + e->AddEntity( &self ); + ent->ProcessEvent( e ); + } + } + +qboolean OpenDoor::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector pos; + + if ( level.time > endtime ) + { + return false; + } + + if ( time < level.time ) + { + pos = self.worldorigin + self.eyeposition; + trace = G_Trace( pos, self.mins, self.maxs, pos + Vector( self.orientation[ 0 ] ) * 32, &self, self.edict->clipmask, "OpenDoor 2" ); + if ( trace.fraction == 1 ) + { + return false; + } + } + + return true; + } + +void OpenDoor::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + PlayAnim Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, PlayAnim, NULL ); + +ResponseDef PlayAnim::Responses[] = + { + { &EV_Behavior_Args, ( Response )PlayAnim::SetArgs }, + { NULL, NULL } + }; + +void PlayAnim::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +void PlayAnim::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nanim: %s\n", anim.c_str() ); + } + +void PlayAnim::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + if ( !self.SetAnim( anim, EV_Actor_FinishedBehavior ) ) + { + //warning( "Begin", "%s does not exist for %s.", anim.c_str(), self.targetname.c_str() ); + self.PostEvent( EV_Actor_FinishedBehavior, 0 ); + } + } + } + +qboolean PlayAnim::Evaluate + ( + Actor &self + ) + + { + return true; + } + +void PlayAnim::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + Wander Class Definition + +****************************************************************************/ +/* +CLASS_DECLARATION( Behavior, Wander, NULL ); + +ResponseDef Wander::Responses[] = + { + { &EV_Behavior_Args, ( Response )Wander::SetArgs }, + { NULL, NULL } + }; + +void Wander::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + maxdistance = ev->GetFloat( 3 ); + maxdistance *= maxdistance; + } + +PathNode *Wander::FindWanderNode + ( + Actor &self + ) + + { + int i; + PathNode *bestnode; + PathNode *node; + FindCoverPath find; + Path *path; + Vector delta; + float dist; + Vector pos; + int count = 0; + + pos = self.worldorigin; + bestnode = NULL; + + for ( i = 0; i < 5; i++ ) + { + node = AI_GetNode( G_Random( ai_maxnode + 1 ) ); + + if ( !node ) + continue; + + delta = node->worldorigin - pos; + dist = delta * delta; + if ( ( dist > 1024 ) && ( dist < maxdistance ) ) + { + bestnode = node; + break; + } + } + + if ( bestnode ) + { + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + path = find.FindPath( self.worldorigin, bestnode->worldorigin ); + if ( path ) + { + node = path->End(); + // Mark node as occupied for a short time + node->occupiedTime = level.time + 1.5; + node->entnum = self.entnum; + chase.SetGoal( node ); + chase.SetPath( path ); + return node; + } + } + return NULL; + } + +void Wander::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nchase:\n" ); + chase.ShowInfo( self ); + + gi.printf( "\nanim: %s\n", anim.c_str() ); + gi.printf( "state: %d\n", state ); + gi.printf( "maxdistance: %f\n", maxdistance ); + } + +void Wander::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "walk"; + } + + movegoal = NULL; + state = 0; + } + +qboolean Wander::Evaluate + ( + Actor &self + ) + + { + if ( !movegoal ) + { + state = 0; + } + + switch( state ) + { + case 0 : + chase.Begin( self ); + movegoal = FindWanderNode( self ); + if ( !movegoal ) + { + return false; + } + if ( anim.length() && ( anim != self.animname ) ) + { + self.SetAnim( anim ); + } + + state = 1; + + case 1 : + if ( chase.Evaluate( self ) ) + { + return true; + } + + // Look for another wander node + state = 0; + chase.End( self ); + return false; + break; + } + return true; + } + +void Wander::End + ( + Actor &self + ) + + { + chase.End( self ); + } +*/ + +/**************************************************************************** + + FindCover Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FindCover, NULL ); + +ResponseDef FindCover::Responses[] = + { + { &EV_Behavior_Args, ( Response )FindCover::SetArgs }, + { NULL, NULL } + }; + +void FindCover::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +PathNode *FindCover::FindCoverNode + ( + Actor &self + ) + + { + int i; + PathNode *bestnode; + float bestdist; + PathNode *desperatebestnode; + float desperatebestdist; + PathNode *node; + FindCoverPath find; + Path *path; + Vector delta; + float dist; + Vector pos; + + pos = self.worldorigin; + + bestnode = NULL; + bestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + desperatebestnode = NULL; + desperatebestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + for( i = 0; i <= ai_maxnode; i++ ) + { + node = AI_GetNode( i ); + if ( node && ( node->nodeflags & ( AI_DUCK | AI_COVER ) ) && + ( ( node->occupiedTime <= level.time ) || ( node->entnum == self.entnum ) ) ) + { + // get the distance squared (faster than getting real distance) + delta = node->worldorigin - pos; + dist = delta * delta; + if ( ( dist < bestdist ) && ( !self.CanSeeEnemyFrom( node->worldorigin ) ||//) )//|| + ( ( node->nodeflags & AI_DUCK ) && !self.CanSeeEnemyFrom( node->worldorigin - Vector( 0, 0, 32 ) ) ) ) ) + { + bestnode = node; + bestdist = dist; + } + else if ( ( dist < desperatebestdist ) && ( desperatebestdist > ( 64 * 64 ) ) ) + { + desperatebestnode = node; + desperatebestdist = dist; + } + } + } + + if ( !bestnode ) + { + bestnode = desperatebestnode; + } + + if ( bestnode ) + { + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + path = find.FindPath( self.worldorigin, bestnode->worldorigin ); + if ( path ) + { + node = path->End(); + + // Mark node as occupied for a short time + node->occupiedTime = level.time + 1.5; + node->entnum = self.entnum; + + chase.SetGoal( node ); + chase.SetPath( path ); + + return node; + } + } + + return NULL; + } + +void FindCover::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nchase:\n" ); + chase.ShowInfo( self ); + + gi.printf( "\nstate: %d\n", state ); + gi.printf( "anim: %s\n", anim.c_str() ); + gi.printf( "nextsearch: %f\n", nextsearch ); + } + +void FindCover::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + movegoal = NULL; + state = 0; + } + +qboolean FindCover::Evaluate + ( + Actor &self + ) + + { + if ( !movegoal ) + { + state = 0; + } + + switch( state ) + { + case 0 : + // Checking for cover + chase.Begin( self ); + movegoal = FindCoverNode( self ); + if ( !movegoal ) + { + // Couldn't find any! + return false; + } + + // Found cover, going to it + if ( anim.length() && ( anim != self.animname ) ) + { + self.SetAnim( anim ); + } + + state = 1; + nextsearch = level.time + 1; + + case 1 : + if ( chase.Evaluate( self ) ) + { + if ( nextsearch < level.time ) + { + state = 0; + } + return true; + } + + // Reached cover + if ( self.CanSeeEnemyFrom( self.worldorigin ) ) + { + state = 0; + } + + if ( movegoal->nodeflags & AI_DUCK ) + { + // ducking + self.SetAnim( "crouch_down" ); + } + else + { + // standing + self.SetAnim( "idle" ); + } + + chase.End( self ); + return false; + break; + } + + return true; + } + +void FindCover::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + FindFlee Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FindFlee, NULL ); + +ResponseDef FindFlee::Responses[] = + { + { &EV_Behavior_Args, ( Response )FindFlee::SetArgs }, + { NULL, NULL } + }; + +void FindFlee::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +PathNode *FindFlee::FindFleeNode + ( + Actor &self + ) + + { + int i; + PathNode *bestnode; + float bestdist; + PathNode *desperatebestnode; + float desperatebestdist; + PathNode *node; + FindFleePath find; + Path *path; + Vector delta; + float dist; + Vector pos; + + pos = self.worldorigin; + + bestnode = NULL; + bestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + desperatebestnode = NULL; + desperatebestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + for( i = 0; i <= ai_maxnode; i++ ) + { + node = AI_GetNode( i ); + if ( node && ( node->nodeflags & AI_FLEE ) && + ( ( node->occupiedTime <= level.time ) || ( node->entnum == self.entnum ) ) ) + { + // get the distance squared (faster than getting real distance) + delta = node->worldorigin - pos; + dist = delta * delta; + if ( ( dist < bestdist ) && !self.CanSeeEnemyFrom( node->worldorigin ) ) + { + bestnode = node; + bestdist = dist; + } + else if ( ( dist < desperatebestdist ) && ( desperatebestdist > ( 64 * 64 ) ) ) + { + desperatebestnode = node; + desperatebestdist = dist; + } + } + } + + if ( !bestnode ) + { + bestnode = desperatebestnode; + } + + if ( bestnode ) + { + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + path = find.FindPath( self.worldorigin, bestnode->worldorigin ); + if ( path ) + { + node = path->End(); + + // Mark node as occupied for a short time + node->occupiedTime = level.time + 1.5; + node->entnum = self.entnum; + + chase.SetGoal( node ); + chase.SetPath( path ); + + return node; + } + } + + return NULL; + } + +void FindFlee::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nchase:\n" ); + chase.ShowInfo( self ); + + gi.printf( "\nstate: %d\n", state ); + gi.printf( "anim: %s\n", anim.c_str() ); + gi.printf( "nextsearch: %f\n", nextsearch ); + } + +void FindFlee::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + movegoal = NULL; + state = 0; + } + +qboolean FindFlee::Evaluate + ( + Actor &self + ) + + { + if ( !movegoal ) + { + state = 0; + } + + switch( state ) + { + case 0 : + // Checking for flee node + chase.Begin( self ); + movegoal = FindFleeNode( self ); + if ( !movegoal ) + { + // Couldn't find any! + return false; + } + + // Found flee node, going to it + if ( anim.length() && ( anim != self.animname ) ) + { + self.SetAnim( anim ); + } + + state = 1; + nextsearch = level.time + 1; + + case 1 : + if ( chase.Evaluate( self ) ) + { + if ( nextsearch < level.time ) + { + state = 0; + } + return true; + } + + // Reached cover + if ( self.CanSeeEnemyFrom( self.worldorigin ) ) + { + state = 0; + } + else + { + // standing + self.SetAnim( "idle" ); + chase.End( self ); + return false; + } + break; + } + + return true; + } + +void FindFlee::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + FindEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FindEnemy, NULL ); + +ResponseDef FindEnemy::Responses[] = + { + { &EV_Behavior_Args, ( Response )FindEnemy::SetArgs }, + { NULL, NULL } + }; + +void FindEnemy::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +void FindEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nchase:\n" ); + chase.ShowInfo( self ); + + gi.printf( "\nstate: %d\n", state ); + gi.printf( "nextsearch: %f\n", nextsearch ); + gi.printf( "anim: %s\n", anim.c_str() ); + } + +void FindEnemy::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + movegoal = NULL; + lastSearchNode = NULL; + state = 0; + } + +PathNode *FindEnemy::FindClosestSightNode + ( + Actor &self + ) + + { + int i; + PathNode *bestnode; + float bestdist; + PathNode *node; + Vector delta; + float dist; + Vector pos; + + if ( self.currentEnemy ) + { + pos = self.currentEnemy->worldorigin; + } + else + { + pos = self.worldorigin; + } + + bestnode = NULL; + bestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + for( i = 0; i <= ai_maxnode; i++ ) + { + node = AI_GetNode( i ); + if ( node && ( ( node->occupiedTime <= level.time ) || ( node->entnum != self.entnum ) ) ) + { + // get the distance squared (faster than getting real distance) + delta = node->worldorigin - pos; + dist = delta * delta; + if ( ( dist < bestdist ) && self.CanSeeFrom( node->worldorigin, self.currentEnemy ) ) + { + bestnode = node; + bestdist = dist; + } + } + } + + return bestnode; + } + +qboolean FindEnemy::Evaluate + ( + Actor &self + ) + + { + if ( !self.currentEnemy ) + { + return false; + } + + if ( nextsearch < level.time ) + { + // check if we should search for the first time + if ( !lastSearchNode ) + { + //gi.dprintf( "%d: %s(%d) first time\n", level.framenum, self.targetname.c_str(), self.entnum ); + state = 0; + } + else + { + // search less often if we're far away + nextsearch = self.DistanceTo( self.currentEnemy ) * ( 1.0 / 512.0 ); + if ( nextsearch < 1 ) + { + nextsearch = 1; + } + nextsearch += level.time; + + // don't search again if our enemy hasn't moved very far + if ( !self.currentEnemy->WithinDistance( lastSearchPos, 256 ) ) + { + //gi.dprintf( "%d: %s(%d) searching again\n", level.framenum, self.targetname.c_str(), self.entnum ); + state = 0; + } + } + } + + switch( state ) + { + case 0 : + // Searching for enemy + chase.Begin( self ); + lastSearchPos = self.currentEnemy->worldorigin; + movegoal = PathManager.NearestNode( lastSearchPos, &self ); + if ( !movegoal ) + { + movegoal = PathManager.NearestNode( lastSearchPos, &self, false ); + } + lastSearchNode = movegoal; + if ( movegoal ) + { + Path *path; + FindEnemyPath find; + PathNode *from; + + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + from = PathManager.NearestNode( self.worldorigin, &self ); + if ( ( from == movegoal ) && ( self.DistanceTo( from->worldorigin ) < 8 ) ) + { + movegoal = NULL; + from = NULL; + } + + if ( from ) + { + path = find.FindPath( from, movegoal ); + if ( path ) + { + chase.SetGoal( movegoal ); + chase.SetPath( path ); + } + else + { + movegoal = NULL; + } + } + } + + if ( !movegoal ) + { + if ( self.CanSee( self.currentEnemy ) || ( !self.currentEnemy->groundentity && !self.waterlevel ) ) + { + chase.SetGoalPos( self.currentEnemy->worldorigin ); + } + else + { + // Couldn't find enemy + // since we can't reach em + // clear out enemy state + // + self.ClearEnemies(); + return false; + } + } + + // Found enemy, going to it + if ( anim.length() && ( anim != self.animname ) ) + { + self.SetAnim( anim ); + } + + state = 1; + + case 1 : + if ( self.CanShoot( self.currentEnemy, false ) ) + { + // Reached enemy + chase.End( self ); + return false; + } + + if ( !chase.Evaluate( self ) ) + { + state = 0; + nextsearch = 0; + } + break; + } + + return true; + } + +void FindEnemy::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + Hide Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Hide, NULL ); + +ResponseDef Hide::Responses[] = + { + { &EV_Behavior_Args, ( Response )Hide::SetArgs }, + { NULL, NULL } + }; + +void Hide::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +void Hide::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nhide:\n" ); + hide.ShowInfo( self ); + + gi.printf( "\nanim: %s\n", anim.c_str() ); + gi.printf( "state: %d\n", state ); + gi.printf( "checktime: %f\n", checktime ); + } + +void Hide::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + state = 0; + } + +qboolean Hide::Evaluate + ( + Actor &self + ) + + { + switch( state ) + { + // init run for cover + case 0 : + state = 1; + hide.Begin( self ); + if ( hide.Evaluate( self ) && anim.length() ) + { + self.SetAnim( anim ); + } + else + { + hide.End( self ); + self.SetAnim( "crouch_down" ); + state = 2; + checktime = level.time + 1; + } + break; + + + // running for cover + case 1 : + if ( !hide.Evaluate( self ) ) + { + hide.End( self ); + self.SetAnim( "crouch_down" ); + state = 2; + checktime = level.time + 1; + } + break; + + // wait for a bit + case 2 : + if ( checktime < level.time ) + { + checktime = level.time + 0.5f; + if ( self.CanSeeEnemyFrom( self.worldorigin ) ) + { + hide.Begin( self ); + if ( hide.Evaluate( self ) && anim.length() ) + { + self.SetAnim( anim ); + state = 1; + } + else + { + hide.End( self ); + checktime = level.time + 2; + } + } + } + break; + } + + return true; + } + +void Hide::End + ( + Actor &self + ) + + { + hide.End( self ); + self.SetAnim( "crouch_down" ); + } + +/**************************************************************************** + + FleeAndRemove Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FleeAndRemove, NULL ); + +ResponseDef FleeAndRemove::Responses[] = + { + { &EV_Behavior_Args, ( Response )FleeAndRemove::SetArgs }, + { NULL, NULL } + }; + +void FleeAndRemove::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +void FleeAndRemove::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nfindflee:\n" ); + flee.ShowInfo( self ); + + gi.printf( "\nanim: %s\n", anim.c_str() ); + gi.printf( "state: %d\n", state ); + gi.printf( "checktime: %f\n", checktime ); + } + +void FleeAndRemove::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + state = 0; + } + +qboolean FleeAndRemove::Evaluate + ( + Actor &self + ) + + { + if ( !self.currentEnemy ) + { + return false; + } + + switch( state ) + { + // init run for cover + case 0 : + state = 1; + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + flee.Begin( self ); + + // if we fail on the first try, probably can't flee. + if ( !flee.Evaluate( self ) ) + { + flee.End( self ); + return false; + } + return true; + + // running for cover + case 1 : + if ( !flee.Evaluate( self ) ) + { + flee.End( self ); + state = 2; + checktime = 0; + self.SetAnim( "crouch_down" ); + } + break; + + // wait for a bit + case 2 : + if ( checktime < level.time ) + { + checktime = level.time + 1; + if ( self.CanSeeEnemyFrom( self.worldorigin ) ) + { + state = 0; + } + else + { + self.PostEvent( EV_Remove, 0 ); + } + } + break; + } + + return true; + } + +void FleeAndRemove::End + ( + Actor &self + ) + + { + flee.End( self ); + self.SetAnim( "crouch_down" ); + } + +/**************************************************************************** + + AimAndShoot Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, AimAndShoot, NULL ); + +ResponseDef AimAndShoot::Responses[] = + { + { &EV_Behavior_Args, ( Response )AimAndShoot::SetArgs }, + { &EV_Behavior_AnimDone, ( Response )AimAndShoot::AnimDone }, + { NULL, NULL } + }; + +AimAndShoot::AimAndShoot() + { + maxshots = 1; + numshots = 0; + } + +void AimAndShoot::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\naim:\n" ); + aim.ShowInfo( self ); + + gi.printf( "\nmode: %d\n", mode ); + gi.printf( "maxshots: %d\n", maxshots ); + gi.printf( "numshots: %d\n", numshots ); + gi.printf( "animdone: %d\n", animdone ); + } + +void AimAndShoot::Begin + ( + Actor &self + ) + + { + enemy_health = 0; + mode = 0; + animdone = false; + + readyfireanim = animprefix + "readyfire"; + if ( !self.HasAnim( readyfireanim.c_str() ) ) + { + readyfireanim = ""; + } + + aimanim = animprefix + "aim"; + if ( !self.HasAnim( aimanim.c_str() ) ) + { + aimanim = ""; + } + + fireanim = animprefix + "fire"; + if ( !self.HasAnim( fireanim.c_str() ) ) + { + fireanim = ""; + } + } + +void AimAndShoot::SetMaxShots + ( + int num + ) + + { + maxshots = (num>>1) + G_Random( num ); + } + +void AimAndShoot::SetArgs + ( + Event *ev + ) + + { + SetMaxShots( ev->GetInteger( 2 ) ); + if ( ev->NumArgs() > 2 ) + { + animprefix = ev->GetString( 3 ); + } + } + +void AimAndShoot::AnimDone + ( + Event *ev + ) + + { + animdone = true; + } + +qboolean AimAndShoot::Evaluate + ( + Actor &self + ) + + { + switch( mode ) + { + case 0 : + if ( !self.currentEnemy ) + { + return false; + } + + if ( !self.CanShoot( self.currentEnemy, false ) ) + { + return false; + } + + if ( readyfireanim.length() ) + { + animdone = false; + self.SetAnim( readyfireanim.c_str(), EV_Actor_NotifyBehavior ); + aim.Begin( self ); + mode = 1; + } + else + { + // skip the ready fire animation + animdone = true; + } + + case 1 : + // readying gun + if ( !animdone ) + { + aim.SetTarget( self.currentEnemy ); + aim.Evaluate( self ); + break; + } + // start Aiming + animdone = false; + if ( aimanim.length() ) + { + self.SetAnim( aimanim.c_str() ); + } + // + // save off time, in case we aim for too long + // + aim_time = level.time + 1; + mode = 2; + + case 2 : + // Aiming + if ( !self.currentEnemy ) + { + return false; + } + // + // see if we aimed for too long + // + if ( aim_time < level.time ) + { + return false; + } + + aim.SetTarget( self.currentEnemy ); + aim.Evaluate( self ); + + // don't go into our firing animation until our weapon is ready, and we are on target + if ( self.WeaponReady() && self.currentEnemy && self.CanShoot( self.currentEnemy, true ) ) + { + animdone = false; + self.Chatter( "snd_inmysights", 5 ); + self.SetAnim( fireanim.c_str(), EV_Actor_NotifyBehavior ); + enemy_health = self.currentEnemy->health; + mode = 3; + } + else if ( !self.currentEnemy || self.currentEnemy->deadflag || + ( self.currentEnemy->health <= 0 ) || !self.CanShoot( self.currentEnemy, false ) ) + { + // either our enemy is dead, or we can't shoot the enemy from here + if ( self.CurrentWeapon() ) + { + self.CurrentWeapon()->ForceReload(); + } + return false; + } + break; + + case 3 : + // Fire + aim.SetTarget( self.currentEnemy ); + aim.Evaluate( self ); + if ( animdone ) + { + if ( !self.currentEnemy || ( self.currentEnemy->health < enemy_health ) ) + self.Chatter( "snd_attacktaunt", 4 ); + else + self.Chatter( "snd_missed", 7 ); + + animdone = false; + numshots++; + + if ( ( numshots >= maxshots ) || !self.currentEnemy || self.currentEnemy->deadflag || + ( self.currentEnemy->health <= 0 ) || !self.CanShoot( self.currentEnemy, false ) ) + { + // either we're out of shots, our enemy is dead, or we can't shoot the enemy from here + if ( self.CurrentWeapon() ) + { + self.CurrentWeapon()->ForceReload(); + } + return false; + } + else if ( !self.WeaponReady() || !self.CanShoot( self.currentEnemy, false ) ) + { + // weapon not ready or not aimed at enemy, so just keep trying to get enemy in our sights + if ( aimanim.length() ) + { + self.SetAnim( aimanim.c_str() ); + } + // + // save off time, in case we aim for too long + // + aim_time = level.time + 1; + mode = 2; + } + else + { + // keep firing + self.SetAnim( fireanim.c_str(), EV_Actor_NotifyBehavior ); + enemy_health = self.currentEnemy->health; + } + } + break; + } + + return true; + } + +void AimAndShoot::End + ( + Actor &self + ) + + { + aim.End( self ); + self.SetAnim( "idle" ); + } + +/**************************************************************************** + + AimAndMelee Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, AimAndMelee, NULL ); + +ResponseDef AimAndMelee::Responses[] = + { + { &EV_Behavior_Args, ( Response )AimAndMelee::SetArgs }, + { &EV_Behavior_AnimDone, ( Response )AimAndMelee::AnimDone }, + { NULL, NULL } + }; + +AimAndMelee::AimAndMelee() + { + } + +void AimAndMelee::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\naim:\n" ); + aim.ShowInfo( self ); + + gi.printf( "\nmode: %d\n", mode ); + gi.printf( "maxshots: %d\n", maxshots ); + gi.printf( "numshots: %d\n", numshots ); + gi.printf( "animdone: %d\n", animdone ); + } + +void AimAndMelee::Begin + ( + Actor &self + ) + + { + mode = 0; + numshots = 0; + maxshots = 2 + G_Random( 4 ); + animdone = false; + } + +void AimAndMelee::SetArgs + ( + Event *ev + ) + + { + int num; + + num = ev->GetInteger( 2 ); + maxshots = (num>>1) + G_Random( num ); + } + +void AimAndMelee::AnimDone + ( + Event *ev + ) + + { + animdone = true; + } + +qboolean AimAndMelee::Evaluate + ( + Actor &self + ) + + { + float r; + Vector delta; + + switch( mode ) + { + case 0 : + if ( !self.has_melee || !self.currentEnemy ) + { + return false; + } + + delta = self.centroid - self.currentEnemy->centroid; + r = delta.length(); + if ( r > self.melee_range ) + { + return false; + } + + aim.SetTarget( self.currentEnemy ); + if ( aim.Evaluate( self ) ) + { + break; + } + numshots++; + animdone = false; + // melee + self.SetAnim( "melee", EV_Actor_NotifyBehavior ); + self.Chatter( "snd_attacktaunt", 4 ); + + mode = 1; + + case 1 : + // finish up the attack + if ( animdone ) + { + if ( numshots < maxshots ) + { + mode = 0; + } + else + { + return false; + } + } + break; + } + + return true; + } + +void AimAndMelee::End + ( + Actor &self + ) + + { + aim.End( self ); + self.SetAnim( "idle" ); + } + +/**************************************************************************** + + Melee Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Melee, NULL ); + +ResponseDef Melee::Responses[] = + { + { &EV_Behavior_Args, ( Response )Melee::SetArgs }, + { &EV_Behavior_AnimDone, ( Response )Melee::AnimDone }, + { NULL, NULL } + }; + +Melee::Melee() + { + } + +void Melee::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nmode: %d\n", mode ); + gi.printf( "animdone: %d\n", animdone ); + } + +void Melee::Begin + ( + Actor &self + ) + + { + mode = 0; + animdone = false; + } + +void Melee::SetArgs + ( + Event *ev + ) + + { + } + +void Melee::AnimDone + ( + Event *ev + ) + + { + animdone = true; + } + +qboolean Melee::Evaluate + ( + Actor &self + ) + + { + float r; + Vector delta; + Vector ang; + + switch( mode ) + { + case 0 : + if ( !self.has_melee || !self.currentEnemy ) + { + return false; + } + + delta = self.currentEnemy->worldorigin - self.worldorigin; + r = delta.length(); + if ( r > self.melee_range ) + { + return false; + } + animdone = false; + // melee + ang = delta.toAngles(); + ang[ PITCH ] = -ang[ PITCH ]; + self.setAngles( ang ); + self.SetAnim( "melee", EV_Actor_NotifyBehavior ); + self.Chatter( "snd_attacktaunt", 4 ); + + mode = 1; + + case 1 : + // finsh up the attack + if ( animdone ) + { + return false; + } + break; + } + + return true; + } + +void Melee::End + ( + Actor &self + ) + + { + self.SetAnim( "idle" ); + } + +/**************************************************************************** + + Repel Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Repel, NULL ); + +ResponseDef Repel::Responses[] = + { + { &EV_Behavior_Args, ( Response )Repel::SetArgs }, + { NULL, NULL } + }; + +void Repel::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + movegoal = AI_FindNode( ev->GetString( 3 ) ); + if ( movegoal ) + { + goal = movegoal->worldorigin; + } + speed = ev->GetFloat( 4 ); + } + +void Repel::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nanim: %s\n", anim.c_str() ); + + gi.printf( "dist: %f\n", dist ); + gi.printf( "len: %f\n", len ); + gi.printf( "speed: %f\n", speed ); + gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z ); + gi.printf( "start: ( %f, %f, %f )\n", start.x, start.y, start.z ); + gi.printf( "dir: ( %f, %f, %f )\n", dir.x, dir.y, dir.z ); + } + +void Repel::Begin + ( + Actor &self + ) + + { + start.x = goal.x; + start.y = goal.y; + start.z = self.worldorigin.z; + dir = goal - start; + len = dir.length(); + dir *= 1 / len; + + if ( speed <= 0 ) + { + speed = 32; + } + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + dist = -1; + } + +qboolean Repel::Evaluate + ( + Actor &self + ) + + { + Vector pos; + qboolean done; + float sp; + trace_t trace; + + done = false; + + // this is silly, but it works + if ( dist < 0 ) + { + sp = 0; + } + else if ( dist < 32 ) + { + sp = dist * speed / 32; + } + else if ( ( len - dist ) < 32 ) + { + sp = ( len - dist ) * speed / 32; + } + else + { + sp = speed; + } + + pos = start + dir * ( dist + sp ); + dist = ( pos - start ) * dir; + if ( dist >= (len-1) ) + { + pos = goal; + done = true; + } + else if ( dist < 1 ) + { + dist = 1; + } + + trace = G_Trace( self.worldorigin, self.mins, self.maxs, pos, &self, self.edict->clipmask, "Repel" ); + self.setOrigin( trace.endpos ); + + return !done; + } + +void Repel::End + ( + Actor &self + ) + + { + self.SetAnim( "idle" ); + } + +/**************************************************************************** + + Pickup Behavior Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, PickupAndThrow, NULL ); + +Event EV_PickupAndThrow_Pickup( "pickup" ); +Event EV_PickupAndThrow_Throw( "throw" ); + +ResponseDef PickupAndThrow::Responses[] = + { + { &EV_Behavior_Args, ( Response )PickupAndThrow::SetArgs }, + { &EV_Behavior_AnimDone, ( Response )PickupAndThrow::AnimDone }, + { &EV_PickupAndThrow_Pickup,( Response )PickupAndThrow::Pickup }, + { &EV_PickupAndThrow_Throw,( Response )PickupAndThrow::Throw }, + { NULL, NULL } + }; + +PickupAndThrow::PickupAndThrow() + { + } + +void PickupAndThrow::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\naim:\n" ); + aim.ShowInfo( self ); + + gi.printf( "\nmode: %d\n", mode ); + gi.printf( "animdone: %d\n", animdone ); + + if ( pickup_target ) + { + gi.printf( "\npickup_target: #%d '%s'\n", pickup_target->entnum, pickup_target->targetname.c_str() ); + } + else + { + gi.printf( "\npickup_target: NULL\n" ); + } + } + +void PickupAndThrow::Begin + ( + Actor &self + ) + + { + mode = 0; + animdone = false; + } + +void PickupAndThrow::SetArgs + ( + Event *ev + ) + + { + pickup_target = ev->GetEntity( 2 ); + } + +void PickupAndThrow::AnimDone + ( + Event *ev + ) + + { + animdone = true; + } + +void PickupAndThrow::Pickup + ( + Event *ev + ) + + { + Entity * ent; + Event * e; + + ent = ev->GetEntity( 1 ); + if ( pickup_target ) + { + e = new Event( EV_ThrowObject_Pickup ); + e->AddEntity( ent ); + e->AddString( ev->GetString( 2 ) ); + pickup_target->ProcessEvent( e ); + } + } + +void PickupAndThrow::Throw + ( + Event *ev + ) + + { + Actor * act; + Event * e; + + act = (Actor *)ev->GetEntity( 1 ); + if ( pickup_target ) + { + if ( !act->currentEnemy ) + return; + e = new Event( EV_ThrowObject_Throw ); + e->AddEntity( act ); + e->AddFloat( 500 ); + e->AddEntity( act->currentEnemy ); + e->AddFloat( 1 ); + pickup_target->ProcessEvent( e ); + } + } + +qboolean PickupAndThrow::Evaluate + ( + Actor &self + ) + + { + Event * ev; + + if ( !self.currentEnemy || !pickup_target ) + return false; + + switch( mode ) + { + case 0 : + if ( self.HasAnim( "pickup" ) ) + { + animdone = false; + self.SetAnim( "pickup", EV_Actor_NotifyBehavior ); + mode = 1; + } + else + { + // skip the pickup animation + ev = new Event( EV_PickupAndThrow_Pickup ); + ev->AddEntity( &self ); + ev->AddString( "gun" ); + ProcessEvent( ev ); + animdone = true; + } + + case 1 : + if ( !animdone ) + break; + aim.Begin( self ); + mode = 2; + if ( self.HasAnim( "throw_aim" ) ) + { + self.SetAnim( "throw_aim", EV_Actor_NotifyBehavior ); + } + else + { + self.SetAnim( "idle" ); + } + + case 2 : + // aim towards our target + aim.SetTarget( self.currentEnemy ); + if ( aim.Evaluate( self ) ) + { + break; + } + mode = 3; + + case 3 : + // Throwing + mode = 4; + if ( self.HasAnim( "throw" ) ) + { + animdone = false; + self.SetAnim( "throw", EV_Actor_NotifyBehavior ); + mode = 4; + } + else + { + // skip the pickup animation + ev = new Event( EV_PickupAndThrow_Throw ); + ev->AddEntity( &self ); + ProcessEvent( ev ); + animdone = true; + } + break; + + case 4 : + if ( !animdone ) + break; + return false; + break; + } + + return true; + } + +void PickupAndThrow::End + ( + Actor &self + ) + + { + aim.End( self ); + self.SetAnim( "idle" ); + } + +/**************************************************************************** + + Jump Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Jump, NULL ); + +ResponseDef Jump::Responses[] = + { + { &EV_Behavior_Args, ( Response )Jump::SetArgs }, + { &EV_Behavior_AnimDone, ( Response )Jump::AnimDone }, + { NULL, NULL } + }; + +Jump::Jump() + { + endtime = 0; + speed = 200; + state = 0; + } + +void Jump::AnimDone + ( + Event *ev + ) + + { + animdone = true; + } + +void Jump::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + + // + // see if it is an entity first + // + movegoal = AI_FindNode( ev->GetString( 3 ) ); + if ( movegoal ) + { + goal = movegoal->worldorigin; + } + else + { + Entity *ent; + + ent = ev->GetEntity( 3 ); + if ( ent ) + { + goal = ent->worldorigin; + } + else + { + gi.dprintf("Jump::SetArgs invalid target %s", ev->GetString( 3 ) ); + } + } + + if ( ev->NumArgs() >= 4 ) + speed = ev->GetFloat( 4 ); + else + speed = 0; + } + +void Jump::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nendtime: %f\n", endtime ); + gi.printf( "speed: %f\n", speed ); + gi.printf( "state: %d\n", state ); + gi.printf( "animdone: %d\n", animdone ); + gi.printf( "anim: %s\n", anim.c_str() ); + } + +void Jump::Begin + ( + Actor &self + ) + + { + float traveltime; + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + traveltime = self.JumpTo( goal, speed ); + endtime = traveltime + level.time; + + self.last_jump_time = endtime; + + state = 0; + } + +qboolean Jump::Evaluate + ( + Actor &self + ) + + { + switch( state ) + { + case 0: + state = 1; + // this is here so that we at least hit this function at least once + // this gaves the character the chance to leave the ground, nulling out + // self.groundentity + break; + case 1: + // + // wait for the character to hit the ground + // + if ( self.groundentity ) + { + state = 2; + // + // if we have an anim, we go to state 3 + // + if ( self.HasAnim( "land" ) ) + { + animdone = false; + self.SetAnim( "land", EV_Actor_NotifyBehavior ); + state = 3; + } + else + { + return false; + } + } + break; + case 2: + // + // we are on the ground and waiting to timeout + // + if ( level.time > endtime ) + return false; + break; + case 3: + // + // we are on the ground and waiting for our landing animation to finish + // + if ( animdone ) + { + return false; + } + break; + } + + return true; + } + +void Jump::End + ( + Actor &self + ) + + { + //turn.End( self ); + self.SetAnim( "idle" ); + } + +/**************************************************************************** + + StrafeAttack Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, StrafeAttack, NULL ); + +ResponseDef StrafeAttack::Responses[] = + { + { NULL, NULL } + }; + +void StrafeAttack::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nturn:\n" ); + turn.ShowInfo( self ); + + gi.printf( "\nstate: %d\n", state ); + } + +void StrafeAttack::Begin + ( + Actor &self + ) + + { + state = 0; + } + +qboolean StrafeAttack::Evaluate + ( + Actor &self + ) + + { + int num; + Vector delta; + Vector left; + Vector pos; + + if ( !self.currentEnemy ) + { + return false; + } + + switch( state ) + { + case 0 : + delta = self.currentEnemy->worldorigin - self.worldorigin; + turn.SetDirection( delta.toYaw() ); + turn.Begin( self ); + state = 1; + break; + + case 1 : + if ( turn.Evaluate( self ) ) + { + return true; + } + + turn.End( self ); + state = 2; + + case 2 : + delta = self.currentEnemy->worldorigin - self.worldorigin; + left.x = -delta.y; + left.y = delta.x; + left.normalize(); + + if ( G_Random( 10 ) < 5 ) + { + num = gi.Anim_Random( self.edict->s.modelindex, "step_left" ); + if ( num != -1 ) + { + gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() ); + delta *= self.edict->s.scale; + pos = self.worldorigin + left * delta.length(); + if ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) ) + { + self.SetAnim( "step_left", EV_Actor_FinishedBehavior ); + state = 3; + return true; + } + } + + num = gi.Anim_Random( self.edict->s.modelindex, "step_right" ); + if ( num != -1 ) + { + gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() ); + delta *= self.edict->s.scale; + pos = self.worldorigin - left * delta.length(); + if ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) ) + { + self.SetAnim( "step_right", EV_Actor_FinishedBehavior ); + state = 3; + return true; + } + } + } + else + { + num = gi.Anim_Random( self.edict->s.modelindex, "step_right" ); + if ( num != -1 ) + { + gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() ); + delta *= self.edict->s.scale; + pos = self.worldorigin - left * delta.length(); + if ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) ) + { + self.SetAnim( "step_right", EV_Actor_FinishedBehavior ); + state = 3; + return true; + } + } + + num = gi.Anim_Random( self.edict->s.modelindex, "step_left" ); + if ( num != -1 ) + { + gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() ); + delta *= self.edict->s.scale; + pos = self.worldorigin + left * delta.length(); + if ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) ) + { + self.SetAnim( "step_left", EV_Actor_FinishedBehavior ); + state = 3; + return true; + } + } + + } + + return false; + break; + } + + return true; + } + +void StrafeAttack::End + ( + Actor &self + ) + + { + turn.End( self ); + self.SetAnim( "idle" ); + } + +/**************************************************************************** + + StrafeTo Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, StrafeTo, NULL ); + +ResponseDef StrafeTo::Responses[] = + { + { &EV_Behavior_Args, ( Response )StrafeTo::SetArgs }, + { NULL, NULL } + }; + +void StrafeTo::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z ); + gi.printf( "fail: %d\n", fail ); + + gi.printf( "\nseek:\n" ); + seek.ShowInfo( self ); + } + +void StrafeTo::SetArgs + ( + Event *ev + ) + + { + Entity *ent; + + if ( ev->IsVectorAt( 2 ) ) + { + goal = ev->GetVector( 2 ); + } + else + { + movegoal = AI_FindNode( ev->GetString( 2 ) ); + if ( movegoal ) + { + goal = movegoal->worldorigin; + } + else + { + ent = ev->GetEntity( 2 ); + if ( ent ) + { + goal = ent->worldorigin; + } + } + } + } + +void StrafeTo::Begin + ( + Actor &self + ) + + { + Vector delta; + float dot; + + seek.Begin( self ); + + delta = goal - self.worldorigin; + dot = delta * self.orientation[ 1 ]; + + if ( dot < 0 ) + { + self.SetAnim( "step_right" ); + } + else + { + self.SetAnim( "step_left" ); + } + + fail = 0; + } + +qboolean StrafeTo::Evaluate + ( + Actor &self + ) + + { + seek.SetMaxSpeed( self.movespeed ); + seek.SetPosition( self.worldorigin ); + seek.SetDir( self.movedir ); + seek.SetTargetPosition( goal ); + + if ( !seek.Evaluate( self ) ) + { + return false; + } + + self.Accelerate( seek.steeringforce ); + + // prevent him from trying to strafing forever if he's stuck + if ( self.lastmove != STEPMOVE_OK ) + { + if ( fail ) + { + return false; + } + + fail++; + } + else + { + fail = 0; + } + + return true; + } + +void StrafeTo::End + ( + Actor &self + ) + + { + seek.End( self ); + self.SetAnim( "idle" ); + } + +/**************************************************************************** + + Random Direction Utility function + +****************************************************************************/ + +Vector ChooseRandomDirection + ( + Actor &self, + Vector previousdir, + float *time, + int allowcontentsmask, + int disallowcontentsmask, + qboolean usepitch + ) + { + static float x[ 9 ] = { 0, 22, -22, 45, -45, 0, 22, -22, 45 }; + Vector dir; + Vector ang; + Vector bestdir; + Vector newdir; + Vector s; + float bestfraction; + trace_t trace; + int i; + int j; + int k; + int t; + int u; + int contents; + qboolean checkmask; + Vector centroid; + + centroid = self.centroid - self.worldorigin; + + checkmask = allowcontentsmask || disallowcontentsmask; + + s = Vector( 0, 0, STEPSIZE ); + bestfraction = -1; + bestdir = self.worldorigin; + for( i = 0; i <= 4; i++ ) + { + t = i * 45; + if ( rand() < 0.5 ) + { + // sometimes we choose left first, other times right. + t = -t; + } + for( j = -1; j < 2; j += 2 ) + { + ang.y = self.worldangles.y + ( t * j ); + + if ( usepitch ) + { + u = ( int )G_Random( 5 ); + for( k = 0; k < 5; k++ ) + { + ang.x = x[ k + u ]; + ang.AngleVectors( &dir, NULL, NULL ); + + dir *= self.movespeed * (*time); + dir += self.worldorigin; + trace = G_Trace( self.worldorigin, self.mins, self.maxs, dir, &self, + self.edict->clipmask, "ChooseRandomDirection 1" ); + + if ( !trace.startsolid && !trace.allsolid ) + { + newdir = Vector( trace.endpos ); + if ( checkmask ) + { + contents = gi.pointcontents( ( newdir + centroid ).vec3() ); + if ( + ( !allowcontentsmask || ( contents & allowcontentsmask ) ) && + ( !disallowcontentsmask || !( contents & disallowcontentsmask ) ) && + ( trace.fraction > bestfraction ) && + ( newdir != bestdir ) && + ( newdir != previousdir ) + ) + + { + bestdir = newdir; + bestfraction = trace.fraction; + } + } + else + { + if ( + ( trace.fraction > bestfraction ) && + ( newdir != bestdir ) && + ( newdir != previousdir ) + ) + { + bestdir = newdir; + bestfraction = trace.fraction; + } + } + } + } + } + else + { + ang.x = 0; + ang.AngleVectors( &dir, NULL, NULL ); + + dir *= self.movespeed * (*time); + dir += self.worldorigin; + dir += s; + trace = G_Trace( self.worldorigin + s, self.mins, self.maxs, dir, &self, + self.edict->clipmask, "ChooseRandomDirection 2" ); + + if ( !trace.startsolid && !trace.allsolid ) + { + newdir = Vector( trace.endpos ); + if ( checkmask ) + { + contents = gi.pointcontents( ( newdir + centroid ).vec3() ); + if ( + ( !allowcontentsmask || ( contents & allowcontentsmask ) ) && + ( !disallowcontentsmask || !( contents & disallowcontentsmask ) ) && + ( trace.fraction > bestfraction ) && + ( newdir != bestdir ) && + ( newdir != previousdir ) + ) + { + bestdir = newdir; + bestfraction = trace.fraction; + } + } + else + { + if ( + ( trace.fraction > bestfraction ) && + ( newdir != bestdir ) && + ( newdir != previousdir ) + ) + { + bestdir = newdir; + bestfraction = trace.fraction; + } + } + } + } + + + if ( ( i == 4 ) || ( i == 0 ) ) + { + break; + } + } + } + if ( bestfraction > 0 ) + { + *time *= bestfraction; + } + else + { + *time = 0; + } + + return bestdir; + } + +/**************************************************************************** + + Swim Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Swim, NULL ); + +ResponseDef Swim::Responses[] = + { + { &EV_Behavior_Args, ( Response )Swim::SetArgs }, + { NULL, NULL } + }; + +void Swim::SetArgs + ( + Event *ev + ) + + { + if ( ev->NumArgs() > 1 ) + anim = ev->GetString( 2 ); + } + +void Swim::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nseek:\n" ); + seek.ShowInfo( self ); + + gi.printf( "\navoid:\n" ); + avoid.ShowInfo( self ); + } + +void Swim::Begin + ( + Actor &self + ) + + { + avoidtime = 0; + avoidvec = vec_zero; + + avoid.Begin( self ); + seek.Begin( self ); + seek.SetTargetVelocity( vec_zero ); + if ( anim.length() ) + { + self.SetAnim( anim ); + } + } + +qboolean Swim::Evaluate + ( + Actor &self + ) + + { + if ( ( self.lastmove != STEPMOVE_OK ) || ( avoidtime <= level.time ) ) + { + Vector dir; + Vector ang; + float time; + + time = 3; + avoidvec = ChooseRandomDirection( self, avoidvec, &time, MASK_WATER, 0, true ); + avoidtime = level.time + time; + dir = avoidvec - self.worldorigin; + ang = dir.toAngles(); + ang[ PITCH ] = -ang[ PITCH ]; + self.setAngles( ang ); + } + + seek.SetMaxSpeed( self.movespeed ); + seek.SetPosition( self.worldorigin ); + seek.SetDir( self.movedir ); + seek.SetTargetPosition( avoidvec ); + // if we reached the goal, re-evaluate + if ( !seek.Evaluate( self ) ) + { + avoidtime = 0; + } + + avoid.SetMaxSpeed( self.movespeed * 2 ); + avoid.SetPosition( self.worldorigin ); + avoid.SetDir( self.movedir ); + avoid.Evaluate( self ); + + self.Accelerate( seek.steeringforce + avoid.steeringforce ); + + return true; + } + +void Swim::End + ( + Actor &self + ) + + { + avoid.End( self ); + seek.End( self ); + } + +/**************************************************************************** + + SwimCloseAttack Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, SwimCloseAttack, NULL ); + +ResponseDef SwimCloseAttack::Responses[] = + { + { &EV_Behavior_Args, ( Response )SwimCloseAttack::SetArgs }, + { NULL, NULL } + }; + +void SwimCloseAttack::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +void SwimCloseAttack::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nseek:\n" ); + seek.ShowInfo( self ); + + gi.printf( "\navoid:\n" ); + avoid.ShowInfo( self ); + + gi.printf( "\navoidtime: %.2f\n", avoidtime ); + } + +void SwimCloseAttack::Begin + ( + Actor &self + ) + + { + avoidtime = 0; + avoiding = false; + avoidvec = vec_zero; + + avoid.Begin( self ); + + seek.Begin( self ); + seek.SetTargetVelocity( vec_zero ); + if ( anim.length() ) + { + self.SetAnim( anim ); + } + } + +qboolean SwimCloseAttack::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector dir; + Vector ang; + Vector goal; + Vector end; + + if ( !self.currentEnemy ) + { + return false; + } + + // + // close enough + // + if ( self.CanShoot( self.currentEnemy, false ) ) + { + if ( self.WeaponReady() ) + return false; + else + return true; + } + + if ( ( self.lastmove == STEPMOVE_STUCK ) || ( avoidtime <= level.time ) ) + { + // + // see if we can get to the player + // + dir = self.currentEnemy->worldorigin - self.worldorigin; + dir.normalize(); + goal = self.currentEnemy->worldorigin; + trace = G_Trace( self.worldorigin, self.mins, self.maxs, goal, &self, self.edict->clipmask, "SwimCloseAttack::Evaluate 2" ); + if ( trace.fraction < 0.5f ) + { + float time; + time = 2.5f; + goal = ChooseRandomDirection( self, goal, &time, MASK_WATER, 0, true ); + avoidtime = level.time + time; + } + else + { + goal = trace.endpos; + avoidtime = level.time + 2.5f; + } + dir = goal - self.worldorigin; + dir.normalize(); + //self.movedir = dir; + ang = dir.toAngles(); + ang[ PITCH ] = -ang[ PITCH ]; + self.setAngles( ang ); + seek.SetTargetPosition( goal ); + } + + seek.SetMaxSpeed( self.movespeed ); + seek.SetPosition( self.worldorigin ); + seek.SetDir( self.movedir ); + // if we reached the goal, re-evaluate + if ( !seek.Evaluate( self ) ) + { + avoidtime = 0; + } + + avoid.SetMaxSpeed( self.movespeed * 2 ); + avoid.SetPosition( self.worldorigin ); + avoid.SetDir( self.movedir ); + avoid.Evaluate( self ); + + self.Accelerate( seek.steeringforce + avoid.steeringforce ); + + return true; + } + +void SwimCloseAttack::End + ( + Actor &self + ) + + { + seek.End( self ); + } + + +/**************************************************************************** + + Fly Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Fly, NULL ); + +ResponseDef Fly::Responses[] = + { + { &EV_Behavior_Args, ( Response )Fly::SetArgs }, + { NULL, NULL } + }; + +void Fly::SetArgs + ( + Event *ev + ) + + { + if ( ev->NumArgs() > 1 ) + anim = ev->GetString( 2 ); + } + +void Fly::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nseek:\n" ); + seek.ShowInfo( self ); + + gi.printf( "\navoid:\n" ); + avoid.ShowInfo( self ); + } + +void Fly::Begin + ( + Actor &self + ) + + { + avoidtime = 0; + avoidvec = vec_zero; + + avoid.Begin( self ); + seek.Begin( self ); + seek.SetTargetVelocity( vec_zero ); + if ( anim.length() ) + { + self.SetAnim( anim ); + } + } + + +qboolean Fly::Evaluate + ( + Actor &self + ) + + { + if ( ( self.lastmove != STEPMOVE_OK ) || ( avoidtime <= level.time ) ) + { + Vector dir; + Vector ang; + float time; + + time = 3; + avoidvec = ChooseRandomDirection( self, avoidvec, &time, 0, MASK_WATER, true ); + avoidtime = level.time + time; + dir = avoidvec - self.worldorigin; + ang = dir.toAngles(); + ang[ PITCH ] = -ang[ PITCH ]; + self.setAngles( ang ); + } + + seek.SetMaxSpeed( self.movespeed ); + seek.SetPosition( self.worldorigin ); + seek.SetDir( self.movedir ); + seek.SetTargetPosition( avoidvec ); + // if we reached the goal, re-evaluate + if ( !seek.Evaluate( self ) ) + { + avoidtime = 0; + } + + avoid.SetMaxSpeed( self.movespeed * 2 ); + avoid.SetPosition( self.worldorigin ); + avoid.SetDir( self.movedir ); + avoid.Evaluate( self ); + + self.Accelerate( seek.steeringforce + avoid.steeringforce ); + + return true; + } + +void Fly::End + ( + Actor &self + ) + + { + avoid.End( self ); + seek.End( self ); + } + +/**************************************************************************** + + FlyCloseAttack Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyCloseAttack, NULL ); + +ResponseDef FlyCloseAttack::Responses[] = + { + { &EV_Behavior_Args, ( Response )FlyCloseAttack::SetArgs }, + { NULL, NULL } + }; + +void FlyCloseAttack::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +void FlyCloseAttack::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nseek:\n" ); + seek.ShowInfo( self ); + + gi.printf( "\navoid:\n" ); + avoid.ShowInfo( self ); + + gi.printf( "\navoidtime: %.2f\n", avoidtime ); + } + +void FlyCloseAttack::Begin + ( + Actor &self + ) + + { + avoidtime = 0; + avoiding = false; + avoidvec = vec_zero; + + avoid.Begin( self ); + + seek.Begin( self ); + seek.SetTargetVelocity( vec_zero ); + if ( anim.length() ) + { + self.SetAnim( anim ); + } + } + +qboolean FlyCloseAttack::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector dir; + Vector ang; + float minrange; + Vector goal; + Vector end; + Sentient *sent; + + if ( !self.currentEnemy ) + { + return false; + } + + if ( !self.CurrentWeapon() && self.currentEnemy->isSubclassOf( Sentient ) ) + { + sent = ( Sentient * )( Entity * )self.currentEnemy; + if ( sent->waterlevel > 1 ) + { + // can no longer get to enemy + self.ClearEnemies(); + return false; + } + } + + minrange = self.MinimumAttackRange(); + // + // close enough + // + if ( self.CanShoot( self.currentEnemy, false ) ) + { + if ( self.WeaponReady() ) + return false; + else + return true; + } + + if ( ( self.lastmove == STEPMOVE_STUCK ) || ( avoidtime <= level.time ) ) + { + // + // see if we can get to the player + // + goal = self.currentEnemy->centroid; + trace = G_Trace( self.worldorigin, self.mins, self.maxs, goal, &self, self.edict->clipmask, "FlyCloseAttack::Evaluate 2" ); + if ( trace.fraction < 0.5f ) + { + float time; + time = 2.5f; + goal = ChooseRandomDirection( self, goal, &time, 0, MASK_WATER, true ); + avoidtime = level.time + time; + } + else + { + goal = trace.endpos; + avoidtime = level.time + 2.5f; + } + dir = goal - self.worldorigin; + dir.normalize(); + //self.movedir = dir; + ang = dir.toAngles(); + ang[ PITCH ] = -ang[ PITCH ]; + self.setAngles( ang ); + seek.SetTargetPosition( goal ); + } + + seek.SetMaxSpeed( self.movespeed ); + seek.SetPosition( self.worldorigin ); + seek.SetDir( self.movedir ); + // if we reached the goal, re-evaluate + if ( !seek.Evaluate( self ) ) + { + avoidtime = 0; + } + + avoid.SetMaxSpeed( self.movespeed * 2 ); + avoid.SetPosition( self.worldorigin ); + avoid.SetDir( self.movedir ); + avoid.Evaluate( self ); + + self.Accelerate( seek.steeringforce + avoid.steeringforce ); + + return true; + } + +void FlyCloseAttack::End + ( + Actor &self + ) + + { + seek.End( self ); + } + + + +/**************************************************************************** + + Wander Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Wander, NULL ); + +ResponseDef Wander::Responses[] = + { + { &EV_Behavior_Args, ( Response )Wander::SetArgs }, + { NULL, NULL } + }; + +void Wander::SetArgs + ( + Event *ev + ) + + { + if ( ev->NumArgs() > 1 ) + anim = ev->GetString( 2 ); + } + +void Wander::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nseek:\n" ); + seek.ShowInfo( self ); + + gi.printf( "\navoid:\n" ); + avoid.ShowInfo( self ); + } + +void Wander::Begin + ( + Actor &self + ) + + { + avoidtime = 0; + avoidvec = vec_zero; + + avoid.Begin( self ); + seek.Begin( self ); + seek.SetTargetVelocity( vec_zero ); + if ( anim.length() ) + { + self.SetAnim( anim ); + } + } + + +qboolean Wander::Evaluate + ( + Actor &self + ) + + { + if ( ( self.lastmove != STEPMOVE_OK ) || ( avoidtime <= level.time ) ) + { + Vector dir; + Vector ang; + float time; + + time = 5; + self.Chatter( "snd_idle", 4 ); + avoidvec = ChooseRandomDirection( self, avoidvec, &time, 0, 0, false ); + avoidtime = level.time + time; + dir = avoidvec - self.worldorigin; + ang = dir.toAngles(); + self.angles[ YAW ] = ang[ YAW ]; + self.setAngles( self.angles ); + } + + seek.SetMaxSpeed( self.movespeed ); + seek.SetPosition( self.worldorigin ); + seek.SetDir( self.movedir ); + seek.SetTargetPosition( avoidvec ); + // if we reached the goal, re-evaluate + if ( !seek.Evaluate( self ) ) + { + avoidtime = 0; + } + + avoid.SetMaxSpeed( self.movespeed * 2 ); + avoid.SetPosition( self.worldorigin ); + avoid.SetDir( self.movedir ); + avoid.Evaluate( self ); + + self.Accelerate( seek.steeringforce + avoid.steeringforce ); + + return true; + } + +void Wander::End + ( + Actor &self + ) + + { + avoid.End( self ); + seek.End( self ); + } + + +/**************************************************************************** + + WanderCloseAttack Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, WanderCloseAttack, NULL ); + +ResponseDef WanderCloseAttack::Responses[] = + { + { &EV_Behavior_Args, ( Response )WanderCloseAttack::SetArgs }, + { NULL, NULL } + }; + +void WanderCloseAttack::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +void WanderCloseAttack::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nseek:\n" ); + seek.ShowInfo( self ); + + gi.printf( "\navoid:\n" ); + avoid.ShowInfo( self ); + + gi.printf( "\navoidtime: %.2f\n", avoidtime ); + } + +void WanderCloseAttack::Begin + ( + Actor &self + ) + + { + avoidtime = 0; + avoiding = false; + avoidvec = vec_zero; + + avoid.Begin( self ); + + seek.Begin( self ); + seek.SetTargetVelocity( vec_zero ); + if ( anim.length() ) + { + self.SetAnim( anim ); + } + } + +qboolean WanderCloseAttack::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector dir; + Vector ang; + float minrange; + Vector goal; + Vector end; + + if ( !self.currentEnemy ) + { + return false; + } + + minrange = self.MinimumAttackRange(); + // + // close enough + // + if ( self.CanShoot( self.currentEnemy, false ) ) + { + if ( self.WeaponReady() ) + return false; + else + return true; + } + + if ( ( self.lastmove == STEPMOVE_STUCK ) || ( avoidtime <= level.time ) ) + { + // + // see if we can get to the player + // + dir = self.currentEnemy->worldorigin - self.worldorigin; + dir.normalize(); + goal = self.currentEnemy->worldorigin; + trace = G_Trace( self.worldorigin, self.mins, self.maxs, goal, &self, self.edict->clipmask, "WanderCloseAttack::Evaluate 2" ); + if ( trace.fraction < 0.5f ) + { + float time; + time = 2.5f; + goal = ChooseRandomDirection( self, goal, &time, 0, 0, false ); + avoidtime = level.time + time; + } + else + { + goal = trace.endpos; + avoidtime = level.time + 2.5f; + } + dir = goal - self.worldorigin; + dir.normalize(); + //self.movedir = dir; + ang = dir.toAngles(); + ang[ PITCH ] = -ang[ PITCH ]; + self.setAngles( ang ); + seek.SetTargetPosition( goal ); + } + + seek.SetMaxSpeed( self.movespeed ); + seek.SetPosition( self.worldorigin ); + seek.SetDir( self.movedir ); + // if we reached the goal, re-evaluate + if ( !seek.Evaluate( self ) ) + { + avoidtime = 0; + } + + avoid.SetMaxSpeed( self.movespeed * 2 ); + avoid.SetPosition( self.worldorigin ); + avoid.SetDir( self.movedir ); + avoid.Evaluate( self ); + + self.Accelerate( seek.steeringforce + avoid.steeringforce ); + + return true; + } + +void WanderCloseAttack::End + ( + Actor &self + ) + + { + seek.End( self ); + } + +/**************************************************************************** + + GetCloseToEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, GetCloseToEnemy, NULL ); + +ResponseDef GetCloseToEnemy::Responses[] = + { + { &EV_Behavior_Args, ( Response )GetCloseToEnemy::SetArgs }, + { NULL, NULL } + }; + +GetCloseToEnemy::GetCloseToEnemy() + { + howclose = 32; + } + +void GetCloseToEnemy::SetArgs + ( + Event *ev + ) + + { + int num; + + num = 2; + if ( !ev->IsNumericAt( num ) ) + { + anim = ev->GetString( num ); + num++; + } + + howclose = 32; + if ( ev->IsNumericAt( num ) ) + { + howclose = ev->GetFloat( num ); + num++; + } + } + +void GetCloseToEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\nchase:\n" ); + chase.ShowInfo( self ); + + gi.printf( "\nstate: %d\n", state ); + gi.printf( "howclose: %f\n", howclose ); + gi.printf( "nextsearch: %f\n", nextsearch ); + gi.printf( "anim: %s\n", anim.c_str() ); + } + +void GetCloseToEnemy::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + movegoal = NULL; + state = 0; + } + +qboolean GetCloseToEnemy::Evaluate + ( + Actor &self + ) + + { + if ( !self.currentEnemy ) + { + return false; + } + + if ( nextsearch < level.time ) + { + state = 0; + } + + switch( state ) + { + case 0 : + chase.Begin( self ); + movegoal = PathManager.NearestNode( self.currentEnemy->worldorigin, &self, false ); + if ( movegoal ) + { + Path *path; + FindEnemyPath find; + + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + path = find.FindPath( self.worldorigin, movegoal->worldorigin ); + movegoal = NULL; + if ( path ) + { + float dist; + float movedist; + + // + // check range + // if distance is less then total_delta, than lets go straight for him + // + dist = path->DistanceAlongPath( self.worldorigin ); + movedist = self.total_delta.length(); + if ( dist >= movedist ) + { + movegoal = path->End(); + + chase.SetGoal( movegoal ); + chase.SetPath( path ); + } + } + } + + if ( !movegoal ) + { + if ( self.CanSee( self.currentEnemy ) ) + { + chase.SetGoalPos( self.currentEnemy->worldorigin ); + } + else + { + // + // since we can't reach em + // clear out enemy state + // + self.ClearEnemies(); + return false; + } + } + + if ( anim.length() && ( anim != self.animname ) ) + { + self.SetAnim( anim ); + } + + state = 1; + nextsearch = level.time + 3; + + case 1 : + if ( self.WithinDistance( self.currentEnemy, howclose ) ) + { + chase.End( self ); + return false; + } + + if ( !chase.Evaluate( self ) || ( nextsearch < level.time ) ) + { + state = 0; + } + break; + } + + return true; + } + +void GetCloseToEnemy::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + PlayAnimSeekEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, PlayAnimSeekEnemy, NULL ); + +ResponseDef PlayAnimSeekEnemy::Responses[] = + { + { &EV_Behavior_Args, ( Response )PlayAnimSeekEnemy::SetArgs }, + { &EV_Behavior_AnimDone, ( Response )PlayAnimSeekEnemy::AnimDone }, + { NULL, NULL } + }; + +void PlayAnimSeekEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.printf( "\naim:\n" ); + aim.ShowInfo( self ); + + gi.printf( "\nmode: %d\n", mode ); + gi.printf( "anim: %s\n", anim.c_str() ); + gi.printf( "animdone: %d\n", animdone ); + } + +void PlayAnimSeekEnemy::Begin + ( + Actor &self + ) + + { + oldanim = self.animname; + mode = 0; + animdone = false; + } + +void PlayAnimSeekEnemy::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 2 ); + } + +void PlayAnimSeekEnemy::AnimDone + ( + Event *ev + ) + + { + animdone = true; + } + +qboolean PlayAnimSeekEnemy::Evaluate + ( + Actor &self + ) + + { + switch( mode ) + { + case 0 : + if ( !anim.length() ) + { + } + + if ( !self.currentEnemy ) + { + return false; + } + + animdone = false; + self.SetAnim( anim.c_str(), EV_Actor_NotifyBehavior ); + + mode = 1; + + case 1 : + aim.SetTarget( self.currentEnemy ); + aim.Evaluate( self ); + + // finish up the attack + if ( animdone ) + { + return false; + } + break; + } + + return true; + } + +void PlayAnimSeekEnemy::End + ( + Actor &self + ) + + { + aim.End( self ); + if ( oldanim.length() ) + { + self.SetAnim( oldanim.c_str() ); + } + } diff --git a/behavior.h b/behavior.h new file mode 100644 index 0000000..1134fd5 --- /dev/null +++ b/behavior.h @@ -0,0 +1,1721 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/behavior.h $ +// $Revision:: 55 $ +// $Author:: Jimdose $ +// $Date:: 10/27/98 8:02p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/behavior.h $ +// +// 55 10/27/98 8:02p Jimdose +// added PlayAnimSeekEnemy +// +// 54 10/27/98 12:42a Jimdose +// added animprefix to AimAndShoot +// +// 53 10/25/98 11:52p Jimdose +// added EXPORT_TEMPLATE +// +// 52 10/25/98 4:59a Jimdose +// Added lastSearchNode and lastSearchPos to chase +// archived howclose in GetCloseToEnemy +// +// 51 10/22/98 11:44p Jimdose +// Added GetCloseToEnemy +// +// 50 10/20/98 11:30p Markd +// Added aim_time to aimandshoot +// +// 49 10/20/98 2:38a Markd +// Added goal variable to Jump +// +// 48 10/17/98 4:41p Markd +// Added curioustime to Investigate behavior +// +// 47 10/16/98 7:20p Markd +// Added maxshots and numshots to AimAndMelee +// +// 46 10/14/98 9:02p Markd +// Made the Jump behavior more robust, and added landing +// +// 45 10/14/98 5:22p Markd +// Moved jump definition +// +// 44 10/13/98 7:38p Markd +// Created behaviors for Wander, FLy, FlyCloseAttack and WanderCloseAttack +// +// 43 10/10/98 6:08p Markd +// Added FleeAndRemove, FindFlee, fixed max_ainode bug +// +// 42 10/10/98 1:25a Jimdose +// Removed unneccesary definition stack template +// +// 41 10/05/98 3:04p Markd +// Added enemy_health variable to AimAndShoot +// +// 40 10/04/98 3:01p Markd +// fixed swimming behavior +// +// 39 10/03/98 7:27p Markd +// working on swimming character and redid some weapon aiming stuff +// +// 38 10/01/98 8:01p Markd +// Added AimAndMelee +// +// 37 9/29/98 5:58p Markd +// put in archive and unarchive +// +// 36 9/22/98 1:55a Jimdose +// Completed ShowInfo for all behaviors +// Added StrafeTo +// +// 35 9/18/98 10:56p Jimdose +// Separated steering behaviors into their own file +// +// 34 9/14/98 5:27p Jimdose +// Added SetPathRate to Chase so that actors can choose how often they search +// for a new path +// +// 33 8/31/98 7:48p Jimdose +// Simplified FollowPath +// +// 32 8/26/98 11:13p Jimdose +// Added StrafeAttack +// +// 31 8/19/98 8:47p Jimdose +// Added Jump behavior +// +// 30 8/14/98 6:25p Jimdose +// Got rid of decelleration for steering +// Added GoalEnt to GotoPathNode +// +// 29 8/08/98 8:24p Markd +// added pickupandthrow +// +// 28 8/07/98 6:02p Jimdose +// Rewrote AimAndShoot +// +// 27 8/06/98 6:57p Jimdose +// Removed FireFromCover (now done in script) +// Added SetArgs to AimAndShoot +// +// 26 8/03/98 7:58p Aldie +// Added a first attempt at wander behavior +// +// 25 7/26/98 11:44a Jimdose +// Added FindClosestSightNode +// +// 24 7/22/98 10:51p Jimdose +// Added repel behavior +// +// 23 7/08/98 9:00p Jimdose +// Made OpenDoor script callable +// +// 22 7/06/98 1:06p Jimdose +// working on ai +// +// 21 6/30/98 6:03p Jimdose +// Chase and GotoPathNode now can use vectors for goals +// Investigate no longer needs currentEnemy to be set +// +// 20 6/24/98 4:26p Jimdose +// Fixed bugs in TurnTo +// +// 19 6/17/98 1:18a Jimdose +// Removed TargetEnemies. Moved it back into actor +// +// 18 6/15/98 10:47p Jimdose +// Added Hide and CoverFire +// +// 17 6/13/98 8:21p Jimdose +// Added FindCover +// +// 16 6/11/98 12:44a Jimdose +// behaviors now get info from the script at startup +// +// 15 6/10/98 10:25p Jimdose +// added priority based state system +// +// 14 6/09/98 4:24p Jimdose +// worked on ai +// +// 13 6/04/98 10:48p Jimdose +// Fixed a bunch of things that got broken just in time for E3. Paths and +// scripting actually work now. +// +// 12 6/03/98 5:44p Jimdose +// Fixed spelling of behavior. :) +// +// 11 5/27/98 7:12a Jimdose +// ai ai ai +// +// 10 5/27/98 6:39a Jimdose +// working on ai +// +// 9 5/27/98 5:11a Jimdose +// working on ai +// +// 8 5/25/98 5:31p Jimdose +// Pathnodes are no longer a subclass of Entity. This was done to save on +// edicts +// +// 7 5/25/98 1:06a Jimdose +// Added chatter +// +// 6 5/24/98 1:02a Jimdose +// Added Investigate +// +// 5 5/23/98 6:29p Jimdose +// Added Aim +// +// 4 5/22/98 9:39p Jimdose +// Worked on ai +// +// 3 5/20/98 6:38p Jimdose +// working on ai +// +// 2 5/18/98 8:15p Jimdose +// Created file +// +// DESCRIPTION: +// Standard class for creating AI behaviors +// + +#ifndef __BEHAVIOR_H__ +#define __BEHAVIOR_H__ + +#include "g_local.h" +#include "entity.h" +#include "path.h" +#include "steering.h" + +extern Event EV_Behavior_Args; +extern Event EV_Behavior_AnimDone; + +class Actor; + +class EXPORT_FROM_DLL Behavior : public Listener + { + protected: + PathNodePtr movegoal; + + public: + CLASS_PROTOTYPE( Behavior ); + + Behavior(); + + virtual void ShowInfo( Actor &self ); + virtual void Begin( Actor &self ); + virtual qboolean Evaluate( Actor &self ); + virtual void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Behavior::Archive + ( + Archiver &arc + ) + { + Listener::Archive( arc ); + + arc.WriteSafePointer( movegoal ); + } + +inline EXPORT_FROM_DLL void Behavior::Unarchive + ( + Archiver &arc + ) + { + Listener::Unarchive( arc ); + + arc.ReadSafePointer( &movegoal ); + } + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL SafePtr; +#endif +typedef SafePtr BehaviorPtr; + +class EXPORT_FROM_DLL Idle : public Behavior + { + private: + float nexttwitch; + str anim; + + public: + CLASS_PROTOTYPE( Idle ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Idle::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteFloat( nexttwitch ); + arc.WriteString( anim ); + } + +inline EXPORT_FROM_DLL void Idle::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadFloat( &nexttwitch ); + arc.ReadString( &anim ); + } + +class EXPORT_FROM_DLL Aim : public Behavior + { + private: + Seek seek; + EntityPtr target; + + public: + CLASS_PROTOTYPE( Aim ); + + void SetTarget( Entity *ent ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Aim::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &seek ); + arc.WriteSafePointer( target ); + } + +inline EXPORT_FROM_DLL void Aim::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &seek ); + arc.ReadSafePointer( &target ); + } + +class EXPORT_FROM_DLL FireOnSight : public Behavior + { + private: + Chase chase; + Aim aim; + int mode; + str anim; + + public: + CLASS_PROTOTYPE( FireOnSight ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void FireOnSight::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &chase ); + arc.WriteObject( &aim ); + arc.WriteInteger( mode ); + arc.WriteString( anim ); + } + +inline EXPORT_FROM_DLL void FireOnSight::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &chase ); + arc.ReadObject( &aim ); + arc.ReadInteger( &mode ); + arc.ReadString( &anim ); + } + +class EXPORT_FROM_DLL TurnTo : public Behavior + { + private: + Seek seek; + EntityPtr ent; + Vector dir; + float yaw; + int mode; + + public: + CLASS_PROTOTYPE( TurnTo ); + + TurnTo(); + void SetDirection( float yaw ); + void SetTarget( Entity *ent ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void TurnTo::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &seek ); + arc.WriteSafePointer( ent ); + arc.WriteVector( dir ); + arc.WriteFloat( yaw ); + arc.WriteInteger( mode ); + } + +inline EXPORT_FROM_DLL void TurnTo::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &seek ); + arc.ReadSafePointer( &ent ); + arc.ReadVector( &dir ); + arc.ReadFloat( &yaw ); + arc.ReadInteger( &mode ); + } + +class EXPORT_FROM_DLL Jump : public Behavior + { + private: + float endtime; + float speed; + str anim; + int state; + qboolean animdone; + Vector goal; + + public: + CLASS_PROTOTYPE( Jump ); + + Jump(); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + void AnimDone( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Jump::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteFloat( endtime ); + arc.WriteFloat( speed ); + arc.WriteString( anim ); + arc.WriteInteger( state ); + arc.WriteBoolean( animdone ); + arc.WriteVector( goal ); + } + +inline EXPORT_FROM_DLL void Jump::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadFloat( &endtime ); + arc.ReadFloat( &speed ); + arc.ReadString( &anim ); + arc.ReadInteger( &state ); + arc.ReadBoolean( &animdone ); + arc.ReadVector( &goal ); + } + + +class EXPORT_FROM_DLL GotoPathNode : public Behavior + { + private: + TurnTo turnto; + Chase chase; + int state; + qboolean usevec; + float time; + str anim; + EntityPtr goalent; + Vector goal; + + public: + CLASS_PROTOTYPE( GotoPathNode ); + + GotoPathNode(); + void SetArgs( Event *ev ); + void SetGoal( PathNode *node ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void GotoPathNode::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &turnto ); + arc.WriteObject( &chase ); + arc.WriteInteger( state ); + arc.WriteBoolean( usevec ); + arc.WriteFloat( time ); + arc.WriteString( anim ); + arc.WriteSafePointer( goalent ); + arc.WriteVector( goal ); + } + +inline EXPORT_FROM_DLL void GotoPathNode::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &turnto ); + arc.ReadObject( &chase ); + arc.ReadInteger( &state ); + arc.ReadBoolean( &usevec ); + arc.ReadFloat( &time ); + arc.ReadString( &anim ); + arc.ReadSafePointer( &goalent ); + arc.ReadVector( &goal ); + } + +class EXPORT_FROM_DLL Investigate : public Behavior + { + private: + Chase chase; + str anim; + Vector goal; + float curioustime; + + qboolean Done( Actor &self ); + + public: + CLASS_PROTOTYPE( Investigate ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Investigate::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &chase ); + arc.WriteString( anim ); + arc.WriteVector( goal ); + arc.WriteFloat( curioustime ); + } + +inline EXPORT_FROM_DLL void Investigate::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &chase ); + arc.ReadString( &anim ); + arc.ReadVector( &goal ); + arc.ReadFloat( &curioustime ); + } + +class EXPORT_FROM_DLL Flee : public Behavior + { + private: + FollowPath follow; + PathPtr path; + ObstacleAvoidance avoid; + float avoidtime; + str anim; + + public: + CLASS_PROTOTYPE( Flee ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Flee::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &follow ); + arc.WriteSafePointer( path ); + arc.WriteObject( &avoid ); + arc.WriteFloat( avoidtime ); + arc.WriteString( anim ); + } + +inline EXPORT_FROM_DLL void Flee::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &follow ); + arc.ReadSafePointer( &path ); + arc.ReadObject( &avoid ); + arc.ReadFloat( &avoidtime ); + arc.ReadString( &anim ); + } + +class EXPORT_FROM_DLL OpenDoor : public Behavior + { + private: + float time; + float endtime; + qboolean usedir; + Vector dir; + + public: + CLASS_PROTOTYPE( OpenDoor ); + + OpenDoor(); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void OpenDoor::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteFloat( time ); + arc.WriteFloat( endtime ); + arc.WriteBoolean( usedir ); + arc.WriteVector( dir ); + } + +inline EXPORT_FROM_DLL void OpenDoor::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadFloat( &time ); + arc.ReadFloat( &endtime ); + arc.ReadBoolean( &usedir ); + arc.ReadVector( &dir ); + } + +class EXPORT_FROM_DLL PlayAnim : public Behavior + { + private: + str anim; + + public: + CLASS_PROTOTYPE( PlayAnim ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void PlayAnim::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteString( anim ); + } + +inline EXPORT_FROM_DLL void PlayAnim::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadString( &anim ); + } + +class EXPORT_FROM_DLL FindCover : public Behavior + { + private: + str anim; + Chase chase; + int state; + float nextsearch; + + public: + CLASS_PROTOTYPE( FindCover ); + + void SetArgs( Event *ev ); + PathNode *FindCoverNode( Actor &self ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void FindCover::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteString( anim ); + arc.WriteObject( &chase ); + arc.WriteInteger( state ); + arc.WriteFloat( nextsearch ); + } + +inline EXPORT_FROM_DLL void FindCover::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadString( &anim ); + arc.ReadObject( &chase ); + arc.ReadInteger( &state ); + arc.ReadFloat( &nextsearch ); + } + +class EXPORT_FROM_DLL FindFlee : public Behavior + { + private: + str anim; + Chase chase; + int state; + float nextsearch; + + public: + CLASS_PROTOTYPE( FindFlee ); + + void SetArgs( Event *ev ); + PathNode *FindFleeNode( Actor &self ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void FindFlee::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteString( anim ); + arc.WriteObject( &chase ); + arc.WriteInteger( state ); + arc.WriteFloat( nextsearch ); + } + +inline EXPORT_FROM_DLL void FindFlee::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadString( &anim ); + arc.ReadObject( &chase ); + arc.ReadInteger( &state ); + arc.ReadFloat( &nextsearch ); + } + +class EXPORT_FROM_DLL FindEnemy : public Behavior + { + private: + str anim; + Chase chase; + int state; + float nextsearch; + PathNodePtr lastSearchNode; + Vector lastSearchPos; + + public: + CLASS_PROTOTYPE( FindEnemy ); + + PathNode *FindClosestSightNode( Actor &self ); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void FindEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteString( anim ); + arc.WriteObject( &chase ); + arc.WriteInteger( state ); + arc.WriteFloat( nextsearch ); + arc.WriteSafePointer( lastSearchNode ); + arc.WriteVector( lastSearchPos ); + } + +inline EXPORT_FROM_DLL void FindEnemy::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadString( &anim ); + arc.ReadObject( &chase ); + arc.ReadInteger( &state ); + arc.ReadFloat( &nextsearch ); + arc.ReadSafePointer( &lastSearchNode ); + arc.ReadVector( &lastSearchPos ); + } + +class EXPORT_FROM_DLL Hide : public Behavior + { + private: + FindCover hide; + str anim; + int state; + float checktime; + + public: + CLASS_PROTOTYPE( Hide ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Hide::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &hide ); + arc.WriteString( anim ); + arc.WriteInteger( state ); + arc.WriteFloat( checktime ); + } + +inline EXPORT_FROM_DLL void Hide::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &hide ); + arc.ReadString( &anim ); + arc.ReadInteger( &state ); + arc.ReadFloat( &checktime ); + } + + +class EXPORT_FROM_DLL FleeAndRemove : public Behavior + { + private: + FindFlee flee; + str anim; + int state; + float checktime; + + public: + CLASS_PROTOTYPE( FleeAndRemove ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void FleeAndRemove::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &flee ); + arc.WriteString( anim ); + arc.WriteInteger( state ); + arc.WriteFloat( checktime ); + } + +inline EXPORT_FROM_DLL void FleeAndRemove::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &flee ); + arc.ReadString( &anim ); + arc.ReadInteger( &state ); + arc.ReadFloat( &checktime ); + } + + +class EXPORT_FROM_DLL AimAndShoot : public Behavior + { + private: + Aim aim; + int mode; + int maxshots; + int numshots; + qboolean animdone; + float enemy_health; + float aim_time; + str animprefix; + str readyfireanim; + str aimanim; + str fireanim; + + public: + CLASS_PROTOTYPE( AimAndShoot ); + + AimAndShoot(); + void SetMaxShots( int num ); + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void AimAndShoot::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &aim ); + arc.WriteInteger( mode ); + arc.WriteInteger( maxshots ); + arc.WriteInteger( numshots ); + arc.WriteBoolean( animdone ); + arc.WriteFloat( enemy_health ); + arc.WriteFloat( aim_time ); + arc.WriteString( animprefix ); + arc.WriteString( readyfireanim ); + arc.WriteString( aimanim ); + arc.WriteString( fireanim ); + } + +inline EXPORT_FROM_DLL void AimAndShoot::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &aim ); + arc.ReadInteger( &mode ); + arc.ReadInteger( &maxshots ); + arc.ReadInteger( &numshots ); + arc.ReadBoolean( &animdone ); + arc.ReadFloat( &enemy_health ); + arc.ReadFloat( &aim_time ); + arc.ReadString( &animprefix ); + arc.ReadString( &readyfireanim ); + arc.ReadString( &aimanim ); + arc.ReadString( &fireanim ); + } + +class EXPORT_FROM_DLL AimAndMelee : public Behavior + { + private: + Aim aim; + int mode; + int maxshots; + int numshots; + qboolean animdone; + + public: + CLASS_PROTOTYPE( AimAndMelee ); + + AimAndMelee(); + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void AimAndMelee::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &aim ); + arc.WriteInteger( mode ); + arc.WriteInteger( maxshots ); + arc.WriteInteger( numshots ); + arc.WriteBoolean( animdone ); + } + +inline EXPORT_FROM_DLL void AimAndMelee::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &aim ); + arc.ReadInteger( &mode ); + arc.ReadInteger( &maxshots ); + arc.ReadInteger( &numshots ); + arc.ReadBoolean( &animdone ); + } + + +class EXPORT_FROM_DLL Melee : public Behavior + { + private: + int mode; + qboolean animdone; + + public: + CLASS_PROTOTYPE( Melee ); + + Melee(); + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Melee::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteInteger( mode ); + arc.WriteBoolean( animdone ); + } + +inline EXPORT_FROM_DLL void Melee::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadInteger( &mode ); + arc.ReadBoolean( &animdone ); + } + +class EXPORT_FROM_DLL Repel : public Behavior + { + private: + str anim; + float dist; + float len; + float speed; + Vector goal; + Vector start; + Vector dir; + + public: + CLASS_PROTOTYPE( Repel ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Repel::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteString( anim ); + arc.WriteFloat( dist ); + arc.WriteFloat( len ); + arc.WriteFloat( speed ); + arc.WriteVector( goal ); + arc.WriteVector( start ); + arc.WriteVector( dir ); + } + +inline EXPORT_FROM_DLL void Repel::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadString( &anim ); + arc.ReadFloat( &dist ); + arc.ReadFloat( &len ); + arc.ReadFloat( &speed ); + arc.ReadVector( &goal ); + arc.ReadVector( &start ); + arc.ReadVector( &dir ); + } + + +class EXPORT_FROM_DLL PickupAndThrow : public Behavior + { + private: + Aim aim; + int mode; + qboolean animdone; + EntityPtr pickup_target; + + + public: + CLASS_PROTOTYPE( PickupAndThrow ); + + PickupAndThrow(); + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void Pickup( Event *ev ); + void Throw( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void PickupAndThrow::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &aim ); + arc.WriteInteger( mode ); + arc.WriteBoolean( animdone ); + arc.WriteSafePointer( pickup_target ); + } + +inline EXPORT_FROM_DLL void PickupAndThrow::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &aim ); + arc.ReadInteger( &mode ); + arc.ReadBoolean( &animdone ); + arc.ReadSafePointer( &pickup_target ); + } + +class EXPORT_FROM_DLL StrafeAttack : public Behavior + { + private: + int state; + TurnTo turn; + + public: + CLASS_PROTOTYPE( StrafeAttack ); + + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void StrafeAttack::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteInteger( state ); + arc.WriteObject( &turn ); + } + +inline EXPORT_FROM_DLL void StrafeAttack::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadInteger( &state ); + arc.ReadObject( &turn ); + } + +class EXPORT_FROM_DLL StrafeTo : public Behavior + { + private: + Vector goal; + Seek seek; + int fail; + + public: + CLASS_PROTOTYPE( StrafeTo ); + + void ShowInfo( Actor &self ); + void SetArgs( Event *ev ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void StrafeTo::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteVector( goal ); + arc.WriteObject( &seek ); + arc.WriteInteger( fail ); + } + +inline EXPORT_FROM_DLL void StrafeTo::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadVector( &goal ); + arc.ReadObject( &seek ); + arc.ReadInteger( &fail ); + } + +class EXPORT_FROM_DLL Swim : public Behavior + { + private: + Seek seek; + ObstacleAvoidance2 avoid; + str anim; + float avoidtime; + Vector avoidvec; + + public: + CLASS_PROTOTYPE( Swim ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Swim::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &seek ); + arc.WriteObject( &avoid ); + arc.WriteString( anim ); + arc.WriteFloat( avoidtime ); + arc.WriteVector( avoidvec ); + } + +inline EXPORT_FROM_DLL void Swim::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &seek ); + arc.ReadObject( &avoid ); + arc.ReadString( &anim ); + arc.ReadFloat( &avoidtime ); + arc.ReadVector( &avoidvec ); + } + +class EXPORT_FROM_DLL SwimCloseAttack : public Behavior + { + private: + Seek seek; + ObstacleAvoidance2 avoid; + qboolean avoiding; + str anim; + float avoidtime; + Vector avoidvec; + + public: + CLASS_PROTOTYPE( SwimCloseAttack ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void SwimCloseAttack::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &seek ); + arc.WriteObject( &avoid ); + arc.WriteBoolean( avoiding ); + arc.WriteString( anim ); + arc.WriteFloat( avoidtime ); + arc.WriteVector( avoidvec ); + } + +inline EXPORT_FROM_DLL void SwimCloseAttack::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &seek ); + arc.ReadObject( &avoid ); + arc.ReadBoolean( &avoiding ); + arc.ReadString( &anim ); + arc.ReadFloat( &avoidtime ); + arc.ReadVector( &avoidvec ); + } + +class EXPORT_FROM_DLL Fly : public Behavior + { + private: + Seek seek; + ObstacleAvoidance2 avoid; + str anim; + float avoidtime; + Vector avoidvec; + + public: + CLASS_PROTOTYPE( Fly ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Fly::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &seek ); + arc.WriteObject( &avoid ); + arc.WriteString( anim ); + arc.WriteFloat( avoidtime ); + arc.WriteVector( avoidvec ); + } + +inline EXPORT_FROM_DLL void Fly::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &seek ); + arc.ReadObject( &avoid ); + arc.ReadString( &anim ); + arc.ReadFloat( &avoidtime ); + arc.ReadVector( &avoidvec ); + } + +class EXPORT_FROM_DLL FlyCloseAttack : public Behavior + { + private: + Seek seek; + ObstacleAvoidance2 avoid; + qboolean avoiding; + str anim; + float avoidtime; + Vector avoidvec; + + public: + CLASS_PROTOTYPE( FlyCloseAttack ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void FlyCloseAttack::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &seek ); + arc.WriteObject( &avoid ); + arc.WriteBoolean( avoiding ); + arc.WriteString( anim ); + arc.WriteFloat( avoidtime ); + arc.WriteVector( avoidvec ); + } + +inline EXPORT_FROM_DLL void FlyCloseAttack::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &seek ); + arc.ReadObject( &avoid ); + arc.ReadBoolean( &avoiding ); + arc.ReadString( &anim ); + arc.ReadFloat( &avoidtime ); + arc.ReadVector( &avoidvec ); + } + +class EXPORT_FROM_DLL Wander : public Behavior + { + private: + Seek seek; + ObstacleAvoidance2 avoid; + str anim; + float avoidtime; + Vector avoidvec; + + public: + CLASS_PROTOTYPE( Wander ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Wander::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &seek ); + arc.WriteObject( &avoid ); + arc.WriteString( anim ); + arc.WriteFloat( avoidtime ); + arc.WriteVector( avoidvec ); + } + +inline EXPORT_FROM_DLL void Wander::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &seek ); + arc.ReadObject( &avoid ); + arc.ReadString( &anim ); + arc.ReadFloat( &avoidtime ); + arc.ReadVector( &avoidvec ); + } + + +class EXPORT_FROM_DLL WanderCloseAttack : public Behavior + { + private: + Seek seek; + ObstacleAvoidance2 avoid; + qboolean avoiding; + str anim; + float avoidtime; + Vector avoidvec; + + public: + CLASS_PROTOTYPE( WanderCloseAttack ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void WanderCloseAttack::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.WriteObject( &seek ); + arc.WriteObject( &avoid ); + arc.WriteBoolean( avoiding ); + arc.WriteString( anim ); + arc.WriteFloat( avoidtime ); + arc.WriteVector( avoidvec ); + } + +inline EXPORT_FROM_DLL void WanderCloseAttack::Unarchive + ( + Archiver &arc + ) + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &seek ); + arc.ReadObject( &avoid ); + arc.ReadBoolean( &avoiding ); + arc.ReadString( &anim ); + arc.ReadFloat( &avoidtime ); + arc.ReadVector( &avoidvec ); + } + +class EXPORT_FROM_DLL GetCloseToEnemy : public Behavior + { + private: + float howclose; + str anim; + Chase chase; + int state; + float nextsearch; + + public: + CLASS_PROTOTYPE( GetCloseToEnemy ); + + GetCloseToEnemy(); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void GetCloseToEnemy::Archive + ( + Archiver &arc + ) + + { + Behavior::Archive( arc ); + + arc.WriteFloat( howclose ); + arc.WriteString( anim ); + arc.WriteObject( &chase ); + arc.WriteInteger( state ); + arc.WriteFloat( nextsearch ); + } + +inline EXPORT_FROM_DLL void GetCloseToEnemy::Unarchive + ( + Archiver &arc + ) + + { + Behavior::Unarchive( arc ); + + arc.ReadFloat( &howclose ); + arc.ReadString( &anim ); + arc.ReadObject( &chase ); + arc.ReadInteger( &state ); + arc.ReadFloat( &nextsearch ); + } + +class EXPORT_FROM_DLL PlayAnimSeekEnemy : public Behavior + { + private: + Aim aim; + int mode; + qboolean animdone; + str anim; + str oldanim; + + public: + CLASS_PROTOTYPE( PlayAnimSeekEnemy ); + + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void PlayAnimSeekEnemy::Archive + ( + Archiver &arc + ) + + { + Behavior::Archive( arc ); + + arc.WriteObject( &aim ); + arc.WriteInteger( mode ); + arc.WriteBoolean( animdone ); + arc.WriteString( anim ); + arc.WriteString( oldanim ); + } + +inline EXPORT_FROM_DLL void PlayAnimSeekEnemy::Unarchive + ( + Archiver &arc + ) + + { + Behavior::Unarchive( arc ); + + arc.ReadObject( &aim ); + arc.ReadInteger( &mode ); + arc.ReadBoolean( &animdone ); + arc.ReadString( &anim ); + arc.ReadString( &oldanim ); + } + +#endif /* behavior.h */ diff --git a/bouncingbetty.cpp b/bouncingbetty.cpp new file mode 100644 index 0000000..3d29354 --- /dev/null +++ b/bouncingbetty.cpp @@ -0,0 +1,396 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/bouncingbetty.cpp $ +// $Revision:: 24 $ +// $Author:: Jimdose $ +// $Date:: 11/12/98 11:30p $ +// +// 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/bouncingbetty.cpp $ +// +// 24 11/12/98 11:30p Jimdose +// changed impact_bodyimpact to impact_goryimpact +// +// 23 10/26/98 2:17p Aldie +// Upped the damage +// +// 22 10/24/98 12:50a Aldie +// Made betty launcher take damage +// +// 21 10/22/98 7:57p Markd +// put in proper pre-caching in all the classes +// +// 20 10/07/98 8:04p Aldie +// Move bubbles to client +// +// 19 9/01/98 3:05p Markd +// Rewrote explosion code +// +// 18 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 17 8/18/98 11:08p Markd +// Added new Alias System +// +// 16 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 +// +// 15 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 14 7/09/98 12:09a Jimdose +// explode no longer scales the explosion (explosion creates a temp entity now) +// +// 13 6/10/98 2:10p Aldie +// Updated damage function. +// +// 12 5/11/98 2:19p Markd +// Fixed randomsound stuff +// +// 11 5/03/98 4:30p Jimdose +// Changed Vector class. No longer includes PointsTo +// +// 10 4/18/98 5:45p Markd +// Removed SINED calls, because they are now in DEF file format +// +// 9 4/10/98 4:54p Jimdose +// made bubbles work +// +// 8 4/10/98 2:36a Jimdose +// Works with Q2 +// +// 7 4/08/98 4:22p Jimdose +// Getting ready to conver to Q2 +// +// 5 12/15/97 3:33p Jimdose +// BettyLauncher first switches to the betty model before going to the iris +// model in order to precache the sounds that the betty model uses. +// +// 4 12/15/97 1:34a Jimdose +// Increased splash damage from betty exploding +// +// 3 12/14/97 5:48p Jimdose +// Finished basic behaviour code +// +// 2 12/13/97 4:43p Jimdose +// Created file +// +// DESCRIPTION: +// + +#include "g_local.h" +#include "bouncingbetty.h" +#include "explosion.h" +#include "specialfx.h" + +Event EV_Betty_CheckVicinity( "checkvicinity" ); +Event EV_Betty_Launch( "launch" ); +Event EV_Betty_AttackFinished( "attack_finished" ); +Event EV_Betty_Fire( "fire" ); +Event EV_Betty_Detonate( "detonate" ); +Event EV_Betty_Explode( "explode" ); +Event EV_BettySpike_Bubbles( "bubble" ); + +CLASS_DECLARATION( Entity, BettyLauncher, "trap_bouncingbetty" ); + +ResponseDef BettyLauncher::Responses[] = + { + { &EV_Betty_CheckVicinity, ( Response )BettyLauncher::CheckVicinity }, + { &EV_Activate, ( Response )BettyLauncher::Launch }, + { &EV_Betty_Launch, ( Response )BettyLauncher::Launch }, + { &EV_Betty_AttackFinished, ( Response )BettyLauncher::AttackFinished }, + { &EV_Betty_Fire, ( Response )BettyLauncher::ReleaseBetty }, + { &EV_Killed, ( Response )BettyLauncher::Killed }, + { NULL, NULL } + }; + +BettyLauncher::BettyLauncher + ( + ) + + { + setModel( "iris.def" ); + modelIndex( "betty.def" ); + modelIndex( "bettyspike.def" ); + RandomAnimate( "idle", NULL ); + + setSize( "-16 -16 0", "16 16 2" ); + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_BBOX ); + health = G_GetFloatArg( "health", 200 ); + + takedamage = DAMAGE_YES; + flags |= FL_SPARKS; + + firing = false; + activator = 0; + + if ( !Targeted() ) + { + PostEvent( EV_Betty_CheckVicinity, 0.3f ); + } + } + +void BettyLauncher::Killed + ( + Event *ev + ) + + { + takedamage = DAMAGE_NO; + CreateExplosion( worldorigin, 30, 0.5, true, this, this, this ); + PostEvent( EV_Remove, 0 ); + } + +qboolean BettyLauncher::inRange + ( + Entity *ent + ) + + { + Vector delta; + float dist; + + delta = worldorigin - ent->worldorigin; + dist = delta.length(); + + if ( dist > BOUNCINGBETTY_RANGE ) + { + return false; + } + return true; + } + +void BettyLauncher::CheckVicinity + ( + Event *ev + ) + + { + Entity *ent; + edict_t *ed; + int i; + Event *e; + + if ( firing ) + { + return; + } + + for( i = 0; i < game.maxclients; i++ ) + { + ed = &g_edicts[ 1 + i ]; + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + ent = ed->entity; + if ( ( ent->health < 0 ) || ( ent->flags & FL_NOTARGET ) ) + { + continue; + } + + if ( inRange( ent ) ) + { + e = new Event( EV_Betty_Launch ); + e->AddEntity( ent ); + ProcessEvent( e ); + + // wait a couple seconds before checking again + PostEvent( EV_Betty_CheckVicinity, 2 ); + return; + } + } + + PostEvent( EV_Betty_CheckVicinity, 0.3f ); + } + +void BettyLauncher::Launch + ( + Event *ev + ) + + { + if ( !firing ) + { + firing = true; + activator = ( int )ev->GetEntity( 1 )->entnum; + RandomAnimate( "open", NULL ); + } + } + +void BettyLauncher::AttackFinished + ( + Event *ev + ) + + { + firing = false; + } + +void BettyLauncher::ReleaseBetty + ( + Event *ev + ) + + { + BouncingBetty *betty; + + betty = new BouncingBetty; + betty->Launch( worldorigin, activator ); + activator = 0; + } + +CLASS_DECLARATION( Entity, BouncingBetty, NULL ); + +ResponseDef BouncingBetty::Responses[] = + { + { &EV_Betty_Detonate, ( Response )BouncingBetty::Detonate }, + { &EV_Betty_Explode, ( Response )BouncingBetty::Explode }, + { NULL, NULL } + }; + +BouncingBetty::BouncingBetty() + { + setModel( "betty.def" ); + setSize( "-4 -4 0", "4 4 8" ); + + setMoveType( MOVETYPE_BOUNCE ); + setSolidType( SOLID_BBOX ); + activator = 0; + } + +void BouncingBetty::Launch + ( + Vector pos, + int activatorEnt + ) + + { + activator = activatorEnt; + + setOrigin( pos ); + worldorigin.copyTo( edict->s.old_origin ); + velocity = "0 0 400"; + avelocity = "0 180 0"; + watertype = gi.pointcontents( worldorigin.vec3() ); + + RandomAnimate( "detonate", NULL ); + } + +void BouncingBetty::Detonate + ( + Event *ev + ) + + { + RandomAnimate( "detonate", NULL ); + } + +void BouncingBetty::Explode + ( + Event *ev + ) + + { + BettySpike *spike; + Entity *ent; + Vector vec; + Vector v; + int i; + float r; + + ent = G_GetEntity( activator ); + + CreateExplosion( worldorigin, 150, 1.0f, true, this, ent, this ); + + vec = Vector( 25, 0, 0 ); + + r = G_Random( 360 ); + for( i = 0; i < 12; i++ ) + { + vec[ 1 ] = r; + vec.AngleVectors( &v, NULL, NULL ); + + spike = new BettySpike; + spike->Setup( worldorigin + v * 8, v ); + + r += 360.0 / 12.0; + } + + PostEvent( EV_Remove, 0 ); + } + +CLASS_DECLARATION( Entity, BettySpike, NULL ); + +ResponseDef BettySpike::Responses[] = + { + { &EV_Touch, ( Response )BettySpike::SpikeTouch }, + { NULL, NULL } + }; + +EXPORT_FROM_DLL void BettySpike::SpikeTouch + ( + Event *ev + ) + + { + Entity *other; + int kick = 10; + + if ( HitSky() ) + { + PostEvent( EV_Remove, 0 ); + return; + } + + other = ev->GetEntity( 1 ); + assert( other ); + + if ( other->health ) + { + other->Damage( this, this, 10, worldorigin, velocity, level.impact_trace.plane.normal, kick, 0, MOD_BETTYSPIKE, -1, -1, 1.0f); + RandomGlobalSound( "impact_goryimpact", 1 ); + } + else + { + RandomGlobalSound( "impact_bulletcase", 0.3 ); + } + + PostEvent( EV_Remove, 0 ); + } + +EXPORT_FROM_DLL void BettySpike::Setup + ( + Vector pos, + Vector dir + ) + + { + setModel( "bettyspike.def" ); + setMoveType( MOVETYPE_FLYMISSILE ); + setSolidType( SOLID_BBOX ); + setSize( "0 0 0", "0 0 0" ); + setOrigin( pos ); + worldorigin.copyTo( edict->s.old_origin ); + watertype = gi.pointcontents( worldorigin.vec3() ); + + // set missile speed + velocity = dir; + velocity.normalize(); + velocity *= 500 + G_CRandom( 100 ); + + angles = dir.toAngles(); + angles[ PITCH ] = - angles[ PITCH ]; + setAngles( angles ); + + PostEvent( EV_Remove, 5 ); + } diff --git a/bouncingbetty.h b/bouncingbetty.h new file mode 100644 index 0000000..d7402f2 --- /dev/null +++ b/bouncingbetty.h @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/bouncingbetty.h $ +// $Revision:: 10 $ +// $Author:: Aldie $ +// $Date:: 10/24/98 12:52a $ +// +// 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/bouncingbetty.h $ +// +// 10 10/24/98 12:52a Aldie +// Added killed event +// +// 9 10/07/98 8:04p Aldie +// Move bubbles to client +// +// 8 9/29/98 5:58p Markd +// put in archive and unarchive +// +// 7 4/10/98 2:36a Jimdose +// Works with Q2 +// +// 6 4/08/98 4:22p Jimdose +// Getting ready to conver to Q2 +// +// 4 12/15/97 1:34a Jimdose +// Increased the detection range for the betty +// +// 3 12/14/97 5:48p Jimdose +// Finished basic behaviour code +// +// 2 12/13/97 4:43p Jimdose +// Created file +// +// DESCRIPTION: +// + +#ifndef __BOUNCINGBETTY_H__ +#define __BOUNCINGBETTY_H__ + +#include "g_local.h" +#include "entity.h" + +#define BOUNCINGBETTY_RANGE 192 + +class EXPORT_FROM_DLL BettyLauncher : public Entity + { + protected: + qboolean firing; + int activator; + + public: + CLASS_PROTOTYPE( BettyLauncher ); + + BettyLauncher(); + qboolean inRange( Entity *ent ); + void CheckVicinity( Event *ev ); + void Launch( Event *ev ); + void AttackFinished( Event *ev ); + void ReleaseBetty( Event *ev ); + void Killed( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void BettyLauncher::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteBoolean( firing ); + arc.WriteInteger( activator ); + } + +inline EXPORT_FROM_DLL void BettyLauncher::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadBoolean( &firing ); + arc.ReadInteger( &activator ); + } + +class EXPORT_FROM_DLL BouncingBetty : public Entity + { + protected: + int activator; + + public: + CLASS_PROTOTYPE( BouncingBetty ); + + BouncingBetty(); + void Launch( Vector pos, int activatorEnt ); + void Detonate( Event *ev ); + void Explode( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void BouncingBetty::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteInteger( activator ); + } + +inline EXPORT_FROM_DLL void BouncingBetty::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadInteger( &activator ); + } + +class EXPORT_FROM_DLL BettySpike : public Entity + { + protected: + int activator; + + public: + CLASS_PROTOTYPE( BettySpike ); + + void SpikeTouch( Event *ev ); + void Setup( Vector pos, Vector dir ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void BettySpike::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteInteger( activator ); + } + +inline EXPORT_FROM_DLL void BettySpike::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadInteger( &activator ); + } + +#endif /* bouncingbetty.h */ diff --git a/box.cpp b/box.cpp new file mode 100644 index 0000000..4383f1c --- /dev/null +++ b/box.cpp @@ -0,0 +1,410 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/box.cpp $ +// $Revision:: 36 $ +// $Author:: Markd $ +// $Date:: 11/15/98 7:51p $ +// +// 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/box.cpp $ +// +// 36 11/15/98 7:51p Markd +// fixed boxes so that they wouldn't be case as Item's +// +// 35 11/13/98 2:41p Markd +// fixed box precaching +// +// 34 11/09/98 12:13a Aldie +// Do a modelindex on stuff in boxes +// +// 33 10/26/98 12:28a Markd +// Put in no jc support +// +// 32 10/25/98 2:38a Markd +// put in dialog time in box code +// +// 31 10/25/98 12:47a Markd +// fixed mutant flag for dialog +// +// 30 10/25/98 12:24a Markd +// Put in sounds when blade blows up boxes +// +// 29 10/19/98 12:10a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 28 10/17/98 7:35p Jimdose +// Changed G_CallSpawn to G_CallSpawn2 +// +// 27 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 26 8/18/98 11:08p Markd +// Added new Alias System +// +// 25 8/18/98 5:50p Aldie +// Added target activation to box killed function +// +// 24 8/12/98 3:18p Aldie +// +// 23 8/09/98 6:10p Aldie +// New box behavior +// +// 22 8/08/98 9:02p Aldie +// Removed unused var +// +// 21 8/08/98 8:22p Markd +// Took out random items from box code +// +// 20 7/14/98 6:56p Aldie +// Updated healths +// +// 19 6/24/98 12:39p Markd +// Added default tesselation percentage +// +// 18 6/18/98 5:41p Markd +// Fixed crash with boxes +// +// 17 6/18/98 2:00p Markd +// rewrote tesselation code +// +// 16 6/10/98 1:19p Markd +// Removed 357 bullets +// +// 15 5/27/98 9:04p Markd +// weighted boxes with more ammo +// +// 14 5/27/98 8:34p Markd +// Put more health in boxes +// +// 13 5/27/98 5:01a Markd +// Put in dynamic spawning of goodies when box is destroyed +// +// 12 5/25/98 7:51p Jimdose +// Made TellNeighborsToFall use G_NextEntity to check all entities +// +// 11 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 +// +// 10 5/24/98 1:03a Jimdose +// Added breaking sound event when killed +// +// 9 5/11/98 3:51p Markd +// Changed crate blowup sound +// +// 8 5/01/98 11:09a Markd +// Added sound to tesselation event +// +// 7 4/14/98 6:55p Markd +// Added thickness to tesselation +// +// 6 4/08/98 4:19p Jimdose +// Converted to Q2 +// +// 4 10/31/97 7:18p Markd +// Changed triagulate call and added a hidemodel call when blowing up +// +// 3 10/31/97 4:32p Markd +// Added shatter sound and also fixed a swapped MOVETYPE/SOLIDTYPE bug +// +// 2 10/30/97 7:42p Jimdose +// Created file +// +// DESCRIPTION: +// Explodable box that falls when boxes below it are destroyed. +// + +#include "g_local.h" +#include "entity.h" +#include "box.h" +#include "ammo.h" +#include "health.h" +#include "specialfx.h" + +Event EV_Box_Think( "think" ); + +ResponseDef Box::Responses[] = + { + { &EV_Box_Think, ( Response )Box::Falling }, + { &EV_Killed, ( Response )Box::Killed }, + { NULL, NULL } + }; + +/*****************************************************************************/ +/*SINED func_box (0 .5 .8) ? + + Explodable box that falls when boxes below it are destroyed. +"items" - List of classnames to spawn when the box is destroyed. Separate +each classname by spaces (E.g. Adrenaline RocketLauncher). Default is NULL. +"angles" - Orientation of the item that is spawned. +"health" - Health of the box ( default is 60 ) +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, Box, "func_box" ); + +EXPORT_FROM_DLL void Box::StartFalling + ( + void + ) + + { + movetime = 0; + velocity += "0 0 100"; + setMoveType( MOVETYPE_TOSS ); + setSolidType( SOLID_BBOX ); + setOrigin( worldorigin + Vector( 0, 0, 1 ) ); + PostEvent( EV_Box_Think, FRAMETIME ); + } + +EXPORT_FROM_DLL void Box::Falling + ( + Event *ev + ) + + { + if ( velocity != vec_zero ) + { + movetime = level.time + 1; + } + + if ( movetime < level.time ) + { + setMoveType( MOVETYPE_PUSH ); + setSolidType( SOLID_BSP ); + } + else + { + PostEvent( EV_Box_Think, FRAMETIME ); + } + } + +void Box::TellNeighborsToFall + ( + void + ) + + { + Entity *ent; + //Event *e; + Vector min; + Vector max; + Entity *next; + + min = absmin + Vector( 6, 6, 6 ); + max = absmax + Vector( -6, -6, 0 ); + + for( ent = G_NextEntity( world ); ent != NULL; ent = next ) + { + next = G_NextEntity( ent ); + + if ( ( ent != this ) && ent->isSubclassOf( Box ) ) + { + if ( !( ( ent->absmax[ 0 ] < min[ 0 ] ) || + ( ent->absmax[ 1 ] < min[ 1 ] ) || + ( ent->absmax[ 2 ] < min[ 2 ] ) || + ( ent->absmin[ 0 ] > max[ 0 ] ) || + ( ent->absmin[ 1 ] > max[ 1 ] ) || + ( ent->absmin[ 2 ] > max[ 2 ] ) ) ) + + { + if ( ent->takedamage != DAMAGE_NO ) + ( ( Box * )ent )->StartFalling(); + // Ok, it's a hack. + //if ( ent->takedamage != DAMAGE_NO ) + // { + // e = new Event( ev ); + // ent->ProcessEvent( e ); + // } + } + } + } + } + +void Box::Killed + ( + Event *ev + ) + + { + Entity *attacker; + Vector dir; + Vector org; + Entity *ent; + const char *s; + const char *token; + int width = 0; + int depth = 0; + int boxwidth; + char temp[ 128 ]; + const char *name; + int num; + Event *event; + qboolean spawned; + static float last_dialog_time = 0; + + hideModel(); + RandomGlobalSound( "impact_crateexplo", 1, CHAN_BODY, ATTN_NORM ); + + takedamage = DAMAGE_NO; + + TellNeighborsToFall(); + + ProcessEvent( EV_BreakingSound ); + + attacker = ev->GetEntity( 1 ); + dir = worldorigin - attacker->worldorigin; + TesselateModel + ( + this, + tess_min_size, + tess_max_size, + dir, + ev->GetInteger( 2 ), + tess_percentage, + tess_thickness, + vec3_origin + ); + + // + // fire off targets + // + name = Target(); + if ( name && strcmp( name, "" ) ) + { + num = 0; + do + { + num = G_FindTarget( num, name ); + if ( !num ) + { + break; + } + + ent = G_GetEntity( num ); + + event = new Event( EV_Activate ); + event->AddEntity( attacker ); + ent->PostEvent( event, 0 ); + } + while ( 1 ); + } + + + + // items holds the list of def files to spawn + s = items.c_str(); + + G_InitSpawnArguments(); + + if ( setangles ) + { + sprintf( temp, "%f %f %f", angles[ 0 ],angles[ 1 ],angles[ 2 ] ); + G_SetSpawnArg( "angles", temp ); + } + + spawned = false; + boxwidth = maxs[0]; + while (1) + { + token = COM_Parse(&s); + + if (!token[0]) + break; + + G_SetSpawnArg( "model", token ); + + if ( ( width * 32 ) > boxwidth ) + { + width = 0; + depth++; + } + + // Calculate and set the origin + org = worldorigin + Vector("0 0 32") + Vector("32 0 0") * width + Vector("0 32 0") * depth; + width++; + sprintf( temp, "%f %f %f", org[ 0 ], org[ 1 ], org[ 2 ] ); + G_SetSpawnArg( "origin", temp ); + + // Create the item + ent = ( Entity * )G_CallSpawn(); + spawned = true; + + // Postpone the Drop because the box is still there. + ent->PostponeEvent( EV_Item_DropToFloor, 0.1f ); + } + G_InitSpawnArguments(); + + if ( + spawned && + attacker->isClient() && + ( last_dialog_time < level.time ) && + ( !( attacker->flags & FL_SP_MUTANT ) ) && + ( !deathmatch->value ) + ) + { + char name[ 128 ]; + int num; + + last_dialog_time = level.time + 25; + if ( level.no_jc ) + { + num = (int)G_Random( 3 ) + 1; + } + else + { + num = (int)G_Random( 5 ) + 1; + } + sprintf( name, "global/universal_script.scr::blade_finds_item%d", num ); + ExecuteThread( name, true ); + } + PostEvent( EV_Remove, 0 ); + } + +Box::Box() + { + const char *text; + const char *s; + char token[ MAX_TOKEN_CHARS ]; + + movetime = 0; + showModel(); + setMoveType( MOVETYPE_PUSH ); + setSolidType( SOLID_BSP ); + setOrigin( origin ); + + health = G_GetIntArg( "health", 60 ); + max_health = health; + takedamage = DAMAGE_YES; + tess_thickness = 20; + text = G_GetSpawnArg( "items" ); + + if ( text ) + { + items = text; + s = items.c_str(); + while ( 1 ) + { + strcpy( token, COM_Parse(&s) ); + if ( !token[0] ) + break; + modelIndex( token ); + } + } + + setangles = ( G_GetSpawnArg( "angle" ) || G_GetSpawnArg( "angles" ) ); + if ( setangles ) + { + float angle; + angle = G_GetFloatArg( "angle", 0 ); + angles = G_GetVectorArg( "angles", Vector( 0, angle, 0 ) ); + } + } diff --git a/box.h b/box.h new file mode 100644 index 0000000..396484a --- /dev/null +++ b/box.h @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/box.h $ +// $Revision:: 6 $ +// $Author:: Markd $ +// $Date:: 9/29/98 5:58p $ +// +// 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/box.h $ +// +// 6 9/29/98 5:58p Markd +// put in archive and unarchive +// +// 5 8/09/98 6:11p Aldie +// New box behavior +// +// 4 4/08/98 4:19p Jimdose +// Converted to Q2 +// +// 2 10/30/97 7:42p Jimdose +// Created file +// +// DESCRIPTION: +// Explodable box that falls when boxes below it are destroyed. +// + +#ifndef __BOX_H__ +#define __BOX_H__ + +#include "g_local.h" +#include "entity.h" + +class EXPORT_FROM_DLL Box : public Entity + { + private: + float movetime; + str items; + qboolean setangles; + + public: + CLASS_PROTOTYPE( Box ); + + Box(); + void StartFalling( void ); + void Falling( Event *ev ); + void TellNeighborsToFall( void ); + virtual void Killed( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Box::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteFloat( movetime ); + arc.WriteString( items ); + arc.WriteBoolean( setangles ); + } + +inline EXPORT_FROM_DLL void Box::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadFloat( &movetime ); + arc.ReadString( &items ); + arc.ReadBoolean( &setangles ); + } + +#endif /* box.h */ diff --git a/bspline.cpp b/bspline.cpp new file mode 100644 index 0000000..1382f27 --- /dev/null +++ b/bspline.cpp @@ -0,0 +1,677 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/bspline.cpp $ +// $Revision:: 12 $ +// $Author:: Jimdose $ +// $Date:: 10/19/98 9:51p $ +// +// Copyright (C) 1998 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/bspline.cpp $ +// +// 12 10/19/98 9:51p Jimdose +// fixed savegame bugs with bspline +// +// 11 10/10/98 9:12p Markd +// Fixed angle errors with bsplines +// +// 10 8/15/98 2:39p Markd +// fixed up some bspline stuff +// +// 9 7/11/98 6:31p Markd +// removed valid orientation, simplified code +// +// 8 7/10/98 1:11p Markd +// Added additional two paramter append control point +// +// 7 7/08/98 12:41p Markd +// Added speed and quaternion support +// +// 6 7/02/98 9:48p Markd +// added orientation +// +// 5 5/26/98 7:55p Jimdose +// Added Drawcurve with offset +// +// 4 5/07/98 10:40p Jimdose +// Added spline type for selecting between looping and non-looping curves +// +// 3 5/05/98 2:37p Jimdose +// Added code to allow spline loop and clamping the spline start and end +// +// 2 5/03/98 4:42p Jimdose +// Added file to Sin +// +// DESCRIPTION: +// Uniform non-rational bspline class. +// + +#include "g_local.h" +#include "BSpline.h" + +void BSpline::Set + ( + Vector *control_points_, + int num_control_points_, + splinetype_t type + ) + + { + int i; + + SetType( type ); + + has_orientation = false; + + if ( control_points ) + { + delete [] control_points; + control_points = NULL; + } + + num_control_points = num_control_points_; + if ( num_control_points ) + { + control_points = new BSplineControlPoint[ num_control_points ]; + assert( control_points ); + + for( i = 0; i < num_control_points; i++ ) + { + control_points[ i ].Set( control_points_[ i ] ); + } + } + } + +void BSpline::Set + ( + Vector *control_points_, + Vector *control_orients_, + float *control_speeds_, + int num_control_points_, + splinetype_t type + ) + + { + int i; + + SetType( type ); + + has_orientation = true; + + if ( control_points ) + { + delete [] control_points; + control_points = NULL; + } + + num_control_points = num_control_points_; + if ( num_control_points ) + { + control_points = new BSplineControlPoint[ num_control_points ]; + assert( control_points ); + + for( i = 0; i < num_control_points; i++ ) + { + control_points[ i ].Set( control_points_[ i ], control_orients_[ i ], control_speeds_[ i ] ); + } + } + } + +void BSpline::Clear + ( + void + ) + + { + if( control_points ) + { + delete [] control_points; + control_points = NULL; + } + num_control_points = 0; + has_orientation = false; + } + +inline float BSpline::EvalNormal + ( + float u, + Vector& pos, + Vector& orient + ) + + { + int segment_id; + float B[ 4 ]; + float tmp; + float u_2; + float u_3; + Vector ang; + float roll; + float speed; + + segment_id = ( int )u; + if ( segment_id < 0 ) + { + segment_id = 0; + } + if ( segment_id > num_control_points - 4 ) + { + segment_id = num_control_points - 4; + } + u -= ( float )segment_id; + + u_2 = u * u; + u_3 = u * u_2; + + tmp = 1 - u; + B[ 0 ] = ( tmp * tmp * tmp ) * ( 1.0f / 6.0f ); + B[ 1 ] = ( 3.0f * u_3 - 6.0f * u_2 + 4.0f ) * ( 1.0f / 6.0f ); + B[ 2 ] = ( -3.0f * u_3 + 3.0f * u_2 + 3.0f * u + 1 ) * ( 1.0f / 6.0f ); + B[ 3 ] = u_3 * ( 1.0f / 6.0f ); + + pos = + *control_points[ 0 + segment_id ].GetPosition() * B[ 0 ] + + *control_points[ 1 + segment_id ].GetPosition() * B[ 1 ] + + *control_points[ 2 + segment_id ].GetPosition() * B[ 2 ] + + *control_points[ 3 + segment_id ].GetPosition() * B[ 3 ]; + + ang = + *control_points[ 0 + segment_id ].GetOrientation() * B[ 0 ] + + *control_points[ 1 + segment_id ].GetOrientation() * B[ 1 ] + + *control_points[ 2 + segment_id ].GetOrientation() * B[ 2 ] + + *control_points[ 3 + segment_id ].GetOrientation() * B[ 3 ]; + + roll = + *control_points[ 0 + segment_id ].GetRoll() * B[ 0 ] + + *control_points[ 1 + segment_id ].GetRoll() * B[ 1 ] + + *control_points[ 2 + segment_id ].GetRoll() * B[ 2 ] + + *control_points[ 3 + segment_id ].GetRoll() * B[ 3 ]; + + speed = + *control_points[ 0 + segment_id ].GetSpeed() * B[ 0 ] + + *control_points[ 1 + segment_id ].GetSpeed() * B[ 1 ] + + *control_points[ 2 + segment_id ].GetSpeed() * B[ 2 ] + + *control_points[ 3 + segment_id ].GetSpeed() * B[ 3 ]; + + orient = ang.toAngles(); + orient[ ROLL ] = roll; + + return speed; + } + +inline float BSpline::EvalLoop + ( + float t, + Vector& pos, + Vector& orient + ) + + { + Vector retval; + Vector ang; + float speed; + float roll; + int segment_id; + int next_id; + float B[ 4 ]; + float tmp; + float u; + float u_2; + float u_3; + int i; + int j; + + segment_id = ( int )floor( t ); + u = t - floor( t ); + + segment_id %= num_control_points; + if ( segment_id < 0 ) + { + segment_id += num_control_points; + } + + u_2 = u * u; + u_3 = u * u_2; + + tmp = 1 - u; + B[ 0 ] = ( tmp * tmp * tmp ) * ( 1.0f / 6.0f ); + B[ 1 ] = ( 3.0f * u_3 - 6.0f * u_2 + 4.0f ) * ( 1.0f / 6.0f ); + B[ 2 ] = ( -3.0f * u_3 + 3.0f * u_2 + 3.0f * u + 1 ) * ( 1.0f / 6.0f ); + B[ 3 ] = u_3 * ( 1.0f / 6.0f ); + + speed = 0; + roll = 0; + + for( i = 0, j = segment_id; i < 4; i++, j++ ) + { + if ( j >= num_control_points ) + { + j -= ( num_control_points - loop_control_point ); + } + + retval += *control_points[ j ].GetPosition() * B[ i ]; + ang += *control_points[ j ].GetOrientation() * B[ i ]; + speed += *control_points[ j ].GetSpeed() * B[ i ]; + roll += *control_points[ j ].GetRoll() * B[ i ]; + } + + pos = retval; + + next_id = segment_id + 1; + if ( next_id >= num_control_points ) + { + next_id -= ( num_control_points - loop_control_point ); + } + orient = ang.toAngles(); + orient[ ROLL ] = roll; + + return speed; + } + +inline float BSpline::EvalClamp + ( + float t, + Vector& pos, + Vector& orient + ) + + { + Vector retval; + Vector ang; + int segment_id; + int next_id; + float B[ 4 ]; + float tmp; + float u; + float u_2; + float u_3; + int i; + int j; + float speed; + float roll; + + segment_id = ( int )floor( t ); + u = t - floor( t ); + + u_2 = u * u; + u_3 = u * u_2; + + tmp = 1 - u; + B[ 0 ] = ( tmp * tmp * tmp ) * ( 1.0f / 6.0f ); + B[ 1 ] = ( 3.0f * u_3 - 6.0f * u_2 + 4.0f ) * ( 1.0f / 6.0f ); + B[ 2 ] = ( -3.0f * u_3 + 3.0f * u_2 + 3.0f * u + 1 ) * ( 1.0f / 6.0f ); + B[ 3 ] = u_3 * ( 1.0f / 6.0f ); + + speed = 0; + roll = 0; + for( i = 0; i < 4; i++, segment_id++ ) + { + j = segment_id; + if ( j < 0 ) + { + j = 0; + } + else if ( j >= num_control_points ) + { + j = num_control_points - 1; + } + + retval += *control_points[ j ].GetPosition() * B[ i ]; + ang += *control_points[ j ].GetOrientation() * B[ i ]; + speed += *control_points[ j ].GetSpeed() * B[ i ]; + roll += *control_points[ j ].GetRoll() * B[ i ]; + } + + pos = retval; + + next_id = segment_id + 1; + if ( segment_id < 0 ) + { + segment_id = 0; + } + if ( segment_id >= num_control_points ) + { + segment_id = num_control_points - 1; + } + if ( next_id < 0 ) + { + next_id = 0; + } + if ( next_id >= num_control_points ) + { + next_id = num_control_points - 1; + } + orient = ang.toAngles(); + orient[ ROLL ] = roll; + + return speed; + } + + +Vector BSpline::Eval + ( + float u + ) + + { + Vector pos; + Vector orient; + + switch( curvetype ) + { + default: + case SPLINE_NORMAL : + EvalNormal( u, pos, orient ); + break; + + case SPLINE_CLAMP: + EvalClamp( u, pos, orient ); + break; + + case SPLINE_LOOP: + EvalLoop( u, pos, orient ); + break; + } + return pos; + } + +float BSpline::Eval + ( + float u, + Vector &pos, + Vector &orient + ) + + { + switch( curvetype ) + { + default: + case SPLINE_NORMAL : + return EvalNormal( u, pos, orient ); + break; + + case SPLINE_CLAMP: + return EvalClamp( u, pos, orient ); + break; + + case SPLINE_LOOP: + return EvalLoop( u, pos, orient ); + break; + } + } + +void BSpline::DrawControlSegments + ( + void + ) + + { + int i; + + G_BeginLine(); + for( i = 0; i < num_control_points; i++ ) + { + G_Vertex( *control_points[ i ].GetPosition() ); + } + G_EndLine(); + } + +void BSpline::DrawCurve + ( + int num_subdivisions + ) + + { + float u; + float du; + + du = 1.0f / ( float )num_subdivisions; + + G_BeginLine(); + for( u = 0.0f; u <= ( float )num_control_points; u += du ) + { + G_Vertex( ( Vector )Eval( u ) ); + } + G_EndLine(); + } + +void BSpline::DrawCurve + ( + Vector offset, + int num_subdivisions + ) + + { + float u; + float du; + + du = 1.0f / ( float )num_subdivisions; + + G_BeginLine(); + for( u = 0.0f; u <= ( float )num_control_points; u += du ) + { + G_Vertex( offset + ( Vector )Eval( u ) ); + } + G_EndLine(); + } + +void BSpline::AppendControlPoint + ( + const Vector& new_control_point + ) + + { + BSplineControlPoint *old_control_points; + int i; + + old_control_points = control_points; + num_control_points++; + + control_points = new BSplineControlPoint[num_control_points]; + assert( control_points ); + + if ( old_control_points ) + { + for( i = 0; i < num_control_points - 1; i++ ) + { + control_points[ i ] = old_control_points[ i ]; + } + delete [] old_control_points; + } + + control_points[ num_control_points - 1 ].Set( new_control_point ); + } + +void BSpline::AppendControlPoint + ( + const Vector& new_control_point, + const float& speed + ) + + { + BSplineControlPoint *old_control_points; + int i; + + old_control_points = control_points; + num_control_points++; + + control_points = new BSplineControlPoint[num_control_points]; + assert( control_points ); + + if ( old_control_points ) + { + for( i = 0; i < num_control_points - 1; i++ ) + { + control_points[ i ] = old_control_points[ i ]; + } + delete [] old_control_points; + } + + control_points[ num_control_points - 1 ].Set( new_control_point, speed ); + } + +void BSpline::AppendControlPoint + ( + const Vector& new_control_point, + const Vector& new_control_orient, + const float& new_control_speed + ) + + { + BSplineControlPoint *old_control_points; + int i; + + has_orientation = true; + + old_control_points = control_points; + num_control_points++; + + control_points = new BSplineControlPoint[num_control_points]; + assert( control_points ); + + if ( old_control_points ) + { + for( i = 0; i < num_control_points - 1; i++ ) + { + control_points[ i ] = old_control_points[ i ]; + } + delete [] old_control_points; + } + + control_points[ num_control_points - 1 ].Set( new_control_point, new_control_orient, new_control_speed ); + } + +void BSpline::SetLoopPoint + ( + const Vector& pos + ) + { + int i; + + for( i = 0; i < num_control_points; i++ ) + { + if ( pos == *control_points[ i ].GetPosition() ) + { + loop_control_point = i; + break; + } + } + } + +int BSpline::PickControlPoint + ( + const Vector& window_point, + float pick_size + ) + + { + int i; + float closest_dist_2; + int closest_index; + float dist_2; + Vector delta; + + closest_index = -1; + closest_dist_2 = 1000000.0f; + for( i = 0; i < num_control_points; i++ ) + { + delta = window_point - *control_points[ i ].GetPosition(); + dist_2 = delta * delta; + if ( dist_2 < closest_dist_2 ) + { + closest_dist_2 = dist_2; + closest_index = i; + } + } + + if ( pick_size * pick_size >= closest_dist_2 ) + { + return closest_index; + } + else + { + return -1; + } + } + +CLASS_DECLARATION( Entity, SplinePath, "info_splinepath" ); + +Event EV_SplinePath_Create( "SplinePath_create" ); + +ResponseDef SplinePath::Responses[] = + { + { &EV_SplinePath_Create, ( Response )SplinePath::CreatePath }, + { NULL, NULL } + }; + +SplinePath::SplinePath() + { + owner = this; + next = NULL; + loop = NULL; + loop_name = G_GetStringArg( "loop" ); + angles = G_GetVectorArg( "angles" ); + speed = G_GetFloatArg( "speed", 1 ); + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_NOT ); + hideModel(); + + if ( !LoadingSavegame ) + { + PostEvent( EV_SplinePath_Create, 0 ); + } + } + +void SplinePath::CreatePath + ( + Event *ev + ) + + { + const char *target; + int num; + + // Make the path from the targetlist. + target = Target(); + if ( target[ 0 ] ) + { + if ( num = G_FindTarget( 0, target ) ) + { + next = ( SplinePath * )G_GetEntity( num ); + next->owner = this; + } + else + { + gi.error( "SplinePath::CreatePath: target %s not found\n", target ); + } + } + if ( loop_name.length() ) + { + if ( num = G_FindTarget( 0, loop_name.c_str() ) ) + { + loop = ( SplinePath * )G_GetEntity( num ); + } + } + } + +SplinePath *SplinePath::GetNext + ( + void + ) + + { + return next; + } + +SplinePath *SplinePath::GetLoop + ( + void + ) + + { + return loop; + } diff --git a/bspline.h b/bspline.h new file mode 100644 index 0000000..64d3772 --- /dev/null +++ b/bspline.h @@ -0,0 +1,609 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/bspline.h $ +// $Revision:: 19 $ +// $Author:: Jimdose $ +// $Date:: 10/25/98 11:52p $ +// +// Copyright (C) 1998 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/bspline.h $ +// +// 19 10/25/98 11:52p Jimdose +// added EXPORT_TEMPLATE +// +// 18 10/19/98 9:52p Jimdose +// fixed savegame bugs with bspline +// +// 17 10/11/98 2:03p Markd +// Inverted pitch on orientation +// +// 16 10/10/98 9:12p Markd +// Fixed angle errors with bsplines +// +// 15 9/24/98 1:19a Markd +// bullet proofed some equating bspline code +// +// 14 9/23/98 11:00p Markd +// put in some garbage collection on stuff that wasn't freed up +// +// 13 9/21/98 10:15p Markd +// Putting archiving and unarchiving functions in +// +// 12 8/15/98 2:40p Markd +// fixed bspline stuff +// +// 11 7/11/98 6:31p Markd +// removed valid orientation, simplified code +// +// 10 7/10/98 2:10p Markd +// Endpoint now returns number of controlpoints. +// +// 9 7/10/98 1:12p Markd +// Added additonal setup function which takes speed as well as position +// +// 8 7/09/98 11:54p Markd +// Put in default speed of 1 +// +// 7 7/08/98 12:42p Markd +// Added quaternion support +// +// 6 7/02/98 9:48p Markd +// added orientation +// +// 5 5/26/98 7:56p Jimdose +// added scripted cameras +// +// 4 5/07/98 10:41p Jimdose +// Added spline type for selecting between looping and non-looping curves +// +// 3 5/05/98 2:37p Jimdose +// Added code to allow spline loop and clamping the spline start and end +// +// 2 5/03/98 4:42p Jimdose +// Added file to Sin +// +// DESCRIPTION: +// Uniform non-rational bspline class. +// + +#ifndef __BSPLINE_H__ +#define __BSPLINE_H__ + +#include "g_local.h" +#include "Vector.h" + +typedef enum + { + SPLINE_NORMAL, + SPLINE_LOOP, + SPLINE_CLAMP + } splinetype_t; + +class EXPORT_FROM_DLL BSplineControlPoint + { + private: + float roll; + Vector position; + Vector orientation; + float speed; + + public: + BSplineControlPoint(); + BSplineControlPoint( Vector pos, Vector orient, float speed ); + BSplineControlPoint( Vector pos ); + void Clear( void ); + void Set( Vector pos ); + void Set( Vector pos, float speed ); + void Set( Vector pos, Vector orient, float speed ); + void Get( Vector& pos, Vector& orient, float& speed ); + void Get( Vector& pos ); + Vector *GetPosition( void ); + Vector *GetOrientation( void ); + float *GetRoll( void ); + float *GetSpeed( void ); + void operator=( BSplineControlPoint &point ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void BSplineControlPoint::Archive + ( + Archiver &arc + ) + + { + arc.WriteVector( position ); + arc.WriteVector( orientation ); + arc.WriteFloat( speed ); + arc.WriteFloat( roll ); + } + +inline EXPORT_FROM_DLL void BSplineControlPoint::Unarchive + ( + Archiver &arc + ) + + { + arc.ReadVector( &position ); + arc.ReadVector( &orientation ); + arc.ReadFloat( &speed ); + arc.ReadFloat( &roll ); + } + +inline void BSplineControlPoint::operator= + ( + BSplineControlPoint &point + ) + + { + position = point.position; + orientation = point.orientation; + speed = point.speed; + roll = point.roll; + } + +inline BSplineControlPoint::BSplineControlPoint() + { + roll = 0; + speed = 1; + } + +inline BSplineControlPoint::BSplineControlPoint + ( + Vector pos + ) + + { + speed = 1; + position = pos; + } + +inline BSplineControlPoint::BSplineControlPoint + ( + Vector pos, + Vector orient, + float speed + ) + + { + position = pos; + orient[ PITCH ] = -orient[ PITCH ]; + orient.AngleVectors( &orientation, NULL, NULL ); + roll = orient[ ROLL ]; + if ( roll > 180 ) + { + roll -= 360; + } + if ( roll < -180 ) + { + roll += 360; + } + this->speed = speed; + } + +inline void BSplineControlPoint::Clear + ( + void + ) + + { + roll = 0; + position = "0 0 0"; + vec_zero.AngleVectors( &orientation, NULL, NULL ); + speed = 1.0f; + } + +inline void BSplineControlPoint::Set + ( + Vector pos + ) + + { + speed = 1; + position = pos; + } + +inline void BSplineControlPoint::Set + ( + Vector pos, + float pointspeed + ) + + { + speed = pointspeed; + position = pos; + } + +inline void BSplineControlPoint::Set + ( + Vector pos, + Vector orient, + float speed + ) + + { + position = pos; + orient[ PITCH ] = -orient[ PITCH ]; + orient.AngleVectors( &orientation, NULL, NULL ); + roll = orient[ ROLL ]; + if ( roll > 180 ) + { + roll -= 360; + } + if ( roll < -180 ) + { + roll += 360; + } + this->speed = speed; + } + +inline void BSplineControlPoint::Get + ( + Vector& pos + ) + { + pos = position; + } + +inline Vector *BSplineControlPoint::GetPosition + ( + void + ) + + { + return &position; + } + +inline void BSplineControlPoint::Get + ( + Vector& pos, + Vector& orient, + float& speed + ) + + { + pos = position; + orient = orientation; + speed = this->speed; + } + +inline Vector *BSplineControlPoint::GetOrientation + ( + void + ) + { + return &orientation; + } + +inline float *BSplineControlPoint::GetRoll + ( + void + ) + { + return &roll; + } + +inline float *BSplineControlPoint::GetSpeed + ( + void + ) + { + return &speed; + } + +class EXPORT_FROM_DLL BSpline + { + private: + BSplineControlPoint *control_points; + int num_control_points; + int loop_control_point; + splinetype_t curvetype; + qboolean has_orientation; + + float EvalNormal( float u, Vector &pos, Vector& orient ); + float EvalLoop( float u, Vector &pos, Vector& orient ); + float EvalClamp( float u, Vector &pos, Vector& orient ); + + public: + BSpline(); + ~BSpline(); + BSpline( Vector *control_points_, int num_control_points_, splinetype_t type ); + BSpline( Vector *control_points_, Vector *control_orients_, float *control_speeds_, int num_control_points_, splinetype_t type ); + void operator=( BSpline &spline ); + void SetType( splinetype_t type ); + int GetType( void ); + void Clear( void ); + void Set( Vector *control_points_, int num_control_points_, splinetype_t type ); + void Set( Vector *control_points_, Vector *control_orients_, float *control_speeds_, int num_control_points_, splinetype_t type ); + void AppendControlPoint( const Vector& new_control_point ); + void AppendControlPoint( const Vector& new_control_point, const float& speed ); + void AppendControlPoint( const Vector& new_control_point, const Vector& new_control_orient, const float& speed ); + Vector Eval( float u ); + float Eval( float u, Vector& pos, Vector& orient ); + + void DrawControlSegments( void ); + void DrawCurve( int num_subdivisions ); + void DrawCurve( Vector offset, int num_subdivisions ); + + void SetLoopPoint( const Vector& pos ); + + float EndPoint( void ); + + // return the index of the control point picked or -1 if none. + int PickControlPoint( const Vector& window_point, float pick_size ); + + Vector *GetControlPoint( int id ); + void GetControlPoint( int id, Vector& pos, Vector& orient, float& speed ); + void SetControlPoint( int id, const Vector& new_control_point ); + void SetControlPoint( int id, const Vector& new_control_point, const Vector& new_control_orient, const float& speed ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline BSpline::BSpline() + { + has_orientation = false; + control_points = NULL; + num_control_points = 0; + loop_control_point = 0; + curvetype = SPLINE_NORMAL; + } + +inline BSpline::~BSpline() + { + if ( control_points ) + { + delete [] control_points; + control_points = NULL; + } + } + +inline BSpline::BSpline + ( + Vector *control_points_, + int num_control_points_, + splinetype_t type + ) + + { + has_orientation = false; + control_points = NULL; + num_control_points = 0; + loop_control_point = 0; + curvetype = SPLINE_NORMAL; + + Set( control_points_, num_control_points_, type ); + } + +inline BSpline::BSpline + ( + Vector *control_points_, + Vector *control_orients_, + float *control_speeds_, + int num_control_points_, + splinetype_t type + ) + + { + has_orientation = false; + control_points = NULL; + num_control_points = 0; + loop_control_point = 0; + curvetype = SPLINE_NORMAL; + + Set( control_points_, control_orients_, control_speeds_, num_control_points_, type ); + } + +inline void BSpline::operator= + ( + BSpline &spline + ) + + { + int i; + + Clear(); + num_control_points = spline.num_control_points; + loop_control_point = spline.loop_control_point; + curvetype = spline.curvetype; + has_orientation = spline.has_orientation; + + control_points = new BSplineControlPoint[num_control_points]; + assert( control_points ); + for ( i = 0; i < num_control_points ; i++ ) + control_points[ i ] = spline.control_points[ i ]; + } + +inline void BSpline::SetType + ( + splinetype_t type + ) + + { + curvetype = type; + } + +inline int BSpline::GetType + ( + void + ) + + { + return curvetype; + } + +inline float BSpline::EndPoint + ( + void + ) + + { + return num_control_points; + } + +inline Vector *BSpline::GetControlPoint + ( + int id + ) + + { + assert( id >= 0 ); + assert( id < num_control_points ); + if ( ( id < 0 ) && ( id >= num_control_points ) ) + { + // probably wrong, but if we're in release mode we have no recourse + id = 0; + } + + return control_points[ id ].GetPosition(); + } + +inline void BSpline::GetControlPoint + ( + int id, + Vector& pos, + Vector& orient, + float& speed + ) + + { + assert( id >= 0 ); + assert( id < num_control_points ); + if ( ( id >= 0 ) && ( id < num_control_points ) ) + { + control_points[ id ].Get( pos, orient, speed ); + } + } + +inline void BSpline::SetControlPoint + ( + int id, + const Vector& new_control_point + ) + + { + assert( id >= 0 ); + assert( id < num_control_points ); + if ( ( id >= 0 ) && ( id < num_control_points ) ) + { + control_points[ id ].Set( new_control_point ); + } + } + +inline void BSpline::SetControlPoint + ( + int id, + const Vector& new_control_point, + const Vector& new_control_orient, + const float& speed + ) + + { + assert( id >= 0 ); + assert( id < num_control_points ); + if ( ( id >= 0 ) && ( id < num_control_points ) ) + { + control_points[ id ].Set( new_control_point, new_control_orient, speed ); + } + } + +inline EXPORT_FROM_DLL void BSpline::Archive + ( + Archiver &arc + ) + { + int i; + + arc.WriteInteger( num_control_points ); + arc.WriteInteger( loop_control_point ); + arc.WriteInteger( curvetype ); + arc.WriteBoolean( has_orientation ); + for( i = 0; i < num_control_points; i++ ) + { + control_points[ i ].Archive( arc ); + } + } + +inline EXPORT_FROM_DLL void BSpline::Unarchive + ( + Archiver &arc + ) + { + int i; + + arc.ReadInteger( &num_control_points ); + arc.ReadInteger( &loop_control_point ); + arc.ReadInteger( &i ); + curvetype = ( splinetype_t )i; + arc.ReadBoolean( &has_orientation ); + control_points = new BSplineControlPoint[ num_control_points ]; + for( i = 0; i < num_control_points; i++ ) + { + control_points[ i ].Unarchive( arc ); + } + } + +class SplinePath; +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL SafePtr; +#endif +typedef SafePtr SplinePathPtr; + +class EXPORT_FROM_DLL SplinePath : public Entity + { + protected: + SplinePathPtr owner; + SplinePathPtr next; + SplinePathPtr loop; + str loop_name; + + void CreatePath( Event *ev ); + + public: + float speed; + + CLASS_PROTOTYPE( SplinePath ); + + SplinePath(); + SplinePath *GetNext( void ); + SplinePath *GetLoop( void ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void SplinePath::Archive + ( + Archiver &arc + ) + + { + Entity::Archive( arc ); + + arc.WriteSafePointer( owner ); + arc.WriteSafePointer( next ); + arc.WriteSafePointer( loop ); + arc.WriteString( loop_name ); + arc.WriteFloat( speed ); + } + +inline EXPORT_FROM_DLL void SplinePath::Unarchive + ( + Archiver &arc + ) + + { + Entity::Unarchive( arc ); + + arc.ReadSafePointer( &owner ); + arc.ReadSafePointer( &next ); + arc.ReadSafePointer( &loop ); + arc.ReadString( &loop_name ); + arc.ReadFloat( &speed ); + } + +#endif /* __BSPLINE_H__ */ diff --git a/bullet.cpp b/bullet.cpp new file mode 100644 index 0000000..6c3997f --- /dev/null +++ b/bullet.cpp @@ -0,0 +1,515 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/bullet.cpp $ +// $Revision:: 71 $ +// $Author:: Markd $ +// $Date:: 10/22/98 7:57p $ +// +// 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/bullet.cpp $ +// +// 71 10/22/98 7:57p Markd +// put in proper pre-caching in all the classes +// +// 70 10/20/98 8:26p Markd +// Added Attacker to DamageSurface stuff +// +// 69 10/13/98 5:27p Markd +// revamped ricochet stuff +// +// 68 10/13/98 12:35p Markd +// Fixed tracers and bullet holes on non-player guys +// +// 67 10/05/98 11:30p Markd +// Added MakeBreakingSound when firing at things +// +// 66 10/04/98 10:36p Markd +// put in ricochet code +// +// 65 9/27/98 3:53p Aldie +// Put in some debugging information +// +// 64 9/18/98 8:14p Markd +// rewrote surface system so that surfaces are now damaged by surface name +// instead of by surfinfo +// +// 63 8/31/98 7:45p Aldie +// Updated surface data structure and removed surfinfo field +// +// 62 8/29/98 9:39p Jimdose +// Added call info to G_Trace +// +// 61 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 60 8/25/98 7:55p Markd +// Fixed weapon angles +// +// 59 8/19/98 6:38p Markd +// Moved Assault rifle tracer into def file +// +// 58 8/14/98 5:37p Aldie +// Moved blood stuff to sentient +// +// 57 8/13/98 8:09p Aldie +// Moved blood particles to armor damage. +// +// 56 8/10/98 6:52p Aldie +// Added scale to bloodsplats +// +// 55 8/06/98 10:53p Aldie +// Added weapon tweaks and kickback. Also modified blast radius damage and +// rocket jumping. +// +// 54 8/04/98 3:27p Aldie +// Revert back to sending PITCH and YAW +// +// 53 8/03/98 3:28p Aldie +// Only send over the PITCH of the gun when firing it +// +// 52 8/01/98 4:39p Aldie +// Put some debug prints +// +// 51 8/01/98 3:24p Aldie +// Added server effects flag for specific weapons +// +// 50 7/31/98 7:42p Aldie +// Client side bullet effects +// +// 49 7/25/98 5:48p Aldie +// Remove shotgun effect +// +// 48 7/24/98 10:03p Aldie +// Fixed bullet damage with invalid traces +// +// 47 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 +// +// 46 7/22/98 10:41p Aldie +// Fixed tracers +// +// 45 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 44 7/17/98 7:57p Markd +// Added radius to FullTrace +// +// 43 7/15/98 9:57p Markd +// Added shields support +// +// 42 6/20/98 7:48p Markd +// using FullTrace now, also checking to make sure we have a valid intersection +// before trying to determine group num etc. +// +// 41 6/19/98 9:29p Jimdose +// Moved gun orientation code to Weapon +// +// 40 6/15/98 10:52p Jimdose +// Disabled fulltrace until bmodels are checked. +// +// 39 6/15/98 10:07p Jimdose +// Made bullet weapons use G_FullTrace +// +// 38 6/11/98 3:34p Jimdose +// TraceAttack was damaging the entity before checking the blood and sparks +// flags, so if the entity died before the check, we got a NULL pointer +// TraceAttack was doing the ricochet trace before checking if it should +// ricochet +// +// 37 6/10/98 2:10p Aldie +// Updated damage function. +// +// 36 6/08/98 11:35a Aldie +// Updated location based damage stuff +// +// 35 6/05/98 6:23p Aldie +// Added location to AddMultiDamage +// +// 34 5/27/98 7:34p Markd +// commented out damage reduction if not hitting body part +// +// 33 5/27/98 5:21p Markd +// fixed up ray intersection a bit +// +// 32 5/27/98 5:02a Markd +// increased nominal multiplier to 0.5f +// +// 31 5/26/98 8:44p Markd +// Put in DamageSkin suport +// +// 30 5/26/98 4:22p Markd +// Working on tri-ray intersection stuff +// +// 29 5/23/98 4:46p Aldie +// Added bulletholes. +// +// 28 5/05/98 7:38p Markd +// Added timeofs to ricochets in bulllet smoke events +// +// 27 5/03/98 8:08p Markd +// Took out old code +// +// 26 5/03/98 4:30p Jimdose +// Changed Vector class +// +// 25 5/02/98 8:41p Markd +// Put in recursive bullet attacks and got rid of ricochets +// +// 24 4/10/98 1:22a Markd +// Added FL_BLOOD support +// +// 23 4/07/98 6:42p Jimdose +// Rewrote weapon code. +// Added order to rank +// +// 22 4/06/98 7:52p Aldie +// Increased range of bullets. +// +// 21 4/04/98 7:31p Jimdose +// Bullets don't spark or ricochet against sky +// +// 20 4/02/98 4:48p Jimdose +// Changed TraceAttack to properly place bloodsplats +// +// 19 3/30/98 2:35p Jimdose +// Moved firing from Magnum to make more general +// Added Ammo +// Added world models +// +// 18 3/27/98 6:34p Jimdose +// Added sparks to show impact of bullets +// +// 17 3/26/98 8:16p Jimdose +// Added precaching of sounds +// +// 16 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 15 3/05/98 5:44p Aldie +// Support for ricochet flag. +// +// 14 3/04/98 8:02p Aldie +// More support for damage surfaces. +// +// 13 3/04/98 5:12p Aldie +// Added support for damage surfaces. +// +// 12 2/19/98 2:35p Jimdose +// Updated to work with Q2 based progs +// +// 10 12/11/97 3:30p Markd +// Moved SpawnBlood to Weapon +// +// 9 11/18/97 5:28p Markd +// Commented out shotgun +// +// 8 11/11/97 9:41p Markd +// tweaked minimum scale in blood splats +// +// 7 11/11/97 9:38p Markd +// made blood splats be based off of distance as well as damage. +// +// 6 10/31/97 9:02p Jimdose +// Made it so that bsp objects don't leave bloodsplats +// +// 5 10/31/97 7:18p Markd +// Put in BloodSpray +// +// 4 10/31/97 4:28p Jimdose +// Removed redefinition of owner in base class Weapon, so any reference to +// gunoffset through owner had to use type overriding. +// +// 3 10/27/97 3:29p Jimdose +// Removed dependency on quakedef.h +// +// 2 9/26/97 4:43p Jimdose +// Added standard Ritual header +// +// DESCRIPTION: +// Base class for all bullet (hitscan) weapons. Includes definition for shotgun. +// + +#include "g_local.h" +#include "specialfx.h" +#include "misc.h" +#include "bullet.h" +#include "q_shared.h" +#include "surface.h" + +CLASS_DECLARATION( Weapon, BulletWeapon, NULL ); + +ResponseDef BulletWeapon::Responses[] = + { + { NULL, NULL } + }; + +BulletWeapon::BulletWeapon() + { + modelIndex( "sprites/tracer.spr" ); + modelIndex( "sprites/bullethole.spr" ); + } + +void BulletWeapon::TraceAttack + ( + Vector start, + Vector end, + int damage, + trace_t *trace, + int numricochets, + int kick, + int dflags, + int meansofdeath, + qboolean server_effects + ) + + { + Vector org; + Vector dir; + Vector endpos; + int surfflags; + int surftype; + int timeofs; + Entity *ent; + qboolean ricochet; + + if ( HitSky( trace ) ) + { + return; + } + + ricochet = false; + dir = end - start; + dir.normalize(); + + org = end - dir; + + ent = trace->ent->entity; + + if ( !trace->surface ) + { + surfflags = 0; + surftype = 0; + } + else + { + surfflags = trace->surface->flags; + surftype = SURFACETYPE_FROM_FLAGS( surfflags ); + surfaceManager.DamageSurface( trace, damage, owner ); + + if ( surfflags & SURF_RICOCHET ) + ricochet = true; + } + if ( trace->intersect.valid && ent ) + { + // + // see if the parent group has ricochet turned on + // + if ( trace->intersect.parentgroup >= 0 ) + { + int flags; + + flags = gi.Group_Flags( ent->edict->s.modelindex, trace->intersect.parentgroup ); + if ( flags & MDL_GROUP_RICOCHET ) + { + surftype = ( flags >> 8 ) & 0xf; + ricochet = true; + } + } + } + + if ( ent ) + { + if ( !(ent->flags & FL_SHIELDS) ) + { + if ( ent->flags & FL_SPARKS ) + { + // Take care of ricochet effects on the server + if ( server_effects && !ricochet ) + { + timeofs = MAX_RICOCHETS - numricochets; + if ( timeofs > 0xf ) + { + timeofs = 0xf; + } + gi.WriteByte( svc_temp_entity ); + gi.WriteByte( TE_GUNSHOT ); + gi.WritePosition( org.vec3() ); + gi.WriteDir( trace->plane.normal ); + gi.WriteByte( 0 ); + gi.multicast( org.vec3(), MULTICAST_PVS ); + } + MadeBreakingSound( org, owner ); + } + + if ( ent->takedamage ) + { + if ( trace->intersect.valid ) + { + // We hit a valid group so send in location based damage + ent->Damage( this, + owner, + damage, + trace->endpos, + dir, + trace->plane.normal, + kick, + dflags, + meansofdeath, + trace->intersect.parentgroup, + -1, + trace->intersect.damage_multiplier ); + } + else + { + // We didn't hit any groups, so send in generic damage + ent->Damage( this, + owner, + damage, + trace->endpos, + dir, + trace->plane.normal, + kick, + dflags, + meansofdeath, + -1, + -1, + 1 ); + } + } + } + else + { + surftype = SURF_TYPE_METAL; + ricochet = true; + } + } + + if ( ricochet && ( server_effects || ( numricochets < MAX_RICOCHETS ) ) ) + { + timeofs = MAX_RICOCHETS - numricochets; + if ( timeofs > 0xf ) + { + timeofs = 0xf; + } + gi.WriteByte( svc_temp_entity ); + gi.WriteByte( TE_GUNSHOT ); + gi.WritePosition( org.vec3() ); + gi.WriteDir( trace->plane.normal ); + gi.WriteByte( timeofs ); + gi.multicast( org.vec3(), MULTICAST_PVS ); + } + + if ( + ricochet && + numricochets && + damage + ) + { + dir += Vector( trace->plane.normal ) * 2; + endpos = org + dir * 8192; + + // + // since this is a ricochet, we don't ignore the wewapon owner this time. + // + *trace = G_FullTrace( org, vec_zero, vec_zero, endpos, 5, NULL, MASK_SHOT, "BulletWeapon::TraceAttack" ); + if ( trace->fraction != 1.0 ) + { + endpos = trace->endpos; + TraceAttack( org, endpos, damage * 0.8f, trace, numricochets - 1, kick, dflags, meansofdeath, true ); + } + } + } + +void BulletWeapon::FireBullets + ( + int numbullets, + Vector spread, + int mindamage, + int maxdamage, + int dflags, + int meansofdeath, + qboolean server_effects + ) + + { + Vector src; + Vector dir; + Vector end; + trace_t trace; + Vector right; + Vector up; + int i; + + assert( owner ); + if ( !owner ) + { + return; + } + + GetMuzzlePosition( &src, &dir ); + + owner->angles.AngleVectors( NULL, &right, &up ); + + angles = dir.toAngles(); + setAngles( angles ); + + for( i = 0; i < numbullets; i++ ) + { + end = src + + dir * 8192 + + right * G_CRandom() * spread.x + + up * G_CRandom() * spread.y; + + trace = G_FullTrace( src, vec_zero, vec_zero, end, 5, owner, MASK_SHOT, "BulletWeapon::FireBullets" ); +#if 0 + Com_Printf("Server OWNER Angles:%0.2f %0.2f %0.2f\n",owner->angles[0],owner->angles[1],owner->angles[2]); + Com_Printf("Server Bullet Angles:%0.2f %0.2f %0.2f\n",angles[0],angles[1],angles[2]); + Com_Printf("Right :%0.2f %0.2f %0.2f\n",right[0],right[1],right[2]); + Com_Printf("Up :%0.2f %0.2f %0.2f\n",up[0],up[1],up[2]); + Com_Printf("Direction :%0.2f %0.2f %0.2f\n",dir[0],dir[1],dir[2]); + Com_Printf("Endpoint :%0.2f %0.2f %0.2f\n",end[0],end[1],end[2]); + Com_Printf("Server Trace Start :%0.2f %0.2f %0.2f\n",src[0],src[1],src[2]); + Com_Printf("Server Trace End :%0.2f %0.2f %0.2f\n",trace.endpos[0],trace.endpos[1],trace.endpos[2]); + Com_Printf("\n"); +#endif + if ( trace.fraction != 1.0 ) + { + TraceAttack( src, trace.endpos, mindamage + (int)G_Random( maxdamage - mindamage + 1 ), &trace, MAX_RICOCHETS, kick, dflags, meansofdeath, server_effects ); + } + } + } + +void BulletWeapon::FireTracer( void ) + { + Entity *tracer; + Vector src,dir,end; + trace_t trace; + + GetMuzzlePosition( &src, &dir ); + end = src + dir * 8192; + trace = G_Trace( src, vec_zero, vec_zero, end, owner, MASK_SHOT, "BulletWeapon::FireTracer" ); + + tracer = new Entity; + + tracer->angles = dir.toAngles(); + tracer->angles[ PITCH ] = -tracer->angles[ PITCH ] + 90; + //tracer->angles[PITCH] *= -1; + + tracer->setAngles( tracer->angles ); + + tracer->setMoveType( MOVETYPE_NONE ); + tracer->setSolidType( SOLID_NOT ); + tracer->setModel( "sprites/tracer.spr" ); + tracer->setSize( "0 0 0", "0 0 0" ); + tracer->setOrigin( trace.endpos ); + tracer->edict->s.renderfx &= ~RF_FRAMELERP; + + VectorCopy( src, tracer->edict->s.old_origin ); + tracer->PostEvent(EV_Remove,0.1f); + } \ No newline at end of file diff --git a/bullet.h b/bullet.h new file mode 100644 index 0000000..42dffcb --- /dev/null +++ b/bullet.h @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/bullet.h $ +// $Revision:: 21 $ +// $Author:: Aldie $ +// $Date:: 8/06/98 10:53p $ +// +// 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/bullet.h $ +// +// 21 8/06/98 10:53p Aldie +// Added weapon tweaks and kickback. Also modified blast radius damage and +// rocket jumping. +// +// 20 8/01/98 3:24p Aldie +// Added server effects flag for specific weapons +// +// 19 7/22/98 10:41p Aldie +// Fixed tracers +// +// 18 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 17 6/10/98 2:10p Aldie +// Updated damage function. +// +// 16 5/05/98 7:38p Markd +// Got rid of RicochetSound from header +// +// 15 5/02/98 8:44p Markd +// Got rid of RicochetSound, modified TraceAttack +// +// 14 4/02/98 4:51p Jimdose +// Changed TraceAttack +// +// 13 3/30/98 2:38p Jimdose +// Moved firing to BulletWeapon to make more general +// Added Ammo +// Added world models +// +// 12 3/26/98 8:10p Jimdose +// Added constructor +// +// 11 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 10 3/05/98 6:06p Aldie +// Added ricochet sounds. +// +// 9 3/02/98 8:49p Jimdose +// Changed CLASS_PROTOTYPE to only take the classname +// +// 8 2/19/98 2:35p Jimdose +// Updated to work with Q2 based progs +// +// 6 12/11/97 3:31p Markd +// Removed SpawnBlood +// +// 5 11/18/97 5:27p Markd +// Commented out ShotGun +// +// 4 10/31/97 7:18p Markd +// Added bloodspray +// +// 3 10/27/97 2:48p Jimdose +// Removed dependency on quakedef.h +// +// 2 9/26/97 4:44p Jimdose +// Added standard Ritual header +// +// DESCRIPTION: +// Base class for all bullet (hitscan) weapons. Includes definition for shotgun. +// + +#ifndef __BULLET_H__ +#define __BULLET_H__ + +#include "g_local.h" +#include "item.h" +#include "weapon.h" + +#define MAX_RICOCHETS 10 + +class EXPORT_FROM_DLL BulletWeapon : public Weapon + { + protected: + BulletWeapon(); + virtual void TraceAttack( Vector start, Vector end, int damage, trace_t *trace, int numricochets, int kick, int dflags, int meansofdeath, qboolean server_effects ); + virtual void FireBullets( int numbullets, Vector spread, int mindamage, int maxdamage, int dflags, int meansofdeath, qboolean server_effects ); + virtual void FireTracer( void ); + + public: + CLASS_PROTOTYPE( BulletWeapon ); + }; + +#endif /* BULLET.h */ diff --git a/camera.cpp b/camera.cpp new file mode 100644 index 0000000..00d7fa7 --- /dev/null +++ b/camera.cpp @@ -0,0 +1,1359 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/camera.cpp $ +// $Revision:: 47 $ +// $Author:: Markd $ +// $Date:: 11/14/98 2:33a $ +// +// 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/camera.cpp $ +// +// 47 11/14/98 2:33a Markd +// put in some better jump cutting +// +// 46 11/13/98 11:00p Jimdose +// Took EV_CONSOLE off of fov +// +// 45 11/13/98 10:50p Markd +// fixed watch camera +// +// 44 11/10/98 7:06p Markd +// fixed 3rd person camera +// +// 43 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 42 10/21/98 1:18a Markd +// took warning out of camera +// +// 41 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 40 10/12/98 5:02p Markd +// Changed fov event +// +// 39 9/30/98 10:13p Markd +// put in an additional comment line +// +// 38 9/24/98 1:46a Markd +// fixed camera lerping +// +// 37 9/20/98 6:49p Markd +// Added Thread ability to cameras when they are looked through +// +// 36 9/07/98 4:36p Markd +// fixing camera stuff +// +// 35 8/29/98 9:39p Jimdose +// Added call info to G_Trace +// +// 34 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 33 8/27/98 9:01p Jimdose +// Changed grav to a reference +// +// 32 8/24/98 6:51p Jimdose +// Fixed inverted gravity axis +// +// 31 8/22/98 9:35p Markd +// When stopping the camera make sure to check if followtime or watchtime are +// non-zero, if so copy over the new states +// +// 30 8/22/98 8:48p Jimdose +// Started getting camera working with alternate gravity axis +// +// 29 8/18/98 12:03p Markd +// fixed camera fov stuff +// +// 28 8/17/98 7:42p Markd +// Added SetOverlay command +// +// 27 8/17/98 6:05p Markd +// Added nextcamera and overlay, made SetCamera send to all players +// +// 26 8/17/98 4:33p Markd +// Added new camera stuff +// +// 25 8/15/98 2:39p Markd +// fixed camera stuff +// +// 24 8/14/98 8:18p Markd +// reworked camera class +// +// 23 8/08/98 7:29p Aldie +// Added intermissions for deathmatch +// +// 22 7/31/98 8:09p Jimdose +// Script commands now include flags to indicate cheats and console commands +// +// 21 7/25/98 11:49p Markd +// fixed camera startup stuff +// +// 20 7/22/98 5:28p Markd +// Redid camera stuff +// +// 19 7/22/98 4:15p Markd +// Redid camera system +// +// 18 7/20/98 2:38p Markd +// Fixed camera wackiness +// +// 17 7/18/98 6:14p Markd +// Added optional watchEnt at the end of follow and orbit commands +// +// 16 7/13/98 12:59p Markd +// Added fixedposition and nofixedposition +// +// 15 7/12/98 9:43p Markd +// Accidentally commented out some camera following stuff +// +// 14 7/12/98 8:43p Markd +// Fixed some camera smoothness issues +// +// 13 7/11/98 6:31p Markd +// removed valid orientation, simplified code +// +// 12 7/10/98 1:11p Markd +// Added some new camera commands +// +// 11 7/08/98 12:41p Markd +// Added quaternion support to bsplines and also removed CameraPath +// +// 10 7/02/98 9:48p Markd +// allowed camera to have orientation support from the bspline +// +// 9 5/27/98 3:18a Jimdose +// cameras can now watch specific entities +// +// 8 5/26/98 8:49p Jimdose +// Added yaw commands +// +// 7 5/26/98 8:17p Jimdose +// added height script command +// fixed fov bug +// +// 6 5/26/98 7:55p Jimdose +// added scripted cameras +// +// 5 5/26/98 4:36a Jimdose +// Follow camera no longer clips into walls +// +// 4 5/13/98 4:57p Jimdose +// now uses SafePtrs +// +// 3 5/07/98 10:41p Jimdose +// fleshed out functionality a bit more +// +// 2 5/05/98 2:37p Jimdose +// Created file +// +// DESCRIPTION: +// Camera. Duh. +// + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "camera.h" +#include "bspline.h" +#include "player.h" +#include "camera.h" + +cvar_t *sv_showcameras; + +Event EV_Camera_FollowingPath( "followingpath" ); +Event EV_Camera_StartMoving( "start" ); +Event EV_Camera_Pause( "pause" ); +Event EV_Camera_Continue( "continue" ); +Event EV_Camera_StopMoving( "stop" ); +Event EV_Camera_SetSpeed( "speed" ); +Event EV_Camera_SetDistance( "distance" ); +Event EV_Camera_SetHeight( "height" ); +Event EV_Camera_SetYaw( "yaw" ); +Event EV_Camera_FixedYaw( "fixedyaw" ); +Event EV_Camera_RelativeYaw( "relativeyaw" ); +Event EV_Camera_SetFOV( "fov" ); +Event EV_Camera_Orbit( "orbit" ); +Event EV_Camera_Follow( "follow" ); +Event EV_Camera_Watch( "watch" ); +Event EV_Camera_LookAt( "lookat" ); +Event EV_Camera_TurnTo( "turnto" ); +Event EV_Camera_MoveToEntity( "moveto" ); +Event EV_Camera_MoveToPos( "movetopos" ); +Event EV_Camera_NoWatch( "nowatch" ); +Event EV_Camera_IgnoreAngles( "ignoreangles" ); +Event EV_Camera_UseAngles( "useangles" ); +Event EV_Camera_SplineAngles( "splineangles" ); +Event EV_Camera_NormalAngles( "normalangles" ); +Event EV_Camera_FixedPosition( "fixedposition" ); +Event EV_Camera_NoFixedPosition( "nofixedposition" ); +Event EV_Camera_JumpTime( "jumptime" ); +Event EV_Camera_JumpCut( "jumpcut" ); +Event EV_Camera_Pan( "pan" ); +Event EV_Camera_StopPan( "stoppan" ); +Event EV_Camera_PanSpeed( "panspeed" ); +Event EV_Camera_PanMax( "panmax" ); +Event EV_Camera_SetPanAngles( "setpanangles" ); +Event EV_Camera_SetNextCamera( "nextcamera" ); +Event EV_Camera_SetOverlay( "setoverlay" ); +Event EV_Camera_SetThread( "setthread" ); + +/*****************************************************************************/ +/*SINED func_camera (1 0 0) ? ORBIT START_ON PAN + +Camera used for cinematic sequences. Start + +"target" points to the target to orbit or follow. If it points to a path, the camera will follow the path. +"distance" the distance to follow or orbit if the target is not a path. (default 128). +"speed" specifies how fast to move on the path or orbit. (default 1). +"fov" specifies fov of camera, default 90. +"yaw" specifies yaw of camera, default 0. +"height" specifies height of camera from origin, default 128. +"panspeed" speed at which to pan ( 7 degrees per second ) +"panmax" maximum angle offset for panning ( 45 degrees ) +"nextcamera" a link to the next camera in a chain of cameras +"overlay" an overlay to use while looking through the camera +"thread" a thread label that will be fired when the camera is looked through + +ORBIT tells the camera to create a circular path around the object it points to. It the camera points to a path, it will loop when it gets to the end of the path. +START_ON causes the camera to be moving as soon as it is spawned. +PAN camera should pan from right to left + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, Camera, "func_camera" ); + +ResponseDef Camera::Responses[] = + { + { &EV_Camera_FollowingPath, ( Response )Camera::FollowingPath }, + { &EV_Activate, ( Response )Camera::StartMoving }, + { &EV_Camera_StartMoving, ( Response )Camera::StartMoving }, + { &EV_Camera_StopMoving, ( Response )Camera::StopMoving }, + { &EV_Camera_Pause, ( Response )Camera::Pause }, + { &EV_Camera_Continue, ( Response )Camera::Continue }, + { &EV_Camera_SetSpeed, ( Response )Camera::SetSpeed }, + { &EV_Camera_SetDistance, ( Response )Camera::SetDistance }, + { &EV_Camera_SetHeight, ( Response )Camera::SetHeight }, + { &EV_Camera_SetYaw, ( Response )Camera::SetYaw }, + { &EV_Camera_FixedYaw, ( Response )Camera::FixedYaw }, + { &EV_Camera_RelativeYaw, ( Response )Camera::RelativeYaw }, + { &EV_Camera_SetFOV, ( Response )Camera::SetFOV }, + { &EV_Camera_Orbit, ( Response )Camera::OrbitEvent }, + { &EV_Camera_Follow, ( Response )Camera::FollowEvent }, + { &EV_Camera_Watch, ( Response )Camera::WatchEvent }, + { &EV_Camera_NoWatch, ( Response )Camera::NoWatchEvent }, + { &EV_Camera_LookAt, ( Response )Camera::LookAt }, + { &EV_Camera_TurnTo, ( Response )Camera::TurnTo }, + { &EV_Camera_MoveToEntity, ( Response )Camera::MoveToEntity }, + { &EV_Camera_MoveToPos, ( Response )Camera::MoveToPos }, + { &EV_Camera_IgnoreAngles, ( Response )Camera::IgnoreAngles }, + { &EV_Camera_UseAngles, ( Response )Camera::UseAngles }, + { &EV_Camera_SplineAngles, ( Response )Camera::SplineAngles }, + { &EV_Camera_NormalAngles, ( Response )Camera::NormalAngles }, + { &EV_Camera_FixedPosition, ( Response )Camera::FixedPosition }, + { &EV_Camera_NoFixedPosition, ( Response )Camera::NoFixedPosition }, + { &EV_Camera_JumpCut, ( Response )Camera::JumpCut }, + { &EV_Camera_JumpTime, ( Response )Camera::JumpTime }, + { &EV_Camera_Pan, ( Response )Camera::PanEvent }, + { &EV_Camera_StopPan, ( Response )Camera::StopPanEvent }, + { &EV_Camera_PanSpeed, ( Response )Camera::PanSpeedEvent }, + { &EV_Camera_PanMax, ( Response )Camera::PanMaxEvent }, + { &EV_Camera_SetPanAngles, ( Response )Camera::SetPanAngles }, + { &EV_Camera_SetNextCamera, ( Response )Camera::SetNextCamera }, + { &EV_Camera_SetOverlay, ( Response )Camera::SetOverlay }, + { &EV_Camera_SetThread, ( Response )Camera::SetThread }, + + { NULL, NULL } + }; +Camera::Camera() + { + Vector ang; + + default_fov = G_GetFloatArg( "fov", 90 ); + if ( default_fov <= 0 ) + default_fov = 90; + default_yaw = G_GetFloatArg( "yaw", 0 ); + + default_follow_dist = G_GetFloatArg( "distance", 128 ); + default_height = G_GetFloatArg( "height", 128 ); + default_speed = G_GetFloatArg( "speed", 1 ); + + default_pan_speed = G_GetFloatArg( "panspeed", 7 ); + default_pan_max = G_GetFloatArg( "panmax", 45 ); + + nextCamera = G_GetStringArg( "nextcamera" ); + overlay = G_GetStringArg( "overlay" ); + thread = G_GetStringArg( "thread" ); + + watchTime = 0; + followTime = 0; + + targetEnt = NULL; + targetWatchEnt = NULL; + + fov = default_fov; + + jumpTime = 2.0f; + + setSolidType( SOLID_NOT ); + setMoveType( MOVETYPE_NONE ); + + ang = G_GetVectorArg( "angles", vec_zero ); + setAngles( ang ); + default_angles = ang; + + InitializeState( currentstate ); + InitializeState( newstate ); + + sv_showcameras = gi.cvar( "sv_showcameras", "0", 0 ); + showcamera = sv_showcameras->value; + if ( showcamera ) + { + setModel( "xyz.def" ); + showModel(); + } + else + { + hideModel(); + } + + if ( spawnflags & START_ON ) + { + PostEvent( EV_Activate, FRAMETIME ); + } + } + +void Camera::InitializeMoveState( CameraMoveState &movestate ) + { + movestate.pos = worldorigin; + movestate.followEnt = NULL; + movestate.orbitEnt = NULL; + + movestate.followingpath = false; + movestate.cameraTime = 0; + movestate.cameraPath.Clear(); + + movestate.fov = default_fov; + movestate.fixed_position = false; + + movestate.follow_dist = default_follow_dist; + movestate.follow_mask = MASK_SOLID; + movestate.height = default_height; + movestate.speed = default_speed; + } + +void Camera::InitializeWatchState( CameraWatchState &watchstate ) + { + worldangles.AngleVectors( &watchstate.dir, NULL, NULL ); + watchstate.watchEnt = NULL; + + watchstate.ignoreangles = false; + watchstate.splineangles = true; + watchstate.panning = false; + + watchstate.pan_offset = 0; + watchstate.pan_dir = 1; + watchstate.pan_max = default_pan_max; + watchstate.pan_speed = default_pan_speed; + watchstate.pan_angles = default_angles; + + watchstate.yaw = default_yaw; + watchstate.fixedyaw = false; + } + +void Camera::InitializeState( CameraState &state ) + { + InitializeMoveState( state.move ); + InitializeWatchState( state.watch ); + } + + +#define DELTA 1e-6 + +void Camera::EvaluatePosition + ( + CameraState &state + ) + { + Vector oldpos, olddir; + float speed_multiplier; + Vector prevpos; + + prevpos = state.move.pos; + + olddir = state.watch.dir; + + // + // evaluate position + // + if ( state.move.followingpath ) + { + speed_multiplier = state.move.cameraPath.Eval( state.move.cameraTime, oldpos, olddir ); + + state.move.cameraTime += FRAMETIME * state.move.speed * speed_multiplier; + + if ( state.move.orbitEnt ) + { + oldpos += state.move.orbitEnt->worldorigin; + } + } + else + { + if ( !state.move.followEnt ) + { + oldpos = state.move.pos; + } + else + { + trace_t trace; + Vector start, end, ang, back, temp; + const gravityaxis_t &grav = gravity_axis[ state.move.followEnt->gravaxis ]; + + start = state.move.followEnt->worldorigin; + start[ grav.z ] += state.move.followEnt->maxs[ 2 ]; + + if ( state.watch.fixedyaw ) + { + ang = vec_zero; + } + else + { + if ( state.move.followEnt->isSubclassOf( Player ) ) + { + Entity * ent; + ent = state.move.followEnt; + ang = ( ( Player * )ent )->v_angle; + } + else + { + ang = state.move.followEnt->worldangles; + } + } + ang.y += state.watch.yaw; + ang.AngleVectors( &temp, NULL, NULL ); + back[ grav.x ] = temp[ 0 ]; + back[ grav.y ] = temp[ 1 ] * grav.sign; + back[ grav.z ] = temp[ 2 ] * grav.sign; + + end = start - back * state.move.follow_dist; + end[ 2 ] += 24; + + trace = G_Trace( start, vec_zero, vec_zero, end, state.move.followEnt, state.move.follow_mask, "Camera::EvaluatePosition" ); + //dir = start - trace.endpos; + //dir.normalize(); + + end = trace.endpos; + oldpos = end + back * 16; + } + } + // + // evaluate old orientation + // + if ( state.watch.watchEnt ) + { + Vector watchPos; + + watchPos.x = state.watch.watchEnt->worldorigin.x; + watchPos.y = state.watch.watchEnt->worldorigin.y; + watchPos.z = state.watch.watchEnt->absmax.z; + if ( state.move.followEnt == state.watch.watchEnt ) + { + olddir = watchPos - oldpos; + } + else + { + olddir = watchPos - worldorigin; + } + } + else + { + if ( state.watch.ignoreangles ) + { + olddir = state.watch.dir; + } + else if ( state.move.followingpath ) + { + if ( !state.watch.splineangles ) + { + olddir = oldpos - prevpos; + } + else + { + Vector dir; + + dir = olddir; + dir.AngleVectors( &olddir, NULL, NULL ); + } + } + else if ( state.move.followEnt ) + { + Vector start; + + start = state.move.followEnt->worldorigin; + start[ 2 ] += state.move.followEnt->maxs[ 2 ]; + olddir = oldpos - start; + } + else if ( state.watch.panning ) + { + Vector ang; + state.watch.pan_offset += FRAMETIME * state.watch.pan_speed * state.watch.pan_dir; + if ( state.watch.pan_offset > state.watch.pan_max ) + { + state.watch.pan_offset = state.watch.pan_max; + state.watch.pan_dir = -state.watch.pan_dir; + } + else if ( state.watch.pan_offset < -state.watch.pan_max ) + { + state.watch.pan_offset = -state.watch.pan_max; + state.watch.pan_dir = -state.watch.pan_dir; + } + + ang = state.watch.pan_angles; + ang[ YAW ] += state.watch.pan_offset; + ang.AngleVectors( &olddir, NULL, NULL ); + } + } + olddir.normalize(); + + if ( !state.move.fixed_position ) + state.move.pos = oldpos; + state.watch.dir = olddir; + } + + + +void Camera::FollowingPath + ( + Event *ev + ) + + { + Vector pos; + Vector dir; + float len; + + // + // evaluate position + // + if ( followTime || watchTime ) + { + int i; + float t; + + EvaluatePosition( currentstate ); + EvaluatePosition( newstate ); + + if ( followTime ) + { + t = followTime - level.time; + if ( t < 0 ) + { + t = 0; + currentstate.move = newstate.move; + InitializeMoveState( newstate.move ); + followTime = 0; + } + // + // we want the transition to happen over 2 seconds + // + t = ( jumpTime - t ) / jumpTime; + + for ( i = 0; i < 3; i++ ) + { + pos[ i ] = currentstate.move.pos[ i ] + ( t * ( newstate.move.pos[ i ] - currentstate.move.pos[ i ] ) ); + } + fov = currentstate.move.fov + ( t * ( newstate.move.fov - currentstate.move.fov ) ); + } + else + { + fov = currentstate.move.fov; + pos = currentstate.move.pos; + } + + if ( watchTime ) + { + t = watchTime - level.time; + if ( t < 0 ) + { + t = 0; + currentstate.watch = newstate.watch; + InitializeWatchState( newstate.watch ); + watchTime = 0; + } + // + // we want the transition to happen over 2 seconds + // + t = ( jumpTime - t ) / jumpTime; + + dir = LerpVector( currentstate.watch.dir, newstate.watch.dir, t ); + } + else + { + dir = currentstate.watch.dir; + } + } + else + { + EvaluatePosition( currentstate ); + fov = currentstate.move.fov; + pos = currentstate.move.pos; + dir = currentstate.watch.dir; + //warning("FollowingPath","%p pos x%.2f y%.2f z%2.f time %.2f", this, pos.x, pos.y, pos.z, level.time ); + } + + setOrigin( pos ); + + len = dir.length(); + if ( len > 0.05 ) + { + dir *= ( 1 / len ); + angles = dir.toAngles(); + angles[ PITCH ] = -angles[ PITCH ]; + setAngles( angles ); + //warning("FollowingPath","%p angles x%.2f y%.2f z%2.f time %.2f", this, angles.x, angles.y, angles.z, level.time ); + } + + + if ( sv_showcameras->value != showcamera ) + { + showcamera = sv_showcameras->value; + if ( showcamera ) + { + setModel( "xyz.def" ); + showModel(); + } + else + { + hideModel(); + } + } + + if ( sv_showcameras->value != showcamera ) + { + showcamera = sv_showcameras->value; + if ( showcamera ) + { + setModel( "xyz.def" ); + showModel(); + } + else + { + hideModel(); + } + } + if ( showcamera && currentstate.move.followingpath ) + { + G_Color3f( 1, 1, 0 ); + if ( currentstate.watch.watchEnt ) + { + currentstate.move.cameraPath.DrawCurve( currentstate.watch.watchEnt->worldorigin, 10 ); + } + else + { + currentstate.move.cameraPath.DrawCurve( 10 ); + } + } + + + PostEvent( EV_Camera_FollowingPath, FRAMETIME ); + } + +void Camera::LookAt + ( + Event *ev + ) + + { + Vector pos, delta; + float len; + Entity * ent; + + ent = ev->GetEntity( 1 ); + + if ( !ent ) + return; + + pos.x = ent->worldorigin.x; + pos.y = ent->worldorigin.y; + pos.z = ent->absmax.z; + delta = pos - worldorigin; + delta.normalize(); + currentstate.watch.dir = delta; + + len = currentstate.watch.dir.length(); + if ( len > 0.05 ) + { + angles = currentstate.watch.dir.toAngles(); + angles[ PITCH ] = -angles[ PITCH ]; + setAngles( angles ); + } + } + +void Camera::TurnTo + ( + Event *ev + ) + + { + Vector ang; + + ang = ev->GetVector( 1 ); + ang.AngleVectors( ¤tstate.watch.dir, NULL, NULL ); + setAngles( ang ); + } + +void Camera::MoveToEntity + ( + Event *ev + ) + + { + Entity * ent; + + ent = ev->GetEntity( 1 ); + if ( ent ) + currentstate.move.pos = ent->worldorigin; + setOrigin( currentstate.move.pos ); + } + +void Camera::MoveToPos + ( + Event *ev + ) + + { + currentstate.move.pos = ev->GetVector( 1 ); + setOrigin( currentstate.move.pos ); + } + +void Camera::Stop + ( + void + ) + + { + if ( followTime ) + { + currentstate.move = newstate.move; + InitializeMoveState( newstate.move ); + } + if ( watchTime ) + { + currentstate.watch = newstate.watch; + InitializeWatchState( newstate.watch ); + } + CancelEventsOfType( moveevent ); +// moveevent = NullEvent; + watchTime = 0; + followTime = 0; + } + +void Camera::CreateOribit + ( + Vector pos, + float radius + ) + + { + newstate.move.cameraPath.Clear(); + newstate.move.cameraPath.SetType( SPLINE_LOOP ); + + newstate.move.cameraPath.AppendControlPoint( pos + Vector( radius, 0, 0 ) ); + newstate.move.cameraPath.AppendControlPoint( pos + Vector( 0, radius, 0 ) ); + newstate.move.cameraPath.AppendControlPoint( pos + Vector( -radius, 0, 0 ) ); + newstate.move.cameraPath.AppendControlPoint( pos + Vector( 0, -radius, 0 ) ); + } + +void Camera::CreatePath + ( + SplinePath *path, + splinetype_t type + ) + + { + SplinePath *node; + SplinePath *loop; + + newstate.move.cameraPath.Clear(); + newstate.move.cameraPath.SetType( type ); + + node = path; + while( node != NULL ) + { + newstate.move.cameraPath.AppendControlPoint( node->worldorigin, node->angles, node->speed ); + loop = node->GetLoop(); + if ( loop ) + { + newstate.move.cameraPath.SetLoopPoint( loop->worldorigin ); + } + node = node->GetNext(); + + if ( node == path ) + { + break; + } + } + } + +void Camera::FollowPath + ( + SplinePath *path, + qboolean loop, + Entity * watch + ) + + { + Stop(); + if ( loop ) + { + CreatePath( path, SPLINE_LOOP ); + } + else + { + CreatePath( path, SPLINE_CLAMP ); + } + + newstate.move.cameraTime = -2; + newstate.move.followingpath = true; + followTime = level.time + jumpTime; + watchTime = level.time + jumpTime; + + if ( watch ) + { + newstate.watch.watchEnt = watch; + } + else + { + newstate.watch.watchEnt = NULL; + } + + moveevent = EV_Camera_FollowingPath; + PostEvent( EV_Camera_FollowingPath, FRAMETIME ); + } + +void Camera::Orbit + ( + Entity *ent, + float dist, + Entity *watch + ) + + { + Stop(); + CreateOribit( Vector( 0, 0, newstate.move.height ), dist ); + newstate.move.cameraTime = -2; + newstate.move.orbitEnt = ent; + newstate.move.followingpath = true; + followTime = level.time + jumpTime; + + watchTime = level.time + jumpTime; + if ( watch ) + { + newstate.watch.watchEnt = watch; + } + else + { + newstate.watch.watchEnt = ent; + } + + moveevent = EV_Camera_FollowingPath; + PostEvent( EV_Camera_FollowingPath, FRAMETIME ); + } + +void Camera::FollowEntity + ( + Entity *ent, + float dist, + int mask, + Entity *watch + ) + + { + assert( ent ); + + Stop(); + + if ( ent ) + { + newstate.move.followEnt = ent; + newstate.move.followingpath = false; + followTime = level.time + jumpTime; + watchTime = level.time + jumpTime; + if ( watch ) + { + newstate.watch.watchEnt = watch; + } + else + { + newstate.watch.watchEnt = ent; + } + newstate.move.follow_dist = dist; + newstate.move.follow_mask = mask; + moveevent = EV_Camera_FollowingPath; + PostEvent( EV_Camera_FollowingPath, 0 ); + } + } + +void Camera::StartMoving + ( + Event *ev + ) + + { + Entity *ent; + SplinePath *path; + int num; + + if ( !targetEnt ) + { + num = G_FindTarget( 0, Target() ); + ent = G_GetEntity( num ); + if ( !num || !ent ) + { + if ( spawnflags & PANNING ) + { + currentstate.watch.panning = true; + moveevent = EV_Camera_FollowingPath; + PostEvent( EV_Camera_FollowingPath, FRAMETIME ); + return; + } + // + // we took this out just because of too many warnings, oh well + // + //warning("StartMoving", "Can't find target for camera\n" ); + return; + } + } + else + { + ent = targetEnt; + } + + if ( ent->isSubclassOf( SplinePath ) ) + { + path = ( SplinePath * )ent; + FollowPath( path, spawnflags & ORBIT, targetWatchEnt ); + } + else + { + if ( spawnflags & ORBIT ) + { + Orbit( ent, newstate.move.follow_dist, targetWatchEnt ); + } + else + { + FollowEntity( ent, newstate.move.follow_dist, newstate.move.follow_mask, targetWatchEnt ); + } + } + } + +void Camera::StopMoving + ( + Event *ev + ) + + { + Stop(); + } + +void Camera::Pause + ( + Event *ev + ) + + { + CancelEventsOfType( moveevent ); + } + +void Camera::Continue + ( + Event *ev + ) + + { + if ( ( int )moveevent != ( int )NullEvent ) + { + CancelEventsOfType( moveevent ); + PostEvent( moveevent, 0 ); + } + } + +void Camera::SetSpeed + ( + Event *ev + ) + + { + newstate.move.speed = ev->GetFloat( 1 ); + } + +void Camera::SetDistance + ( + Event *ev + ) + + { + newstate.move.follow_dist = ev->GetFloat( 1 ); + } + +void Camera::SetHeight + ( + Event *ev + ) + + { + newstate.move.height = ev->GetFloat( 1 ); + } + +void Camera::SetYaw + ( + Event *ev + ) + + { + newstate.watch.yaw = ev->GetFloat( 1 ); + } + +void Camera::FixedYaw + ( + Event *ev + ) + + { + newstate.watch.fixedyaw = true; + } + +void Camera::RelativeYaw + ( + Event *ev + ) + + { + newstate.watch.fixedyaw = false; + } + +void Camera::IgnoreAngles + ( + Event *ev + ) + + { + newstate.watch.ignoreangles = true; + } + +void Camera::UseAngles + ( + Event *ev + ) + + { + newstate.watch.ignoreangles = false; + } + +void Camera::SplineAngles + ( + Event *ev + ) + + { + newstate.watch.splineangles = true; + } + +void Camera::NormalAngles + ( + Event *ev + ) + + { + newstate.watch.splineangles = false; + } + +void Camera::FixedPosition + ( + Event *ev + ) + + { + newstate.move.fixed_position = true; + } + +void Camera::NoFixedPosition + ( + Event *ev + ) + + { + newstate.move.fixed_position = false; + } + +void Camera::PanEvent + ( + Event *ev + ) + + { + currentstate.watch.panning = true; + } + +void Camera::StopPanEvent + ( + Event *ev + ) + + { + currentstate.watch.panning = false; + } + +void Camera::PanSpeedEvent + ( + Event *ev + ) + + { + currentstate.watch.pan_speed = ev->GetFloat( 1 ); + } + +void Camera::PanMaxEvent + ( + Event *ev + ) + + { + currentstate.watch.pan_max = ev->GetFloat( 1 ); + } + +void Camera::SetPanAngles + ( + Event *ev + ) + + { + if ( ev->NumArgs() > 0 ) + { + currentstate.watch.pan_angles = ev->GetVector( 1 ); + } + else + { + currentstate.watch.pan_angles = worldangles; + } + } + +void Camera::SetNextCamera + ( + Event *ev + ) + + { + nextCamera = ev->GetString( 1 ); + } + +void Camera::SetOverlay + ( + Event *ev + ) + + { + overlay = ev->GetString( 1 ); + } + +void Camera::JumpCut + ( + Event *ev + ) + { + if ( followTime ) + { + currentstate.move = newstate.move; + InitializeMoveState( newstate.move ); + followTime = 0; + } + if ( watchTime ) + { + currentstate.watch = newstate.watch; + InitializeWatchState( newstate.watch ); + watchTime = 0; + } + if ( moveevent ) + { + CancelEventsOfType( moveevent ); + ProcessEvent( Event( moveevent ) ); + } + } + +void Camera::JumpTime + ( + Event *ev + ) + + { + float t; + float newjumptime; + + newjumptime = ev->GetFloat( 1 ); + if ( followTime ) + { + t = ( jumpTime - ( level.time - followTime ) ) / jumpTime; + followTime = level.time + ( t * newjumptime ); + } + if ( watchTime ) + { + t = ( jumpTime - ( level.time - watchTime ) ) / jumpTime; + watchTime = level.time + ( t * newjumptime ); + } + jumpTime = newjumptime; + } + +void Camera::OrbitEvent + ( + Event *ev + ) + + { + Entity *ent; + + spawnflags |= ORBIT; + ent = ev->GetEntity( 1 ); + if ( ent ) + { + targetEnt = ent; + targetWatchEnt = NULL; + if ( ev->NumArgs() > 1 ) + targetWatchEnt = ev->GetEntity( 2 ); + if ( moveevent ) + { + Stop(); + } + ProcessEvent( EV_Activate ); + } + } + +void Camera::FollowEvent + ( + Event *ev + ) + + { + Entity *ent; + + spawnflags &= ~ORBIT; + ent = ev->GetEntity( 1 ); + if ( ent ) + { + targetEnt = ent; + targetWatchEnt = NULL; + if ( ev->NumArgs() > 1 ) + targetWatchEnt = ev->GetEntity( 2 ); + if ( moveevent ) + { + Stop(); + } + ProcessEvent( EV_Activate ); + } + } + +void Camera::SetFOV + ( + Event *ev + ) + + { + currentstate.move.fov = ev->GetFloat( 1 ); + } + +void Camera::WatchEvent + ( + Event *ev + ) + + { + watchTime = level.time + jumpTime; + newstate.watch.watchEnt = ev->GetEntity( 1 ); + } + +void Camera::NoWatchEvent + ( + Event *ev + ) + + { + watchTime = level.time + jumpTime; + newstate.watch.watchEnt = NULL; + } + +void SetCamera + ( + Entity *ent + ) + + { + int j; + edict_t *other; + + for( j = 1; j <= game.maxclients; j++ ) + { + other = &g_edicts[ j ]; + if ( other->inuse && other->client ) + { + Player * client; + client = ( Player * )other->entity; + client->SetCamera( ent ); + } + } + } + +str &Camera::NextCamera + ( + void + ) + + { + return nextCamera; + } + +str &Camera::Overlay + ( + void + ) + + { + return overlay; + } + +void Camera::SetThread + ( + Event *ev + ) + + { + thread = ev->GetString( 1 ); + } + +str &Camera::Thread + ( + void + ) + + { + return thread; + } + +CLASS_DECLARATION( Camera, SecurityCamera, "func_securitycamera" ); + +ResponseDef SecurityCamera::Responses[] = + { + { NULL, NULL } + }; + +SecurityCamera::SecurityCamera() + { + setModel( "camera.def" ); + showModel(); + } diff --git a/camera.h b/camera.h new file mode 100644 index 0000000..0724765 --- /dev/null +++ b/camera.h @@ -0,0 +1,513 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/camera.h $ +// $Revision:: 25 $ +// $Author:: Jimdose $ +// $Date:: 10/25/98 11:53p $ +// +// 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/camera.h $ +// +// 25 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 24 9/29/98 5:58p Markd +// put in archive and unarchive +// +// 23 9/24/98 1:19a Markd +// bullet proofed some equating bspline code +// +// 22 9/20/98 6:49p Markd +// Added Thread ability to cameras when they are looked through +// +// 21 8/17/98 7:42p Markd +// Added SetOverlay +// +// 20 8/17/98 6:20p Markd +// Changed SetCamera to a Player method +// +// 19 8/17/98 4:35p Markd +// Added SecurityCamera +// +// 18 8/14/98 8:18p Markd +// reworked camera class +// +// 17 8/08/98 7:29p Aldie +// Added intermissions for deathmatch +// +// 16 7/22/98 5:22p Markd +// Added MoveToPos and TurnTo +// +// 15 7/22/98 4:12p Markd +// Redid camera stuff +// +// 14 7/18/98 6:14p Markd +// Added optional watch commands at the end of follow and orbit events +// +// 13 7/13/98 12:58p Markd +// added fixed_position +// +// 12 7/11/98 6:31p Markd +// removed valid orientation, simplified code +// +// 11 7/10/98 1:12p Markd +// Added more functionality to camera stuff +// +// 10 7/08/98 12:42p Markd +// Removed CameraPath with SplinePath +// +// 9 7/02/98 9:48p Markd +// Added orientation support to cameras +// +// 8 5/27/98 3:18a Jimdose +// cameras can now watch specific entities +// +// 7 5/26/98 8:49p Jimdose +// Added yaw commands +// +// 6 5/26/98 8:18p Jimdose +// added height event +// +// 5 5/26/98 7:56p Jimdose +// added scripted cameras +// +// 4 5/13/98 4:55p Jimdose +// now uses SafePtrs +// +// 3 5/07/98 10:41p Jimdose +// fleshed out functionality a bit more +// +// 2 5/05/98 2:37p Jimdose +// Created file +// +// DESCRIPTION: +// Camera. Duh. +// + +#ifndef __CAMERA_H__ +#define __CAMERA_H__ + +#include "g_local.h" +#include "entity.h" +#include "bspline.h" + +#define ORBIT 1 +#define START_ON 2 +#define PANNING 4 + +extern Event EV_Camera_FollowingPath; +extern Event EV_Camera_StartMoving; +extern Event EV_Camera_Pause; +extern Event EV_Camera_Continue; +extern Event EV_Camera_StopMoving; +extern Event EV_Camera_SetSpeed; +extern Event EV_Camera_SetDistance; +extern Event EV_Camera_SetHeight; +extern Event EV_Camera_SetYaw; +extern Event EV_Camera_FixedYaw; +extern Event EV_Camera_RelativeYaw; +extern Event EV_Camera_SetFOV; +extern Event EV_Camera_Orbit; +extern Event EV_Camera_Follow; +extern Event EV_Camera_Watch; +extern Event EV_Camera_LookAt; +extern Event EV_Camera_TurnTo; +extern Event EV_Camera_MoveToEntity; +extern Event EV_Camera_MoveToPos; +extern Event EV_Camera_NoWatch; +extern Event EV_Camera_IgnoreAngles; +extern Event EV_Camera_UseAngles; +extern Event EV_Camera_SplineAngles; +extern Event EV_Camera_NormalAngles; +extern Event EV_Camera_FixedPosition; +extern Event EV_Camera_NoFixedPosition; +extern Event EV_Camera_JumpTime; +extern Event EV_Camera_JumpCut; +extern Event EV_Camera_Pan; +extern Event EV_Camera_StopPan; +extern Event EV_Camera_PanSpeed; +extern Event EV_Camera_PanMax; +extern Event EV_Camera_SetPanAngles; + + +class EXPORT_FROM_DLL CameraMoveState + { + public: + Vector pos; + + BSpline cameraPath; + float cameraTime; + + EntityPtr followEnt; + EntityPtr orbitEnt; + + qboolean followingpath; + float speed; + qboolean fixed_position; + float fov; + float height; + float follow_dist; + int follow_mask; + + void operator=( CameraMoveState &newstate ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline void CameraMoveState::operator= + ( + CameraMoveState &newstate + ) + + { + pos = newstate.pos; + + cameraPath = newstate.cameraPath; + + cameraTime = newstate.cameraTime; + + followEnt = newstate.followEnt; + orbitEnt = newstate.orbitEnt; + + followingpath = newstate.followingpath; + speed = newstate.speed; + fixed_position = newstate.fixed_position; + fov = newstate.fov; + height = newstate.height; + follow_dist = newstate.follow_dist; + follow_mask = newstate.follow_mask; + } + +inline EXPORT_FROM_DLL void CameraMoveState::Archive + ( + Archiver &arc + ) + { + arc.WriteVector( pos ); + + cameraPath.Archive( arc ); + + arc.WriteFloat( cameraTime ); + + arc.WriteSafePointer( followEnt ); + arc.WriteSafePointer( orbitEnt ); + + arc.WriteBoolean( followingpath ); + arc.WriteFloat( speed ); + arc.WriteBoolean( fixed_position ); + + arc.WriteFloat( fov ); + arc.WriteFloat( height ); + arc.WriteFloat( follow_dist ); + arc.WriteInteger( follow_mask ); + } + +inline EXPORT_FROM_DLL void CameraMoveState::Unarchive + ( + Archiver &arc + ) + { + arc.ReadVector( &pos ); + + cameraPath.Unarchive( arc ); + + arc.ReadFloat( &cameraTime ); + + arc.ReadSafePointer( &followEnt ); + arc.ReadSafePointer( &orbitEnt ); + + arc.ReadBoolean( &followingpath ); + arc.ReadFloat( &speed ); + arc.ReadBoolean( &fixed_position ); + + arc.ReadFloat( &fov ); + arc.ReadFloat( &height ); + arc.ReadFloat( &follow_dist ); + arc.ReadInteger( &follow_mask ); + } + +class EXPORT_FROM_DLL CameraWatchState + { + public: + Vector dir; + + EntityPtr watchEnt; + + qboolean ignoreangles; + qboolean splineangles; + qboolean panning; + + float pan_offset; + float pan_dir; + float pan_max; + float pan_speed; + Vector pan_angles; + + float yaw; + qboolean fixedyaw; + + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void CameraWatchState::Archive + ( + Archiver &arc + ) + { + arc.WriteVector( dir ); + arc.WriteSafePointer( watchEnt ); + + arc.WriteBoolean( ignoreangles ); + arc.WriteBoolean( splineangles ); + arc.WriteBoolean( panning ); + + arc.WriteFloat( pan_offset ); + arc.WriteFloat( pan_dir ); + arc.WriteFloat( pan_max ); + arc.WriteFloat( pan_speed ); + arc.WriteVector( pan_angles ); + + arc.WriteFloat( yaw ); + arc.WriteBoolean( fixedyaw ); + } + +inline EXPORT_FROM_DLL void CameraWatchState::Unarchive + ( + Archiver &arc + ) + { + arc.ReadVector( &dir ); + arc.ReadSafePointer( &watchEnt ); + + arc.ReadBoolean( &ignoreangles ); + arc.ReadBoolean( &splineangles ); + arc.ReadBoolean( &panning ); + + arc.ReadFloat( &pan_offset ); + arc.ReadFloat( &pan_dir ); + arc.ReadFloat( &pan_max ); + arc.ReadFloat( &pan_speed ); + arc.ReadVector( &pan_angles ); + + arc.ReadFloat( &yaw ); + arc.ReadBoolean( &fixedyaw ); + } + +class EXPORT_FROM_DLL CameraState + { + public: + CameraMoveState move; + CameraWatchState watch; + + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void CameraState::Archive + ( + Archiver &arc + ) + { + move.Archive( arc ); + watch.Archive( arc ); + } + +inline EXPORT_FROM_DLL void CameraState::Unarchive + ( + Archiver &arc + ) + { + move.Unarchive( arc ); + watch.Unarchive( arc ); + } + +class EXPORT_FROM_DLL Camera : public Entity + { + private: + float default_fov; + float default_yaw; + float default_follow_dist; + float default_height; + float default_speed; + float default_pan_max; + float default_pan_speed; + + Vector default_angles; + + protected: + CameraState currentstate; + CameraState newstate; + + float watchTime; + float followTime; + float jumpTime; + + EntityPtr targetEnt; + EntityPtr targetWatchEnt; + + str nextCamera; + str overlay; + str thread; + + qboolean showcamera; + + Event moveevent; + + void FollowingPath( Event *ev ); + void CreateOribit( Vector pos, float radius ); + void CreatePath( SplinePath *path, splinetype_t type ); + void InitializeMoveState( CameraMoveState &movestate ); + void InitializeWatchState( CameraWatchState &watchstate ); + void InitializeState( CameraState &state ); + + public: + CLASS_PROTOTYPE( Camera ); + + float fov; + + Camera(); + void Stop( void ); + void FollowPath( SplinePath *path, qboolean loop, Entity *watch ); + void Orbit( Entity *ent, float dist, Entity *watch ); + void FollowEntity( Entity *ent, float dist, int mask, Entity *watch = NULL ); + void StartMoving( Event *ev ); + void StopMoving( Event *ev ); + void Pause( Event *ev ); + void Continue( Event *ev ); + void SetSpeed( Event *ev ); + void SetDistance( Event *ev ); + void SetHeight( Event *ev ); + void SetYaw( Event *ev ); + void FixedYaw( Event *ev ); + void RelativeYaw( Event *ev ); + void SetFOV( Event *ev ); + void OrbitEvent( Event *ev ); + void FollowEvent( Event *ev ); + void WatchEvent( Event *ev ); + void NoWatchEvent( Event *ev ); + void LookAt( Event *ev ); + void MoveToEntity( Event *ev ); + void MoveToPos( Event *ev ); + void IgnoreAngles( Event *ev ); + void UseAngles( Event *ev ); + void SplineAngles( Event *ev ); + void NormalAngles( Event *ev ); + void FixedPosition( Event *ev ); + void NoFixedPosition( Event *ev ); + void JumpCut( Event *ev ); + void JumpTime( Event *ev ); + void TurnTo( Event *ev ); + void EvaluatePosition( CameraState &state ); + void PanEvent( Event *ev ); + void StopPanEvent( Event *ev ); + void PanSpeedEvent( Event *ev ); + void PanMaxEvent( Event *ev ); + void SetPanAngles( Event *ev ); + void SetNextCamera( Event *ev ); + void SetOverlay( Event *ev ); + void SetThread( Event *ev ); + + str &NextCamera( void ); + str &Thread( void ); + str &Overlay( void ); + + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Camera::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteFloat( default_fov ); + arc.WriteFloat( default_yaw ); + arc.WriteFloat( default_follow_dist ); + arc.WriteFloat( default_height ); + arc.WriteFloat( default_speed ); + arc.WriteFloat( default_pan_max ); + arc.WriteFloat( default_pan_speed ); + arc.WriteVector( default_angles ); + + // currentstate + currentstate.Archive( arc ); + // newstate + newstate.Archive( arc ); + + arc.WriteFloat( watchTime ); + arc.WriteFloat( followTime ); + arc.WriteFloat( jumpTime ); + + arc.WriteSafePointer( targetEnt ); + arc.WriteSafePointer( targetWatchEnt ); + + arc.WriteString( nextCamera ); + arc.WriteString( overlay ); + arc.WriteString( thread ); + + arc.WriteBoolean( showcamera ); + arc.WriteEvent( moveevent ); + arc.WriteFloat( fov ); + } + +inline EXPORT_FROM_DLL void Camera::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadFloat( &default_fov ); + arc.ReadFloat( &default_yaw ); + arc.ReadFloat( &default_follow_dist ); + arc.ReadFloat( &default_height ); + arc.ReadFloat( &default_speed ); + arc.ReadFloat( &default_pan_max ); + arc.ReadFloat( &default_pan_speed ); + arc.ReadVector( &default_angles ); + + // currentstate + currentstate.Unarchive( arc ); + // newstate + newstate.Unarchive( arc ); + + arc.ReadFloat( &watchTime ); + arc.ReadFloat( &followTime ); + arc.ReadFloat( &jumpTime ); + + arc.ReadSafePointer( &targetEnt ); + arc.ReadSafePointer( &targetWatchEnt ); + + arc.ReadString( &nextCamera ); + arc.ReadString( &overlay ); + arc.ReadString( &thread ); + + arc.ReadBoolean( &showcamera ); + arc.ReadEvent( &moveevent ); + arc.ReadFloat( &fov ); + } + +void SetCamera( Entity *ent ); + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL SafePtr; +#endif +typedef SafePtr CameraPtr; + +class EXPORT_FROM_DLL SecurityCamera : public Camera + { + public: + CLASS_PROTOTYPE( SecurityCamera ); + + SecurityCamera(); + }; + +#endif /* camera.h */ diff --git a/camgun.cpp b/camgun.cpp new file mode 100644 index 0000000..5fa2fa7 --- /dev/null +++ b/camgun.cpp @@ -0,0 +1,298 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/camgun.cpp $ +// $Revision:: 18 $ +// $Author:: Markd $ +// $Date:: 10/15/98 3:39p $ +// +// Copyright (C) 1998 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/camgun.cpp $ +// +// 18 10/15/98 3:39p Markd +// Moved health parsing into turrets constructor +// +// 17 10/01/98 4:01p Markd +// Added Archive and Unarchive functions +// +// 16 9/22/98 4:57p Aldie +// Moved lagtime check right before firing +// +// 15 9/20/98 9:08p Aldie +// Fixed some stuff with the patience of turrets +// +// 14 9/19/98 6:09p Aldie +// Made wakeupdistance and firingdistance spawn args +// +// 13 8/27/98 9:01p Jimdose +// Centroid is now a variable +// +// 12 8/17/98 8:36p Aldie +// Fixed angle clamping +// +// 11 8/17/98 2:56p Aldie +// Make camgun take into account angles +// +// 10 8/15/98 2:18p Aldie +// Added health to camguns +// +// 9 8/10/98 6:52p Aldie +// Fixed an error in ClassDeclaration +// +// 8 7/26/98 6:54a Aldie +// Tweaked range and no damage +// +// 7 7/26/98 6:43a Aldie +// Increase distance +// +// 6 7/22/98 5:15p Aldie +// Fixed general behavior +// +// 5 7/17/98 7:04p Aldie +// Increased next seek time and removed some useless code. +// +// 4 7/17/98 4:39p Aldie +// Fixed some camgun stuff. +// +// 3 7/10/98 5:38p Aldie +// Changed camgun's health and made it take damage +// +// 2 7/07/98 4:10p Aldie +// First version of camgun +// +// DESCRIPTION: +// Generic turret + +#include "turret.h" +#include "weapon.h" + +class EXPORT_FROM_DLL Camgun : public Turret + { + private: + Vector zeroangle; + float yawrange; + float pitchrange; + float maxpitch; + float maxyaw; + Vector new_orientation; + public: + CLASS_PROTOTYPE( Camgun ); + + Camgun(); + virtual void Seek( Event *ev ); + virtual void Turn( Event *ev ); + virtual void Down( Event *ev ); + virtual void ClampOrientation( void ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +EXPORT_FROM_DLL void Camgun::Archive + ( + Archiver &arc + ) + { + Turret::Archive( arc ); + + arc.WriteVector( zeroangle ); + arc.WriteFloat( yawrange ); + arc.WriteFloat( pitchrange ); + arc.WriteFloat( maxpitch ); + arc.WriteFloat( maxyaw ); + arc.WriteVector( new_orientation ); + } + +EXPORT_FROM_DLL void Camgun::Unarchive + ( + Archiver &arc + ) + { + Turret::Unarchive( arc ); + + arc.ReadVector( &zeroangle ); + arc.ReadFloat( &yawrange ); + arc.ReadFloat( &pitchrange ); + arc.ReadFloat( &maxpitch ); + arc.ReadFloat( &maxyaw ); + arc.ReadVector( &new_orientation ); + } + +CLASS_DECLARATION( Turret, Camgun, "trap_camgun" ); + +ResponseDef Camgun::Responses[] = + { + { NULL, NULL } + }; + +Camgun::Camgun + ( + void + ) + + { + setModel( "camgun2.def" ); + RandomAnimate( "down_idle", NULL ); + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_BBOX ); + + gunoffset = "0 0 0"; + neworientation = angles.yaw(); + flags |= FL_SPARKS; + + zeroangle = Vector ( 0, G_GetFloatArg( "angle", 0 ), 0 ); + + if ( zeroangle[ YAW ] > 180 ) + zeroangle[ YAW ] -= 360; + if ( zeroangle[ YAW ] < -180 ) + zeroangle[ YAW ] += 360; + + yawrange = G_GetFloatArg( "yawrange", 180 ); + maxyaw = yawrange; + pitchrange = G_GetFloatArg( "pitchrange", 180 ); + maxpitch = pitchrange; + + wakeupdistance = G_GetFloatArg( "wakeupdistance", 750 ); + firingdistance = G_GetFloatArg( "firingdistance", 800 ); + } + +void Camgun::ClampOrientation + ( + ) + + { + Vector delta; + + delta = new_orientation - zeroangle; + + if ( delta[ PITCH ] > 180 ) + delta[ PITCH ] -= 360; + if ( delta[ PITCH ] < -180 ) + delta[ PITCH ] += 360; + + if ( delta[ PITCH ] > maxpitch ) + delta[ PITCH ] = maxpitch; + if ( delta[ PITCH ] < -maxpitch ) + delta[ PITCH ] = -maxpitch; + + if ( delta[ YAW ] > 180 ) + delta[ YAW ] -= 360; + if ( delta[ YAW ] < -180 ) + delta[ YAW ] += 360; + + if ( delta[YAW] > maxyaw ) + delta[YAW] = maxyaw; + if ( delta[YAW] < -maxyaw ) + delta[YAW] = -maxyaw; + + new_orientation[ PITCH ] = zeroangle[ PITCH ] + delta[ PITCH ]; + new_orientation[ YAW ] = zeroangle[ YAW ] + delta[ YAW ]; + } + +void Camgun::Seek + ( + Event *ev + ) + + { + Entity *ent; + Vector v; + Vector s; + int range; + Vector f; + Vector pos; + + active = true; + ent = NULL; + if ( enemy ) + { + ent = G_GetEntity( enemy ); + if ( ( !ent ) || ( ent->health <= 0 ) || ( ent->flags & FL_NOTARGET ) || ( ent == this ) ) + { + enemy = 0; + ent = NULL; + } + else + { + range = Range( Distance( ent ) ); + } + } + + if ( ( lastSightTime ) && ( ( lastSightTime + patience ) < level.time ) ) + { + ProcessEvent( EV_Turret_GoDown ); + } + + if ( !enemy ) + { + FindTarget(); + PostEvent( EV_Turret_Seek, FRAMETIME * 2 ); + return; + } + + if ( ( range != TURRET_OUTOFRANGE ) && ent && CanSee( ent ) ) + { + lastSightTime = level.time; + v = ent->centroid - worldorigin; + new_orientation = v.toAngles(); + ClampOrientation(); + } + + if ( ( angles[ YAW ] != new_orientation[ YAW ] ) && !turning) + { + Event *event; + event = new Event( EV_Turret_Turn ); + event->AddVector( new_orientation ); + ProcessEvent( event ); + } + + if ( range == TURRET_FIRERANGE && !attacking) + { + // Allow some freetime to let player get somewhere before turret shoots + if ( level.time < firetime ) + { + PostEvent( EV_Turret_Seek, FRAMETIME ); + return; + } + PostEvent( EV_Turret_Attack, 0.1 ); + } + + PostEvent( EV_Turret_Seek, FRAMETIME ); + } + +void Camgun::Turn + ( + Event *ev + ) + + { + Vector new_angle = ev->GetVector( 1 ); + + if ( angles[ YAW ] != new_angle[ YAW ] ) + { + turntime = level.time + 0.2; + turning = true; + } + else if ( turntime < level.time ) + { + turning = false; + angles[ PITCH ] = -new_angle[ PITCH ]; + return; + } + + angles[ PITCH ] = -new_angle[ PITCH ]; + angles[ YAW ] = new_angle[ YAW ];//AdjustAngle( 12, angles[ YAW ], new_angle[ YAW ] ); + setAngles( angles ); + PostEvent( ev, FRAMETIME ); + } + +void Camgun::Down + ( + Event *ev + ) + + { + } \ No newline at end of file diff --git a/chaingun.cpp b/chaingun.cpp new file mode 100644 index 0000000..a42f235 --- /dev/null +++ b/chaingun.cpp @@ -0,0 +1,365 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/chaingun.cpp $ +// $Revision:: 43 $ +// $Author:: Jimdose $ +// $Date:: 11/18/98 6:11p $ +// +// Copyright (C) 1998 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/chaingun.cpp $ +// +// 43 11/18/98 6:11p Jimdose +// fix problems with gravaxis +// +// 42 11/15/98 9:12p Markd +// Put in more precaching for models and sprites +// +// 41 11/13/98 3:30p Markd +// put in more precaching on weapons +// +// 40 10/27/98 3:43a Aldie +// Tweak damage +// +// 39 10/26/98 2:50p Aldie +// Fixed a bug with checking of NULL owners +// +// 38 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 37 10/23/98 5:38a Jimdose +// Added SpawnBlastDamage +// +// 36 10/22/98 7:57p Markd +// put in proper pre-caching in all the classes +// +// 35 10/22/98 5:56p Markd +// Made a bunch of global sounds local to that entity +// +// 34 10/20/98 8:26p Markd +// Added Attacker to DamageSurface stuff +// +// 33 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 32 10/16/98 10:22p Aldie +// Updated single player damage settings +// +// 31 10/16/98 9:49p Aldie +// Added SecondaryAmmo command +// +// 30 10/10/98 7:40p Aldie +// Added a smoke anim +// +// 29 10/10/98 7:14p Aldie +// Tweaked damage +// +// 28 9/27/98 4:19p Aldie +// Increase the damage a little +// +// 27 9/18/98 8:14p Markd +// rewrote surface system so that surfaces are now damaged by surface name instead +// of by surfinfo +// +// 26 9/05/98 12:10p Aldie +// Bounce grenades off things with < 0 health +// +// 25 9/01/98 3:05p Markd +// Rewrote explosion code +// +// 24 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 23 8/24/98 6:51p Jimdose +// Added SetGravityAxis +// +// 22 8/22/98 9:37p Jimdose +// Added support for alternate gravity axis +// +// 21 8/18/98 11:08p Markd +// Added new Alias System +// +// 20 8/18/98 8:12p Aldie +// Added dual mode weapons to base class +// +// 19 8/06/98 10:53p Aldie +// Added weapon tweaks and kickback. Also modified blast radius damage and rocket +// jumping. +// +// 18 8/01/98 3:24p Aldie +// Added server effects flag for specific weapons +// +// 17 8/01/98 3:03p Aldie +// Client side muzzle flash (dynamic light) +// +// 16 7/25/98 7:19p Aldie +// Client side explosion +// +// 15 7/25/98 7:10p Markd +// Put in EV_Removes for demo +// +// 14 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 +// +// 13 7/22/98 9:57p Markd +// Defined weapon type +// +// 12 7/22/98 5:15p Aldie +// Fixed NextAttack time +// +// 11 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 10 6/30/98 6:47p Aldie +// Changed damage and explosion effect. +// +// 9 6/19/98 9:29p Jimdose +// Moved gun orientation code to Weapon +// +// 8 6/15/98 10:36a Aldie +// Updated the mode setting +// +// 7 6/10/98 10:03p Aldie +// Updated orientation of shell +// +// 6 6/10/98 2:10p Aldie +// Updated damage function. +// +// 5 6/09/98 12:52p Aldie +// Added alternate use - Grenade Launcher +// +// 4 6/08/98 7:21p Aldie +// Updated attack time +// +// 3 5/27/98 1:33a Markd +// fixed spawn function name +// +// 2 5/11/98 11:24a Markd +// First time +// +// 1 5/11/98 11:13a Markd +// +// 1 5/11/98 9:55a Markd +// +// DESCRIPTION: +// High velocity chain gun +// + +#include "g_local.h" +#include "bullet.h" +#include "chaingun.h" +#include "rocketlauncher.h" +#include "explosion.h" +#include "specialfx.h" +#include "misc.h" +#include "surface.h" + +#define BULLET_MODE 1 + + +CLASS_DECLARATION( Projectile, Grenade, "grenade" ); + +Event EV_Grenade_Explode( "grenade_explode" ); + +ResponseDef Grenade::Responses[] = + { + { &EV_Touch, ( Response )Grenade::Grenade_Touch }, + { &EV_Grenade_Explode, ( Response )Grenade::Explode }, + { NULL, NULL } + }; + +EXPORT_FROM_DLL void Grenade::Grenade_Touch + ( + Event *ev + ) + + { + Entity *other; + + other = ev->GetEntity( 1 ); + assert( other ); + + if ( other->entnum == owner ) + return; + + if ( HitSky() ) + { + PostEvent( EV_Remove, 0 ); + return; + } + + if ( !other->takedamage || other->health <= 0 ) + { + // Play a bouncy sound + RandomSound( "grenade_bounce", 1 ); + return; + } + + Explode( ev ); + } + +EXPORT_FROM_DLL void Grenade::Explode + ( + Event *ev + ) + + { + int damg; + Vector v; + Entity *other; + Entity *owner; + + other = ev->GetEntity( 1 ); + assert( other ); + + if ( other->isSubclassOf( Teleporter ) ) + { + return; + } + + stopsound( CHAN_VOICE ); + setSolidType( SOLID_NOT ); + hideModel(); + + if ( HitSky() ) + { + PostEvent( EV_Remove, 0 ); + return; + } + + owner = G_GetEntity( this->owner ); + + if ( !owner ) + owner = world; + + damg = 75 + ( int )G_Random( 25 ); + + if ( !deathmatch->value && owner->isClient() ) + damg *= 1.5; + + if ( other->takedamage ) + other->Damage( this, owner, damg, worldorigin, velocity, level.impact_trace.plane.normal, 30, 0, MOD_GRENADE, -1, -1, 1.0f ); + + SpawnBlastDamage( &level.impact_trace, damg, owner ); + + v = velocity; + v.normalize(); + + // don't do radius damage to the other, because all the damage + // was done in the impact + v = worldorigin - v * 24; + CreateExplosion( v, damg, 1.0f, true, this, owner, other ); + PostEvent( EV_Remove, 0.1 ); + } + +EXPORT_FROM_DLL void Grenade::Setup + ( + Entity *owner, + Vector pos, + Vector forward, + Vector right, + Vector up + ) + + { + Event *ev; + + setModel( "grenade.def" ); + + this->owner = owner->entnum; + edict->owner = owner->edict; + setMoveType( MOVETYPE_BOUNCE ); + setSolidType( SOLID_BBOX ); + takedamage = DAMAGE_YES; + edict->clipmask = MASK_PROJECTILE; + health = 10; + SetGravityAxis( owner->gravaxis ); + + velocity = forward * ( 500 + G_Random( 200 ) ); + velocity += up * ( 200 + crandom() * 10.0 ); + velocity += right * ( crandom() * 10.0 ); + + avelocity = "575 0 0"; + + ev = new Event( EV_Grenade_Explode ); + ev->AddEntity( world ); + PostEvent( ev, 2.5 + G_Random(1.0) ); + + edict->s.effects |= EF_ROCKET; + setOrigin( pos ); + worldorigin.copyTo( edict->s.old_origin ); + setSize( "-4 -4 -4", "4 4 4" ); + + edict->s.effects |= EF_EVERYFRAME; + RandomAnimate( "smoke", NULL ); + } + +CLASS_DECLARATION( BulletWeapon, ChainGun, "weapon_highvelocitygun" ); + +ResponseDef ChainGun::Responses[] = + { + { &EV_Weapon_Shoot, ( Response )ChainGun::Shoot }, + { NULL, NULL } + }; + +ChainGun::ChainGun + ( + ) + + { +#ifdef SIN_DEMO + PostEvent( EV_Remove, 0 ); + return; +#endif + SetModels( "hvgun.def", "view_hvgun.def" ); + SetAmmo( "Bullet50mm", 1, 30 ); + SetSecondaryAmmo( "Rockets", 1, 5); + SetRank( 50, 50 ); + SetType( WEAPON_2HANDED_LO ); + dualmode = true; + modelIndex( "grenade.def" ); + modelIndex( "rocket.def" ); + modelIndex( "rockets.def" ); + modelIndex( "50mm.def" ); + modelIndex( "sprites/blastmark.spr" ); + modelIndex( "sprites/hvblast.spr" ); + modelIndex( "sprites/tracer.spr" ); + modelIndex( "hvshell.def" ); + } + + +void ChainGun::Shoot + ( + Event *ev + ) + + { + if ( weaponmode == PRIMARY ) + { + if ( deathmatch->value ) + FireBullets( 1, "300 300 300", 24, 32, DAMAGE_BULLET, MOD_CHAINGUN, false ); + else + FireBullets( 1, "300 300 300", 16, 24, DAMAGE_BULLET, MOD_CHAINGUN, false ); + NextAttack( 0 ); + } + else + { + Grenade *grenade; + Vector pos; + Vector forward; + Vector up; + Vector right; + + GetMuzzlePosition( &pos, &forward, &up, &right ); + grenade = new Grenade; + grenade->Setup( owner, pos, forward, up, right ); + NextAttack( 0.8 ); + } + } + diff --git a/chaingun.h b/chaingun.h new file mode 100644 index 0000000..5f1e36d --- /dev/null +++ b/chaingun.h @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/chaingun.h $ +// $Revision:: 9 $ +// $Author:: Jimdose $ +// $Date:: 11/18/98 6:12p $ +// +// Copyright (C) 1998 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/chaingun.h $ +// +// 9 11/18/98 6:12p Jimdose +// fix problems with gravaxis +// +// 8 9/21/98 4:50p Markd +// Fixed projectile owner +// +// 7 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 6 8/18/98 8:12p Aldie +// Added dual mode weapons to base class +// +// 5 8/06/98 10:53p Aldie +// Added weapon tweaks and kickback. Also modified blast radius damage and rocket +// jumping. +// +// 4 6/10/98 2:10p Aldie +// Updated damage function. +// +// 3 6/09/98 12:54p Aldie +// Added grenade launcher alternate use function +// +// 2 5/11/98 11:24a Markd +// First time +// +// 1 5/11/98 11:13a Markd +// +// 1 5/11/98 9:55a Markd +// +// DESCRIPTION: +// High Velocity Gun +// + +#ifndef __CHAINGUN_H__ +#define __CHAINGUN_H__ + +#include "g_local.h" +#include "item.h" +#include "weapon.h" +#include "bullet.h" +#include "specialfx.h" + +class EXPORT_FROM_DLL Grenade : public Projectile + { + public: + CLASS_PROTOTYPE( Grenade ); + + virtual void Explode( Event *ev ); + virtual void Grenade_Touch( Event *ev ); + void Setup( Entity *owner, Vector pos, Vector forward, Vector up, Vector right ); + + }; + +class EXPORT_FROM_DLL ChainGun : public BulletWeapon + { + public: + CLASS_PROTOTYPE( ChainGun ); + + ChainGun::ChainGun(); + virtual void Shoot( Event *ev ); + }; + +#endif /* ChainGun.h */ diff --git a/class.cpp b/class.cpp new file mode 100644 index 0000000..775f3e9 --- /dev/null +++ b/class.cpp @@ -0,0 +1,643 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/class.cpp $ +// $Revision:: 20 $ +// $Author:: Jimdose $ +// $Date:: 10/19/98 12:07a $ +// +// 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/class.cpp $ +// +// 20 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 19 10/07/98 11:42p Jimdose +// Got savegames working +// +// 18 9/24/98 1:49a Jimdose +// Added DisplayMemoryUsage +// +// 17 9/21/98 2:15a Jimdose +// Moved non-type specific code in SafePtr to SafePtrBase to help with save +// games +// +// 16 8/27/98 9:04p Jimdose +// NumEventCommands is now a member of Event +// +// 15 6/27/98 9:18p Jimdose +// Made lookup for event responses for faster processing +// +// 14 6/15/98 9:09p Aldie +// Fixed checkInheritance printfs +// +// 13 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 12 5/11/98 8:06p Jimdose +// Added SafePtr +// +// 11 5/08/98 2:51p Jimdose +// Added archiving functions +// +// 10 3/27/98 6:35p Jimdose +// made checking of classnames case independant +// +// 9 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 8 3/04/98 1:49p Jimdose +// Changed overloaded delete so that it used delete[] instead of delete, since +// we allocate with new[]; +// +// 7 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. +// +// 6 2/03/98 10:53a Jimdose +// Updated to work with Quake 2 engine +// Made class registration automatic +// +// 5 1/22/98 6:50p Jimdose +// Made Q2 compatible +// +// 3 10/27/97 3:40p Jimdose +// Included stdarg.h +// +// 2 9/26/97 6:13p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Base class that all classes that are used in conjunction with Sin should +// be based off of. Class gives run-time type information about any class +// derived from it. This is really handy when you have a pointer to an object +// that you need to know if it supports certain behaviour. +// + +#include +#include +#include +#include "g_local.h" +#include "class.h" +#include "linklist.h" + +int totalmemallocated = 0; +int numclassesallocated = 0; + +static ClassDef *classlist = NULL; + +ClassDef::ClassDef() + { + this->classname = NULL; + this->classID = NULL; + this->superclass = NULL; + this->responses = NULL, + this->numEvents = 0; + this->responseLookup = NULL; + this->newInstance = NULL; + this->classSize = 0; + this->super = NULL; + this->prev = this; + this->next = this; + } + +ClassDef::ClassDef + ( + const char *classname, + const char *classID, + const char *superclass, + ResponseDef *responses, + void *( *newInstance )( void ), + int classSize + ) + + { + ClassDef *node; + + if ( classlist == NULL ) + { + classlist = new ClassDef; + } + + this->classname = classname; + this->classID = classID; + this->superclass = superclass; + this->responses = responses; + this->numEvents = 0; + this->responseLookup = NULL; + this->newInstance = newInstance; + this->classSize = classSize; + this->super = getClass( superclass ); + + // It's not uncommon for classes to not have a class id, so just set it + // to an empty string so that we're not checking for it all the time. + if ( !classID ) + { + this->classID = ""; + } + + // Check if any subclasses were initialized before their superclass + for( node = classlist->next; node != classlist; node = node->next ) + { + if ( ( node->super == NULL ) && ( !Q_stricmp( node->superclass, this->classname ) ) && + ( Q_stricmp( node->classname, "Class" ) ) ) + { + node->super = this; + } + } + + // Add to front of list + LL_Add( classlist, this, prev, next ); + } + +ClassDef::~ClassDef() + { + ClassDef *node; + + if ( classlist != this ) + { + LL_Remove( this, prev, next ); + + // Check if any subclasses were initialized before their superclass + for( node = classlist->next; node != classlist; node = node->next ) + { + if ( node->super == this ) + { + node->super = NULL; + } + } + } + else + { + // If the head of the list is deleted before the list is cleared, then we may have problems + assert( this->next == this->prev ); + } + + if ( responseLookup ) + { + delete[] responseLookup; + responseLookup = NULL; + } + } + +EXPORT_FROM_DLL void ClassDef::BuildResponseList + ( + void + ) + + { + ClassDef *c; + ResponseDef *r; + int ev; + int i; + qboolean *set; + int num; + + if ( responseLookup ) + { + delete[] responseLookup; + responseLookup = NULL; + } + + num = Event::NumEventCommands(); + responseLookup = ( Response ** )new char[ sizeof( Response * ) * num ]; + memset( responseLookup, 0, sizeof( Response * ) * num ); + + set = new qboolean[ num ]; + memset( set, 0, sizeof( qboolean ) * num ); + + this->numEvents = num; + + for( c = this; c != NULL; c = c->super ) + { + r = c->responses; + if ( r ) + { + for( i = 0; r[ i ].event != NULL; i++ ) + { + ev = ( int )*r[ i ].event; + if ( !set[ ev ] ) + { + set[ ev ] = true; + if ( r[ i ].response ) + { + responseLookup[ ev ] = &r[ i ].response; + } + else + { + responseLookup[ ev ] = NULL; + } + } + } + } + } + + delete[] set; + } + +EXPORT_FROM_DLL void BuildEventResponses + ( + void + ) + + { + ClassDef *c; + int amount; + int numclasses; + + amount = 0; + numclasses = 0; + for( c = classlist->next; c != classlist; c = c->next ) + { + c->BuildResponseList(); + + amount += c->numEvents * sizeof( Response * ); + numclasses++; + } + + gi.dprintf( "\n------------------\nEvent system initialized:\n" + "%d classes\n%d events\n%d total memory in response list\n\n", + numclasses, Event::NumEventCommands(), amount ); + } + +EXPORT_FROM_DLL ClassDef *getClassForID + ( + const char *name + ) + + { + ClassDef *c; + + for( c = classlist->next; c != classlist; c = c->next ) + { + if ( c->classID && !Q_stricmp( c->classID, name ) ) + { + return c; + } + } + + return NULL; + } + +EXPORT_FROM_DLL ClassDef *getClass + ( + const char *name + ) + + { + ClassDef *c; + + for( c = classlist->next; c != classlist; c = c->next ) + { + if ( !Q_stricmp( c->classname, name ) ) + { + return c; + } + } + + return NULL; + } + +EXPORT_FROM_DLL ClassDef *getClassList + ( + void + ) + + { + return classlist; + } + +EXPORT_FROM_DLL void listAllClasses + ( + void + ) + + { + ClassDef *c; + + for( c = classlist->next; c != classlist; c = c->next ) + { + gi.dprintf( "%s\n", c->classname ); + } + } + +EXPORT_FROM_DLL void listInheritanceOrder + ( + const char *classname + ) + + { + ClassDef *cls; + ClassDef *c; + + cls = getClass( classname ); + if ( !cls ) + { + gi.dprintf( "Unknown class: %s\n", classname ); + return; + } + for( c = cls; c != NULL; c = c->super ) + { + gi.dprintf( "%s\n", c->classname ); + } + } + +EXPORT_FROM_DLL qboolean checkInheritance + ( + ClassDef *superclass, + ClassDef *subclass + ) + + { + ClassDef *c; + + for( c = subclass; c != NULL; c = c->super ) + { + if ( c == superclass ) + { + return true; + } + } + return false; + } + +EXPORT_FROM_DLL qboolean checkInheritance + ( + ClassDef *superclass, + const char *subclass + ) + + { + ClassDef *c; + + c = getClass( subclass ); + if ( c == NULL ) + { + gi.dprintf( "Unknown class: %s\n", subclass ); + return false; + } + return checkInheritance( superclass, c ); + } + +EXPORT_FROM_DLL qboolean checkInheritance + ( + const char *superclass, + const char *subclass + ) + + { + ClassDef *c1; + ClassDef *c2; + + c1 = getClass( superclass ); + c2 = getClass( subclass ); + if ( c1 == NULL ) + { + gi.dprintf( "Unknown class: %s\n", superclass ); + return false; + } + if ( c2 == NULL ) + { + gi.dprintf( "Unknown class: %s\n", subclass ); + return false; + } + return checkInheritance( c1, c2 ); + } + +CLASS_DECLARATION( NULL, Class, NULL ); + +ResponseDef Class::Responses[] = + { + { NULL, NULL } + }; + +#ifdef NDEBUG + +EXPORT_FROM_DLL void * Class::operator new( size_t s ) + { + int *p; + + s += sizeof( int ); + p = ( int * )::new char[ s ]; + *p = s; + totalmemallocated += s; + numclassesallocated++; + return p + 1; + } + +EXPORT_FROM_DLL void Class::operator delete( void *ptr ) + { + int *p; + + p = ( ( int * )ptr ) - 1; + totalmemallocated -= *p; + numclassesallocated--; + ::delete[]( p ); + } + +#else + +EXPORT_FROM_DLL void * Class::operator new( size_t s ) + { + int *p; + + s += sizeof( int ) * 3; + p = ( int * )::new char[ s ]; + p[ 0 ] = 0x12348765; + *( int * )( ((byte *)p) + s - sizeof( int ) ) = 0x56784321; + p[ 1 ] = s; + totalmemallocated += s; + numclassesallocated++; + return p + 2; + } + +EXPORT_FROM_DLL void Class::operator delete( void *ptr ) + { + int *p; + + p = ( ( int * )ptr ) - 2; + + assert( p[ 0 ] == 0x12348765 ); + assert( *( int * )( ((byte *)p) + p[ 1 ] - sizeof( int ) ) == 0x56784321 ); + + totalmemallocated -= p[ 1 ]; + numclassesallocated--; + ::delete[]( p ); + } + +#endif + +EXPORT_FROM_DLL void DisplayMemoryUsage + ( + void + ) + + { + gi.printf( "Classes %-5d Class memory used: %d\n", numclassesallocated, totalmemallocated ); + } + Class::Class() + { + SafePtrList = NULL; + } + +Class::~Class() + { + while( SafePtrList != NULL ) + { + SafePtrList->Clear(); + } + } + +EXPORT_FROM_DLL void Class::Archive + ( + Archiver &arc + ) + + { + } + +EXPORT_FROM_DLL void Class::Unarchive + ( + Archiver &arc + ) + + { + } + +EXPORT_FROM_DLL void Class::warning + ( + const char *function, + const char *fmt, + ... + ) + + { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( getClassID() ) + { + gi.dprintf( "%s::%s : %s\n", getClassID(), function, text ); + } + else + { + gi.dprintf( "%s::%s : %s\n", getClassname(), function, text ); + } + } + +EXPORT_FROM_DLL void Class::error + ( + const char *function, + const char *fmt, + ... + ) + + { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( getClassID() ) + { + gi.error( "%s::%s : %s\n", getClassID(), function, text ); + } + else + { + gi.error( "%s::%s : %s\n", getClassname(), function, text ); + } + } + +EXPORT_FROM_DLL qboolean Class::inheritsFrom + ( + const char *name + ) + + { + ClassDef *c; + + c = getClass( name ); + if ( c == NULL ) + { + gi.dprintf( "Unknown class: %s\n", name ); + return false; + } + return checkInheritance( c, classinfo() ); + } + +EXPORT_FROM_DLL qboolean Class::isInheritedBy + ( + const char *name + ) + + { + ClassDef *c; + + c = getClass( name ); + if ( c == NULL ) + { + gi.dprintf( "Unknown class: %s\n", name ); + return false; + } + return checkInheritance( classinfo(), c ); + } + +EXPORT_FROM_DLL const char *Class::getClassname + ( + void + ) + + { + ClassDef *cls; + + cls = classinfo(); + return cls->classname; + } + +EXPORT_FROM_DLL const char *Class::getClassID + ( + void + ) + + { + ClassDef *cls; + + cls = classinfo(); + return cls->classID; + } + +EXPORT_FROM_DLL const char *Class::getSuperclass + ( + void + ) + + { + ClassDef *cls; + + cls = classinfo(); + return cls->superclass; + } + +EXPORT_FROM_DLL void *Class::newInstance + ( + void + ) + + { + ClassDef *cls; + + cls = classinfo(); + return cls->newInstance(); + } diff --git a/class.h b/class.h new file mode 100644 index 0000000..60885c9 --- /dev/null +++ b/class.h @@ -0,0 +1,447 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/class.h $ +// $Revision:: 17 $ +// $Author:: Jimdose $ +// $Date:: 10/25/98 11:53p $ +// +// 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/class.h $ +// +// 17 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 16 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 15 10/07/98 11:42p Jimdose +// Got savegames working +// +// 14 9/24/98 1:49a Jimdose +// Added DisplayMemoryUsage +// +// 13 9/21/98 2:15a Jimdose +// Moved non-type specific code in SafePtr to SafePtrBase to help with save +// games +// +// 12 9/03/98 9:08p Jimdose +// Overrided == and != for SafePtr +// +// 11 6/27/98 9:18p Jimdose +// Made lookup for event responses for faster processing +// +// 10 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 9 5/11/98 8:06p Jimdose +// Added SafePtr +// +// 8 5/08/98 2:51p Jimdose +// Added archiving functions +// +// 7 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 6 3/02/98 8:49p Jimdose +// Changed CLASS_PROTOTYPE to only take the classname +// +// 5 2/03/98 10:53a Jimdose +// Updated to work with Quake 2 engine +// Made class registration automatic +// +// 4 1/22/98 6:50p Jimdose +// Made Q2 compatible +// +// 2 9/26/97 6:13p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Base class that all classes that are used in conjunction with Sin should +// be based off of. Class gives run-time type information about any class +// derived from it. This is really handy when you have a pointer to an object +// that you need to know if it supports certain behaviour. +// + +#ifndef __CLASS_H__ +#define __CLASS_H__ + +#include "g_local.h" + +class Class; +class Event; +class Archiver; + +typedef void ( Class::*Response )( Event *event ); +typedef struct + { + Event *event; + Response response; + } ResponseDef; + +/*********************************************************************** + + ClassDef + +***********************************************************************/ + +class EXPORT_FROM_DLL ClassDef + { + public: + const char *classname; + const char *classID; + const char *superclass; + void *( *newInstance )( void ); + int classSize; + ResponseDef *responses; + int numEvents; + Response **responseLookup; + ClassDef *super; + ClassDef *next; + ClassDef *prev; + + ClassDef(); + ~ClassDef(); + ClassDef( const char *classname, const char *classID, const char *superclass, + ResponseDef *responses, void *( *newInstance )( void ), int classSize ); + void BuildResponseList( void ); + }; + +/*********************************************************************** + + SafePtr + +***********************************************************************/ + +class SafePtrBase; + +class Class; + +class EXPORT_FROM_DLL SafePtrBase + { + private: + void AddReference( Class *ptr ); + void RemoveReference( Class *ptr ); + + protected: + SafePtrBase *prevSafePtr; + SafePtrBase *nextSafePtr; + Class *ptr; + + public: + SafePtrBase(); + virtual ~SafePtrBase(); + void InitSafePtr( Class *newptr ); + void Clear( void ); + }; + +/*********************************************************************** + + Class + +***********************************************************************/ + +#define CLASS_DECLARATION( nameofsuperclass, nameofclass, classid ) \ + ClassDef nameofclass::ClassInfo \ + ( \ + #nameofclass, classid, #nameofsuperclass, \ + nameofclass::Responses, nameofclass::_newInstance, \ + sizeof( nameofclass ) \ + ); \ + EXPORT_FROM_DLL void *nameofclass::_newInstance( void ) \ + { \ + return new nameofclass; \ + } \ + ClassDef *nameofclass::classinfo( void ) \ + { \ + return &( nameofclass::ClassInfo ); \ + } + +#define CLASS_PROTOTYPE( nameofclass ) \ + public: \ + static ClassDef ClassInfo; \ + static void *nameofclass::_newInstance( void ); \ + virtual ClassDef *nameofclass::classinfo( void ); \ + static ResponseDef nameofclass::Responses[]; + +class EXPORT_FROM_DLL Class + { + private: + SafePtrBase *SafePtrList; + friend class SafePtrBase; + + public: + CLASS_PROTOTYPE( Class ); + void * operator new( size_t ); + void operator delete( void * ); + + Class(); + virtual ~Class(); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + void warning( const char *function, const char *fmt, ... ); + void error( const char *function, const char *fmt, ... ); + qboolean inheritsFrom( ClassDef *c ); + qboolean inheritsFrom( const char *name ); + qboolean isInheritedBy( const char *name ); + qboolean isInheritedBy( ClassDef *c ); + const char *getClassname( void ); + const char *getClassID( void ); + const char *getSuperclass( void ); + void *newInstance( void ); + }; + +EXPORT_FROM_DLL void BuildEventResponses( void ); +EXPORT_FROM_DLL ClassDef *getClassForID( const char *name ); +EXPORT_FROM_DLL ClassDef *getClass( const char *name ); +EXPORT_FROM_DLL ClassDef *getClassList( void ); +EXPORT_FROM_DLL void listAllClasses( void ); +EXPORT_FROM_DLL void listInheritanceOrder( const char *classname ); +EXPORT_FROM_DLL qboolean checkInheritance( ClassDef *superclass, ClassDef *subclass ); +EXPORT_FROM_DLL qboolean checkInheritance( ClassDef *superclass, const char *subclass ); +EXPORT_FROM_DLL qboolean checkInheritance( const char *superclass, const char *subclass ); +EXPORT_FROM_DLL void DisplayMemoryUsage( void ); + +inline EXPORT_FROM_DLL qboolean Class::inheritsFrom + ( + ClassDef *c + ) + + { + return checkInheritance( c, classinfo() ); + } + +inline EXPORT_FROM_DLL qboolean Class::isInheritedBy + ( + ClassDef *c + ) + + { + return checkInheritance( classinfo(), c ); + } + +// The lack of a space between the ")" and "inheritsFrom" is intentional. +// It allows the macro to compile like a function call. However, this +// may cause problems in some compilers (like gcc), so we may have to +// change this to work like a normal macro with the object passed in +// as a parameter to the macro. +#define isSubclassOf( classname )inheritsFrom( &classname::ClassInfo ) +#define isSuperclassOf( classname )isInheritedBy( &classname::ClassInfo ) + +/*********************************************************************** + + SafePtr + +***********************************************************************/ + +inline EXPORT_FROM_DLL SafePtrBase::SafePtrBase() + { + prevSafePtr = NULL; + nextSafePtr = NULL; + ptr = NULL; + } + +inline EXPORT_FROM_DLL SafePtrBase::~SafePtrBase() + { + Clear(); + } + +inline EXPORT_FROM_DLL void SafePtrBase::Clear + ( + void + ) + + { + if ( ptr ) + { + RemoveReference( ptr ); + ptr = NULL; + } + } + +inline EXPORT_FROM_DLL void SafePtrBase::InitSafePtr + ( + Class *newptr + ) + + { + if ( ptr != newptr ) + { + if ( ptr ) + { + RemoveReference( ptr ); + } + + ptr = newptr; + if ( ptr == NULL ) + { + return; + } + + AddReference( ptr ); + } + } + +inline EXPORT_FROM_DLL void SafePtrBase::AddReference + ( + Class *ptr + ) + + { + if ( !ptr->SafePtrList ) + { + ptr->SafePtrList = this; + LL_Reset( this, nextSafePtr, prevSafePtr ); + } + else + { + LL_Add( ptr->SafePtrList, this, nextSafePtr, prevSafePtr ); + } + } + +inline EXPORT_FROM_DLL void SafePtrBase::RemoveReference + ( + Class *ptr + ) + + { + if ( ptr->SafePtrList == this ) + { + if ( ptr->SafePtrList->nextSafePtr == this ) + { + ptr->SafePtrList = NULL; + } + else + { + ptr->SafePtrList = nextSafePtr; + LL_Remove( this, nextSafePtr, prevSafePtr ); + } + } + else + { + LL_Remove( this, nextSafePtr, prevSafePtr ); + } + } + +template +class EXPORT_FROM_DLL SafePtr : public SafePtrBase + { + public: + SafePtr( T* objptr = 0 ); + SafePtr( const SafePtr& obj ); + + SafePtr& operator=( const SafePtr& obj ); + SafePtr& operator=( T * const obj ); + + friend EXPORT_FROM_DLL int operator==( SafePtr a, T *b ); + friend EXPORT_FROM_DLL int operator!=( SafePtr a, T *b ); + friend EXPORT_FROM_DLL int operator==( T *a, SafePtr b ); + friend EXPORT_FROM_DLL int operator!=( T *a, SafePtr b ); + + operator T*() const; + T* operator->() const; + T& operator*() const; + }; + +template +inline EXPORT_FROM_DLL SafePtr::SafePtr( T* objptr ) + { + InitSafePtr( objptr ); + } + +template +inline EXPORT_FROM_DLL SafePtr::SafePtr( const SafePtr& obj ) + { + InitSafePtr( obj.ptr ); + } + +template +inline EXPORT_FROM_DLL SafePtr& SafePtr::operator=( const SafePtr& obj ) + { + InitSafePtr( obj.ptr ); + return *this; + } + +template +inline EXPORT_FROM_DLL SafePtr& SafePtr::operator=( T * const obj ) + { + InitSafePtr( obj ); + return *this; + } + +template +inline EXPORT_FROM_DLL int operator== + ( + SafePtr a, + T* b + ) + + { + return a.ptr == b; + } + +template +inline EXPORT_FROM_DLL int operator!= + ( + SafePtr a, + T* b + ) + + { + return a.ptr != b; + } + +template +inline EXPORT_FROM_DLL int operator== + ( + T* a, + SafePtr b + ) + + { + return a == b.ptr; + } + +template +inline EXPORT_FROM_DLL int operator!= + ( + T* a, + SafePtr b + ) + + { + return a != b.ptr; + } + +template +inline SafePtr::operator T*() const + { + return ( T * )ptr; + } + +template +inline T* SafePtr::operator->() const + { + return ( T * )ptr; + } + +template +inline T& SafePtr::operator*() const + { + return *( T * )ptr; + } + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL SafePtr; +#endif +typedef SafePtr ClassPtr; + +#include "archive.h" + +#endif /* class.h */ diff --git a/console.cpp b/console.cpp new file mode 100644 index 0000000..ed183c1 --- /dev/null +++ b/console.cpp @@ -0,0 +1,1394 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/console.cpp $ +// $Revision:: 68 $ +// $Author:: Jimdose $ +// $Date:: 12/18/98 11:03p $ +// +// 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/console.cpp $ +// +// 68 12/18/98 11:03p Jimdose +// removed include of qcommon.h +// +// 67 11/07/98 5:28p Aldie +// Only filter missioncon for coop +// +// 66 11/07/98 3:52p Aldie +// Fixed loop bug for console parsing +// +// 65 11/07/98 2:25p Aldie +// Fixed check for --- +// +// 64 11/07/98 2:21p Aldie +// Added coop filter for some console stuff +// +// 63 10/27/98 1:42p Markd +// Fixed de-slashify stuff +// +// 62 10/19/98 11:50p Aldie +// Added slashfix to menufiles. +// +// +// 60 10/17/98 8:16p Jimdose +// Got rid of code posting the event 1 second later when console wasn't found. +// Functions just return now. +// Made ConsoleExists print out warning during any developer setting, not just +// > 1 +// +// 59 10/11/98 12:02a Aldie +// Console savegame fix +// +// 58 10/10/98 9:59p Jimdose +// working on console savegames +// +// 57 10/07/98 11:43p Jimdose +// Got savegames working +// +// 56 9/23/98 10:07p Aldie +// Don't remove consoles that aren't in the manager +// +// 55 9/14/98 6:42p Aldie +// Fixed a bug with the nouse spawnflag which was setting hideModel() +// incorrectly +// +// 54 9/10/98 8:39p Markd +// Removed level.hidestats, replaced it with a player event +// +// 53 8/19/98 2:24p Aldie +// Moved warning message to developer 2 +// +// 52 8/17/98 6:19p Markd +// Changed SetCamera to a Player method +// +// 51 8/15/98 3:00p Markd +// Fixed camera/console issues with weapons +// +// 50 8/08/98 1:59p Aldie +// Fixed console commands so they can be recieved from the client +// +// 49 8/06/98 10:53p Aldie +// Added weapon tweaks and kickback. Also modified blast radius damage and +// rocket jumping. +// +// 48 7/31/98 8:09p Jimdose +// Script commands now include flags to indicate cheats and console commands +// +// 47 7/29/98 3:38p Aldie +// Don't use a camera if there isn't one. +// +// 46 7/26/98 4:14a Aldie +// Console camera fun stuff. +// +// 45 7/26/98 1:17a Aldie +// Camera system for consoles +// +// 44 7/23/98 2:38p Aldie +// Made mission computer not be in the PVS +// +// 43 7/21/98 7:32p Aldie +// Changed mission computer defaults +// +// 42 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 41 7/09/98 9:35p Jimdose +// Removed RF_CONSOLE renderfx flag +// +// 40 7/09/98 4:45p Markd +// hid non-use consoles +// +// 39 7/09/98 4:33p Markd +// added pvs support for consoles +// +// 38 7/03/98 12:00p Aldie +// Better error checking when setting a layout. +// +// 37 7/01/98 7:01p Aldie +// Added mission computer +// +// 36 5/25/98 2:28p Aldie +// Fixed issues with not loading game dll +// +// 35 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 +// +// 34 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 33 5/17/98 8:13p Aldie +// New menu cutover changes. +// +// 32 5/02/98 8:38p Aldie +// More console stuff for demos +// +// 31 4/30/98 4:46p Aldie +// Server side console states. +// +// 30 4/21/98 2:24p Aldie +// Added ability to kick users. +// +// 29 4/18/98 6:09p Aldie +// Added generic status bars. +// +// 28 4/08/98 4:55p Aldie +// Alpha characters on consoles. +// +// 27 4/07/98 3:49p Aldie +// Added more menu control. +// +// 26 4/05/98 9:28p Aldie +// Added foreground color to consoles. +// +// 25 4/05/98 5:19p Aldie +// Rearranged console rendering +// +// 24 4/04/98 6:02p Jimdose +// Made response from EV_Trigger_ActivateTargets to EV_Trigger_Effect +// +// 23 4/03/98 1:10p Aldie +// Made all commands wait until console is actually created. +// +// 22 3/31/98 7:50p Aldie +// Fixed menufile bug. +// +// 21 3/31/98 7:35p Aldie +// Fixed menufile and focus. +// +// 20 3/31/98 11:55a Aldie +// Fixed loadmenufile. +// +// 19 3/30/98 11:46p Aldie +// Update to use triggers. +// +// 18 3/30/98 11:06p Aldie +// Added menus. +// +// 17 3/28/98 3:28p Aldie +// Added layout files. +// +// 16 3/27/98 6:23p Aldie +// Added rows, cols, and clear. +// +// 15 3/27/98 12:05p Aldie +// Changed an event name. +// +// 14 3/26/98 7:15p Aldie +// Added deactivate but it won't be used. +// +// 13 3/26/98 6:22p Aldie +// Fixed sending of layout commands. +// +// 12 3/26/98 4:09p Aldie +// Virtual width and height support. +// +// 11 3/26/98 2:51p Aldie +// Added interface between consoles and scripts. +// +// 10 3/24/98 4:29p Aldie +// New event system changes. +// +// 9 3/24/98 12:28p Aldie +// Added some commands to test consoles. +// +// 8 3/23/98 1:08p Aldie +// Added console entity. +// +// 6 12/12/97 4:22p Jimdose +// Added "message" key to console. +// +// 5 12/06/97 4:51p Markd +// Added GetArgs as commands for future processing +// +// 4 10/27/97 3:34p Jimdose +// Included stdarg.h +// +// 3 10/27/97 3:29p Jimdose +// Removed dependency on quakedef.h +// +// 2 9/26/97 5:23p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Consoles are script controlled decals that can change dynamically. Eventually, +// their behaviour will be expanded to include interaction with the player as well. +// + +#include "console.h" +#include "scriptmaster.h" +#include "camera.h" +#include "Player.h" + +ConsoleManager consoleManager; + +Event EV_Console_Activate( "conactivate" ); + +CLASS_DECLARATION( TriggerUse, Console, "console" ); + +ResponseDef Console::Responses[] = + { + { &EV_Console_Activate, ( Response )Console::Activate }, + { &EV_Trigger_Effect, ( Response )Console::Activate }, + { &EV_Use, ( Response )Console::Use }, + { NULL, NULL } + }; + +/*****************************************************************************/ +/*SINED console (0 .5 .8) ? NOUSE SCROLL MENU NOPVS + consolename (required) + virtualwidth + virtualheight + fraction + rows + cols + menufile + scroll + menu +/*****************************************************************************/ + +Console::Console() + { + netconsole_t *con; + str mfile; + + if ( LoadingSavegame ) + { + // Increment the global number of consoles and return + globals.num_consoles++; + return; + } + + showModel(); + setMoveType( MOVETYPE_PUSH ); + setSolidType( SOLID_BSP ); + + console_name = G_GetSpawnArg( "consolename", "" ); + if ( !LoadingSavegame && !console_name.length() ) + { + error("Console", "consolename is undefined\n" ); + } + + if ( console_name == MAIN_CONSOLE ) + { + error("Console", "console name \"maincon\" is reserved\n"); + } + if ( console_name == MISSION_CONSOLE ) + { + error("Console", "console name \"missioncon\" is reserved\n"); + } + + + wait = G_GetFloatArg("wait",1.0f); + virtual_width = G_GetFloatArg("virtualwidth",640.0f); + virtual_height = G_GetFloatArg("virtualheight",480.0f); + fraction = G_GetFloatArg("fraction",1.0f); + rows = G_GetIntArg("rows",32); + cols = G_GetIntArg("cols",80); + mfile = G_GetSpawnArg("menufile", ""); + scroll = G_GetIntArg("scroll",0); + menu = G_GetIntArg("menu",0); + respondto = TRIGGER_PLAYERS; + + menufile = G_FixSlashes( mfile.c_str() ); + + if (scroll) + spawnflags |= 2; + if (menu) + spawnflags |= 4; + + // A console of this name already exists, so just assign it's number to the number of the + // one that already exists + if ( !LoadingSavegame ) + { + console_number = consoleManager.ConsoleExists( console_name ); + if ( console_number ) + { + return; + } + } + + // Check for a free console on the server + if (globals.num_consoles >= globals.max_consoles) + error("Console::Create", "No free consoles\n" ); + + // Increment the global number of consoles. + globals.num_consoles++; + + console_number = globals.num_consoles; + con = &g_consoles[globals.num_consoles]; + con->inuse = true; + con->s.spawnflags = spawnflags; + con->s.consoleactive = true; + con->s.create_time = -1; + con->s.number = globals.num_consoles; + con->s.virtual_width = virtual_width; + con->s.virtual_height = virtual_height; + con->s.fraction = fraction; + con->s.rows = rows; + con->s.cols = cols; + con->s.menu_file[0] = 0; + con->s.linepos = 1; + + con->s.console_owner = entnum; + + created = true; + if ( menufile.length() ) + { + strcpy( con->s.menu_file, menufile.c_str() ); + con->s.menufile_update_time = -1; + } + else + { + con->s.menufile_update_time = 0; + } + + strcpy( con->s.console_name, console_name.c_str() ); + con->s.name_update_time = -1; + con->s.console_return_time = 0; + + if ( !LoadingSavegame ) + { + // Add it to the manager + consoleManager.AddConsole(this); + } + } + +Console::~Console() + { + consoleManager.RemoveConsole(this); + } + +void Console::Use( Event *ev ) + { + // Don't respond to users using me! + if (spawnflags & 1) + return; + + TriggerStuff(ev); + } + +void Console::Activate( Event *ev ) + { + char string[1024]; + Entity *other; + Event *ev2; + Camera *cam; + int num; + + if (!created) + { + Event *ev1; + ev1 = new Event(ev); + PostEvent(ev1,0.1); + return; + } + + assert(created); + + other = ev->GetEntity(1); + + num = G_FindTarget( 0, Target() ); + + if ( num && other->isClient() ) + { + Player * client; + + client = ( Player * )other; + cam = (Camera *) G_GetEntity( num ); + assert( cam ); + client->SetCamera( cam ); + ev2 = new Event( EV_Player_HideStats ); + client->ProcessEvent( ev2 ); + } + + Com_sprintf( string, sizeof( string ), "use %s", console_name.c_str() ); + + gi.WriteByte (svc_console_command); + gi.WriteString (string); + gi.unicast (other->edict, true); + + ev2 = new Event(EV_EnterConsole); + ev2->AddString(console_name); + other->PostEvent(ev2,0); + } + +CLASS_DECLARATION( Listener, ConsoleManager, "consolemgr" ); + +Event EV_ConsoleManager_ProcessCommand( "consolecmd", EV_CONSOLE ); +Event EV_ConsoleManager_ProcessVariable( "consolevar", EV_CONSOLE ); +Event EV_ConsoleManager_ConPositionPositive( "consolepos", EV_CONSOLE ); +Event EV_ConsoleManager_ConPositionNegative( "consoleneg", EV_CONSOLE ); +Event EV_ConsoleManager_ConPositionReturn( "consoleret", EV_CONSOLE ); +Event EV_ConsoleManager_ConMenuInfo( "consolemenu", EV_CONSOLE ); +Event EV_ConsoleManager_ConPrint( "conprint" ); +Event EV_ConsoleManager_ConNewline( "connewline" ); +Event EV_ConsoleManager_ConLayout( "conlayout" ); +Event EV_ConsoleManager_ConAppLayout( "conapplayout" ); +Event EV_ConsoleManager_ConClearLayout( "conclearlayout" ); +Event EV_ConsoleManager_ConVirtualWidth( "convirtualwidth" ); +Event EV_ConsoleManager_ConVirtualHeight( "convirtualheight" ); +Event EV_ConsoleManager_ConFraction( "confraction" ); +Event EV_ConsoleManager_ConDeactivate( "condeactivate" ); +Event EV_ConsoleManager_ConActivate( "conactivate" ); +Event EV_ConsoleManager_ConRows( "rows" ); +Event EV_ConsoleManager_ConColumns( "cols" ); +Event EV_ConsoleManager_ConClear( "conclear" ); +Event EV_ConsoleManager_ConLayoutFile( "conlayoutfile" ); +Event EV_ConsoleManager_ConLoadMenuFile( "conmenufile" ); +Event EV_ConsoleManager_ConFocus( "focus" ); +Event EV_ConsoleManager_ConForeground( "foreground" ); +Event EV_ConsoleManager_MenuActive( "menuactive" ); +Event EV_ConsoleManager_MenuInactive( "menuinactive" ); +Event EV_ConsoleManager_ConStatusBar( "sbar" ); +Event EV_ConsoleManager_ConStatusBarValue( "sbarvalue" ); +Event EV_ConsoleManager_ConKickUsers( "kick" ); + +Event EV_KickFromConsole( "kickcon" ); +Event EV_EnterConsole( "entercon" ); +Event EV_ExitConsole( "exitcon", EV_CONSOLE ); + +ResponseDef ConsoleManager::Responses[] = + { + { &EV_ConsoleManager_ProcessCommand, ( Response )ConsoleManager::ProcessCmd }, + { &EV_ConsoleManager_ProcessVariable, ( Response )ConsoleManager::ProcessVar }, + { &EV_ConsoleManager_ConPositionPositive, ( Response )ConsoleManager::ConsolePositionPositive }, + { &EV_ConsoleManager_ConPositionNegative, ( Response )ConsoleManager::ConsolePositionNegative }, + { &EV_ConsoleManager_ConPositionReturn, ( Response )ConsoleManager::ConsolePositionReturn }, + { &EV_ConsoleManager_ConMenuInfo, ( Response )ConsoleManager::ConsoleMenuInfo }, + { &EV_ConsoleManager_ConPrint, ( Response )ConsoleManager::ConsolePrint }, + { &EV_ConsoleManager_ConPrint, ( Response )ConsoleManager::ConsolePrint }, + { &EV_ConsoleManager_ConNewline, ( Response )ConsoleManager::ConsoleNewline }, + { &EV_ConsoleManager_ConLayout, ( Response )ConsoleManager::ConsoleLayout }, + { &EV_ConsoleManager_ConLayoutFile, ( Response )ConsoleManager::ConsoleLayoutFile }, + { &EV_ConsoleManager_ConAppLayout, ( Response )ConsoleManager::ConsoleAppLayout }, + { &EV_ConsoleManager_ConClearLayout, ( Response )ConsoleManager::ConsoleClearLayout }, + { &EV_ConsoleManager_ConVirtualWidth, ( Response )ConsoleManager::ConsoleVirtualWidth }, + { &EV_ConsoleManager_ConVirtualHeight, ( Response )ConsoleManager::ConsoleVirtualHeight }, + { &EV_ConsoleManager_ConFraction, ( Response )ConsoleManager::ConsoleFraction }, + { &EV_ConsoleManager_ConDeactivate, ( Response )ConsoleManager::ConsoleDeactivate }, + { &EV_ConsoleManager_ConActivate, ( Response )ConsoleManager::ConsoleActivate }, + { &EV_ConsoleManager_ConRows, ( Response )ConsoleManager::ConsoleRows }, + { &EV_ConsoleManager_ConColumns, ( Response )ConsoleManager::ConsoleColumns }, + { &EV_ConsoleManager_ConClear, ( Response )ConsoleManager::ConsoleClear }, + { &EV_ConsoleManager_ConLoadMenuFile, ( Response )ConsoleManager::ConsoleLoadMenuFile }, + { &EV_ConsoleManager_ConFocus, ( Response )ConsoleManager::ConsoleFocus }, + { &EV_ConsoleManager_ConForeground, ( Response )ConsoleManager::ConsoleForeground }, + { &EV_ConsoleManager_MenuActive, ( Response )ConsoleManager::ConsoleMenuActive }, + { &EV_ConsoleManager_MenuInactive, ( Response )ConsoleManager::ConsoleMenuInactive }, + { &EV_ConsoleManager_ConStatusBar, ( Response )ConsoleManager::ConsoleStatusBar }, + { &EV_ConsoleManager_ConStatusBarValue, ( Response )ConsoleManager::ConsoleStatusBarValue }, + { &EV_ConsoleManager_ConKickUsers, ( Response )ConsoleManager::ConsoleKickUsers }, + { NULL, NULL } + }; + +//============= +//ConsoleExists - returns the number of the console, 0 if not found. +//============= +int ConsoleManager::ConsoleExists + ( + str con_name + ) + + { + int num,i; + Console *p; + + // Check for mission computer + if ( con_name == "missioncon" ) + return mission_console_number; + + num = consoleList.NumObjects(); + for( i = 1; i <= num; i++ ) + { + p = ( Console * )consoleList.ObjectAt( i ); + if ( con_name == p->ConsoleName() ) + { + return p->ConsoleNumber(); + } + } + + gi.dprintf( "Console %s does not exist\n", con_name.c_str() ); + + return 0; + } + +//============= +//ConsoleExists +//============= +qboolean ConsoleManager::ConsoleExists + ( + int con_number + ) + + { + int num,i; + Console *p; + + // Check for mission computer + if ( con_number == mission_console_number ) + return true; + + num = consoleList.NumObjects(); + for( i = 1; i <= num; i++ ) + { + p = ( Console * )consoleList.ObjectAt( i ); + if (p->ConsoleNumber() == con_number) + return true; + } + return false; + } + +//========== +//ProcessCmd - Send this command to the script director. +//========== +void ConsoleManager::ProcessCmd + ( + Event *ev + ) + { + Director.ConsoleInput(ev->GetToken(1),ev->GetToken(2)); + } + +//========== +//ProcessVar - Send this variable to the script director. +//========== +void ConsoleManager::ProcessVar + ( + Event *ev + ) + { + Director.ConsoleVariable(ev->GetToken(1),ev->GetToken(2)); + } + +//===================== +//ConsolePositionReturn +//===================== +void ConsoleManager::ConsolePositionReturn + ( + Event *ev + ) + { + netconsole_t *svcon; + int num = ev->GetInteger(1); + + if (!ConsoleExists(num)) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.linepos = 1; + svcon->s.cmdline[svcon->s.linepos] = 0; + svcon->s.console_return_time = level.time; + } + +//======================= +//ConsolePositionPositive +//======================= +void ConsoleManager::ConsolePositionPositive + ( + Event *ev + ) + { + netconsole_t *svcon; + int num = ev->GetInteger(1); + + if (!ConsoleExists(num)) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + + if (svcon->s.linepos < MAXCMDLINE-1) + { + svcon->s.cmdline[svcon->s.linepos] = ev->GetInteger(2); + svcon->s.linepos++; + svcon->s.cmdline[svcon->s.linepos] = 0; + } + } + + +//======================= +//ConsolePositionNegative +//======================= +void ConsoleManager::ConsolePositionNegative + ( + Event *ev + ) + { + netconsole_t *svcon; + int num = ev->GetInteger(1); + + if (!ConsoleExists(num)) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + + if (svcon->s.linepos > 1) + { + svcon->s.linepos--; + svcon->s.cmdline[svcon->s.linepos] = 0; + } + } + + +//=============== +//ConsoleMenuInfo +//=============== +void ConsoleManager::ConsoleMenuInfo + ( + Event *ev + ) + { + netconsole_t *svcon; + int num = ev->GetInteger(1); + + if (!ConsoleExists(num)) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.menu_level = ev->GetInteger(2); + svcon->s.sel_menu_item = ev->GetInteger(3); + } + +void ConsoleManager::CreateMissionComputer + ( + void + ) + + { + netconsole_t *con; + + // Check for a free console on the server + if (globals.num_consoles >= globals.max_consoles) + gi.error("Console::CreateMissionComputer", "No free consoles\n" ); + + // Increment the global number of consoles. + globals.num_consoles++; + mission_console_number = globals.num_consoles; + con = &g_consoles[globals.num_consoles]; + con->inuse = true; + con->s.spawnflags = 2|8; + con->s.consoleactive = true; + con->s.create_time = -1; + con->s.number = globals.num_consoles; + con->s.virtual_width = 320; + con->s.virtual_height = 240; + con->s.fraction = 1.0f; + con->s.rows = 30; + con->s.cols = 40; + con->s.menu_file[0] = 0; + con->s.linepos = 1; + con->s.menufile_update_time = 0; + strcpy( con->s.console_name, MISSION_CONSOLE ); + con->s.name_update_time = -1; + con->s.console_return_time = 0; + } + +void ConsoleManager::Reset + ( + void + ) + + { + globals.num_consoles = 0; + mission_console_number = 0; + consoleList.ClearObjectList(); + } + +//========== +//AddConsole - Add a console to the manager +//========== +int ConsoleManager::AddConsole + ( + Console *console + ) + + { + int num; + num = consoleList.AddObject( console ); + return num; + } + +//============= +//RemoveConsole - Remove a console from the manager +//============= +void ConsoleManager::RemoveConsole + ( + Console *console + ) + { + // Make sure that this exists in the manager + if ( consoleList.IndexOfObject( console ) ) + consoleList.RemoveObject( console ); + } + +//============ +//ConsolePrint - Print a string to the buffer of the console +//============ +void ConsoleManager::ConsolePrint + ( + Event *ev + ) + { + char *bufptr; + const char *str; + netconbuffer_t *svbuff; + netconsole_t *svcon; + int *start; + int *end; + int num; + + num = ConsoleExists( ev->GetString( 1 ) ); + if ( !num ) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svbuff = &g_conbuffers[num]; + bufptr = &svbuff->s.buffer[0]; + start = &svbuff->s.start; + end = &svbuff->s.end; + str = ev->GetString(2); + svbuff->s.end_index += strlen(str); + + while (*str) + { + if ( (*end+1)%MAX_BUFFER_LENGTH == (*start) ) + { + svbuff->s.start_index += 1; + *start = (*start + 1)%MAX_BUFFER_LENGTH; + } + + bufptr[*end]=*str; + *end = (*end+1) % MAX_BUFFER_LENGTH; + str++; + } + } + +//============== +//ConsoleNewline - Prints a newline to the buffer of the console +//============== +void ConsoleManager::ConsoleNewline + ( + Event *ev + ) + { + ev->AddString("\n"); + ConsolePrint(ev); + } + +//================= +//ConsoleLayoutFile - Orders the console to load a client side layout file +//================= +void ConsoleManager::ConsoleLayoutFile + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + const char *layout_filename; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + layout_filename = ev->GetString(2); + strcpy(svcon->s.layout_file, layout_filename); + svcon->s.layoutfile_update_time = level.time; + } + +//============= +//ConsoleLayout - Set the layout string +//============= +void ConsoleManager::ConsoleLayout + ( + Event *ev + ) + { + str console_name; + netconsole_t *svcon; + int num; + char *layout; + char *token; + char newlayout[ MAX_LAYOUT_LENGTH ]; + static const char *seps = " "; + + console_name = ev->GetString( 1 ); + num = ConsoleExists( console_name ); + + if ( !num ) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[ num ]; + layout = strdup( ev->GetString( 2 ) ); + + if ( strlen( layout ) > MAX_LAYOUT_LENGTH ) + error( "ConsoleManager::ConsoleLayout", "Max layout length exceeded for %s\n", ev->GetString( 1 ) ); + + strcpy( newlayout, layout ); + + if ( coop->value && ( console_name == "missioncon" ) ) + { + newlayout[ 0 ] = 0; + + token = strtok( layout, seps ); + while ( token ) + { + // Skip over "fc" console commands in coop + if ( !strcmp( token, "fc" ) ) + { + strtok( NULL, seps ); + strtok( NULL, seps ); + strtok( NULL, seps ); + strtok( NULL, seps ); + } + else if ( strstr( token, "---" ) ) + { + strcat( newlayout, " " ); + strcat( newlayout, "\"\"" ); + // Skip over extraneous lines of characters + } + else + { + strcat( newlayout, " " ); + strcat( newlayout, token ); + } + token = strtok( NULL, seps ); + } + } + + free( layout ); + strcpy( svcon->s.layout, newlayout ); + svcon->s.layout_update_time = level.time; + } + +//================ +//ConsoleAppLayout - Append to the layout string +//================ +void ConsoleManager::ConsoleAppLayout + ( + Event *ev + ) + { + netconsole_t *svcon; + int layout_length, num; + char *layout; + const char *token; + char newlayout[ MAX_LAYOUT_LENGTH ]; + static const char *seps = " "; + str consolename; + + consolename = ev->GetString( 1 ); + num = ConsoleExists( consolename ); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + + layout = strdup( ev->GetString( 2 ) ); + layout_length = strlen(layout) + strlen(svcon->s.layout) + 1; + + if ( layout_length > MAX_LAYOUT_LENGTH ) + error("ConsoleManager::ConsoleAppLayout", "Max layout length exceeded for %s\n", ev->GetString(1) ); + + strcpy( newlayout, layout ); + + if ( coop->value && ( consolename == "missioncon" ) ) + { + newlayout[ 0 ] = 0; + + token = strtok( layout, seps ); + while ( token ) + { + // Skip over "fc" console commands in coop + if ( !strcmp( token, "fc" ) ) + { + strtok ( NULL, seps ); + strtok ( NULL, seps ); + strtok ( NULL, seps ); + strtok ( NULL, seps ); + } + else if ( strstr( token, "---" ) ) + { + strcat( newlayout, "\"\"" ); + // Skip over extraneous lines of characters + } + else + { + strcat( newlayout, " " ); + strcat( newlayout, token ); + } + token = strtok( NULL, seps ); + } + } + + free( layout ); + strcat(svcon->s.layout, " "); + strcat(svcon->s.layout, newlayout); + svcon->s.layout_update_time = level.time; + } + +//================== +//ConsoleClearLayout - Clear the layout string +//================== +void ConsoleManager::ConsoleClearLayout + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.layout[0] = 0; + svcon->s.layout_update_time = level.time; + } + +//=========== +//ConsoleRows - Set the number of rows in the console +//=========== +void ConsoleManager::ConsoleRows + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.rows = ev->GetInteger( 2 ); + } + +//============== +//ConsoleColumns - Set the number of columns in the console +//============== +void ConsoleManager::ConsoleColumns + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.cols = ev->GetInteger( 2 ); + } + +//=================== +//ConsoleVirtualWidth - Set the virtual width of the console +//=================== +void ConsoleManager::ConsoleVirtualWidth + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.virtual_width = ev->GetFloat( 2 ); + } + +//==================== +//ConsoleVirtualHeight - Set the virtual height of the console +//==================== +void ConsoleManager::ConsoleVirtualHeight + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.virtual_height = ev->GetFloat( 2 ); + } + +//=============== +//ConsoleFraction - Set the fraction of the console that the scrolling +//part of the console covers. +//=============== +void ConsoleManager::ConsoleFraction + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.fraction = ev->GetFloat( 2 ); + } + +//================= +//ConsoleDeactivate - Deactivate the console. +//================= +void ConsoleManager::ConsoleDeactivate + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.consoleactive = false; + } + +//================= +//ConsoleActivate - Activate the console +//================= +void ConsoleManager::ConsoleActivate + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.consoleactive = true; + } + +//============ +//ConsoleClear - Clears the buffer of the scrolling part of the console +//============ +void ConsoleManager::ConsoleClear + ( + Event *ev + ) + { + const char *bufptr; + netconbuffer_t *svbuff; + netconsole_t *svcon; + + int num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svbuff = &g_conbuffers[num]; + bufptr = &svbuff->s.buffer[0]; + svbuff->s.start = 0; + svbuff->s.end = 0; + svbuff->s.end_index = 0; + svbuff->s.start_index = 0; + svcon->s.cleared_console_time = level.time; + } + +//============== +//ConsoleManager - Load a client side menu file +//============== +void ConsoleManager::ConsoleLoadMenuFile + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + const char *path; + str mfile; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + path = ev->GetString(2); + mfile = G_FixSlashes( path ); + strcpy(svcon->s.menu_file, mfile.c_str() ); + svcon->s.menufile_update_time = level.time; + } + +//============ +//ConsoleFocus - Change the focus of console to the scrolling part or +//the menu part. +//============ +void ConsoleManager::ConsoleFocus + ( + Event *ev + ) + { + const char *focus; + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + focus = ev->GetString( 2 ); + + if (!stricmp(focus,"menu")) + { + svcon->s.focus = MENU3D; + } + else if (!strcmp(focus,"console")) + { + svcon->s.focus = CONSOLE3D; + } + else + { + error("ConsoleManager::ConsoleFocus", "invalid focus type\n" ); + } + } + +//================= +//ConsoleForeground - Set the foreground color of the console +//================= +void ConsoleManager::ConsoleForeground + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + num = ConsoleExists(ev->GetString( 1 )); + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + svcon = &g_consoles[num]; + svcon->s.red = ev->GetFloat( 2 ); + svcon->s.green = ev->GetFloat( 3 ); + svcon->s.blue = ev->GetFloat( 4 ); + svcon->s.alpha = ev->GetFloat( 5 ); + } + +//=================== +//ConsoleMenuActivate - Activates the menu (i.e. draw it) +//=================== +void ConsoleManager::ConsoleMenuActive + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.menuactive = true; + } + +//=================== +//ConsoleMenuInactive - Deactivates the menu (i.e. don't draw it) +//=================== +void ConsoleManager::ConsoleMenuInactive + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + svcon = &g_consoles[num]; + svcon->s.menuactive = false; + } + +//================ +//ConsoleStatusBar - Create a status bar on the console. +//================ +void ConsoleManager::ConsoleStatusBar + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + int sbar_num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + sbar_num = ev->GetInteger(2); + svcon = &g_consoles[num]; + + svcon->s.sbar[sbar_num].width = ev->GetFloat(3); + svcon->s.sbar[sbar_num].height = ev->GetFloat(4); + svcon->s.sbar[sbar_num].min = ev->GetFloat(5); + svcon->s.sbar[sbar_num].max = ev->GetFloat(6); + svcon->s.sbar[sbar_num].value = ev->GetFloat(7); + svcon->s.sbar[sbar_num].red = ev->GetFloat(8); + svcon->s.sbar[sbar_num].green = ev->GetFloat(9); + svcon->s.sbar[sbar_num].blue = ev->GetFloat(10); + svcon->s.sbar[sbar_num].alpha = ev->GetFloat(11); + svcon->s.sbar[sbar_num].update_time = level.time; + } + +//===================== +//ConsoleStatusBarValue +//===================== +void ConsoleManager::ConsoleStatusBarValue + ( + Event *ev + ) + { + netconsole_t *svcon; + int num; + int sbar_num; + + num = ConsoleExists(ev->GetString( 1 )); + + if (!num) + { + // ConsoleExists will give a warning about this console not existing + return; + } + + sbar_num = ev->GetInteger(2); + svcon = &g_consoles[num]; + svcon->s.sbar[sbar_num].value = ev->GetFloat(3); + } + +//================ +//ConsoleKickUsers +//================ +void ConsoleManager::ConsoleKickUsers + ( + Event *ev + ) + { + char msg[ MAX_MSGLEN ]; + + if (!ConsoleExists(ev->GetString( 1 ))) + { + // ConsoleExists will give a warning about this console not existing + return; + } + sprintf(msg,"sku %s", ev->GetString(1)); + gi.WriteByte (svc_console_command); + gi.WriteString (msg); + gi.multicast (NULL, MULTICAST_ALL); + } diff --git a/console.h b/console.h new file mode 100644 index 0000000..8dcb165 --- /dev/null +++ b/console.h @@ -0,0 +1,272 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/console.h $ +// $Revision:: 30 $ +// $Author:: Jimdose $ +// $Date:: 10/25/98 11:53p $ +// +// 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/console.h $ +// +// 30 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 29 10/19/98 6:13p Jimdose +// Unarchive now accounts for the mission computer +// +// 28 10/10/98 9:59p Jimdose +// working on console savegames +// +// 27 10/07/98 11:43p Jimdose +// Rewrote archiving functions +// +// 26 9/29/98 5:58p Markd +// put in archive and unarchive +// +// 25 5/25/98 2:28p Aldie +// Fixed issues with not loading game dll +// +// 24 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 +// +// 23 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 22 5/02/98 8:37p Aldie +// More console stuff for demos +// +// 21 4/30/98 4:46p Aldie +// Server side console states. +// +// 20 4/27/98 1:51p Aldie +// Added activate to console. +// +// 19 4/21/98 2:25p Aldie +// +// 18 4/18/98 6:12p Aldie +// +// 17 4/07/98 3:49p Aldie +// +// 16 4/05/98 9:27p Aldie +// Added foreground color to consoles. +// +// +// DESCRIPTION: +// Consoles are script controlled decals that can change dynamically. Eventually, +// their behaviour will be expanded to include interaction with the player as well. +// + +#ifndef __CONSOLE_H__ +#define __CONSOLE_H__ + + +#include "g_local.h" +#include "trigger.h" +#include "container.h" + + +// Console stuff +extern Event EV_EnterConsole; +extern Event EV_ExitConsole; +extern Event EV_KickFromConsole; + +class EXPORT_FROM_DLL Console : public TriggerUse + { + private: + str console_name; + str menufile; + int rows; + int cols; + int console_number; + qboolean scroll; + qboolean menu; + float virtual_width; + float virtual_height; + float fraction; + qboolean created; + + public: + CLASS_PROTOTYPE( Console ); + Console( ); + ~Console( ); + void Activate( Event *ev ); + void ProcessCmd( Event *ev ); + void Use(Event *ev); + const char *ConsoleName() { return console_name.c_str(); }; + int ConsoleNumber() {return console_number;}; + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Console::Archive + ( + Archiver &arc + ) + { + TriggerUse::Archive( arc ); + + arc.WriteString( console_name ); + arc.WriteString( menufile ); + arc.WriteInteger( rows ); + arc.WriteInteger( cols ); + arc.WriteInteger( console_number ); + arc.WriteBoolean( scroll ); + arc.WriteBoolean( menu ); + arc.WriteFloat( virtual_width ); + arc.WriteFloat( virtual_height ); + arc.WriteFloat( fraction ); + arc.WriteBoolean( created ); + } + +inline EXPORT_FROM_DLL void Console::Unarchive + ( + Archiver &arc + ) + { + TriggerUse::Unarchive( arc ); + + arc.ReadString( &console_name ); + arc.ReadString( &menufile ); + arc.ReadInteger( &rows ); + arc.ReadInteger( &cols ); + arc.ReadInteger( &console_number ); + arc.ReadBoolean( &scroll ); + arc.ReadBoolean( &menu ); + arc.ReadFloat( &virtual_width ); + arc.ReadFloat( &virtual_height ); + arc.ReadFloat( &fraction ); + arc.ReadBoolean( &created ); + } + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL ConsoleManager : public Listener + { + private: + Container consoleList; + int mission_console_number; + + public: + CLASS_PROTOTYPE( ConsoleManager ); + int AddConsole( Console *console ); + void RemoveConsole( Console *console ); + void CreateMissionComputer( void ); + void Reset(); + int ConsoleExists( str con_name ); + qboolean ConsoleExists(int con_number); + void ProcessCmd(Event *ev); + void ProcessVar(Event *ev); + void ConsolePositionPositive( Event *ev ); + void ConsolePositionNegative( Event *ev ); + void ConsolePositionReturn( Event *ev ); + void ConsoleMenuInfo( Event *ev ); + void ConsolePrint( Event *ev ); + void ConsoleNewline( Event *ev ); + void ConsoleLayout( Event *ev ); + void ConsoleLayoutFile( Event *ev ); + void ConsoleAppLayout( Event *ev ); + void ConsoleClearLayout( Event *ev ); + void ConsoleVirtualWidth ( Event *ev ); + void ConsoleVirtualHeight ( Event *ev ); + void ConsoleFraction ( Event *ev ); + void ConsoleDeactivate ( Event *ev ); + void ConsoleActivate( Event *ev ); + void ConsoleRows ( Event *ev ); + void ConsoleColumns ( Event *ev ); + void ConsoleClear ( Event *ev ); + void ConsoleLoadMenuFile ( Event *ev ); + void ConsoleFocus ( Event *ev ); + void ConsoleForeground ( Event *ev ); + void ConsoleMenuActive ( Event *ev ); + void ConsoleMenuInactive ( Event *ev ); + void ConsoleStatusBar ( Event *ev ); + void ConsoleStatusBarValue ( Event *ev ); + void ConsoleKickUsers ( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void ConsoleManager::Archive + ( + Archiver &arc + ) + + { + int i; + int num; + netconsole_t *s; + + Listener::Archive( arc ); + + arc.WriteInteger( mission_console_number ); + + num = consoleList.NumObjects(); + arc.WriteInteger( num ); + for( i = 1; i <= num; i++ ) + { + arc.WriteObjectPointer( consoleList.ObjectAt( i ) ); + } + + // read the console states + s = g_consoles; + for( i = 0; i < game.maxconsoles; i++, s++ ) + { + arc.WriteBoolean( s->inuse ); + if ( s->inuse ) + { + arc.WriteRaw( &s->s, sizeof( s->s ) ); + } + } + } + +inline EXPORT_FROM_DLL void ConsoleManager::Unarchive + ( + Archiver &arc + ) + + { + int i; + int num; + netconsole_t *s; + + Reset(); + + Listener::Unarchive( arc ); + + arc.ReadInteger( &mission_console_number ); + + arc.ReadInteger( &num ); + consoleList.Resize( num ); + for( i = 1; i <= num; i++ ) + { + arc.ReadObjectPointer( ( Class ** )consoleList.AddressOfObjectAt( i ) ); + } + + // write the console states + s = g_consoles; + for( i = 0; i < game.maxconsoles; i++, s++ ) + { + arc.ReadBoolean( &s->inuse ); + if ( s->inuse ) + { + arc.ReadRaw( &s->s, sizeof( s->s ) ); + } + } + + // account for mission computer since we don't create an object for it. + globals.num_consoles++; + } + +extern ConsoleManager consoleManager; + +#endif /* console.h */ diff --git a/container.h b/container.h new file mode 100644 index 0000000..d112ddd --- /dev/null +++ b/container.h @@ -0,0 +1,464 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/container.h $ +// $Revision:: 17 $ +// $Author:: Jimdose $ +// $Date:: 10/25/98 11:53p $ +// +// 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/container.h $ +// +// 17 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 16 10/07/98 11:45p Jimdose +// Added \n to dprintf's +// Made resize assert only when size was negative +// +// 15 9/21/98 6:01p Markd +// tried putting archiving back in, but took it out again, when I realized it +// wouldn't work because the class hierarchy hasn't been created yet. +// +// 14 9/21/98 4:21p Markd +// Put in archive functions and rewrote all archive routines +// +// 13 8/20/98 4:42p Jimdose +// Added Sort function. +// +// 12 7/09/98 1:40a Jimdose +// ClearObjectList now only reallocates the list if there are any objects in +// the list +// +// 11 5/25/98 2:29p Aldie +// Fixed issues with not loading game dll +// +// 10 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 9 3/04/98 1:43p Jimdose +// Changed IndexOfObject so that it did a comparison instead of a memcmp +// +// 8 3/02/98 5:27p Jimdose +// Changed Container to a template to make it more flexible +// +// 7 2/03/98 10:51a Jimdose +// Updated to work with Quake 2 engine +// +// 5 11/20/97 9:03p Jimdose +// Changed Container class to use entity # (int) instead of void * +// +// 4 11/07/97 6:37p Jimdose +// Added FreeObjectList to empty the list and free up the memory. +// Changed ClearObjectList to only empty the list, but keep the memory +// allocated. +// Changed all functions from being virtual to being exported by the DLL. +// +// 3 10/27/97 2:49p Jimdose +// Removed dependency on quakedef.h +// +// 2 9/26/97 6:13p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Base class for a dynamic array. Allows adding, removing, index of, +// and finding of entries with specified value. Originally created for +// cataloging entities, but pointers to objects that may be removed at +// any time are bad to keep around, so only entity numbers should be +// used in the future. +// + +#ifndef __CONTAINER_H__ +#define __CONTAINER_H__ + +#include "g_local.h" +#include + +template< class Type > +class EXPORT_FROM_DLL Container + { + private: + Type *objlist; + int numobjects; + int maxobjects; + + public: + Container(); + ~Container(); + void FreeObjectList( void ); + void ClearObjectList( void ); + int NumObjects( void ); + void Resize( int maxelements ); + void SetObjectAt( int index, Type& obj ); + int AddObject( Type& obj ); + int AddUniqueObject( Type& obj ); + void AddObjectAt( int index, Type& obj ); + int IndexOfObject( Type& obj ); + qboolean ObjectInList( Type& obj ); + Type& ObjectAt( int index ); + Type *AddressOfObjectAt( int index ); + void RemoveObjectAt( int index ); + void RemoveObject( Type& obj ); + void Sort( int ( __cdecl *compare )( const void *elem1, const void *elem2 ) ); +// virtual void Archive( Archiver &arc ); +// virtual void Unarchive( Archiver &arc ); + }; +/* +template< class Type > +EXPORT_FROM_DLL void Container::Archive + ( + Archiver &arc + ) + { + int i; + + arc.WriteInteger( maxobjects ); + arc.WriteInteger( numobjects ); + + for ( i = 0; i < numobjects; i++ ) + { + arc.WriteRaw( objlist[ i ], sizeof( Type ) ); + } + } + +template< class Type > +EXPORT_FROM_DLL void Container::Unarchive + ( + Archiver &arc + ) + + { + int i; + + FreeObjectList(); + + maxobjects = arc.ReadInteger(); + numobjects = arc.ReadInteger(); + + objlist = new Type[ maxobjects ]; + for ( i = 0; i < numobjects; i++ ) + { + arc.ReadRaw( &objlist[ i ], sizeof( Type ) ); + } + } +*/ + +template< class Type > +Container::Container() + { + objlist = NULL; + FreeObjectList(); + } + +template< class Type > +Container::~Container() + { + FreeObjectList(); + } + +template< class Type > +EXPORT_FROM_DLL void Container::FreeObjectList + ( + void + ) + + { + if ( objlist ) + { + delete[] objlist; + } + objlist = NULL; + numobjects = 0; + maxobjects = 0; + } + +template< class Type > +EXPORT_FROM_DLL void Container::ClearObjectList + ( + void + ) + + { + // only delete the list if we have objects in it + if ( objlist && numobjects ) + { + delete[] objlist; + objlist = new Type[ maxobjects ]; + numobjects = 0; + } + } + +template< class Type > +EXPORT_FROM_DLL int Container::NumObjects + ( + void + ) + + { + return numobjects; + } + +template< class Type > +EXPORT_FROM_DLL void Container::Resize + ( + int maxelements + ) + + { + Type *temp; + int i; + + assert( maxelements >= 0 ); + + if ( maxelements <= 0 ) + { + FreeObjectList(); + return; + } + + if ( !objlist ) + { + maxobjects = maxelements; + objlist = new Type[ maxobjects ]; + } + else + { + temp = objlist; + maxobjects = maxelements; + if ( maxobjects < numobjects ) + { + maxobjects = numobjects; + } + + objlist = new Type[ maxobjects ]; + for( i = 0; i < numobjects; i++ ) + { + objlist[ i ] = temp[ i ]; + } + delete[] temp; + } + } + +template< class Type > +EXPORT_FROM_DLL void Container::SetObjectAt + ( + int index, + Type& obj + ) + + { + if ( ( index <= 0 ) || ( index > numobjects ) ) + { + gi.error( "Container::SetObjectAt : index out of range" ); + } + + objlist[ index - 1 ] = obj; + } + +template< class Type > +EXPORT_FROM_DLL int Container::AddObject + ( + Type& obj + ) + + { + if ( !objlist ) + { + Resize( 10 ); + } + + if ( numobjects == maxobjects ) + { + Resize( maxobjects * 2 ); + } + + objlist[ numobjects ] = obj; + numobjects++; + + return numobjects; + } + +template< class Type > +EXPORT_FROM_DLL int Container::AddUniqueObject + ( + Type& obj + ) + + { + int index; + + index = IndexOfObject( obj ); + if ( !index ) + index = AddObject( obj ); + return index; + } + +template< class Type > +EXPORT_FROM_DLL void Container::AddObjectAt + ( + int index, + Type& obj + ) + + { + // + // this should only be used when reconstructing a list that has to be identical to the original + // + if ( index > maxobjects ) + { + Resize( index ); + } + if ( index > numobjects ) + { + numobjects = index; + } + SetObjectAt( index, obj ); + } + +template< class Type > +EXPORT_FROM_DLL int Container::IndexOfObject + ( + Type& obj + ) + + { + int i; + + for( i = 0; i < numobjects; i++ ) + { + if ( objlist[ i ] == obj ) + { + return i + 1; + } + } + + return 0; + } + +template< class Type > +EXPORT_FROM_DLL qboolean Container::ObjectInList + ( + Type& obj + ) + + { + if ( !IndexOfObject( obj ) ) + { + return false; + } + + return true; + } + +template< class Type > +EXPORT_FROM_DLL Type& Container::ObjectAt + ( + int index + ) + + { + if ( ( index <= 0 ) || ( index > numobjects ) ) + { + gi.error( "Container::ObjectAt : index out of range" ); + } + + return objlist[ index - 1 ]; + } + +template< class Type > +EXPORT_FROM_DLL Type * Container::AddressOfObjectAt + ( + int index + ) + + { + // + // this should only be used when reconstructing a list that has to be identical to the original + // + if ( index > maxobjects ) + { + gi.error( "Container::AddressOfObjectAt : index is greater than maxobjects" ); + } + if ( index > numobjects ) + { + numobjects = index; + } + return &objlist[ index - 1 ]; + } + +template< class Type > +EXPORT_FROM_DLL void Container::RemoveObjectAt + ( + int index + ) + + { + int i; + + if ( !objlist ) + { + gi.dprintf( "Container::RemoveObjectAt : Empty list\n" ); + return; + } + + if ( ( index <= 0 ) || ( index > numobjects ) ) + { + gi.error( "Container::RemoveObjectAt : index out of range" ); + return; + } + + i = index - 1; + numobjects--; + for( i = index - 1; i < numobjects; i++ ) + { + objlist[ i ] = objlist[ i + 1 ]; + } + } + +template< class Type > +EXPORT_FROM_DLL void Container::RemoveObject + ( + Type& obj + ) + + { + int index; + + index = IndexOfObject( obj ); + if ( !index ) + { + gi.dprintf( "Container::RemoveObject : Object not in list\n" ); + return; + } + + RemoveObjectAt( index ); + } + +template< class Type > +EXPORT_FROM_DLL void Container::Sort + ( + int ( __cdecl *compare )( const void *elem1, const void *elem2 ) + ) + + { + if ( !objlist ) + { + gi.dprintf( "Container::RemoveObjectAt : Empty list\n" ); + return; + } + + qsort( ( void * )objlist, ( size_t )numobjects, sizeof( Type ), compare ); + } + +// +// Exported templated classes must be explicitly instantiated +// +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +#endif /* container.h */ diff --git a/ctf.cpp b/ctf.cpp new file mode 100644 index 0000000..52de2e9 --- /dev/null +++ b/ctf.cpp @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/ctf.cpp $ +// $Revision:: 3 $ +// $Author:: Jimdose $ +// $Date:: 10/10/98 3:37a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/ctf.cpp $ +// +// 3 10/10/98 3:37a Jimdose +// Began converting to Sin +// +// 2 10/10/98 3:03a Jimdose +// Created file +// +// 1 10/10/98 3:02a Jimdose +// +// DESCRIPTION: +// Game code for Threewave Capture the Flag. +// +// The original source for this code was graciously provided by Zoid and +// Id Software. Many thanks! +// +// Original credits: +// +// Programming - Dave 'Zoid' Kirsch +// Original CTF Art Design - Brian 'Whaleboy' Cozzens +// + +#include "g_local.h" +#include "ctf.h" +#include "ctf_player.h" + +cvar_t *ctf; +cvar_t *ctf_forcejoin; + +typedef struct ctfgame_s + { + int team1, team2; + int total1, total2; // these are only set when going into intermission! + float last_flag_capture; + int last_capture_team; + } ctfgame_t; + +ctfgame_t ctfgame; + +qboolean techspawn = false; + +void CTFInit + ( + void + ) + + { + ctf = gi.cvar("ctf", "1", CVAR_SERVERINFO); + ctf_forcejoin = gi.cvar("ctf_forcejoin", "", 0); + + memset( &ctfgame, 0, sizeof( ctfgame ) ); + techspawn = false; + } diff --git a/ctf.h b/ctf.h new file mode 100644 index 0000000..43c31ef --- /dev/null +++ b/ctf.h @@ -0,0 +1,167 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/ctf.h $ +// $Revision:: 2 $ +// $Author:: Jimdose $ +// $Date:: 10/10/98 3:03a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/ctf.h $ +// +// 2 10/10/98 3:03a Jimdose +// Created file +// +// 1 10/10/98 3:02a Jimdose +// +// DESCRIPTION: +// Game code for Threewave Capture the Flag. +// +// The original source for this code was graciously provided by Zoid and +// Id Software. Many thanks! +// +// Original credits: +// +// Programming - Dave 'Zoid' Kirsch +// Original CTF Art Design - Brian 'Whaleboy' Cozzens +// + +#ifndef __CTF_H__ +#define __CTF_H__ + +#include "g_local.h" + +#define CTF_VERSION 1.02 +#define CTF_VSTRING2(x) #x +#define CTF_VSTRING(x) CTF_VSTRING2(x) +#define CTF_STRING_VERSION CTF_VSTRING(CTF_VERSION) + +#define STAT_CTF_TEAM1_PIC 17 +#define STAT_CTF_TEAM1_CAPS 18 +#define STAT_CTF_TEAM2_PIC 19 +#define STAT_CTF_TEAM2_CAPS 20 +#define STAT_CTF_FLAG_PIC 21 +#define STAT_CTF_JOINED_TEAM1_PIC 22 +#define STAT_CTF_JOINED_TEAM2_PIC 23 +#define STAT_CTF_TEAM1_HEADER 24 +#define STAT_CTF_TEAM2_HEADER 25 +#define STAT_CTF_TECH 26 +#define STAT_CTF_ID_VIEW 27 + +typedef enum + { + CTF_NOTEAM, + CTF_TEAM1, + CTF_TEAM2 + } ctfteam_t; + +typedef enum + { + CTF_STATE_START, + CTF_STATE_PLAYING + } ctfstate_t; + +typedef enum + { + CTF_GRAPPLE_STATE_FLY, + CTF_GRAPPLE_STATE_PULL, + CTF_GRAPPLE_STATE_HANG + } ctfgrapplestate_t; + +extern cvar_t *ctf; + +#define CTF_TEAM1_SKIN "ctf_r" +#define CTF_TEAM2_SKIN "ctf_b" + +#define DF_CTF_FORCEJOIN 131072 +#define DF_ARMOR_PROTECT 262144 +#define DF_CTF_NO_TECH 524288 + +#define CTF_CAPTURE_BONUS 15 // what you get for capture +#define CTF_TEAM_BONUS 10 // what your team gets for capture +#define CTF_RECOVERY_BONUS 1 // what you get for recovery +#define CTF_FLAG_BONUS 0 // what you get for picking up enemy flag +#define CTF_FRAG_CARRIER_BONUS 2 // what you get for fragging enemy flag carrier +#define CTF_FLAG_RETURN_TIME 40 // seconds until auto return + +#define CTF_CARRIER_DANGER_PROTECT_BONUS 2 // bonus for fraggin someone who has recently hurt your flag carrier +#define CTF_CARRIER_PROTECT_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag carrier +#define CTF_FLAG_DEFENSE_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag +#define CTF_RETURN_FLAG_ASSIST_BONUS 1 // awarded for returning a flag that causes a capture to happen almost immediately +#define CTF_FRAG_CARRIER_ASSIST_BONUS 2 // award for fragging a flag carrier if a capture happens almost immediately + +#define CTF_TARGET_PROTECT_RADIUS 400 // the radius around an object being defended where a target will be worth extra frags +#define CTF_ATTACKER_PROTECT_RADIUS 400 // the radius around an object being defended where an attacker will get extra frags when making kills + +#define CTF_CARRIER_DANGER_PROTECT_TIMEOUT 8 +#define CTF_FRAG_CARRIER_ASSIST_TIMEOUT 10 +#define CTF_RETURN_FLAG_ASSIST_TIMEOUT 10 + +#define CTF_AUTO_FLAG_RETURN_TIMEOUT 30 // number of seconds before dropped flag auto-returns + +#define CTF_TECH_TIMEOUT 60 // seconds before techs spawn again + +#define CTF_GRAPPLE_SPEED 650 // speed of grapple in flight +#define CTF_GRAPPLE_PULL_SPEED 650 // speed player is pulled at + +#if 0 +void CTFInit(void); + +void SP_info_player_team1(edict_t *self); +void SP_info_player_team2(edict_t *self); + +char *CTFTeamName(int team); +char *CTFOtherTeamName(int team); +void CTFAssignSkin(edict_t *ent, char *s); +void CTFAssignTeam(gclient_t *who); +edict_t *SelectCTFSpawnPoint (edict_t *ent); +qboolean CTFPickup_Flag(edict_t *ent, edict_t *other); +qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item); +void CTFEffects(edict_t *player); +void CTFCalcScores(void); +void SetCTFStats(edict_t *ent); +void CTFDeadDropFlag(edict_t *self); +void CTFScoreboardMessage (edict_t *ent, edict_t *killer); +void CTFTeam_f (edict_t *ent); +void CTFID_f (edict_t *ent); +void CTFSay_Team(edict_t *who, char *msg); +void CTFFlagSetup (edict_t *ent); +void CTFResetFlag(int ctf_team); +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker); +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker); + +// GRAPPLE +void CTFWeapon_Grapple (edict_t *ent); +void CTFPlayerResetGrapple(edict_t *ent); +void CTFGrapplePull(edict_t *self); +void CTFResetGrapple(edict_t *self); + +//TECH +gitem_t *CTFWhat_Tech(edict_t *ent); +qboolean CTFPickup_Tech (edict_t *ent, edict_t *other); +void CTFDrop_Tech(edict_t *ent, gitem_t *item); +void CTFDeadDropTech(edict_t *ent); +void CTFSetupTechSpawn(void); +int CTFApplyResistance(edict_t *ent, int dmg); +int CTFApplyStrength(edict_t *ent, int dmg); +qboolean CTFApplyStrengthSound(edict_t *ent); +qboolean CTFApplyHaste(edict_t *ent); +void CTFApplyHasteSound(edict_t *ent); +void CTFApplyRegeneration(edict_t *ent); +qboolean CTFHasRegeneration(edict_t *ent); +void CTFRespawnTech(edict_t *ent); + +void CTFOpenJoinMenu(edict_t *ent); +qboolean CTFStartClient(edict_t *ent); + +qboolean CTFCheckRules(void); + +void SP_misc_ctf_banner (edict_t *ent); +void SP_misc_ctf_small_banner (edict_t *ent); +#endif + +#endif /* ctf.h */ diff --git a/ctf_player.cpp b/ctf_player.cpp new file mode 100644 index 0000000..0cab02c --- /dev/null +++ b/ctf_player.cpp @@ -0,0 +1,208 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/ctf_player.cpp $ +// $Revision:: 4 $ +// $Author:: Markd $ +// $Date:: 10/10/98 12:38p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/ctf_player.cpp $ +// +// 4 10/10/98 12:38p Markd +// Fixed a misnamed function +// +// 3 10/10/98 3:37a Jimdose +// Began converting to Sin +// +// 2 10/10/98 3:03a Jimdose +// Created file +// +// 1 10/10/98 3:02a Jimdose +// +// DESCRIPTION: +// Player code for Threewave Capture the Flag. +// +// The original source for this code was graciously provided by Zoid and +// Id Software. Many thanks! +// +// Original credits: +// +// Programming - Dave 'Zoid' Kirsch +// Original CTF Art Design - Brian 'Whaleboy' Cozzens +// + +#include "g_local.h" +#include "ctf_player.h" + +extern Event EV_Player_Respawn; + +CLASS_DECLARATION( Player, CTF_Player, "player" ); + +ResponseDef CTF_Player::Responses[] = + { + { &EV_ClientMove, ( Response )CTF_Player::ClientThink }, + { &EV_Player_Respawn, ( Response )CTF_Player::Respawn }, + { &EV_Killed, ( Response )CTF_Player::Killed }, + { &EV_GotKill, ( Response )CTF_Player::GotKill }, + { &EV_ClientEndFrame, ( Response )CTF_Player::EndFrame }, + + { NULL, NULL } + }; + +CTF_Player::CTF_Player() + { + } + +void CTF_Player::Init + ( + void + ) + + { + Player::Init(); + } + +CTF_Player::~CTF_Player() + { + } + +void CTF_Player::Respawn + ( + Event *ev + ) + + { + Player::Respawn( ev ); + } + +const char *CTF_Player::TeamName + ( + void + ) + + { + switch ( team ) + { + case CTF_TEAM1: + return "RED"; + + case CTF_TEAM2: + return "BLUE"; + } + + return "UKNOWN"; + } + +const char *CTF_Player::OtherTeamName + ( + void + ) + + { + switch ( team ) + { + case CTF_TEAM1: + return "BLUE"; + + case CTF_TEAM2: + return "RED"; + } + + return "UKNOWN"; + } + +int CTF_Player::Team + ( + void + ) + + { + return team; + } + +int CTF_Player::OtherTeam + ( + void + ) + + { + switch ( team ) + { + case CTF_TEAM1: + return CTF_TEAM2; + + case CTF_TEAM2: + return CTF_TEAM1; + } + + // invalid value + return -1; + } + +void CTF_Player::Killed + ( + Event *ev + ) + + { + Player::Killed( ev ); + } + +void CTF_Player::Prethink + ( + void + ) + + { + Player::Prethink(); + } + +void CTF_Player::Postthink + ( + void + ) + + { + Player::Postthink(); + } + +void CTF_Player::ClientThink + ( + Event *ev + ) + + { + Player::ClientThink( ev ); + } + +void CTF_Player::UpdateStats + ( + void + ) + + { + Player::UpdateStats(); + } + +void CTF_Player::EndFrame + ( + Event *ev + ) + + { + Player::EndFrame( ev ); + } + +void CTF_Player::GotKill + ( + Event *ev + ) + + { + Player::GotKill( ev ); + } diff --git a/ctf_player.h b/ctf_player.h new file mode 100644 index 0000000..1bda24c --- /dev/null +++ b/ctf_player.h @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/ctf_player.h $ +// $Revision:: 3 $ +// $Author:: Jimdose $ +// $Date:: 10/10/98 3:37a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/ctf_player.h $ +// +// 3 10/10/98 3:37a Jimdose +// Began converting to Sin +// +// 2 10/10/98 3:03a Jimdose +// Created file +// +// 1 10/10/98 3:02a Jimdose +// +// DESCRIPTION: +// Player code for Threewave Capture the Flag. +// +// The original source for this code was graciously provided by Zoid and +// Id Software. Many thanks! +// +// Original credits: +// +// Programming - Dave 'Zoid' Kirsch +// Original CTF Art Design - Brian 'Whaleboy' Cozzens +// + +#ifndef __CTF_PLAYER_H__ +#define __CTF_PLAYER_H__ + +#include "player.h" +#include "ctf.h" + +class EXPORT_FROM_DLL CTF_Player : public Player + { + private: + int team; + + public: + + CLASS_PROTOTYPE( CTF_Player ); + + CTF_Player(); + virtual ~CTF_Player(); + virtual void Init( void ); + + const char *TeamName( void ); + const char *OtherTeamName( void ); + int Team( void ); + int OtherTeam( void ); + + virtual void Respawn( Event *ev ); + virtual void Killed( Event *ev ); + virtual void GotKill( Event *ev ); + void ClientThink( Event *ev ); + virtual void EndFrame( Event *ev ); + + virtual void UpdateStats( void ); + + virtual void Prethink( void ); + virtual void Postthink( void ); + + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void CTF_Player::Archive + ( + Archiver &arc + ) + + { + Player::Archive( arc ); + } + +inline EXPORT_FROM_DLL void CTF_Player::Unarchive + ( + Archiver &arc + ) + + { + Player::Unarchive( arc ); + } + +#endif /* ctf_player.h */ diff --git a/datamap.h b/datamap.h new file mode 100644 index 0000000..b4d9d51 --- /dev/null +++ b/datamap.h @@ -0,0 +1,437 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/datamap.h $ +// $Revision:: 2 $ +// $Author:: Jimdose $ +// $Date:: 8/31/98 5:40p $ +// +// Copyright (C) 1998 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/datamap.h $ +// +// 2 8/31/98 5:40p Jimdose +// Created file. Still need to test it. +// +// 1 8/31/98 5:40p Jimdose +// +// DESCRIPTION: +// Template class for mapping values of one data type to another. +// + +#ifndef __DATAMAP_H__ +#define __DATAMAP_H__ + +#include "g_local.h" +#include + +template< class Key, class Value > +class EXPORT_FROM_DLL DataMap + { + private: + Key **keyarray; + Value **valuearray; + int numobjects; + int maxobjects; + + public: + DataMap(); + DataMap( DataMap &map ); + ~DataMap(); + void FreeObjectList( void ); + void ClearObjectList( void ); + int NumObjects( void ); + void Resize( int maxelements ); + Value& operator[]( Key key ); + int SetValue( Key key, Value obj ); + void SetValueAt( int index, Value& obj ); + int AddKey( Key& key ); + int AddKeyPair( Key& key, Value& value ); + int FindKey( Key& key ); + void KeyInList( Key& key ); + Value& ValueAt( int index ); + Key& KeyAt( int index ); + void RemoveKeyAt( int index ); + void RemoveKey( Key& key ); + }; + +template< class Key, class Value> +DataMap::DataMap() + { + keyarray = NULL; + valuearray = NULL; + numobjects = 0; + maxobjects = 0; + } + +template< class Key, class Value> +DataMap::DataMap( DataMap &map ) + { + numobjects = 0; + maxobjects = 0; + keyarray = NULL; + valuearray = NULL; + + Resize( map.maxobjects ); + for( i = 0; i < map.numobjects; i++ ) + { + AddKeyPair( map.KeyAt( i ), map.ValueAt( i ) ); + } + } + +template< class Key, class Value> +DataMap::~DataMap() + { + FreeObjectList(); + } + +template< class Key, class Value> +EXPORT_FROM_DLL void DataMap::FreeObjectList + ( + void + ) + + { + if ( keyarray ) + { + ClearObjectList(); + + delete[] keyarray; + delete[] valuearray; + } + + keyarray = NULL; + valuearray = NULL; + numobjects = 0; + maxobjects = 0; + } + +template< class Key, class Value> +EXPORT_FROM_DLL void DataMap::ClearObjectList + ( + void + ) + + { + // only delete the list if we have objects in it + if ( keyarray && numobjects ) + { + for( i = 0; i < numobjects; i++ ) + { + delete keyarray[ i ]; + delete valuearray[ i ]; + } + + memset( keyarray, 0, maxobjects * sizeof( Key * ) ); + memset( valuearray, 0, maxobjects * sizeof( Value * ) ); + + numobjects = 0; + } + } + +template< class Key, class Value> +EXPORT_FROM_DLL int DataMap::NumObjects + ( + void + ) + + { + return numobjects; + } + +template< class Key, class Value> +EXPORT_FROM_DLL void DataMap::Resize + ( + int maxelements + ) + + { + Key *keytemp; + Value *valuetemp; + + assert( maxelements > 0 ); + + if ( maxelements <= 0 ) + { + FreeObjectList(); + return; + } + + if ( !keyarray ) + { + maxobjects = maxelements; + + keyarray = new Key[ maxobjects ]; + memset( keyarray, 0, maxobjects * sizeof( Key * ) ); + + valuearray = new Value[ maxobjects ]; + memset( valuearray, 0, maxobjects * sizeof( Value * ) ); + } + else + { + keytemp = keyarray; + valuetemp = valuearray; + + maxobjects = maxelements; + if ( maxobjects < numobjects ) + { + maxobjects = numobjects; + } + + keyarray = new Key[ maxobjects ]; + valuearray = new Value[ maxobjects ]; + + memcpy( keyarray, keytemp, sizeof( Key * ) * maxobjects ); + memcpy( valuearray, valuetemp, sizeof( Value * ) * maxobjects ); + + delete[] keytemp; + delete[] valuetemp; + } + } + +template< class Key, class Value> +EXPORT_FROM_DLL inline Value& DataMap::operator[] + ( + Key key + ) + + { + int index; + + index = FindKey( key ); + if ( index == -1 ) + { + index = AddKey( key ); + } + + assert( ( index >= 0 ) && ( index < numobjects ) ); + assert( valuearray ); + + return *valuearray[ index ]; + } + +template< class Key, class Value> +EXPORT_FROM_DLL inline int DataMap::SetValue + ( + Key key, + Value value + ) + + { + int index; + + index = FindKey( key ); + if ( index == -1 ) + { + index = AddKey( key ); + } + + assert( ( index >= 0 ) && ( index < numobjects ) ); + assert( valuearray ); + + *valuearray[ index ] = value; + + return index; + } + +template< class Key, class Value> +EXPORT_FROM_DLL void DataMap::SetValueAt + ( + int index, + Value& obj + ) + + { + if ( ( index < 0 ) || ( index >= numobjects ) ) + { + gi.error( "DataMap::SetValueAt : index out of range" ); + } + + assert( valuearray ); + + *valuearray[ index ] = obj; + } + +template< class Key, class Value> +EXPORT_FROM_DLL int DataMap::AddKey + ( + Key& key + ) + + { + int index; + + if ( !keyarray ) + { + Resize( 10 ); + } + + if ( numobjects == maxobjects ) + { + Resize( maxobjects * 2 ); + } + + index = numobjects; + numobjects++; + + keyarray[ index ] = new Key; + valuearray[ index ] = new Value; + *keyarray[ index ] = key; + + return index; + } + +template< class Key, class Value> +EXPORT_FROM_DLL int DataMap::AddKeyPair + ( + Key& key, + Value& value + ) + + { + int index; + + if ( !keyarray ) + { + Resize( 10 ); + } + + if ( numobjects == maxobjects ) + { + Resize( maxobjects * 2 ); + } + + index = numobjects; + numobjects++; + + keyarray[ index ] = new Key; + valuearray[ index ] = new Value; + *keyarray[ index ] = key; + *valuearray[ index ] = value; + + return index; + } + +template< class Key, class Value> +EXPORT_FROM_DLL int DataMap::FindKey + ( + Key& key + ) + + { + int i; + + for( i = 0; i < numobjects; i++ ) + { + if ( *keyarray[ i ] == key ) + { + return i; + } + } + + return -1; + } + +template< class Key, class Value> +EXPORT_FROM_DLL void DataMap::KeyInList + ( + Key& key + ) + + { + if ( FindKey( key ) == -1 ) + { + return false; + } + + return true; + } + +template< class Key, class Value> +EXPORT_FROM_DLL Value &DataMap::ValueAt + ( + int index + ) + + { + if ( ( index < 0 ) || ( index >= numobjects ) ) + { + gi.error( "DataMap::ValueAt : index out of range" ); + } + + return *valuearray[ index ]; + } + +template< class Key, class Value> +EXPORT_FROM_DLL Key &DataMap::KeyAt + ( + int index + ) + + { + if ( ( index < 0 ) || ( index >= numobjects ) ) + { + gi.error( "DataMap::KeyAt : index out of range" ); + } + + return *keyarray[ index ]; + } + +template< class Key, class Value> +EXPORT_FROM_DLL void DataMap::RemoveKeyAt + ( + int index + ) + + { + int i; + + if ( !keyarray ) + { + gi.dprintf( "DataMap::RemoveKeyAt : Empty list" ); + return; + } + + if ( ( index < 0 ) || ( index >= numobjects ) ) + { + gi.error( "DataMap::RemoveKeyAt : index out of range" ); + return; + } + + delete keyarray[ index ]; + delete valuearray[ index ]; + + for( i = index; i < numobjects; i++ ) + { + keyarray[ i ] = keyarray[ i + 1 ]; + valuearray[ i ] = valuearray[ i + 1 ]; + } + + numobjects--; + keyarray[ numobjects ] = NULL; + valuearray[ numobjects ] = NULL; + } + +template< class Key, class Value> +EXPORT_FROM_DLL void DataMap::RemoveKey + ( + Key& key + ) + + { + int index; + + index = FindKey( key ); + if ( index == -1 ) + { + gi.dprintf( "DataMap::RemoveKey : Object not in list" ); + return; + } + + RemoveKeyAt( index ); + } + +#endif /* datamap.h */ diff --git a/deadbody.cpp b/deadbody.cpp new file mode 100644 index 0000000..fc2a54b --- /dev/null +++ b/deadbody.cpp @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/deadbody.cpp $ +// $Revision:: 13 $ +// $Author:: Jimdose $ +// $Date:: 11/19/98 9:28p $ +// +// Copyright (C) 1998 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/deadbody.cpp $ +// +// 13 11/19/98 9:28p Jimdose +// made deadbodies copy the gravaxis +// +// 12 11/11/98 11:11p Aldie +// Make deadbodies do blood and gib properly +// +// 11 11/10/98 7:20p Aldie +// Fix for deadbodies not displaying death properly +// +// 10 10/17/98 9:40p Markd +// dead body queue won't initialize unless in deathmatch or coop +// +// 9 10/10/98 1:26a Jimdose +// InitializeBodyQueue is disabled while loading savegames +// +// 8 9/22/98 5:19p Markd +// Put in new consolidated gib function +// +// 7 9/22/98 3:21p Markd +// put in parentmode lockout for blood and gibs +// +// 6 9/19/98 6:09p Markd +// changed linkentity to link() call +// +// 5 8/29/98 9:40p Jimdose +// Moved bodyque to deadbody +// +// 4 7/29/98 2:31p Aldie +// Changed health to a float +// +// 3 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 +// +// 2 7/13/98 4:59p Aldie +// Added dead player bodies with gibbing +// +// DESCRIPTION: +// Dead body + +#include "deadbody.h" +#include "gibs.h" + +CLASS_DECLARATION( Sentient, Deadbody, "deadbody" ); + +ResponseDef Deadbody::Responses[] = + { + { &EV_Gib, ( Response )Deadbody::GibEvent }, + { NULL, NULL } + }; + +void Deadbody::GibEvent + ( + Event *ev + ) + + { + takedamage = DAMAGE_NO; + + if ( sv_gibs->value && !parentmode->value ) + { + setSolidType( SOLID_NOT ); + hideModel(); + + CreateGibs( this, health, 1.0f, 3 ); + } + } + +void CopyToBodyQueue + ( + edict_t *ent + ) + + { + edict_t *body; + + // grab a body from the queue and cycle to the next one + body = &g_edicts[ ( int )maxclients->value + level.body_queue + 1 ]; + level.body_queue = ( level.body_queue + 1 ) % BODY_QUEUE_SIZE; + + gi.unlinkentity( ent ); + gi.unlinkentity( body ); + + body->s = ent->s; + body->s.number = body - g_edicts; + body->svflags = ent->svflags; + body->solid = ent->solid; + body->clipmask = ent->clipmask; + body->owner = ent->owner; + body->entity->movetype = ent->entity->movetype; + body->entity->takedamage = DAMAGE_YES; + body->entity->deadflag = DEAD_DEAD; + body->s.renderfx &= ~RF_DONTDRAW; + body->entity->origin = ent->entity->worldorigin; + body->entity->setSize(ent->mins,ent->maxs); + body->entity->link(); + body->entity->SetGravityAxis( ent->entity->gravaxis ); + } + +void InitializeBodyQueue + ( + void + ) + + { + int i; + Deadbody *body; + + if ( ( !LoadingSavegame ) && ( deathmatch->value || coop->value ) ) + { + level.body_queue = 0; + for ( i=0; iedict->owner = NULL; + body->edict->s.skinnum = -1; + body->flags |= (FL_DIE_GIBS|FL_BLOOD); + } + } + } diff --git a/deadbody.h b/deadbody.h new file mode 100644 index 0000000..30bbbb3 --- /dev/null +++ b/deadbody.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/deadbody.h $ +// $Revision:: 5 $ +// $Author:: Jimdose $ +// $Date:: 11/18/98 9:18p $ +// +// Copyright (C) 1998 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/deadbody.h $ +// +// 5 11/18/98 9:18p Jimdose +// lowered number of bodies to 4 +// +// 4 11/11/98 11:13p Aldie +// Made deadbodies blood and gib properly +// +// 3 8/29/98 9:48p Jimdose +// Added bodyqueue code to deadbody +// +// 2 7/13/98 5:01p Aldie +// Added dead player bodies with gibbing +// +// DESCRIPTION: +// Dead body + +#ifndef __DEADBODY_H__ +#define __DEADBODY_H__ + +#include "g_local.h" +#include "sentient.h" + +#define BODY_QUEUE_SIZE 4 + +EXPORT_FROM_DLL void InitializeBodyQueue( void ); +EXPORT_FROM_DLL void CopyToBodyQueue( edict_t *ent ); + +class EXPORT_FROM_DLL Deadbody : public Sentient + { + public: + CLASS_PROTOTYPE( Deadbody ); + + virtual void GibEvent( Event *ev ); + }; + +#endif /* deadbody.h */ diff --git a/doors.cpp b/doors.cpp new file mode 100644 index 0000000..846260a --- /dev/null +++ b/doors.cpp @@ -0,0 +1,1433 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/doors.cpp $ +// $Revision:: 71 $ +// $Author:: Markd $ +// $Date:: 11/16/98 9:48p $ +// +// 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/doors.cpp $ +// +// 71 11/16/98 9:48p Markd +// made door sounds always stop to kill looping sounds +// +// 70 11/13/98 4:35p Jimdose +// doors weren't staying open when wait was -1 +// +// 69 10/24/98 3:25a Jimdose +// open was postponing the close event further and further into the future due +// to touchfield activation. Changed it to a cancel and a post of close. +// fixes doors staying open +// +// 68 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 67 10/23/98 11:10p Jimdose +// removed trigger_finished +// Close now cancels pending close events. Hopefully this fixes the spinning +// door bug. +// +// 66 10/23/98 10:12p Jimdose +// fixed bug where doors don't close after actors open them +// fixed code that determines direction door should open +// +// 65 10/21/98 10:58p Jimdose +// fixed (hopefully) most of the door bugs +// +// 64 10/21/98 2:35a Jimdose +// Don't spawn trigger field when DOOR_TOGGLE is set +// +// 63 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 62 10/16/98 8:24p Jimdose +// Put some extra checks in to make sure master is set +// +// 61 10/07/98 4:11p Markd +// Fixed using of doors +// +// 60 10/06/98 6:07p Markd +// Fixed door message code +// +// 59 10/02/98 7:45p Markd +// put in more door debug info +// +// 58 9/30/98 3:42p Markd +// put in bulletproofing for start thread functions +// +// 57 9/24/98 1:50a Jimdose +// Fixed side of door checks when opening doors +// +// 56 9/23/98 2:19p Markd +// made all doors play sounds not just the masters +// +// 55 9/23/98 1:31p Markd +// changed default door sound documentation +// +// 54 9/22/98 4:23p Markd +// Fixed some targetname stuff for lights, doors and scriptobjects +// +// 53 9/03/98 9:07p Jimdose +// Added CanBeOpenedBy so that the AI can check if a door is useable by an +// actor +// changed owner to master +// Fixed bug with doors that don't stop +// opened doors can be used again to close them +// fixed door blocked +// +// 52 8/28/98 4:14p Markd +// Added sound_locked support +// +// 51 8/26/98 6:43p Jimdose +// doors now only show the item needed when other is a client +// +// 50 8/24/98 6:10p Markd +// Added killable doors, that stop for nothing +// +// 49 8/24/98 5:47p Markd +// Fixed triggerable doors +// +// 48 8/24/98 11:32a Markd +// Added Start method to threads, repladed all ProcessEvent( +// EV_ScriptThread_execute) with thread->Start( -1 ) +// +// 47 8/21/98 3:48p Markd +// Added openthread and closethread events +// +// 46 8/21/98 1:43a Markd +// Added full ScriptDoor functionality +// +// 45 8/18/98 11:08p Markd +// Added new Alias System +// +// 44 8/15/98 10:42a Markd +// took out auto-open for actors for non-sin-demo +// +// 43 8/14/98 8:18p Markd +// Fixed silly str thing to get rid of weird messages. +// +// 42 8/13/98 4:57p Jimdose +// Made Open check if there are any event args. If not, it prints a warning so +// that the level designers know. +// +// 41 8/03/98 7:52p Jimdose +// added hack fix doors for matt +// +// 40 7/29/98 2:31p Aldie +// Changed health to a float +// +// 39 7/26/98 3:40p Jimdose +// locked doors don't autoopen for actors +// +// 38 7/26/98 9:51a Markd +// fixed "snd_locked" bug +// +// 37 7/26/98 9:22a Jimdose +// Doors auto-open for actors +// +// 36 7/26/98 1:03a Markd +// Fixed door trigger field +// +// 35 7/26/98 12:48a Jimdose +// Fixed calculating bbox in LinkDoors +// +// 34 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 +// +// 33 7/21/98 4:22p Aldie +// Make icons work for locks +// +// 32 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 31 7/20/98 10:15a Markd +// Fixed funky door problem +// +// 30 7/18/98 4:42p Markd +// Added snd_locked support for locked doors +// +// 29 7/11/98 4:49p Markd +// Fixed minor bug in door code with not setting other +// +// 28 7/11/98 4:46p Markd +// Fixed doors not being lockable +// +// 27 7/10/98 10:34p Markd +// Fixed locked doors +// +// 26 7/10/98 2:09p Markd +// Added locking capability to doors +// +// 25 6/30/98 6:03p Jimdose +// TryOpen now checks if other is NULL +// +// 24 6/28/98 3:15p Markd +// Fixed sliding doors +// +// 23 6/27/98 2:17p Aldie +// Updated centerprint to use new method +// +// 22 6/25/98 8:48p Markd +// Merged RotatingDoors and regular Doors +// +// 21 6/24/98 8:45p Markd +// Rewrote door code +// +// 12 6/17/98 10:14p Jimdose +// Added isOpen for AI +// Made doors stay open while an entity is in its trigger field. +// +// 11 6/10/98 2:10p Aldie +// Updated damage function. +// +// 10 6/04/98 4:35p Markd +// made door sounds not use PHS +// +// 9 5/24/98 8:55p Jimdose +// Removed the char * cast from soundindex call +// +// 8 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 +// +// 7 5/24/98 3:42p Markd +// fixed str bug +// +// 6 5/24/98 2:46p Markd +// Made char *'s into const char *'s +// +// 5 5/24/98 1:04a Jimdose +// Added sound events for ai +// +// 4 5/22/98 9:38p Jimdose +// added DoorBlocked +// +// 3 5/03/98 4:36p Jimdose +// Changed Vector class +// +// 2 4/29/98 9:55p Jimdose +// Created file +// +// DESCRIPTION: +// Doors are environment objects that rotate open when activated by triggers +// or when used by the player. +// + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "mover.h" +#include "doors.h" +#include "sentient.h" +#include "areaportal.h" +#include "scriptmaster.h" +#include "item.h" +#include "actor.h" +#include "player.h" + +Event EV_Door_TriggerFieldTouched( "door_triggerfield" ); +Event EV_Door_TryOpen( "tryToOpen" ); +Event EV_Door_Close( "close" ); +Event EV_Door_Open( "open" ); +Event EV_Door_DoClose( "doclose" ); +Event EV_Door_DoOpen( "doopen" ); +Event EV_Door_CloseEnd( "doorclosed" ); +Event EV_Door_OpenEnd( "dooropened" ); +Event EV_Door_Fire( "toggledoor" ); +Event EV_Door_Link( "linkdoor" ); +Event EV_Door_SetTime( "time" ); +Event EV_Door_Lock( "lock" ); +Event EV_Door_Unlock( "unlock" ); + +CLASS_DECLARATION( ScriptSlave, Door, "NormalDoor" ); + +#define DOOR_START_OPEN 1 +#define DOOR_OPEN_DIRECTION 2 +#define DOOR_DONT_LINK 4 +#define DOOR_TOGGLE 32 +#define DOOR_AUTO_OPEN 64 +#define DOOR_TARGETED 128 + +#define STATE_OPEN 1 +#define STATE_OPENING 2 +#define STATE_CLOSING 3 +#define STATE_CLOSED 4 + +/* + +Doors are similar to buttons, but can spawn a fat trigger field around them +to open without a touch, and they link together to form simultanious +double/quad doors. + +Door.master is the master door. If there is only one door, it points to itself. +If multiple doors, all will point to a single one. + +Door.enemy chains from the master door through all doors linked in the chain. + +*/ + +ResponseDef Door::Responses[] = + { + { &EV_Door_TriggerFieldTouched, ( Response )Door::FieldTouched }, + { &EV_Trigger_Effect, ( Response )Door::TryOpen }, + { &EV_Activate, ( Response )Door::TryOpen }, + { &EV_Door_TryOpen, ( Response )Door::TryOpen }, + { &EV_Door_Close, ( Response )Door::Close }, + { &EV_Door_Open, ( Response )Door::Open }, + { &EV_Door_CloseEnd, ( Response )Door::CloseEnd }, + { &EV_Door_OpenEnd, ( Response )Door::OpenEnd }, + { &EV_Door_Fire, ( Response )Door::DoorFire }, + { &EV_Door_Link, ( Response )Door::LinkDoors }, + { &EV_Door_SetTime, ( Response )Door::SetTime }, + { &EV_Use, ( Response )Door::DoorUse }, + { &EV_Killed, ( Response )Door::DoorFire }, + { &EV_Blocked, ( Response )Door::DoorBlocked }, + { &EV_Door_Lock, ( Response )Door::LockDoor }, + { &EV_Door_Unlock, ( Response )Door::UnlockDoor }, + { &EV_Touch, NULL }, + { NULL, NULL } + }; + +Door::Door() + { + float t; + + nextdoor = 0; + trigger = 0; + locked = false; + master = this; + lastblocktime = 0; + diropened = 0; + + showModel(); + + sound_stop = G_GetStringArg( "sound_stop", gi.GlobalAlias_FindRandom( "door_stop" ) ); + sound_move = G_GetStringArg( "sound_move", gi.GlobalAlias_FindRandom( "door_moving" ) ); + sound_message = G_GetStringArg( "sound_message" ); + sound_locked = G_GetStringArg( "sound_locked" ); + + gi.soundindex( sound_stop.c_str() ); + gi.soundindex( sound_move.c_str() ); + if ( sound_message.length() > 1 ) + gi.soundindex( sound_message.c_str() ); + + health = G_GetFloatArg( "health" ); + max_health = health; + + traveltime = G_GetFloatArg( "time", 0.3 ); + + if ( traveltime < FRAMETIME ) + { + traveltime = FRAMETIME; + } + + speed = 1.0f / traveltime; + wait = G_GetFloatArg( "wait", 3 ); + + if ( ( spawnflags & DOOR_TOGGLE ) && ( wait == 3 ) ) + wait = 0; + + dmg = G_GetIntArg( "dmg", 0 ); + + setSolidType( SOLID_BSP ); + setMoveType( MOVETYPE_PUSH ); + + setSize( mins, maxs ); + + dir = G_GetMovedir(); + t = dir[ 0 ]; + dir[ 0 ] = -dir[ 1 ]; + dir[ 1 ] = t; + + setOrigin( origin ); + doormin = absmin; + doormax = absmax; + + + // DOOR_START_OPEN is to allow an entity to be lighted in the closed position + // but spawn in the open position + if ( spawnflags & DOOR_START_OPEN ) + { + state = STATE_OPEN; + PostEvent( EV_Door_Open, 0.05f ); + } + else + { + state = STATE_CLOSED; + } + previous_state = state; + + if ( health ) + { + takedamage = DAMAGE_YES; + } + + // LinkDoors can't be done until all of the doors have been spawned, so + // the sizes can be detected properly. + nextdoor = 0; + PostEvent( EV_Door_Link, 0 ); + + // Default to work with monsters and players + respondto = TRIGGER_PLAYERS | TRIGGER_MONSTERS; + if ( spawnflags & 8 ) + { + respondto &= ~TRIGGER_PLAYERS; + } + if ( spawnflags & 16 ) + { + respondto &= ~TRIGGER_MONSTERS; + } + } + +qboolean Door::isOpen + ( + void + ) + + { + return ( state == STATE_OPEN ); + } + +void Door::OpenEnd + ( + Event *ev + ) + + { +// if ( master == this ) + { + if ( sound_stop.length() > 1 ) + { + ProcessEvent( EV_DoorSound ); + sound( sound_stop, 1, CHAN_VOICE + CHAN_NO_PHS_ADD, ATTN_NORM ); + } + else + { + RandomGlobalSound( "null_sound", 1, CHAN_VOICE + CHAN_NO_PHS_ADD, ATTN_NORM ); + } + } + previous_state = state; + state = STATE_OPEN; + if ( spawnflags & DOOR_TOGGLE ) + { + // don't close automatically + return; + } + + if ( ( wait > 0 ) && ( master == this ) ) + { + PostEvent( EV_Door_Close, wait ); + } + } + +void Door::CloseEnd + ( + Event *ev + ) + + { +// if ( master == this ) + { + if ( sound_stop.length() > 1 ) + { + ProcessEvent( EV_DoorSound ); + sound( sound_stop, 1, CHAN_VOICE + CHAN_NO_PHS_ADD, ATTN_NORM ); + } + else + { + RandomGlobalSound( "null_sound", 1, CHAN_VOICE + CHAN_NO_PHS_ADD, ATTN_NORM ); + } + } + + SetAreaPortals( Target(), false ); + + previous_state = state; + state = STATE_CLOSED; + } + +void Door::Close + ( + Event *ev + ) + + { + Door *door; + + CancelEventsOfType( EV_Door_Close ); + + previous_state = state; + state = STATE_CLOSING; + + ProcessEvent( EV_Door_DoClose ); + + if ( sound_move.length() > 1 ) + { + ProcessEvent( EV_DoorSound ); + sound( sound_move, 1, CHAN_VOICE + CHAN_NO_PHS_ADD, ATTN_NORM ); + } + if ( master == this ) + { + if ( max_health ) + { + takedamage = DAMAGE_YES; + health = max_health; + } + + // trigger all paired doors + door = ( Door * )G_GetEntity( nextdoor ); + assert( door->isSubclassOf( Door ) ); + while( door && ( door != this ) ) + { + door->ProcessEvent( EV_Door_Close ); + door = ( Door * )G_GetEntity( door->nextdoor ); + assert( door->isSubclassOf( Door ) ); + } + } + } + +void Door::Open + ( + Event *ev + ) + + { + Door *door; + Event *e; + Entity *other; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "No entity specified to open door. Door may open the wrong way." ); + other = world; + } + else + { + other = ev->GetEntity( 1 ); + } + + if ( state == STATE_OPENING ) + { + // already going up + return; + } + + if ( state == STATE_OPEN ) + { + // reset top wait time + if ( wait > 0 ) + { + CancelEventsOfType( EV_Door_Close ); + PostEvent( EV_Door_Close, wait ); + } + return; + } + + previous_state = state; + state = STATE_OPENING; + + e = new Event( EV_Door_DoOpen ); + e->AddEntity( other ); + ProcessEvent( e ); + + if ( sound_move.length() > 1 ) + { + ProcessEvent( EV_DoorSound ); + sound( sound_move, 1, CHAN_VOICE + CHAN_NO_PHS_ADD, ATTN_NORM ); + } + if ( master == this ) + { + // trigger all paired doors + door = ( Door * )G_GetEntity( nextdoor ); + assert( door->isSubclassOf( Door ) ); + while( door && ( door != this ) ) + { + e = new Event( EV_Door_Open ); + e->AddEntity( other ); + door->ProcessEvent( e ); + door = ( Door * )G_GetEntity( door->nextdoor ); + assert( door->isSubclassOf( Door ) ); + } + + SetAreaPortals( Target(), true ); + } + } + +void Door::DoorUse + ( + Event *ev + ) + + { + Entity *other; + qboolean respond; + Event *e; + + other = ev->GetEntity( 1 ); + + respond = ( ( ( respondto & TRIGGER_PLAYERS ) && other->isClient() ) || + ( ( respondto & TRIGGER_MONSTERS ) && other->isSubclassOf( Actor ) ) ); + + if ( !respond ) + return; + + // only allow use when not triggerd by other events + if ( health || ( spawnflags & ( DOOR_AUTO_OPEN | DOOR_TARGETED ) ) ) + { + if ( other->isSubclassOf( Sentient ) && ( state == STATE_CLOSED ) ) + { + if ( health ) + { + gi.centerprintf ( other->edict, "jcx yv 20 string \"This door is jammed.\"" ); + } + else if ( spawnflags & DOOR_TARGETED ) + { + RandomGlobalSound( "door_triggered", 1, CHAN_VOICE + CHAN_NO_PHS_ADD, ATTN_NORM ); + //gi.centerprintf ( other->edict, "jcx yv 20 string \"This door opens elsewhere.\"" ); + } + } + return; + } + + assert( master ); + if ( !master ) + { + // bulletproofing + master = this; + } + + if ( master->state == STATE_CLOSED ) + { + e = new Event( EV_Door_TryOpen ); + e->AddEntity( other ); + master->ProcessEvent( e ); + } + else if ( master->state == STATE_OPEN ) + { + e = new Event( EV_Door_Close ); + e->AddEntity( other ); + master->ProcessEvent( e ); + } + } + +void Door::DoorFire + ( + Event *ev + ) + + { + Event *e; + Entity *other; + + other = ev->GetEntity( 1 ); + + assert( master == this ); + if ( master != this ) + { + gi.error( "DoorFire: master != self" ); + } + + // no more messages + SetMessage( NULL ); + + // reset health in case we were damage triggered + health = max_health; + + // will be reset upon return + takedamage = DAMAGE_NO; + + if ( ( spawnflags & ( DOOR_TOGGLE | DOOR_START_OPEN ) ) && ( state == STATE_OPENING || state == STATE_OPEN ) ) + { + spawnflags &= ~DOOR_START_OPEN; + ProcessEvent( EV_Door_Close ); + } + else + { + e = new Event( EV_Door_Open ); + e->AddEntity( other ); + ProcessEvent( e ); + } + } + +void Door::DoorBlocked + ( + Event *ev + ) + + { + Event *e; + Entity *other; + + assert( master ); + if ( ( master ) && ( master != this ) ) + { + master->ProcessEvent( ev ); + return; + } + + if ( lastblocktime > level.time ) + { + return; + } + + lastblocktime = level.time + 0.3; + + other = ev->GetEntity( 1 ); + + if ( dmg ) + { + other->Damage( this, this, (int)dmg, worldorigin, vec_zero, vec_zero, 0, 0, MOD_CRUSH, -1, -1, 1.0f ); + } + + // + // if we killed him, lets keep on going + // + if ( other->health <= 0 ) + { + return; + } + + if ( state == STATE_OPENING || state == STATE_OPEN ) + { + spawnflags &= ~DOOR_START_OPEN; + ProcessEvent( EV_Door_Close ); + } + else + { + e = new Event( EV_Door_Open ); + e->AddEntity( other ); + ProcessEvent( e ); + } + } + +void Door::FieldTouched + ( + Event *ev + ) + + { + Entity *other; + + other = ev->GetEntity( 1 ); + +#ifdef SIN_DEMO + if ( ( state != STATE_OPEN ) && !( spawnflags & DOOR_AUTO_OPEN ) && + ( !other || !other->isSubclassOf( Actor ) ) ) +#else + if ( ( state != STATE_OPEN ) && !( spawnflags & DOOR_AUTO_OPEN ) ) +#endif + { + return; + } + + TryOpen( ev ); + } + +qboolean Door::CanBeOpenedBy + ( + Entity *ent + ) + + { + assert( master ); + if ( ( master ) && ( master != this ) ) + { + return master->CanBeOpenedBy( ent ); + } + + if ( !locked && !key.length() ) + { + return true; + } + + if ( ent && ent->isSubclassOf( Sentient ) && ( ( Sentient * )ent )->HasItem( key.c_str() ) ) + { + return true; + } + + return false; + } + +void Door::TryOpen + ( + Event *ev + ) + + { + Entity *other; + Event *event; + + //FIXME + // hack so that doors aren't triggered by guys when game starts. + // have to fix delay that guys go through before setting up their threads + if ( level.time < 0.4 ) + { + return; + } + + other = ev->GetEntity( 1 ); + + assert( master ); + if ( master && ( this != master ) ) + { + event = new Event( EV_Door_TryOpen ); + event->AddEntity( other ); + master->ProcessEvent( event ); + return; + } + + if ( !other || ( other->health <= 0 ) ) + { + return; + } + + if ( locked ) + { + if ( sound_locked.length() > 1 ) + { + other->sound( sound_locked.c_str(), 1, CHAN_VOICE ); + } + else if ( other->isSubclassOf( Player) ) + { + other->RandomSound( "snd_locked", 1, CHAN_VOICE ); + //gi.centerprintf ( other->edict, "jcx yv 20 string \"This door is locked.\"" ); + } + + // locked doors don't open for anyone + return; + } + + if ( !CanBeOpenedBy( other ) ) + { + Item *item; + ClassDef *cls; + + if ( other->isClient() ) + { + cls = getClass( key.c_str() ); + if ( !cls ) + { + gi.dprintf( "No item named '%s'\n", key.c_str() ); + return; + } + item = ( Item * )cls->newInstance(); + item->CancelEventsOfType( EV_Item_DropToFloor ); + item->CancelEventsOfType( EV_Remove ); + item->ProcessPendingEvents(); + gi.centerprintf ( other->edict, "jcx yv 20 string \"You need this item:\" jcx yv -20 icon %d", item->GetIconIndex() ); + delete item; + } + return; + } + + // once we're opened by an item, we no longer need that item to open the door + key = ""; + + if ( Message().length() ) + { + gi.centerprintf( other->edict, "jcx jcy string \"%s\"", Message().c_str() ); + sound( sound_message, 1, CHAN_VOICE + CHAN_NO_PHS_ADD, ATTN_NORM ); + } + + event = new Event( EV_Door_Fire ); + event->AddEntity( other ); + ProcessEvent( event ); + } + +void Door::SpawnTriggerField + ( + Vector fmins, + Vector fmaxs + ) + + { + TouchField *trig; + Vector min; + Vector max; + + min = fmins - "60 60 8"; + max = fmaxs + "60 60 8"; + + trig = new TouchField; + trig->Setup( this, EV_Door_TriggerFieldTouched, min, max, respondto ); + + trigger = trig->entnum; + } + +EXPORT_FROM_DLL qboolean Door::DoorTouches + ( + Door *e1 + ) + + { + if ( e1->doormin.x > doormax.x ) + { + return false; + } + if ( e1->doormin.y > doormax.y ) + { + return false; + } + if ( e1->doormin.z > doormax.z ) + { + return false; + } + if ( e1->doormax.x < doormin.x ) + { + return false; + } + if ( e1->doormax.y < doormin.y ) + { + return false; + } + if ( e1->doormax.z < doormin.z ) + { + return false; + } + + return true; + } + +void Door::LinkDoors + ( + Event *ev + ) + + { + Door *ent; + Door *next; + Vector cmins; + Vector cmaxs; + int t; + int i; + + if ( nextdoor ) + { + // already linked by another door + return; + } + + // master doors own themselves + master = this; + + if ( spawnflags & DOOR_DONT_LINK ) + { + // don't want to link this door + nextdoor = entnum; + return; + } + + cmins = absmin; + cmaxs = absmax; + + ent = this; + for( t = entnum; t != 0; t = G_FindClass( t, getClassID() ) ) + { + next = ( Door * )G_GetEntity( t ); + if ( !ent->DoorTouches( next ) ) + { + continue; + } + + if ( next->nextdoor ) + { + error( "cross connected doors. Targetname = %s entity %d", TargetName(), entnum ); + } + + ent->nextdoor = next->entnum; + ent = next; + + for( i = 0; i < 3; i++ ) + { + if ( ent->absmin[ i ] < cmins[ i ] ) + { + cmins[ i ] = ent->absmin[ i ]; + } + if ( ent->absmax[ i ] > cmaxs[ i ] ) + { + cmaxs[ i ] = ent->absmax[ i ]; + } + } + + // set master door + ent->master = this; + + if ( ent->health ) + { + health = ent->health; + } + + if ( ent->Targeted() ) + { + if ( !Targeted() ) + { + SetTargetName( ent->TargetName() ); + } + else if ( strcmp( TargetName(), ent->TargetName() ) ) + { + // not a critical error, but let them know about it. + gi.dprintf( "cross connected doors" ); + + ent->SetTargetName( TargetName() ); + } + } + + if ( ent->Message().length() ) + { + if ( Message().length() && !strcmp( Message().c_str(), ent->Message().c_str() ) ) + { + // not a critical error, but let them know about it. + gi.dprintf( "Different messages on linked doors. Targetname = %s", TargetName() ); + } + + // only master should have a message + SetMessage( ent->Message().c_str() ); + ent->SetMessage( NULL ); + } + } + + // make the chain a loop + ent->nextdoor = entnum; + + // open up any portals we control + SetAreaPortals( Target(), ( spawnflags & DOOR_START_OPEN ) ? true : false ); + + // shootable or targeted doors don't need a trigger + if ( health || ( spawnflags & DOOR_TARGETED ) ) + { + // Don't let the player trigger the door + return; + } + + // Don't spawn trigger field when set to toggle + if ( !( spawnflags & DOOR_TOGGLE ) ) + { + SpawnTriggerField( cmins, cmaxs ); + } + } + +void Door::SetTime + ( + Event *ev + ) + + { + traveltime = ev->GetFloat( 1 ); + if ( traveltime < FRAMETIME ) + { + traveltime = FRAMETIME; + } + + speed = 1.0f / traveltime; + } + +void Door::LockDoor + ( + Event *ev + ) + + { + locked = true; + } + +void Door::UnlockDoor + ( + Event *ev + ) + + { + locked = false; + } + +/*****************************************************************************/ +/*SINED func_rotatingdoor (0 .5 .8) ? START_OPEN OPEN_DIRECTION DOOR_DONT_LINK NOT_PLAYERS NOT_MONSTERS TOGGLE AUTO_OPEN TARGETED +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. +DOOR_DONT_LINK is for when you have two doors that are touching but you want to operate independently. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). +OPEN_DIRECTION indicates which direction to open when START_OPEN is set. +AUTO_OPEN causes the door to open when a player is near instead of waiting for the player to use the door. +TARGETED door is only operational from triggers or script + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"openangle" how wide to open the door +"angle" determines the opening direction. point toward the middle of the door (away from the hinge) +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"time" move time (0.3 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (0 default) +"key" The item needed to open this door (default nothing) + +"sound_stop" Specify the sound that plays when the door stops moving (default global door_stop) +"sound_move" Specify the sound that plays when the door opens or closes (default global door_moving) +"sound_message" Specify the sound that plays when the door displays a message +"sound_locked" Specify the sound that plays when the door is locked + +/*****************************************************************************/ +CLASS_DECLARATION( Door, RotatingDoor, "func_rotatingdoor" ); + +ResponseDef RotatingDoor::Responses[] = + { + { &EV_Door_DoClose, ( Response )RotatingDoor::DoClose }, + { &EV_Door_DoOpen, ( Response )RotatingDoor::DoOpen }, + { NULL, NULL } + }; + +void RotatingDoor::DoOpen + ( + Event *ev + ) + + { + Vector ang; + + if ( previous_state == STATE_CLOSED ) + { + if ( ev->NumArgs() > 0 ) + { + Entity *other; + Vector p; + + other = ev->GetEntity( 1 ); + p = other->worldorigin - worldorigin; + p.z = 0; + diropened = dir * p; + } + else + { + diropened = 0 - init_door_direction; + } + } + + if ( diropened < 0 ) + { + ang = startangle + Vector( 0, angle, 0 ); + } + else + { + ang = startangle - Vector( 0, angle, 0 ); + } + + MoveTo( worldorigin, ang, fabs( speed*angle ), EV_Door_OpenEnd ); + } + +void RotatingDoor::DoClose + ( + Event *ev + ) + + { + MoveTo( worldorigin, startangle, fabs( speed*angle ), EV_Door_CloseEnd ); + } + +RotatingDoor::RotatingDoor() + { + startangle = angles; + + angle = G_GetFloatArg( "openangle", 90 ); + + init_door_direction = (spawnflags & DOOR_OPEN_DIRECTION); + } + +/* +/*****************************************************************************/ +/*SINED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK NOT_PLAYERS NOT_MONSTERS TOGGLE AUTO_OPEN TARGETED +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. +DOOR_DONT_LINK is for when you have two doors that are touching but you want to operate independently. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). +OPEN_DIRECTION indicates which direction to open when START_OPEN is set. +AUTO_OPEN causes the door to open when a player is near instead of waiting for the player to use the door. +TARGETED door is only operational from triggers or script + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction. point toward the middle of the door (away from the hinge) +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" move speed (100 default) +"time" move time (1/speed default, overides speed) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (0 default) +"key" The item needed to open this door (default nothing) + +"sound_stop" Specify the sound that plays when the door stops moving (default global door_stop) +"sound_move" Specify the sound that plays when the door opens or closes (default global door_moving) +"sound_message" Specify the sound that plays when the door displays a message +"sound_locked" Specify the sound that plays when the door is locked + +/*****************************************************************************/ +CLASS_DECLARATION( Door, SlidingDoor, "func_door" ); + +ResponseDef SlidingDoor::Responses[] = + { + { &EV_Door_DoClose, ( Response )SlidingDoor::DoClose }, + { &EV_Door_DoOpen, ( Response )SlidingDoor::DoOpen }, + { NULL, NULL } + }; + +void SlidingDoor::DoOpen + ( + Event *ev + ) + + { + MoveTo( pos2, angles, speed*totalmove, EV_Door_OpenEnd ); + } + +void SlidingDoor::DoClose + ( + Event *ev + ) + + { + MoveTo( pos1, angles, speed*totalmove, EV_Door_CloseEnd ); + } + +SlidingDoor::SlidingDoor() + { + Vector movedir; + float sp; + + lip = G_GetFloatArg( "lip", 8 ); + + movedir = G_GetMovedir(); + totalmove = fabs( movedir * size ) - lip; + pos1 = worldorigin; + pos2 = pos1 + movedir * totalmove; + setOrigin( pos1 ); + + sp = G_GetFloatArg( "speed", 0 ); + if (sp) + { + speed = sp / totalmove; + } + } + +/* +/*****************************************************************************/ +/*SINED func_scriptdoor (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK NOT_PLAYERS NOT_MONSTERS TOGGLE AUTO_OPEN TARGETED +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. +DOOR_DONT_LINK is for when you have two doors that are touching but you want to operate independently. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). +OPEN_DIRECTION indicates which direction to open when START_OPEN is set. +AUTO_OPEN causes the door to open when a player is near instead of waiting for the player to use the door. +TARGETED door is only operational from triggers or script + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction. point toward the middle of the door (away from the hinge) +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" move speed (100 default) +"time" move time (1/speed default, overides speed) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (0 default) +"key" The item needed to open this door (default nothing) +"initthread" code to execute to setup the door (optional) +"openthread" code to execute when opening the door (required) +"closethread" code to execute when closing the door (required) + +"sound_stop" Specify the sound that plays when the door stops moving (default global door_stop) +"sound_move" Specify the sound that plays when the door opens or closes (default global door_moving) +"sound_message" Specify the sound that plays when the door displays a message +"sound_locked" Specify the sound that plays when the door is locked + +/*****************************************************************************/ +CLASS_DECLARATION( Door, ScriptDoor, "func_scriptdoor" ); + +Event EV_ScriptDoor_DoInit( "doinit" ); +Event EV_ScriptDoor_SetOpenThread( "openthread" ); +Event EV_ScriptDoor_SetCloseThread( "closethread" ); + +ResponseDef ScriptDoor::Responses[] = + { + { &EV_ScriptDoor_DoInit, ( Response )ScriptDoor::DoInit }, + { &EV_Door_DoClose, ( Response )ScriptDoor::DoClose }, + { &EV_Door_DoOpen, ( Response )ScriptDoor::DoOpen }, + { &EV_ScriptDoor_SetOpenThread, ( Response )ScriptDoor::SetOpenThread }, + { &EV_ScriptDoor_SetCloseThread, ( Response )ScriptDoor::SetCloseThread }, + { NULL, NULL } + }; + +void ScriptDoor::SetOpenThread + ( + Event *ev + ) + { + openthreadname = ev->GetString( 1 ); + } + +void ScriptDoor::SetCloseThread + ( + Event *ev + ) + { + closethreadname = ev->GetString( 1 ); + } + +void ScriptDoor::DoInit + ( + Event *ev + ) + { + const char * label = NULL; + GameScript * s; + const char * tname; + + s = ScriptLib.GetScript( ScriptLib.GetGameScript() ); + + if ( !s ) + { + warning( "DoInit", "Null game script" ); + return; + } + + if ( initthreadname.length() ) + label = initthreadname.c_str(); + + doorthread = Director.CreateThread( s, label, MODEL_SCRIPT ); + if ( !doorthread ) + { + warning( "DoInit", "Could not allocate thread." ); + return; + } + doorthread->Vars()->SetVariable( "self", this ); + tname = TargetName(); + if ( tname && tname[ 0 ] ) + { + str name; + name = "$" + str( tname ); + doorthread->Vars()->SetVariable( "targetname", name.c_str() ); + } + doorthread->Vars()->SetVariable( "startorigin", startorigin ); + doorthread->Vars()->SetVariable( "startangles", startangle ); + doorthread->Vars()->SetVariable( "movedir", movedir ); + doorthread->Vars()->SetVariable( "doorsize", doorsize ); + if ( initthreadname.length() ) + { + // start right away + doorthread->Start( -1 ); + } + } + +void ScriptDoor::DoOpen + ( + Event *ev + ) + { + if ( !doorthread ) + { + warning( "DoOpen", "No Thread allocated." ); + return; + } + else + { + if ( !doorthread->Goto( openthreadname.c_str() ) ) + { + warning( "DoOpen", "Could not goto %s", openthreadname.c_str() ); + return; + } + } + + if ( previous_state == STATE_CLOSED ) + { + diropened = 0; + if ( ev->NumArgs() > 0 ) + { + Entity *other; + Vector p; + + other = ev->GetEntity( 1 ); + p = other->worldorigin - worldorigin; + p.z = 0; + diropened = dir * p; + } + } + doorthread->Vars()->SetVariable( "origin", worldorigin ); + doorthread->Vars()->SetVariable( "opendot", diropened ); + doorthread->Start( 0 ); + } + +void ScriptDoor::DoClose + ( + Event *ev + ) + { + if ( !doorthread ) + { + warning( "DoClose", "No Thread allocated." ); + return; + } + else + { + if ( !doorthread->Goto( closethreadname.c_str() ) ) + { + warning( "DoOpen", "Could not goto %s", closethreadname.c_str() ); + } + } + doorthread->Vars()->SetVariable( "origin", worldorigin ); + doorthread->Start( 0 ); + } + +ScriptDoor::ScriptDoor() + { + const char * text; + + startangle = angles; + // + // see if we have an initthread + // + text = G_GetSpawnArg( "initthread" ); + if ( text ) + initthreadname = text; + + // + // see if we have an openthread + // + text = G_GetSpawnArg( "openthread" ); + if ( text ) + openthreadname = text; + else + warning("ScriptDoor","No openthread defined for door at %.2f %.2f %.2f", origin[0], origin[1], origin[2] ); + + // + // see if we have an closethread + // + text = G_GetSpawnArg( "closethread" ); + if ( text ) + closethreadname = text; + else + warning("ScriptDoor","No closethread defined for door at %.2f %.2f %.2f", origin[0], origin[1], origin[2] ); + // + // clear out the sounds if necessary + // scripted doors typically have their own sounds + // + text = G_GetSpawnArg( "sound_stop" ); + if ( !text ) + sound_stop = ""; + text = G_GetSpawnArg( "sound_move" ); + if ( !text ) + sound_move = ""; + + movedir = G_GetMovedir(); + startorigin = worldorigin; + doorsize = fabs( movedir * size ); + PostEvent( EV_ScriptDoor_DoInit, 0 ); + } diff --git a/doors.h b/doors.h new file mode 100644 index 0000000..aa82762 --- /dev/null +++ b/doors.h @@ -0,0 +1,368 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/doors.h $ +// $Revision:: 26 $ +// $Author:: Jimdose $ +// $Date:: 11/08/98 10:50p $ +// +// 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/doors.h $ +// +// 26 11/08/98 10:50p Jimdose +// reordered archived variables so that they match the order in the struct +// +// 25 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 24 10/23/98 11:10p Jimdose +// removed trigger_finished +// +// 23 10/21/98 10:58p Jimdose +// fixed (hopefully) most of the door bugs +// +// 22 9/29/98 5:58p Markd +// put in archive and unarchive +// +// 21 9/03/98 9:07p Jimdose +// Added CanBeOpenedBy so that the AI can check if a door is useable by an +// actor +// changed owner to master +// Fixed bug with doors that don't stop +// opened doors can be used again to close them +// fixed door blocked +// +// 20 8/28/98 4:14p Markd +// Added sound_locked to doors +// +// 19 8/24/98 6:10p Markd +// removed dmg from doors +// +// 18 8/21/98 3:48p Markd +// Added openthread and closethread events +// +// 17 8/21/98 1:43a Markd +// Added full ScriptDoor functionality +// +// 16 7/26/98 3:40p Jimdose +// made locked public +// +// 15 7/10/98 2:09p Markd +// Added locking capability to doors +// +// 14 6/25/98 8:48p Markd +// Rewrote Item class, added keyed items to triggers, cleaned up item system +// +// 13 6/24/98 8:45p Markd +// Rewrote Door Code +// +// 12 5/13/98 4:54p Jimdose +// now uses SafePtrs +// +// 11 4/30/98 9:24p Jimdose +// Changed use of string to str class +// +// 10 3/29/98 9:38p Jimdose +// Changed killed to an event +// +// 9 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 8 3/11/98 2:25p Jimdose +// Updated to work with areaportals +// +// 7 3/07/98 5:06p Jimdose +// Converted to Quake2 +// +// 5 12/06/97 4:50p Markd +// Added interpretCommands. +// Added GetArgs as commands for future processing +// Removed dmg,attentuatioin and volume, moved these to Trigger +// +// 4 10/27/97 2:50p Jimdose +// Removed dependency on quakedef.h +// +// 3 10/27/97 2:38p Jimdose +// Changed nextdoor from a Door * into an int. +// +// 2 9/26/97 5:23p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Doors are environment objects that slide open when activated by triggers +// or when used by the player. +// + +#ifndef __DOORS_H__ +#define __DOORS_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "scriptslave.h" + +extern Event EV_Door_TryOpen; +extern Event EV_Door_GoDown; +extern Event EV_Door_GoUp; +extern Event EV_Door_HitBottom; +extern Event EV_Door_HitTop; +extern Event EV_Door_Fire; +extern Event EV_Door_Link; +extern Event EV_Door_SetSpeed; +extern Event EV_Door_Lock; +extern Event EV_Door_Unlock; + +class Door; +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL SafePtr; +#endif +typedef SafePtr DoorPtr; + +class EXPORT_FROM_DLL Door : public ScriptSlave + { + protected: + str sound_stop; + str sound_move; + str sound_message; + str sound_locked; + float lastblocktime; + float angle; + Vector dir; + Vector doormin; + Vector doormax; + float diropened; + int state; + int previous_state; + int trigger; + int nextdoor; + DoorPtr master; + + void OpenEnd( Event *ev ); + void CloseEnd( Event *ev ); + void Close( Event *ev ); + void Open( Event *ev ); + void DoorUse( Event *ev ); + void DoorFire( Event *ev ); + void DoorBlocked( Event *ev ); + void FieldTouched( Event *ev ); + void TryOpen( Event *ev ); + void SpawnTriggerField( Vector fmins, Vector fmaxs ); + qboolean DoorTouches( Door *e1 ); + void LinkDoors( Event *ev ); + void SetTime( Event *ev ); + void LockDoor( Event *ev ); + void UnlockDoor( Event *ev ); + + public: + CLASS_PROTOTYPE( Door ); + + qboolean locked; + + Door(); + qboolean isOpen( void ); + qboolean CanBeOpenedBy( Entity *ent ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Door::Archive + ( + Archiver &arc + ) + { + ScriptSlave::Archive( arc ); + + arc.WriteString( sound_stop ); + arc.WriteString( sound_move ); + arc.WriteString( sound_message ); + arc.WriteString( sound_locked ); + arc.WriteFloat( lastblocktime ); + arc.WriteFloat( angle ); + arc.WriteVector( dir ); + arc.WriteVector( doormin ); + arc.WriteVector( doormax ); + arc.WriteFloat( diropened ); + arc.WriteInteger( state ); + arc.WriteInteger( previous_state ); + arc.WriteInteger( trigger ); + arc.WriteInteger( nextdoor ); + arc.WriteSafePointer( master ); + arc.WriteBoolean( locked ); + } + +inline EXPORT_FROM_DLL void Door::Unarchive + ( + Archiver &arc + ) + { + ScriptSlave::Unarchive( arc ); + + arc.ReadString( &sound_stop ); + arc.ReadString( &sound_move ); + arc.ReadString( &sound_message ); + arc.ReadString( &sound_locked ); + arc.ReadFloat( &lastblocktime ); + arc.ReadFloat( &angle ); + arc.ReadVector( &dir ); + arc.ReadVector( &doormin ); + arc.ReadVector( &doormax ); + arc.ReadFloat( &diropened ); + arc.ReadInteger( &state ); + arc.ReadInteger( &previous_state ); + arc.ReadInteger( &trigger ); + arc.ReadInteger( &nextdoor ); + arc.ReadSafePointer( &master ); + arc.ReadBoolean( &locked ); + } + + +class EXPORT_FROM_DLL SlidingDoor : public Door + { + protected: + float totalmove; + float lip; + Vector pos1; + Vector pos2; + + public: + CLASS_PROTOTYPE( SlidingDoor ); + + void DoOpen( Event *ev ); + void DoClose( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + + SlidingDoor(); + }; + +inline EXPORT_FROM_DLL void SlidingDoor::Archive + ( + Archiver &arc + ) + { + Door::Archive( arc ); + + arc.WriteFloat( totalmove ); + arc.WriteFloat( lip ); + arc.WriteVector( pos1 ); + arc.WriteVector( pos2 ); + } + +inline EXPORT_FROM_DLL void SlidingDoor::Unarchive + ( + Archiver &arc + ) + { + Door::Unarchive( arc ); + + arc.ReadFloat( &totalmove ); + arc.ReadFloat( &lip ); + arc.ReadVector( &pos1 ); + arc.ReadVector( &pos2 ); + } + +class EXPORT_FROM_DLL RotatingDoor : public Door + { + protected: + float angle; + Vector startangle; + int init_door_direction; + + public: + CLASS_PROTOTYPE( RotatingDoor ); + + void DoOpen( Event *ev ); + void DoClose( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + + RotatingDoor(); + }; + +inline EXPORT_FROM_DLL void RotatingDoor::Archive + ( + Archiver &arc + ) + { + Door::Archive( arc ); + + arc.WriteFloat( angle ); + arc.WriteVector( startangle ); + arc.WriteInteger( init_door_direction ); + } + +inline EXPORT_FROM_DLL void RotatingDoor::Unarchive + ( + Archiver &arc + ) + { + Door::Unarchive( arc ); + + arc.ReadFloat( &angle ); + arc.ReadVector( &startangle ); + arc.ReadInteger( &init_door_direction ); + } + +class EXPORT_FROM_DLL ScriptDoor : public Door + { + protected: + ThreadPtr doorthread; + str initthreadname; + str openthreadname; + str closethreadname; + float doorsize; + Vector startangle; + Vector startorigin; + Vector movedir; + + public: + CLASS_PROTOTYPE( ScriptDoor ); + + void DoInit( Event *ev ); + void DoOpen( Event *ev ); + void DoClose( Event *ev ); + void SetOpenThread( Event *ev ); + void SetCloseThread( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + ScriptDoor(); + }; + +inline EXPORT_FROM_DLL void ScriptDoor::Archive + ( + Archiver &arc + ) + { + Door::Archive( arc ); + + arc.WriteSafePointer( doorthread ); + arc.WriteString( initthreadname ); + arc.WriteString( openthreadname ); + arc.WriteString( closethreadname ); + arc.WriteFloat( doorsize ); + arc.WriteVector( startangle ); + arc.WriteVector( startorigin ); + arc.WriteVector( movedir ); + } + +inline EXPORT_FROM_DLL void ScriptDoor::Unarchive + ( + Archiver &arc + ) + { + Door::Unarchive( arc ); + + arc.ReadSafePointer( &doorthread ); + arc.ReadString( &initthreadname ); + arc.ReadString( &openthreadname ); + arc.ReadString( &closethreadname ); + arc.ReadFloat( &doorsize ); + arc.ReadVector( &startangle ); + arc.ReadVector( &startorigin ); + arc.ReadVector( &movedir ); + } +#endif /* doors.h */ diff --git a/earthquake.cpp b/earthquake.cpp new file mode 100644 index 0000000..320b28a --- /dev/null +++ b/earthquake.cpp @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/earthquake.cpp $ +// $Revision:: 13 $ +// $Author:: Jimdose $ +// $Date:: 11/08/98 10:47p $ +// +// 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/earthquake.cpp $ +// +// 13 11/08/98 10:47p Jimdose +// moved earthquake to level struct +// +// 12 10/22/98 5:56p Markd +// Made a bunch of global sounds local to that entity +// +// 11 8/18/98 11:08p Markd +// Added new Alias System +// +// 10 7/30/98 4:34p Aldie +// Don't respond to touch +// +// 9 5/24/98 8:46p Jimdose +// Made a lot of functions more str-friendly. +// Got rid of a lot of char * based strings +// Cleaned up get spawn arg functions and sound functions +// sound functions now use consistant syntax +// +// 8 5/22/98 12:35p Aldie +// Removed some unused functions +// +// 7 5/22/98 12:19p Aldie +// Updated color of earthquake in SinEd +// +// 6 5/21/98 10:58a Aldie +// Removed a printf +// +// 5 5/20/98 10:21p Aldie +// Updated earthquake to new event system, may want to add radius later. +// +// 3 12/06/97 4:50p Markd +// Added interpretCommands. +// Added GetArgs as commands for future processing +// +// 2 10/27/97 7:32p Jimdose +// Created file +// +// DESCRIPTION: +// Earthquake trigger causes a localized earthquake when triggered. +// The earthquake effect is visible to the user as the shaking of his screen. +// +#include "earthquake.h" + +/*****************************************************************************/ +/*SINED func_earthquake (.5 .5 .8) (-8 -8 -8) (8 8 8) + Causes an earthquake +"duration" is the duration of the earthquake. Default is 0.8 seconds. +/*****************************************************************************/ + +CLASS_DECLARATION( Trigger, Earthquake, "func_earthquake" ) + +Event EV_Earthquake_Deactivate( "earthquake_deactivate" ); + +ResponseDef Earthquake::Responses[] = + { + { &EV_Touch, NULL }, + { &EV_Trigger_Effect, ( Response )Earthquake::Activate }, + { &EV_Earthquake_Deactivate, ( Response )Earthquake::Deactivate }, + { NULL, NULL } + }; + +Earthquake::Earthquake + ( + void + ) + + { + const char * name; + + duration = G_GetFloatArg( "duration", 0.8f ); + quakeactive = false; + + // cache in the quake sound + name = gi.GlobalAlias_FindRandom( "earthquake" ); + gi.soundindex( name ); + } + +EXPORT_FROM_DLL void Earthquake::Activate + ( + Event *ev + ) + + { + float newtime; + Event *event; + + newtime = duration + level.time; + if ( newtime > level.earthquake ) + { + level.earthquake = newtime; + } + quakeactive = true; + RandomGlobalSound( "earthquake", 1, CHAN_VOICE|CHAN_NO_PHS_ADD, ATTN_NONE ); + event = new Event(EV_Earthquake_Deactivate); + PostEvent(event,duration); + }; + +EXPORT_FROM_DLL void Earthquake::Deactivate + ( + Event *ev + ) + + { + quakeactive = false; + level.earthquake = 0; + RandomGlobalSound( "null_sound", 1, CHAN_VOICE|CHAN_NO_PHS_ADD, ATTN_NORM ); + } diff --git a/earthquake.h b/earthquake.h new file mode 100644 index 0000000..4d2aaab --- /dev/null +++ b/earthquake.h @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/earthquake.h $ +// $Revision:: 8 $ +// $Author:: Jimdose $ +// $Date:: 11/08/98 10:47p $ +// +// 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/earthquake.h $ +// +// 8 11/08/98 10:47p Jimdose +// moved earthquake to level struct +// +// 7 9/29/98 5:58p Markd +// put in archive and unarchive +// +// 6 5/22/98 12:35p Aldie +// Removed some unused functions +// +// 5 5/20/98 10:22p Aldie +// Converted earthquake to new event system. +// +// 3 12/06/97 4:49p Markd +// Moved duration to Trigger +// Added interpretCommand +// +// 2 10/27/97 7:32p Jimdose +// Created file +// +// DESCRIPTION: +// Earthquake trigger causes a localized earthquake when triggered. +// The earthquake effect is visible to the user as the shaking of his screen. +// + +#ifndef __EARTHQUAKE_H__ +#define __EARTHQUAKE_H__ + +#include "g_local.h" +#include "trigger.h" + +#define EARTHQUAKE_STRENGTH 100 + +class EXPORT_FROM_DLL Earthquake : public Trigger + { + protected: + qboolean quakeactive; + float duration; + + public: + CLASS_PROTOTYPE( Earthquake ) + Earthquake(); + void Activate(Event *ev); + void Deactivate(Event *ev); + qboolean EarthquakeActive() {return quakeactive;}; + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Earthquake::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.WriteBoolean( quakeactive ); + arc.WriteFloat( duration ); + } + +inline EXPORT_FROM_DLL void Earthquake::Unarchive + ( + Archiver &arc + ) + { + Trigger::Unarchive( arc ); + + arc.ReadBoolean( &quakeactive ); + arc.ReadFloat( &duration ); + } + +#endif /* Earthquake.h */ diff --git a/entity.cpp b/entity.cpp new file mode 100644 index 0000000..fed9f14 --- /dev/null +++ b/entity.cpp @@ -0,0 +1,5139 @@ +//----------------------------------------------------------------------------- +// +// $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 ); + } diff --git a/entity.h b/entity.h new file mode 100644 index 0000000..c0a2c93 --- /dev/null +++ b/entity.h @@ -0,0 +1,1662 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/entity.h $ +// $Revision:: 157 $ +// $Author:: Markd $ +// $Date:: 11/15/98 11:33p $ +// +// 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.h $ +// +// 157 11/15/98 11:33p Markd +// added fat projectile flag +// +// 156 11/13/98 2:35a Aldie +// Added mutant drain MOD +// +// 155 11/08/98 10:50p Jimdose +// changed how archive wrote groundentity entity number +// +// 154 10/27/98 3:51a Jimdose +// added FL_NOION +// +// 153 10/26/98 4:30p Aldie +// Added Ghost command +// +// 152 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 151 10/25/98 4:37a Aldie +// Moved link() +// +// 150 10/25/98 12:01a Markd +// put in censored support +// +// 149 10/24/98 7:15p Jimdose +// archive wasn't saving orientation +// +// 148 10/23/98 5:39a Jimdose +// Added SetMassEvent +// +// 147 10/22/98 1:40a Markd +// Added stealth mode +// +// 146 10/20/98 11:30p Markd +// Increased ranges on BroadcastSounds +// +// 145 10/20/98 3:30a Jimdose +// Added isBoundTo +// +// 144 10/20/98 12:44a Markd +// Made setSize virtual +// +// 143 10/18/98 8:44p Jimdose +// Added GetEntName +// +// 142 10/17/98 11:02p Markd +// Added ifskill +// +// 141 10/17/98 8:11p Jimdose +// Changed Damage to DamgeEvent +// +// 140 10/16/98 1:42a Jimdose +// Added FL_DONTSAVE +// +// 139 10/15/98 3:39p Markd +// Added FL_FORCEFIELD +// +// 138 10/13/98 11:14p Markd +// Added hurt and mutate events +// +// 137 10/13/98 5:25p Markd +// Added UseBoundingBoxEvent +// +// 136 10/11/98 8:50p Jimdose +// Added RandomGlobalEntitySound and RandomGlobalEntitySoundEvent +// +// 135 10/11/98 7:41p Aldie +// Mutate and restore commands for Richard +// +// 134 10/11/98 5:34p Aldie +// Added MOD_MUTANTHANDS +// +// 133 10/10/98 9:13p Markd +// Took out SetAliasPrefix +// +// 132 10/10/98 9:13p Aldie +// Added SPIDERSPLASH +// +// 131 10/10/98 3:35a Jimdose +// changed team to moveteam +// +// 130 10/10/98 1:32a Jimdose +// moved edict archiving out of entity archive functions +// no longer call SetOrigin during unarchiving since it needs parent and +// bindmaster pointers. Instead, edicts are now fully unarchived. +// +// 129 10/09/98 8:59p Aldie +// Moved air_finished to player +// +// 128 10/09/98 4:33p Aldie +// Add MOD_FRIENDLY_FIRE +// +// 127 10/08/98 7:39p Aldie +// Added lightoffset +// +// 126 10/08/98 7:25p Aldie +// minlight, gravity, lightoffset +// +// 125 10/07/98 11:45p Jimdose +// Added DistanceTo and WithinDistance for vectors +// Added SetDeltaAngles +// +// 124 10/06/98 10:50p Aldie +// Created an oxygenator +// +// 123 10/06/98 9:39p Markd +// removed last_origin +// +// 122 10/05/98 11:23p Markd +// Moved all SOUND_RADIUSES to header +// +// 121 10/05/98 10:37p Aldie +// Added FL_SILENCER +// +// 120 10/04/98 10:28p Aldie +// Added multiple weapon changes. Damage, flashes, quantum stuff +// +// 119 10/03/98 1:12p Aldie +// Added new pulse effects +// +// 118 10/02/98 11:27p Jimdose +// Added SetEntNum +// +// 117 9/28/98 9:12p Markd +// Put in archive and unarchive functions +// +// 116 9/28/98 4:07p Aldie +// Added oxygen powerup +// +// 115 9/26/98 4:46p Aldie +// Added mutant mode +// +// 114 9/23/98 10:07p Aldie +// Added ION_DESTRUCT to MOD +// +// 113 9/22/98 2:59p Aldie +// Added effects command +// +// 112 9/15/98 6:37p Markd +// Added RotatedBounds flag support +// +// 111 9/13/98 4:35p Aldie +// Changed MOD_LASERBEAM to MOD_LASER +// +// 110 9/12/98 11:11p Aldie +// Added some more MOD +// +// 109 9/11/98 4:24p Aldie +// Added a couple more means of death +// +// 108 9/09/98 6:45p Markd +// put in world weapon model animations +// +// 107 9/08/98 11:30p Jimdose +// Added AnimEvent +// +// 106 9/02/98 11:08a Markd +// Put in setModel into Sentient so that weapon could be properly detached and +// re-attached again. +// +// 105 8/31/98 7:45p Aldie +// Updated surface data structure and removed surfinfo field +// +// 104 8/31/98 5:45p Aldie +// Added FL_CLOAK +// +// 103 8/29/98 9:49p Jimdose +// moved #defines and enum defines from g_local.h +// +// 102 8/28/98 3:46p Markd +// Added centroid to edict_s +// +// 101 8/27/98 9:04p Jimdose +// Moved a lot of small functions to the header as inline +// Made Centroid a variable +// +// 100 8/24/98 6:50p Jimdose +// Added SetGravityAxis +// +// 99 8/22/98 8:55p Jimdose +// Added support for alternate gravity axis +// +// 98 8/18/98 11:08p Markd +// Added new Alias System +// +// 97 8/18/98 11:12a Markd +// Added "skin" event +// +// 96 8/08/98 8:18p Markd +// Made max_health a float +// +// 95 8/08/98 7:51p Jimdose +// Made definition of world into include of worldspawn.h +// +// 94 7/31/98 8:10p Jimdose +// Script commands now include flags to indicate cheats and console commands +// +// 93 7/29/98 2:32p Aldie +// Changed health to a float +// +// 92 7/25/98 3:58p Markd +// Added EV_GotKill +// +// 91 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 +// +// 90 7/21/98 9:34p Jimdose +// Added AliasExists and PrefixAliasExists +// +// 89 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 88 7/20/98 5:08p Aldie +// Added explicit processinitcommands +// +// 87 7/18/98 11:15p Markd +// Added takedamage and nodamage +// +// 86 7/18/98 4:02p Markd +// Added attach, detach, attachmodel events +// +// 85 7/17/98 4:04p Markd +// Added HasAnim to entity.cpp +// +// 84 7/15/98 11:23p Markd +// Added processinitcommands stuff +// +// 83 7/14/98 11:35p Markd +// Added PHSSound and RandomPHSSound +// +// 82 7/14/98 3:54p Markd +// Added last_animation_time +// +// 81 7/13/98 5:01p Aldie +// Added dead player bodies with gibbing +// +// 80 7/11/98 2:49p Markd +// Added dialog event +// +// 79 7/11/98 2:25p Markd +// removed dialog event +// +// 78 7/10/98 11:11p Markd +// Added dialog event +// +// 77 7/09/98 9:35p Jimdose +// Added getParentVector +// +// 76 7/08/98 12:58p Jimdose +// Added classname event +// +// 75 6/24/98 12:23p Markd +// Added shatter_percentage +// +// 74 6/19/98 4:45p Jimdose +// Added Centroid, DistanceTo, and WithinDistance +// +// 73 6/19/98 10:56a Markd +// re-ordered tesselation event +// +// 72 6/18/98 2:00p Markd +// rewrote tesselation code +// +// 71 6/10/98 5:10p Markd +// Added ExpandAlias +// +// 70 6/10/98 2:10p Aldie +// Updated damage function. +// +// 69 6/08/98 4:58p Markd +// Added GroupModelEvent +// +// 68 6/05/98 6:27p Aldie +// Added location to Damage function. +// +// 67 5/26/98 10:53p Markd +// made sounds be SOUND_SYNCH by default +// +// 66 5/26/98 9:39p Markd +// removed damage regions +// +// 65 5/26/98 9:25p Aldie +// Added kill event +// +// 64 5/26/98 8:44p Markd +// Added damage_regions and DamageSkin Method +// +// 63 5/25/98 12:22p Aldie +// Inited waterlevel and water type +// +// 62 5/25/98 7:58p Markd +// Added RandomPositionedSound +// +// 61 5/25/98 6:47p Jimdose +// Made animateframe, prethink and posthink into functions built into the base +// entity class +// +// 60 5/25/98 4:43p Markd +// Added SpawnParticles +// +// 59 5/24/98 9:01p Jimdose +// Changed classname to a const char * +// +// 58 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 +// +// 57 5/24/98 1:05a Jimdose +// Added sound events for ai +// +// 56 5/20/98 11:12a Markd +// removed char * dependency +// +// 55 5/14/98 10:20p Jimdose +// world is now an EntityPtr +// +// 54 5/13/98 4:54p Jimdose +// now uses SafePtrs +// +// 53 5/11/98 8:07p Jimdose +// Added EntityPtr +// +// 52 5/11/98 5:53p Markd +// Added aliascache command +// +// 51 5/11/98 2:19p Markd +// Fixed randomsound stuff +// +// 50 5/08/98 2:57p Markd +// Added another RandomSound method +// +// 49 5/07/98 11:32p Markd +// Removed footstep command and event +// +// 48 5/04/98 8:32p Markd +// Removed cachemodel and cachesound +// +// 47 5/03/98 4:37p Jimdose +// removed oldorigin +// +// 46 5/02/98 8:45p Markd +// Added CacheModel, CacheSound and entityflags events +// +// 45 5/02/98 12:49a Jimdose +// added scale event +// +// 44 5/01/98 7:32p Jimdose +// Added groundplane, groundsurface, groundcontents +// +// 43 4/29/98 10:46p Markd +// added positioned_sound and random_sound +// +// 42 4/16/98 1:56p Jimdose +// Added EndAnimEvent and PrevAnimEvent +// +// 41 4/10/98 12:34a Jimdose +// got rid of damage_inflictor +// +// 40 4/09/98 3:30p Jimdose +// sound and stopsound are now virtual +// +// 39 4/07/98 8:00p Markd +// +// 38 4/05/98 10:43p Markd +// Added Tesselate +// +// 37 4/05/98 10:17p Jimdose +// added lastorigin +// +// 36 4/05/98 7:20p Aldie +// Added dyamic lights +// +// 35 4/05/98 1:59a Jimdose +// Added setmodelevent +// +// 34 4/04/98 7:28p Jimdose +// added HitSky for generic trace +// +// 33 4/04/98 6:14p Jimdose +// Added HitSky and RandomSound +// +// 32 4/02/98 4:52p Jimdose +// Added parameter to droptofloor +// Added animation control events +// +// 31 3/31/98 5:40p Markd +// Added StartAnimatingEvent +// +// 30 3/30/98 11:39p Markd +// Added modelIndex function +// +// 29 3/30/98 11:21p Markd +// Added setScale +// +// 28 3/30/98 11:16p Markd +// Added sound and random sound support +// +// 27 3/30/98 7:29p Markd +// Added Footstep method +// +// 26 3/29/98 9:38p Jimdose +// Changed Killed and Pain to events +// Added damage event +// +// 25 3/27/98 7:01p Markd +// Added vieworigin and viewangles +// +// 24 3/26/98 8:10p Jimdose +// Changed groundentity to an edict_t * +// Added GetBone +// +// 23 3/25/98 3:24p Markd +// Added model binding variables +// +// 22 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 21 3/18/98 7:19p Jimdose +// Added RandomAnimate +// Added animDoneEvent +// +// 20 3/11/98 11:30a Markd +// Added movement variable totaldelta +// +// 19 3/07/98 2:05p Markd +// Added stuff for Animation system +// +// 18 3/02/98 8:49p Jimdose +// Changed CLASS_PROTOTYPE to only take the classname +// +// 17 2/17/98 6:59p Jimdose +// no longer pass script into interpretCommand +// +// 16 2/16/98 2:05p Jimdose +// Added hierarchial object binding. +// Added joinTeam and quitTeam for object teams +// Added getLocalVector to aid in translating into the object's local +// coordinate system. +// Added orientation rotation matrix that is calculated each time setAngles is +// called, allowing us to get rid of a lot of calls to AngleVectors. +// +// 15 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. +// +// 14 2/03/98 10:57a Jimdose +// Updated to work with Quake 2 engine +// Moved initialization to constructor and removed Init function +// +// 12 12/12/97 4:27p Markd +// Added "soundprefix" +// +// 11 11/24/97 6:54p Markd +// Added Register Sound and Random Sound +// +// 10 11/15/97 6:53p Markd +// added ProcessNoteCommands, added RandomAnimate, added animloop_count and +// animloop_anim variables for animation loop tracking +// +// 9 11/15/97 2:48p Jimdose +// Added ProcessEvent member function +// +// 8 11/14/97 4:44p Jimdose +// Added PostEvent +// +// 7 11/12/97 5:13p Jimdose +// Created event definitions +// +// 6 10/29/97 4:18p Jimdose +// Added FadeOut. +// +// 5 10/28/97 4:13p Jimdose +// Added interpretCommand to make Entity be controllable by scripts via +// ScriptMaster. +// +// 4 9/29/97 6:18p Markd +// working on animate +// +// 3 9/26/97 6:14p 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). +// + +#ifndef __ENTITY_H__ +#define __ENTITY_H__ + +#include "g_local.h" +#include "class.h" +#include "vector.h" +#include "script.h" +#include "listener.h" + +typedef enum + { + DAMAGE_NO, + DAMAGE_YES, // will take damage if hit + DAMAGE_AIM // auto targeting recognizes this + } damage_t; + +//deadflag +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +// flags +#define FL_FLY 0x00000001 +#define FL_SWIM 0x00000002 // implied immunity to drowining +#define FL_INWATER 0x00000004 +#define FL_GODMODE 0x00000008 +#define FL_NOTARGET 0x00000010 +#define FL_PARTIALGROUND 0x00000020 // not all corners are valid +#define FL_FATPROJECTILE 0x00000040 // projectile should use fat trace +#define FL_TEAMSLAVE 0x00000080 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000100 +#define FL_PRETHINK 0x00000200 +#define FL_POSTTHINK 0x00000400 +#define FL_BLOOD 0x00000800 +#define FL_SPARKS 0x00001000 +#define FL_TESSELATE 0x00002000 +#define FL_BLASTMARK 0x00004000 +#define FL_DIE_TESSELATE 0x00008000 +#define FL_DARKEN 0x00010000 +#define FL_DIE_GIBS 0x00020000 +#define FL_SHIELDS 0x00040000 // sentient has reactive shields +#define FL_DIE_EXPLODE 0x00080000 // when it dies, it will explode +#define FL_ADRENALINE 0x00100000 // sentient is under adrenaline effects +#define FL_CLOAK 0x00200000 // sentient is cloaked +#define FL_ROTATEDBOUNDS 0x00400000 // model uses rotated mins and maxs +#define FL_MUTANT 0x00800000 // sentient is in mutant mode +#define FL_OXYGEN 0x01000000 // sentient has oxygen powerup +#define FL_SILENCER 0x02000000 // sentient has silencer +#define FL_SP_MUTANT 0x04000000 // mutant mode single player +#define FL_MUTATED 0x08000000 // keep track of mutation +#define FL_FORCEFIELD 0x10000000 // sentient has force field ( invulnerable ) +#define FL_DONTSAVE 0x20000000 // don't add to the savegame +#define FL_STEALTH 0x40000000 // character is in "stealth" mode +#define FL_NOION 0x80000000 // don't allow Ion tesselation + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_ENERGY 0x00000004 // damage is from an energy based weapon +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_BULLET 0x00000010 // damage is from a bullet (used for ricochets) +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect +#define DAMAGE_NO_SKILL 0x00000040 // damage is not affected by skill level + +// means of death flags + +typedef enum { + MOD_FISTS, + MOD_MAGNUM, + MOD_SHOTGUN, + MOD_ASSRIFLE, + MOD_CHAINGUN, + MOD_GRENADE, + MOD_ROCKET, + MOD_ROCKETSPLASH, + MOD_PULSE, + MOD_PULSELASER, + MOD_SPEARGUN, + MOD_SNIPER, + MOD_VEHICLE, + MOD_CRUSH, + MOD_SHOTROCKET, + MOD_FALLING, + MOD_DROWN, + MOD_SUICIDE, + MOD_EXPLODEWALL, + MOD_ELECTRIC, + MOD_TELEFRAG, + MOD_GENBULLET, + MOD_LASER, + MOD_BETTYSPIKE, + MOD_HELIGUN, + MOD_DEBRIS, + MOD_THROWNOBJECT, + MOD_LAVA, + MOD_SLIME, + MOD_ADRENALINE, + MOD_ION, + MOD_ION_DESTRUCT, + MOD_QUANTUM, + MOD_BEAM, + MOD_IMPACT, + MOD_FRIENDLY_FIRE, + MOD_SPIDERSPLASH, + MOD_MUTANTHANDS, + MOD_MUTANT_DRAIN + } mod_type_t; + +// +// Sound travel distances +// + +#define SOUND_BREAKING_RADIUS 500 +#define SOUND_WEAPON_RADIUS 800 +#define SOUND_MOVEMENT_RADIUS 256 +#define SOUND_PAIN_RADIUS 320 +#define SOUND_DEATH_RADIUS 800 +#define SOUND_DOOR_RADIUS 240 +#define SOUND_MUTANT_RADIUS 256 +#define SOUND_VOICE_RADIUS 800 +#define SOUND_MACHINE_RADIUS 512 +#define SOUND_RADIO_RADIUS 8192 + +extern Event EV_ClientConnect; +extern Event EV_ClientDisconnect; +extern Event EV_ClientKill; +extern Event EV_ClientMove; +extern Event EV_ClientEndFrame; + +// Generic entity events +extern Event EV_Classname; +extern Event EV_Activate; +extern Event EV_Use; +//extern Event EV_Footstep; +extern Event EV_FadeOut; +extern Event EV_Fade; +extern Event EV_Killed; +extern Event EV_GotKill; +extern Event EV_Pain; +extern Event EV_Damage; +extern Event EV_Gib; +extern Event EV_Mutate; + +// Physics events +extern Event EV_MoveDone; +extern Event EV_Touch; +extern Event EV_Blocked; +extern Event EV_Attach; +extern Event EV_AttachModel; +extern Event EV_Detach; +extern Event EV_UseBoundingBox; + +// Animation events +extern Event EV_NewAnim; +extern Event EV_LastFrame; +extern Event EV_TakeDamage; +extern Event EV_NoDamage; +extern Event EV_SetSkin; + +// script stuff +extern Event EV_Hide; +extern Event EV_Show; +extern Event EV_BecomeSolid; +extern Event EV_BecomeNonSolid; +extern Event EV_PlaySound; +extern Event EV_StopSound; +extern Event EV_GravityAxis; +extern Event EV_Bind; +extern Event EV_Unbind; +extern Event EV_JoinTeam; +extern Event EV_QuitTeam; +extern Event EV_SetHealth; +extern Event EV_SetSize; +extern Event EV_SetAlpha; +extern Event EV_SetOrigin; +extern Event EV_SetTargetName; +extern Event EV_SetTarget; +extern Event EV_SetKillTarget; +extern Event EV_SetAngles; +extern Event EV_RegisterAlias; +extern Event EV_RandomSound; +extern Event EV_EntitySound; +extern Event EV_RandomEntitySound; +extern Event EV_RandomGlobalEntitySound; +extern Event EV_StopEntitySound; +extern Event EV_Anim; +extern Event EV_StartAnimating; +extern Event EV_GroupModelEvent; +extern Event EV_DialogEvent; +extern Event EV_RandomPHSSound; +extern Event EV_PHSSound; +extern Event EV_ProcessInitCommands; +// dir is 1 +// power is 2 +// minsize is 3 +// maxsize is 4 +// percentage is 5 +// thickness 6 +// entity is 7 +// origin 8 +extern Event EV_Tesselate; +extern Event EV_Shatter_MinSize; +extern Event EV_Shatter_MaxSize; +extern Event EV_Shatter_Thickness; +extern Event EV_Shatter_Percentage; + +// AI sound events +extern Event EV_WeaponSound; +extern Event EV_MovementSound; +extern Event EV_PainSound; +extern Event EV_DeathSound; +extern Event EV_BreakingSound; +extern Event EV_DoorSound; +extern Event EV_MutantSound; +extern Event EV_VoiceSound; +extern Event EV_MachineSound; +extern Event EV_RadioSound; + +extern Event EV_HeardWeapon; +extern Event EV_HeardMovement; +extern Event EV_HeardPain; +extern Event EV_HeardDeath; +extern Event EV_HeardBreaking; +extern Event EV_HeardDoor; +extern Event EV_HeardMutant; +extern Event EV_HeardVoice; +extern Event EV_HeardMachine; +extern Event EV_HeardRadio; +extern Event EV_Hurt; +extern Event EV_IfSkill; + +// Define ScriptMaster +class ScriptMaster; + +// +// Spawn args +// +// "spawnflags" +// "alpha" default 1.0 +// "model" +// "origin" +// "targetname" +// "target" +// +#define MAX_MODEL_CHILDREN 8 + +class Entity; +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL SafePtr; +#endif +typedef SafePtr EntityPtr; + +class EXPORT_FROM_DLL Entity : public Listener + { + public: + CLASS_PROTOTYPE( Entity ); + + // spawning variables + int entnum; + edict_t *edict; + gclient_t *client; + const char *classname; + int spawnflags; + + // rendering variables + float translucence; + int viewheight; // height above origin where eyesight is determined + int light_level; // keeps track of light level at origin + + // Animation variables + str model; + int next_anim; // index of next_anim, if an anim change is pending, + // this value is non-negative + int next_frame; // index of next_frame, if a frame change is pending, + // this value is non-negative + int last_frame_in_anim;// last frame in the current animation + Vector frame_delta; // current movement from this frame + Vector total_delta; // total unprocessed movement + Vector next_anim_delta; // total delta of next animation + float next_anim_time; // total time of next animation + qboolean animating; // whether the model is currently animating + Event *animDoneEvent; + float last_animation_time; // the last server frame this model was animated + int num_frames_in_gun_anim; // num frames in the gun animation, if there is one + + // physics variables + Vector mins; + Vector maxs; + Vector absmin; + Vector absmax; + Vector size; + Vector centroid; + Vector origin; + Vector velocity; + Vector avelocity; + Vector angles; + Vector worldorigin; + Vector worldangles; + Vector vieworigin; + Vector viewangles; + int contents; + int movetype; + int mass; + float gravity; // per entity gravity multiplier (1.0 is normal) + int gravaxis; // per entity gravity axis + + edict_t *groundentity; + csurface_t *groundsurface; + cplane_t groundplane; + int groundcontents; + + int groundentity_linkcount; + + // Binding variables + Entity *bindmaster; + str moveteam; + Entity *teamchain; + Entity *teammaster; + float orientation[3][3]; + + // Model Binding variables + int numchildren; + int children[MAX_MODEL_CHILDREN]; + + // targeting variables + str target; + str targetname; + str killtarget; + + // Character state + float health; + float max_health; + int deadflag; + int flags; + + // underwater variables + int watertype; + int waterlevel; + + // Pain and damage variables + damage_t takedamage; + EntityPtr enemy; + float pain_finished; + float damage_debounce_time; + + // tesselation variables + int tess_min_size; + int tess_max_size; + int tess_thickness; + float tess_percentage; + + Entity::Entity(); + virtual Entity::~Entity(); + + void SetEntNum( int num ); + void GetEntName( Event *ev ); + + qboolean DistanceTo( Vector pos ); + qboolean DistanceTo( Entity *ent ); + qboolean WithinDistance( Vector pos, float dist ); + qboolean WithinDistance( Entity *ent, float dist ); + + const char *Target( void ); + void SetTarget( const char *target ); + qboolean Targeted( void ); + const char *TargetName( void ); + void SetTargetName( const char *target ); + void SetKillTarget( const char *killtarget ); + const char *KillTarget( void ); + + int modelIndex( const char * mdl ); + virtual void setModel( const char *model ); + virtual void setModel( str &mdl ); + void SetModelEvent( Event *ev ); + void hideModel( void ); + void EventHideModel( Event *ev ); + void showModel( void ); + void EventShowModel( Event *ev ); + qboolean hidden( void ); + void ProcessInitCommandsEvent( Event *ev ); + void ProcessInitCommands( int index ); + + void setAlpha( float alpha ); + float alpha( void ); + + void setMoveType( int type ); + int getMoveType( void ); + + void setSolidType( solid_t type ); + int getSolidType( void ); + + Vector getParentVector( Vector vec ); + Vector getLocalVector( Vector vec ); + + virtual void setSize( Vector min, Vector max ); + void setOrigin( Vector org ); + qboolean GetBone( const char *name, Vector *pos, Vector *forward, Vector *right, Vector *up ); + void setAngles( Vector ang ); + + void link( void ); + void unlink( void ); + + void setContents( int type ); + int getContents( void ); + void setScale( float scale ); + + qboolean droptofloor( float maxfall ); + qboolean isClient( void ); + + virtual void SetDeltaAngles( void ); + + virtual void DamageEvent( Event *event ); + virtual void Damage( Entity *inflictor, + Entity *attacker, + int damage, + Vector position, + Vector direction, + Vector normal, + int knockback, + int flags, + int meansofdeath, + int groupnum, + int trinum, + float damage_multiplier ); + + virtual qboolean CanDamage( Entity *target ); + + qboolean IsTouching( Entity *e1 ); + void NextAnim( int animnum ); + void NextFrame( int framenum ); + void AnimateFrame( void ); + void StopAnimating( void ); + void StartAnimating( void ); + void RandomAnimate( const char *animname, Event *endevent ); + void RandomAnimate( const char *animname, Event &endevent ); + qboolean HasAnim( const char *animname ); + + void joinTeam( Entity *teammember ); + void quitTeam( void ); + void EventQuitTeam( Event *ev ); + qboolean isBoundTo( Entity *master ); + void bind( Entity *master ); + void unbind( void ); + void EventUnbind( Event *ev ); + + void FadeOut( Event *ev ); + void Fade( Event *ev ); + + virtual void CheckGround( void ); + virtual qboolean HitSky( trace_t *trace ); + virtual qboolean HitSky( void ); + + void BecomeSolid( Event *ev ); + void BecomeNonSolid( Event *ev ); + void PlaySound( Event *ev ); + void StopSound( Event *ev ); + void SetGravityAxis( int axis ); + void GravityAxisEvent( Event *ev ); + void BindEvent( Event *ev ); + void JoinTeam( Event *ev ); + void SetHealth( Event *ev ); + void SetSize( Event *ev ); + void SetScale( Event *ev ); + void SetAlpha( Event *ev ); + void SetOrigin( Event *ev ); + void SetTargetName( Event *ev ); + void SetTarget( Event *ev ); + void SetKillTarget( Event *ev ); + void SetAngles( Event *ev ); + + void CourseAnglesEvent( Event *ev ); + void SmoothAnglesEvent( Event *ev ); + + str GetRandomAlias( str name ); + void SetWaterType( void ); + + // model binding functions + qboolean attach( int parent_entity_num, int group_num, int tri_num, Vector orient ); + void detach( void ); + + void RegisterAlias( Event *ev ); + void RegisterAliasAndCache( Event *ev ); + + qboolean GlobalAliasExists( const char *name ); + qboolean AliasExists( const char *name ); + + virtual void positioned_sound( Vector origin, str soundname, float volume = 1.0f, + int channel = CHAN_BODY, int attenuation = ATTN_NORM, float pitch = 1.0f, + float timeofs = 0, float fadetime = 0, int flags = SOUND_SYNCH ); + + virtual void sound( str soundname, float volume = 1.0f, int channel = CHAN_BODY, + int attenuation = ATTN_NORM, float pitch = 1.0f, float timeofs = 0, + float fadetime = 0, int flags = SOUND_SYNCH ); + + virtual void stopsound( int channel ); + + virtual void RandomPositionedSound( Vector origin, str soundname, float volume = 1.0f, + int channel = CHAN_BODY, int attenuation = ATTN_NORM, float pitch = 1.0f, + float timeofs = 0, float fadetime = 0, int flags = SOUND_SYNCH ); + + void RandomSound( str soundname, float volume = 1.0f, int channel = CHAN_BODY, + int attenuation = ATTN_NORM, float pitch = 1.0f, float timeofs = 0, + float fadetime = 0, int flags = SOUND_SYNCH ); + + void RandomGlobalSound( str soundname, float volume = 1.0f, int channel = CHAN_BODY, + int attenuation = ATTN_NORM, float pitch = 1.0f, float timeofs = 0, + float fadetime = 0, int flags = SOUND_SYNCH ); + + void RandomGlobalEntitySound( str soundname, int attenuation = ATTN_IDLE ); + void RandomGlobalEntitySoundEvent( Event *ev ); + + void RandomSound( Event *ev ); + void EntitySound( Event *ev ); + void StopEntitySound( Event *ev ); + void RandomEntitySound( Event *ev ); + void AnimEvent( Event *ev ); + void StartAnimatingEvent( Event *ev ); + void StopAnimatingEvent( Event *ev ); + void EndAnimEvent( Event *ev ); + void NextAnimEvent( Event *ev ); + void NextFrameEvent( Event *ev ); + void PrevFrameEvent( Event *ev ); + void SetFrameEvent( Event *ev ); + void SetLight(Event *ev); + void LightOn(Event *ev); + void LightOff(Event *ev); + void LightRed(Event *ev); + void LightGreen(Event *ev); + void LightBlue(Event *ev); + void LightRadius(Event *ev); + void Tesselate(Event *ev); + void SetShatterMinSize(Event *ev); + void SetShatterMaxSize(Event *ev); + void SetShatterThickness(Event *ev); + void SetShatterPercentage(Event *ev); + void Flags( Event *ev ); + void Effects( Event *ev ); + void RenderEffects( Event *ev ); + + void BroadcastSound( Event *soundevent, int channel, Event &event, float radius ); + void WeaponSound( Event *ev ); + void MovementSound( Event *ev ); + void PainSound( Event *ev ); + void DeathSound( Event *ev ); + void BreakingSound( Event *ev ); + void DoorSound( Event *ev ); + void MutantSound( Event *ev ); + void VoiceSound( Event *ev ); + void MachineSound( Event *ev ); + void RadioSound( Event *ev ); + void SpawnParticles( Event *ev ); + void Kill( Event *ev ); + void GroupModelEvent( Event *ev ); + + virtual void Prethink( void ); + virtual void Postthink( void ); + void DamageSkin( trace_t * trace, float damage ); + virtual void DialogEvent( Event *ev ); + void PHSSound( Event *ev ); + void RandomPHSSound( Event *ev ); + void AttachEvent( Event *ev ); + void AttachModelEvent( Event *ev ); + void DetachEvent( Event *ev ); + void TakeDamageEvent( Event *ev ); + void NoDamageEvent( Event *ev ); + void SetSkinEvent( Event *ev ); + void Lightoffset( Event *ev ); + void Gravity( Event *ev ); + void Minlight( Event *ev ); + void GiveOxygen( float time ); + void UseBoundingBoxEvent( Event *ev ); + void HurtEvent( Event *ev ); + void IfSkillEvent( Event *ev ); + void SetMassEvent( Event *ev ); + void Censor( Event *ev ); + void Ghost( Event *ev ); + + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL qboolean Entity::DistanceTo + ( + Vector pos + ) + + { + Vector delta; + + delta = worldorigin - pos; + return delta.length(); + } + +inline EXPORT_FROM_DLL qboolean Entity::DistanceTo + ( + Entity *ent + ) + + { + Vector delta; + + assert( ent ); + + if ( !ent ) + { + // "Infinite" distance + return 999999; + } + + delta = worldorigin - ent->worldorigin; + return delta.length(); + } + +inline EXPORT_FROM_DLL qboolean Entity::WithinDistance + ( + Vector pos, + float dist + ) + + { + Vector delta; + + delta = worldorigin - pos; + + // check squared distance + return ( ( delta * delta ) < ( dist * dist ) ); + } + +inline EXPORT_FROM_DLL qboolean Entity::WithinDistance + ( + Entity *ent, + float dist + ) + + { + Vector delta; + + assert( ent ); + + if ( !ent ) + { + return false; + } + + delta = worldorigin - ent->worldorigin; + + // check squared distance + return ( ( delta * delta ) < ( dist * dist ) ); + } + +inline EXPORT_FROM_DLL const char *Entity::Target + ( + void + ) + + { + return target.c_str(); + } + +inline EXPORT_FROM_DLL qboolean Entity::Targeted + ( + void + ) + + { + if ( !targetname.length() ) + { + return false; + } + return true; + } + +inline EXPORT_FROM_DLL const char *Entity::TargetName + ( + void + ) + + { + return targetname.c_str(); + } + +inline EXPORT_FROM_DLL const char * Entity::KillTarget + ( + void + ) + + { + return killtarget.c_str(); + } + +inline EXPORT_FROM_DLL qboolean Entity::hidden + ( + void + ) + + { + if ( edict->s.renderfx & RF_DONTDRAW ) + { + return true; + } + return false; + } + +inline EXPORT_FROM_DLL void Entity::setModel + ( + str &mdl + ) + + { + setModel( mdl.c_str() ); + } + +inline EXPORT_FROM_DLL void Entity::SetModelEvent + ( + Event *ev + ) + + { + setModel( ev->GetString( 1 ) ); + } + +inline EXPORT_FROM_DLL void Entity::hideModel + ( + void + ) + + { + edict->s.renderfx |= RF_DONTDRAW; + if ( getSolidType() <= SOLID_TRIGGER ) + { + edict->svflags |= SVF_NOCLIENT; + } + } + +inline EXPORT_FROM_DLL void Entity::showModel + ( + void + ) + + { + edict->s.renderfx &= ~RF_DONTDRAW; + edict->svflags &= ~SVF_NOCLIENT; + } + +inline EXPORT_FROM_DLL float Entity::alpha + ( + void + ) + + { + return 1.0f - translucence; + } + +inline EXPORT_FROM_DLL void Entity::setMoveType + ( + int type + ) + + { + movetype = type; + } + +inline EXPORT_FROM_DLL int Entity::getMoveType + ( + void + ) + + { + return movetype; + } + +inline EXPORT_FROM_DLL int Entity::getSolidType + ( + void + ) + + { + return edict->solid; + } + +inline EXPORT_FROM_DLL void Entity::unlink + ( + void + ) + + { + gi.unlinkentity( edict ); + } + +inline EXPORT_FROM_DLL void Entity::setContents + ( + int type + ) + + { + contents = type; + } + +inline EXPORT_FROM_DLL int Entity::getContents + ( + void + ) + + { + return contents; + } + +inline EXPORT_FROM_DLL qboolean Entity::isClient + ( + void + ) + + { + if ( client ) + { + return true; + } + return false; + } + +inline EXPORT_FROM_DLL void Entity::SetDeltaAngles + ( + void + ) + + { + int i; + + if ( client ) + { + for( i = 0; i < 3; i++ ) + { + client->ps.pmove.delta_angles[ i ] = ANGLE2SHORT( client->ps.viewangles[ i ] ); + } + } + } + +inline EXPORT_FROM_DLL void Entity::RandomAnimate + ( + const char *animname, + Event &endevent + ) + + { + Event *ev; + + ev = new Event( endevent ); + RandomAnimate( animname, ev ); + } + +inline EXPORT_FROM_DLL qboolean Entity::HasAnim + ( + const char *animname + ) + { + int num; + + num = gi.Anim_Random( edict->s.modelindex, animname ); + return ( num >= 0 ); + } + +inline EXPORT_FROM_DLL qboolean Entity::GlobalAliasExists + ( + const char *name + ) + + { + assert( name ); + + return ( gi.GlobalAlias_FindRandom( name ) != NULL ); + } + +inline EXPORT_FROM_DLL qboolean Entity::AliasExists + ( + const char *name + ) + + { + assert( name ); + + return ( gi.Alias_FindRandom( edict->s.modelindex, name ) != NULL ); + } + +inline EXPORT_FROM_DLL void Entity::stopsound + ( + int channel + ) + + { + RandomGlobalSound( "null_sound", 0.1, channel, 0 ); + } + +inline EXPORT_FROM_DLL str Entity::GetRandomAlias + ( + str name + ) + + { + str realname; + const char *s; + + s = gi.Alias_FindRandom( edict->s.modelindex, name.c_str() ); + if ( s ) + { + realname = s; + } + + return realname; + } + +inline EXPORT_FROM_DLL qboolean Entity::HitSky + ( + trace_t *trace + ) + + { + assert( trace ); + if ( trace->surface && ( trace->surface->flags & SURF_SKY ) ) + { + return true; + } + return false; + } + +inline EXPORT_FROM_DLL qboolean Entity::HitSky + ( + void + ) + + { + return HitSky( &level.impact_trace ); + } + +inline EXPORT_FROM_DLL void Entity::Archive + ( + Archiver &arc + ) + + { + Listener::Archive( arc ); + + G_ArchiveEdict( arc, edict ); + + arc.WriteInteger( spawnflags ); + + arc.WriteFloat( translucence ); + arc.WriteInteger( viewheight ); + arc.WriteInteger( light_level ); + + arc.WriteString( model ); + arc.WriteInteger( next_anim ); + arc.WriteInteger( next_frame ); + arc.WriteInteger( last_frame_in_anim ); + arc.WriteVector( frame_delta ); + arc.WriteVector( total_delta ); + arc.WriteVector( next_anim_delta ); + arc.WriteFloat( next_anim_time ); + arc.WriteBoolean( animating ); + arc.WriteEvent( *animDoneEvent ); + arc.WriteFloat( last_animation_time ); + arc.WriteInteger( num_frames_in_gun_anim ); + + arc.WriteVector( mins ); + arc.WriteVector( maxs ); + arc.WriteVector( absmin ); + arc.WriteVector( absmax ); + arc.WriteVector( size ); + arc.WriteVector( centroid ); + arc.WriteVector( origin ); + arc.WriteVector( velocity ); + arc.WriteVector( avelocity ); + arc.WriteVector( angles ); + arc.WriteVector( worldorigin ); + arc.WriteVector( worldangles ); + arc.WriteRaw( orientation, sizeof( orientation ) ); + arc.WriteVector( vieworigin ); + arc.WriteVector( viewangles ); + arc.WriteInteger( contents ); + arc.WriteInteger( movetype ); + arc.WriteInteger( mass ); + arc.WriteFloat( gravity ); + arc.WriteInteger( gravaxis ); + + if ( groundentity ) + { + arc.WriteInteger( groundentity - g_edicts ); + } + else + { + arc.WriteInteger( -1 ); + } + + arc.WriteRaw( &groundplane, sizeof( groundplane ) ); + arc.WriteInteger( groundcontents ); + + arc.WriteInteger( groundentity_linkcount ); + + arc.WriteObjectPointer( bindmaster ); + arc.WriteString( moveteam ); + arc.WriteObjectPointer( teamchain ); + arc.WriteObjectPointer( teammaster ); + + arc.WriteInteger( numchildren ); + arc.WriteRaw( children, sizeof( children ) ); + + arc.WriteString( target ); + arc.WriteString( targetname ); + // add to target list to rebuild targetlists + arc.WriteString( killtarget ); + + arc.WriteFloat( health ); + arc.WriteFloat( max_health ); + arc.WriteInteger( deadflag ); + arc.WriteInteger( flags ); + + arc.WriteInteger( watertype ); + arc.WriteInteger( waterlevel ); + + arc.WriteInteger( ( int )takedamage ); + arc.WriteSafePointer( enemy ); + arc.WriteFloat( pain_finished ); + arc.WriteFloat( damage_debounce_time ); + + arc.WriteInteger( tess_min_size ); + arc.WriteInteger( tess_max_size ); + arc.WriteInteger( tess_thickness ); + arc.WriteFloat( tess_percentage ); + } + +inline EXPORT_FROM_DLL void Entity::Unarchive + ( + Archiver &arc + ) + + { + int temp; + + Listener::Unarchive( arc ); + + G_UnarchiveEdict( arc, edict ); + + arc.ReadInteger( &spawnflags ); + + arc.ReadFloat( &translucence ); + arc.ReadInteger( &viewheight ); + arc.ReadInteger( &light_level ); + + arc.ReadString( &model ); + setModel( model ); + + arc.ReadInteger( &next_anim ); + arc.ReadInteger( &next_frame ); + arc.ReadInteger( &last_frame_in_anim ); + arc.ReadVector( &frame_delta ); + arc.ReadVector( &total_delta ); + arc.ReadVector( &next_anim_delta ); + arc.ReadFloat( &next_anim_time ); + arc.ReadBoolean( &animating ); + animDoneEvent = new Event( arc.ReadEvent() ); + arc.ReadFloat( &last_animation_time ); + arc.ReadInteger( &num_frames_in_gun_anim ); + + arc.ReadVector( &mins ); + arc.ReadVector( &maxs ); + arc.ReadVector( &absmin ); + arc.ReadVector( &absmax ); + arc.ReadVector( &size ); + arc.ReadVector( ¢roid ); + arc.ReadVector( &origin ); + arc.ReadVector( &velocity ); + arc.ReadVector( &avelocity ); + arc.ReadVector( &angles ); + arc.ReadVector( &worldorigin ); + arc.ReadVector( &worldangles ); + arc.ReadRaw( orientation, sizeof( orientation ) ); + arc.ReadVector( &vieworigin ); + arc.ReadVector( &viewangles ); + arc.ReadInteger( &contents ); + arc.ReadInteger( &movetype ); + arc.ReadInteger( &mass ); + arc.ReadFloat( &gravity ); + arc.ReadInteger( &gravaxis ); + + temp = arc.ReadInteger(); + if ( temp == -1 ) + groundentity = NULL; + else + groundentity = &g_edicts[ temp ]; + + groundsurface = NULL; + + arc.ReadRaw( &groundplane, sizeof( groundplane ) ); + arc.ReadInteger( &groundcontents ); + + arc.ReadInteger( &groundentity_linkcount ); + + arc.ReadObjectPointer( ( Class ** )&bindmaster ); + arc.ReadString( &moveteam ); + arc.ReadObjectPointer( ( Class ** )&teamchain ); + arc.ReadObjectPointer( ( Class ** )&teammaster ); + + arc.ReadInteger( &numchildren ); + arc.ReadRaw( children, sizeof( children ) ); + + arc.ReadString( &target ); + arc.ReadString( &targetname ); + arc.ReadString( &killtarget ); + + // reset target stuff + SetTargetName( targetname.c_str() ); + SetTarget( target.c_str() ); + + arc.ReadFloat( &health ); + arc.ReadFloat( &max_health ); + arc.ReadInteger( &deadflag ); + arc.ReadInteger( &flags ); + + arc.ReadInteger( &watertype ); + arc.ReadInteger( &waterlevel ); + + temp = arc.ReadInteger(); + takedamage = ( damage_t )temp; + arc.ReadSafePointer( &enemy ); + arc.ReadFloat( &pain_finished ); + arc.ReadFloat( &damage_debounce_time ); + + arc.ReadInteger( &tess_min_size ); + arc.ReadInteger( &tess_max_size ); + arc.ReadInteger( &tess_thickness ); + arc.ReadFloat( &tess_percentage ); + } + +#include "worldspawn.h" + +#endif diff --git a/eonandpeon.cpp b/eonandpeon.cpp new file mode 100644 index 0000000..a17931f --- /dev/null +++ b/eonandpeon.cpp @@ -0,0 +1,218 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/eonandpeon.cpp $ +// $Revision:: 8 $ +// $Author:: Markd $ +// $Date:: 11/09/98 12:31a $ +// +// Copyright (C) 1998 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/eonandpeon.cpp $ +// +// 8 11/09/98 12:31a Markd +// slowed down eonbomb +// +// 7 11/09/98 12:24a Markd +// make sure we have a currentEnemy when dieing +// +// 6 10/27/98 3:47p Markd +// fixed potential eon and peon bug +// +// 5 10/27/98 5:24a Markd +// made eon and peon not fade out +// +// 4 10/27/98 3:53a Markd +// Put in eon throwing +// +// 3 10/25/98 4:43a Markd +// incremental +// +// 2 10/23/98 3:41p Markd +// incremental check in +// +// 1 10/23/98 5:06a Markd +// +// DESCRIPTION: +// Eon and Peon +// + +#include "g_local.h" +#include "actor.h" +#include "eonandpeon.h" +#include "specialfx.h" +#include "gibs.h" + + + + +class EXPORT_FROM_DLL EonBomb : public Projectile + { + public: + CLASS_PROTOTYPE( EonBomb ); + + virtual void Setup( Entity *owner, Vector pos, Vector vel ); + virtual void EonBombTouch( Event *ev ); + }; + +CLASS_DECLARATION( Projectile, EonBomb, NULL ); + +ResponseDef EonBomb::Responses[] = + { + { &EV_Touch, ( Response )EonBomb::EonBombTouch }, + { NULL, NULL } + }; + +void EonBomb::EonBombTouch + ( + Event *ev + ) + + { + if ( sv_gibs->value && !parentmode->value ) + { + CreateGibs( this, health, 0.3, 10 ); + } + PostEvent( EV_Remove, 0 ); + } + +void EonBomb::Setup + ( + Entity *owner, + Vector pos, + Vector vel + ) + + { + // Flies like a grenade + setMoveType( MOVETYPE_TOSS ); + setSolidType( SOLID_BBOX ); + edict->clipmask = MASK_PROJECTILE; + setModel( "eon.def" ); + RandomAnimate( "idle", NULL ); + + // Set the flying velocity + velocity = vel; + + takedamage = DAMAGE_NO; + + setSize( "-1 -1 -1", "1 1 1" ); + setOrigin( pos ); + worldorigin.copyTo(edict->s.old_origin); + + // Remove the projectile in the future + PostEvent( EV_Remove, 30 ); + } + + +CLASS_DECLARATION( Peon, EonAndPeon, "boss_eonandpeon" ); + +Event EV_EonAndPeon_SpawnGoo( "spawngoo" ); + +ResponseDef EonAndPeon::Responses[] = + { + { &EV_EonAndPeon_SpawnGoo, ( Response )EonAndPeon::SpawnGoo }, + { &EV_Killed, ( Response )EonAndPeon::Killed }, + { &EV_FadeOut, NULL }, + { NULL, NULL } + }; + +EonAndPeon::EonAndPeon() + { + eon = new Entity; + eon->setModel( "boss_eon.def" ); + + levelVars.SetVariable( "eon", eon ); + + setModel( "boss_peon.def" ); + flags |= FL_POSTTHINK; + } + +void EonAndPeon::Chatter + ( + const char *snd, + float chance, + float volume, + int channel + ) + + { + if ( chattime > level.time ) + { + return; + } + + if ( eon ) + eon->RandomSound( snd, volume, channel, ATTN_NONE ); + RandomSound( snd, volume, channel, ATTN_NONE ); + + chattime = level.time + 7 + G_Random( 5 ); + } + +void EonAndPeon::Postthink + ( + void + ) + { + if ( eon ) + { + eon->setOrigin( worldorigin ); + eon->setAngles( worldangles ); + eon->edict->s.anim = edict->s.anim; + eon->edict->s.frame = edict->s.frame; + eon->edict->s.scale = edict->s.scale; + } + } + +void EonAndPeon::Killed + ( + Event *ev + ) + + { + EonBomb * eonbomb; + Vector vel; + Vector pos; + Vector target; + float speed; + + pos = worldorigin; + pos.z = absmax.z; + + speed = 250; + + if ( currentEnemy ) + { + target = G_PredictPosition( pos, currentEnemy->centroid, currentEnemy->velocity, speed ); + + vel = G_CalculateImpulse + ( + pos, + target, + speed, + 0.1f + ); + } + else + { + vel.z = 200; + } + + eonbomb = new EonBomb; + eonbomb->gravity = 0.1f; + eonbomb->Setup( this, pos, vel ); + eonbomb->RandomSound( "snd_yell" ); + + // delete the real eon + eon->PostEvent( EV_Remove, 0 ); + eon = NULL; + levelVars.SetVariable( "eon", 0 ); + + // + // call normal actor function + // + Actor::Killed( ev ); + } diff --git a/eonandpeon.h b/eonandpeon.h new file mode 100644 index 0000000..aeaba3d --- /dev/null +++ b/eonandpeon.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/eonandpeon.h $ +// $Revision:: 5 $ +// $Author:: Jimdose $ +// $Date:: 11/08/98 10:51p $ +// +// Copyright (C) 1998 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/eonandpeon.h $ +// +// 5 11/08/98 10:51p Jimdose +// added archive functions +// +// 4 10/27/98 3:53a Markd +// Added Killed and Chatter +// +// 3 10/25/98 4:44a Markd +// incremental +// +// 2 10/23/98 3:41p Markd +// incremental check in +// +// 1 10/23/98 5:06a Markd +// +// DESCRIPTION: +// Eon and Peon +// + +#ifndef __EONANDPEON_H__ +#define __EONANDPEON_H__ + +#include "g_local.h" +#include "actor.h" +#include "peon.h" + +class EXPORT_FROM_DLL EonAndPeon : public Peon + { + private: + EntityPtr eon; + + public: + CLASS_PROTOTYPE( EonAndPeon ); + + EonAndPeon::EonAndPeon(); + void Postthink( void ); + void Killed( Event *ev ); + virtual void Chatter( const char *sound, float chance = 10, float volume = 1.0f, int channel = CHAN_VOICE ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void EonAndPeon::Archive + ( + Archiver &arc + ) + + { + Peon::Archive( arc ); + + arc.WriteSafePointer( eon ); + } + +inline EXPORT_FROM_DLL void EonAndPeon::Unarchive + ( + Archiver &arc + ) + + { + Peon::Unarchive( arc ); + + arc.ReadSafePointer( &eon ); + } + +#endif /* eonandpeon.h */ diff --git a/explosion.cpp b/explosion.cpp new file mode 100644 index 0000000..a0d844e --- /dev/null +++ b/explosion.cpp @@ -0,0 +1,522 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/explosion.cpp $ +// $Revision:: 48 $ +// $Author:: Markd $ +// $Date:: 10/24/98 12:42a $ +// +// 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/explosion.cpp $ +// +// 48 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 47 10/22/98 7:57p Markd +// put in proper pre-caching in all the classes +// +// 46 10/22/98 5:03p Jimdose +// Made MakeExplosion properly use the activator +// +// 45 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 44 10/14/98 10:55p Jimdose +// made CreateExplosion not ignore the explosion if attacker and ignore are not +// set +// +// 43 10/07/98 11:47p Jimdose +// made FlashPlayers only check clients instead of using findradius +// +// 42 10/05/98 10:23p Aldie +// Made flash based on distance +// +// 41 10/04/98 10:28p Aldie +// Added multiple weapon changes. Damage, flashes, quantum stuff +// +// 40 10/02/98 7:19p Aldie +// Added FlashPlayers to do blinding flashes +// +// 39 9/05/98 12:09p Aldie +// Made RadiusDamage non static +// +// 38 9/01/98 3:05p Markd +// Rewrote explosion code +// +// 37 8/30/98 7:06p Markd +// forgot to put scale into func_exploder +// +// 36 8/29/98 7:15p Markd +// Added big explosion support +// +// 35 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 34 8/27/98 9:01p Jimdose +// Changed centroid to a variable +// +// 33 8/17/98 2:55p Aldie +// Increased splash damage a little +// +// 32 8/06/98 10:53p Aldie +// Added weapon tweaks and kickback. Also modified blast radius damage and +// rocket jumping. +// +// 31 7/26/98 1:18a Aldie +// Put explosion stuff back in +// +// 30 7/25/98 7:59p Aldie +// Moved more stuff client side. +// +// 29 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 +// +// 28 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 27 7/15/98 9:58p Markd +// changed syntax of TempDLight +// +// 26 7/09/98 12:02a Jimdose +// Changed remove event to be posted instead of processed +// +// 25 6/29/98 8:21p Aldie +// Updated explosion methods +// +// 24 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// 23 6/15/98 10:37a Aldie +// Made explosion more generic +// +// 22 6/10/98 2:10p Aldie +// Updated damage function. +// +// 21 5/25/98 8:00p Markd +// Accidentally left AUTO_ANIMATE on +// +// 20 5/25/98 7:58p Markd +// Cleaned it up a bunch +// +// 19 5/25/98 7:04p Markd +// Changed to TE_TEMPMODEL system +// +// 18 5/24/98 11:01p Markd +// made sure activator is valid +// +// 17 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 +// +// 16 5/24/98 2:47p Markd +// Made char *'s into const char *'s +// +// 15 5/24/98 1:03a Jimdose +// Added sound events for ai +// +// 14 5/23/98 6:28p Jimdose +// Fixed bug in RadiusDamage where the entity could be freed by damage and the +// pointer then used in findradius. Damage is now posted as an event +// +// 13 5/03/98 4:31p Jimdose +// Changed Vector class. No longer includes PointsTo +// +// 12 4/28/98 5:32p Jimdose +// Changed the radius back again +// +// 11 4/18/98 2:31p Jimdose +// Changed radius of splash damage from explosion +// +// 10 4/06/98 5:30p Jimdose +// Fixed NULL attacker bug in MakeExplosion +// +// 9 4/05/98 6:42p Jimdose +// Added Exploders and MultiExploders +// +// 8 3/27/98 11:03p Jimdose +// changed explode.def to explode.spr +// +// 7 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 6 3/18/98 2:27p Jimdose +// Converted to work with new sin +// +// 4 10/27/97 3:30p Jimdose +// Removed dependency on quakedef.h +// +// 3 10/03/97 12:45a Jimdose +// Made explosion give off Redish-orange flash +// +// 2 9/26/97 5:23p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Standard explosion object that is spawned by other entites and not map designers. +// Explosion is used by many of the weapons for the blast effect, but is also used +// by the Exploder and MultiExploder triggers. These triggers create one or more +// explosions each time they are activated. +// + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "explosion.h" +#include "specialfx.h" +#include "player.h" + +#define RANDOM_TIME (1<<1) +#define RANDOM_SCALE (1<<2) +#define BIG_EXPLOSION (1<<3) + +void FlashPlayers + ( + Vector org, + float r, + float g, + float b, + float a, + float rad + ) + + { + Event *ev1; + trace_t trace; + Vector delta; + float length; + Player *player; + edict_t *edict; + int i; + + for( i = 0; i < maxclients->value; i++ ) + { + edict = g_edicts + 1 + i; + if ( !edict->inuse || !edict->client || !edict->entity || !edict->entity->isSubclassOf( Player ) || + !edict->entity->WithinDistance( org, rad ) ) + { + continue; + } + + player = ( Player * )edict->entity; + + trace = G_Trace( org, vec_zero, vec_zero, player->worldorigin + player->eyeposition, player, MASK_OPAQUE, "FlashPlayers" ); + if ( trace.fraction != 1.0 ) + { + continue; + } + + delta = org - trace.endpos; + length = delta.length(); + + a = a * ( 1 - length / rad ); + + ev1 = new Event( EV_Player_SetFlashColor ); + ev1->AddFloat( r ); + ev1->AddFloat( g ); + ev1->AddFloat( b ); + ev1->AddFloat( a ); + player->ProcessEvent( ev1 ); + } + } + +void RadiusDamage + ( + Entity *inflictorent, + Entity *attackerent, + int damage, + Entity *ignoreent, + int mod + ) + + { + float points; + Entity *ent; + Vector org; + Vector v; + float rad; + + rad = ( float )( damage + 60 ); + + ent = findradius( NULL, inflictorent->worldorigin.vec3(), rad ); + while( ent ) + { + if ( ( ent != ignoreent ) && ( ent->takedamage ) ) + { + org = ent->centroid; + v = org - inflictorent->worldorigin; + points = v.length() * 0.5f; + if ( points < 0 ) + { + points = 0; + } + points = damage - points; + if ( ent == attackerent ) + { + points *= 0.5; + } + + if ( points > 0 ) + { + if ( inflictorent->CanDamage( ent ) ) + { + ent->Damage(inflictorent, attackerent, points, + org, v, vec_zero, points, + DAMAGE_RADIUS, mod, + -1, -1, 1.0f ); + } + } + } + ent = findradius( ent, inflictorent->worldorigin.vec3(), rad ); + } + } + +void CreateExplosion + ( + Vector pos, + float damage, + float scale, + qboolean bigexplosion, + Entity *inflictor, + Entity *attacker, + Entity *ignore, + float volume, + float attenuation, + float r, + float g, + float b, + float light_radius, + float life, + float decay + ) + + { + assert( inflictor ); + + if ( !inflictor ) + { + return; + } + + if ( !attacker ) + { + attacker = world; + } + + if ( volume > 4.0f ) + volume = 4.0f; + + if ( damage < 120 ) + { + inflictor->RandomPositionedSound( pos, "impact_smallexplosion", volume, CHAN_AUTO, attenuation ); + } + else + { + inflictor->RandomPositionedSound( pos, "impact_bigexplosion", volume, CHAN_AUTO, attenuation ); + } + + RadiusDamage( inflictor, attacker, damage, ignore, MOD_ROCKETSPLASH ); + inflictor->ProcessEvent( EV_WeaponSound ); + + if ( bigexplosion ) + SpawnScaledExplosion( pos, scale ); + else + TempModel( NULL, pos, "0 0 0", "sprites/explode.spr", 0, scale, 1.0f, TEMPMODEL_ANIMATE_ONCE, 10 ); + } + +/*****************************************************************************/ +/*SINED func_exploder (0.4 0 0) (0 0 0) (8 8 8) x x x BIG_EXPLOSION + + Spawns an explosion when triggered. Triggers any targets. + + "dmg" specifies how much damage to cause. Default indicates 120. + "volume" volume at which to play explosions (default 1.0) + "attenuation" attenuation for explosions (default normal) + "key" The item needed to activate this. (default nothing) +/*****************************************************************************/ + +ResponseDef Exploder::Responses[] = + { + { &EV_Touch, NULL }, + { &EV_Trigger_Effect, ( Response )Exploder::MakeExplosion }, + { NULL, NULL } + }; + +CLASS_DECLARATION( Trigger, Exploder, "func_exploder" ); + +void Exploder::MakeExplosion + ( + Event *ev + ) + + { + CreateExplosion + ( + worldorigin, + edict->s.scale * damage, + edict->s.scale, + ( spawnflags & BIG_EXPLOSION ), + this, + ev->GetEntity( 1 ), + this, + volume * edict->s.scale, + attenuation + ); + } + +Exploder::Exploder() + { + damage = G_GetIntArg( "dmg", 120 ); + if ( damage < 0 ) + { + damage = 0; + } + + modelIndex( "sprites/explode.spr" ); + attenuation = G_GetFloatArg( "attenuation", 1.0 ); + volume = G_GetFloatArg( "volume", 1.0 ); + respondto = TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES; + } + +/*****************************************************************************/ +/*SINED func_multi_exploder (0.4 0 0) ? x RANDOM_TIME RANDOM_SCALE BIG_EXPLOSION + + Spawns an explosion when triggered. Triggers any targets. + size of brush determines where explosions will occur. + + "dmg" specifies how much damage to cause from each explosion. (Default 120) + "delay" delay before exploding (Default 0 seconds) + "duration" how long to explode for (Default 1 second) + "wait" time between each explosion (default 0.25 seconds) + "volume" volume to play explosion sound at (default 0.5) + "attenuation" attenuation for explosions (default normal) + "random" random factor (default 0.25) + "key" The item needed to activate this. (default nothing) + + RANDOM_TIME adjusts the wait between each explosion by the random factor + RANDOM_SCALE adjusts the size of each explosion by the random factor + +/*****************************************************************************/ + +CLASS_DECLARATION( Trigger, MultiExploder, "func_multi_exploder" ); + +ResponseDef MultiExploder::Responses[] = + { + { &EV_Touch, NULL }, + { &EV_Trigger_Effect, ( Response )MultiExploder::MakeExplosion }, + { NULL, NULL } + }; + +void MultiExploder::MakeExplosion + ( + Event *ev + ) + + { + Vector pos; + float t; + float r; + float v; + Entity *other; + Event *event; + + other = ev->GetEntity( 1 ); + + // make sure other is valid + if ( !other ) + { + other = world; + } + + // prevent the trigger from triggering again + trigger_time = -1; + + if ( !explode_time ) + { + explode_time = level.time + duration; + } + + if ( spawnflags & RANDOM_TIME ) + { + t = explodewait * ( 1 + G_CRandom( randomness ) ); + } + else + { + t = explodewait; + } + + event = new Event( EV_Trigger_Effect ); + event->AddEntity( other ); + PostEvent( event, t ); + + if ( level.time > explode_time ) + { + PostEvent( EV_Remove, 0 ); + return; + } + + pos[ 0 ] = absmin[ 0 ] + G_Random( absmax[ 0 ] - absmin[ 0 ] ); + pos[ 1 ] = absmin[ 1 ] + G_Random( absmax[ 1 ] - absmin[ 1 ] ); + pos[ 2 ] = absmin[ 2 ] + G_Random( absmax[ 2 ] - absmin[ 2 ] ); + + if ( spawnflags & RANDOM_SCALE ) + { + r = edict->s.scale + G_CRandom( randomness ); + } + else + { + r = edict->s.scale; + } + + if ( r < 1 ) + { + v = volume * r; + } + else + { + v = volume; + } + CreateExplosion + ( + pos, + damage * r, + r, + ( spawnflags & BIG_EXPLOSION ), + this, + other, + this, + volume, + attenuation + ); + } + +MultiExploder::MultiExploder() + { + damage = G_GetIntArg( "dmg", 120 ); + if ( damage < 0 ) + { + damage = 0; + } + + attenuation = G_GetFloatArg( "attenuation", 1.0 ); + volume = G_GetFloatArg( "volume", 1.0 ); + duration = G_GetFloatArg( "duration", 1.0 ); + explodewait = G_GetFloatArg( "wait", 0.25 ); + randomness = G_GetFloatArg( "random", 0.25 ); + explode_time = 0; + + // So that we don't get deleted after we're triggered + count = -1; + + respondto = TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES; + modelIndex( "sprites/explode.spr" ); + } diff --git a/explosion.h b/explosion.h new file mode 100644 index 0000000..61beb74 --- /dev/null +++ b/explosion.h @@ -0,0 +1,201 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/explosion.h $ +// $Revision:: 14 $ +// $Author:: Aldie $ +// $Date:: 10/02/98 7:20p $ +// +// 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/explosion.h $ +// +// 14 10/02/98 7:20p Aldie +// Added flashplayers to do blinding flashes +// +// 13 9/28/98 9:12p Markd +// Put in archive and unarchive functions +// +// 12 9/05/98 12:13p Aldie +// externed RadiusDamage +// +// 11 9/01/98 3:05p Markd +// Rewrote explosion code +// +// 10 6/15/98 10:39a Aldie +// Updated explosion +// +// 9 5/25/98 7:08p Markd +// commented out some stuff +// +// 8 4/05/98 6:42p Jimdose +// Added Exploders and MultiExploders +// +// 7 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 6 3/18/98 2:27p Jimdose +// Converted to work with new sin +// +// 4 12/06/97 4:48p Markd +// Added interpretCommands. +// Added GetArgs as commands for future processing +// Removed dmg,attentuatioin and volume, moved these to Trigger +// +// 3 10/27/97 2:59p Jimdose +// Removed dependency on quakedef.h +// +// 2 9/26/97 5:23p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Standard explosion object that is spawned by other entites and not map designers. +// Explosion is used by many of the weapons for the blast effect, but is also used +// by the Exploder and MultiExploder triggers. These triggers create one or more +// explosions each time they are activated. +// + +#ifndef __EXPLOSION_H__ +#define __EXPLOSION_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" + +class EXPORT_FROM_DLL Exploder : public Trigger + { + private: + int damage; + float attenuation; + float volume; + + virtual void MakeExplosion( Event *ev ); + + public: + CLASS_PROTOTYPE( Exploder ) + + Exploder(); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Exploder::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.WriteInteger( damage ); + arc.WriteFloat( attenuation ); + arc.WriteFloat( volume ); + } + +inline EXPORT_FROM_DLL void Exploder::Unarchive + ( + Archiver &arc + ) + { + Trigger::Unarchive( arc ); + + arc.ReadInteger( &damage ); + arc.ReadFloat( &attenuation ); + arc.ReadFloat( &volume ); + } + + +class EXPORT_FROM_DLL MultiExploder : public Trigger + { + private: + float explodewait; + float explode_time; + float duration; + int damage; + float attenuation; + float volume; + float randomness; + + virtual void MakeExplosion( Event *ev ); + + public: + CLASS_PROTOTYPE( MultiExploder ); + + MultiExploder(); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void MultiExploder::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.WriteFloat( explodewait ); + arc.WriteFloat( explode_time ); + arc.WriteFloat( duration ); + arc.WriteInteger( damage ); + arc.WriteFloat( attenuation ); + arc.WriteFloat( volume ); + arc.WriteFloat( randomness ); + } + +inline EXPORT_FROM_DLL void MultiExploder::Unarchive + ( + Archiver &arc + ) + { + Trigger::Unarchive( arc ); + + arc.ReadFloat( &explodewait ); + arc.ReadFloat( &explode_time ); + arc.ReadFloat( &duration ); + arc.ReadInteger( &damage ); + arc.ReadFloat( &attenuation ); + arc.ReadFloat( &volume ); + arc.ReadFloat( &randomness ); + } + +void CreateExplosion + ( + Vector pos, + float damage = 120, + float scale = 1.0f, + qboolean bigexplosion = true, + Entity *inflictor = NULL, + Entity *attacker = NULL, + Entity *ignore = NULL, + float volume = 1.0f, + float attenuation = ATTN_NORM, + float r = 1.0f, + float g = 0.2f, + float b = 0.0f, + float light_radius = 240, + float life = 1, + float decay = 0.95 + ); + +void RadiusDamage + ( + Entity *inflictorent, + Entity *attackerent, + int damage, + Entity *ignoreent, + int mod + ); + +void FlashPlayers + ( + Vector org, + float r, + float g, + float b, + float a, + float rad + ); + +#endif /* explosion.h */ diff --git a/fists.cpp b/fists.cpp new file mode 100644 index 0000000..ec376c8 --- /dev/null +++ b/fists.cpp @@ -0,0 +1,264 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/fists.cpp $ +// $Revision:: 34 $ +// $Author:: Markd $ +// $Date:: 11/17/98 1:31a $ +// +// Copyright (C) 1998 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/fists.cpp $ +// +// 34 11/17/98 1:31a Markd +// took out damage multiplier for fists +// +// 33 11/12/98 11:31p Jimdose +// changed impact_bodyimpact to impact_goryimpact +// increased kick from melee +// +// 32 10/20/98 8:26p Markd +// Added Attacker to DamageSurface stuff +// +// 31 10/20/98 3:59p Aldie +// Tweaked fist radius +// +// 30 10/14/98 12:12a Aldie +// Tweak damage +// +// 29 10/11/98 5:35p Aldie +// Added meansofdeath +// +// 28 10/05/98 10:23p Aldie +// Fixed rank +// +// 27 10/01/98 3:35p Onethumb +// +// 26 9/18/98 10:12p Markd +// made fists not use MASK_SHOT, MASK_PROJECTILE instead +// +// 25 9/18/98 8:14p Markd +// rewrote surface system so that surfaces are now damaged by surface name instead +// of by surfinfo +// +// 24 8/31/98 7:45p Aldie +// Updated surface data structure and removed surfinfo field +// +// 23 8/31/98 4:33p Markd +// Made fists use fulltrace +// +// 22 8/29/98 9:40p Jimdose +// Added call info to G_Trace +// +// 21 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 20 8/18/98 11:08p Markd +// Added new Alias System +// +// 19 8/17/98 3:16p Aldie +// Made fists not ignore armor +// +// 18 8/06/98 6:58p Jimdose +// Added min/max range, and projectile speed +// +// 17 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 +// +// 16 7/22/98 9:57p Markd +// Defined weapon type +// +// 15 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 14 7/19/98 10:33p Aldie +// Update fist damage +// +// 13 6/19/98 9:29p Jimdose +// Moved gun orientation code to Weapon +// +// 12 6/10/98 10:03p Markd +// Got working with reach and damage +// +// 11 6/10/98 4:00p Aldie +// Updated fists to do damage skins +// +// 10 6/10/98 2:10p Aldie +// Updated damage function. +// +// 9 5/27/98 5:21a Markd +// changed ranking of fists +// +// 8 5/26/98 5:42p Markd +// Made fists more realistic damage wise +// +// 7 5/25/98 7:58p Markd +// Moved TakeDamage a bit +// +// 6 5/25/98 5:38p Markd +// Put in strike sound and stuff +// +// 5 5/25/98 1:00a Markd +// Fixed Fists +// +// 4 5/23/98 5:38p Markd +// slowed down firing rate +// +// 3 5/20/98 10:43p Markd +// made fists into bullets +// +// 2 5/11/98 11:24a Markd +// First time +// +// DESCRIPTION: +// Normal Hands +// + +#include "g_local.h" +#include "fists.h" +#include "misc.h" +#include "specialfx.h" +#include "surface.h" + +CLASS_DECLARATION( Weapon, Fists, NULL); + +ResponseDef Fists::Responses[] = + { + { &EV_Weapon_Shoot, ( Response )Fists::Shoot }, + { NULL, NULL } + }; + +Fists::Fists() + { + SetModels( NULL, "view_punch.def" ); + SetAmmo( NULL, 0, 0 ); + SetRank( 10, 10 ); + strike_reach = 64; + strike_damage = 20; + SetMaxRange( strike_reach ); + SetType( WEAPON_MELEE ); + kick = 40; + meansofdeath = MOD_FISTS; + } + +void Fists::Shoot( Event * ev ) + { + trace_t trace; + Vector start; + Vector end; + float damage; + Vector org; + Vector dir; + int surfflags; + int surftype; + + assert( owner ); + if ( !owner ) + { + return; + } + + NextAttack( 1 ); + + damage = G_Random(strike_damage)+strike_damage; + + GetMuzzlePosition( &start, &dir ); + end = start + dir * strike_reach; + + trace = G_FullTrace( start, vec_zero, vec_zero, end, 64, owner, MASK_PROJECTILE, "Fists::Shoot" ); + + if ( !trace.surface ) + { + surfflags = 0; + surftype = 0; + } + else + { + surfflags = trace.surface->flags; + surftype = SURFACETYPE_FROM_FLAGS( surfflags ); + surfaceManager.DamageSurface( &trace, damage, owner ); + } + dir = Vector(trace.endpos) - start; + dir.normalize(); + + org = Vector(trace.endpos) - dir; + + if ( (trace.fraction < 1.0f) ) + { + if ( trace.ent->entity && trace.ent->entity->takedamage ) + { + if ( trace.ent->entity->flags & FL_BLOOD ) + { + if ( ( meansofdeath == MOD_MUTANTHANDS ) || ( trace.ent->entity->health < -500 ) ) + { + owner->RandomGlobalSound("impact_goryimpact"); + } + else + { + owner->RandomGlobalSound("impact_bodyimpact"); + } + SpawnBlood( org, trace.plane.normal, damage ); + } + else + { + gi.WriteByte( svc_temp_entity ); + gi.WriteByte( TE_STRIKE ); + gi.WritePosition( org.vec3() ); + gi.WriteDir( trace.plane.normal ); + gi.WriteByte( 120 ); + gi.WriteByte( surftype ); + gi.multicast( org.vec3(), MULTICAST_PVS ); + } + if ( trace.intersect.valid ) + { + // take the ground out so that the kick works + trace.ent->entity->groundentity = NULL; + + // We hit a valid group so send in location based damage + trace.ent->entity->Damage( this, + owner, + damage, + trace.endpos, + dir, + trace.plane.normal, + kick, + 0, + meansofdeath, + trace.intersect.parentgroup, + -1, + 1 ); + //trace.intersect.damage_multiplier ); + } + else + { + // We didn't hit any groups, so send in generic damage + trace.ent->entity->Damage( this, + owner, + damage, + trace.endpos, + dir, + trace.plane.normal, + kick, + 0, + meansofdeath, + -1, + -1, + 1 ); + } + } + else + { + gi.WriteByte( svc_temp_entity ); + gi.WriteByte( TE_STRIKE ); + gi.WritePosition( org.vec3() ); + gi.WriteDir( trace.plane.normal ); + gi.WriteByte( 120 ); + gi.WriteByte( surftype ); + gi.multicast( org.vec3(), MULTICAST_PVS ); + } + } + } diff --git a/fists.h b/fists.h new file mode 100644 index 0000000..bb61395 --- /dev/null +++ b/fists.h @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/fists.h $ +// $Revision:: 7 $ +// $Author:: Aldie $ +// $Date:: 10/11/98 5:34p $ +// +// Copyright (C) 1998 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/fists.h $ +// +// 7 10/11/98 5:34p Aldie +// Added meansofdeath +// +// 6 9/28/98 9:12p Markd +// Put in archive and unarchive functions +// +// 5 6/10/98 10:02p Markd +// put reach and damage into fists +// +// 4 5/25/98 1:08a Markd +// Made fists a Weapon, not a BulletWeapon +// +// 3 5/20/98 10:43p Markd +// made fists into bullets! +// +// 2 5/11/98 11:25a Markd +// First time +// +// 1 5/11/98 10:28a Markd +// +// 1 5/11/98 10:20a Markd +// +// 1 5/11/98 10:18a Markd +// +// 1 5/11/98 9:55a Markd +// +// DESCRIPTION: +// Mutant Hands +// + +#ifndef __FISTS_H__ +#define __FISTS_H__ + +#include "g_local.h" +#include "item.h" +#include "weapon.h" + +class EXPORT_FROM_DLL Fists : public Weapon + { + public: + float strike_reach; + float strike_damage; + int meansofdeath; + + CLASS_PROTOTYPE( Fists ); + + Fists::Fists(); + virtual void Shoot( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Fists::Archive + ( + Archiver &arc + ) + { + Weapon::Archive( arc ); + + arc.WriteFloat( strike_reach ); + arc.WriteFloat( strike_damage ); + arc.WriteInteger( meansofdeath ); + } + +inline EXPORT_FROM_DLL void Fists::Unarchive + ( + Archiver &arc + ) + { + Weapon::Unarchive( arc ); + + arc.ReadFloat( &strike_reach ); + arc.ReadFloat( &strike_damage ); + arc.ReadInteger( &meansofdeath ); + } + +#endif /* Fists.h */ diff --git a/g_local.h b/g_local.h new file mode 100644 index 0000000..ff53bac --- /dev/null +++ b/g_local.h @@ -0,0 +1,580 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/g_local.h $ +// $Revision:: 98 $ +// $Author:: Jimdose $ +// $Date:: 11/10/98 5:51p $ +// +// Copyright (C) 1998 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/g_local.h $ +// +// 98 11/10/98 5:51p Jimdose +// disabled exporting symbols from the dll +// +// 97 11/10/98 4:34p Jimdose +// added clearsavegames to level +// +// 96 11/08/98 10:47p Jimdose +// moved earthquake to level struct +// +// 95 11/06/98 5:18p Jimdose +// added missionfailed and missionfailedtime to level vars +// when a mission has failed or the player is dead in single player, the game +// code immediately shows the loadmenu, preventing them from letting the game +// continue running if they exit the menu +// +// 94 10/27/98 9:46p Aldie +// Changed training cvar to level.training +// +// 93 10/26/98 2:16p Aldie +// Added AirClamp +// +// 92 10/25/98 11:49p Jimdose +// added EXPORT_TEMPLATE +// moved playerfrozen to level +// +// 91 10/25/98 6:26p Markd +// Added in no_jc abililty +// +// 90 10/24/98 2:18p Markd +// Added stufftext service +// +// 89 10/19/98 9:52p Jimdose +// changed slime variables to lightvolume +// +// 88 10/10/98 1:33a Jimdose +// moved include of g_utils.h to be before g_spawn.h +// +// 87 10/08/98 12:38a Jimdose +// Made savegames work +// +// 86 9/30/98 5:39p Aldie +// Added showinfo command +// +// 85 9/27/98 6:28p Aldie +// Added water, slime, and lava colors to worldspawn +// +// 84 9/19/98 5:01p Markd +// took out current_music and fallback_music +// +// 83 9/10/98 8:34p Markd +// Removed hidestats and drawoverlay from level variables +// +// 82 9/07/98 8:29p Markd +// Added fullmins fullmaxs and fullradius +// +// 81 9/02/98 7:48p Aldie +// Added ValidPlayerModels list +// +// 80 8/29/98 9:52p Jimdose +// General cleanup of g_local.h +// got rid of unused variables and macros +// moved prototypes to their appropriate headers +// +// 79 8/28/98 3:46p Markd +// Added centroid to edict_s +// +// 78 8/27/98 9:06p Jimdose +// Moved some prototypes that didn't belong here to g_utils.h +// Made STEPSIZE global +// +// 77 8/27/98 2:31p Aldie +// Added adrenaline flags +// +// 76 8/25/98 7:47p Jimdose +// Added gravaxis to SelectSpawnPoint +// +// 75 8/21/98 5:26p Markd +// Added sv_precache and cl_precache +// +// 74 8/19/98 8:50p Aldie +// Changed MOD to enumerated type +// +// 73 8/17/98 7:43p Markd +// Added MOD_LAVA and MOD_SLIME +// +// 72 8/17/98 4:35p Markd +// Added cinematic variable +// +// 71 8/14/98 8:14p Aldie +// Added generic overlay system +// +// 70 8/08/98 8:24p Markd +// Added MOD_THROWOBJECT +// +// 69 8/07/98 6:01p Aldie +// Added frag credits for falling damage +// +// 68 8/04/98 6:06p Aldie +// Relocated SPAWNFLAG_DETAIL +// +// 67 8/03/98 7:53p Jimdose +// Added G_DrawDebugNumber +// +// 66 8/03/98 7:36p Markd +// Added MOD_DEBRIS +// +// 65 8/02/98 9:00p Markd +// Merged code 3.17 +// +// 64 7/24/98 6:19p Aldie +// enable and disable huds and dialog checking +// +// 63 7/23/98 2:32p Aldie +// Made speed of rocket a tweak cvar +// +// 62 7/21/98 10:05p Markd +// Added FL_DIE_EXPLODE +// +// 61 7/21/98 9:06p Markd +// Added current_mood and fallback_mood +// +// 60 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 59 7/18/98 5:08p Aldie +// Added showdamage +// +// 58 7/17/98 7:58p Markd +// Added radius to G_FullTrace +// +// 57 7/15/98 9:59p Markd +// Added FL_SHIELDS +// +// 56 7/13/98 5:01p Aldie +// Added dead player bodies with gibbing +// +// 55 7/09/98 10:38p Aldie +// Moved body parts to game +// +// 54 7/08/98 12:58p Jimdose +// made developer cvar global +// +// 53 7/03/98 12:02p Aldie +// Updated client persistent data +// +// 52 7/02/98 2:34p Aldie +// Mission computer +// +// 51 6/23/98 9:55p Jimdose +// Fixed infinite loop bug in G_RunFrame +// +// 50 6/15/98 10:07p Jimdose +// Added G_FullTrace +// +// 49 6/08/98 11:42a Aldie +// Added MOVETYPE_SLIDE +// +// 48 5/27/98 5:28a Aldie +// Added sv_gibs +// +// 47 5/27/98 5:04a Aldie +// Added queue for bloodsplats +// +// 46 5/25/98 6:52a Aldie +// Added maxbulletholes +// +// 45 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 44 5/23/98 12:53p Aldie +// Updated surfaces networking. +// +// 43 5/20/98 7:17p Markd +// Added G_DebugBBox +// +// 42 5/20/98 11:12a Markd +// removed char * dependency +// +// 41 5/14/98 10:21p Jimdose +// Added G_NextEntity +// Made G_MoveStep return extended failure information +// +// 40 5/13/98 4:47p Aldie +// Update damage surfaces +// +// 39 5/12/98 7:07p Markd +// Put in development spawnflag +// +// 38 5/11/98 5:53p Markd +// Added FL_DIE_GIBS +// +// 37 5/08/98 7:01p Markd +// Added FL_DARKEN flag +// +// 36 5/05/98 2:49p Jimdose +// removed kick_angles, kick_origin, v_dmg_roll, v_dmg_pitch, v_dmg_time, +// fall_time, fall_value, damage_alpha, bonus_alpha, damage_blend, v_angle, +// bobtime, oldviewangles, and oldvelocity from gclient_t. Most are moved into +// Player class. +// +// 35 5/05/98 2:41p Aldie +// added server side surface states +// +// 34 5/03/98 4:44p Jimdose +// added line drawing utility functions similar to opengl +// +// 33 5/02/98 8:46p Markd +// Changed gamedir from Basesin1 to BASE +// +// 32 5/01/98 5:08p Jimdose +// added prototype for world as an Entity *. This points to the entity created +// by WorldSpawn +// +// 31 4/30/98 9:24p Jimdose +// Changed use of string to str class +// +// 30 4/30/98 4:48p Aldie +// Server side console states +// +// 29 4/28/98 7:00p Aldie +// Added sever side console buffer +// +// 28 4/27/98 3:21p Jimdose +// Added DebugLines +// +// 27 4/27/98 1:51p Aldie +// Added server side console states. +// +// 26 4/16/98 2:09p Jimdose +// Removed navigation_numbeacons +// +// 25 4/10/98 4:57p Jimdose +// added spawntime to edict +// +// 24 4/10/98 1:24a Markd +// Added new FL_ flags for sparks, blood, tesselation, blastmarks and +// die_tesselation +// +// 23 4/07/98 3:49p Aldie +// Added zooming crosshair +// +// 22 4/04/98 6:14p Jimdose +// Added G_GetNameForSurface +// +// 21 4/02/98 4:51p Jimdose +// Added stats and deathmatch scoreboard +// +// 20 3/30/98 10:43p Jimdose +// Added G_Movestep +// +// 19 3/28/98 4:36p Jimdose +// Added deathmatch starts +// +// 18 3/27/98 5:39p Jimdose +// Made BeginIntermission accept a string instead of an edict +// +// 17 3/26/98 8:15p Jimdose +// Added coop variable +// Removed unused structures and prototypes +// Added prototypes of game interface functions +// +// 16 3/24/98 5:03p Jimdose +// Included listener.h in place of g_event.h +// +// 15 3/24/98 4:30p Aldie +// New svc commands for consoles. +// +// 14 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 13 3/11/98 7:38p Markd +// Added current_viewthing +// +// 12 3/04/98 8:00p Aldie +// More support for damage surfaces. +// +// 11 3/02/98 5:49p Jimdose +// Added num_navbeacons to level_locals_t +// Added entname to edict_s +// Added findradius +// Removed unused Q2 functions +// Added include of string.h from standard C++ library +// +// 10 2/21/98 1:16p Jimdose +// Added G_TestMove and G_TestStepMove for use in pathfinding code +// Removed buttons, oldbuttons, and latched_buttons from gclient_t structure +// +// 9 2/18/98 8:08p Jimdose +// Prototyped IsNumeric +// +// 8 2/16/98 2:23p Jimdose +// Added active_edicts and free_edicts for faster entity processing and for +// operations that depend on the order that physics is processed on entities +// (such as binding). +// Added next and prev fields to edict_t +// Included linklist.h +// +// 7 2/09/98 2:35p Aldie +// Removed const of vec_origin and vec_zero +// +// 6 2/09/98 11:55a Jimdose +// Made vec_zero and vec_origin const +// Prototyped G_PushMove +// +// 5 2/06/98 5:50p Jimdose +// Update prototypes +// +// 4 2/03/98 11:06a Jimdose +// In process of converting to work with Sin progs +// +// 3 12/30/97 6:04p Jimdose +// Added header text +// +// DESCRIPTION: +// local definitions for game module +// + +#ifndef __G_LOCAL_H__ +#define __G_LOCAL_H__ + +#if 0 + +#define EXPORT_FROM_DLL __declspec( dllexport ) +#define EXPORT_TEMPLATE + +#else + +#define EXPORT_FROM_DLL + +#endif + +#include "q_shared.h" + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and edict_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "game.h" +#include "container.h" +#include "str.h" + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "base" + +// memory tags to allow dynamic memory to be cleaned up +#define TAG_GAME 765 // clear when unloading the dll +#define TAG_LEVEL 766 // clear when loading a new level + +// protocol bytes that can be directly added to messages +#define svc_muzzleflash 1 +#define svc_muzzleflash2 2 +#define svc_temp_entity 3 +#define svc_layout 4 +#define svc_inventory 5 +#define svc_console_command 6 +#define svc_stufftext 12 + +// handedness values +#define RIGHT_HANDED 0 +#define LEFT_HANDED 1 +#define CENTER_HANDED 2 + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +// predefine Entity so that we can add it to edict_t without any errors +class Entity; + +// +// Exported templated classes must be explicitly instantiated +// +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +// client data that stays across multiple level loads +typedef struct + { + char userinfo[MAX_INFO_STRING]; + char netname[16]; + char model[MAX_QPATH]; + char skin[MAX_QPATH]; + int hand; + + // values saved and restored from edicts when changing levels + int health; + int max_health; + + } client_persistant_t; + +// client data that stays across deathmatch respawns +typedef struct + { + int enterframe; // level.framenum the client entered the game + int score; // frags, etc + vec3_t cmd_angles; // angles sent over in the last command + } client_respawn_t; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +typedef struct gclient_s + { + // known to server + player_state_t ps; // communicated by server to clients + int ping; + + // private to game + client_persistant_t pers; + client_respawn_t resp; + qboolean showinfo; + } gclient_t; + +struct edict_s + { + entity_state_t s; + gclient_t *client; // NULL if not a player + // the server expects the first part + // of gclient_t to be a player_state_t + // but the rest of it is opaque + + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + vec3_t fullmins, fullmaxs; + float fullradius; + vec3_t centroid; + solid_t solid; + int clipmask; + edict_t *owner; + + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + + //================================ + Entity *entity; + float freetime; // sv.time when the object was freed + float spawntime; // sv.time when the object was spawned + + char entname[ 64 ]; + + edict_t *next; + edict_t *prev; + }; + +#include "vector.h" +#include "linklist.h" +#include "class.h" + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// + +class EXPORT_FROM_DLL game_locals_t : public Class + { + public: + CLASS_PROTOTYPE( game_locals_t ); + + gclient_t *clients; // [maxclients] + qboolean autosaved; + + // can't store spawnpoint in level, because + // it would get overwritten by the savegame restore + str spawnpoint; // needed for coop respawns + + // store latched cvars here that we want to get at often + int maxclients; + int maxentities; + int maxconsoles; + int maxsurfaces; + + qboolean force_entnum; + int spawn_entnum; + + // List of valid player models loaded from players global scriptfile + Container ValidPlayerModels; + + game_locals_t(); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// +class EXPORT_FROM_DLL level_locals_t : public Class + { + public: + CLASS_PROTOTYPE( level_locals_t ); + + int framenum; + float time; + + str level_name; // the descriptive name (Outer Base, etc) + str mapname; // the server name (base1, etc) + str nextmap; // go here when fraglimit is hit + + // used for cinematics + qboolean playerfrozen; + + // used to prevent players from continuing failed games + qboolean missionfailed; + float missionfailedtime; + + // intermission state + float intermissiontime; // time the intermission was started + int exitintermission; + + edict_t *next_edict; // Used to keep track of the next edict to process in G_RunFrame + + int total_secrets; + int found_secrets; + + Entity *current_entity; // entity running from G_RunFrame + + // FIXME - remove this later when it is passed in the event. + trace_t impact_trace; + + int body_queue; + + float earthquake; + + qboolean clearsavegames; + qboolean cinematic; + + qboolean no_jc; + + // Blending color for water, light volumes,lava + Vector water_color; + Vector lightvolume_color; + Vector lava_color; + float water_alpha; + float lightvolume_alpha; + float lava_alpha; + qboolean airclamp; + qboolean training; + + level_locals_t(); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +#include "g_main.h" +#include "listener.h" +#include "g_utils.h" +#include "g_spawn.h" +#include "g_phys.h" + +#endif diff --git a/g_main.cpp b/g_main.cpp new file mode 100644 index 0000000..93b6aec --- /dev/null +++ b/g_main.cpp @@ -0,0 +1,3330 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/g_main.cpp $ +// $Revision:: 168 $ +// $Author:: Aldie $ +// $Date:: 12/08/98 7:04p $ +// +// Copyright (C) 1998 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/g_main.cpp $ +// +// 168 12/08/98 7:04p Aldie +// Added setup and shutdown calls to comm for arcade +// +// 167 11/17/98 6:21p Aldie +// Put a check for DM in the fixbodies +// +// 166 11/17/98 4:07a Markd +// put in stopsound when changing levels +// +// 165 11/15/98 11:31p Markd +// changed default fat rocket setting +// +// 164 11/15/98 9:02p Jimdose +// added sv_fatrockets +// +// 163 11/14/98 2:55a Aldie +// Don't update skin in single player in the clientuserinfo. The player Init +// function will take care of it. +// +// 162 11/13/98 11:02p Jimdose +// made fov persistant across levels and after zooming in and out +// +// 161 11/13/98 10:05p Jimdose +// G_SaveClientData is now called when exiting the level, instead of when the +// level is saved. This should fix the problem of the player keeping objects +// from the previous level when he crosses an episode (when savegames are +// cleared out). +// +// 160 11/13/98 2:38a Aldie +// Fixed userinfo changed so manumit is preserved in loadgames +// +// 159 11/11/98 2:56p Aldie +// Fix for deadbody code +// +// 158 11/10/98 8:05p Aldie +// Fix dead bodies for players changing models +// +// 157 11/10/98 4:34p Jimdose +// added clearsavegames to level +// +// 156 11/09/98 1:04a Markd +// made parentmode not LATCHED +// +// 155 11/09/98 12:55a Jimdose +// added sv_footsteps cvar so that server admins can turn footsteps off +// completely +// added CVAR_USERINFO to parentmode +// +// 154 11/08/98 10:48p Jimdose +// moved earthquake to level struct +// upped savegame version +// +// 153 11/08/98 8:09p Aldie +// Upped the savegame version +// +// 152 11/06/98 10:04p Jimdose +// Added G_AllocDebugLines +// +// 151 11/06/98 5:32p Jimdose +// added missionfailed and missionfailedtime to level vars +// when a mission has failed or the player is dead in single player, the game +// code immediately shows the loadmenu, preventing them from letting the game +// continue running if they exit the menu +// +// 150 10/27/98 9:46p Aldie +// Changed training cvar to level.training +// +// 149 10/27/98 6:52p Jimdose +// changed savegame version +// +// 148 10/27/98 5:41a Jimdose +// upped the savegame version +// +// 147 10/27/98 3:43a Aldie +// Removed the "loading" command string +// +// 146 10/26/98 5:14p Jimdose +// change recalcpaths to ai_recalcpaths +// +// 145 10/26/98 4:42p Jimdose +// added recalcpaths +// +// 144 10/26/98 2:17p Aldie +// Added AirClamp +// +// 143 10/25/98 11:56p Jimdose +// moved playerfrozen from game to level +// +// 142 10/25/98 10:11p Markd +// made default dialog be mode 2 instead of mode 3 +// +// 141 10/25/98 9:10p Markd +// made dialog variable archived +// +// 140 10/25/98 6:26p Markd +// Added in no_jc abililty +// +// 139 10/25/98 6:09p Aldie +// Added training cvar +// +// 138 10/25/98 4:53a Jimdose +// Increased the savegame version +// +// 137 10/25/98 12:48a Markd +// changed manumit.def to manumit_pl.def +// +// 136 10/24/98 6:05p Jimdose +// made waitForPlayer work in change level threads +// upped the savegame version number +// +// 135 10/24/98 3:34p Markd +// Added loading command, fixed typo +// +// 134 10/24/98 2:17p Markd +// Put in a loading in exit level +// +// 133 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 132 10/23/98 6:46p Aldie +// Archive maxclients +// +// 131 10/22/98 4:57p Aldie +// Removed blastscale_z value +// +// 130 10/22/98 3:27a Jimdose +// changed the savegame version +// +// 129 10/21/98 6:42p Markd +// Added sv_drawtrace +// +// 128 10/20/98 10:30p Jimdose +// added savegame version +// +// 127 10/20/98 2:21a Aldie +// Added sv_maplist for map rotation +// +// 126 10/19/98 9:54p Jimdose +// changed slime variables to lightvolume +// +// 125 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 124 10/18/98 8:42p Jimdose +// Cleaned unnecessary or slow stuff from G_RunFrame +// +// 123 10/18/98 3:23a Jimdose +// Added code for timing entities +// +// 122 10/17/98 12:20a Jimdose +// Save games now archive paths +// Fixed bug due to ReadLevel not freeing the spawned entities +// G_SaveClientData doesn't save data during deathmatch +// +// 121 10/16/98 1:50a Jimdose +// Added FL_DONTSAVE flag to mark entities that shouldn't be saved to savegames +// Send end level event to clients on level exit +// Added autosave variable to WriteLevel +// +// 120 10/14/98 11:55p Markd +// put in assert( 0 ) in G_Error +// +// 119 10/14/98 10:55p Jimdose +// More work on persitant data +// +// 118 10/14/98 1:17a Jimdose +// Got cross-level persistant info working +// +// 117 10/11/98 8:51p Jimdose +// Added LoadingServer variable +// +// 116 10/10/98 1:26a Jimdose +// added G_Precache +// +// 115 10/08/98 7:18p Jimdose +// Added IP filtering +// Removed noexit and samelevel cvars since they are dmflags +// fixed bug with restarting level +// +// 114 10/08/98 12:38a Jimdose +// Made savegames work +// +// 113 9/30/98 5:39p Aldie +// Added showinfo command +// +// 112 9/26/98 4:44p Aldie +// Added mutant mode +// +// 111 9/25/98 4:36p Markd +// replaced Event::Find with Event::Exists +// +// 110 9/24/98 1:49a Jimdose +// Added g_showmem +// +// 109 9/22/98 3:21p Markd +// put in parentmode lockout for blood and gibs +// +// 108 9/19/98 5:01p Markd +// took out current_music and fallback_music +// +// 107 9/19/98 4:33p Jimdose +// added eventlist client command +// +// 106 9/18/98 5:37p Aldie +// Removed dead code +// +// 105 9/11/98 2:49p Aldie +// Cleaned up intermission stuff +// +// 104 9/10/98 8:38p Aldie +// Electrical beam effects +// +// 103 9/09/98 3:03p Markd +// Fixed deathmatch camera +// +// 102 9/07/98 8:29p Markd +// Added fulltrace bboxes +// +// 101 9/03/98 2:49p Aldie +// Changed default of sv_showdamagelocation to 0 +// +// 100 9/02/98 7:46p Aldie +// Added ValidPlayerModels list +// +// 99 8/31/98 4:44p Markd +// fixed setting on non-blade in demo and non-deathmatch +// +// 98 8/29/98 9:41p Jimdose +// Made all function names consistantly begin with G_ +// Added call info to G_Trace +// +// 97 8/21/98 5:26p Markd +// Added sv_precache and cl_precache +// +// 96 8/19/98 8:49p Aldie +// Added sv_showdamagelocation for kicks +// +// 95 8/13/98 8:10p Aldie +// Deathmatch score bug +// +// 94 8/13/98 7:30p Aldie +// New deathmatch scoreboard +// +// 93 8/08/98 7:29p Aldie +// Added intermissions for deathmatch +// +// 92 8/07/98 4:20p Aldie +// Fixed say command when command is not known +// +// 91 8/07/98 2:28p Aldie +// Fixed a bug where dead person could go into idle animation +// +// 90 8/03/98 7:54p Jimdose +// Added sv_showentnums to show the entity number above any entity +// +// 89 8/02/98 9:00p Markd +// Merged code 3.17 +// +// 88 7/31/98 8:08p Jimdose +// Script commands now include flags to indicate cheats and console commands +// +// 87 7/31/98 4:19p Jimdose +// Fixed deathmatch cheats +// +// 86 7/26/98 9:29a Aldie +// Fixed parameter error on skin checking +// +// 85 7/26/98 9:12a Aldie +// Fix multiplayer skin lookups for invalid models +// +// 84 7/26/98 5:32a Markd +// put in rudimentary savegame +// +// 83 7/25/98 3:12p Aldie +// Initialize showdamage to 0 +// +// 82 7/24/98 10:03p Aldie +// Changed the gibs layout +// +// 81 7/24/98 6:17p Aldie +// Dialog checking +// +// 80 7/24/98 4:51p Jimdose +// Bounding boxes no longer show up in deathmatch +// +// 79 7/24/98 3:47p Aldie +// Don't allow invalid models, skins, etc... +// +// 78 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 +// +// 77 7/23/98 2:32p Aldie +// Made speed of rocket a tweak cvar +// +// 76 7/21/98 9:04p Markd +// Added current_mood and fallback mood for music +// +// 75 7/18/98 5:04p Aldie +// Added showdamage +// +// 74 7/13/98 4:59p Aldie +// Added dead player bodies with gibbing +// +// 73 7/11/98 8:58p Markd +// Removed testthread command +// +// 72 7/11/98 8:42p Markd +// Added testthread command +// +// 71 7/10/98 6:20a Jimdose +// Added "add" command to add a value to a cvar +// Added G_DrawCSystem for use in debugging orientations +// +// 70 7/09/98 10:38p Aldie +// Put bodyparts into game init +// +// 69 7/08/98 12:54p Jimdose +// Made developer c_var global +// +// 68 7/08/98 12:23p Aldie +// Updated deathmatch scoreboard +// +// 67 7/03/98 12:01p Aldie +// New userinfo stuff +// +// 66 7/01/98 7:02p Aldie +// Removed zoom crosshair stuff +// +// 65 6/23/98 9:54p Jimdose +// Fixed infinite loop bug in G_RunFrame +// +// 64 6/21/98 6:11p Jimdose +// set level.current_entity in G_ClientThink +// +// 63 6/19/98 6:37p Aldie +// Removed one of the stat #defines +// +// 62 6/18/98 8:46p Jimdose +// Added better event error handling +// Added source info to events +// +// 61 6/15/98 8:04p Markd +// changed max_entities +// +// 60 6/10/98 9:31p Markd +// Put in infiite loop checking into main loop +// +// 59 6/09/98 4:19p Jimdose +// Fixed infinite loop bug in G_RunFrame +// +// 58 5/27/98 5:27a Aldie +// added sv_gibs flag +// +// 57 5/27/98 5:02a Aldie +// Added gibs and gore +// +// 56 5/25/98 6:51a Aldie +// Added sv_maxbulletholes +// +// 55 5/24/98 9:10p Markd +// added another mode to sv_showbboxes +// +// 54 5/24/98 8:34p Markd +// changed max_entities +// +// 53 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 52 5/20/98 7:16p Markd +// Added ClientDrawBoundingBoxes +// +// 51 5/18/98 8:13p Jimdose +// Renamed Navigator back to PathManager +// +// 50 5/14/98 10:11p Jimdose +// g_edicts is now initialized to NULL +// +// 49 5/09/98 8:02p Jimdose +// Added ai commands to Clientcommand. +// Disables Cmd_Say_f +// Added path saving +// +// 48 5/08/98 2:54p Jimdose +// working on Cmd_Say_f +// +// 47 5/07/98 10:57p Jimdose +// Fixed gi.error crashing in ClientCommand +// +// 46 5/05/98 2:43p Aldie +// Added server side surface states +// +// 45 5/03/98 4:31p Jimdose +// Increased MAX_DEBUG_LINES +// +// 44 5/02/98 8:37p Aldie +// More console stuff for demos +// +// 43 4/29/98 5:04p Jimdose +// Fixed gi.error so that it doesn't crash when called from C++ +// +// 42 4/28/98 8:16p Jimdose +// Added checks to ensure that SOLID_BSP objects have models +// +// 41 4/28/98 6:59p Aldie +// Added server side console buffer +// +// 40 4/27/98 6:09p Jimdose +// Added debug lines +// +// 39 4/27/98 5:28p Aldie +// Added server side console states. +// +// 38 4/23/98 5:00p Jimdose +// Added ai_debugpath +// +// 37 4/20/98 2:45p Jimdose +// working on ai +// +// 36 4/18/98 3:01p Jimdose +// Added ai_createnodes and ai_showpath +// +// 35 4/16/98 2:03p Jimdose +// edict->s.prevframe is set to -1 (cleared) at the beginning of each frame +// Removed pathmanager +// +// 34 4/08/98 12:20a Jimdose +// Made viewcommands unavailble without cheats enabled. +// +// 33 4/07/98 3:48p Aldie +// Added zooming crosshair +// +// 32 4/06/98 7:10p Aldie +// Added zooming for SniperRifle +// +// 31 4/06/98 5:42p Jimdose +// G_RunFrame now clears RF_FRAMELERP in renderfx and sets the oldorigin on +// entities +// +// 30 4/05/98 11:02p Jimdose +// Took out that lastorigin bullshit +// +// 29 4/05/98 10:34p Jimdose +// Added lastorigin to entity so that we can properly track oldorigin since +// worldorigin may be changed prior to setting oldorigin +// +// 28 4/05/98 2:56a Jimdose +// fixed bug where deathmatch scores never went away +// +// 27 4/05/98 1:56a Jimdose +// Fixed bug where oldorigin was being updated with origin instead of +// worldorigin +// +// 26 4/04/98 6:03p Jimdose +// Got rid of unreferenced variable +// +// 25 4/03/98 3:36p Jimdose +// Defined draw flags for STAT_LAYOUTS +// +// 24 4/03/98 1:10p Aldie +// Added consolevar +// +// 23 4/02/98 4:49p Jimdose +// Added stats and scoreboard +// +// 22 3/30/98 2:43p Jimdose +// Started on status bar and dm scores +// +// 21 3/27/98 5:36p Jimdose +// Enabled level changing +// +// 20 3/27/98 12:05p Aldie +// Added consolecmd +// +// 19 3/26/98 8:23p Jimdose +// Added coop variable +// made deathmatch work +// changed groundentity to an edict_t * +// Changed assertions when traversing active entities so that it doesn't crash +// out +// +// 18 3/24/98 4:54p Jimdose +// G_RunFrame now does pre and post physics checks for pending events +// +// 17 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 16 3/15/98 5:06p Aldie +// fixed bug with viewspan and appending models +// +// 15 3/13/98 10:52a Markd +// fixed bug +// +// 14 3/13/98 10:48a Markd +// Prepended "models/" to the beginning of viewmodel and viewspawn +// +// 13 3/12/98 9:49a Markd +// Re-worked viewthing commands +// +// 12 3/11/98 11:31a Markd +// Re-worked viewthing commands a bunch +// +// 11 3/07/98 2:04p Markd +// Fixed View* commands +// +// 10 3/05/98 6:43p Markd +// Added viewthing support +// +// 9 3/02/98 5:47p Jimdose +// ShutdownGame now frees all entities and paths before exiting +// +// 8 2/21/98 7:32p Jimdose +// Added checks for cl_oldladders and cl_oldnoclip in ClientUserinfoChanged +// +// 7 2/21/98 1:14p Jimdose +// Temporarily bumped up maxentities +// Made player commands act as script commands +// Fixed physics loop so that old_origin is only set when RF_BEAM is not set. +// +// 6 2/17/98 7:01p Jimdose +// gameVars are cleared upon game startup +// +// 5 2/16/98 2:22p Jimdose +// Added active_edicts and free_edicts for faster entity processing and for +// operations that depend on the order that physics is processed on entities +// (such as binding). +// +// 4 2/09/98 2:35p Aldie +// Removed const of vec_origin and vec_zero +// +// 3 2/09/98 11:54a Jimdose +// Made vec_zero and vec_origin const +// +// 2 2/03/98 11:05a Jimdose +// In process of converting to work with Sin progs +// +// 1 1/23/98 5:53p Jimdose +// +// 3 12/30/97 6:04p Jimdose +// Added header text +// +// DESCRIPTION: +// + +#define SAVEGAME_VERSION 13 + +#include +#include "g_local.h" +#include "g_utils.h" +#include "Entity.h" +#include "vector.h" +#include "scriptmaster.h" +#include "navigate.h" +#include "viewthing.h" +#include "console.h" +#include "player.h" +#include "surface.h" +#include "gravpath.h" +#include "deadbody.h" + +#ifdef SIN_ARCADE +#include "arcade_comm.h" +#endif + +Vector vec_origin = "0 0 0"; +Vector vec_zero = "0 0 0"; + +qboolean LoadingSavegame = false; +qboolean LoadingServer = false; +game_locals_t game; +level_locals_t level; +game_import_t gi; +game_export_t globals; + +edict_t *g_edicts = NULL; +edict_t active_edicts; +edict_t free_edicts; + +netconsole_t *g_consoles; +netconbuffer_t *g_conbuffers; +netsurface_t *g_surfaces; + +cvar_t *developer; +cvar_t *deathmatch; +cvar_t *coop; +cvar_t *dmflags; +cvar_t *skill; +cvar_t *fraglimit; +cvar_t *timelimit; +cvar_t *password; + +cvar_t *filterban; + +cvar_t *flood_msgs; +cvar_t *flood_persecond; +cvar_t *flood_waitdelay; + +cvar_t *maxclients; +cvar_t *maxentities; +cvar_t *maxconsoles; +cvar_t *maxsurfaces; +cvar_t *g_select_empty; +cvar_t *g_unlimited_ammo; +cvar_t *nomonsters; +cvar_t *dm_respawn; +cvar_t *dialog; +cvar_t *precache; +cvar_t *g_showmem; +cvar_t *g_timeents; + +cvar_t *sv_maxvelocity; +cvar_t *sv_gravity; + +cvar_t *dedicated; + +cvar_t *sv_rollspeed; +cvar_t *sv_rollangle; +cvar_t *gun_x; +cvar_t *gun_y; +cvar_t *gun_z; + +cvar_t *run_pitch; +cvar_t *run_roll; +cvar_t *bob_up; +cvar_t *bob_pitch; +cvar_t *bob_roll; + +cvar_t *sv_cheats; +cvar_t *sv_showbboxes; +cvar_t *sv_showentnums; +cvar_t *sv_rocketspeed; +cvar_t *sv_rocketrate; + +cvar_t *sv_stopspeed; +cvar_t *sv_friction; +cvar_t *sv_waterfriction; +cvar_t *sv_waterspeed; + +cvar_t *sv_maxbulletholes; +cvar_t *sv_maxbloodsplats; +cvar_t *sv_gore; +cvar_t *sv_gibs; +cvar_t *sv_showdamage; +cvar_t *sv_showdamagelocation; +cvar_t *sv_traceinfo; +cvar_t *sv_drawtrace; +cvar_t *sv_maplist; +cvar_t *sv_footsteps; +cvar_t *sv_fatrockets; + +cvar_t *csys_posx; +cvar_t *csys_posy; +cvar_t *csys_posz; +cvar_t *csys_x; +cvar_t *csys_y; +cvar_t *csys_z; +cvar_t *csys_draw; + +cvar_t *parentmode; + +int sv_numtraces; + +usercmd_t *current_ucmd; + +void G_AllocDebugLines( void ); +void G_ClientDrawBoundingBoxes( void ); +void ( *ServerError )( const char *fmt, ... ); +char G_ErrorMessage[ 1024 ]; +jmp_buf G_AbortGame; + +/* +=============== +G_Error + +Abort the server with a game error +=============== +*/ +void G_Error + ( + const char *fmt, + ... + ) + + { + va_list argptr; + + va_start( argptr, fmt ); + vsprintf( G_ErrorMessage, fmt, argptr ); + va_end( argptr ); + + assert( 0 ); + + longjmp( G_AbortGame, -1 ); + } + +/* +=============== +G_ExitWithError + +Calls the server's error function with the last error that occurred. +Should only be called after a setjmp( G_AbortGame ) call +=============== +*/ +void G_ExitWithError + ( + void + ) + + { + ServerError( G_ErrorMessage ); + } + +/* +================= +G_ShutdownGame + +Frees up any resources +================= +*/ +void G_ShutdownGame + ( + void + ) + + { + gi.dprintf ("==== ShutdownGame ====\n"); + + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + G_LevelShutdown(); + + gi.FreeTags (TAG_GAME); +#ifdef SIN_ARCADE + ARCADE_CloseCommunications(); +#endif + } + +/* +============ +G_InitGame + +This will be called when the dll is first loaded, which +only happens when a new game is begun +============ +*/ +void G_InitGame + ( + void + ) + + { + gi.dprintf ("==== InitGame ====\n"); + + // Install our own error handler, since we can't + // call the EXE's handler from within a C++ class + ServerError = gi.error; + gi.error = G_Error; + + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + // initialize the game variables + gameVars.ClearList(); + + gun_x = gi.cvar ("gun_x", "0", 0); + gun_y = gi.cvar ("gun_y", "0", 0); + gun_z = gi.cvar ("gun_z", "0", 0); + + developer = gi.cvar( "developer", "0", 0 ); + precache = gi.cvar( "sv_precache", "1", 0 ); + + //FIXME: sv_ prefix is wrong for these + sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0); + sv_rollangle = gi.cvar ("sv_rollangle", "2", 0); + sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0); + sv_gravity = gi.cvar ("sv_gravity", "800", 0); + sv_maxbulletholes = gi.cvar ("sv_maxbulletholes", "32", 0); + sv_maxbloodsplats = gi.cvar ("sv_maxbloodspats", "5", 0); + sv_gore = gi.cvar ("sv_gore", "1", 0); + sv_gibs = gi.cvar ("sv_gibs", "1", 0); + sv_showdamage = gi.cvar ("sv_showdetaildamage", "0", 0); + sv_showdamagelocation = gi.cvar ("sv_showdamagelocation", "0", 0); + + // noset vars + dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET); + + // latched vars + sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH); + deathmatch = gi.cvar ("deathmatch", "0", CVAR_SERVERINFO|CVAR_LATCH); + coop = gi.cvar ("coop", "0", CVAR_SERVERINFO|CVAR_LATCH); + skill = gi.cvar ("skill", "1", CVAR_SERVERINFO|CVAR_LATCH); +#ifdef SIN + maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE ); +#else + maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); +#endif + maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH); + maxconsoles = gi.cvar ("maxconsoles", "32", CVAR_LATCH); + maxsurfaces = gi.cvar ("maxsurfaces", "1024", CVAR_LATCH); + + // flood control + flood_msgs = gi.cvar ("flood_msgs", "4", 0); + flood_persecond = gi.cvar ("flood_persecond", "4", 0); + flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0); + + // change anytime vars + password = gi.cvar ("password", "", CVAR_USERINFO); + filterban = gi.cvar ("filterban", "1", 0); + dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO); + fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO); + timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO); + g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE); + g_unlimited_ammo = gi.cvar ("g_unlimited_ammo", "0", CVAR_SERVERINFO); + g_showmem = gi.cvar ("g_showmem", "0", 0 ); + g_timeents = gi.cvar ("g_timeents", "0", 0 ); + dm_respawn = gi.cvar ("dm_respawn", "2", CVAR_SERVERINFO); + nomonsters = gi.cvar ("nomonsters", "0", CVAR_SERVERINFO); + dialog = gi.cvar ("dialog", "2", CVAR_SERVERINFO | CVAR_ARCHIVE ); + + run_pitch = gi.cvar ("run_pitch", "0.002", 0); + run_roll = gi.cvar ("run_roll", "0.005", 0); + bob_up = gi.cvar ("bob_up", "0.005", 0); + bob_pitch = gi.cvar ("bob_pitch", "0.002", 0); + bob_roll = gi.cvar ("bob_roll", "0.002", 0); + + bob_roll = gi.cvar ("bob_roll", "0.002", 0); + + csys_posx = gi.cvar ("csys_posx", "0", 0); + csys_posy = gi.cvar ("csys_posy", "0", 0); + csys_posz = gi.cvar ("csys_posz", "0", 0); + csys_x = gi.cvar ("csys_x", "0", 0); + csys_y = gi.cvar ("csys_y", "0", 0); + csys_z = gi.cvar ("csys_z", "0", 0); + csys_draw = gi.cvar ("csys_draw", "0", 0); + + sv_traceinfo = gi.cvar ("sv_traceinfo", "0", 0); + sv_drawtrace = gi.cvar ("sv_drawtrace", "0", 0); + + // debug stuff + sv_showbboxes = gi.cvar ("sv_showbboxes", "0", 0); + sv_showentnums = gi.cvar ("sv_showentnums", "0", 0); + sv_rocketspeed = gi.cvar ("sv_rocketspeed", "300", 0); + sv_rocketrate = gi.cvar ("sv_rocketrate", "1.2", 0); + + sv_friction = gi.cvar ("sv_friction", "4", CVAR_SERVERINFO); + sv_stopspeed = gi.cvar ("sv_stopspeed", "100", CVAR_SERVERINFO); + sv_waterfriction = gi.cvar ("sv_waterfriction", "1", CVAR_SERVERINFO); + sv_waterspeed = gi.cvar ("sv_waterspeed", "400", CVAR_SERVERINFO); + sv_maplist = gi.cvar ("sv_maplist", "", CVAR_SERVERINFO|CVAR_ARCHIVE); + sv_footsteps = gi.cvar ("sv_footsteps", "1", CVAR_SERVERINFO|CVAR_ARCHIVE); + + if ( deathmatch->value ) + { + sv_fatrockets = gi.cvar ("sv_fatrockets", "1", CVAR_SERVERINFO); + } + else + { + sv_fatrockets = gi.cvar ("sv_fatrockets", "1", CVAR_SERVERINFO); + } + + parentmode = gi.cvar ("parentmode", "0", CVAR_USERINFO|CVAR_SERVERINFO|CVAR_ARCHIVE); + + G_InitEvents(); + sv_numtraces = 0; + + game.maxentities = maxentities->value; + if (maxclients->value * 8 > game.maxentities) + { + game.maxentities = maxclients->value * 8; + } + game.maxclients = maxclients->value; + game.maxconsoles = maxconsoles->value; + game.maxsurfaces = maxsurfaces->value; + G_AllocGameData(); +#ifdef SIN_ARCADE + ARCADE_SetupCommunications(); +#endif + } + +void G_AllocGameData + ( + void + ) + + { + int i; + + gi.FreeTags( TAG_GAME ); + + // Initialize debug lines + G_AllocDebugLines(); + + // initialize all entities for this game + g_edicts = ( edict_t * )gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME); + globals.edicts = g_edicts; + globals.max_edicts = game.maxentities; + + // Add all the edicts to the free list + LL_Reset( &free_edicts, next, prev ); + LL_Reset( &active_edicts, next, prev ); + for( i = 0; i < game.maxentities; i++ ) + { + LL_Add( &free_edicts, &g_edicts[ i ], next, prev ); + } + + // initialize all clients for this game + game.clients = ( gclient_t * )gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME); + memset( game.clients, 0, game.maxclients * sizeof( game.clients[ 0 ] ) ); + for (i=0 ; i GAME_API_VERSION ) + { + gi.error( "Savegame from version %d of Sin.\n", version ); + } + + arc.ReadInteger( &savegame_version ); + if ( savegame_version < SAVEGAME_VERSION ) + { + gi.error( "Savegame from an older version (%d) of Sin.\n", version ); + } + else if ( savegame_version > SAVEGAME_VERSION ) + { + gi.error( "Savegame from version %d of Sin.\n", version ); + } + + // Read the map name (needed by G_MapInit) + arc.ReadString( &mapname ); + + // Set up for a new map + G_MapInit( mapname.c_str() ); + + arc.ReadObject( &PersistantData ); + arc.ReadObject( &game ); + arc.ReadObject( &gameVars ); + + arc.Close(); + } + +void G_ReadGame + ( + const char *name + ) + + { + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + ReadGame( name ); + } + +/* +============ +WriteGame + +This will be called whenever the game goes to a new level, +and when the user explicitly saves the game. + +Game information include cross level data, like multi level +triggers, help computer info, and all client states. + +A single player death will automatically restore from the +last save position. +============ +*/ + +void WriteGame + ( + const char *name, + qboolean autosave + ) + + { + Archiver arc; + + game.autosaved = autosave; + + arc.Create( name ); + + arc.WriteInteger( GAME_API_VERSION ); + arc.WriteInteger( SAVEGAME_VERSION ); + + arc.WriteString( level.mapname ); + + arc.WriteObject( &PersistantData ); + arc.WriteObject( &game ); + arc.WriteObject( &gameVars ); + + arc.Close(); + + game.autosaved = false; + } + +void G_WriteGame + ( + const char *name, + qboolean autosave + ) + + { + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + WriteGame( name, autosave ); + } + +/* +============== +G_WriteClient +============== +*/ +void G_WriteClient + ( + Archiver &arc, + gclient_t *client + ) + + { + arc.WriteRaw( client, sizeof( gclient_t ) ); + } + +/* +============== +G_ReadClient +============== +*/ +void G_ReadClient + ( + Archiver &arc, + gclient_t *client + ) + + { + arc.ReadRaw( client, sizeof( gclient_t ) ); + } + +/* +================== +G_SaveClientData + +Some information that should be persistant, like health, +is stored in the Entity structure, so it needs to be mirrored +out to the client structure before all the edicts are wiped. +================== +*/ +void G_SaveClientData + ( + void + ) + + { + int i; + edict_t *ent; + + PersistantData.Reset(); + + if ( deathmatch->value ) + { + return; + } + + for( i = 0; i < game.maxclients; i++ ) + { + ent = &g_edicts[ 1 + i ]; + if ( !ent->inuse || !ent->entity ) + { + continue; + } + + PersistantData.AddEnt( ent->entity ); + } + } + +/* +================= +WriteLevel + +================= +*/ +void WriteLevel + ( + const char *filename, + qboolean autosave + ) + + { + int i; + int num; + edict_t *edict; + Archiver arc; + + if ( autosave ) + { + for( i = 0; i < game.maxclients; i++ ) + { + edict = &g_edicts[ 1 + i ]; + if ( !edict->inuse && !edict->entity ) + { + continue; + } + + delete edict->entity; + } + } + + arc.Create( filename ); + + // write out the version number + arc.WriteInteger( GAME_API_VERSION ); + arc.WriteInteger( SAVEGAME_VERSION ); + + // Write out the pending events. These are written first in case + // later objects need to post events when reading the archive. + G_ArchiveEvents( arc ); + + // write out level_locals_t + arc.WriteObject( &level ); + + // write out consoles + arc.WriteObject( &consoleManager ); + + // write out script librarian + arc.WriteObject( &ScriptLib ); + + // write out gravity paths + arc.WriteObject( &gravPathManager ); + + // write out paths + arc.WriteObject( &PathManager ); + + // write out script controller + arc.WriteObject( &Director ); + + // write out surface manager + arc.WriteObject( &surfaceManager ); + + // write out Viewmodel manager (for debugging only) + arc.WriteObject( &Viewmodel ); + + // count the entities + num = 0; + for( i = 0; i < globals.num_edicts; i++ ) + { + edict = &g_edicts[ i ]; + if ( edict->inuse && edict->entity && !( edict->entity->flags & FL_DONTSAVE ) ) + { + num++; + } + } + + // write out all the entities + arc.WriteInteger( globals.num_edicts ); + arc.WriteInteger( num ); + for( i = 0; i < globals.num_edicts; i++ ) + { + edict = &g_edicts[ i ]; + if ( !edict->inuse || !edict->entity || ( edict->entity->flags & FL_DONTSAVE ) ) + { + continue; + } + + arc.WriteObject( edict->entity ); + } + + arc.Close(); + } + +void G_WriteLevel + ( + const char *filename, + qboolean autosave + ) + + { + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + WriteLevel( filename, autosave ); + } + +/* +================= +ReadLevel + +SpawnEntities will already have been called on the +level the same way it was when the level was saved. + +That is necessary to get the baselines set up identically. + +The server will have cleared all of the world links before +calling ReadLevel. + +No clients are connected yet. +================= +*/ +void ReadLevel + ( + const char *filename + ) + + { + int i; + int num; + Archiver arc; + int version; + int savegame_version; + + LoadingSavegame = true; + + // Get rid of anything left over from the last level + G_LevelShutdown(); + G_ResetEdicts(); + + arc.Read( filename ); + + // read the version number + arc.ReadInteger( &version ); + if ( version < GAME_API_VERSION ) + { + gi.error( "Savegame from an older version (%d) of Sin.\n", version ); + } + else if ( version > GAME_API_VERSION ) + { + gi.error( "Savegame from version %d of Sin.\n", version ); + } + + arc.ReadInteger( &savegame_version ); + if ( savegame_version < SAVEGAME_VERSION ) + { + gi.error( "Savegame from an older version (%d) of Sin.\n", version ); + } + else if ( savegame_version > SAVEGAME_VERSION ) + { + gi.error( "Savegame from version %d of Sin.\n", version ); + } + + // Read in the pending events. These are read in first in case + // later objects need to post events. + G_UnarchiveEvents( arc ); + + // read level_locals_t + arc.ReadObject( &level ); + + // read consoles + arc.ReadObject( &consoleManager ); + + // read script librarian + arc.ReadObject( &ScriptLib ); + + // read gravity paths + arc.ReadObject( &gravPathManager ); + + // read paths + arc.ReadObject( &PathManager ); + + // read script controller + arc.ReadObject( &Director ); + + // read surface manager + arc.ReadObject( &surfaceManager ); + + // read Viewmodel manager (for debugging only) + arc.ReadObject( &Viewmodel ); + + // read all the entities + arc.ReadInteger( &globals.num_edicts ); + arc.ReadInteger( &num ); + for( i = 0; i < num; i++ ) + { + arc.ReadObject(); + } + + arc.Close(); + + // call the precache scripts + G_Precache(); + + LoadingSavegame = false; + } + +void G_ReadLevel + ( + const char *filename + ) + + { + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + ReadLevel( filename ); + } + +/* +================= +GetGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +game_export_t *GetGameAPI + ( + game_import_t *import + ) + + { + gi = *import; + + globals.apiversion = GAME_API_VERSION; + globals.Init = G_InitGame; + globals.Shutdown = G_ShutdownGame; + globals.SpawnEntities = G_SpawnEntities; + globals.CreateSurfaces = CreateSurfaces; + + globals.WriteGame = G_WriteGame; + globals.ReadGame = G_ReadGame; + globals.WriteLevel = G_WriteLevel; + globals.ReadLevel = G_ReadLevel; + + globals.ClientThink = G_ClientThink; + globals.ClientConnect = G_ClientConnect; + globals.ClientUserinfoChanged = G_ClientUserinfoChanged; + globals.ClientDisconnect = G_ClientDisconnect; + globals.ClientBegin = G_ClientBegin; + globals.ClientCommand = G_ClientCommand; + + globals.RunFrame = G_RunFrame; + globals.ServerCommand = G_ServerCommand; + + globals.edict_size = sizeof(edict_t); + globals.console_size = sizeof(netconsole_t); + globals.conbuffer_size = sizeof(netconbuffer_t); + globals.surface_size = sizeof(netsurface_t); + + return &globals; + } + +#ifndef GAME_HARD_LINKED +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error + ( + const char *error, + ... + ) + + { + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + gi.error (ERR_FATAL, "%s", text); + } + +void Com_Printf + ( + const char *msg, + ... + ) + + { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + gi.dprintf ("%s", text); + } + +#endif + +//====================================================================== + + +/* +================= +G_ClientEndServerFrames +================= +*/ +void G_ClientEndServerFrames + ( + void + ) + + { + int i; + edict_t *ent; + + // calc the player views now that all pushing + // and damage has been added + for( i = 0; i < maxclients->value; i++ ) + { + ent = g_edicts + 1 + i; + if ( !ent->inuse || !ent->client || !ent->entity ) + { + continue; + } + + ent->entity->ProcessEvent( EV_ClientEndFrame ); + } + } + +/* +================= +G_EndDMLevel + +The timelimit or fraglimit has been exceeded +================= +*/ +void G_EndDMLevel + ( + void + ) + + { + int num; + TriggerChangeLevel *ent; + char *s, *t, *f; + static const char *seps = " ,\n\r"; + + // stay on same level flag + if ( DM_FLAG( DF_SAME_LEVEL ) ) + { + G_BeginIntermission( level.mapname.c_str() ); + return; + } + + // see if it's in the map list + if ( *sv_maplist->string ) + { + s = strdup(sv_maplist->string); + f = NULL; + t = strtok( s, seps ); + while ( t != NULL ) + { + if ( !stricmp( t, level.mapname.c_str() ) ) + { + // it's in the list, go to the next one + t = strtok( NULL, seps ); + if ( t == NULL ) + { // end of list, go to first one + if ( f == NULL ) // there isn't a first one, same level + G_BeginIntermission( level.mapname.c_str() ); + else + G_BeginIntermission( f ); + } + else + { + G_BeginIntermission( t ); + } + free(s); + return; + } + if (!f) + { + f = t; + } + t = strtok(NULL, seps); + } + free(s); + } + + if ( !level.nextmap.length() ) + { + // search for a changelevel + num = G_FindClass( 0, "target_changelevel" ); + if ( !num ) + { + // the map designer didn't include a changelevel, + // so go back to the same level + G_BeginIntermission( level.mapname.c_str() ); + } + else + { + ent = ( TriggerChangeLevel * )G_GetEntity( num ); + G_BeginIntermission( ent->Map() ); + } + } + } + +/* +================= +G_CheckDMRules +================= +*/ +void G_CheckDMRules + ( + void + ) + + { + int i; + gclient_t *cl; + + if ( level.intermissiontime ) + { + return; + } + + if ( !deathmatch->value ) + { + return; + } + + if ( timelimit->value ) + { + if ( level.time >= timelimit->value * 60 ) + { + gi.bprintf( PRINT_HIGH, "Timelimit hit.\n" ); + G_EndDMLevel(); + return; + } + } + + if ( fraglimit->value ) + { + for( i = 0; i < maxclients->value; i++ ) + { + cl = game.clients + i; + if ( !g_edicts[ i + 1 ].inuse ) + { + continue; + } + + if ( cl->resp.score >= fraglimit->value ) + { + gi.bprintf( PRINT_HIGH, "Fraglimit hit.\n" ); + G_EndDMLevel(); + return; + } + } + } + } + +void G_MoveClientToIntermission + ( + Entity *ent + ) + + { + // Display the scores for the client + if ( deathmatch->value || coop->value ) + { + ent->client->showinfo = true; + G_DeathmatchScoreboardMessage( ent, NULL ); + gi.unicast( ent->edict, true ); + } + } + +void G_BeginIntermission + ( + const char *map + ) + + { + edict_t *client; + Entity *ent; + Entity *path; + int i,num; + Event *event, event2; + + assert( map ); + if ( !map ) + { + gi.dprintf( "G_BeginIntermission : Null map name\n" ); + return; + } + + if ( level.missionfailed ) + { + // don't allow map changes when a mission has failed + return; + } + + if ( level.intermissiontime ) + { + // already activated + return; + } + + level.intermissiontime = level.time; + + if ( level.clearsavegames && ( map[ 0 ] != '*' ) ) + { + level.nextmap = str( "*" ) + map; + } + else + { + level.nextmap = map; + } + + level.clearsavegames = false; + + level.exitintermission = !( deathmatch->value || coop->value ); + + // find an intermission spot + num = G_FindClass( 0, "info_player_intermission" ); + + // Only do the camera stuff if the node exists. + if ( num ) + { + ent = G_GetEntity( num ); + SetCamera( ent ); + event = new Event( EV_Camera_Orbit ); + + // Find the end node + num = G_FindTarget( 0, "endnode1" ); + if ( num ) + { + path = G_GetEntity( num ); + event->AddEntity( path ); + ent->ProcessEvent( event ); + event = new Event( EV_Camera_JumpCut ); + ent->ProcessEvent( event ); + } + } + + // Display scores for all the clients + for( i = 0; i < maxclients->value; i++ ) + { + client = g_edicts + 1 + i; + + if (!client->inuse) + continue; + + ent = G_GetEntity( client->s.number ); + G_MoveClientToIntermission( ent ); + } + + // tell the script that the player's not ready so that if we return to this map, + // we can do something about it. + Director.PlayerNotReady(); + } + +/* +============= +G_ExitLevel +============= +*/ +void G_ExitLevel + ( + void + ) + + { + char command[ 256 ]; + int j; + edict_t *ent; + + // kill the sounds + Com_sprintf( command, sizeof( command ), "stopsound\n" ); + gi.AddCommandString( command ); + + Com_sprintf( command, sizeof( command ), "gamemap \"%s\"\n", level.nextmap.c_str() ); + gi.AddCommandString( command ); + + level.nextmap = ""; + + level.exitintermission = 0; + level.intermissiontime = 0; + + G_SaveClientData(); + + // Tell all the client that the level is done + for( j = 1; j <= game.maxclients; j++ ) + { + ent = &g_edicts[ j ]; + if ( !ent->inuse || !ent->entity ) + { + continue; + } + + ent->entity->ProcessEvent( EV_Player_EndLevel ); + } + + G_ClientEndServerFrames(); + + // tell the script that the player's not ready so that if we return to this map, + // we can do something about it. + Director.PlayerNotReady(); + } + +void G_DrawCSystem + ( + void + ) + + { + Vector pos; + Vector ang; + Vector f; + Vector r; + Vector u; + Vector v; + + pos.x = csys_posx->value; + pos.y = csys_posy->value; + pos.z = csys_posz->value; + + ang.x = csys_x->value; + ang.y = csys_y->value; + ang.z = csys_z->value; + + ang.AngleVectors( &f, &r, &u ); + + G_DebugLine( pos, pos + f * 48, 1.0, 0, 0, 1 ); + G_DebugLine( pos, pos + r * 48, 0, 1.0, 0, 1 ); + G_DebugLine( pos, pos + u * 48, 0, 0, 1.0, 1 ); + } + +/* +================ +G_RunFrame + +Advances the world by 0.1 seconds +================ +*/ +void G_RunFrame + ( + void + ) + + { + edict_t *edict; + Entity *ent; + int num; + qboolean showentnums; + int start; + int end; + + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + level.framenum++; + level.time = level.framenum * FRAMETIME; + + if ( g_showmem->value ) + { + DisplayMemoryUsage(); + } + + // exit intermissions + if ( level.exitintermission ) + { + G_ExitLevel(); + return; + } + + // if the player in the server and the mission has failed, show the loadmenu + if ( g_edicts[ 1 ].inuse && level.missionfailed && ( level.missionfailedtime < level.time ) ) + { + // restart the entire server + gi.AddCommandString( "con_clearfade\n" ); + gi.AddCommandString( "menu_loadgame\n" ); + return; + } + + path_checksthisframe = 0; + + // Reset debug lines + G_InitDebugLines(); + + // testing coordinate system + if ( csys_draw->value ) + { + G_DrawCSystem(); + } + + PathManager.ShowNodes(); + + // don't show entnums during deathmatch + showentnums = ( sv_showentnums->value && ( !deathmatch->value || sv_cheats->value ) ); + + // Process most of the events before the physics are run + // so that we can affect the physics immediately + G_ProcessPendingEvents(); + + // + // treat each object in turn + // + for( edict = active_edicts.next, num = 0; edict != &active_edicts; edict = level.next_edict, num++ ) + { + assert( edict ); + assert( edict->inuse ); + assert( edict->entity ); + + level.next_edict = edict->next; + + // Paranoia - It's a way of life + assert( num <= MAX_EDICTS ); + if ( num > MAX_EDICTS ) + { + gi.dprintf( "Possible infinite loop in G_RunFrame.\n"); + break; + } + + ent = edict->entity; + level.current_entity = ent; + + if ( g_timeents->value ) + { + start = G_Milliseconds(); + G_RunEntity( ent ); + end = G_Milliseconds(); + + if ( g_timeents->value <= ( end - start ) ) + { + G_DebugPrintf( "%d: '%s'(%d) : %d\n", level.framenum, ent->targetname.c_str(), ent->entnum, end - start ); + } + } + else + { + G_RunEntity( ent ); + } + + if ( showentnums ) + { + G_DrawDebugNumber( ent->worldorigin + Vector( 0, 0, ent->maxs.z + 2 ), ent->entnum, 2, 1, 1, 0 ); + } + } + + // Process any pending events that got posted during the physics code. + G_ProcessPendingEvents(); + + // see if it is time to end a deathmatch + G_CheckDMRules(); + + // build the playerstate_t structures for all players + G_ClientEndServerFrames(); + + // see if we should draw the bounding boxes + G_ClientDrawBoundingBoxes(); + + // show how many traces the game code is doing + if ( sv_traceinfo->value ) + { + if ( sv_traceinfo->value == 3 ) + { + G_DebugPrintf( "%0.1f : Total traces %d\n", level.time, sv_numtraces ); + } + else + { + gi.dprintf( "%0.1f : Total traces %d\n", level.time, sv_numtraces ); + } + } + + // reset out count of the number of game traces + sv_numtraces = 0; + } + +void G_ClientThink + ( + edict_t *ent, + usercmd_t *ucmd + ) + + { + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + if ( ent->entity ) + { + current_ucmd = ucmd; + level.current_entity = ent->entity; + ent->entity->ProcessEvent( EV_ClientMove ); + current_ucmd = NULL; + } + } + +/* +=========== +G_PutClientInServer + +Called when a player connects to a server +============ +*/ +void G_PutClientInServer + ( + edict_t *ent + ) + + { + if ( !ent->entity ) + { + G_InitSpawnArguments(); + G_SetSpawnArg( "classname", "player" ); + + game.force_entnum = true; + game.spawn_entnum = ent->s.number; + G_CallSpawn(); + game.force_entnum = false; + + if ( ent->entity && ent->entity->isSubclassOf( Player ) ) + { + ( ( Player * )ent->entity )->Init(); + } + } + } + +/* +=========== +G_ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the game. This will happen every level load. +============ +*/ +void G_ClientBegin + ( + edict_t *ent, + qboolean loadgame + ) + + { + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + if ( ent->inuse && ent->entity ) + { + // the client has cleared the client side viewangles upon + // connecting to the server, which is different than the + // state when the game is saved, so we need to compensate + // with deltaangles + ent->entity->SetDeltaAngles(); + } + else + { + // a spawn point will completely reinitialize the entity + G_InitEdict( ent ); + G_InitClientResp( ent->client ); + G_PutClientInServer( ent ); + } + + if ( level.intermissiontime && ent->entity ) + { + G_MoveClientToIntermission( ent->entity ); + } + else + { + // send effect if in a multiplayer game + if ( game.maxclients > 1 ) + { + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + } + } + + // make sure all view stuff is valid + if ( ent->entity ) + { + ent->entity->ProcessEvent( EV_ClientEndFrame ); + } + } + +void FixDeadBodiesForPlayer + ( + edict_t *ent + ) + + { + int i,playernum; + edict_t *body; + + + if ( !deathmatch->value ) + return; + + playernum = ent-g_edicts-1; + + for ( i=0; ivalue + 1 + i ]; + + if ( ( body->s.skinnum == playernum ) && ( body->s.modelindex != ent->s.modelindex ) ) + { + body->s.renderfx |= RF_DONTDRAW; + body->s.skinnum = -1; + } + } + } + +/* +=========== +G_ClientUserInfoChanged + +called whenever the player updates a userinfo variable. + +The game can override any of the settings in place +(forcing skins or names, etc) before copying it off. +============ +*/ +void G_ClientUserinfoChanged + ( + edict_t *ent, + const char *userinfo + ) + + { + const char *s; + int playernum; + Player *player; + str model; + float fov; + Event *ev; + + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + player = ( Player * )ent->entity; + + ent->client->ps.pmove.pm_flags &= ~PMF_OLDNOCLIP; + s = Info_ValueForKey( userinfo, "cl_oldnoclip" ); + if (strlen(s)) + { + if ( atoi(s) ) + { + ent->client->ps.pmove.pm_flags |= PMF_OLDNOCLIP; + } + } + + // set name + s = Info_ValueForKey( userinfo, "name" ); + strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1); + + // Don't allow zero length names + if ( !strlen( ent->client->pers.netname ) ) + strcpy( ent->client->pers.netname, "Blade" ); + + if ( deathmatch->value ) + { + // set skin + s = Info_ValueForKey( userinfo, "skin" ); + strncpy( ent->client->pers.skin, s, sizeof( ent->client->pers.skin ) - 1 ); + } + + // Don't allow zero length skins + if ( !strlen( ent->client->pers.skin ) ) + { + strcpy( ent->client->pers.skin, "blade_base" ); + } + + // set model only if player not a mutant + if ( !( player && ( player->flags & (FL_MUTANT|FL_SP_MUTANT) ) ) ) + { + s = Info_ValueForKey( userinfo, "model" ); + COM_StripExtension( s, ent->client->pers.model ); + strcat( ent->client->pers.model, ".def" ); + + // Don't allow zero length models + if ( !strlen( ent->client->pers.model ) ) + { + strcpy( ent->client->pers.model, "pl_blade.def" ); + } + + // Only allow models that the server sets up in the players script file + model = ent->client->pers.model; + if ( !game.ValidPlayerModels.ObjectInList( model ) ) + { + // Fall back to blade + strcpy( ent->client->pers.model, "pl_blade.def" ); + } + +#ifdef SIN_DEMO + if ( 1 ) +#else + // Always be blade in single player + if ( !deathmatch->value ) +#endif + { + strcpy( ent->client->pers.model, "pl_blade.def" ); + } + // Call the player's setModel function if he exists + // Prepend 'models/' to make things easier + if ( !strchr( ent->client->pers.model, '*' ) && !strchr( ent->client->pers.model, '\\' ) && !strchr( ent->client->pers.model, '/' ) ) + { + model = "models/"; + model += ent->client->pers.model; + } + else + { + model = ent->client->pers.model; + } + + if ( player && !player->deadflag && ( player->model != model ) ) + { + player->setModel( model ); + player->RandomAnimate( "idle", NULL ); + } + } + + // Fov + if ( player ) + { + fov = atof( Info_ValueForKey( userinfo, "fov" ) ); + if ( fov < 1 ) + { + fov = 90; + } + else if ( fov > 160 ) + { + fov = 160; + } + ev = new Event( EV_Player_Fov ); + ev->AddFloat( fov ); + player->ProcessEvent( ev ); + } + + // Player number + playernum = ent - g_edicts - 1; + + // combine name, skin and model into a configstring + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s\\%s", + ent->client->pers.netname, + ent->client->pers.model, + ent->client->pers.skin)); + + // handedness + s = Info_ValueForKey( userinfo, "hand" ); + if ( strlen( s ) ) + { + ent->client->pers.hand = atoi( s ); + } + + // save off the userinfo in case we want to check something later + strncpy( ent->client->pers.userinfo, userinfo, sizeof( ent->client->pers.userinfo )-1 ); + + // Hide the bodies that are associated with this player so that + // no weird animations show up on the client + if ( ( !LoadingSavegame ) && ( deathmatch->value || coop->value ) ) + FixDeadBodiesForPlayer( ent ); + } + +/* +=========== +G_ClientConnect + +Called when a player begins connecting to the server. +The game can refuse entrance to a client by returning false. +If the client is allowed, the connection process will continue +and eventually get to ClientBegin() +Changing levels will NOT cause this to be called again. +============ +*/ +qboolean G_ClientConnect + ( + edict_t *ent, + const char *userinfo + ) + + { + const char *value; + + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + // check to see if they are on the banned IP list + value = Info_ValueForKey( userinfo, "ip" ); + if ( SV_FilterPacket( value ) ) + { + return false; + } + + // check for a password + value = Info_ValueForKey( userinfo, "password" ); + if ( strcmp( password->string, value ) != 0 ) + { + return false; + } + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if ( ent->inuse == false ) + { + // clear the respawning variables + G_InitClientResp( ent->client ); + if ( !game.autosaved )//|| !ent->client->pers.weapon) + { + G_InitClientPersistant( ent->client ); + } + } + + G_ClientUserinfoChanged( ent, userinfo ); + + if ( game.maxclients > 1 ) + { + gi.printf( "%s connected\n", ent->client->pers.netname ); + } + + LoadingServer = false; + + return true; + } + +/* +=========== +G_ClientDisconnect + +called when a player drops from the server + +============ +*/ +void G_ClientDisconnect + ( + edict_t *ent + ) + + { + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + if ( ( !ent->client ) || ( !ent->entity ) ) + { + return; + } + + delete ent->entity; + ent->entity = NULL; + } + +/* +================== +Cmd_Say_f +================== +*/ +void G_Say + ( + edict_t *ent, + qboolean team, + qboolean arg0 + ) + + { + int j; + edict_t *other; + const char *p; + char text[ 2048 ]; + + if ( gi.argc() < 2 && !arg0 ) + { + return; + } + + if ( !DM_FLAG( DF_MODELTEAMS | DF_SKINTEAMS ) ) + { + team = false; + } + + if ( team ) + { + Com_sprintf( text, sizeof( text ), "(%s): ", ent->client->pers.netname ); + } + else + { + Com_sprintf( text, sizeof( text ), "%s: ", ent->client->pers.netname ); + } + + if ( arg0 ) + { + strcat( text, gi.argv( 0 ) ); + strcat( text, " " ); + strcat( text, gi.args() ); + } + else + { + p = gi.args(); + + if ( *p == '"' ) + { + p++; + strcat( text, p ); + text[ strlen( text ) - 1 ] = 0; + } + else + { + strcat( text, p ); + } + } + + // don't let text be too long for malicious reasons + if ( strlen( text ) > 150 ) + { + text[ 150 ] = 0; + } + + strcat( text, "\n" ); + + if ( dedicated->value ) + { + gi.cprintf( NULL, PRINT_CHAT, "%s", text ); + } + + for( j = 1; j <= game.maxclients; j++ ) + { + other = &g_edicts[ j ]; + if ( !other->inuse || !other->client ) + { + continue; + } +#if 0 + if ( team ) + { + if ( !OnSameTeam( ent, other ) ) + { + continue; + } + } +#endif + gi.cprintf( other, PRINT_CHAT, "%s", text ); + } + } + +void ClientCommand + ( + edict_t *ent + ) + + { + const char *cmd; + int i; + int n; + Event *ev; + qboolean found; + cvar_t *cvar; + float t; + + if ( !ent->client || !ent->entity ) + { + // not fully in game yet + return; + } + + cmd = gi.argv( 0 ); + n = gi.argc(); + + if ( !Q_strcasecmp( cmd, "say" ) ) + { + G_Say( ent, false, false ); + return; + } + else if ( game.maxclients == 1 ) + { + // only allow these commands when we only have one client (most likely only a local game) + if ( !Q_strcasecmp( cmd, "add" ) ) + { + if ( n < 3 ) + { + gi.cprintf( ent, PRINT_HIGH, "Syntax: add [var name] [amount].\n" ); + return; + } + + cvar = gi.cvar( gi.argv( 1 ), "0", 0 ); + t = cvar->value + atof( gi.argv( 2 ) ); + gi.cvar_set( gi.argv( 1 ), va( "%f", t ) ); + gi.dprintf( "%s = %f\n", gi.argv( 1 ), cvar->value ); + return; + } + else if ( !Q_strcasecmp( cmd, "eventlist" ) ) + { + const char *mask; + + mask = NULL; + if ( n > 1 ) + { + mask = gi.argv( 1 ); + } + Event::ListCommands( mask ); + return; + } + else if ( !Q_strcasecmp( cmd, "classlist" ) ) + { + listAllClasses(); + return; + } + else if ( !Q_strcasecmp( cmd, "classtree" ) ) + { + if ( n > 1 ) + { + listInheritanceOrder( gi.argv( 1 ) ); + } + else + { + gi.cprintf( ent, PRINT_HIGH, "Syntax: classtree [classname].\n" ); + } + return; + } + } + + found = false; + + if ( Event::Exists( cmd ) ) + { + ev = new Event( cmd ); + ev->SetSource( EV_FROM_CONSOLE ); + ev->SetConsoleEdict( ent ); + for( i = 1; i < n; i++ ) + { + ev->AddToken( gi.argv( i ) ); + } + + if ( !Q_strncasecmp( cmd, "view", 4 ) ) + { + found = Viewmodel.ProcessEvent( ev ); + } + else if ( !Q_strncasecmp( cmd, "ai_", 2 ) ) + { + found = PathManager.ProcessEvent( ev ); + } + else if ( !Q_strncasecmp( cmd, "console", 7 ) ) + { + found = consoleManager.ProcessEvent( ev ); + } + else + { + found = ent->entity->ProcessEvent( ev ); + } + } + + if ( !found ) + { + // anything that doesn't match a command will be a chat + G_Say( ent, false, true ); + } + } + +void G_ClientCommand + ( + edict_t *ent + ) + + { + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + //FIXME + // setjmp doesn't seem to like to work inside the above function, so I've broken it out, + // which makes it happy. Wierd. + ClientCommand( ent ); + } + +/* +================== +G_DeathmatchScoreboardMessage + +================== +*/ +void G_DeathmatchScoreboardMessage + ( + Entity *ent, + Entity *killer + ) + + { + char entry[ 1024 ]; + char string[ 1400 ]; + int stringlength; + int i, j, k; + int sorted[ MAX_CLIENTS ]; + int sortedscores[ MAX_CLIENTS ]; + int score, total; + int x,y; + gclient_t *cl; + edict_t *cl_ent, *killeredict, *entedict; + const char *tag; + + killeredict = NULL; + entedict = NULL; + if ( killer ) + { + killeredict = killer->edict; + } + if ( ent ) + { + entedict = ent->edict; + } + + // sort the clients by score + total = 0; + for( i = 0; i < game.maxclients; i++ ) + { + cl_ent = g_edicts + 1 + i; + if ( !cl_ent->inuse ) + { + continue; + } + + score = game.clients[ i ].resp.score; + for( j = 0; j < total; j++ ) + { + if ( score > sortedscores[ j ] ) + break; + } + for( k = total; k > j; k-- ) + { + sorted[ k ] = sorted[ k - 1 ]; + sortedscores[ k ] = sortedscores[ k - 1 ]; + } + sorted[ j ] = i; + sortedscores[ j ] = score; + total++; + } + + // print level name and exit rules + string[ 0 ] = 0; + + stringlength = strlen( string ); + + // add the clients in sorted order + if ( total > 12 ) + { + total = 12; + } + + for( i = 0; i < total; i++ ) + { + cl = &game.clients[ sorted[ i ] ]; + cl_ent = g_edicts + 1 + sorted[ i ]; + + x = (i>=6) ? 160 : 0; + y = 32 + 32 * (i%6); + + // Add a tag to the player and the killer + if (cl_ent == entedict) + tag = "tag1"; + else if (cl_ent == killeredict) + tag = "tag2"; + else + tag = NULL; + + // send the layout + Com_sprintf( entry, sizeof( entry ), + "client %i %i %i %i %i %i ", + x, y, sorted[ i ], cl->resp.score, cl->ping, ( level.framenum - cl->resp.enterframe ) / 600 ); + + // Put the tag on the end of the client command + if ( tag ) + strcat( entry, va( "1 %s ",tag ) ); + else + strcat( entry, va( "0 " ) ); + + j = strlen( entry ); + if ( stringlength + j > 1024 ) + { + break; + } + strcpy( string + stringlength, entry ); + stringlength += j; + } + + gi.WriteByte( svc_layout ); + gi.WriteString( string ); + } + +/* +================== +G_DeathmatchScoreboard + +Draw instead of help message. +Note that it isn't that hard to overflow the 1400 byte message limit! +================== +*/ +void G_DeathmatchScoreboard + ( + Entity *ent + ) + + { + G_DeathmatchScoreboardMessage( ent, ent->enemy ); + gi.unicast( ent->edict, true ); + } + +/* +================= +G_ClientDrawBoundingBoxes +================= +*/ +void G_ClientDrawBoundingBoxes + ( + void + ) + + { + edict_t *edict; + Entity *ent; + Vector eye; + + // don't show bboxes during deathmatch + if ( !sv_showbboxes->value || ( deathmatch->value && !sv_cheats->value ) ) + { + return; + } + + edict = g_edicts + 1 + 0; + ent = edict->entity; + if ( ent ) + { + eye = ent->worldorigin; + ent = findradius( NULL, eye, 1000 ); + while( ent ) + { + switch ((int)sv_showbboxes->value) + { + case 1: + if ( ent->edict != edict && ent->edict->s.solid) + { + if (ent->bindmaster) + G_DebugBBox( ent->worldorigin, ent->mins, ent->maxs, 0, 1, 0, 1 ); + else + G_DebugBBox( ent->worldorigin, ent->mins, ent->maxs, 1, 1, 0, 1 ); + } + break; + case 2: + if ( ent->edict != edict && ent->edict->s.solid) + { + if (ent->bindmaster) + G_DebugBBox( "0 0 0", ent->edict->absmin, ent->edict->absmax, 0, 0, 1, 1 ); + else + G_DebugBBox( "0 0 0", ent->edict->absmin, ent->edict->absmax, 1, 0, 1, 1 ); + } + break; + case 3: + if ( ent->edict->s.modelindex && !(ent->edict->s.renderfx & RF_DONTDRAW) ) + G_DebugBBox( ent->worldorigin, ent->mins, ent->maxs, 1, 1, 0, 1 ); + break; + case 4: + default: + G_DebugBBox( ent->worldorigin, ent->mins, ent->maxs, 1, 1, 0, 1 ); + break; + case 5: + if ( ent->edict->s.solid ) + { + G_DebugBBox( ent->worldorigin, ent->edict->fullmins, ent->edict->fullmaxs, 1, 1, 1, 1 ); + } + break; + } + ent = findradius( ent, eye, 1000 ); + } + } + } + +CLASS_DECLARATION( Class, game_locals_t, NULL ); + +ResponseDef game_locals_t::Responses[] = + { + { NULL, NULL } + }; + +game_locals_t::game_locals_t() + { + clients = NULL; + + autosaved = false; + spawnpoint = ""; + + maxentities = 0; + maxclients = 0; + maxconsoles = 0; + maxsurfaces = 0; + + force_entnum = false; + spawn_entnum = 0; + + ValidPlayerModels.FreeObjectList(); + } + +EXPORT_FROM_DLL void game_locals_t::Archive + ( + Archiver &arc + ) + + { + int i; + int num; + + Class::Archive( arc ); + + arc.WriteBoolean( autosaved ); + arc.WriteString( spawnpoint ); + arc.WriteBoolean( force_entnum ); + arc.WriteInteger( spawn_entnum ); + + // List of valid player models loaded from players global scriptfile + num = ValidPlayerModels.NumObjects(); + arc.WriteInteger( num ); + for( i = 1; i <= num; i++ ) + { + arc.WriteString( ValidPlayerModels.ObjectAt( i ) ); + } + + arc.WriteInteger( maxentities ); + arc.WriteInteger( maxclients ); + arc.WriteInteger( maxconsoles ); + arc.WriteInteger( maxsurfaces ); + + for( i = 0; i < maxclients; i++ ) + { + G_WriteClient( arc, &clients[ i ] ); + } + } + +EXPORT_FROM_DLL void game_locals_t::Unarchive + ( + Archiver &arc + ) + + { + int i; + int num; + str modelname; + + Class::Unarchive( arc ); + + arc.ReadBoolean( &autosaved ); + arc.ReadString( &spawnpoint ); + arc.ReadBoolean( &force_entnum ); + arc.ReadInteger( &spawn_entnum ); + + // Load list of valid player models + arc.ReadInteger( &num ); + for( i = 1; i <= num; i++ ) + { + arc.ReadString( &modelname ); + ValidPlayerModels.AddObject( modelname ); + } + + arc.ReadInteger( &maxentities ); + arc.ReadInteger( &maxclients ); + arc.ReadInteger( &maxconsoles ); + arc.ReadInteger( &maxsurfaces ); + G_AllocGameData(); + + for( i = 0; i < maxclients; i++ ) + { + G_ReadClient( arc, &clients[ i ] ); + } + } + +CLASS_DECLARATION( Class, level_locals_t, NULL ); + +ResponseDef level_locals_t::Responses[] = + { + { NULL, NULL } + }; + +level_locals_t::level_locals_t() + { + framenum = 0; + time = 0; + + level_name = ""; + mapname = ""; + nextmap = ""; + + playerfrozen = false; + intermissiontime = 0; + exitintermission = 0; + + next_edict = NULL; + + total_secrets = 0; + found_secrets = 0; + + current_entity = NULL; + memset( &impact_trace, 0, sizeof( impact_trace ) ); + + body_queue = 0; + + earthquake = 0; + + clearsavegames = false; + + cinematic = false; + no_jc = false; + + water_color = vec_zero; + lightvolume_color = vec_zero; + lava_color = vec_zero; + water_alpha = lightvolume_alpha = lava_alpha = 0; + training = false; + airclamp = true; + + missionfailed = false; + missionfailedtime = 0; + } + +EXPORT_FROM_DLL void level_locals_t::Archive + ( + Archiver &arc + ) + + { + Class::Archive( arc ); + + arc.WriteInteger( framenum ); + arc.WriteFloat( time ); + arc.WriteString( level_name ); + arc.WriteString( mapname ); + arc.WriteString( nextmap ); + + arc.WriteBoolean( playerfrozen ); + arc.WriteFloat( intermissiontime ); + arc.WriteInteger( exitintermission ); + + arc.WriteInteger( total_secrets ); + arc.WriteInteger( found_secrets ); + + arc.WriteInteger( body_queue ); + + arc.WriteFloat( earthquake ); + + arc.WriteBoolean( clearsavegames ); + + arc.WriteBoolean( cinematic ); + arc.WriteBoolean( no_jc ); + + arc.WriteVector( water_color ); + arc.WriteVector( lightvolume_color ); + arc.WriteVector( lava_color ); + arc.WriteFloat( water_alpha ); + arc.WriteFloat( lightvolume_alpha ); + arc.WriteFloat( lava_alpha ); + arc.WriteBoolean( airclamp ); + arc.WriteBoolean( training ); + + arc.WriteBoolean( missionfailed ); + arc.WriteFloat( missionfailedtime ); + } + +EXPORT_FROM_DLL void level_locals_t::Unarchive + ( + Archiver &arc + ) + + { + Class::Unarchive( arc ); + + arc.ReadInteger( &framenum ); + arc.ReadFloat( &time ); + arc.ReadString( &level_name ); + arc.ReadString( &mapname ); + arc.ReadString( &nextmap ); + + arc.ReadBoolean( &playerfrozen ); + arc.ReadFloat( &intermissiontime ); + arc.ReadInteger( &exitintermission ); + + // not archived since we can't save mid-frame + next_edict = NULL; + + arc.ReadInteger( &total_secrets ); + arc.ReadInteger( &found_secrets ); + + // not archived since we can't save mid-frame + current_entity = NULL; + memset( &impact_trace, 0, sizeof( impact_trace ) ); + + arc.ReadInteger( &body_queue ); + + arc.ReadFloat( &earthquake ); + + arc.ReadBoolean( &clearsavegames ); + + arc.ReadBoolean( &cinematic ); + arc.ReadBoolean( &no_jc ); + arc.ReadVector( &water_color ); + arc.ReadVector( &lightvolume_color ); + arc.ReadVector( &lava_color ); + arc.ReadFloat( &water_alpha ); + arc.ReadFloat( &lightvolume_alpha ); + arc.ReadFloat( &lava_alpha ); + arc.ReadBoolean( &airclamp ); + arc.ReadBoolean( &training ); + + arc.ReadBoolean( &missionfailed ); + arc.ReadFloat( &missionfailedtime ); + } + +/* +============================================================================== + +PACKET FILTERING + + +You can add or remove addresses from the filter list with: + +addip +removeip + +The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with "addip 192.246.40". + +Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host. + +listip +Prints the current list of filters. + +writeip +Dumps "addip " commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and restored by default, because I beleive it would cause too much confusion. + +filterban <0 or 1> + +If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting. + +If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network. + + +============================================================================== +*/ + +typedef struct + { + unsigned mask; + unsigned compare; + } ipfilter_t; + +#define MAX_IPFILTERS 1024 + +ipfilter_t ipfilters[ MAX_IPFILTERS ]; +int numipfilters; + +/* +================= +StringToFilter +================= +*/ +static qboolean StringToFilter + ( + const char *s, + ipfilter_t *f + ) + + { + char num[ 128 ]; + int i; + int j; + byte b[ 4 ]; + byte m[ 4 ]; + + for( i = 0; i < 4; i++ ) + { + b[ i ] = 0; + m[ i ] = 0; + } + + for( i = 0; i < 4; i++ ) + { + if ( *s < '0' || *s > '9' ) + { + gi.cprintf( NULL, PRINT_HIGH, "Bad filter address: %s\n", s ); + return false; + } + + j = 0; + while( *s >= '0' && *s <= '9' ) + { + num[ j++ ] = *s++; + } + + num[ j ] = 0; + b[ i ] = atoi( num ); + if ( b[ i ] != 0 ) + { + m[ i ] = 255; + } + + if ( !*s ) + { + break; + } + + s++; + } + + f->mask = *( unsigned * )m; + f->compare = *( unsigned * )b; + + return true; + } + +/* +================= +SV_FilterPacket +================= +*/ +qboolean SV_FilterPacket + ( + const char *from + ) + + { + int i; + unsigned in; + byte m[ 4 ]; + const char *p; + + i = 0; + p = from; + while( *p && i < 4 ) + { + m[ i ] = 0; + while( *p >= '0' && *p <= '9' ) + { + m[ i ] = m[ i ] * 10 + ( *p - '0' ); + p++; + } + + if ( !*p || *p == ':' ) + { + break; + } + + i++; + p++; + } + + in = *( unsigned * )m; + for( i = 0; i < numipfilters; i++ ) + { + if ( ( in & ipfilters[ i ].mask ) == ipfilters[ i ].compare ) + { + return ( int )filterban->value; + } + } + + return !( int )filterban->value; + } + + +/* +================= +SV_AddIP_f +================= +*/ +void SVCmd_AddIP_f + ( + void + ) + + { + int i; + + if ( gi.argc() < 3 ) + { + gi.cprintf( NULL, PRINT_HIGH, "Usage: addip \n" ); + return; + } + + for( i = 0; i < numipfilters; i++ ) + { + if ( ipfilters[ i ].compare == 0xffffffff ) + { + // free spot + break; + } + } + + if ( i == numipfilters ) + { + if ( numipfilters == MAX_IPFILTERS ) + { + gi.cprintf( NULL, PRINT_HIGH, "IP filter list is full\n" ); + return; + } + numipfilters++; + } + + if ( !StringToFilter( gi.argv( 2 ), &ipfilters[ i ] ) ) + { + ipfilters[ i ].compare = 0xffffffff; + } + } + +/* +================= +SV_RemoveIP_f +================= +*/ +void SVCmd_RemoveIP_f + ( + void + ) + + { + ipfilter_t f; + int i; + int j; + + if ( gi.argc() < 3 ) + { + gi.cprintf( NULL, PRINT_HIGH, "Usage: sv removeip \n" ); + return; + } + + if ( !StringToFilter( gi.argv( 2 ), &f ) ) + { + return; + } + + for( i = 0; i < numipfilters; i++ ) + { + if ( ( ipfilters[ i ].mask == f.mask ) && ( ipfilters[ i ].compare == f.compare ) ) + { + for ( j = i + 1; j < numipfilters; j++ ) + { + ipfilters[ j - 1 ] = ipfilters[ j ]; + } + + numipfilters--; + gi.cprintf( NULL, PRINT_HIGH, "Removed.\n" ); + + return; + } + } + + gi.cprintf( NULL, PRINT_HIGH, "Didn't find %s.\n", gi.argv( 2 ) ); + } + +/* +================= +SV_ListIP_f +================= +*/ +void SVCmd_ListIP_f + ( + void + ) + + { + int i; + byte b[ 4 ]; + + gi.cprintf( NULL, PRINT_HIGH, "Filter list:\n" ); + for( i = 0; i < numipfilters; i++ ) + { + *( unsigned * )b = ipfilters[ i ].compare; + gi.cprintf( NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[ 0 ], b[ 1 ], b[ 2 ], b[ 3 ] ); + } + } + +/* +================= +SV_WriteIP_f +================= +*/ +void SVCmd_WriteIP_f + ( + void + ) + + { + FILE *f; + char name[ MAX_OSPATH ]; + byte b[ 4 ]; + int i; + cvar_t *game; + + game = gi.cvar( "game", "", 0 ); + + if ( !*game->string ) + { + sprintf( name, "%s/listip.cfg", GAMEVERSION ); + } + else + { + sprintf( name, "%s/listip.cfg", game->string ); + } + + gi.cprintf( NULL, PRINT_HIGH, "Writing %s.\n", name ); + + f = fopen( name, "wb" ); + if ( !f ) + { + gi.cprintf( NULL, PRINT_HIGH, "Couldn't open %s\n", name ); + return; + } + + fprintf( f, "set filterban %d\n", ( int )filterban->value ); + + for( i = 0; i < numipfilters; i++ ) + { + *( unsigned * )b = ipfilters[ i ].compare; + fprintf( f, "sv addip %i.%i.%i.%i\n", b[ 0 ], b[ 1 ], b[ 2 ], b[ 3 ] ); + } + + fclose( f ); + } + +/* +================= +G_ServerCommand + +G_ServerCommand will be called when an "sv" command is issued. +The game can issue gi.argc() / gi.argv() commands to get the rest +of the parameters +================= +*/ +void G_ServerCommand + ( + void + ) + + { + const char *cmd; + + cmd = gi.argv(1); + if ( Q_stricmp( cmd, "addip" ) == 0 ) + { + SVCmd_AddIP_f(); + } + else if ( Q_stricmp( cmd, "removeip" ) == 0 ) + { + SVCmd_RemoveIP_f(); + } + else if ( Q_stricmp( cmd, "listip" ) == 0 ) + { + SVCmd_ListIP_f(); + } + else if ( Q_stricmp( cmd, "writeip" ) == 0 ) + { + SVCmd_WriteIP_f(); + } + else + { + gi.cprintf( NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd ); + } + } diff --git a/g_main.h b/g_main.h new file mode 100644 index 0000000..92b6cce --- /dev/null +++ b/g_main.h @@ -0,0 +1,187 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/g_main.h $ +// $Revision:: 14 $ +// $Author:: Aldie $ +// $Date:: 11/13/98 2:35a $ +// +// Copyright (C) 1998 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/g_main.h $ +// +// 14 11/13/98 2:35a Aldie +// Declared fixbodiesforplayer +// +// 13 11/09/98 12:55a Jimdose +// added sv_footsteps cvar so that server admins can turn footsteps off +// completely +// +// 12 10/27/98 9:46p Aldie +// Changed training cvar to level.training +// +// 11 10/25/98 10:16p Aldie +// Added training cvar +// +// 10 10/22/98 5:02p Aldie +// Removed blastscale_z +// +// 9 10/21/98 6:42p Markd +// Added sv_drawtrace +// +// 8 10/16/98 1:56a Jimdose +// Added autosave variable to G_WriteLevel +// +// 7 10/11/98 8:50p Jimdose +// Added LoadingServer +// +// 6 10/09/98 2:07a Aldie +// Updated DMFLAGS +// +// 5 10/08/98 7:41p Jimdose +// changed noexit to a dmflag +// added server commands +// Added ip filtering +// +// 4 10/08/98 12:38a Jimdose +// Made savegames work +// +// 3 9/22/98 3:21p Markd +// put in parentmode lockout for blood and gibs +// +// 2 8/29/98 9:49p Jimdose +// created file +// +// 1 8/29/98 6:44p Jimdose +// +// DESCRIPTION: +// Global header file for g_main.cpp +// + +#ifndef __G_MAIN_H__ +#define __G_MAIN_H__ + +#include "g_local.h" + +extern Vector vec_origin; +extern Vector vec_zero; + +extern qboolean LoadingSavegame; +extern qboolean LoadingServer; + +extern game_locals_t game; +extern level_locals_t level; +extern game_import_t gi; +extern game_export_t globals; + +extern edict_t *g_edicts; +extern edict_t active_edicts; +extern edict_t free_edicts; + +extern netconsole_t *g_consoles; +extern netconbuffer_t *g_conbuffers; +extern netsurface_t *g_surfaces; + +extern cvar_t *developer; +extern cvar_t *precache; + +extern cvar_t *maxentities; +extern cvar_t *maxconsoles; +extern cvar_t *deathmatch; +extern cvar_t *coop; +extern cvar_t *dmflags; +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; + +extern cvar_t *filterban; + +extern cvar_t *flood_msgs; +extern cvar_t *flood_persecond; +extern cvar_t *flood_waitdelay; + +extern cvar_t *g_select_empty; +extern cvar_t *g_unlimited_ammo; +extern cvar_t *nomonsters; +extern cvar_t *dialog; + +extern cvar_t *sv_gravity; +extern cvar_t *sv_maxvelocity; +extern cvar_t *sv_maxbulletholes; +extern cvar_t *sv_maxbloodsplats; +extern cvar_t *sv_gore; +extern cvar_t *sv_gibs; +extern cvar_t *sv_showdamage; +extern cvar_t *sv_showdamagelocation; + +extern cvar_t *gun_x, *gun_y, *gun_z; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_rollangle; + +extern cvar_t *run_pitch; +extern cvar_t *run_roll; +extern cvar_t *bob_up; +extern cvar_t *bob_pitch; +extern cvar_t *bob_roll; + +extern cvar_t *sv_cheats; +extern cvar_t *maxclients; + +extern cvar_t *sv_rocketspeed; +extern cvar_t *sv_rocketrate; + +extern cvar_t *sv_stopspeed; +extern cvar_t *sv_friction; +extern cvar_t *sv_waterfriction; +extern cvar_t *sv_waterspeed; +extern cvar_t *sv_footsteps; + +extern cvar_t *sv_traceinfo; +extern cvar_t *sv_drawtrace; +extern int sv_numtraces; + +extern cvar_t *parentmode; + +extern usercmd_t *current_ucmd; + +#define DM_FLAG( flag ) ( deathmatch->value && ( ( int )dmflags->value & ( flag ) ) ) + +void G_BeginIntermission( const char *map ); +void G_PutClientInServer( edict_t *ent ); +void G_SaveClientData( void ); +void G_MoveClientToIntermission( Entity *client ); +void G_DeathmatchScoreboard( Entity *ent ); +void G_DeathmatchScoreboardMessage( Entity *client, Entity *killer ); +void G_WriteClient( Archiver &arc, gclient_t *client ); +void G_AllocGameData( void ); + +extern "C" { + void G_ClientEndServerFrames( void ); + void G_ClientThink( edict_t *ent, usercmd_t *cmd ); + qboolean G_ClientConnect( edict_t *ent, const char *userinfo ); + void G_ClientUserinfoChanged( edict_t *ent, const char *userinfo ); + void G_ClientDisconnect( edict_t *ent ); + void G_ClientBegin( edict_t *ent, qboolean loadgame ); + void G_ClientCommand( edict_t *ent ); + void G_WriteGame( const char *filename, qboolean autosave ); + void G_ReadGame( const char *filename ); + void G_WriteLevel( const char *filename, qboolean autosave ); + void G_ReadLevel( const char *filename ); + void G_InitGame( void ); + void G_ShutdownGame( void ); + void G_RunFrame( void ); + void G_ServerCommand( void ); + void G_ClientThink( edict_t *ent, usercmd_t *ucmd ); + void FixDeadBodiesForPlayer( edict_t *ent ); + }; + +qboolean SV_FilterPacket( const char *from ); +void SVCmd_AddIP_f( void ); +void SVCmd_RemoveIP_f( void ); +void SVCmd_ListIP_f( void ); +void SVCmd_WriteIP_f( void ); + +#endif /* g_main.h */ diff --git a/g_phys.cpp b/g_phys.cpp new file mode 100644 index 0000000..0c89697 --- /dev/null +++ b/g_phys.cpp @@ -0,0 +1,1740 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/g_phys.cpp $ +// $Revision:: 61 $ +// $Author:: Markd $ +// $Date:: 11/20/98 7:17p $ +// +// Copyright (C) 1998 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/g_phys.cpp $ +// +// 61 11/20/98 7:17p Markd +// Took out G_SnapPosition +// +// 60 11/19/98 9:28p Jimdose +// fixed some more gravaxis issues +// +// 59 11/19/98 7:54p Jimdose +// Added G_SnapPosition. Fixes bug with player getting hurt when standing on +// some moving objects. +// +// 58 11/18/98 5:23a Jimdose +// G_Physics_Toss properly checks normal an alternate grav axis +// +// 57 11/16/98 9:43p Jimdose +// fixed sliding on alternate gravaxis +// +// 56 11/16/98 8:24p Markd +// fixed vehicles getting stuck +// +// 55 11/15/98 11:32p Markd +// change fat rocket behavior a bit +// +// 54 11/15/98 8:50p Jimdose +// added sv_fatrockets +// made SV_Physics_Toss do a fulltrace when sv_fatrockets is true, allowing you +// to shoot rockets and grenades through character's legs. +// +// 53 11/13/98 10:03p Jimdose +// added G_CheckWater +// G_Physics_Step now checks water +// +// 52 10/27/98 1:49a Markd +// made physics work better with waterlevel +// +// 51 10/25/98 11:57p Jimdose +// don't call animate during intermissions +// +// 50 10/18/98 8:43p Jimdose +// removed unnecessary setting or the origin and angles when bindmaster is set +// in G_RunEntity +// +// 49 10/16/98 2:21a Jimdose +// Made gravity different in water +// +// 48 10/16/98 2:09a Aldie +// +// 47 10/08/98 6:30p Markd +// Fixed TestEntityPosition +// +// 46 10/06/98 10:17p Markd +// fixed vehicle physics +// +// 45 10/04/98 10:28p Aldie +// Added multiple weapon changes. Damage, flashes, quantum stuff +// +// 44 9/22/98 10:21p Markd +// changed default vehicle clipmask +// +// 43 9/18/98 10:59p Jimdose +// made pointcontents test in G_Physics_Toss use worldorigin +// +// 42 9/15/98 3:49p Markd +// Put blocking functionality into MOVETYPE_VEHICLES +// +// 41 9/14/98 6:12p Markd +// Added MOVETYPE_VEHICLE +// +// 40 8/31/98 7:45p Aldie +// Updated surface data structure and removed surfinfo field +// +// 39 8/29/98 9:42p Jimdose +// Made all function names consistantly begin with G_ +// Added call info to G_Trace +// +// 38 8/27/98 9:05p Jimdose +// Moved STEPSIZE definition to g_local.h +// +// 37 8/22/98 9:36p Jimdose +// Added support for alternate gravity axis +// +// 36 8/18/98 11:50p Jimdose +// Made G_PushEntity so that the entity doesn't touch itself +// +// 35 8/18/98 11:08p Markd +// Added new Alias System +// +// 34 7/25/98 8:38p Markd +// put in landing sound +// +// 33 7/18/98 8:27p Jimdose +// Fixed sliding bug with entities standing on rotating entities +// +// 32 7/11/98 8:23p Jimdose +// G_Push does one less setorigin +// +// 31 7/09/98 9:38p Jimdose +// Changed calls to getLocalVector to getParentVector +// Removed quantization of move to 1/8 of a unit from G_Push +// +// 30 7/02/98 7:58p Markd +// When MOVETYPE_BOUNCE We still rotate if avelocity is non-zero +// +// 29 6/27/98 2:16p Aldie +// Changed some sliding stuff +// +// 28 6/09/98 4:19p Jimdose +// worked on ai +// +// 27 6/08/98 11:35a Aldie +// Added some slide move stuff +// +// 26 5/26/98 1:54a Markd +// Put in default water transition sounds +// +// 25 5/25/98 6:47p Jimdose +// Made animateframe, prethink and posthink into functions built into the base +// entity class +// +// 24 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 +// +// 23 5/22/98 7:18p Jimdose +// Working on ai stuff +// +// 22 5/14/98 10:12p Jimdose +// Added extended result information for G_Movestep +// +// 21 5/03/98 4:32p Jimdose +// Changed Vector class +// +// 20 5/02/98 12:02a Jimdose +// added groundplane, groundsurface, groundcontents +// added current and conveyors to non-player entities +// +// 19 4/30/98 4:48p Jimdose +// G_Push no longer gets blocked by SOLID_TRIGGER or SOLID_NOT +// +// 18 4/20/98 2:45p Jimdose +// working on ai +// +// 17 4/18/98 3:04p Jimdose +// Made G_Physics_Toss properly update angles using setAngles +// working on ai +// +// 16 4/16/98 2:03p Jimdose +// Added G_TestMovestep +// +// 15 4/10/98 4:56p Jimdose +// only play water transitition sound on ents with mass of 0 or spawntime older +// than FRAMETIME +// +// 14 4/09/98 3:32p Jimdose +// G_RunEntity now does a setOrigin and setAngle on bound entities to keep +// their worldorigin and worlangles up to date +// +// 13 4/04/98 6:04p Jimdose +// cleared out level.impact_trace after impact +// +// 12 3/31/98 1:04a Jimdose +// Added monster movement code +// +// 11 3/30/98 2:42p Jimdose +// Made stepmove take the animation delta into account +// +// 10 3/26/98 8:20p Jimdose +// Made groundentity an edict_t * +// Changed landing sound +// +// 9 3/24/98 7:35p Jimdose +// Made change to try to fix problem where non-solid entities were being +// blocked by solid ones. +// +// 8 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 7 3/18/98 7:15p Markd +// Changed sound code call in game.h +// +// 6 3/04/98 8:01p Aldie +// More support for damage surfaces. +// +// 5 2/21/98 1:15p Jimdose +// Added G_TestMove and G_TestStepMove for use in pathfinding code +// +// 4 2/16/98 2:25p Jimdose +// Added G_PushMove to allow non-physics functions to push an entity into +// place without having to use setOrigin or messing with velocities. +// Added hierarchial object binding. Lots of changes all over the place. +// +// 3 2/06/98 5:52p Jimdose +// Converted physics to be c++ +// +// 2 2/03/98 11:05a Jimdose +// In process of converting to work with Sin progs +// +// 1 1/26/98 5:48p Jimdose +// +// 3 12/30/97 6:04p Jimdose +// Added header text +// +// DESCRIPTION: +// + +#include "g_local.h" +#include "sentient.h" +#include "actor.h" + +extern cvar_t *sv_fatrockets; + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + +typedef struct + { + Entity *ent; + Vector origin; + Vector worldorigin; + Vector angles; + Vector worldangles; + float deltayaw; + } pushed_t; + +pushed_t pushed[ MAX_EDICTS ]; +pushed_t *pushed_p; + +Entity *obstacle; + + +/* +============ +G_TestEntityPosition + +============ +*/ +Entity *G_TestEntityPosition + ( + Entity *ent + ) + + { + int mask; + trace_t trace; + + mask = ent->edict->clipmask; + if ( !mask ) + mask = MASK_SOLID; + + trace = G_Trace( ent->worldorigin, ent->mins, ent->maxs, ent->worldorigin, ent, mask, "G_TestEntityPosition" ); + + if ( trace.startsolid ) + { + //return g_edicts->entity; + assert( trace.ent->entity ); + return trace.ent->entity; + } + + return NULL; + } + + +/* +================ +G_CheckVelocity +================ +*/ +void G_CheckVelocity + ( + Entity *ent + ) + + { + int i; + + // + // bound velocity + // + for( i = 0; i < 3; i++ ) + { + if ( ent->velocity[ i ] > sv_maxvelocity->value ) + { + ent->velocity[ i ] = sv_maxvelocity->value; + } + else if ( ent->velocity[ i ] < -sv_maxvelocity->value ) + { + ent->velocity[ i ] = -sv_maxvelocity->value; + } + } + } + +/* +================== +G_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void G_Impact + ( + Entity *e1, + trace_t *trace + ) + + { + edict_t *e2; + Event *ev; + qboolean doImpactDamage=false; + + e2 = trace->ent; + + if ( ( e1->movetype == MOVETYPE_HURL ) || ( e2->entity->movetype == MOVETYPE_HURL ) ) + { + doImpactDamage = true; + } + + //FIXME - this should be passed in the event. + level.impact_trace = *trace; + + if ( e1->edict->solid != SOLID_NOT ) + { + ev = new Event( EV_Touch ); + ev->AddEntity( e2->entity ); + e1->ProcessEvent( ev ); + + if ( doImpactDamage ) + { + ev = new Event( EV_Sentient_ImpactDamage ); + e1->ProcessEvent( ev ); + } + } + + if ( e2->entity && e2->solid != SOLID_NOT ) + { + ev = new Event( EV_Touch ); + ev->AddEntity( e1 ); + e2->entity->ProcessEvent( ev ); + + if ( doImpactDamage ) + { + ev = new Event( EV_Sentient_ImpactDamage ); + e2->entity->ProcessEvent( ev ); + } + } + + memset( &level.impact_trace, 0, sizeof( level.impact_trace ) ); + } + +/* +============= +G_AddCurrents +============= +*/ +void G_AddCurrents + ( + Entity *ent, + Vector *basevel + ) + + { + float speed; + csurface_t *surface; + float angle; + Vector vel; + + vel = vec_zero; + + // + // add water currents + // + if ( ent->watertype & MASK_CURRENT ) + { + speed = sv_waterspeed->value; + if ( ( ent->waterlevel == 1 ) && ( ent->groundentity ) ) + { + speed /= 2; + } + + if ( ent->watertype & CONTENTS_CURRENT_0 ) + { + vel[ 0 ] += speed; + } + if ( ent->watertype & CONTENTS_CURRENT_90 ) + { + vel[ 1 ] += speed; + } + if ( ent->watertype & CONTENTS_CURRENT_180 ) + { + vel[ 0 ] -= speed; + } + if ( ent->watertype & CONTENTS_CURRENT_270 ) + { + vel[ 1 ] -= speed; + } + if ( ent->watertype & CONTENTS_CURRENT_UP ) + { + vel[ 2 ] += speed; + } + if ( ent->watertype & CONTENTS_CURRENT_DOWN ) + { + vel[ 2 ] -= speed; + } + } + + // + // add conveyor belt velocities + // + if ( ent->groundentity && ent->groundsurface ) + { + surface = ent->groundsurface; + if ( ( surface->flags & SURF_CONVEYOR ) && ( surface->flags & SURF_TRANSLATE ) ) + { + angle = surface->trans_angle * ( 3.14159 / 180.0 ); + + vel[ 0 ] += -cos( angle ) * surface->trans_mag; + vel[ 1 ] += sin( angle ) * surface->trans_mag; + } + } + + *basevel = vel; + } + +/* +================== +G_ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int G_ClipVelocity + ( + Vector& in, + Vector& normal, + Vector& out, + float overbounce, + int gravaxis + ) + + { + int i; + int blocked; + float backoff; + + blocked = 0; + if ( ( normal[ gravity_axis[ gravaxis ].z ] * gravity_axis[ gravaxis ].sign ) > 0 ) + { + // floor + blocked |= 1; + } + if ( !normal[ gravity_axis[ gravaxis ].z ] ) + { + // step + blocked |= 2; + } + + backoff = ( in * normal ) * overbounce; + + out = in - normal * backoff; + for( i = 0; i < 3; i++ ) + { + if ( out[ i ] > -STOP_EPSILON && out[ i ] < STOP_EPSILON ) + { + out[ i ] = 0; + } + } + + return blocked; + } + + +/* +============ +G_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +#define MAX_CLIP_PLANES 5 + +int G_FlyMove + ( + Entity *ent, + Vector basevel, + float time, + int mask + ) + + { + Entity *hit; + edict_t *edict; + int bumpcount, numbumps; + Vector dir; + float d; + int numplanes; + vec3_t planes[ MAX_CLIP_PLANES ]; + Vector primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + Vector end; + float time_left; + int blocked; +#if 0 + Vector move; + Vector v; +#endif + + edict = ent->edict; + + numbumps = 4; + + blocked = 0; + original_velocity = ent->velocity; + primal_velocity = ent->velocity; + numplanes = 0; + +#if 1 + time_left = time; +#else + time_left = 1.0;//time; + + v = ent->total_delta; + v[ 1 ] = -v[ 1 ]; // sigh... + MatrixTransformVector( v.vec3(), ent->orientation, move.vec3() ); + move += ent->velocity * time; + ent->total_delta = vec_zero; +#endif + + ent->groundentity = NULL; + for( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) + { +#if 1 + end = ent->worldorigin + time_left * ( ent->velocity + basevel ); +#else + end = ent->worldorigin + time_left * move; +#endif + + trace = G_Trace( ent->worldorigin, ent->mins, ent->maxs, end, ent, mask, "G_FlyMove" ); + + if ( + ( trace.allsolid ) || + ( + ( trace.startsolid ) && + ( ent->movetype == MOVETYPE_VEHICLE ) + ) + ) + { + // entity is trapped in another solid + ent->velocity = vec_zero; + return 3; + } + + if ( trace.fraction > 0 ) + { + // actually covered some distance + ent->setOrigin( trace.endpos ); + original_velocity = ent->velocity; + numplanes = 0; + } + + if ( trace.fraction == 1 ) + { + // moved the entire distance + break; + } + + hit = trace.ent->entity; + + if ( trace.plane.normal[ 2 ] > 0.7 ) + { + // floor + blocked |= 1; + if ( hit->getSolidType() == SOLID_BSP ) + { + ent->groundentity = hit->edict; + ent->groundentity_linkcount = hit->edict->linkcount; + ent->groundplane = trace.plane; + ent->groundsurface = trace.surface; + ent->groundcontents = trace.contents; + } + } + + if ( !trace.plane.normal[ 2 ] ) + { + // step + blocked |= 2; + } + + // + // run the impact function + // + G_Impact( ent, &trace ); + if ( !edict->inuse ) + { + break; // removed by the impact function + } + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if ( numplanes >= MAX_CLIP_PLANES ) + { + // this shouldn't really happen + ent->velocity = vec_zero; + return 3; + } + + VectorCopy( trace.plane.normal, planes[ numplanes ] ); + numplanes++; + + // + // modify original_velocity so it parallels all of the clip planes + // + for( i = 0; i < numplanes; i++ ) + { + G_ClipVelocity( original_velocity, Vector( planes[ i ] ), new_velocity, 1.01, ent->gravaxis ); + for( j = 0; j < numplanes; j++ ) + { + if ( j != i ) + { + if ( ( new_velocity * planes[ j ] ) < 0 ) + { + // not ok + break; + } + } + } + + if ( j == numplanes ) + { + break; + } + } + + if ( i != numplanes ) + { + // go along this plane + ent->velocity = new_velocity; + } + else + { + // go along the crease + if ( numplanes != 2 ) + { + ent->velocity = vec_zero; + return 7; + } + CrossProduct( planes[ 0 ], planes[ 1 ], dir.vec3() ); + d = dir * ent->velocity; + ent->velocity = dir * d; + } + + // + // if original velocity is against the original velocity, stop dead + // to avoid tiny occilations in sloping corners + // + if ( ( ent->velocity * primal_velocity ) <= 0 ) + { + ent->velocity = vec_zero; + return blocked; + } + } + + return blocked; + } + + +/* +============ +G_AddGravity + +============ +*/ +void G_AddGravity + ( + Entity *ent + ) + + { + float grav; + + if ( ent->waterlevel > 2 ) + { + grav = ent->gravity * 60 * FRAMETIME * gravity_axis[ ent->gravaxis ].sign; + } + else + { + grav = ent->gravity * sv_gravity->value * FRAMETIME * gravity_axis[ ent->gravaxis ].sign; + } + + ent->velocity[ gravity_axis[ ent->gravaxis ].z ] -= grav; + } + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +G_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t G_PushEntity + ( + Entity *ent, + Vector push + ) + + { + trace_t trace; + Vector start; + Vector end; + int mask; + edict_t *edict; + + start = ent->worldorigin; + end = start + push; + +retry: + if ( ent->edict->clipmask ) + { + mask = ent->edict->clipmask; + } + else + { + mask = MASK_SOLID; + } + + if ( !sv_fatrockets->value && ( ent->flags & FL_FATPROJECTILE ) ) + { + trace = G_FullTrace( start, ent->mins, ent->maxs, end, ent->maxs.y, ent, mask, "G_PushEntity" ); + } + else + { + trace = G_Trace( start, ent->mins, ent->maxs, end, ent, mask, "G_PushEntity" ); + } + + edict = ent->edict; + + ent->setOrigin( trace.endpos ); + + if ( trace.fraction != 1.0 ) + { + G_Impact( ent, &trace ); + + // if the pushed entity went away and the pusher is still there + if ( !trace.ent->inuse && edict->inuse ) + { + // move the pusher back and try again + ent->setOrigin( start ); + goto retry; + } + } + + if ( edict && ( edict != ent->edict ) ) + { + G_TouchTriggers( ent ); + } + + return trace; + } + +/* +============ +G_SlideEntity +============ +*/ +trace_t G_SlideEntity + ( + Entity *ent, + Vector push + ) + + { + trace_t trace; + Vector start; + Vector end; + int mask; + edict_t *edict; + + start = ent->worldorigin; + end = start + push; + + if ( ent->edict->clipmask ) + { + mask = ent->edict->clipmask; + } + else + { + mask = MASK_SOLID; + } + + trace = G_Trace( start, ent->mins, ent->maxs, end, ent, mask, "G_SlideEntity" ); + + edict = ent->edict; + + ent->setOrigin( trace.endpos ); + + return trace; + } + + +/* +================ +G_SnapPosition + +================ +*/ +/* +qboolean G_SnapPosition + ( + Entity *ent + ) + + { + int x, y, z; + Vector offset( 0, -1, 1 ); + Vector base; + + base = ent->worldorigin; + for ( z = 0; z < 3; z++ ) + { + ent->worldorigin.z = base.z + offset[ z ]; + for ( y = 0; y < 3; y++ ) + { + ent->worldorigin.y = base.y + offset[ y ]; + for ( x = 0; x < 3; x++ ) + { + ent->worldorigin.x = base.x + offset[ x ]; + if ( G_TestEntityPosition( ent ) ) + { + ent->origin.x += offset[ x ]; + ent->origin.y += offset[ y ]; + ent->origin.z += offset[ z ]; + ent->setOrigin( ent->origin ); + return true; + } + } + } + } + + // can't find a good position, so put him back. + ent->worldorigin = base; + + return false; + } +*/ + +/* +============ +G_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean G_Push + ( + Entity *pusher, + Vector pushermove, + Vector pusheramove + ) + + { + Entity *check, *block; + edict_t *edict, *next; + Vector move, amove; + Vector mins, maxs; + Vector save; + pushed_t *p; + Vector org, org2, move2; + float mat[ 3 ][ 3 ]; + pushed_t *pusher_p; + + // save the pusher's original position + pusher_p = pushed_p; + pushed_p->ent = pusher; + pushed_p->origin = pusher->origin; + pushed_p->worldorigin = pusher->worldorigin; + pushed_p->angles = pusher->angles; + pushed_p->worldangles = pusher->worldangles; + if ( pusher->client ) + { + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[ YAW ]; + } + pushed_p++; + if ( pushed_p >= &pushed[ MAX_EDICTS ] ) + { + gi.error( ERR_FATAL, "Pushed too many entities." ); + } + + // move the pusher to it's final position + pusher->setAngles( pusher->angles + pusheramove ); + pusher->setOrigin( pusher->origin + pushermove ); + + if ( pusher->edict->solid == SOLID_NOT ) + { + // Doesn't push anything + return true; + } + + // change the move to worldspace + move = pusher->worldorigin - pusher_p->worldorigin; + amove = pusher->worldangles - pusher_p->worldangles; + + // we need this for pushing things later + AnglesToMat( amove.vec3(), mat ); + + // find the bounding box + mins = pusher->absmin; + maxs = pusher->absmax; + + // see if any solid entities are inside the final position + for( edict = g_edicts->next; edict != &active_edicts; edict = next ) + { + assert( edict ); + assert( edict->inuse ); + assert( edict->entity ); + + next = edict->next; + check = edict->entity; + + if ( check->movetype == MOVETYPE_PUSH || + check->movetype == MOVETYPE_STOP || + check->movetype == MOVETYPE_NONE || + check->movetype == MOVETYPE_NOCLIP ) + { + continue; + } + + // if the entity is standing on the pusher, it will definitely be moved + if ( check->groundentity != pusher->edict ) + { + // Only move triggers and non-solid objects if they're sitting on a moving object + if ( check->edict->solid == SOLID_TRIGGER || check->edict->solid == SOLID_NOT ) + { + continue; + } + + // see if the ent needs to be tested + if ( check->absmin[ 0 ] >= maxs[ 0 ] || + check->absmin[ 1 ] >= maxs[ 1 ] || + check->absmin[ 2 ] >= maxs[ 2 ] || + check->absmax[ 0 ] <= mins[ 0 ] || + check->absmax[ 1 ] <= mins[ 1 ] || + check->absmax[ 2 ] <= mins[ 2 ] ) + { + continue; + } + + // see if the ent's bbox is inside the pusher's final position + if ( !G_TestEntityPosition( check ) ) + { + continue; + } + } + + if ( ( pusher->movetype == MOVETYPE_PUSH ) || ( check->groundentity == pusher->edict ) ) + { + // move this entity + pushed_p->ent = check; + pushed_p->origin = check->origin; + pushed_p->worldorigin = check->worldorigin; + pushed_p->angles = check->angles; + pushed_p->worldangles = check->worldangles; + pushed_p++; + if ( pushed_p >= &pushed[ MAX_EDICTS ] ) + { + gi.error( ERR_FATAL, "Pushed too many entities." ); + } + + // save off the origin + save = check->origin; + + // try moving the contacted entity + move2 = move; + +#if 0 +// The way id had this was wrong (delta_angles are shorts, not floats) +// This is probably what it should be, but it's not interpolated by the client, so it jitters. + if ( check->client ) + { + // FIXME: doesn't rotate monsters? + //check->client->ps.pmove.delta_angles[ YAW ] += amove[ YAW ]; + + check->client->ps.pmove.delta_angles[ YAW ] = + ANGLE2SHORT( + SHORT2ANGLE( check->client->ps.pmove.delta_angles[ YAW ] ) + amove[ YAW ] ); + } +#endif + + // figure movement due to the pusher's amove + org = check->worldorigin - pusher->worldorigin; + + MatrixTransformVector( org.vec3(), mat, org2.vec3() ); + move2 += org2 - org; + + //FIXME + // We should probably do a flymove here so that we slide against other objects + check->setOrigin( check->origin + check->getParentVector( move2 ) ); + + // may have pushed them off an edge + if ( check->groundentity != pusher->edict ) + { + check->groundentity = NULL; + } + + block = G_TestEntityPosition( check ); + if ( !block ) + { + // pushed ok + check->link(); + + // impact? + continue; + } + + // try to snap it to a good position + /* + if ( G_SnapPosition( check ) ) + { + // snapped ok. we don't have to link since G_SnapPosition does it for us. + continue; + } + */ + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + check->setOrigin( save ); + block = G_TestEntityPosition( check ); + if ( !block ) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for( p = pushed_p - 1; p >= pushed; p-- ) + { + p->ent->angles = p->angles; + p->ent->origin = p->origin; + if ( p->ent->client ) + { + p->ent->client->ps.pmove.delta_angles[ YAW ] = p->deltayaw; + } + } + + // Only "really" move it in order so that the bound coordinate system is correct + for( p = pushed; p < pushed_p; p++ ) + { + p->ent->setAngles( p->ent->angles ); + p->ent->setOrigin( p->ent->origin ); + } + + return false; + } + + //FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for( p = pushed_p - 1; p >= pushed; p-- ) + { + G_TouchTriggers( p->ent ); + } + + return true; + } + +/* +================ +G_PushMove +================ +*/ +qboolean G_PushMove + ( + Entity *ent, + Vector move, + Vector amove + ) + + { + Entity *part; + Vector m, a; + Event *ev; + + m = move; + a = amove; + + pushed_p = pushed; + for( part = ent; part; part = part->teamchain ) + { + if ( !G_Push( part, m, a ) ) + { + // move was blocked + // call the pusher's "blocked" function + // otherwise, just stay in place until the obstacle is gone + ev = new Event( EV_Blocked ); + ev->AddEntity( obstacle ); + part->ProcessEvent( ev ); + return false; + } + + m = vec_zero; + a = vec_zero; + } + + return true; + } + +/* +================ +G_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void G_Physics_Pusher + ( + Entity *ent + ) + + { + Vector move, amove; + Entity *part, *mv; + Event *ev; + + // team slaves are only moved by their captains + if ( ent->flags & FL_TEAMSLAVE ) + { + return; + } + + // Check if anyone on the team is moving + for( part = ent; part; part = part->teamchain ) + { + if ( part->velocity != vec_zero || part->avelocity != vec_zero ) + { + break; + } + } + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out + pushed_p = pushed; + while( part ) + { + move = part->velocity * FRAMETIME; + amove = part->avelocity * FRAMETIME; + if ( !G_Push( part, move, amove ) ) + { + // move was blocked + break; + } + + part = part->teamchain; + } + + if ( part ) + { + // the move failed, bump all movedone times + for( mv = ent; mv; mv = mv->teamchain ) + { + mv->PostponeEvent( EV_MoveDone, FRAMETIME ); + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + ev = new Event( EV_Blocked ); + ev->AddEntity( obstacle ); + part->ProcessEvent( ev ); + } + } + +//================================================================== + +/* +============= +G_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void G_Physics_Noclip + ( + Entity *ent + ) + + { + ent->angles += ent->avelocity * FRAMETIME; + ent->origin += ent->velocity * FRAMETIME; + ent->link(); + } + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +G_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void G_Physics_Toss + ( + Entity *ent + ) + + { + trace_t trace; + Vector move; + float backoff; + Entity *slave; + qboolean wasinwater; + qboolean isinwater; + Vector old_origin; + Vector basevel; + edict_t *edict; + qboolean onconveyor; + const gravityaxis_t &grav = gravity_axis[ ent->gravaxis ]; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE ) + { + return; + } + + if ( ( ent->velocity[ grav.z ] * grav.sign ) > 0 ) + { + ent->groundentity = NULL; + } + + // check for the groundentity going away + if ( ent->groundentity && !ent->groundentity->inuse ) + { + ent->groundentity = NULL; + } + + G_AddCurrents( ent, &basevel ); + onconveyor = ( basevel != vec_zero ); + + // if onground, return without moving + if ( ent->groundentity && !onconveyor && ( ent->movetype != MOVETYPE_VEHICLE ) ) + { + if ( ent->avelocity.length() ) + { + // move angles + ent->setAngles( ent->angles + ent->avelocity * FRAMETIME ); + } + ent->velocity = vec_zero; + return; + } + + old_origin = ent->origin; + + G_CheckVelocity( ent ); + + // add gravity + if ( !onconveyor && ent->movetype != MOVETYPE_FLY && ent->movetype != MOVETYPE_FLYMISSILE ) + { + G_AddGravity( ent ); + } + + // move angles + ent->setAngles( ent->angles + ent->avelocity * FRAMETIME ); + + // move origin + move = ( ent->velocity + basevel ) * FRAMETIME; + + edict = ent->edict; + if ( ent->movetype == MOVETYPE_VEHICLE ) + { + int mask; + + if ( ent->edict->clipmask ) + { + mask = ent->edict->clipmask; + } + else + { + mask = MASK_MONSTERSOLID; + } + G_FlyMove( ent, basevel, FRAMETIME, mask ); + G_TouchTriggers( ent ); + return; + } + else + { + trace = G_PushEntity( ent, move ); + } + + if ( (trace.fraction == 0) && (ent->movetype == MOVETYPE_SLIDE) ) + { + // Check for slide by removing the downward velocity + Vector slide; + + slide[ grav.x ] = move[ grav.x ] * 0.7f; + slide[ grav.y ] = move[ grav.y ] * 0.7f; + slide[ grav.z ] = 0; + G_PushEntity( ent, slide ); + } + + if ( !edict->inuse ) + { + return; + } + + if ( trace.fraction < 1 ) + { + if ( ent->movetype == MOVETYPE_BOUNCE ) + { + backoff = 1.5; + } + else + { + backoff = 1; + } + + G_ClipVelocity( ent->velocity, Vector( trace.plane.normal ), ent->velocity, backoff, ent->gravaxis ); + + // stop if on ground + if ( ( trace.plane.normal[ grav.z ] * grav.sign ) > 0.7 ) + { + if (( ( ent->velocity[ grav.z ] * grav.sign ) < 60 || ent->movetype != MOVETYPE_BOUNCE ) && + (ent->movetype != MOVETYPE_SLIDE)) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + ent->groundplane = trace.plane; + ent->groundsurface = trace.surface; + ent->groundcontents = trace.contents; + ent->velocity = vec_zero; + ent->avelocity = vec_zero; + } + } + } + + if ( ( move[ grav.z ] == 0 ) && onconveyor ) + { + // Check if we still have a ground + ent->CheckGround(); + } + + // check for water transition + wasinwater = ( ent->watertype & MASK_WATER ); + ent->watertype = gi.pointcontents( ent->worldorigin.vec3() ); + isinwater = ent->watertype & MASK_WATER; + + if ( isinwater ) + { + ent->waterlevel = 1; + } + else + { + ent->waterlevel = 0; + } + + if ( ( edict->spawntime < ( level.time - FRAMETIME ) ) && ( ent->mass > 0 ) ) + { + if ( !wasinwater && isinwater ) + { +#ifdef SIN + ent->RandomPositionedSound( old_origin.vec3(), "impact_watersplash" ); +#else + gi.positioned_sound( old_origin.vec3(), g_edicts, CHAN_AUTO, + gi.soundindex( "misc/h2ohit1.wav" ), 1, 1, 0, 1, 0, 0 ); +#endif + } + else if ( wasinwater && !isinwater ) + { +#ifdef SIN + ent->RandomPositionedSound( old_origin.vec3(), "impact_watersplash" ); +#else + gi.positioned_sound( old_origin.vec3(), g_edicts, CHAN_AUTO, + gi.soundindex( "misc/h2ohit1.wav" ), 1, 1, 0, 1, 0, 0 ); +#endif + } + } + + // move teamslaves + for( slave = ent->teamchain; slave; slave = slave->teamchain ) + { + slave->origin = ent->origin; + slave->link(); + } + + G_TouchTriggers( ent ); + } + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +void G_AddRotationalFriction + ( + Entity *ent + ) + + { + int n; + float adjustment; + + ent->angles += FRAMETIME * ent->avelocity; + adjustment = FRAMETIME * sv_stopspeed->value * sv_friction->value; + for( n = 0; n < 3; n++ ) + { + if ( ent->avelocity[ n ] > 0) + { + ent->avelocity[ n ] -= adjustment; + if ( ent->avelocity[ n ] < 0 ) + { + ent->avelocity[ n ] = 0; + } + } + else + { + ent->avelocity[ n ] += adjustment; + if ( ent->avelocity[ n ] > 0 ) + { + ent->avelocity[ n ] = 0; + } + } + } + } + +/* +============= +G_CheckWater + +============= +*/ + +void G_CheckWater + ( + Entity *ent + ) + + { + if ( ent->isSubclassOf( Actor ) ) + { + ( ( Actor * )ent )->CheckWater(); + } + else + { + ent->watertype = gi.pointcontents( ent->worldorigin.vec3() ); + if ( ent->watertype & MASK_WATER ) + { + ent->waterlevel = 1; + } + else + { + ent->waterlevel = 0; + } + } + } + +/* +============= +G_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +FIXME: is this true? +============= +*/ + +void G_Physics_Step + ( + Entity *ent + ) + + { + qboolean wasonground; + qboolean hitsound = false; + Vector vel; + float speed, newspeed, control; + float friction; + int mask; + Vector basevel; + + // airborn monsters should always check for ground + if ( !ent->groundentity ) + { + ent->CheckGround(); + } + + if ( ent->groundentity ) + { + wasonground = true; + } + else + { + wasonground = false; + } + + G_CheckVelocity( ent ); + + if ( ent->avelocity != vec_zero ) + { + G_AddRotationalFriction( ent ); + } + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if ( !wasonground ) + { + if ( !( ent->flags & FL_FLY ) ) + { + if ( !( ( ent->flags & FL_SWIM ) && ( ent->waterlevel > 2 ) ) ) + { + if ( ent->velocity[ gravity_axis[ ent->gravaxis ].z ] < sv_gravity->value * ent->gravity * -0.1 * + gravity_axis[ ent->gravaxis ].sign ) + { + hitsound = true; + } + + // Testing water gravity. If this doesn't work, just restore the uncommented lines + //if ( ent->waterlevel == 0 ) + //{ + G_AddGravity( ent ); + //} + } + } + } + + // friction for flying monsters that have been given vertical velocity + if ( ( ent->flags & FL_FLY ) && ( ent->velocity.z != 0 ) ) + { + speed = fabs( ent->velocity.z ); + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + friction = sv_friction->value / 3; + newspeed = speed - ( FRAMETIME * control * friction ); + if ( newspeed < 0 ) + { + newspeed = 0; + } + newspeed /= speed; + ent->velocity.z *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ( ( ent->flags & FL_SWIM ) && ( ent->velocity.z != 0 ) ) + { + speed = fabs( ent->velocity.z ); + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + newspeed = speed - ( FRAMETIME * control * sv_waterfriction->value * ent->waterlevel ); + if ( newspeed < 0 ) + { + newspeed = 0; + } + newspeed /= speed; + ent->velocity.z *= newspeed; + } + + if ( ent->velocity != vec_zero ) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ( ( wasonground ) || ( ent->flags & ( FL_SWIM | FL_FLY ) ) ) + { + if ( !( ent->health <= 0.0 && !M_CheckBottom( ent ) ) ) + { + vel = ent->velocity; + vel.z = 0; + speed = vel.length(); + if ( speed ) + { + friction = sv_friction->value; + + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + newspeed = speed - FRAMETIME * control * friction; + + if ( newspeed < 0 ) + { + newspeed = 0; + } + + newspeed /= speed; + + ent->velocity.x *= newspeed; + ent->velocity.y *= newspeed; + } + } + } + } + + G_AddCurrents( ent, &basevel ); + + if ( ( basevel != vec_zero ) || ( ent->velocity != vec_zero ) || ( ent->total_delta != vec_zero ) ) + { + if ( ent->edict->svflags & SVF_MONSTER ) + { + mask = MASK_MONSTERSOLID; + } + else + { + mask = MASK_SOLID; + } + + G_FlyMove( ent, basevel, FRAMETIME, mask ); + + ent->link(); + + G_CheckWater( ent ); + G_TouchTriggers( ent ); + + if ( ent->groundentity && !wasonground && hitsound ) + { + ent->RandomGlobalSound( "impact_softland", 0.5f, CHAN_BODY, 1 ); + } + } + } + +//============================================================================ +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity + ( + Entity *ent + ) + + { + edict_t *edict; + + edict = ent->edict; + + if ( ent->animating && !level.intermissiontime ) + { + ent->AnimateFrame(); + } + + if ( edict->inuse && ent->flags & FL_PRETHINK ) + { + ent->Prethink(); + } + + if ( edict->inuse ) + { + switch ( ( int )ent->movetype ) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + G_Physics_Pusher( ent ); + break; + case MOVETYPE_NONE: + case MOVETYPE_WALK: + break; + case MOVETYPE_NOCLIP: + G_Physics_Noclip( ent ); + break; + case MOVETYPE_STEP: + case MOVETYPE_HURL: + G_Physics_Step( ent ); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + case MOVETYPE_SLIDE: + case MOVETYPE_VEHICLE: + G_Physics_Toss( ent ); + break; + default: + gi.error( "G_Physics: bad movetype %i", ( int )ent->movetype ); + } + } + + if ( ( edict->inuse ) && ( ent->flags & FL_POSTTHINK ) ) + { + ent->Postthink(); + } + } diff --git a/g_phys.h b/g_phys.h new file mode 100644 index 0000000..36d31c5 --- /dev/null +++ b/g_phys.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/g_phys.h $ +// $Revision:: 5 $ +// $Author:: Jimdose $ +// $Date:: 11/13/98 10:04p $ +// +// Copyright (C) 1998 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/g_phys.h $ +// +// 5 11/13/98 10:04p Jimdose +// added G_CheckWater +// got rid of unused physics functions +// +// 4 10/04/98 10:28p Aldie +// Added multiple weapon changes. Damage, flashes, quantum stuff +// +// 3 9/14/98 6:12p Markd +// Added MOVETYPE_VEHICLE +// +// 2 8/29/98 9:49p Jimdose +// created file +// +// 1 8/29/98 6:44p Jimdose +// +// DESCRIPTION: +// Global header file for g_phys.cpp +// + +#ifndef __G_PHYS_H__ +#define __G_PHYS_H__ + +#include "g_local.h" + +typedef enum + { + STEPMOVE_OK, + STEPMOVE_BLOCKED_BY_ENTITY, + STEPMOVE_BLOCKED_BY_WORLD, + STEPMOVE_BLOCKED_BY_WATER, + STEPMOVE_BLOCKED_BY_FALL, + STEPMOVE_STUCK, + } stepmoveresult_t; + +#define STEPSIZE 18 + +// movetype values +typedef enum + { + MOVETYPE_NONE, // never moves + MOVETYPE_NOCLIP, // origin and angles change with no interaction + MOVETYPE_PUSH, // no clip to world, push on box contact + MOVETYPE_STOP, // no clip to world, stops on box contact + MOVETYPE_WALK, // gravity + MOVETYPE_STEP, // gravity, special edge handling + MOVETYPE_FLY, + MOVETYPE_TOSS, // gravity + MOVETYPE_FLYMISSILE, // extra size to monsters + MOVETYPE_BOUNCE, + MOVETYPE_SLIDE, + MOVETYPE_VEHICLE, + MOVETYPE_HURL + } movetype_t; + +void G_RunEntity (Entity *ent); +void G_Impact( Entity *e1, trace_t *trace ); +qboolean G_PushMove( Entity *pusher, Vector move, Vector amove ); +void G_CheckWater( Entity *ent ); + +#endif /* g_phys.h */ diff --git a/g_spawn.cpp b/g_spawn.cpp new file mode 100644 index 0000000..8f7eb5d --- /dev/null +++ b/g_spawn.cpp @@ -0,0 +1,1785 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/g_spawn.cpp $ +// $Revision:: 66 $ +// $Author:: Jimdose $ +// $Date:: 11/07/98 10:01p $ +// +// Copyright (C) 1998 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/g_spawn.cpp $ +// +// 66 11/07/98 10:01p Jimdose +// added G_GetClassFromArgs +// +// 65 11/05/98 8:29p Aldie +// Removed checksumming and fixed stuff for loading times +// +// 64 10/28/98 4:46p Jimdose +// G_ArchiveEdict was accessing edict->owner->entity, which may be cleared out +// when called. +// +// 63 10/27/98 9:46p Aldie +// Changed training cvar to level.training +// +// 62 10/26/98 4:20p Markd +// Added game.skill variable +// +// 61 10/25/98 11:57p Jimdose +// moved playerfrozen from game to level +// +// 60 10/24/98 11:47p Jimdose +// made RestoreEnt call G_InitSpawnArguments +// +// 59 10/23/98 3:43a Markd +// put in universal_script support +// +// 58 10/19/98 11:46p Aldie +// Clear spawn arguments out after spawning things +// +// 57 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 56 10/18/98 8:43p Jimdose +// Started adding check for SOLID_BSP and no model in SpawnEntities +// +// 55 10/17/98 8:15p Jimdose +// Changed G_CallSpawn2 to G_CallSpawn +// +// 54 10/17/98 12:21a Jimdose +// Added G_ResetEdicts +// +// 53 10/16/98 1:50a Jimdose +// Removed G_SaveClientData from G_LevelShutdown +// +// 52 10/15/98 7:13p Jimdose +// Overrode = for SpawnArgGroup. Fixes crash on level change with multiplayer +// +// 51 10/14/98 10:56p Jimdose +// More work on persistant data +// added archive functions for spawnargs classes +// +// 50 10/14/98 1:18a Jimdose +// Got cross-level persistant info working +// +// 49 10/11/98 8:51p Jimdose +// Added LoadingServer variable +// +// 48 10/10/98 12:39p Markd +// Renamed team to moveteam +// +// 47 10/10/98 1:27a Jimdose +// added G_Precache +// +// 46 10/07/98 11:47p Jimdose +// Got savegames working +// +// 45 9/15/98 6:46p Aldie +// Clear out the event list after deleting all the edicts +// +// 44 9/02/98 3:47p Markd +// Put in precache ability for the main game script +// +// 43 8/29/98 9:42p Jimdose +// Made all function names consistantly begin with G_ +// Added call info to G_Trace +// +// 42 8/29/98 2:53p Aldie +// Added status meter for loading levels. +// +// 41 8/27/98 4:18p Jimdose +// Only start script when the world is spawned so that demomap won't start +// scripts +// +// 40 8/24/98 1:33p Markd +// fixed null string problem with gamescript +// +// 39 8/24/98 11:32a Markd +// Added Start method to threads, repladed all ProcessEvent( +// EV_ScriptThread_execute) with thread->Start( -1 ) +// +// 38 8/12/98 3:19p Aldie +// Created a G_CallSpawn2 that returns pointer to Entity that is created. +// Currently used with func_box. +// +// 37 8/04/98 6:05p Aldie +// Relocated DETAIL spawnflag +// +// 36 7/08/98 12:54p Jimdose +// Made developer c_var global +// +// 35 7/02/98 5:24p Markd +// Added detail spawning abilities +// +// 34 6/24/98 4:49p Aldie +// Made it so func_spawn will work. +// +// 33 6/23/98 9:54p Jimdose +// Fixed infinite loop bug in G_RunFrame +// +// 32 6/16/98 9:04p Markd +// made objects default to "Object" instead of "entity" +// +// 31 5/27/98 5:02a Aldie +// Added queue for bloodsplats +// +// 30 5/26/98 7:56p Jimdose +// added scripted cameras +// +// 29 5/25/98 6:52a Aldie +// Added ResetBulletHoles to SpawnEntity +// +// 28 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 +// +// 27 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 26 5/24/98 2:47p Markd +// Made char *'s into const char *'s +// +// 25 5/20/98 11:11a Markd +// removed char * dependency +// +// 24 5/18/98 8:13p Jimdose +// Renamed Navigator back to PathManager +// +// 23 5/12/98 7:07p Markd +// Put in Development spawnflag +// +// 22 5/09/98 8:02p Jimdose +// AI nodes are saved after map changes +// +// 21 4/30/98 5:28p Jimdose +// Removed check for null models in G_CallSpawn. If an entity deleted itself +// in its constructor, the check would access deallocated memory +// +// 20 4/29/98 5:04p Jimdose +// Fixed gi.error so that it doesn't crash when called from C++ +// +// 19 4/28/98 8:13p Jimdose +// Added checks to ensure that SOLID_BSP objects have models +// +// 18 4/28/98 5:26p Jimdose +// Made spawnflags for !easy, !medium, !hard, !deathmatch and !coop work. +// +// 17 4/27/98 3:42p Jimdose +// Navigator is initialized in SpawnEntities +// +// 16 4/18/98 6:53p Markd +// Fixed spawning of new entity classes +// +// 15 4/16/98 2:04p Jimdose +// Removed pathmanager +// +// 14 4/07/98 9:18p Jimdose +// Think I found that bug where free_edicts gets put on the active_edicts list. +// +// 13 4/07/98 8:02p Markd +// Added tons 'o SINMDL prefixed commands +// +// 12 4/07/98 5:41p Jimdose +// Added assertions to G_Spawn and G_FreeEdict +// +// 11 4/05/98 1:56a Jimdose +// fixed bug where edicts were being freed, but not entities when the map +// changed +// +// 10 3/31/98 12:30a Markd +// Added models/ to spawning of model stuff +// +// 9 3/30/98 9:44p Markd +// Added in proper spawing for classname "model" +// +// 8 3/27/98 5:37p Jimdose +// SpawnEntities now frees up any entities for when levels change, but the game +// is the same. +// +// 7 3/26/98 8:19p Jimdose +// G_FreeEdict no longer clears the edicts client variable +// +// 6 3/13/98 7:24p Aldie +// Added get vector arg. +// +// 5 3/02/98 5:46p Jimdose +// pathManager frees all cached paths on starting a map. +// +// 4 2/19/98 5:03p Jimdose +// Moved G_Entity, G_Random, and G_CRandom to g_utils +// +// 3 2/16/98 2:18p Jimdose +// Made G_FindTeams work +// Added active_edicts and free_edicts for faster entity processing and for +// operations that depend on the order that physics is processed on entities +// (such as binding). +// +// 2 2/03/98 11:06a Jimdose +// Converted to work with Sin progs +// +// 1 1/21/98 2:56p Jimdose +// +// 3 12/30/97 6:04p Jimdose +// Added header text +// +// DESCRIPTION: +// + +#include "g_local.h" +#include "class.h" +#include "Entity.h" +#include "g_spawn.h" +#include "navigate.h" +#include +#include "player.h" +#include "gravpath.h" +#include "surface.h" +#include "console.h" +#include "object.h" + +void G_ExitWithError( void ); +extern jmp_buf G_AbortGame; + +SpawnArgsForEntity PersistantData; + +typedef struct + { + char key[ 64 ]; + char value[ 256 ]; + } spawnargs_t; + +#define NUM_SPAWN_ARGS 32 + +int numSpawnArgs = 0; +spawnargs_t spawnArgs[ NUM_SPAWN_ARGS ]; + +/**************************************************************************** + + SpawnArg Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Class, SpawnArg, NULL ); + +ResponseDef SpawnArg::Responses[] = + { + { NULL, NULL } + }; + +SpawnArg::SpawnArg() + { + memset( key, 0, sizeof( key ) ); + memset( value, 0, sizeof( value ) ); + } + +SpawnArg::SpawnArg + ( + SpawnArg &arg + ) + + { + strcpy( key, arg.key ); + strcpy( value, arg.value ); + } + +void SpawnArg::Archive + ( + Archiver &arc + ) + + { + Class::Archive( arc ); + + arc.WriteRaw( key, sizeof( key ) ); + arc.WriteRaw( value, sizeof( value ) ); + } + +void SpawnArg::Unarchive + ( + Archiver &arc + ) + + { + Class::Unarchive( arc ); + + arc.ReadRaw( key, sizeof( key ) ); + arc.ReadRaw( value, sizeof( value ) ); + } + +/**************************************************************************** + + SpawnArgs Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Class, SpawnArgs, NULL ); + +ResponseDef SpawnArgs::Responses[] = + { + { NULL, NULL } + }; + +SpawnArgs::SpawnArgs() + { + } + +SpawnArgs::SpawnArgs + ( + SpawnArgs &otherlist + ) + + { + SpawnArg arg1; + SpawnArg *arg2; + int num; + int i; + + num = otherlist.NumArgs(); + for( i = 1; i <= num; i++ ) + { + arg2 = otherlist.argList.AddressOfObjectAt( i ); + + strcpy( arg1.key, arg2->key ); + strcpy( arg1.value, arg2->value ); + + argList.AddObject( arg1 ); + } + argList.Resize( num ); + } + +void SpawnArgs::operator= + ( + SpawnArgs &otherlist + ) + + { + SpawnArg arg1; + SpawnArg *arg2; + int num; + int i; + + argList.ClearObjectList(); + + num = otherlist.NumArgs(); + for( i = 1; i <= num; i++ ) + { + arg2 = otherlist.argList.AddressOfObjectAt( i ); + + strcpy( arg1.key, arg2->key ); + strcpy( arg1.value, arg2->value ); + + argList.AddObject( arg1 ); + } + argList.Resize( num ); + } + +int SpawnArgs::NumArgs + ( + void + ) + + { + return argList.NumObjects(); + } + +void SpawnArgs::SetArgs + ( + void + ) + + { + SpawnArg arg; + int i; + + for( i = 0; i < numSpawnArgs; i++ ) + { + strcpy( arg.key, spawnArgs[ i ].key ); + strcpy( arg.value, spawnArgs[ i ].value ); + argList.AddObject( arg ); + } + argList.Resize( numSpawnArgs ); + } + +void SpawnArgs::RestoreArgs + ( + void + ) + + { + SpawnArg *arg; + int i; + + numSpawnArgs = argList.NumObjects(); + for( i = 0; i < numSpawnArgs; i++ ) + { + arg = &argList.ObjectAt( i + 1 ); + strcpy( spawnArgs[ i ].key, arg->key ); + strcpy( spawnArgs[ i ].value, arg->value ); + } + } + +void SpawnArgs::Archive + ( + Archiver &arc + ) + + { + int i; + int num; + + Class::Archive( arc ); + + num = argList.NumObjects(); + arc.WriteInteger( num ); + for( i = 1; i <= num; i++ ) + { + arc.WriteObject( argList.AddressOfObjectAt( i ) ); + } + } + +void SpawnArgs::Unarchive + ( + Archiver &arc + ) + + { + int i; + int num; + + Class::Unarchive( arc ); + + argList.FreeObjectList(); + + num = arc.ReadInteger(); + argList.Resize( num ); + for( i = 1; i <= num; i++ ) + { + arc.ReadObject( argList.AddressOfObjectAt( i ) ); + } + } + +/**************************************************************************** + + SpawnArgGroup Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Class, SpawnArgGroup, NULL ); + +ResponseDef SpawnArgGroup::Responses[] = + { + { NULL, NULL } + }; + +SpawnArgGroup::SpawnArgGroup() + { + } + +SpawnArgGroup::SpawnArgGroup + ( + SpawnArgGroup &group + ) + + { + SpawnArgs *arg; + int num; + int i; + + num = group.spawnArgList.NumObjects(); + for( i = 1; i <= num; i++ ) + { + arg = group.spawnArgList.AddressOfObjectAt( i ); + spawnArgList.AddObject( *arg ); + } + spawnArgList.Resize( num ); + } + +void SpawnArgGroup::operator= + ( + SpawnArgGroup &group + ) + + { + SpawnArgs *arg; + int num; + int i; + + num = group.spawnArgList.NumObjects(); + for( i = 1; i <= num; i++ ) + { + arg = group.spawnArgList.AddressOfObjectAt( i ); + spawnArgList.AddObject( *arg ); + } + spawnArgList.Resize( num ); + } + +int SpawnArgGroup::NumInGroup + ( + void + ) + + { + return spawnArgList.NumObjects(); + } + +void SpawnArgGroup::AddArgs + ( + void + ) + + { + SpawnArgs args; + + args.SetArgs(); + spawnArgList.AddObject( args ); + spawnArgList.Resize( NumInGroup() ); + } + +void SpawnArgGroup::RestoreArgs + ( + int i + ) + + { + assert( ( i > 0 ) && ( i <= NumInGroup() ) ); + if ( ( i <= 0 ) || ( i > NumInGroup() ) ) + { + G_InitSpawnArguments(); + return; + } + + spawnArgList.ObjectAt( i ).RestoreArgs(); + } + +void SpawnArgGroup::Archive + ( + Archiver &arc + ) + + { + int i; + int num; + + Class::Archive( arc ); + + num = spawnArgList.NumObjects(); + arc.WriteInteger( num ); + for( i = 1; i <= num; i++ ) + { + arc.WriteObject( spawnArgList.AddressOfObjectAt( i ) ); + } + } + +void SpawnArgGroup::Unarchive + ( + Archiver &arc + ) + + { + int i; + int num; + + Class::Unarchive( arc ); + + spawnArgList.FreeObjectList(); + + num = arc.ReadInteger(); + spawnArgList.Resize( num ); + for( i = 1; i <= num; i++ ) + { + arc.ReadObject( spawnArgList.AddressOfObjectAt( i ) ); + } + } + +/**************************************************************************** + + SpawnArgsForEntity Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Class, SpawnArgsForEntity, NULL ); + +ResponseDef SpawnArgsForEntity::Responses[] = + { + { NULL, NULL } + }; + +void SpawnArgsForEntity::Reset + ( + void + ) + + { + groupList.FreeObjectList(); + entnumList.FreeObjectList(); + } + +void SpawnArgsForEntity::AddEnt + ( + Entity *ent + ) + + { + int num; + SpawnArgGroup group; + + if ( ent && ent->isSubclassOf( Sentient ) ) + { + G_InitSpawnArguments(); + + entnumList.AddObject( ent->entnum ); + groupList.AddObject( group ); + num = groupList.NumObjects(); + groupList.Resize( num ); + entnumList.Resize( num ); + ( ( Sentient * )ent )->WritePersistantData( groupList.ObjectAt( num ) ); + } + } + +qboolean SpawnArgsForEntity::RestoreEnt + ( + Entity *ent + ) + + { + int num; + SpawnArgGroup *group; + + num = entnumList.IndexOfObject( ent->entnum ); + if ( num && ent->isSubclassOf( Sentient ) ) + { + group = groupList.AddressOfObjectAt( num ); + ( ( Sentient * )ent )->RestorePersistantData( *group ); + + G_InitSpawnArguments(); + + return true; + } + + return false; + } + +void SpawnArgsForEntity::RestoreEnts + ( + void + ) + + { + int num; + int i; + int entnum; + edict_t *ent; + SpawnArgGroup *group; + + num = groupList.NumObjects(); + for( i = 1; i <= num; i++ ) + { + entnum = entnumList.ObjectAt( i ); + ent = &g_edicts[ entnum ]; + + group = groupList.AddressOfObjectAt( i ); + + group->RestoreArgs( 1 ); + + game.force_entnum = true; + game.spawn_entnum = ent->s.number; + G_CallSpawn(); + game.force_entnum = false; + + if ( ent->entity && ent->entity->isSubclassOf( Sentient ) ) + { + ( ( Sentient * )ent->entity )->RestorePersistantData( *group ); + } + } + + Reset(); + } + +void SpawnArgsForEntity::Archive + ( + Archiver &arc + ) + + { + int i; + int num; + + Class::Archive( arc ); + + num = groupList.NumObjects(); + arc.WriteInteger( num ); + for( i = 1; i <= num; i++ ) + { + arc.WriteInteger( entnumList.ObjectAt( i ) ); + arc.WriteObject( groupList.AddressOfObjectAt( i ) ); + } + } + +void SpawnArgsForEntity::Unarchive + ( + Archiver &arc + ) + + { + int i; + int num; + + Reset(); + + Class::Unarchive( arc ); + + num = arc.ReadInteger(); + entnumList.Resize( num ); + groupList.Resize( num ); + for( i = 1; i <= num; i++ ) + { + arc.ReadInteger( entnumList.AddressOfObjectAt( i ) ); + arc.ReadObject( groupList.AddressOfObjectAt( i ) ); + } + } + +/**************************************************************************** + + spawn arg management + +****************************************************************************/ + +void G_SetFloatArg + ( + const char *key, + double value + ) + + { + char text[ 20 ]; + + sprintf( text, "%f", value ); + G_SetSpawnArg( key, text ); + } + +void G_SetIntArg + ( + const char *key, + int value + ) + + { + char text[ 20 ]; + + sprintf( text, "%d", value ); + G_SetSpawnArg( key, text ); + } + +void G_DefaultArg + ( + const char *key, + const char *defaultvalue + ) + + { + if ( !G_GetSpawnArg( key ) ) + { + G_SetSpawnArg( key, defaultvalue ); + } + } + +void G_DefaultFloatArg + ( + const char *key, + double defaultvalue + ) + + { + if ( !G_GetSpawnArg( key ) ) + { + G_SetFloatArg( key, defaultvalue ); + } + } + +void G_DefaultIntArg + ( + const char *key, + int defaultvalue + ) + + { + if ( !G_GetSpawnArg( key ) ) + { + G_SetIntArg( key, defaultvalue ); + } + } + +Vector G_GetVectorArg + ( + const char *key, + Vector defaultvalue + ) + + { + const char *text; + + text = G_GetSpawnArg( key ); + if ( text ) + { + return Vector(text); + } + return defaultvalue; + } + +float G_GetFloatArg + ( + const char *key, + double defaultvalue + ) + + { + const char *text; + + text = G_GetSpawnArg( key ); + if ( text ) + { + return (float)atof( text ); + } + return (float)defaultvalue; + } + +int G_GetIntArg + ( + const char *key, + int defaultvalue + ) + + { + const char *text; + + text = G_GetSpawnArg( key ); + if ( text ) + { + return atoi( text ); + } + return defaultvalue; + } + +str G_GetStringArg + ( + const char *key, + const char *defaultvalue + ) + + { + const char *text; + str ret; + + text = G_GetSpawnArg( key ); + if ( !text ) + { + text = defaultvalue; + } + + if ( text ) + { + return text; + } + + return ""; + } + +void G_InitSpawnArguments + ( + void + ) + + { + int i; + + numSpawnArgs = 0; + for( i = 0; i < NUM_SPAWN_ARGS; i++ ) + { + memset( spawnArgs[ i ].key, 0, sizeof( spawnArgs[ i ].key ) ); + memset( spawnArgs[ i ].value, 0, sizeof( spawnArgs[ i ].value ) ); + } + } + +qboolean G_SetSpawnArg + ( + const char *keyname, + const char *value + ) + + { + int i; + + for( i = 0; i < numSpawnArgs; i++ ) + { + if ( !strcmp( keyname, spawnArgs[ i ].key ) ) + { + break; + } + } + + if ( i >= NUM_SPAWN_ARGS ) + { + return false; + } + + if ( i == numSpawnArgs ) + { + strncpy( spawnArgs[ i ].key, keyname, sizeof( spawnArgs[ 0 ].key ) - 1 ); + numSpawnArgs++; + } + + strncpy( spawnArgs[ i ].value, value, sizeof( spawnArgs[ 0 ].value ) - 1 ); + + return true; + } + +const char *G_GetSpawnArg + ( + const char *key, + const char *defaultvalue + ) + + { + int i; + + for( i = 0; i < numSpawnArgs; i++ ) + { + if ( !strcmp( key, spawnArgs[ i ].key ) ) + { + return spawnArgs[ i ].value; + } + } + + return defaultvalue; + } + +int G_GetNumSpawnArgs + ( + void + ) + + { + return numSpawnArgs; + } + +/* +=============== +G_GetClassFromArgs + +Finds the spawn function for the entity and returns ClassDef * +=============== +*/ +ClassDef *G_GetClassFromArgs + ( + void + ) + + { + const char *classname; + ClassDef *cls = NULL; + + classname = G_GetSpawnArg( "classname" ); + + // + // check normal spawn functions + // see if the class name is stored within the model + // + if ( classname ) + { + cls = getClassForID( classname ); + if ( !cls ) + { + cls = getClass( classname ); + } + } + + if ( !cls ) + { + const char *model; + + // + // get Object in case we cannot find an alternative + // + cls = &Object::ClassInfo; + model = G_GetSpawnArg( "model" ); + if ( model ) + { + sinmdl_cmd_t *cmds; + int modelindex; + int i; + + // + // get handle to def file + // + if ( ( strlen( model ) >= 3 ) && ( !strcmpi( &model[ strlen(model) - 3 ], "def" ) ) ) + { + if ( !strchr( model, '\\' ) && !strchr( model, '/' ) ) + { + char str[128]; + strcpy( str, "models/" ); + strcat( str, model ); + modelindex = gi.modelindex( str ); + } + else + modelindex = gi.modelindex( model ); + if ( gi.IsModel( modelindex ) ) + { + cmds = gi.InitCommands( modelindex ); + if (cmds) + { + for (i=0;inum_cmds;i++) + { + if ( !strcmpi( cmds->cmds[i].args[0], "classname" ) ) + { + cls = getClass( cmds->cmds[i].args[1] ); + break; + } + } + if ( i == cmds->num_cmds ) + gi.dprintf( "Classname %s used, but 'classname' was not found in Initialization commands, using Object.\n", classname ); + } + else + gi.dprintf( "Classname %s used, but SINMDL had no Initialization commands, using Object.\n", classname ); + } + else + gi.dprintf( "Classname %s used, but SINMDL was not valid, using Object.\n", classname ); + } + else + gi.dprintf( "Classname %s used, but model was not a SINMDL, using Object.\n", classname ); + } + else + { + gi.dprintf( "Classname %s' used, but no model was set, using Object.\n", classname ); + } + } + + return cls; + } + +/* +=============== +G_CallSpawn + +Finds the spawn function for the entity and calls it. +Returns pointer to Entity +=============== +*/ +Entity *G_CallSpawn + ( + void + ) + + { + str classname; + ClassDef *cls; + Entity *obj; + + classname = G_GetStringArg( "classname" ); + cls = G_GetClassFromArgs(); + if ( !cls ) + { + gi.dprintf( "%s doesn't have a spawn function\n", classname.c_str() ); + G_InitSpawnArguments(); + return NULL; + } + + obj = ( Entity * )cls->newInstance(); + G_InitSpawnArguments(); + if ( !obj ) + { + gi.dprintf( "%s failed on newInstance\n", classname.c_str() ); + return NULL; + } + + return obj; + } + +/* +==================== +G_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +const char *G_ParseEdict + ( + const char *data + ) + + { + qboolean init; + char keyname[ 256 ]; + const char *com_token; + + init = false; + + G_InitSpawnArguments(); + + // go through all the dictionary pairs + while (1) + { + // parse key + com_token = COM_Parse( &data ); + if ( com_token[ 0 ] == '}' ) + { + break; + } + + if ( !data ) + { + gi.error( "G_ParseEntity: EOF without closing brace" ); + } + + strncpy( keyname, com_token, sizeof( keyname ) - 1 ); + + // parse value + com_token = COM_Parse( &data ); + if ( !data ) + { + gi.error( "G_ParseEntity: EOF without closing brace" ); + } + + if ( com_token[ 0 ] == '}' ) + { + gi.error( "G_ParseEntity: closing brace without data" ); + } + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if ( keyname[ 0 ] == '_' ) + { + continue; + } + + G_SetSpawnArg( keyname, com_token ); + } + + return data; + } + + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. + +All but the first will have the FL_TEAMSLAVE flag set. +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams (void) + { + edict_t *e; + edict_t *e2; + edict_t *next; + edict_t *next2; + Entity *chain; + Entity *ent; + Entity *ent2; + int c; + int c2; + + c = 0; + c2 = 0; + + for( e = active_edicts.next; e != &active_edicts; e = next ) + { + assert( e ); + assert( e->inuse ); + assert( e->entity ); + + next = e->next; + + if ( e == g_edicts ) + { + continue; + } + + ent = e->entity; + if ( !ent->moveteam.length() ) + { + continue; + } + + if ( ent->flags & FL_TEAMSLAVE ) + { + continue; + } + + chain = ent; + ent->teammaster = ent; + c++; + c2++; + for( e2 = next; e2 != &active_edicts; e2 = next2 ) + { + assert( e2 ); + assert( e2->inuse ); + assert( e2->entity ); + + next2 = e2->next; + + ent2 = e2->entity; + if ( !ent2->moveteam.length() ) + { + continue; + } + + if ( ent2->flags & FL_TEAMSLAVE ) + { + continue; + } + + if ( ent->moveteam == ent2->moveteam ) + { + c2++; + chain->teamchain = ent2; + ent2->teammaster = ent; + chain = ent2; + ent2->flags |= FL_TEAMSLAVE; + } + } + } + + gi.dprintf( "%i teams with %i entities\n", c, c2 ); + } + +/* +============== +G_LevelShutdown + +Get rid of anything left over from the last level +============== +*/ +void G_LevelShutdown + ( + void + ) + + { + PathManager.SavePaths(); + + assert( active_edicts.next ); + assert( active_edicts.next->prev = &active_edicts ); + assert( active_edicts.prev ); + assert( active_edicts.prev->next = &active_edicts ); + assert( free_edicts.next ); + assert( free_edicts.next->prev == &free_edicts ); + assert( free_edicts.prev ); + assert( free_edicts.prev->next == &free_edicts ); + + while( active_edicts.next != &active_edicts ) + { + assert( active_edicts.next != &free_edicts ); + assert( active_edicts.prev != &free_edicts ); + + assert( active_edicts.next ); + assert( active_edicts.next->prev = &active_edicts ); + assert( active_edicts.prev ); + assert( active_edicts.prev->next = &active_edicts ); + assert( free_edicts.next ); + assert( free_edicts.next->prev == &free_edicts ); + assert( free_edicts.prev ); + assert( free_edicts.prev->next == &free_edicts ); + + if ( active_edicts.next->entity ) + { + delete active_edicts.next->entity; + } + else + { + G_FreeEdict( active_edicts.next ); + } + } + + globals.num_edicts = game.maxclients + 1; + + // Reset the gravity paths + gravPathManager.Reset(); + + // close all the scripts + Director.CloseScript(); + + // invalidate player readiness + Director.PlayerNotReady(); + + // clearout any waiting events + G_ClearEventList(); + + gi.FreeTags( TAG_LEVEL ); + } + +/* +============== +G_ResetEdicts +============== +*/ +void G_ResetEdicts + ( + void + ) + + { + int i; + + memset( g_edicts, 0, game.maxentities * sizeof( g_edicts[ 0 ] ) ); + + // Add all the edicts to the free list + LL_Reset( &free_edicts, next, prev ); + LL_Reset( &active_edicts, next, prev ); + for( i = 0; i < game.maxentities; i++ ) + { + LL_Add( &free_edicts, &g_edicts[ i ], next, prev ); + } + + for (i=0 ; iStart( 0 ); + } + } + } + +/* +============== +G_Precache + +Calls precache scripts +============== +*/ +void G_Precache + ( + void + ) + + { + const char *scriptname; + int i; + + // + // load in global0-9.scr + // + for( i = 0; i < 10; i++ ) + { + G_LoadAndExecScript( va( "global/global%i.scr", i ) ); + } + + // + // load in precache0-9.scr + // + if ( precache->value ) + { + for( i = 0; i < 10; i++ ) + { + G_LoadAndExecScript( va( "global/precache%i.scr", i ) ); + } + } + + // + // load in players0-9.scr + // + for( i = 0; i< 10; i++ ) + { + G_LoadAndExecScript( va( "global/players%i.scr", i ) ); + } + + // + // load in universal_script.scr + // + G_LoadAndExecScript( "global/universal_script.scr", "precache:" ); + + // + // precache for the game script + // + scriptname = ScriptLib.GetGameScript(); + if ( scriptname && scriptname[ 0 ] ) + { + G_LoadAndExecScript( scriptname, "precache:" ); + } + } + +/* +============== +G_SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +void G_SpawnEntities + ( + const char *mapname, + const char *entities, + const char *spawnpoint + ) + + { + int inhibit; + const char *com_token; + float skill_level; + const char *value; + int spawnflags; + qboolean world_spawned; + cvar_t *lowdetail; + int i=0; +#if 0 + Class *obj; + Entity *ent; +#endif + + // If we get an error, call the server's error function + if ( setjmp( G_AbortGame ) ) + { + G_ExitWithError(); + } + + lowdetail = gi.cvar( "r_lowdetail", "0", CVAR_ARCHIVE ); + + // Init the level variables + level = level_locals_t(); + level.mapname = mapname; + game.spawnpoint = spawnpoint; + + if ( !LoadingServer ) + { + // Get rid of anything left over from the last level + G_LevelShutdown(); + + G_ResetEdicts(); + + // Set up for a new map + G_MapInit( mapname ); + } + + // Init surface manager & consoles + surfaceManager.Reset(); + globals.num_surfaces = 0; + memset (g_surfaces, 0, game.maxsurfaces * sizeof (g_surfaces[0])); + + globals.num_consoles = 0; + memset (g_consoles, 0, game.maxconsoles * sizeof (g_consoles[0])); + + skill_level = floor( skill->value ); + skill_level = bound( skill_level, 0, 3 ); + if ( skill->value != skill_level ) + { + gi.cvar_forceset( "skill", va( "%f", skill_level ) ); + } + + gameVars.SetVariable( "skill", skill_level ); + + // reset out count of the number of game traces + sv_numtraces = 0; + + level.playerfrozen = false; + + inhibit = 0; + world_spawned = false; + + // parse ents + while (1) + { + // parse the opening brace + com_token = COM_Parse (&entities); + if (!entities) + { + break; + } + if (com_token[0] != '{') + { + gi.error ("G_LoadFromFile: found %s when expecting {",com_token); + } + + i++; + if ( !( i % 20 ) ) + gi.IncrementStatusCount( 20 ); + + entities = G_ParseEdict (entities); + + // remove things (except the world) from different skill levels or deathmatch + value = G_GetSpawnArg( "spawnflags" ); + if ( world_spawned && value ) + { + spawnflags = atoi( value ); + if (deathmatch->value) + { + if ( spawnflags & SPAWNFLAG_NOT_DEATHMATCH ) + { + inhibit++; + continue; + } + } + else + { + if ( + ((skill->value == 0) && (spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == 1) && (spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == 2) || (skill->value == 3)) && (spawnflags & SPAWNFLAG_NOT_HARD) || + ( coop->value && (spawnflags & SPAWNFLAG_NOT_COOP) )) || + ( !developer->value && ( spawnflags & SPAWNFLAG_DEVELOPMENT ) ) || + ( lowdetail->value && ( spawnflags & SPAWNFLAG_DETAIL ) ) + ) + { + inhibit++; + continue; + } + } + } + + game.force_entnum = !world_spawned; + game.spawn_entnum = 0; + G_CallSpawn(); + world_spawned = true; + +#if 0 + // have to fix G_CallSpawn so that freed entities are accounted for + if ( obj && obj->isSubclassOf( Entity ) ) + { + ent = ( Entity * )obj; + + // Sanity check to see if we're expecting a B-Model + assert( !( ( ent->edict->solid == SOLID_BSP ) && !ent->edict->s.modelindex ) ); + if ( ( ent->edict->solid == SOLID_BSP ) && !ent->edict->s.modelindex ) + { + if ( ent->edict->s.number == 0 ) + { + gi.error( "No model for worldspawn!" ); + } + else + { + gi.dprintf( "Deleting %s with SOLID_BSP and no model\n", ent->getClassID() ); + delete ent; + } + } + } +#endif + } + + game.force_entnum = false; + gi.dprintf ("%i entities inhibited\n", inhibit); + + G_InitSpawnArguments(); + G_LevelStart(); + } + +/* +================= +G_Spawn + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +edict_t *G_Spawn + ( + void + ) + + { + int i; + edict_t *e; + + e = &g_edicts[ ( int )maxclients->value + 1 ]; + for ( i = maxclients->value + 1; i < globals.num_edicts; i++, e++ ) + { + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if ( !e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) ) + { + assert( e->next ); + assert( e->prev ); + LL_Remove( e, next, prev ); + G_InitEdict( e ); + assert( active_edicts.next ); + assert( active_edicts.prev ); + LL_Add( &active_edicts, e, next, prev ); + assert( e->next ); + assert( e->prev ); + return e; + } + } + + if ( i == game.maxentities ) + { + gi.error( "G_Spawn: no free edicts" ); + } + + globals.num_edicts++; + assert( e->next ); + assert( e->prev ); + LL_Remove( e, next, prev ); + G_InitEdict( e ); + assert( active_edicts.next ); + assert( active_edicts.prev ); + LL_Add( &active_edicts, e, next, prev ); + assert( e->next ); + assert( e->prev ); + + assert( e->next != &free_edicts ); + assert( e->prev != &free_edicts ); + + return e; + } + +/* +================= +G_FreeEdict + +Marks the edict as free +================= +*/ +void G_FreeEdict + ( + edict_t *ed + ) + + { + gclient_t *client; + + assert( ed != &free_edicts ); + + // unlink from world + gi.unlinkentity ( ed ); + + assert( ed->next ); + assert( ed->prev ); + + if ( level.next_edict == ed ) + { + level.next_edict = ed->next; + } + + LL_Remove( ed, next, prev ); + + assert( ed->next == ed ); + assert( ed->prev == ed ); + assert( free_edicts.next ); + assert( free_edicts.prev ); + + client = ed->client; + memset( ed, 0, sizeof( *ed ) ); + ed->client = client; + ed->freetime = level.time; + ed->inuse = false; + ed->s.number = ed - g_edicts; + + assert( free_edicts.next ); + assert( free_edicts.prev ); + + LL_Add( &free_edicts, ed, next, prev ); + + assert( ed->next ); + assert( ed->prev ); + } + +/* +============== +G_InitClientPersistant + +This is only called when the game first initializes in single player, +but is called after each death and level change in deathmatch +============== +*/ +void G_InitClientPersistant + ( + gclient_t *client + ) + + { + memset( &client->pers, 0, sizeof( client->pers ) ); + + client->pers.health = 100; + client->pers.max_health = 100; + } + + +void G_InitClientResp + ( + gclient_t *client + ) + + { + memset( &client->resp, 0, sizeof( client->resp ) ); + client->resp.enterframe = level.framenum; + } diff --git a/g_spawn.h b/g_spawn.h new file mode 100644 index 0000000..1cb3802 --- /dev/null +++ b/g_spawn.h @@ -0,0 +1,254 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/g_spawn.h $ +// $Revision:: 18 $ +// $Author:: Jimdose $ +// $Date:: 11/07/98 10:01p $ +// +// Copyright (C) 1998 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/g_spawn.h $ +// +// 18 11/07/98 10:01p Jimdose +// added G_GetClassFromArgs +// +// 17 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 16 10/17/98 8:12p Jimdose +// Changed G_CallSpawn2 to G_CallSpawn +// +// 15 10/17/98 12:21a Jimdose +// Added G_ResetEdicts +// +// 14 10/15/98 7:13p Jimdose +// Overrode = for SpawnArgGroup. Fixes crash on level change with multiplayer +// +// 13 10/14/98 10:58p Jimdose +// added archive functions to spawnargs classes +// G_MapInit now accepts mapname +// +// 12 10/14/98 1:20a Jimdose +// Got cross-level persistant info working +// +// 11 10/10/98 1:33a Jimdose +// Added G_LevelStart and G_Precache +// +// 10 10/07/98 11:47p Jimdose +// Added G_LevelShutdown and G_MapInit +// +// 9 8/29/98 9:49p Jimdose +// Moved prototypes from g_local.h +// +// 8 8/27/98 9:05p Jimdose +// Moved some prototypes that didn't belong here to g_utils.h +// +// 7 8/12/98 3:19p Aldie +// Added G_CallSpawn2 that returns pointer to entity created. +// +// 6 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 +// +// 5 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 4 5/24/98 2:46p Markd +// Made char *'s into const char *'s +// +// 3 3/13/98 7:25p Aldie +// Added GetVector +// +// 2 2/03/98 11:06a Jimdose +// Converted to work with Sin progs +// +// DESCRIPTION: +// + +#ifndef __G_SPAWN_H__ +#define __G_SPAWN_H__ + +#ifdef __cplusplus + +#include "entity.h" + +// spawnflags +// these are set with checkboxes on each entity in the map editor +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 +#define SPAWNFLAG_DEVELOPMENT 0x00002000 +#define SPAWNFLAG_DETAIL 0x00004000 + +class EXPORT_FROM_DLL SpawnArg : public Class + { + public: + char key[ 64 ]; + char value[ 256 ]; + + CLASS_PROTOTYPE( SpawnArg ); + + SpawnArg(); + SpawnArg( SpawnArg &arg ); + friend int operator==( SpawnArg a, SpawnArg b ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + + }; + +inline int operator== + ( + SpawnArg a, + SpawnArg b + ) + + { + int i; + + i = strcmp( a.key, b.key ); + if ( i ) + { + return i; + } + + return strcmp( a.value, b.value ); + } + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL SpawnArgs : public Class + { + public: + Container argList; + + CLASS_PROTOTYPE( SpawnArgs ); + + SpawnArgs(); + SpawnArgs( SpawnArgs &arglist ); + int NumArgs( void ); + void SetArgs( void ); + void RestoreArgs( void ); + void operator=( SpawnArgs &a ); + friend int operator==( SpawnArgs a, SpawnArgs b ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline int operator== + ( + SpawnArgs a, + SpawnArgs b + ) + + { + return -1; + } + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL SpawnArgGroup : public Class + { + public: + Container spawnArgList; + + CLASS_PROTOTYPE( SpawnArgGroup ); + + SpawnArgGroup(); + SpawnArgGroup( SpawnArgGroup &group ); + int NumInGroup( void ); + void AddArgs( void ); + void RestoreArgs( int i ); + void operator=( SpawnArgGroup &a ); + friend int operator==( SpawnArgGroup a, SpawnArgGroup b ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline int operator== + ( + SpawnArgGroup a, + SpawnArgGroup b + ) + + { + return -1; + } + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL SpawnArgsForEntity : public Class + { + public: + Container groupList; + Container entnumList; + + CLASS_PROTOTYPE( SpawnArgsForEntity ); + + void Reset( void ); + void AddEnt( Entity *ent ); + qboolean RestoreEnt( Entity *ent ); + void RestoreEnts( void ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +extern SpawnArgsForEntity PersistantData; + +void G_SetFloatArg( const char *key, double value ); +void G_SetIntArg( const char *key, int value ); +qboolean G_SetSpawnArg( const char *keyname, const char *value ); + +void G_DefaultArg( const char *key, const char *defaultvalue ); +void G_DefaultFloatArg( const char *key, double defaultvalue ); +void G_DefaultIntArg( const char *key, int defaultvalue ); + +float G_GetFloatArg( const char *key, double defaultvalue = 0 ); +Vector G_GetVectorArg( const char *key, Vector defaultvalue = Vector( 0, 0, 0 ) ); +int G_GetIntArg( const char *key, int defaultvalue = 0 ); +str G_GetStringArg( const char *key, const char *defaultvalue = NULL ); +const char *G_GetSpawnArg( const char *key, const char *defaultvalue = NULL ); + +void G_InitSpawnArguments( void ); +int G_GetNumSpawnArgs( void ); + +void G_InitClientPersistant( gclient_t *client ); +void G_InitClientResp( gclient_t *client ); + +ClassDef *G_GetClassFromArgs( void ); +Entity *G_CallSpawn( void ); +const char *G_ParseEdict( const char *data, edict_t *ent); +void G_FindTeams( void ); + +void G_LevelShutdown( void ); +void G_ResetEdicts( void ); +void G_MapInit( const char *mapname ); +void G_LevelStart( void ); +void G_Precache( void ); + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +EXPORT_FROM_DLL void G_SpawnEntities( const char *mapname, const char *entities, const char *spawnpoint ); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/g_utils.cpp b/g_utils.cpp new file mode 100644 index 0000000..94f9c55 --- /dev/null +++ b/g_utils.cpp @@ -0,0 +1,2163 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/g_utils.cpp $ +// $Revision:: 68 $ +// $Author:: Jimdose $ +// $Date:: 12/15/98 6:17p $ +// +// Copyright (C) 1998 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/g_utils.cpp $ +// +// 68 12/15/98 6:17p Jimdose +// made SelectSpawnPoint handle progressive starts +// +// 67 11/19/98 9:28p Jimdose +// Made killbox work better +// +// 66 11/15/98 7:51p Markd +// Made location based stuff case insensitive +// +// 65 11/06/98 10:04p Jimdose +// Added G_AllocDebugLines +// Added g_numdebuglines +// +// 64 11/06/98 5:47p Markd +// got rid of edict event archiving +// +// 63 10/28/98 4:46p Jimdose +// G_ArchiveEdict was accessing edict->owner->entity, which may be cleared out +// when called. +// +// 62 10/27/98 9:46p Aldie +// Changed training cvar to level.training +// +// 61 10/26/98 3:50a Markd +// put in prediction +// +// 60 10/26/98 2:41a Aldie +// Training startpoint +// +// 59 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 58 10/21/98 6:42p Markd +// Added sv_drawtrace +// +// 57 10/18/98 3:23a Jimdose +// Added G_Milliseconds and G_DebugPrintf +// +// 56 10/16/98 7:18p Markd +// Changed ExecuteThread a little bit +// +// 55 10/10/98 1:27a Jimdose +// Added G_LoadAndExecScript +// added edict archiving functions +// +// 54 10/09/98 4:53p Markd +// Added ExecuteThread code +// +// 53 10/09/98 4:33p Aldie +// Added some team functions +// +// 52 10/09/98 2:05a Aldie +// Updated DMFLAGS +// +// 51 10/07/98 11:48p Jimdose +// changed game.spawnpoint to a str +// +// 50 10/03/98 1:09p Aldie +// Added findclientsinradius +// +// 49 9/28/98 5:50p Markd +// Changed Swamp to Duct +// +// 48 9/22/98 4:23p Markd +// Fixed some targetname stuff for lights, doors and scriptobjects +// +// 47 9/14/98 5:41p Jimdose +// Added G_CalcBoundsOfMove +// +// 46 9/01/98 7:49p Markd +// Put code into findradius that will return NULL if world is NULL +// +// 45 9/01/98 7:45p Aldie +// Updated location string stuff +// +// 44 8/31/98 7:49p Jimdose +// Made M_CheckBottom allow greater falls +// +// 43 8/31/98 7:45p Aldie +// Updated surface data structure and removed surfinfo field +// +// 42 8/29/98 9:43p Jimdose +// Made all function names consistantly begin with G_ +// Added call info to G_Trace +// Added G_ShowTrace +// Moved all of DebugLines functions into g_utils +// +// 41 8/29/98 2:53p Aldie +// Added status meter for loading levels. +// +// 40 8/28/98 3:46p Markd +// Put in centroid support for find radius +// +// 39 8/27/98 9:05p Jimdose +// Moved several short functions to g_utils.h as inline +// +// 38 8/25/98 7:47p Jimdose +// Added gravaxis to SelectSpawnPoint +// +// 37 8/24/98 4:56p Markd +// Added G_CalculateImpulse +// +// 36 8/18/98 11:51p Jimdose +// Changed G_TouchTriggers to check for the entity touching itself +// +// 35 8/14/98 4:21p Aldie +// Changed rad to rad2 in findradius +// +// 34 8/13/98 4:57p Jimdose +// Made findradius not do a squareroot calculation. +// +// 33 8/08/98 7:50p Jimdose +// changed realWorld to world +// +// 32 8/03/98 7:55p Jimdose +// Added G_DrawDebugNumber +// +// 31 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 +// +// 30 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 29 7/17/98 7:57p Markd +// Added radius to FullTrace +// +// 28 6/15/98 10:07p Jimdose +// Added G_FullTrace +// +// 27 6/10/98 2:10p Aldie +// Updated damage function. +// +// 26 5/26/98 4:22p Markd +// Rewrote G_GetTarget +// +// 25 5/24/98 8:55p Jimdose +// Removed the char * cast from Q_stricmp call +// +// 24 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 +// +// 23 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 22 5/20/98 7:17p Markd +// Added G_DebugBBox +// +// 21 5/20/98 11:11a Markd +// removed char * dependency +// +// 20 5/14/98 10:12p Jimdose +// Added G_NextEntity +// +// 19 5/03/98 4:44p Jimdose +// changed Vector class +// added line drawing utility functions similar to OpenGL +// +// 18 5/02/98 12:01a Jimdose +// added groundplane, groundsurface, groundcontents +// +// +// 17 4/29/98 5:05p Jimdose +// SelectSpawnPoint was using the world as the spawnspot when no spawn spot +// existed +// +// 16 4/16/98 2:04p Jimdose +// G_InitEdict inits frame and prevframe +// Working on M_CheckBottom +// +// 15 4/10/98 4:56p Jimdose +// Set spawntime in G_InitEdict +// +// 14 4/06/98 5:43p Jimdose +// G_InitEdict sets RF_FRAMELERP on new ents +// +// 13 4/04/98 6:04p Jimdose +// Added G_GetNameForSurface +// +// 12 3/28/98 4:36p Jimdose +// Added deathmatch starts +// +// 11 3/26/98 8:19p Jimdose +// Changed groundentity to an edict_t * +// Fixed killbox +// +// 10 3/24/98 4:55p Jimdose +// Fixed G_GetMovedir to return a vector properly (points to bug with Vector.h) +// +// 9 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 8 3/11/98 2:27p Jimdose +// G_GetMovedir had a bug where the result was rotated 90 degrees +// +// 7 3/05/98 7:18p Markd +// Added default scale to InitEdict +// +// 6 3/02/98 5:45p Jimdose +// Added findradius +// Removed unused Q2 code +// +// 5 2/19/98 5:02p Jimdose +// Moved G_Entity, G_Random, and G_CRandom to g_utils +// +// 4 2/18/98 8:07p Jimdose +// Added IsNumeric +// +// 3 2/06/98 5:51p Jimdose +// Added KillBox +// Changed G_TouchTriggers and M_CheckBottom to be .cpp compatible +// +// 2 2/03/98 10:50a Jimdose +// Created file. +// Merged with pre-Q2 dlls +// +// DESCRIPTION: +// + +#include "g_local.h" +#include "g_utils.h" +#include "ctype.h" +#include "worldspawn.h" +#include "scriptmaster.h" +#include "windows.h" + +cvar_t *g_numdebuglines; + +debugline_t *DebugLines = NULL; +Vector currentVertex( 0, 0, 0 ); +Vector vertColor( 1, 1, 1 ); +float vertAlpha = 1; +float vertexIndex = 0; + +/* +============ +G_TouchTriggers + +============ +*/ +void G_TouchTriggers + ( + Entity *ent + ) + + { + int i; + int num; + edict_t *touch[ MAX_EDICTS ]; + edict_t *hit; + Event *ev; + + // dead things don't activate triggers! + if ( ( ent->client || ( ent->edict->svflags & SVF_MONSTER ) ) && ( ent->health <= 0 ) ) + { + return; + } + + num = gi.BoxEdicts( ent->absmin.vec3(), ent->absmax.vec3(), touch, MAX_EDICTS, AREA_TRIGGERS ); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for( i = 0; i < num; i++ ) + { + hit = touch[ i ]; + if ( !hit->inuse || ( hit->entity == ent ) ) + { + continue; + } + + assert( hit->entity ); + + // FIXME + // should we post the events on the list with zero time + ev = new Event( EV_Touch ); + ev->AddEntity( ent ); + hit->entity->ProcessEvent( ev ); + } + } + +/* +============ +G_TouchSolids + +Call after linking a new trigger in during gameplay +to force all entities it covers to immediately touch it +============ +*/ +void G_TouchSolids + ( + Entity *ent + ) + + { + int i; + int num; + edict_t *touch[ MAX_EDICTS ]; + edict_t *hit; + Event *ev; + + num = gi.BoxEdicts( ent->absmin.vec3(), ent->absmax.vec3(), touch, MAX_EDICTS, AREA_SOLID ); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for( i = 0; i < num; i++ ) + { + hit = touch[ i ]; + if ( !hit->inuse ) + { + continue; + } + + assert( hit->entity ); + + //FIXME + // should we post the events so that we don't have to worry about any entities going away + ev = new Event( EV_Touch ); + ev->AddEntity( ent ); + hit->entity->ProcessEvent( ev ); + } + } + +EXPORT_FROM_DLL void G_ShowTrace + ( + trace_t *trace, + edict_t *passent, + const char *reason + ) + + { + str text; + str pass; + str hit; + + assert( reason ); + assert( trace ); + + if ( passent ) + { + pass = va( "'%s'(%d)", passent->entname, passent->s.number ); + } + else + { + pass = "NULL"; + } + + if ( trace->ent ) + { + hit = va( "'%s'(%d)", trace->ent->entname, trace->ent->s.number ); + } + else + { + hit = "NULL"; + } + + text = va( "%0.1f : Pass %s Frac %f Hit %s : '%s'\n", + level.time, pass.c_str(), trace->fraction, hit.c_str(), reason ? reason : "" ); + + if ( sv_traceinfo->value == 3 ) + { + G_DebugPrintf( text.c_str() ); + } + else + { + gi.dprintf( "%s", text.c_str() ); + } + } + +EXPORT_FROM_DLL void G_CalcBoundsOfMove + ( + Vector &start, + Vector &end, + Vector &mins, + Vector &maxs, + Vector *minbounds, + Vector *maxbounds + ) + + { + Vector bmin; + Vector bmax; + + ClearBounds( bmin.vec3(), bmax.vec3() ); + AddPointToBounds( start.vec3(), bmin.vec3(), bmax.vec3() ); + AddPointToBounds( end.vec3(), bmin.vec3(), bmax.vec3() ); + bmin += mins; + bmax += maxs; + + if ( minbounds ) + { + *minbounds = bmin; + } + + if ( maxbounds ) + { + *maxbounds = bmax; + } + } + +EXPORT_FROM_DLL trace_t G_Trace + ( + vec3_t start, + vec3_t mins, + vec3_t maxs, + vec3_t end, + edict_t *passent, + int contentmask, + const char *reason + ) + + { + trace_t trace; + + trace = gi.trace( start, mins, maxs, end, passent, contentmask ); + assert( !trace.ent || trace.ent->entity ); + + if ( sv_traceinfo->value > 1 ) + { + G_ShowTrace( &trace, passent, reason ); + } + sv_numtraces++; + + if ( sv_drawtrace->value ) + { + G_DebugLine( Vector( start ), Vector( end ), 1, 1, 0, 1 ); + } + + return trace; + } + +EXPORT_FROM_DLL trace_t G_Trace + ( + Vector &start, + Vector &mins, + Vector &maxs, + Vector &end, + Entity *passent, + int contentmask, + const char *reason + ) + + { + edict_t *ent; + trace_t trace; + + assert( reason ); + + if ( passent == NULL ) + { + ent = NULL; + } + else + { + ent = passent->edict; + } + + trace = gi.trace( start.vec3(), mins.vec3(), maxs.vec3(), end.vec3(), ent, contentmask ); + + assert( !trace.ent || trace.ent->entity ); + + if ( sv_traceinfo->value > 1 ) + { + G_ShowTrace( &trace, ent, reason ); + } + sv_numtraces++; + + if ( sv_drawtrace->value ) + { + G_DebugLine( start, end, 1, 1, 0, 1 ); + } + + return trace; + } + +EXPORT_FROM_DLL trace_t G_FullTrace + ( + Vector &start, + Vector &mins, + Vector &maxs, + Vector &end, + float radius, + Entity *passent, + int contentmask, + const char *reason + ) + + { + edict_t *ent; + trace_t trace; + + if ( passent == NULL ) + { + ent = NULL; + } + else + { + ent = passent->edict; + } + + trace = gi.fulltrace( start.vec3(), mins.vec3(), maxs.vec3(), end.vec3(), radius, ent, contentmask ); + assert( !trace.ent || trace.ent->entity ); + + if ( sv_traceinfo->value > 1 ) + { + G_ShowTrace( &trace, ent, reason ); + } + sv_numtraces++; + + if ( sv_drawtrace->value ) + { + G_DebugLine( start, end, 0, 1, 1, 1 ); + } + + return trace; + } + +EXPORT_FROM_DLL trace_t G_FullTrace + ( + vec3_t start, + vec3_t mins, + vec3_t maxs, + vec3_t end, + float radius, + edict_t *passent, + int contentmask, + const char *reason + ) + + { + trace_t trace; + + trace = gi.fulltrace( start, mins, maxs, end, radius, passent, contentmask ); + assert( !trace.ent || trace.ent->entity ); + + if ( sv_traceinfo->value > 1 ) + { + G_ShowTrace( &trace, passent, reason ); + } + sv_numtraces++; + + if ( sv_drawtrace->value ) + { + G_DebugLine( Vector( start ), Vector( end ), 0, 1, 1, 1 ); + } + + return trace; + } + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +PlayersRangeFromSpot + +Returns the distance to the nearest player from the given spot +================ +*/ +float PlayersRangeFromSpot + ( + Entity *spot + ) + + { + Entity *player; + float bestplayerdistance; + Vector v; + int n; + float playerdistance; + + bestplayerdistance = 9999999; + for( n = 1; n <= maxclients->value; n++ ) + { + if ( !g_edicts[ n ].inuse || !g_edicts[ n ].entity ) + { + continue; + } + + player = g_edicts[ n ].entity; + if ( player->health <= 0 ) + { + continue; + } + + v = spot->worldorigin - player->worldorigin; + playerdistance = v.length(); + + if ( playerdistance < bestplayerdistance ) + { + bestplayerdistance = playerdistance; + } + } + + return bestplayerdistance; + } + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point, but NOT the two points closest +to other players +================ +*/ +Entity *SelectRandomDeathmatchSpawnPoint + ( + void + ) + + { + Entity *spot, *spot1, *spot2; + int count = 0; + int selection; + int num; + float range, range1, range2; + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + num = 0; + while( ( num = G_FindClass( num, "info_player_deathmatch" ) ) != 0 ) + { + spot = G_GetEntity( num ); + count++; + range = PlayersRangeFromSpot( spot ); + if ( range < range1 ) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if ( !count ) + { + return NULL; + } + + if ( count <= 2 ) + { + spot1 = spot2 = NULL; + } + else + { + count -= 2; + } + + selection = rand() % count; + + spot = NULL; + num = 0; + do + { + num = G_FindClass( num, "info_player_deathmatch" ); + spot = G_GetEntity( num ); + if ( spot == spot1 || spot == spot2 ) + { + selection++; + } + } + while( selection-- ); + + return spot; + } + +/* +================ +SelectFarthestDeathmatchSpawnPoint + +================ +*/ +Entity *SelectFarthestDeathmatchSpawnPoint + ( + void + ) + + { + Entity *bestspot; + float bestdistance; + float bestplayerdistance; + Entity *spot; + int num; + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + num = 0; + while( ( num = G_FindClass( num, "info_player_deathmatch" ) ) != NULL ) + { + spot = G_GetEntity( num ); + + bestplayerdistance = PlayersRangeFromSpot( spot ); + if ( bestplayerdistance > bestdistance ) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if ( bestspot ) + { + return bestspot; + } + + // if there is a player just spawned on each and every start spot + // we have no choice to turn one into a telefrag meltdown + num = G_FindClass( 0, "info_player_deathmatch" ); + spot = G_GetEntity( num ); + + return spot; + } + +Entity *SelectDeathmatchSpawnPoint + ( + void + ) + + { + if ( DM_FLAG( DF_SPAWN_FARTHEST ) ) + { + return SelectFarthestDeathmatchSpawnPoint(); + } + else + { + return SelectRandomDeathmatchSpawnPoint(); + } + } + +Entity *SelectCoopSpawnPoint + ( + void + ) + + { + const char *tname; + Entity *spot = NULL; + int num; + + num = 0; + while( ( num = G_FindClass( num, "info_player_coop" ) ) != 0 ) + { + spot = G_GetEntity( num ); + tname = spot->TargetName(); + if ( !game.spawnpoint.length() || !tname || !tname[ 0 ] ) + { + break; + } + + if ( Q_stricmp( game.spawnpoint.c_str(), spot->TargetName() ) == 0 ) + { + break; + } + } + + return spot; + } + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, coop start, etc +============ +*/ +void SelectSpawnPoint + ( + Vector &origin, + Vector &angles, + int *gravaxis + ) + + { + const char * tname; + Entity *spot = NULL; + Entity *spot2 = NULL; + int num; + + if ( ( level.training == 2 ) && game.spawnpoint.length() ) + { + num = 0; + while( ( num = G_FindClass( num, "info_player_progressivestart" ) ) != 0 ) + { + spot2 = G_GetEntity( num ); + tname = spot2->TargetName(); + if ( !tname || !tname[ 0 ] ) + { + break; + } + + if ( Q_stricmp( game.spawnpoint.c_str(), spot2->TargetName() ) == 0 ) + { + spot = spot2; + break; + } + } + } + else if ( deathmatch->value || level.training == 1 ) + { + spot = SelectDeathmatchSpawnPoint(); + } + else if ( coop->value ) + { + spot = SelectCoopSpawnPoint(); + } + + // find a single player start spot + if ( !spot ) + { + num = 0; + while( ( num = G_FindClass( num, "info_player_start" ) ) != 0 ) + { + spot = G_GetEntity( num ); + tname = spot->TargetName(); + if ( !game.spawnpoint.length() || !tname || !tname[ 0 ] ) + { + break; + } + + if ( Q_stricmp( game.spawnpoint.c_str(), spot->TargetName() ) == 0 ) + { + break; + } + } + + if ( !spot ) + { + if ( !game.spawnpoint.length() ) + { + // there wasn't a spawnpoint without a target, so use any + num = G_FindClass( 0, "info_player_start" ); + spot = G_GetEntity( num ); + } + + if ( !spot || !spot->entnum ) + { + gi.error( "Couldn't find spawn point %s\n", game.spawnpoint.c_str() ); + } + } + } + + origin = spot->worldorigin + "0 0 9"; + angles = spot->angles; + if ( gravaxis ) + { + *gravaxis = spot->gravaxis; + } + } + +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean M_CheckBottom + ( + Entity *ent + ) + + { + Vector mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + mins = ent->worldorigin + ent->mins * 0.5; + maxs = ent->worldorigin + ent->maxs * 0.5; + + // if all of the points under the corners are solid world, don't bother + // with the tougher checks + // the corners must be within 16 of the midpoint + start[ 2 ] = mins[ 2 ] - 1; + + for( x = 0; x <= 1; x++ ) + { + for( y = 0; y <= 1; y++ ) + { + start[ 0 ] = x ? maxs[ 0 ] : mins[ 0 ]; + start[ 1 ] = y ? maxs[ 1 ] : mins[ 1 ]; + if ( gi.pointcontents( start.vec3() ) != CONTENTS_SOLID ) + { + goto realcheck; + } + } + } + + c_yes++; + return true; // we got out easy + +realcheck: + + c_no++; + + // + // check it for real... + // + start[ 2 ] = mins[ 2 ]; + + // the midpoint must be within 16 of the bottom + start[ 0 ] = stop[ 0 ] = ( mins[ 0 ] + maxs[ 0 ] ) * 0.5; + start[ 1 ] = stop[ 1 ] = ( mins[ 1 ] + maxs[ 1 ] ) * 0.5; + stop[ 2 ] = start[ 2 ] - 3 * STEPSIZE;//2 * STEPSIZE; + + trace = G_Trace( start, vec_zero, vec_zero, stop, ent, MASK_MONSTERSOLID, "M_CheckBottom 1" ); + + if ( trace.fraction == 1.0 ) + { + return false; + } + + mid = bottom = trace.endpos[ 2 ]; + + // the corners must be within 16 of the midpoint + for( x = 0; x <= 1; x++ ) + { + for( y = 0; y <= 1; y++ ) + { + start[ 0 ] = stop[ 0 ] = x ? maxs[ 0 ] : mins[ 0 ]; + start[ 1 ] = stop[ 1 ] = y ? maxs[ 1 ] : mins[ 1 ]; + + trace = G_Trace( start, vec_zero, vec_zero, stop, ent, MASK_MONSTERSOLID, "M_CheckBottom 2" ); + + if ( trace.fraction != 1.0 && trace.endpos[ 2 ] > bottom ) + { + bottom = trace.endpos[ 2 ]; + } + + if ( trace.fraction == 1.0 || mid - trace.endpos[ 2 ] > STEPSIZE ) + { + return false; + } + } + } + + c_yes++; + return true; + } + +char *G_CopyString + ( + const char *in + ) + + { + char *newb; + char *new_p; + int i,l; + + assert( in ); + + l = strlen( in ) + 1; + + newb = ( char * )gi.TagMalloc( l, TAG_LEVEL ); + new_p = newb; + + for( i = 0; i < l; i++ ) + { + if ( ( in[ i ] == '\\' ) && ( i < l - 1 ) ) + { + i++; + if ( in[ i ] == 'n' ) + { + *new_p++ = '\n'; + } + else + { + *new_p++ = '\\'; + } + } + else + { + *new_p++ = in[ i ]; + } + } + + return newb; + } + +int G_FindClass + ( + int entnum, + const char *classname + ) + + { + edict_t *from; + + for ( from = &g_edicts[ entnum + 1 ]; from < &g_edicts[ globals.num_edicts ] ; from++ ) + { + if ( !from->inuse ) + { + continue; + } + if ( !Q_stricmp ( from->entity->getClassID(), classname ) ) + { + return from->s.number; + } + } + + return 0; + } + +int G_FindTarget + ( + int entnum, + const char *name + ) + + { + edict_t *from; + Entity *next; + + if ( name && name[ 0 ] ) + { + from = &g_edicts[ entnum ]; + next = world->GetNextEntity( str( name ), from->entity ); + if ( next ) + { + return next->entnum; + } + } + + return 0; + } + +EXPORT_FROM_DLL Entity *G_NextEntity + ( + Entity *ent + ) + + { + edict_t *from; + + if ( !g_edicts ) + { + return NULL; + } + + if ( !ent ) + { + ent = world; + } + + if ( !ent ) + { + return NULL; + } + + for ( from = ent->edict + 1; from < &g_edicts[ globals.num_edicts ] ; from++ ) + { + if ( !from->inuse || !from->entity ) + { + continue; + } + + return from->entity; + } + + return NULL; + } + +// +// QuakeEd only writes a single float for angles (bad idea), so up and down are +// just constant angles. +// +Vector G_GetMovedir + ( + void + ) + + { + float angle; + + angle = G_GetFloatArg( "angle" ); + if ( angle == -1 ) + { + return Vector( 0, 0, 1 ); + } + else if ( angle == -2 ) + { + return Vector( 0, 0, -1 ); + } + + angle *= ( M_PI * 2 / 360 ); + return Vector( cos( angle ), sin( angle ), 0 ); + } + +/* +================= +KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +qboolean KillBox + ( + Entity *ent + ) + + { + int i; + int num; + edict_t *touch[ MAX_EDICTS ]; + edict_t *hit; + Vector min; + Vector max; + int fail; + + fail = 0; + + min = ent->worldorigin + ent->mins; + max = ent->worldorigin + ent->maxs; + num = gi.BoxEdicts( min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_SOLID ); + for( i = 0; i < num; i++ ) + { + hit = touch[ i ]; + + if ( !hit->inuse || ( hit->entity == ent ) || !hit->entity || ( hit->entity == world ) ) + { + continue; + } + + hit->entity->Damage( ent, ent, hit->entity->health + 100000, ent->worldorigin, vec_zero, vec_zero, + 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG, -1, -1, 1.0f ); + + // + // if we didn't kill it, fail + // + if ( hit->entity->getSolidType() != SOLID_NOT ) + { + fail++; + } + } + + // + // all clear + // + return !fail; + } + +qboolean IsNumeric + ( + const char *str + ) + + { + int len; + int i; + qboolean dot; + + if ( *str == '-' ) + { + str++; + } + + dot = false; + len = strlen( str ); + for( i = 0; i < len; i++ ) + { + if ( !isdigit( str[ i ] ) ) + { + if ( ( str[ i ] == '.' ) && !dot ) + { + dot = true; + continue; + } + return false; + } + } + + return true; + } + +void G_InitEdict + ( + edict_t *e + ) + + { + e->inuse = true; + e->s.number = e - g_edicts; + + // make sure a default scale gets set + e->s.scale = 1.0f; + e->s.renderfx |= RF_FRAMELERP; + e->spawntime = level.time; + e->s.frame = 0; + e->s.prevframe = -1; + } + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +Entity *findradius + ( + Entity *startent, + Vector org, + float rad + ) + + { + Vector eorg; + edict_t *from; + float r2; + + if ( !startent ) + { + startent = world; + } + + if ( !startent ) + { + return NULL; + } + + // square the radius so that we don't have to do a square root + r2 = rad * rad; + + assert( startent->edict ); + for ( from = startent->edict + 1; from < &g_edicts[ globals.num_edicts ]; from++ ) + { + if ( !from->inuse ) + { + continue; + } + + assert( from->entity ); + + eorg = org - from->entity->centroid; + + // dot product returns length squared + if ( ( eorg * eorg ) <= r2 ) + { + return from->entity; + } + } + + return NULL; + } + +/* +================= +findclientinradius + +Returns clients that have origins within a spherical area + +findclientinradius (origin, radius) +================= +*/ +Entity *findclientsinradius + ( + Entity *startent, + Vector org, + float rad + ) + + { + Vector eorg; + edict_t *ed; + float r2; + int i; + + // square the radius so that we don't have to do a square root + r2 = rad * rad; + + for( i = startent->entnum; i < game.maxclients; i++ ) + { + ed = &g_edicts[ 1 + i ]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + eorg = org - ed->entity->centroid; + + // dot product returns length squared + if ( ( eorg * eorg ) <= r2 ) + { + return ed->entity; + } + } + + return NULL; + } + +const char *G_GetNameForSurface + ( + csurface_t *s + ) + + { + switch( s->flags & MASK_SURF_TYPE ) + { + case SURF_TYPE_WOOD : + return "wood"; + + case SURF_TYPE_METAL : + return "metal"; + + case SURF_TYPE_STONE : + return "stone"; + + case SURF_TYPE_CONCRETE : + return "concrete"; + + case SURF_TYPE_DIRT : + return "dirt"; + + case SURF_TYPE_FLESH : + return "flesh"; + + case SURF_TYPE_GRILL : + return "grill"; + + case SURF_TYPE_GLASS : + return "glass"; + + case SURF_TYPE_FABRIC : + return "fabric"; + + case SURF_TYPE_MONITOR : + return "monitor"; + + case SURF_TYPE_GRAVEL : + return "gravel"; + + case SURF_TYPE_VEGETATION : + return "vegetation"; + + case SURF_TYPE_PAPER : + return "paper"; + + case SURF_TYPE_DUCT : + return "duct"; + + case SURF_TYPE_WATER : + return "water"; + } + + return ""; + } + +void G_InitDebugLines + ( + void + ) + + { + *gi.DebugLines = DebugLines; + *gi.numDebugLines = 0; + + currentVertex = vec_zero; + vertColor = Vector( 1, 1, 1 ); + vertAlpha = 1; + vertexIndex = 0; + } + +void G_AllocDebugLines + ( + void + ) + + { + g_numdebuglines = gi.cvar( "g_numdebuglines", "4096", CVAR_LATCH ); + + DebugLines = ( debugline_t * )gi.TagMalloc( ( int )g_numdebuglines->value * sizeof( debugline_t ), TAG_GAME ); + + G_InitDebugLines(); + } + +void G_DebugLine + ( + Vector start, + Vector end, + float r, + float g, + float b, + float alpha + ) + + { + debugline_t *line; + + if ( !g_numdebuglines ) + { + return; + } + + if ( *gi.numDebugLines >= g_numdebuglines->value ) + { + gi.dprintf( "G_DebugLine: Exceeded MAX_DEBUG_LINES\n" ); + return; + } + + line = &DebugLines[ *gi.numDebugLines ]; + ( *gi.numDebugLines )++; + + VectorCopy( start.vec3(), line->start ); + VectorCopy( end.vec3(), line->end ); + VectorSet( line->color, r, g, b ); + line->alpha = alpha; + } + +void G_Color3f + ( + float r, + float g, + float b + ) + + { + vertColor = Vector( r, g, b ); + } + +void G_Color3v + ( + Vector color + ) + + { + vertColor = color; + } + +void G_Color4f + ( + float r, + float g, + float b, + float alpha + ) + + { + vertColor = Vector( r, g, b ); + vertAlpha = alpha; + } + +void G_Color3vf + ( + Vector color, + float alpha + ) + + { + vertColor = color; + vertAlpha = alpha; + } + +void G_BeginLine + ( + void + ) + + { + currentVertex = vec_zero; + vertexIndex = 0; + } + +void G_Vertex + ( + Vector v + ) + + { + vertexIndex++; + if ( vertexIndex > 1 ) + { + G_DebugLine( currentVertex, v, vertColor[ 0 ], vertColor[ 1 ], vertColor[ 2 ], vertAlpha ); + } + currentVertex = v; + } + +void G_EndLine + ( + void + ) + + { + currentVertex = vec_zero; + vertexIndex = 0; + } + +void G_DebugBBox + ( + Vector origin, + Vector mins, + Vector maxs, + float r, + float g, + float b, + float alpha + ) + { + int i; + Vector points[8]; + + /* + ** compute a full bounding box + */ + for ( i = 0; i < 8; i++ ) + { + Vector tmp; + + if ( i & 1 ) + tmp[0] = origin[0] + mins[0]; + else + tmp[0] = origin[0] + maxs[0]; + + if ( i & 2 ) + tmp[1] = origin[1] + mins[1]; + else + tmp[1] = origin[1] + maxs[1]; + + if ( i & 4 ) + tmp[2] = origin[2] + mins[2]; + else + tmp[2] = origin[2] + maxs[2]; + + points[i] = tmp; + } + + G_Color4f( r, g, b, alpha ); + + G_BeginLine(); + G_Vertex( points[0] ); + G_Vertex( points[1] ); + G_Vertex( points[3] ); + G_Vertex( points[2] ); + G_Vertex( points[0] ); + G_EndLine(); + + G_BeginLine(); + G_Vertex( points[4] ); + G_Vertex( points[5] ); + G_Vertex( points[7] ); + G_Vertex( points[6] ); + G_Vertex( points[4] ); + G_EndLine(); + + for ( i = 0; i < 4; i++ ) + { + G_BeginLine(); + G_Vertex( points[i] ); + G_Vertex( points[4 + i] ); + G_EndLine(); + } + } + +// +// LED style digits +// +// ****1*** +// * * 8 == / +// 6 *4 +// * * * +// ****2*** +// * * * +// 7 *--8 5 +// ** * +// ****3*** +// + +static int Numbers[ 10 ][ 8 ] = + { + { 1, 3, 4, 5, 6, 7, 0, 0 }, // 0 + { 4, 5, 0, 0, 0, 0, 0, 0 }, // 1 + { 1, 4, 2, 7, 3, 0, 0, 0 }, // 2 + { 1, 4, 2, 5, 3, 0, 0, 0 }, // 3 + { 6, 4, 2, 5, 0, 0, 0, 0 }, // 4 + { 1, 6, 2, 5, 3, 0, 0, 0 }, // 5 + { 1, 6, 2, 5, 7, 3, 0, 0 }, // 6 + { 1, 8, 0, 0, 0, 0, 0, 0 }, // 7 + { 1, 2, 3, 4, 5, 6, 7, 0 }, // 8 + { 1, 6, 4, 2, 5, 3, 0, 0 }, // 9 + }; + +static float Lines[ 9 ][ 4 ] = + { + { 0, 0, 0, 0 }, // Unused + { -4, 8, 4, 8 }, // 1 + { -4, 4, 4, 4 }, // 2 + { -4, 0, 4, 0 }, // 3 + { 4, 8, 4, 4 }, // 4 + { 4, 4, 4, 0 }, // 5 + { -4, 8, -4, 4 }, // 6 + { -4, 4, -4, 0 }, // 7 + { 4, 8, -4, 0 }, // 8 + }; + +void G_DrawDebugNumber + ( + Vector origin, + int number, + float scale, + float r, + float g, + float b + ) + + { + int i; + int j; + int l; + int num; + Vector up; + Vector right; + Vector pos; + Vector start; + Vector ang; + str text; + Vector delta; + + // only draw entity numbers within a certain radius + delta = Vector( g_edicts[ 1 ].s.origin ) - origin; + if ( ( delta * delta ) > ( 1000 * 1000 ) ) + { + return; + } + + G_Color4f( r, g, b, 1.0 ); + + ang = game.clients[ 0 ].ps.viewangles; + ang.AngleVectors( NULL, &right, &up ); + + up *= scale; + right *= scale; + + text = va( "%d", number ); + start = origin - ( text.length() - 1 ) * 5 * right; + + for( i = 0; i < text.length(); i++ ) + { + num = text[ i ] - '0'; + for( j = 0; j < 8; j++ ) + { + l = Numbers[ num ][ j ]; + if ( l == 0 ) + { + break; + } + + G_BeginLine(); + + pos = start + Lines[ l ][ 0 ] * right + Lines[ l ][ 1 ] * up; + G_Vertex( pos ); + + pos = start + Lines[ l ][ 2 ] * right + Lines[ l ][ 3 ] * up; + G_Vertex( pos ); + + G_EndLine(); + } + + start += 10 * right; + } + } + +Vector G_CalculateImpulse + ( + Vector start, + Vector end, + float speed, + float gravity + ) + + { + float traveltime, vertical_speed; + Vector dir, xydir, velocity; + + dir = end - start; + xydir = dir; + xydir.z = 0; + traveltime = xydir.length() / speed; + vertical_speed = ( dir.z / traveltime ) + ( 0.5f * gravity * sv_gravity->value * traveltime ); + xydir.normalize(); + + velocity = speed * xydir; + velocity.z = vertical_speed; + return velocity; + } + +Vector G_PredictPosition + ( + Vector start, + Vector target, + Vector targetvelocity, + float speed + ) + + { + Vector projected; + float traveltime; + Vector dir, xydir; + + dir = target - start; + xydir = dir; + xydir.z = 0; + traveltime = xydir.length() / speed; + projected = target + ( targetvelocity * traveltime ); + + return projected; + } + +const char *ExpandLocation + ( + const char *location + ) + + { + if ( !strcmpi( location, "all" ) ) + return NULL; + + if ( !strnicmp( location, "torso", 5 ) ) + { + if ( location[6] == 'u' ) + { + return ( "upper chest" ); + } + else if (location[6] == 'l') + { + return ( "lower chest" ); + } + else + { + return ( "chest" ); + } + } + else if ( !strnicmp( location, "leg", 3 ) ) + { + if ( location[9] == 'u' ) + { + return ( "upper leg" ); + } + else if (location[9] == 'l') + { + return ( "lower leg" ); + } + else + { + return ( "leg" ); + } + } + else if ( !strnicmp( location, "arm", 3 ) ) + { + return ( "arm" ); + } + else if ( !strnicmp( location, "head", 4 ) ) + { + return ( "head" ); + } + + return NULL; + } + +char *ClientTeam + ( + edict_t *ent + ) + + { + static char value[512]; + + value[0] = 0; + + if (!ent->client) + return value; + + if ( DM_FLAG( DF_MODELTEAMS ) ) + COM_StripExtension( Info_ValueForKey( ent->client->pers.userinfo, "model" ), value ); + else if ( DM_FLAG( DF_SKINTEAMS ) ) + COM_StripExtension( Info_ValueForKey( ent->client->pers.userinfo, "skin" ), value ); + + return( value ); + } + + +qboolean OnSameTeam + ( + Entity *ent1, + Entity *ent2 + ) + + { + char ent1Team [512]; + char ent2Team [512]; + + if ( !DM_FLAG( DF_MODELTEAMS | DF_SKINTEAMS ) ) + return false; + + strcpy (ent1Team, ClientTeam (ent1->edict)); + strcpy (ent2Team, ClientTeam (ent2->edict)); + + if ( !strcmp( ent1Team, ent2Team ) ) + return true; + + return false; + } + +/* +============== +G_LoadAndExecScript + +Like the man says... +============== +*/ +void G_LoadAndExecScript + ( + const char *filename, + const char *label + ) + + { + ScriptThread *pThread; + + if ( gi.LoadFile( filename, NULL, 0 ) != -1 ) + { + pThread = Director.CreateThread( filename, LEVEL_SCRIPT, label ); + if ( pThread ) + { + // start right away + pThread->Start( -1 ); + } + else + { + gi.dprintf( "G_LoadAndExecScript : %s could not create thread.", filename ); + } + } + } + +ScriptThread * ExecuteThread + ( + str thread_name, + qboolean start + ) + { + GameScript * s; + + if ( thread_name.length() ) + { + ScriptThread * pThread; + + s = ScriptLib.GetScript( ScriptLib.GetGameScript() ); + if ( !s ) + { + gi.dprintf( "StartThread::Null game script\n" ); + return false; + } + pThread = Director.CreateThread( s, thread_name.c_str() ); + if ( pThread ) + { + if ( start ) + { + // start right away + pThread->Start( -1 ); + } + } + else + { + gi.dprintf( "StartThread::unable to go to %s\n", thread_name.c_str() ); + return NULL; + } + return pThread; + } + return NULL; + } + +/* +============== +G_ArchiveEdict +============== +*/ +void G_ArchiveEdict + ( + Archiver &arc, + edict_t *edict + ) + + { + assert( edict ); + + if ( edict->client ) + { + arc.WriteRaw( edict->client, sizeof( *edict->client ) ); + } + + arc.WriteVector( Vector( edict->s.origin ) ); + arc.WriteVector( Vector( edict->s.angles ) ); + + arc.WriteQuat( Quat( edict->s.quat ) ); + arc.WriteQuat( Quat( edict->s.mat ) ); + + arc.WriteVector( Vector( edict->s.old_origin ) ); + arc.WriteInteger( edict->s.modelindex ); + arc.WriteInteger( edict->s.frame ); + arc.WriteInteger( edict->s.prevframe ); + + arc.WriteVector( Vector( edict->s.vieworigin ) ); + arc.WriteVector( Vector( edict->s.viewangles ) ); + + arc.WriteInteger( edict->s.anim ); + arc.WriteFloat( edict->s.scale ); + arc.WriteFloat( edict->s.alpha ); + arc.WriteFloat( edict->s.color_r ); + arc.WriteFloat( edict->s.color_g ); + arc.WriteFloat( edict->s.color_b ); + arc.WriteInteger( edict->s.radius ); + arc.WriteRaw( &edict->s.bone, sizeof( edict->s.bone ) ); + arc.WriteInteger( edict->s.parent ); + arc.WriteInteger( edict->s.numgroups ); + arc.WriteRaw( &edict->s.groups, sizeof( edict->s.groups ) ); + arc.WriteInteger( edict->s.gunanim ); + arc.WriteInteger( edict->s.gunframe ); + // index into configstrings + arc.WriteInteger( edict->s.gunmodelindex ); + arc.WriteInteger( edict->s.lightofs ); + arc.WriteInteger( edict->s.skinnum ); + arc.WriteInteger( edict->s.effects ); + arc.WriteInteger( edict->s.renderfx ); + arc.WriteInteger( edict->s.solid ); + // index into configstrings + arc.WriteInteger( edict->s.sound ); +// arc.WriteInteger( edict->s.event ); + + arc.WriteInteger( edict->svflags ); + arc.WriteVector( Vector( edict->mins ) ); + arc.WriteVector( Vector( edict->maxs ) ); + arc.WriteVector( Vector( edict->absmin ) ); + arc.WriteVector( Vector( edict->absmax ) ); + arc.WriteVector( Vector( edict->size ) ); + arc.WriteVector( Vector( edict->fullmins ) ); + arc.WriteVector( Vector( edict->fullmaxs ) ); + arc.WriteFloat( edict->fullradius ); + arc.WriteVector( Vector( edict->centroid ) ); + arc.WriteInteger( ( int )edict->solid ); + arc.WriteInteger( edict->clipmask ); + if ( edict->owner ) + { + // s.number may be cleared out, so write the actual number + arc.WriteInteger( edict->owner - g_edicts ); + } + else + { + arc.WriteInteger( -1 ); + } + + arc.WriteFloat( edict->freetime ); + arc.WriteFloat( edict->spawntime ); + arc.WriteString( str( edict->entname ) ); + } + +/* +============== +G_UnarchiveEdict +============== +*/ +void G_UnarchiveEdict + ( + Archiver &arc, + edict_t *edict + ) + + { + Vector tempvec; + str tempstr; + Quat q; + int temp; + + assert( edict ); + + // + // edict will already be setup as far as entnum is concerned + // + if ( edict->client ) + { + arc.ReadRaw( edict->client, sizeof( *edict->client ) ); + } + + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->s.origin ); + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->s.angles ); + + q = arc.ReadQuat(); + edict->s.quat[ 0 ] = q.x; + edict->s.quat[ 1 ] = q.y; + edict->s.quat[ 2 ] = q.z; + edict->s.quat[ 3 ] = q.w; + + q = arc.ReadQuat(); + QuatToMat( q.vec4(), edict->s.mat ); + + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->s.old_origin ); + arc.ReadInteger( &edict->s.modelindex ); + arc.ReadInteger( &edict->s.frame ); + arc.ReadInteger( &edict->s.prevframe ); + + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->s.vieworigin ); + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->s.viewangles ); + + arc.ReadInteger( &edict->s.anim ); + arc.ReadFloat( &edict->s.scale ); + arc.ReadFloat( &edict->s.alpha ); + arc.ReadFloat( &edict->s.color_r ); + arc.ReadFloat( &edict->s.color_g ); + arc.ReadFloat( &edict->s.color_b ); + arc.ReadInteger( &edict->s.radius ); + arc.ReadRaw( &edict->s.bone, sizeof( edict->s.bone ) ); + arc.ReadInteger( &edict->s.parent ); + arc.ReadInteger( &edict->s.numgroups ); + arc.ReadRaw( &edict->s.groups, sizeof( edict->s.groups ) ); + arc.ReadInteger( &edict->s.gunanim ); + arc.ReadInteger( &edict->s.gunframe ); + // index into configstrings + arc.ReadInteger( &edict->s.gunmodelindex ); + arc.ReadInteger( &edict->s.lightofs ); + arc.ReadInteger( &edict->s.skinnum ); + arc.ReadInteger( &edict->s.effects ); + arc.ReadInteger( &edict->s.renderfx ); + arc.ReadInteger( &edict->s.solid ); + // index into configstrings + arc.ReadInteger( &edict->s.sound ); +// arc.ReadInteger( &edict->s.event ); + + arc.ReadInteger( &edict->svflags ); + + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->mins ); + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->maxs ); + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->absmin ); + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->absmax ); + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->size ); + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->fullmins ); + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->fullmaxs ); + arc.ReadFloat( &edict->fullradius ); + tempvec = arc.ReadVector(); + tempvec.copyTo( edict->centroid ); + + edict->solid = ( solid_t )arc.ReadInteger(); + arc.ReadInteger( &edict->clipmask ); + + temp = arc.ReadInteger(); + if ( temp < 0 ) + { + edict->owner = NULL; + } + else + { + edict->owner = &g_edicts[ temp ]; + } + + arc.ReadFloat( &edict->freetime ); + arc.ReadFloat( &edict->spawntime ); + tempstr = arc.ReadString(); + strcpy( edict->entname, tempstr.c_str() ); + + gi.linkentity( edict ); + } + +/* +================ +G_Milliseconds +================ +*/ +int G_Milliseconds + ( + void + ) + + { +#ifdef _WIN32 + static int base; + static qboolean initialized = false; + + if ( !initialized ) + { + // let base retain 16 bits of effectively random data + base = timeGetTime() & 0xffff0000; + initialized = true; + } + + return timeGetTime() - base; +#else + //FIXME + return 0; +#endif + } + +/* +=============== +G_DebugPrintf + +Outputs a string to the debug window +=============== +*/ +void G_DebugPrintf + ( + const char *fmt, + ... + ) + + { + va_list argptr; + char message[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( message, fmt, argptr ); + va_end( argptr ); + +#ifdef _WIN32 + OutputDebugString( message ); +#else + gi.dprintf( message ); +#endif + } diff --git a/g_utils.h b/g_utils.h new file mode 100644 index 0000000..73c5dc5 --- /dev/null +++ b/g_utils.h @@ -0,0 +1,289 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/g_utils.h $ +// $Revision:: 15 $ +// $Author:: Markd $ +// $Date:: 10/26/98 3:50a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/g_utils.h $ +// +// 15 10/26/98 3:50a Markd +// put in prediction +// +// 14 10/18/98 3:23a Jimdose +// Added G_Milliseconds and G_DebugPrintf +// +// 13 10/16/98 7:18p Markd +// Changed ExecuteThread a little bit +// +// 12 10/10/98 1:34a Jimdose +// added edict archiving functions +// +// 11 10/09/98 4:54p Markd +// Added ExecuteThread +// +// 10 10/07/98 11:48p Jimdose +// Added G_FixSlashes +// +// 9 10/03/98 1:10p Aldie +// added findclientsinradius +// +// 8 9/14/98 5:41p Jimdose +// Added G_CalcBoundsOfMove +// +// 7 8/29/98 9:50p Jimdose +// Moved prototypes from g_local.h +// Moved all debugline functions from g_main.cpp +// Added trace info to G_Trace +// +// 6 8/27/98 9:05p Jimdose +// Moved several short functions to g_utils.h as inline +// +// 5 8/24/98 4:55p Markd +// Added G_CalculateImpulse +// +// 4 2/18/98 8:08p Jimdose +// Prototyped IsNumeric +// +// 3 2/06/98 5:51p Jimdose +// Added KillBox +// Changed G_TouchTriggers and M_CheckBottom to be .cpp compatible +// +// 2 2/03/98 10:49a Jimdose +// Created file. +// +// DESCRIPTION: +// + +#ifndef __G_UTILS_H__ +#define __G_UTILS_H__ + +class Archiver; + +EXPORT_FROM_DLL void G_ArchiveEdict( Archiver &arc, edict_t *edict ); +EXPORT_FROM_DLL void G_UnarchiveEdict( Archiver &arc, edict_t *edict ); + +#include "entity.h" + +EXPORT_FROM_DLL void G_InitEdict (edict_t *e); +EXPORT_FROM_DLL edict_t *G_Spawn (void); +EXPORT_FROM_DLL void G_FreeEdict (edict_t *e); + +EXPORT_FROM_DLL void G_TouchTriggers (Entity *ent); +EXPORT_FROM_DLL void G_TouchSolids (Entity *ent); + +EXPORT_FROM_DLL char *G_CopyString (const char *in); + +EXPORT_FROM_DLL int G_FindClass( int entnum, const char *classname ); +EXPORT_FROM_DLL Entity *G_NextEntity( Entity *ent ); + +EXPORT_FROM_DLL void G_CalcBoundsOfMove( Vector &start, Vector &end, Vector &mins, Vector &maxs, Vector *minbounds, Vector *maxbounds ); + +EXPORT_FROM_DLL void G_ShowTrace( trace_t *trace, edict_t *passent, const char *reason ); +EXPORT_FROM_DLL trace_t G_Trace( Vector &start, Vector &mins, Vector &maxs, Vector &end, Entity *passent, int contentmask, const char *reason ); +EXPORT_FROM_DLL trace_t G_Trace( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask, const char *reason ); +EXPORT_FROM_DLL trace_t G_FullTrace( Vector &start, Vector &mins, Vector &maxs, Vector &end, float radius, Entity *passent, int contentmask, const char *reason ); +EXPORT_FROM_DLL trace_t G_FullTrace( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, float radius, edict_t *passent, int contentmask, const char *reason ); + +EXPORT_FROM_DLL void SelectSpawnPoint( Vector &origin, Vector &angles, int *gravaxis = NULL ); + +EXPORT_FROM_DLL int G_FindTarget( int entnum, const char *name ); +EXPORT_FROM_DLL Entity *G_NextEntity( Entity *ent ); + +EXPORT_FROM_DLL qboolean M_CheckBottom( Entity *ent ); + +EXPORT_FROM_DLL Vector G_GetMovedir( void ); +EXPORT_FROM_DLL qboolean KillBox( Entity *ent ); +EXPORT_FROM_DLL qboolean IsNumeric( const char *str ); + +EXPORT_FROM_DLL Entity *findradius( Entity *startent, Vector org, float rad ); +EXPORT_FROM_DLL Entity *findclientsinradius( Entity *startent, Vector org, float rad ); +EXPORT_FROM_DLL const char *G_GetNameForSurface( csurface_t *s ); + +EXPORT_FROM_DLL Vector G_CalculateImpulse( Vector start, Vector end, float speed, float gravity ); +EXPORT_FROM_DLL Vector G_PredictPosition( Vector start, Vector target, Vector targetvelocity, float speed ); + +EXPORT_FROM_DLL void G_InitDebugLines( void ); +EXPORT_FROM_DLL void G_DebugLine( Vector start, Vector end, float r, float g, float b, float alpha ); +EXPORT_FROM_DLL void G_Color3f( float r, float g, float b ); +EXPORT_FROM_DLL void G_Color3v( Vector color ); +EXPORT_FROM_DLL void G_Color4f( float r, float g, float b, float alpha ); +EXPORT_FROM_DLL void G_Color3vf( Vector color, float alpha ); +EXPORT_FROM_DLL void G_BeginLine( void ); +EXPORT_FROM_DLL void G_Vertex( Vector v ); +EXPORT_FROM_DLL void G_EndLine( void ); +EXPORT_FROM_DLL void G_DebugBBox( Vector origin, Vector mins, Vector maxs, float r, float g, float b, float alpha ); +EXPORT_FROM_DLL void G_DrawDebugNumber( Vector origin, int number, float scale, float r, float g, float b ); + +EXPORT_FROM_DLL void G_LoadAndExecScript( const char *filename, const char *label = NULL ); +EXPORT_FROM_DLL ScriptThread *ExecuteThread( str thread_name, qboolean start = true ); + +EXPORT_FROM_DLL int G_Milliseconds( void ); +EXPORT_FROM_DLL void G_DebugPrintf( const char *fmt, ... ); + +//================================================================== +// +// Inline functions +// +//================================================================== + +inline EXPORT_FROM_DLL float angmod + ( + float v + ) + + { + int b; + + b = ( int )v; + + b = b - ( b % 360 ); + if ( b < 0 ) + { + b -= 360; + } + + return v - ( float )b; + } + +/* +================= +G_GetEntity + +Takes an index to an entity and returns pointer to it. +================= +*/ + +inline EXPORT_FROM_DLL Entity *G_GetEntity + ( + int entnum + ) + + { + if ( ( entnum < 0 ) || ( entnum >= globals.max_edicts ) ) + { + gi.error ("G_GetEntity: %d out of valid range.", entnum ); + } + + return ( Entity * )g_edicts[ entnum ].entity; + } + +/* +================= +G_Random + +Returns a number from 0<= num < 1 + +random() +================= +*/ + +inline EXPORT_FROM_DLL float G_Random + ( + void + ) + + { + return ( ( float )( rand() & 0x7fff ) ) / ( ( float )0x8000 ); + } + +/* +================= +G_Random + +Returns a number from 0 <= num < n + +random() +================= +*/ + +inline EXPORT_FROM_DLL float G_Random + ( + float n + ) + + { + return G_Random() * n; + } + +/* +================= +G_CRandom + +Returns a number from -1 <= num < 1 + +crandom() +================= +*/ + +inline EXPORT_FROM_DLL float G_CRandom + ( + void + ) + + { + return G_Random( 2 ) - 1; + } + +/* +================= +G_CRandom + +Returns a number from -n <= num < n + +crandom() +================= +*/ + +inline EXPORT_FROM_DLL float G_CRandom + ( + float n + ) + + { + return G_CRandom() * n; + } + +/* +================= +G_FixSlashes + +Converts all backslashes in a string to forward slashes. +Used to make filenames consistant. +================= +*/ + +inline EXPORT_FROM_DLL str G_FixSlashes + ( + const char *filename + ) + + { + int i; + int len; + str text; + + if ( filename ) + { + // Convert all forward slashes to back slashes + text = filename; + len = text.length(); + for( i = 0; i < len; i++ ) + { + if ( text[ i ] == '\\' ) + { + text[ i ] = '/'; + } + } + } + + return text; + } + +#endif /* g_utils.h */ diff --git a/game.def b/game.def new file mode 100644 index 0000000..df4958f --- /dev/null +++ b/game.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/game.dsp b/game.dsp new file mode 100644 index 0000000..ef915f4 --- /dev/null +++ b/game.dsp @@ -0,0 +1,868 @@ +# Microsoft Developer Studio Project File - Name="game" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 5.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=game - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "game.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "game.mak" CFG="game - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "game - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "game - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP Scc_ProjName ""$/Quake 2 Engine/Sin/code/game", FTDAAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "game - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "." +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "." +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "SIN" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" /d "SIN" /d "SIN_DISABLECOPYPROTECTION" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winmm.lib /nologo /base:"0x11000000" /subsystem:windows /dll /machine:I386 /out:"Release/gamex86.dll" +# SUBTRACT LINK32 /incremental:yes /map /debug + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "." +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\Debug" +# PROP Intermediate_Dir ".\Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "." +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /W3 /Gm /GX /Zi /Od /I "\newsin\code\game" /D "BUILDING_REF_GL" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "SIN" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" /d "SIN" /d "SIN_DISABLECOPYPROTECTION" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /base:"0x11000000" /subsystem:windows /dll /map /debug /machine:I386 /out:"Debug/gamex86.dll" + +!ENDIF + +# Begin Target + +# Name "game - Win32 Release" +# Name "game - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\actor.cpp +# End Source File +# Begin Source File + +SOURCE=.\ammo.cpp +# End Source File +# Begin Source File + +SOURCE=.\animals.cpp +# End Source File +# Begin Source File + +SOURCE=.\arcade_comm.cpp +# End Source File +# Begin Source File + +SOURCE=.\archive.cpp +# End Source File +# Begin Source File + +SOURCE=.\areaportal.cpp +# End Source File +# Begin Source File + +SOURCE=.\armor.cpp +# End Source File +# Begin Source File + +SOURCE=.\assaultrifle.cpp +# End Source File +# Begin Source File + +SOURCE=.\bacrodai.cpp +# End Source File +# Begin Source File + +SOURCE=.\behavior.cpp +# End Source File +# Begin Source File + +SOURCE=.\bouncingbetty.cpp +# End Source File +# Begin Source File + +SOURCE=.\box.cpp +# End Source File +# Begin Source File + +SOURCE=.\bspline.cpp +# End Source File +# Begin Source File + +SOURCE=.\bullet.cpp +# End Source File +# Begin Source File + +SOURCE=.\camera.cpp +# End Source File +# Begin Source File + +SOURCE=.\camgun.cpp +# End Source File +# Begin Source File + +SOURCE=.\chaingun.cpp +# End Source File +# Begin Source File + +SOURCE=.\class.cpp +# End Source File +# Begin Source File + +SOURCE=.\console.cpp +# End Source File +# Begin Source File + +SOURCE=.\ctf.cpp +# End Source File +# Begin Source File + +SOURCE=.\ctf_player.cpp +# End Source File +# Begin Source File + +SOURCE=.\deadbody.cpp +# End Source File +# Begin Source File + +SOURCE=.\doors.cpp +# End Source File +# Begin Source File + +SOURCE=.\earthquake.cpp +# End Source File +# Begin Source File + +SOURCE=.\entity.cpp +# End Source File +# Begin Source File + +SOURCE=.\eonandpeon.cpp +# End Source File +# Begin Source File + +SOURCE=.\explosion.cpp +# End Source File +# Begin Source File + +SOURCE=.\fists.cpp +# End Source File +# Begin Source File + +SOURCE=.\g_main.cpp +# End Source File +# Begin Source File + +SOURCE=.\g_phys.cpp +# End Source File +# Begin Source File + +SOURCE=.\g_spawn.cpp +# End Source File +# Begin Source File + +SOURCE=.\g_utils.cpp +# End Source File +# Begin Source File + +SOURCE=.\gamescript.cpp +# End Source File +# Begin Source File + +SOURCE=.\genericbullet.cpp +# End Source File +# Begin Source File + +SOURCE=.\genericrocket.cpp +# End Source File +# Begin Source File + +SOURCE=.\gibs.cpp +# End Source File +# Begin Source File + +SOURCE=.\glowstick.cpp +# End Source File +# Begin Source File + +SOURCE=.\gravpath.cpp +# End Source File +# Begin Source File + +SOURCE=.\hammer.cpp +# End Source File +# Begin Source File + +SOURCE=.\health.cpp +# End Source File +# Begin Source File + +SOURCE=.\heligun.cpp +# End Source File +# Begin Source File + +SOURCE=.\inventoryitem.cpp +# End Source File +# Begin Source File + +SOURCE=.\item.cpp +# End Source File +# Begin Source File + +SOURCE=.\keys.cpp +# End Source File +# Begin Source File + +SOURCE=.\launcher.cpp +# End Source File +# Begin Source File + +SOURCE=.\lensflare.cpp +# End Source File +# Begin Source File + +SOURCE=.\light.cpp +# End Source File +# Begin Source File + +SOURCE=.\listener.cpp +# End Source File +# Begin Source File + +SOURCE=.\magnum.cpp +# End Source File +# Begin Source File + +SOURCE=.\misc.cpp +# End Source File +# Begin Source File + +SOURCE=.\mover.cpp +# End Source File +# Begin Source File + +SOURCE=.\mutanthands.cpp +# End Source File +# Begin Source File + +SOURCE=.\navigate.cpp +# End Source File +# Begin Source File + +SOURCE=.\object.cpp +# End Source File +# Begin Source File + +SOURCE=.\path.cpp +# End Source File +# Begin Source File + +SOURCE=.\peon.cpp +# End Source File +# Begin Source File + +SOURCE=.\player.cpp +# End Source File +# Begin Source File + +SOURCE=.\PlayerStart.cpp +# End Source File +# Begin Source File + +SOURCE=.\powerups.cpp +# End Source File +# Begin Source File + +SOURCE=.\pulserifle.cpp +# End Source File +# Begin Source File + +SOURCE=.\q_shared.c +# End Source File +# Begin Source File + +SOURCE=.\quantumd.cpp +# End Source File +# Begin Source File + +SOURCE=.\reactiveshields.cpp +# End Source File +# Begin Source File + +SOURCE=.\rocket_turret.cpp +# End Source File +# Begin Source File + +SOURCE=.\rocketlauncher.cpp +# End Source File +# Begin Source File + +SOURCE=.\script.cpp +# End Source File +# Begin Source File + +SOURCE=.\scriptmaster.cpp +# End Source File +# Begin Source File + +SOURCE=.\scriptslave.cpp +# End Source File +# Begin Source File + +SOURCE=.\scriptvariable.cpp +# End Source File +# Begin Source File + +SOURCE=.\secgun.cpp +# End Source File +# Begin Source File + +SOURCE=.\securityturret.cpp +# End Source File +# Begin Source File + +SOURCE=.\sentient.cpp +# End Source File +# Begin Source File + +SOURCE=.\shield.cpp +# End Source File +# Begin Source File + +SOURCE=.\shotgun.cpp +# End Source File +# Begin Source File + +SOURCE=.\shotrocketlauncher.cpp +# End Source File +# Begin Source File + +SOURCE=.\silencer.cpp +# End Source File +# Begin Source File + +SOURCE=.\skeet.cpp +# End Source File +# Begin Source File + +SOURCE=.\sniperrifle.cpp +# End Source File +# Begin Source File + +SOURCE=.\speargun.cpp +# End Source File +# Begin Source File + +SOURCE=.\specialfx.cpp +# End Source File +# Begin Source File + +SOURCE=.\spidermine.cpp +# End Source File +# Begin Source File + +SOURCE=.\steering.cpp +# End Source File +# Begin Source File + +SOURCE=.\str.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /FR + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\stungun.cpp +# End Source File +# Begin Source File + +SOURCE=.\surface.cpp +# End Source File +# Begin Source File + +SOURCE=.\testweapon.cpp +# End Source File +# Begin Source File + +SOURCE=.\thrall.cpp +# End Source File +# Begin Source File + +SOURCE=.\trigger.cpp +# End Source File +# Begin Source File + +SOURCE=.\turret.cpp +# End Source File +# Begin Source File + +SOURCE=.\vehicle.cpp +# End Source File +# Begin Source File + +SOURCE=.\viewthing.cpp +# End Source File +# Begin Source File + +SOURCE=.\weapon.cpp +# End Source File +# Begin Source File + +SOURCE=.\worldspawn.cpp +# End Source File +# Begin Source File + +SOURCE=.\wrench.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\actor.h +# End Source File +# Begin Source File + +SOURCE=.\ammo.h +# End Source File +# Begin Source File + +SOURCE=.\arcade_comm.h +# End Source File +# Begin Source File + +SOURCE=.\archive.h +# End Source File +# Begin Source File + +SOURCE=.\areaportal.h +# End Source File +# Begin Source File + +SOURCE=.\armor.h +# End Source File +# Begin Source File + +SOURCE=.\assaultrifle.h +# End Source File +# Begin Source File + +SOURCE=.\bacrodai.h +# End Source File +# Begin Source File + +SOURCE=.\behavior.h +# End Source File +# Begin Source File + +SOURCE=.\bouncingbetty.h +# End Source File +# Begin Source File + +SOURCE=.\box.h +# End Source File +# Begin Source File + +SOURCE=.\bspline.h +# End Source File +# Begin Source File + +SOURCE=.\bullet.h +# End Source File +# Begin Source File + +SOURCE=.\camera.h +# End Source File +# Begin Source File + +SOURCE=.\chaingun.h +# End Source File +# Begin Source File + +SOURCE=.\class.h +# End Source File +# Begin Source File + +SOURCE=.\console.h +# End Source File +# Begin Source File + +SOURCE=.\container.h +# End Source File +# Begin Source File + +SOURCE=.\ctf.h +# End Source File +# Begin Source File + +SOURCE=.\ctf_player.h +# End Source File +# Begin Source File + +SOURCE=.\datamap.h +# End Source File +# Begin Source File + +SOURCE=.\deadbody.h +# End Source File +# Begin Source File + +SOURCE=.\doors.h +# End Source File +# Begin Source File + +SOURCE=.\earthquake.h +# End Source File +# Begin Source File + +SOURCE=.\entity.h +# End Source File +# Begin Source File + +SOURCE=.\eonandpeon.h +# End Source File +# Begin Source File + +SOURCE=.\explosion.h +# End Source File +# Begin Source File + +SOURCE=.\fists.h +# End Source File +# Begin Source File + +SOURCE=.\g_local.h +# End Source File +# Begin Source File + +SOURCE=.\g_main.h +# End Source File +# Begin Source File + +SOURCE=.\g_phys.h +# End Source File +# Begin Source File + +SOURCE=.\g_spawn.h +# End Source File +# Begin Source File + +SOURCE=.\g_utils.h +# End Source File +# Begin Source File + +SOURCE=.\game.h +# End Source File +# Begin Source File + +SOURCE=.\gamescript.h +# End Source File +# Begin Source File + +SOURCE=.\genericbullet.h +# End Source File +# Begin Source File + +SOURCE=.\genericrocket.h +# End Source File +# Begin Source File + +SOURCE=.\gibs.h +# End Source File +# Begin Source File + +SOURCE=.\gravpath.h +# End Source File +# Begin Source File + +SOURCE=.\health.h +# End Source File +# Begin Source File + +SOURCE=.\heligun.h +# End Source File +# Begin Source File + +SOURCE=.\inventoryitem.h +# End Source File +# Begin Source File + +SOURCE=.\item.h +# End Source File +# Begin Source File + +SOURCE=.\lensflare.h +# End Source File +# Begin Source File + +SOURCE=.\light.h +# End Source File +# Begin Source File + +SOURCE=.\LINKLIST.H +# End Source File +# Begin Source File + +SOURCE=.\listener.h +# End Source File +# Begin Source File + +SOURCE=.\magnum.h +# End Source File +# Begin Source File + +SOURCE=.\misc.h +# End Source File +# Begin Source File + +SOURCE=.\mover.h +# End Source File +# Begin Source File + +SOURCE=.\mutanthands.h +# End Source File +# Begin Source File + +SOURCE=.\navigate.h +# End Source File +# Begin Source File + +SOURCE=.\object.h +# End Source File +# Begin Source File + +SOURCE=.\path.h +# End Source File +# Begin Source File + +SOURCE=.\peon.h +# End Source File +# Begin Source File + +SOURCE=.\player.h +# End Source File +# Begin Source File + +SOURCE=.\PlayerStart.h +# End Source File +# Begin Source File + +SOURCE=.\powerups.h +# End Source File +# Begin Source File + +SOURCE=.\prioritystack.h +# End Source File +# Begin Source File + +SOURCE=.\pulserifle.h +# End Source File +# Begin Source File + +SOURCE=.\q_shared.h +# End Source File +# Begin Source File + +SOURCE=.\quantumd.h +# End Source File +# Begin Source File + +SOURCE=.\queue.h +# End Source File +# Begin Source File + +SOURCE=.\rocket_turret.h +# End Source File +# Begin Source File + +SOURCE=.\rocketlauncher.h +# End Source File +# Begin Source File + +SOURCE=.\script.h +# End Source File +# Begin Source File + +SOURCE=.\scriptmaster.h +# End Source File +# Begin Source File + +SOURCE=.\scriptslave.h +# End Source File +# Begin Source File + +SOURCE=.\scriptvariable.h +# End Source File +# Begin Source File + +SOURCE=.\secgun.h +# End Source File +# Begin Source File + +SOURCE=.\securityturret.h +# End Source File +# Begin Source File + +SOURCE=.\sentient.h +# End Source File +# Begin Source File + +SOURCE=.\shotgun.h +# End Source File +# Begin Source File + +SOURCE=.\shotrocketlauncher.h +# End Source File +# Begin Source File + +SOURCE=.\silencer.h +# End Source File +# Begin Source File + +SOURCE=.\skeet.h +# End Source File +# Begin Source File + +SOURCE=.\sniperrifle.h +# End Source File +# Begin Source File + +SOURCE=.\speargun.h +# End Source File +# Begin Source File + +SOURCE=.\specialfx.h +# End Source File +# Begin Source File + +SOURCE=.\spidermine.h +# End Source File +# Begin Source File + +SOURCE=.\stack.h +# End Source File +# Begin Source File + +SOURCE=.\steering.h +# End Source File +# Begin Source File + +SOURCE=.\str.h +# End Source File +# Begin Source File + +SOURCE=.\stungun.h +# End Source File +# Begin Source File + +SOURCE=.\surface.h +# End Source File +# Begin Source File + +SOURCE=.\testweapon.h +# End Source File +# Begin Source File + +SOURCE=.\thrall.h +# End Source File +# Begin Source File + +SOURCE=.\trigger.h +# End Source File +# Begin Source File + +SOURCE=.\turret.h +# End Source File +# Begin Source File + +SOURCE=.\vector.h +# End Source File +# Begin Source File + +SOURCE=.\vehicle.h +# End Source File +# Begin Source File + +SOURCE=.\viewthing.h +# End Source File +# Begin Source File + +SOURCE=.\weapon.h +# End Source File +# Begin Source File + +SOURCE=.\worldspawn.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\game.def +# End Source File +# End Group +# End Target +# End Project diff --git a/game.h b/game.h new file mode 100644 index 0000000..90d7c06 --- /dev/null +++ b/game.h @@ -0,0 +1,564 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/game.h $ +// $Revision:: 51 $ +// $Author:: Jimdose $ +// $Date:: 12/18/98 11:05p $ +// +// Copyright (C) 1998 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/game.h $ +// +// 51 12/18/98 11:05p Jimdose +// added definitions of server side stuff to get rid of qcommon.h includes +// +// 50 11/11/98 10:04p Jimdose +// Removed comment +// +// 49 11/11/98 10:02p Jimdose +// added SVF_MONSTERCLIP and SVF_PLAYERCLIP +// +// 48 10/20/98 9:46p Jimdose +// made CalcCRC take a const char * +// +// 47 10/20/98 9:39p Jimdose +// Added CalcCRC +// Upped GAME_API_VERSION +// +// 46 10/16/98 1:56a Jimdose +// Added autosave to WriteLevel function +// Increased GAME_API_VERSION +// +// 45 10/10/98 5:58p Aldie +// More quantumdestab fixes +// +// 44 10/08/98 7:42p Jimdose +// Added ServerCommand +// +// 43 9/08/98 7:09p Jimdose +// added printf to game_import_t +// +// 42 9/07/98 8:29p Markd +// Added fullmins fullmaxs and fullradius +// +// 41 9/05/98 11:55a Markd +// Upped game api version +// +// 40 9/01/98 7:47p Aldie +// Added itemname to inventory stuff +// +// 39 8/31/98 7:45p Aldie +// Updated surface data structure and removed surfinfo field +// +// 38 8/29/98 9:51p Jimdose +// made netconsole_s, netconbuffer_s, and netsurface_s visible to game dll to +// get rid of it in g_local.h +// +// 37 8/29/98 2:53p Aldie +// Added status meter for loading levels. +// +// 36 8/28/98 3:46p Markd +// Added centroid to edict_s +// +// 35 8/25/98 7:55p Markd +// Added SVF_ONLYPARENT flag +// +// 34 8/18/98 11:08p Markd +// Added new Alias System +// +// 33 8/02/98 9:00p Markd +// Merged code 3.17 +// +// 32 7/17/98 7:58p Markd +// Added radius to fulltrace, also added SVF_USEBBOX +// +// 31 6/27/98 6:40p Markd +// Added SoundLength +// +// 30 6/20/98 7:50p Markd +// Added BoneGroupName function +// +// 29 6/15/98 10:06p Jimdose +// added gi.fulltrace +// +// 28 6/15/98 7:57p Markd +// Put in Group_Flags +// +// 27 6/08/98 7:23p Aldie +// Added some surface in the game import +// +// 26 5/26/98 8:42p Markd +// Added NumGroups to interface +// +// 25 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 24 5/24/98 2:46p Markd +// Made char *'s into const char *'s +// +// 23 5/23/98 12:53p Aldie +// Updated surfaces networking. +// +// 22 5/20/98 11:12a Markd +// removed char * dependency +// +// 21 5/13/98 4:47p Aldie +// Update damage surfaces +// +// 20 5/09/98 8:01p Jimdose +// added GameDir, PlayerDir, and CreatePath to the import list for the dll +// +// 19 5/05/98 2:44p Aldie +// Added server side surface states +// +// 18 4/30/98 4:48p Aldie +// Server side console states +// +// 17 4/28/98 7:00p Aldie +// Added sever side console buffer +// +// 16 4/27/98 3:20p Jimdose +// Added DebugLines +// +// 15 4/27/98 1:51p Aldie +// Added server side console states. +// +// 14 4/09/98 1:40p Markd +// Added SVF_SHOOTABLE +// +// 13 4/07/98 8:01p Markd +// Changed all SINMDL commands to non-SINMDL prefix +// +// 12 3/30/98 6:12p Markd +// Added Alias commands +// +// 11 3/29/98 8:33p Markd +// Changed nature of InitCmds and FrameCommands +// +// 10 3/27/98 7:00p Markd +// Changed Bone functions a bit +// +// 9 3/25/98 1:22p Markd +// +// 8 3/24/98 9:59a Markd +// Added new bone, and group stuff for models +// +// 7 3/18/98 8:01p Markd +// Accidentally made fadetime an int instead of a float +// +// 6 3/18/98 7:15p Markd +// Changed sound code stuff +// +// 5 3/05/98 6:44p Markd +// Added SINMDL stuff +// +// 4 2/03/98 11:08a Jimdose +// Converted to work with Sin progs +// Could use a little clean up. +// +// 3 12/30/97 6:04p Jimdose +// Added header text +// +// DESCRIPTION: +// game dll information visible to server +// + +#ifndef __GAME_H__ +#define __GAME_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define GAME_API_VERSION 13 + +// edict->svflags +#define FRAMETIME 0.1 + +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects +#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision +#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision +#define SVF_SHOOTABLE 0x00000008 // treat as CONTENTS_SHOOTABLE for collision +#define SVF_USEBBOX 0x00000010 // do not perform perfect collision use the bbox instead +#define SVF_ONLYPARENT 0x00000020 // only send this entity to its parent +#define SVF_HIDEOWNER 0x00000040 // hide the owner of the client +#define SVF_MONSTERCLIP 0x00000080 // treat as CONTENTS_MONSTERCLIP for collision +#define SVF_PLAYERCLIP 0x00000100 // treat as CONTENTS_PLAYERCLIP for collision + +// edict->solid values + +typedef enum + { + SOLID_NOT, // no interaction with other objects + SOLID_TRIGGER, // only touch when inside, after moving + SOLID_BBOX, // touch on edge + SOLID_BSP // bsp clip, touch on edge + } solid_t; + +//=============================================================== + +// link_t is only used for entity area links now +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +#define MAX_ENT_CLUSTERS 16 + +typedef struct edict_s edict_t; +typedef struct gclient_s gclient_t; +typedef struct netconsole_s netconsole_t; +typedef struct netconbuffer_s netconbuffer_t; +typedef struct netsurface_s netsurface_t; + +#ifndef QCOMMON + +// If this is modified in qcommon.h, it must be reflected here. +#define MAX_MSGLEN 1400 // max length of a message + +// If this is modified in qcommon.h, it must be reflected here. +enum svc_ops_e +{ + svc_bad, + svc_muzzleflash, + svc_muzzleflash2, + svc_temp_entity, + svc_layout, + svc_inventory, + svc_console_command +}; + +#endif + +#ifndef GAME_INCLUDE + +typedef struct gclient_s +{ + player_state_t ps; // communicated by server to clients + int ping; + // the game dll can add anything it wants after + // this point in the structure +} gclient_t; + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + vec3_t fullmins, fullmaxs; + float fullradius; + vec3_t centroid; + solid_t solid; + int clipmask; + edict_t *owner; + + // the game dll can add anything it wants after + // this point in the structure +}; + +#endif // GAME_INCLUDE + +struct netconsole_s + { + console_state_t s; + qboolean inuse; + }; + +struct netconbuffer_s + { + console_buffer_state_t s; + }; + +struct netsurface_s + { + surface_state_t s; + qboolean inuse; + }; + +//=============================================================== + +// +// functions provided by the main engine +// +typedef struct +{ + // special messages + void (*bprintf) (int printlevel, const char *fmt, ...); + void (*dprintf) (const char *fmt, ...); + void (*printf) (const char *fmt, ...); + void (*cprintf) (edict_t *ent, int printlevel, const char *fmt, ...); + void (*centerprintf) (edict_t *ent, const char *fmt, ...); + void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs, float pitch, float fadetime, int flags); + void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs, float pitch, float fadetime, int flags); + + // config strings hold all the index strings, the lightstyles, + // and misc data like the sky definition and cdtrack. + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + void (*configstring) (int num, const char *string); + + void (*error) (const char *fmt, ...); + + // new names can only be added during spawning + // existing names can be looked up at any time + int (*modelindex) (const char *name); + int (*soundindex) (const char *name); + int (*imageindex) (const char *name); + int (*itemindex) (const char *name); + + void (*setmodel) (edict_t *ent, const char *name); + + // collision detection against world and bboxes of a-models + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask); + + // for full ray intersection tests against a-model polys and world + trace_t (*fulltrace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, float radius, edict_t *passent, int contentmask); + + int (*pointcontents) (vec3_t point); + qboolean (*inPVS) (vec3_t p1, vec3_t p2); + qboolean (*inPHS) (vec3_t p1, vec3_t p2); + void (*SetAreaPortalState) (int portalnum, qboolean open); + qboolean (*AreasConnected) (int area1, int area2); + + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + void (*linkentity) (edict_t *ent); + void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict + int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype); + void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction + + // network messaging + void (*multicast) (vec3_t origin, multicast_t to); + void (*unicast) (edict_t *ent, qboolean reliable); + void (*WriteChar) (int c); + void (*WriteByte) (int c); + void (*WriteShort) (int c); + void (*WriteLong) (int c); + void (*WriteFloat) (float f); + void (*WriteString) (const char *s); + void (*WritePosition) (vec3_t pos); // some fractional bits + void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse + void (*WriteAngle) (float f); + + // managed memory allocation + void *(*TagMalloc) (int size, int tag); + void (*TagFree) (void *block); + void (*FreeTags) (int tag); + + // console variable interaction + cvar_t *(*cvar) (const char *var_name, const char *value, int flags); + cvar_t *(*cvar_set) (const char *var_name, const char *value); + cvar_t *(*cvar_forceset) (const char *var_name, const char *value); + + // ClientCommand and coneole command parameter checking + int (*argc) (void); + const char *(*argv) (int n); + const char *(*args) (void); + + // add commands to the server console as if they were typed in + // for map changing, etc + void (*AddCommandString) (const char *text); + + void (*DebugGraph) (float value, int color); + + int (*LoadFile) ( const char *path, void **buffer, int tag); + const char *(*GameDir)( void ); + const char *(*PlayerDir)( void ); + void (*CreatePath)(const char *path); + float (*SoundLength) ( const char *path ); + + + // + // Model support + // + qboolean (*IsModel) ( int index ); + + // + // MODEL UTILITY FUNCTIONS + // + + // DEF SPECIFIC STUFF + int (*NumAnims) ( int modelindex ); + int (*NumSkins) ( int modelindex ); + int (*NumGroups) ( int modelindex ); + sinmdl_cmd_t * (*InitCommands) ( int modelindex ); + void (*CalculateBounds) ( int modelindex, float scale, vec3_t mins, vec3_t maxs ); + + // ANIM SPECIFIC STUFF + const char * (*Anim_NameForNum) ( int modelindex, int animnum ); + int (*Anim_NumForName) ( int modelindex, const char * name ); + int (*Anim_Random) ( int modelindex, const char * name ); + int (*Anim_NumFrames) ( int modelindex, int animnum ); + float (*Anim_Time) ( int modelindex, int animnum ); + void (*Anim_Delta) ( int modelindex, int animnum, vec3_t delta ); + + // FRAME SPECIFIC STUFF + sinmdl_cmd_t * (*Frame_Commands) ( int modelindex, int animnum, int framenum ); + void (*Frame_Delta) ( int modelindex, int animnum, int framenum, vec3_t delta ); + float (*Frame_Time) ( int modelindex, int animnum, int framenum ); + + // SKIN SPECIFIC STUFF + const char * (*Skin_NameForNum) ( int modelindex, int skinnum ); + int (*Skin_NumForName) ( int modelindex, const char * name ); + + // GROUP SPECIFIC STUFF + int (*Group_NameToNum) ( int modelindex, const char * name ); + const char * (*Group_NumToName) ( int modelindex, int num ); + float (*Group_DamageMultiplier) ( int modelindex, int num ); + int (*Group_Flags) ( int modelindex, int num ); + + // BONE SPECIFIC STUFF + qboolean (*GetBoneInfo) + ( + int modelindex, + const char * bonename, + int * groupindex, + int * tri_num, + vec3_t orientation + ); + + const char * (*GetBoneGroupName) + ( + int modelindex, + const char * bonename + ); + + qboolean (*GetBoneTransform) + ( + int modelindex, + int groupindex, + int tri_num, + vec3_t orientation, + int anim, + int frame, + float scale, + vec3_t trans[3], + vec3_t pos + ); + + // + // ALIAS SYSTEM + // + qboolean (*Alias_Add)( int modelindex, const char * alias, const char * name, float weight ); + const char * (*Alias_FindRandom)( int modelindex, const char * alias ); + void (*Alias_Dump)( int modelindex ); + void (*Alias_Clear)( int modelindex ); + + // + // GLOBAL ALIAS SYSTEM + // + qboolean (*GlobalAlias_Add)( const char * alias, const char * name, float weight ); + const char * (*GlobalAlias_FindRandom)( const char * alias ); + void (*GlobalAlias_Dump)( void ); + void (*GlobalAlias_Clear)( void ); + + unsigned short ( *CalcCRC )( const char *start, int count ); + + // + // SURFACES + // + int (*Surf_NumSurfaces)( void ); + csurface_t * (*Surf_Surfaces)( void ); + + void (*IncrementStatusCount)( int i ); + debugline_t **DebugLines; + int *numDebugLines; +} game_import_t; + +// +// functions exported by the game subsystem +// +typedef struct +{ + int apiversion; + + // the init function will only be called when a game starts, + // not each time a level is loaded. Persistant data for clients + // and the server can be allocated in init + void (*Init) (void); + void (*Shutdown) (void); + + // each new level entered will cause a call to SpawnEntities + void (*SpawnEntities) (const char *mapname, const char *entstring, const char *spawnpoint); + + // Read/Write Game is for storing persistant cross level information + // about the world state and the clients. + // WriteGame is called every time a level is exited. + // ReadGame is called on a loadgame. + void (*WriteGame) (const char *filename, qboolean autosave); + void (*ReadGame) (const char *filename); + + // ReadLevel is called after the default map information has been + // loaded with SpawnEntities, so any stored client spawn spots will + // be used when the clients reconnect. + void (*WriteLevel) (const char *filename, qboolean autosave ); + void (*ReadLevel) (const char *filename); + + qboolean (*ClientConnect) (edict_t *ent, const char *userinfo ); + void (*ClientBegin) (edict_t *ent, qboolean loadgame); + void (*ClientUserinfoChanged) (edict_t *ent, const char *userinfo); + void (*ClientDisconnect) (edict_t *ent); + void (*ClientCommand) (edict_t *ent); + void (*ClientThink) (edict_t *ent, usercmd_t *cmd); + + void (*RunFrame) (void); + + // ServerCommand will be called when an "sv " command is issued on the + // server console. + // The game can issue gi.argc() / gi.argv() commands to get the rest + // of the parameters + void (*ServerCommand) (void); + + void (*CreateSurfaces) (csurface_t *surfaces, int count); + + // + // global variables shared between game and server + // + + // The edict array is allocated in the game dll so it + // can vary in size from one game to another. + // + // The size will be fixed when ge->Init() is called + struct edict_s *edicts; + int edict_size; + int num_edicts; // current number, <= max_edicts + int max_edicts; + struct netconsole_s *consoles; + int console_size; + int num_consoles; + int max_consoles; + struct netconbuffer_s *conbuffers; + int conbuffer_size; + struct netsurface_s *surfaces; + int surface_size; + int max_surfaces; + int num_surfaces; +} game_export_t; + +game_export_t *GetGameApi (game_import_t *import); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/gamescript.cpp b/gamescript.cpp new file mode 100644 index 0000000..5d96188 --- /dev/null +++ b/gamescript.cpp @@ -0,0 +1,533 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/gamescript.cpp $ +// $Revision:: 15 $ +// $Author:: Jimdose $ +// $Date:: 10/20/98 10:30p $ +// +// 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/gamescript.cpp $ +// +// 15 10/20/98 10:30p Jimdose +// added crc checking to unarchive +// +// 14 10/10/98 1:28a Jimdose +// CloseScripts clears the game and dialog scripts +// +// 13 10/07/98 11:49p Jimdose +// Rewrote archiving functions for savegames +// got rid of type +// +// 12 9/22/98 4:00a Jimdose +// SetSourceScript now copies the sourcescript from the script that is passed +// in. This guarantees that it works if we pass in a copy of a script. +// +// 11 9/22/98 1:49a Jimdose +// Fixed bug where GetScript made a copy of the script that it found. All +// functions that call GetScript were then making copies of the copy, so when +// those scripts checked for labels, they checked their sourcescript's labels +// which were NULL since the sourcescript was itself a copy. whew! +// +// 10 9/21/98 10:15p Markd +// Putting archiving and unarchiving functions in +// +// 9 7/21/98 5:24p Jimdose +// GameScript now initializes type +// +// 8 7/13/98 5:56p Markd +// Added GetGameScript and SetGameScript +// +// 7 7/11/98 8:43p Markd +// Added SetDialogScript command +// +// 6 7/10/98 9:41p Jimdose +// Fixed SetSourceScript so that model scripts don't change types +// +// 5 7/07/98 11:36p Jimdose +// Added GameScriptMarker +// +// 4 6/27/98 5:45p Jimdose +// Removed call to FindLabels in Parse. This caused the script to search for +// labels anytime a new thread started +// +// 3 6/09/98 4:20p Jimdose +// Multi-file scripting implemented +// +// 2 6/05/98 2:37p Jimdose +// Created file +// +// DESCRIPTION: +// Subclass of script that preprocesses labels +// + +#include "g_local.h" +#include "script.h" +#include "gamescript.h" + +ScriptLibrarian ScriptLib; + +CLASS_DECLARATION( Class, GameScriptMarker, NULL ); + +ResponseDef GameScriptMarker::Responses[] = + { + { NULL, NULL } + }; + +CLASS_DECLARATION( Class, ScriptLibrarian, NULL ); + +ResponseDef ScriptLibrarian::Responses[] = + { + { NULL, NULL } + }; + +ScriptLibrarian::~ScriptLibrarian() + { + int i; + int num; + + num = scripts.NumObjects(); + for( i = 1; i <= num; i++ ) + { + delete scripts.ObjectAt( i ); + } + } + +void ScriptLibrarian::CloseScripts + ( + void + ) + + { + int i; + int num; + GameScript *scr; + + // Clear out the game and dialog scripts + SetGameScript( "" ); + SetDialogScript( "" ); + + num = scripts.NumObjects(); + for( i = num; i > 0; i-- ) + { + scr = scripts.ObjectAt( i ); + scripts.RemoveObjectAt( i ); + delete scr; + } + } + +void ScriptLibrarian::SetDialogScript + ( + str scriptname + ) + { + dialog_script = scriptname; + } + +void ScriptLibrarian::SetGameScript + ( + str scriptname + ) + { + game_script = scriptname; + } + +const char *ScriptLibrarian::GetGameScript + ( + void + ) + { + return game_script.c_str(); + } + +GameScript *ScriptLibrarian::FindScript + ( + const char *name + ) + + { + int i; + int num; + GameScript *scr; + str n; + + // Convert all forward slashes to back slashes + n = G_FixSlashes( name ); + + num = scripts.NumObjects(); + for( i = 1; i <= num; i++ ) + { + scr = scripts.ObjectAt( i ); + + if ( scr->Filename() == n ) + { + return scr; + } + } + + return NULL; + } + +GameScript *ScriptLibrarian::GetScript + ( + const char *name + ) + + { + GameScript *scr; + str n; + + n = G_FixSlashes( name ); + scr = FindScript( n.c_str() ); + if ( !scr && ( gi.LoadFile( name, NULL, 0 ) != -1 ) ) + { + scr = new GameScript(); + scr->LoadFile( n.c_str() ); + scripts.AddObject( scr ); + } + + return scr; + } + +qboolean ScriptLibrarian::Goto + ( + GameScript *scr, + const char *name + ) + + { + const char *p; + GameScript *s; + str n; + + p = strstr( name, "::" ); + if ( !p ) + { + return scr->Goto( name ); + } + else + { + n = str( name, 0, p - name ); + if ( n == str( "dialog" ) ) + { + n = dialog_script; + } + s = GetScript( n.c_str() ); + if ( !s ) + { + return false; + } + + p += 2; + if ( s->labelExists( p ) ) + { + scr->SetSourceScript( s ); + return scr->Goto( p ); + } + } + + return false; + } + +qboolean ScriptLibrarian::labelExists + ( + GameScript *scr, + const char *name + ) + + { + const char *p; + GameScript *s; + str n; + + p = strstr( name, "::" ); + if ( !p ) + { + return scr->labelExists( name ); + } + else + { + n = str( name, 0, p - name ); + if ( n == str( "dialog" ) ) + { + n = dialog_script; + } + s = GetScript( n.c_str() ); + if ( !s ) + { + return false; + } + + p += 2; + return s->labelExists( p ); + } + + return false; + } + +CLASS_DECLARATION( Script, GameScript, NULL ); + +ResponseDef GameScript::Responses[] = + { + { NULL, NULL } + }; + +GameScript::GameScript() + { + sourcescript = this; + labelList = NULL; + crc = 0; + } + +GameScript::GameScript + ( + GameScript *scr + ) + + { + crc = 0; + labelList = NULL; + SetSourceScript( scr ); + } + +GameScript::~GameScript() + { + Close(); + } + +void GameScript::Close + ( + void + ) + + { + FreeLabels(); + Script::Close(); + sourcescript = this; + crc = 0; + } + +void GameScript::SetSourceScript + ( + GameScript *scr + ) + + { + if ( scr != this ) + { + Close(); + + sourcescript = scr->sourcescript; + crc = sourcescript->crc; + Parse( scr->buffer, scr->length, scr->Filename() ); + } + } + +void GameScript::FreeLabels + ( + void + ) + + { + int i; + int num; + + if ( labelList ) + { + num = labelList->NumObjects(); + for( i = 1; i <= num; i++ ) + { + delete labelList->ObjectAt( i ); + } + + delete labelList; + } + + labelList = NULL; + } + +void GameScript::LoadFile + ( + const char *name + ) + + { + str n; + + // Convert all forward slashes to back slashes + n = G_FixSlashes( name ); + + sourcescript = this; + Script::LoadFile( n.c_str() ); + FindLabels(); + + crc = gi.CalcCRC( buffer, length ); + } + +void GameScript::FindLabels + ( + void + ) + + { + scriptmarker_t mark; + const char *tok; + script_label_t *label; + int len; + + FreeLabels(); + + labelList = new Container; + + MarkPosition( &mark ); + + Reset(); + + while( TokenAvailable( true ) ) + { + tok = GetToken( true ); + // see if it is a label + if ( tok ) + { + len = strlen( tok ); + if ( len && tok[ len - 1 ] == ':' ) + { + if ( !labelExists( tok ) ) + { + label = new script_label_t; + MarkPosition( &label->pos ); + label->labelname = tok; + labelList->AddObject( label ); + } + else + { + warning( "FindLabels", "Duplicate labels %s\n", tok ); + } + } + } + } + + RestorePosition( &mark ); + } + +EXPORT_FROM_DLL qboolean GameScript::labelExists + ( + const char *name + ) + + { + str labelname; + script_label_t *label; + int i; + int num; + + if ( !sourcescript->labelList ) + { + return false; + } + + labelname = name; + if ( !labelname.length() ) + { + return false; + } + + if ( labelname[ labelname.length() - 1 ] != ':' ) + { + labelname += ":"; + } + + num = sourcescript->labelList->NumObjects(); + for( i = 1; i <= num; i++ ) + { + label = sourcescript->labelList->ObjectAt( i ); + if ( labelname == label->labelname ) + { + return true; + } + } + + return false; + } + +EXPORT_FROM_DLL qboolean GameScript::Goto + ( + const char *name + ) + + { + str labelname; + script_label_t *label; + int i; + int num; + + if ( !sourcescript->labelList ) + { + return false; + } + + labelname = name; + if ( !labelname.length() ) + { + return false; + } + + if ( labelname[ labelname.length() - 1 ] != ':' ) + { + labelname += ":"; + } + + num = sourcescript->labelList->NumObjects(); + for( i = 1; i <= num; i++ ) + { + label = sourcescript->labelList->ObjectAt( i ); + if ( labelname == label->labelname ) + { + RestorePosition( &label->pos ); + return true; + } + } + + return false; + } + +EXPORT_FROM_DLL void GameScript::Mark + ( + GameScriptMarker *mark + ) + + { + assert( mark ); + assert( sourcescript ); + + mark->filename = sourcescript->Filename(); + MarkPosition( &mark->scriptmarker ); + } + +EXPORT_FROM_DLL void GameScript::Restore + ( + GameScriptMarker *mark + ) + + { + // If we change this function, we must update the unarchive function as well + GameScript *scr; + + assert( mark ); + + scr = ScriptLib.FindScript( mark->filename.c_str() ); + if ( scr ) + { + SetSourceScript( scr ); + } + else + { + LoadFile( mark->filename.c_str() ); + } + + RestorePosition( &mark->scriptmarker ); + } diff --git a/gamescript.h b/gamescript.h new file mode 100644 index 0000000..a314511 --- /dev/null +++ b/gamescript.h @@ -0,0 +1,264 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/gamescript.h $ +// $Revision:: 11 $ +// $Author:: Jimdose $ +// $Date:: 10/25/98 11:53p $ +// +// 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/gamescript.h $ +// +// 11 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 10 10/20/98 10:30p Jimdose +// added crc checking to unarchive +// +// 9 10/07/98 11:50p Jimdose +// Rewrote archiving functions for savegames +// got rid of type +// +// 8 9/22/98 1:49a Jimdose +// removed freelabels +// +// 7 9/21/98 10:15p Markd +// Putting archiving and unarchiving functions in +// +// 6 7/13/98 5:57p Markd +// Added Setgamescript and Getgamescript methods +// +// 5 7/11/98 8:43p Markd +// Added SetDialogScript +// +// 4 7/07/98 11:36p Jimdose +// Added GameScriptMarker +// +// 3 6/09/98 4:24p Jimdose +// finished implimentation +// +// 2 6/05/98 2:37p Jimdose +// Created file +// +// DESCRIPTION: +// Subclass of script that preprocesses labels +// + +#ifndef __GAMESCRIPT_H__ +#define __GAMESCRIPT_H__ + +#include "class.h" +#include "script.h" + +typedef struct + { + scriptmarker_t pos; + str labelname; + } script_label_t; + +class GameScript; + +class EXPORT_FROM_DLL GameScriptMarker : public Class + { + public: + CLASS_PROTOTYPE( GameScriptMarker ); + + str filename; + scriptmarker_t scriptmarker; + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void GameScriptMarker::Archive + ( + Archiver &arc + ) + + { + // Game scripts are unique in that we don't call our superclass to archive it's data. + // Instead, we only read enough info to then initialize the script ourselves. + arc.WriteString( filename ); + arc.WriteBoolean( scriptmarker.tokenready ); + arc.WriteInteger( scriptmarker.offset ); + arc.WriteInteger( scriptmarker.line ); + arc.WriteRaw( scriptmarker.token, sizeof( scriptmarker.token ) ); + } + +inline EXPORT_FROM_DLL void GameScriptMarker::Unarchive + ( + Archiver &arc + ) + + { + // Game scripts are unique in that we don't call our superclass to archive it's data. + // Instead, we only read enough info to then initialize the script ourselves. + arc.ReadString( &filename ); + arc.ReadBoolean( &scriptmarker.tokenready ); + arc.ReadInteger( &scriptmarker.offset ); + arc.ReadInteger( &scriptmarker.line ); + arc.ReadRaw( scriptmarker.token, sizeof( scriptmarker.token ) ); + } + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL GameScript : public Script + { + protected: + Container *labelList; + GameScript *sourcescript; + unsigned crc; + + public: + CLASS_PROTOTYPE( GameScript ); + + GameScript(); + GameScript( GameScript *scr ); + ~GameScript(); + void Close( void ); + void SetSourceScript( GameScript *scr ); + void LoadFile( const char *filename ); + + void Mark( GameScriptMarker *mark ); + void Restore( GameScriptMarker *mark ); + + void FreeLabels( void ); + void FindLabels( void ); + qboolean labelExists( const char *name ); + qboolean Goto( const char *name ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL ScriptLibrarian : public Class + { + protected: + Container scripts; + str dialog_script; + str game_script; + + public: + CLASS_PROTOTYPE( ScriptLibrarian ); + + ~ScriptLibrarian(); + + void CloseScripts( void ); + void SetDialogScript( str scriptname ); + void SetGameScript( str scriptname ); + const char *GetGameScript( void ); + GameScript *FindScript( const char *name ); + GameScript *GetScript( const char *name ); + qboolean Goto( GameScript *scr, const char *name ); + qboolean labelExists( GameScript *scr, const char *name ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void ScriptLibrarian::Archive + ( + Archiver &arc + ) + { + GameScript * scr; + int i, num; + + Class::Archive( arc ); + + num = scripts.NumObjects(); + arc.WriteInteger( num ); + for ( i = 1; i <= num; i++ ) + { + scr = scripts.ObjectAt( i ); + arc.WriteObject( scr ); + } + arc.WriteString( dialog_script ); + arc.WriteString( game_script ); + } + +inline EXPORT_FROM_DLL void ScriptLibrarian::Unarchive + ( + Archiver &arc + ) + { + GameScript * scr; + int i, num; + + Class::Unarchive( arc ); + + scripts.FreeObjectList(); + + arc.ReadInteger( &num ); + for ( i = 1; i <= num; i++ ) + { + scr = new GameScript; + arc.ReadObject( scr ); + scripts.AddObject( scr ); + } + + arc.ReadString( &dialog_script ); + arc.ReadString( &game_script ); + } + +extern ScriptLibrarian ScriptLib; + +inline EXPORT_FROM_DLL void GameScript::Archive + ( + Archiver &arc + ) + + { + // Game scripts are unique in that we don't call our superclass to archive it's data. + // Instead, we only read enough info to then initialize the script ourselves. + GameScriptMarker mark; + + arc.WriteUnsigned( crc ); + + Mark( &mark ); + arc.WriteObject( &mark ); + } + +inline EXPORT_FROM_DLL void GameScript::Unarchive + ( + Archiver &arc + ) + + { + // This function is based in part on Restore, so it changes, we must update this function as well. + // Game scripts are unique in that we don't call our superclass to archive it's data. + // Instead, we only read enough info to then initialize the script ourselves. + GameScriptMarker mark; + unsigned filecrc; + GameScript *scr; + + arc.ReadUnsigned( &filecrc ); + arc.ReadObject( &mark ); + + scr = ScriptLib.FindScript( mark.filename.c_str() ); + if ( scr ) + { + SetSourceScript( scr ); + } + else + { + LoadFile( mark.filename.c_str() ); + } + + // Error out if CRCs have changed + if ( filecrc != crc ) + { + gi.error( "File '%s' has changed from when this savegame was written. Load cancelled.\n", filename.c_str() ); + } + + RestorePosition( &mark.scriptmarker ); + } + +#endif diff --git a/genericbullet.cpp b/genericbullet.cpp new file mode 100644 index 0000000..d01a55f --- /dev/null +++ b/genericbullet.cpp @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/genericbullet.cpp $ +// $Revision:: 16 $ +// $Author:: Aldie $ +// $Date:: 10/27/98 3:44a $ +// +// Copyright (C) 1998 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/genericbullet.cpp $ +// +// 16 10/27/98 3:44a Aldie +// Tweak damage +// +// 15 10/23/98 4:45a Aldie +// Added BeeGun for the Beecadrone +// +// 14 10/13/98 2:28p Aldie +// Do a tracer on every shot +// +// 13 10/05/98 11:23p Markd +// Added ReconahGun +// +// 12 10/04/98 10:22p Markd +// Took out NextAttack delerations +// +// 11 8/26/98 5:36p Aldie +// Don't need a worldmodel for genbullet. +// +// 10 8/06/98 10:53p Aldie +// Added weapon tweaks and kickback. Also modified blast radius damage and +// rocket jumping. +// +// 9 8/01/98 3:24p Aldie +// Added server effects flag for specific weapons +// +// 8 7/26/98 3:53p Aldie +// Network tweaking +// +// 7 7/26/98 3:12a Aldie +// Changed muzzle flash +// +// 6 7/22/98 10:41p Aldie +// Fixed tracers +// +// 5 7/22/98 5:16p Aldie +// Added tracers +// +// 4 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 3 7/17/98 4:40p Aldie +// Changed models to genbullet.def +// +// 2 7/08/98 11:23p Markd +// first time +// +// 1 7/08/98 9:07p Markd +// +// DESCRIPTION: +// Generic Bullet Weapon. +// + +#include "g_local.h" +#include "genericbullet.h" + +CLASS_DECLARATION( BulletWeapon, GenericBullet, "weapon_genericbullet" ); + +ResponseDef GenericBullet::Responses[] = + { + { &EV_Weapon_Shoot, ( Response )GenericBullet::Shoot }, + { NULL, NULL } + }; + +GenericBullet::GenericBullet() + { + SetModels( NULL, "view_genbullet.def" ); + SetAmmo( "Bullet10mm", 1, 100 ); + } + +void GenericBullet::Shoot + ( + Event *ev + ) + + { + FireTracer(); + FireBullets( 1, "10 10 10", 2, 3, DAMAGE_BULLET, MOD_GENBULLET, true ); + NextAttack( 0 ); + } + +CLASS_DECLARATION( GenericBullet, ReconahGun, "weapon_reconahgun" ); + +ResponseDef ReconahGun::Responses[] = + { + { &EV_Weapon_Shoot, ( Response )ReconahGun::Shoot }, + { NULL, NULL } + }; + +void ReconahGun::Shoot + ( + Event *ev + ) + + { + if ( ( level.framenum % 3 ) == ( entnum % 3 ) ) + { + FireTracer(); + } + + FireBullets( 1, "10 10 10", 14, 26, DAMAGE_BULLET, MOD_GENBULLET, true ); + } + +CLASS_DECLARATION( GenericBullet, BeeGun, "weapon_beegun" ); + +ResponseDef BeeGun::Responses[] = + { + { &EV_Weapon_Shoot, ( Response )BeeGun::Shoot }, + { NULL, NULL } + }; + +void BeeGun::Shoot + ( + Event *ev + ) + + { + FireBullets( 1, "10 10 10", 4, 8, DAMAGE_BULLET, MOD_GENBULLET, true ); + } diff --git a/genericbullet.h b/genericbullet.h new file mode 100644 index 0000000..9092160 --- /dev/null +++ b/genericbullet.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/genericbullet.h $ +// $Revision:: 6 $ +// $Author:: Aldie $ +// $Date:: 10/23/98 5:09a $ +// +// 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/genericbullet.h $ +// +// 6 10/23/98 5:09a Aldie +// Added BeeGun +// +// 5 10/05/98 11:23p Markd +// Added ReconahGun +// +// 4 7/22/98 10:41p Aldie +// Fixed tracers +// +// 3 7/22/98 5:17p Aldie +// Added tracers +// +// 2 7/08/98 11:23p Markd +// first time +// +// 1 7/08/98 9:07p Markd +// +// DESCRIPTION: +// Generic Bullet Weapon. +// + +#ifndef __GENERIC_BULLET_H__ +#define __GENERIC_BULLET_H__ + +#include "g_local.h" +#include "item.h" +#include "weapon.h" +#include "bullet.h" + +class EXPORT_FROM_DLL GenericBullet : public BulletWeapon + { + public: + CLASS_PROTOTYPE( GenericBullet ); + + GenericBullet::GenericBullet(); + virtual void Shoot( Event *ev ); + }; + +class EXPORT_FROM_DLL ReconahGun : public GenericBullet + { + public: + CLASS_PROTOTYPE( ReconahGun ); + + virtual void Shoot( Event *ev ); + }; + +class EXPORT_FROM_DLL BeeGun : public GenericBullet + { + public: + CLASS_PROTOTYPE( BeeGun ); + + virtual void Shoot( Event *ev ); + }; + +#endif /* generic bullet.h */ diff --git a/genericrocket.cpp b/genericrocket.cpp new file mode 100644 index 0000000..cd35cf8 --- /dev/null +++ b/genericrocket.cpp @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/genericrocket.cpp $ +// $Revision:: 3 $ +// $Author:: Markd $ +// $Date:: 10/04/98 10:23p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/genericrocket.cpp $ +// +// 3 10/04/98 10:23p Markd +// Took out NextAttack stuff +// +// 2 9/15/98 6:46p Aldie +// Generic rocket for pinphat +// +// DESCRIPTION: +// Generic Rocket Launcher - to be used on monsters that have rocket launchers +// shown in their models + +#include "g_local.h" +#include "genericrocket.h" +#include "rocketlauncher.h" + +CLASS_DECLARATION( RocketLauncher, GenericRocket, "weapon_genericrocket" ); + +ResponseDef GenericRocket::Responses[] = + { + { &EV_Weapon_Shoot, ( Response )GenericRocket::Shoot }, + { NULL, NULL } + }; + +GenericRocket::GenericRocket() + { + SetModels( NULL, "view_genrocket.def" ); + SetAmmo( "Rockets", 1, 5 ); + } + +void GenericRocket::Shoot + ( + Event *ev + ) + + { + Rocket *rocket; + Vector pos; + Vector dir; + + assert( owner ); + if ( !owner ) + { + return; + } + + GetMuzzlePosition( &pos, &dir ); + + rocket = new Rocket; + rocket->Setup( owner, pos, dir ); + } + diff --git a/genericrocket.h b/genericrocket.h new file mode 100644 index 0000000..2d893c7 --- /dev/null +++ b/genericrocket.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/genericrocket.h $ +// $Revision:: 3 $ +// $Author:: Markd $ +// $Date:: 10/04/98 10:26p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/genericrocket.h $ +// +// 3 10/04/98 10:26p Markd +// Made it a subclass of RocketLauncher +// +// 2 9/15/98 6:47p Aldie +// Generic rocket for pinphat +// +// DESCRIPTION: +// Generic Rocket Launcher - to be used on monsters that have rocket launchers +// shown in their models + +#ifndef __GENERIC_ROCKET_H__ +#define __GENERIC_ROCKET_H__ + +#include "g_local.h" +#include "item.h" +#include "weapon.h" +#include "rocketlauncher.h" + +class EXPORT_FROM_DLL GenericRocket : public RocketLauncher + { + public: + CLASS_PROTOTYPE( GenericRocket ); + + GenericRocket::GenericRocket(); + virtual void Shoot( Event *ev ); + }; + +#endif /* genericrocket.h */ diff --git a/gibs.cpp b/gibs.cpp new file mode 100644 index 0000000..689ad32 --- /dev/null +++ b/gibs.cpp @@ -0,0 +1,301 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/gibs.cpp $ +// $Revision:: 21 $ +// $Author:: Jimdose $ +// $Date:: 11/19/98 9:29p $ +// +// Copyright (C) 1998 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/gibs.cpp $ +// +// 21 11/19/98 9:29p Jimdose +// gibs copy gravaxis +// +// 20 11/09/98 1:09a Aldie +// Changed fadesplat to fade in 30 secs +// +// 19 11/08/98 8:31p Aldie +// Added ability to not fade splats +// +// 18 10/22/98 9:30p Aldie +// Modified gib physics a little +// +// 17 10/07/98 11:51p Jimdose +// moved body_parts out of game structure +// +// 16 9/22/98 5:26p Markd +// fixed small gib bug +// +// 15 9/22/98 5:19p Markd +// Put in new consolidated gib function +// +// 14 9/20/98 5:11p Aldie +// Added blood trail to gib constructor +// +// 13 8/29/98 9:43p Jimdose +// Added call info to G_Trace +// +// 12 8/27/98 9:02p Jimdose +// Changed centroid to a variable +// +// 11 8/19/98 8:49p Aldie +// Increased gibbage velocity +// +// 10 8/10/98 6:52p Aldie +// Changed the gib throwing code +// +// 9 7/29/98 2:31p Aldie +// Changed health to a float +// +// 8 7/26/98 3:58p Aldie +// Reversed a logic bug +// +// 7 7/25/98 8:39p Aldie +// Bloodsplat only in single player +// +// 6 7/24/98 10:04p Aldie +// Changed the gibs layout +// +// 5 7/23/98 6:55p Aldie +// Fun with gibs +// +// 4 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 +// +// 3 6/30/98 12:40p Aldie +// Changed bloodsplat scale. +// +// 2 5/27/98 5:03a Aldie +// First version of gibs +// +// DESCRIPTION: +// Gibs - nuff said + +#include "gibs.h" + +const char *body_parts[] = + { + "gib1.def", "gibtorso.def", "gibhead.def", "gibleg.def" + //"gib1.def", "gib2.def", "gibhead.def", "gibleg.def", "gibarm.def", "gibribs.def" + }; + +const int num_body_parts = ( sizeof( body_parts ) / sizeof( body_parts[ 0 ] ) ); + +CLASS_DECLARATION( Entity, Gib, "gib" ); + +Event EV_ThrowGib("throwgib"); + +ResponseDef Gib::Responses[] = + { + { &EV_ThrowGib, ( Response )Gib::Throw }, + { &EV_Touch, ( Response )Gib::Splat }, + { NULL, NULL } + }; + +Gib::Gib + ( + const char *name, + qboolean blood_trail + ) + + { + setSize("0 0 0", "0 0 0"); + setModel(name); + setMoveType( MOVETYPE_BOUNCE ); + setSolidType( SOLID_BBOX ); + if ( blood_trail ) + edict->s.effects |= EF_GIB; + sprayed = false; + fadesplat = true; + } + +Gib::Gib() + { + setSize("0 0 0", "0 0 0"); + setModel("gib1.def"); + setMoveType( MOVETYPE_BOUNCE ); + setSolidType( SOLID_BBOX ); + edict->s.effects |= EF_GIB; + sprayed = false; + fadesplat = true; + } + +void Gib::Splat + ( + Event *ev + ) + + { + Vector end; + + if (deathmatch->value) + return; + + if (!sv_gore->value) + return; + + if (sprayed) + return; + + sprayed = true; + end = origin; + end.z -= 1024; + + SprayBlood(origin, end, 25); + + setSolidType(SOLID_NOT); + } + +void Gib::SprayBlood + ( + Vector start, + Vector end, + int damage + ) + + { + trace_t trace; + float dist; + float scale; + Entity *splat; + Vector norm; + + trace = G_Trace( start, vec_zero, vec_zero, end, NULL, MASK_SOLIDNONFENCE, "Gib::SprayBlood" ); + + if ( HitSky( &trace ) || ( trace.ent->solid != SOLID_BSP ) ) + { + return; + } + + dist = ( Vector( trace.endpos ) - start ).length(); + scale = ( float )damage / ( dist * 0.3 ); + if ( scale > 0.6 ) + { + scale = 0.6; + } + if ( scale < 0.02 ) + { + return; + } + + // Do a bloodsplat + splat = new Entity; + splat->setMoveType( MOVETYPE_NONE ); + splat->setSolidType( SOLID_NOT ); + splat->setModel( "sprites/bloodsplat.spr" ); + splat->edict->s.frame = G_Random( 4 ); + splat->setSize( "0 0 0", "0 0 0" ); + splat->edict->s.scale = scale * this->edict->s.scale; + norm = trace.plane.normal; + norm.x = -norm.x; + norm.y = -norm.y; + splat->angles = norm.toAngles(); + splat->angles.z = G_Random( 360 ); + splat->setAngles( splat->angles ); + splat->setOrigin( Vector( trace.endpos ) + ( Vector( trace.plane.normal ) * 0.2 ) ); + + if ( fadesplat ) + splat->PostEvent( EV_FadeOut, 3 ); + else + splat->PostEvent( EV_FadeOut, 30 ); + } + +void Gib::ClipGibVelocity + ( + void + ) + + { + if (velocity[0] < -400) + velocity[0] = -400; + else if (velocity[0] > 400) + velocity[0] = 400; + if (velocity[1] < -400) + velocity[1] = -400; + else if (velocity[1] > 400) + velocity[1] = 400; + if (velocity[2] < 200) + velocity[2] = 200; // always some upwards + else if (velocity[2] > 600) + velocity[2] = 600; +} + +void Gib::SetVelocity + ( + float damage + ) + + { + velocity[0] = 100.0 * crandom(); + velocity[1] = 100.0 * crandom(); + velocity[2] = 200.0 + 100.0 * random(); + + avelocity = Vector( G_Random( 600 ), G_Random( 600 ), G_Random( 600 ) ); + + if ( ( damage < -150 ) && ( G_Random() > 0.95f ) ) + velocity *= 2.0f; + else if ( damage < -100 ) + velocity *= 1.5f; + + ClipGibVelocity(); + } + +void Gib::Throw + ( + Event *ev + ) + + { + Entity *ent; + + ent = ev->GetEntity(1); + setOrigin(ent->centroid); + worldorigin.copyTo(edict->s.old_origin); + SetVelocity(ev->GetInteger(2)); + edict->s.scale = ev->GetFloat(3); + PostEvent(EV_FadeOut, 10 + G_Random(5)); + } + + +void CreateGibs + ( + Entity * ent, + float damage, + float scale, + int num, + const char * modelname + ) + { + int i; + Gib * gib; + + assert( ent ); + + if ( !ent ) + return; + + ent->RandomGlobalSound( "impact_gib" ); + for ( i = 0; i < num; i++ ) + { + if ( modelname ) + { + gib = new Gib( modelname ); + } + else + { + gib = new Gib( body_parts[ i % num_body_parts ] ); + } + gib->setOrigin(ent->centroid); + gib->worldorigin.copyTo(gib->edict->s.old_origin); + gib->SetVelocity( damage ); + gib->edict->s.scale = scale + G_Random( scale * 0.3 ); + gib->SetGravityAxis( ent->gravaxis ); + gib->PostEvent(EV_FadeOut, 10 + G_Random(5)); + } + } diff --git a/gibs.h b/gibs.h new file mode 100644 index 0000000..6462a38 --- /dev/null +++ b/gibs.h @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/gibs.h $ +// $Revision:: 8 $ +// $Author:: Aldie $ +// $Date:: 11/08/98 8:30p $ +// +// Copyright (C) 1998 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/gibs.h $ +// +// 8 11/08/98 8:30p Aldie +// Added ability to not fade splats +// +// 7 9/28/98 9:12p Markd +// Put in archive and unarchive functions +// +// 6 9/22/98 5:19p Markd +// Put in new consolidated gib function +// +// 5 9/20/98 5:13p Aldie +// Added blood trail to gib +// +// 4 8/10/98 6:53p Aldie +// Changed the gib throwing +// +// 3 7/29/98 2:32p Aldie +// Changed health to a float +// +// 2 5/27/98 5:04a Aldie +// First version of gibs +// +// DESCRIPTION: +// Gibs - nuff said + +#ifndef __GIBS_H__ +#define __GIBS_H__ + +#include "g_local.h" + +class EXPORT_FROM_DLL Gib : public Entity + { + private: + qboolean sprayed; + public: + CLASS_PROTOTYPE( Gib ); + + qboolean fadesplat; + Gib(); + Gib(const char *name, qboolean blood_trail=true); + void SetVelocity( float health ); + void SprayBlood( Vector start, Vector end, int damage ); + void Throw(Event *ev); + void Splat(Event *ev); + void ClipGibVelocity( void ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Gib::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteBoolean( sprayed ); + arc.WriteBoolean( fadesplat ); + } + +inline EXPORT_FROM_DLL void Gib::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadBoolean( &sprayed ); + arc.ReadBoolean( &fadesplat ); + } + +void CreateGibs + ( + Entity * ent, + float damage = -50, + float scale = 1.0f, + int num = 1, + const char * modelname = NULL + ); + +extern Event EV_ThrowGib; + +#endif // gibs.h \ No newline at end of file diff --git a/glowstick.cpp b/glowstick.cpp new file mode 100644 index 0000000..0eb3949 --- /dev/null +++ b/glowstick.cpp @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/glowstick.cpp $ +// $Revision:: 12 $ +// $Author:: Aldie $ +// $Date:: 10/24/98 3:14p $ +// +// Copyright (C) 1998 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/glowstick.cpp $ +// +// 12 10/24/98 3:14p Aldie +// Upped the life of sticks +// +// 11 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 10 10/07/98 1:17a Aldie +// New model +// +// 9 7/25/98 7:10p Markd +// Put in EV_Removes for demo +// +// 8 7/21/98 7:33p Aldie +// Changed def file +// +// 7 7/20/98 3:52p Aldie +// Fixed the icon +// +// 6 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// 5 6/24/98 1:36p Aldie +// Implementation of inventory system and picking stuff up +// +// 4 6/20/98 7:03p Aldie +// Changed the avel +// +// 3 6/20/98 6:53p Aldie +// Changed the model back to hvshell.def +// +// 2 6/19/98 6:37p Aldie +// First version of glowstick +// +// DESCRIPTION: +// Glowstick for a lightsource + +#include "inventoryitem.h" + +class EXPORT_FROM_DLL GlowStick : public InventoryItem + { + public: + CLASS_PROTOTYPE( GlowStick ); + GlowStick(); + void Use( Event *ev ); + }; + +CLASS_DECLARATION( InventoryItem, GlowStick, "powerups_glowstick" ) + +ResponseDef GlowStick::Responses[] = + { + { &EV_InventoryItem_Use, ( Response )GlowStick::Use }, + { NULL, NULL } + }; + +GlowStick::GlowStick + ( + ) + + { +#ifdef SIN_DEMO + PostEvent( EV_Remove, 0 ); + return; +#endif + setModel( "glowstick.def" ); + Set( 1 ); + } + +void GlowStick::Use + ( + Event *ev + ) + + { + Entity *glowstick; + Vector dir; + + assert( owner ); + + // Make sure there is a glowstick to + assert( amount ); + + amount--; + + if (amount <= 0) + { + owner->RemoveItem( this ); + } + + dir = owner->orientation[ 0 ]; + + glowstick = new Entity; + + glowstick->angles = dir.toAngles(); + glowstick->setAngles( glowstick->angles ); + glowstick->setMoveType( MOVETYPE_BOUNCE ); + glowstick->setSolidType( SOLID_NOT ); + glowstick->setModel( "glowstick.def" ); + glowstick->edict->s.renderfx |= RF_DLIGHT; + glowstick->avelocity = "500 0 0"; + glowstick->velocity = dir * 500; + glowstick->edict->s.color_r = 0.4; + glowstick->edict->s.color_g = 1.0; + glowstick->edict->s.color_b = 0.1; + glowstick->edict->s.radius = 200; + glowstick->setOrigin( owner->worldorigin + Vector(0,0,owner->viewheight) ); + glowstick->PostEvent(EV_Remove, 60); + } + diff --git a/gravpath.cpp b/gravpath.cpp new file mode 100644 index 0000000..346e379 --- /dev/null +++ b/gravpath.cpp @@ -0,0 +1,722 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/gravpath.cpp $ +// $Revision:: 18 $ +// $Author:: Markd $ +// $Date:: 10/24/98 12:42a $ +// +// Copyright (C) 1998 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/gravpath.cpp $ +// +// 18 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 17 10/21/98 2:24a Jimdose +// Made GravPath not add to the gravPathManager while loading savegames +// +// 16 10/09/98 5:22p Aldie +// Changed commands to activate and deactivate +// +// 15 10/07/98 11:52p Jimdose +// Added destructor for GravPathManager +// +// 14 9/30/98 4:34p Aldie +// Free gravpath when level changes +// +// 13 9/30/98 1:17p Markd +// Fixed gravpath null pointer checks +// +// 12 9/29/98 5:33p Aldie +// Added force gravity to the path +// +// 11 8/29/98 9:43p Jimdose +// Added call info to G_Trace +// +// 10 8/27/98 2:55p Aldie +// Added a headnode spawnflag +// +// 9 8/27/98 2:33p Aldie +// Added functionality so gravpaths work out of water +// +// 8 8/26/98 8:46p Aldie +// Added activate and deactivate commands +// +// 7 5/26/98 4:45p Aldie +// Fixed an error printf +// +// 6 5/25/98 2:28p Aldie +// Fixed issues with not loading game dll +// +// 5 5/23/98 10:21p Aldie +// Removed drawing of gravpath. +// +// 4 5/23/98 5:15p Aldie +// Removed traces checking for obstacles when finding the closest grav point. +// +// 3 5/22/98 12:24p Aldie +// Updated defaults +// +// 2 5/22/98 12:19p Aldie +// First version of gravity path +// +// DESCRIPTION: +// Gravity path - Used for underwater currents and wells. + +#include "g_local.h" +#include "entity.h" +#include "gravpath.h" +#include "container.h" +#include "navigate.h" +#include "misc.h" +#include "player.h" + +GravPathManager gravPathManager; + +CLASS_DECLARATION(Class, GravPathManager, NULL); + +ResponseDef GravPathManager::Responses[] = + { + { NULL,NULL } + }; + +GravPathManager::~GravPathManager() + { + Reset(); + } + +void GravPathManager::Reset( void ) + { + while( pathList.NumObjects() > 0 ) + { + delete ( GravPath * )pathList.ObjectAt( 1 ); + } + + pathList.FreeObjectList(); + } + +void GravPathManager::AddPath(GravPath *p) + { + int num; + num = pathList.AddObject( p ); + pathList.Resize( pathList.NumObjects() ); + } + +void GravPathManager::RemovePath(GravPath *p) + { + pathList.RemoveObject( p ); + pathList.Resize( pathList.NumObjects() ); + } + +Vector GravPathManager::CalculateGravityPull(Entity &ent, Vector pos, qboolean *force) + { + int i,num; + GravPath *p; + GravPathNode *node; + Vector point; + Vector newpoint; + Vector dir; + float bestdist = 99999; + float dist; + float speed; + float radius; + Vector velocity; + int bestpath = 0; + int entity_contents, grav_contents; + + num = pathList.NumObjects(); + + entity_contents = gi.pointcontents( ent.worldorigin.vec3() ); + + for( i = 1; i <= num; i++ ) + { + p = ( GravPath * )pathList.ObjectAt( i ); + + if ( !p ) + continue; + + // Check to see if path is active + node = p->GetNode( 1 ); + if ( !node || !node->active ) + continue; + + // Check to see if the contents are the same + grav_contents = gi.pointcontents( node->worldorigin.vec3() ); + + // If grav node is in water, make sure ent is too. + if ( ( grav_contents & CONTENTS_WATER ) && !( entity_contents & CONTENTS_WATER ) ) + continue; + + // Test to see if we are in this path's bounding box + if ( (pos.x < p->maxs.x) && (pos.y < p->maxs.y) && (pos.z < p->maxs.z) && + (pos.x > p->mins.x) && (pos.y > p->mins.y) && (pos.z > p->mins.z) ) + { + point = p->ClosestPointOnPath(pos, ent, &dist, &speed, &radius); + + // If the closest distance on the path is greater than the radius, then + // do not consider this path. + + if (dist > radius) + { + continue; + } + else if (dist < bestdist) + { + bestpath = i; + bestdist = dist; + } + } + } + + if (!bestpath) + { + return vec_zero; + } + + p = ( GravPath * )pathList.ObjectAt( bestpath ); + if ( !p ) + return velocity; + *force = p->force; + dist = p->DistanceAlongPath(pos, &speed); + newpoint = p->PointAtDistance( dist + speed); + dir = newpoint-pos; + dir.normalize(); + velocity = dir * speed; + return velocity; + } + +/*****************************************************************************/ +/*SINED info_grav_pathnode (0 0 .5) (-16 -16 0) (16 16 32) HEADNODE FORCE + "radius" Radius of the effect of the pull (Default is 256) + "speed" Speed of the pull (Use negative for a repulsion) (Default is 100) + + Set HEADNODE to signify the head of the path. + Set FORCE if you want un-fightable gravity ( i.e. can't go backwards ) +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, GravPathNode, "info_grav_pathnode" ); + +Event EV_GravPath_Create( "gravpath_create" ); +Event EV_GravPath_Activate( "activate" ); +Event EV_GravPath_Deactivate( "deactivate" ); + +ResponseDef GravPathNode::Responses[] = + { + { &EV_GravPath_Create, ( Response )GravPathNode::CreatePath }, + { &EV_GravPath_Activate, ( Response )GravPathNode::Activate }, + { &EV_GravPath_Deactivate, ( Response )GravPathNode::Deactivate }, + { NULL, NULL } + }; + +GravPathNode::GravPathNode() + { + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_NOT ); + hideModel(); + + speed = G_GetFloatArg( "speed",100.0f ); + radius = G_GetFloatArg( "radius",256.0f ); + headnode = spawnflags & 1; + active = true; + + // This is the head of a new path, post an event to create the path + if ( headnode ) + { + PostEvent( EV_GravPath_Create, 0 ); + } + } + +float GravPathNode::Speed( void ) + { + if ( active ) + return speed; + else + return 0; + }; + +void GravPathNode::Activate(Event *ev) + { + GravPathNode *node; + int num; + const char *target; + + active = true; + node = this; + // Go through the entire path and activate it + target = node->Target(); + while (target[0]) + { + if (num = G_FindTarget(0, target)) + { + node = (GravPathNode *)G_GetEntity( num ); + assert( node ); + node->active = true; + } + else + { + gi.error("GravPathNode::CreatePath: target %s not found\n",target); + } + target = node->Target(); + } + } + +void GravPathNode::Deactivate(Event *ev) + { + GravPathNode *node; + int num; + const char *target; + + active = false; + node = this; + // Go through the entire path and activate it + target = node->Target(); + while (target[0]) + { + if (num = G_FindTarget(0, target)) + { + node = (GravPathNode *)G_GetEntity( num ); + assert( node ); + node->active = false; + } + else + { + gi.error("GravPathNode::CreatePath: target %s not found\n",target); + } + target = node->Target(); + } + } + +void GravPathNode::CreatePath(Event *ev) + { + const char *target; + GravPath *path = new GravPath; + GravPathNode *node; + int num; + + ClearBounds(path->mins.vec3(),path->maxs.vec3()); + + // This node is the head of a path, create a new path in the path manager. + // and add it in, then add all of it's children in the path. + node = this; + path->AddNode(node); + path->force = spawnflags & 2; + + // Make the path from the targetlist. + target = node->Target(); + while (target[0]) + { + if (num = G_FindTarget(0, target)) + { + node = (GravPathNode *)G_GetEntity( num ); + assert( node ); + path->AddNode(node); + } + else + { + gi.error("GravPathNode::CreatePath: target %s not found\n",target); + } + target = node->Target(); + } + + // Set the origin. + path->origin = path->mins + path->maxs; + path->origin *= 0.5f; + } + +CLASS_DECLARATION( Listener, GravPath, NULL ); + +Event EV_DrawGravPath( "drawpath" ); + +ResponseDef GravPath::Responses[] = + { + { &EV_DrawGravPath, (Response)GravPath::DrawPath }, + { NULL, NULL } + }; + +GravPath::GravPath() + { + // Event *event; + + pathlength = 0; + from = NULL; + to = NULL; + nextnode = 1; + + if ( !LoadingSavegame ) + { + gravPathManager.AddPath(this); + } + + // event = new Event(EV_DrawGravPath); + // event->AddFloat(1); + // event->AddFloat(0); + // event->AddFloat(0); + // PostEvent(event,0.1f); + } + +GravPath::~GravPath() + { + pathlength = 0; + from = NULL; + to = NULL; + nextnode = 1; + gravPathManager.RemovePath(this); + } + +void GravPath::Clear + ( + void + ) + + { + nextnode = 1; + pathlength = 0; + from = NULL; + to = NULL; + pathlist.FreeObjectList(); + } + +void GravPath::Reset + ( + void + ) + + { + nextnode = 1; + } + +GravPathNode *GravPath::Start + ( + void + ) + + { + return from; + } + +GravPathNode *GravPath::End + ( + void + ) + + { + return to; + } + +void GravPath::AddNode + ( + GravPathNode *node + ) + + { + int num; + Vector r,addp; + + if ( !from ) + { + from = node; + } + + to = node; + pathlist.AddObject( GravPathNodePtr( node ) ); + + num = NumNodes(); + if ( num > 1 ) + { + pathlength += ( node->worldorigin - GetNode( num )->worldorigin ).length(); + } + + r.setXYZ(node->Radius(),node->Radius(),node->Radius()); + addp = node->worldorigin + r; + AddPointToBounds(addp.vec3(),mins.vec3(),maxs.vec3()); + addp = node->worldorigin - r; + AddPointToBounds(addp.vec3(),mins.vec3(),maxs.vec3()); + } + +GravPathNode *GravPath::GetNode + ( + int num + ) + + { + return pathlist.ObjectAt( num ); + } + +GravPathNode *GravPath::NextNode + ( + void + ) + + { + if ( nextnode <= NumNodes() ) + { + return pathlist.ObjectAt( nextnode++ ); + } + return NULL; + } + +Vector GravPath::ClosestPointOnPath + ( + Vector pos, + Entity &ent, + float *ret_dist, + float *speed, + float *radius + ) + + { + GravPathNode *s; + GravPathNode *e; + int num; + int i; + float bestdist; + Vector bestpoint; + float dist; + float segmentlength; + Vector delta; + Vector p1; + Vector p2; + Vector p3; + float t; + trace_t trace; + + num = NumNodes(); + s = GetNode( 1 ); + trace = G_Trace( pos, ent.mins, ent.maxs, s->worldorigin, &ent, MASK_PLAYERSOLID, "GravPath::ClosestPointOnPath 1" ); + bestpoint = s->worldorigin; + delta = bestpoint - pos; + bestdist = delta.length(); + *speed = s->Speed(); + *radius = s->Radius(); + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + // check if we're closest to the endpoint + delta = e->worldorigin - pos; + dist = delta.length(); + + if ( dist < bestdist ) + { + trace = G_Trace( pos, ent.mins, ent.maxs, e->worldorigin, &ent, MASK_PLAYERSOLID, "GravPath::ClosestPointOnPath 2" ); + bestdist = dist; + bestpoint = e->worldorigin; + *speed = e->Speed(); + *radius = e->Radius(); + } + + // check if we're closest to the segment + p1 = e->worldorigin - s->worldorigin; + segmentlength = p1.length(); + p1 *= 1 / segmentlength; + p2 = pos - s->worldorigin; + + t = p1 * p2; + if ( ( t > 0 ) && ( t < segmentlength ) ) + { + p3 = ( p1 * t ) + s->worldorigin; + + delta = p3 - pos; + dist = delta.length(); + if ( dist < bestdist ) + { + trace = G_Trace( pos, ent.mins, ent.maxs, p3, &ent, MASK_PLAYERSOLID, "GravPath::ClosestPointOnPath 3" ); + bestdist = dist; + bestpoint = p3; + *speed = (e->Speed() * t) + (s->Speed() * (1.0f - t)); + *radius = (e->Radius() * t) + (s->Radius() * (1.0f - t)); + } + } + + s = e; + } + *ret_dist = bestdist; + return bestpoint; + } + +float GravPath::DistanceAlongPath + ( + Vector pos, + float *speed + ) + + { + GravPathNode *s; + GravPathNode *e; + int num; + int i; + float bestdist; + float dist; + float segmentlength; + Vector delta; + Vector segment; + Vector p1; + Vector p2; + Vector p3; + float t; + float pathdist; + float bestdistalongpath; + float oosl; + pathdist = 0; + + num = NumNodes(); + s = GetNode( 1 ); + delta = s->worldorigin - pos; + bestdist = delta.length(); + bestdistalongpath = 0; + *speed = s->Speed(); + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + segment = e->worldorigin - s->worldorigin; + segmentlength = segment.length(); + + // check if we're closest to the endpoint + delta = e->worldorigin - pos; + dist = delta.length(); + + if ( dist < bestdist ) + { + bestdist = dist; + bestdistalongpath = pathdist + segmentlength; + *speed = e->Speed(); + } + + // check if we're closest to the segment + oosl = ( 1 / segmentlength ); + p1 = segment * oosl; + p1.normalize(); + p2 = pos - s->worldorigin; + + t = p1 * p2; + if ( ( t > 0 ) && ( t < segmentlength ) ) + { + p3 = ( p1 * t ) + s->worldorigin; + + delta = p3 - pos; + dist = delta.length(); + if ( dist < bestdist ) + { + bestdist = dist; + bestdistalongpath = pathdist + t; + + t *= oosl; + *speed = (e->Speed() * t) + (s->Speed() * (1.0f - t)); + } + } + + s = e; + pathdist += segmentlength; + } + + return bestdistalongpath; + } + +Vector GravPath::PointAtDistance + ( + float dist + ) + + { + GravPathNode *s; + GravPathNode *e; + int num; + int i; + Vector delta; + Vector p1; + float t; + float pathdist; + float segmentlength; + + num = NumNodes(); + s = GetNode( 1 ); + pathdist = 0; + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + delta = e->worldorigin - s->worldorigin; + segmentlength = delta.length(); + + if ( ( pathdist + segmentlength ) > dist ) + { + t = dist - pathdist; + + p1 = delta * ( t / segmentlength ); + return p1 + s->worldorigin; + } + + s = e; + pathdist += segmentlength; + } + + // cap it off at start or end of path + return s->worldorigin; + } + +void GravPath::DrawPath + ( + Event *ev + ) + + { + Vector s; + Vector e; + Vector offset; + GravPathNode *node; + int num; + int i; + float r = ev->GetFloat(1); + float g = ev->GetFloat(2); + float b = ev->GetFloat(3); + Event *event; + + num = NumNodes(); + node = GetNode( 1 ); + s = node->worldorigin; + offset = Vector( r, g, b ) * 4 + Vector( 0, 0, 0 ); + offset = Vector(0, 0, 0); + + for( i = 2; i <= num; i++ ) + { + node = GetNode( i ); + e = node->worldorigin; + + G_DebugLine( s + offset, e + offset, r, g, b, 1 ); + s = e; + } + + G_DebugBBox(origin,mins-origin,maxs-origin,1,0,0,1); + + event = new Event(EV_DrawGravPath); + event->AddFloat(r); + event->AddFloat(g); + event->AddFloat(b); + PostEvent(event,0.1f); + } + +int GravPath::NumNodes + ( + void + ) + + { + return pathlist.NumObjects(); + } + +float GravPath::Length + ( + void + ) + + { + return pathlength; + } diff --git a/gravpath.h b/gravpath.h new file mode 100644 index 0000000..5f7c67b --- /dev/null +++ b/gravpath.h @@ -0,0 +1,271 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/gravpath.h $ +// $Revision:: 11 $ +// $Author:: Jimdose $ +// $Date:: 10/25/98 11:53p $ +// +// Copyright (C) 1998 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/gravpath.h $ +// +// 11 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 10 10/21/98 2:18a Jimdose +// Added Reset to Unarchive +// +// 9 10/07/98 11:51p Jimdose +// Added destructor for GravPathManager +// added Reset to GravPath::Unarchive +// +// 8 9/30/98 4:36p Aldie +// Added reset +// +// 7 9/29/98 11:46p Aldie +// Added force spawnflag +// +// 6 9/28/98 9:12p Markd +// Put in archive and unarchive functions +// +// 5 8/27/98 2:54p Aldie +// Added a headnode flag +// +// 4 8/27/98 2:31p Aldie +// Update active flag as a public var +// +// 3 8/26/98 9:45p Aldie +// Added activate and deactivate +// +// 2 5/22/98 12:19p Aldie +// First version of gravpath +// +// DESCRIPTION: +// Gravity path - Used for underwater currents and wells. + +#ifndef __GRAVPATH_H__ +#define __GRAVPATH_H__ + +#include "g_local.h" +#include "class.h" +#include "container.h" + + +class EXPORT_FROM_DLL GravPathNode : public Entity + { + private: + float speed; + float radius; + qboolean headnode; + + public: + qboolean active; + + CLASS_PROTOTYPE(GravPathNode); + GravPathNode(); + void CreatePath(Event *ev); + void Activate(Event *ev); + void Deactivate(Event *ev); + float Speed(void); + float Radius(void) {return radius;}; + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void GravPathNode::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteFloat( speed ); + arc.WriteFloat( radius ); + arc.WriteBoolean( headnode ); + arc.WriteBoolean( active ); + } + +inline EXPORT_FROM_DLL void GravPathNode::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadFloat( &speed ); + arc.ReadFloat( &radius ); + arc.ReadBoolean( &headnode ); + arc.ReadBoolean( &active ); + } + +typedef SafePtr GravPathNodePtr; + +// +// Exported templated classes must be explicitly instantiated +// +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL SafePtr; +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL GravPath : public Listener + { + private: + Container pathlist; + float pathlength; + + GravPathNodePtr from; + GravPathNodePtr to; + int nextnode; + + public: + CLASS_PROTOTYPE( GravPath ); + + GravPath(); + ~GravPath(); + void Clear(void); + void Reset(void); + void AddNode(GravPathNode *node); + GravPathNode *GetNode(int num); + GravPathNode *NextNode(void); + Vector ClosestPointOnPath(Vector pos, Entity &ent,float *bestdist,float *speed,float *radius); + float DistanceAlongPath(Vector pos, float *speed); + Vector PointAtDistance(float dist); + void DrawPath(Event *ev); + int NumNodes(void); + float Length(void); + GravPathNode *Start(void); + GravPathNode *End(void); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + + Vector mins; + Vector maxs; + Vector origin; + qboolean force; + }; + +inline EXPORT_FROM_DLL void GravPath::Archive + ( + Archiver &arc + ) + + { + int i, num; + + Listener::Archive( arc ); + + num = pathlist.NumObjects(); + arc.WriteInteger( num ); + for ( i = 1; i <= num; i++ ) + { + arc.WriteSafePointer( pathlist.ObjectAt( i ) ); + } + + arc.WriteFloat( pathlength ); + arc.WriteSafePointer( from ); + arc.WriteSafePointer( to ); + arc.WriteInteger( nextnode ); + arc.WriteVector( mins ); + arc.WriteVector( maxs ); + arc.WriteVector( origin ); + arc.WriteBoolean( force ); + } + +inline EXPORT_FROM_DLL void GravPath::Unarchive + ( + Archiver &arc + ) + + { + int i, num; + + Reset(); + + Listener::Unarchive( arc ); + + arc.ReadInteger( &num ); + pathlist.Resize( num ); + for ( i = 1; i <= num; i++ ) + { + arc.ReadSafePointer( pathlist.AddressOfObjectAt( i ) ); + } + + arc.ReadFloat( &pathlength ); + arc.ReadSafePointer( &from ); + arc.ReadSafePointer( &to ); + arc.ReadInteger( &nextnode ); + arc.ReadVector( &mins ); + arc.ReadVector( &maxs ); + arc.ReadVector( &origin ); + arc.ReadBoolean( &force ); + } + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL GravPathManager : public Class + { + private: + Container pathList; + + public: + CLASS_PROTOTYPE( GravPathManager ); + ~GravPathManager(); + void Reset( void ); + void AddPath(GravPath *p); + void RemovePath(GravPath *p); + Vector CalculateGravityPull(Entity &ent, Vector position, qboolean *force); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void GravPathManager::Archive + ( + Archiver &arc + ) + { + int i, num; + + Class::Archive( arc ); + + num = pathList.NumObjects(); + arc.WriteInteger( num ); + for ( i = 1; i <= num; i++ ) + { + arc.WriteObject( pathList.ObjectAt( i ) ); + } + } + +inline EXPORT_FROM_DLL void GravPathManager::Unarchive + ( + Archiver &arc + ) + { + int i, num; + + Reset(); + + Class::Unarchive( arc ); + + arc.ReadInteger( &num ); + for ( i = 1; i <= num; i++ ) + { + GravPath * ptr; + + ptr = new GravPath; + arc.ReadObject( ptr ); + pathList.AddObject( ptr ); + } + } + +extern GravPathManager gravPathManager; + +extern Event EV_DrawGravPath; + +#endif /* gravpath.h */ diff --git a/hammer.cpp b/hammer.cpp new file mode 100644 index 0000000..bc018e6 --- /dev/null +++ b/hammer.cpp @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/hammer.cpp $ +// $Revision:: 4 $ +// $Author:: Markd $ +// $Date:: 10/04/98 10:25p $ +// +// Copyright (C) 1998 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/hammer.cpp $ +// +// 4 10/04/98 10:25p Markd +// Added IsDroppable +// +// 3 9/29/98 7:07p Markd +// changed models for these weapons +// +// 2 9/24/98 12:03p Markd +// new melee weapons +// +// 1 9/24/98 11:59a Markd +// +// DESCRIPTION: +// Hammer Melee weapon +// + +#include "g_local.h" +#include "item.h" +#include "weapon.h" +#include "fists.h" + +class EXPORT_FROM_DLL Hammer : public Fists + { + public: + CLASS_PROTOTYPE( Hammer ); + + Hammer::Hammer(); + virtual qboolean IsDroppable( void ); + }; + +CLASS_DECLARATION( Fists, Hammer, NULL); + +ResponseDef Hammer::Responses[] = + { + { NULL, NULL } + }; + +Hammer::Hammer() + { +#ifdef SIN_DEMO + PostEvent( EV_Remove, 0 ); + return; +#endif + SetModels( "sledgeham.def", "view_sledge.def" ); + SetAmmo( NULL, 0, 0 ); + SetRank( 11, 11 ); + strike_reach = 48; + strike_damage = 55; + SetMaxRange( strike_reach ); + SetType( WEAPON_MELEE ); + kick = 25; + } + +qboolean Hammer::IsDroppable + ( + void + ) + { + return false; + } diff --git a/health.cpp b/health.cpp new file mode 100644 index 0000000..07a9478 --- /dev/null +++ b/health.cpp @@ -0,0 +1,255 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/health.cpp $ +// $Revision:: 19 $ +// $Author:: Jimdose $ +// $Date:: 11/17/98 6:08a $ +// +// 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/health.cpp $ +// +// 19 11/17/98 6:08a Jimdose +// made PickupHealth take health from player's inventory before checking if +// they can pick it up. Fixes bug where player can't pickup health due to +// health somehow being in their inventory. +// +// 18 10/27/98 5:19p Aldie +// Added a few items for health +// +// 17 10/09/98 2:06a Aldie +// Updated DMFLAGS +// +// 16 8/27/98 2:33p Aldie +// Changed adrenaline to megahealth +// +// 15 7/23/98 11:25p Aldie +// Fixed health for hopefully the last time. +// +// 14 7/15/98 11:22p Markd +// Don't set the model unless one hasn't been setup yet +// +// 13 7/14/98 6:58p Aldie +// Updated healths +// +// 12 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// 11 6/24/98 1:36p Aldie +// Implementation of inventory system and picking stuff up +// +// 10 5/27/98 7:33p Markd +// clear out damage if healed +// +// 9 5/24/98 8:46p Jimdose +// Made a lot of functions more str-friendly. +// Got rid of a lot of char * based strings +// Cleaned up get spawn arg functions and sound functions +// sound functions now use consistant syntax +// +// 8 5/13/98 4:45p Jimdose +// Startup event is now posted with 0 delay +// +// 7 5/08/98 2:56p Markd +// Put in health pickup sounds +// +// 6 4/18/98 3:41p Markd +// Changed health types to health_ instead of item_health_ +// +// 5 4/07/98 6:43p Jimdose +// turned off respawn in single player +// +// 4 4/04/98 6:04p Jimdose +// Made response from EV_Trigger_ActivateTargets to EV_Trigger_Effect +// +// 3 3/30/98 9:54p Jimdose +// Changed location of .def files +// +// 2 3/30/98 2:30p Jimdose +// Created file +// +// DESCRIPTION: +// Health powerup +// + +#include "g_local.h" +#include "item.h" +#include "sentient.h" +#include "health.h" + +CLASS_DECLARATION( Item, Health, "health_020" ); + +ResponseDef Health::Responses[] = + { + { &EV_Item_Pickup, ( Response )Health::PickupHealth }, + { NULL, NULL } + }; + +Health::Health() + { + if ( DM_FLAG( DF_NO_HEALTH ) ) + { + PostEvent( EV_Remove, 0 ); + return; + } + + Set( 20 ); + if ( !edict->s.modelindex ) + setModel( "health.def" ); + } + +void Health::PickupHealth + ( + Event *ev + ) + + { + Sentient *sen; + Item * item; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( !other || !other->isSubclassOf( Sentient ) ) + { + return; + } + + sen = ( Sentient * )other; + + // + // We don't want the player to hold on to a box of health! + // This can happen if a player is given a health object, + // so as a precaution, get rid of any health he's carrying. + // + item = sen->FindItem( getClassname() ); + if ( item ) + { + sen->RemoveItem( item ); + item->PostEvent( EV_Remove, 0 ); + } + + if ( !ItemPickup( other ) ) + { + return; + } + + sen->health += amount; + if ( sen->health > 200 ) + { + sen->health = 200; + } + + // + // clear out damage if healed + // + if ( sen->health > 90 ) + { + // clear the damage states + memset( sen->edict->s.groups, 0, sizeof( sen->edict->s.groups ) ); + } + + // + // we don't want the player to hold on to a box of health! + // + item = sen->FindItem( getClassname() ); + if ( item ) + { + sen->RemoveItem( item ); + item->PostEvent( EV_Remove, 0 ); + } + } + + + +CLASS_DECLARATION( Health, SmallHealth, "health_005" ); + +ResponseDef SmallHealth::Responses[] = + { + { NULL, NULL } + }; + +SmallHealth::SmallHealth() + { + Set( 5 ); + setModel( "health_small.def" ); + } + +CLASS_DECLARATION( Health, LargeHealth, "health_050" ); + +ResponseDef LargeHealth::Responses[] = + { + { NULL, NULL } + }; + +LargeHealth::LargeHealth() + { + Set( 50 ); + setModel( "health_large.def" ); + } + +CLASS_DECLARATION( Health, MegaHealth, "health_100" ); + +ResponseDef MegaHealth::Responses[] = + { + { NULL, NULL } + }; + +MegaHealth::MegaHealth() + { + Set( 100 ); + setModel( "health_medkit.def" ); + } + +CLASS_DECLARATION( Health, Apple, NULL ); + +ResponseDef Apple::Responses[] = + { + { NULL, NULL } + }; + +Apple::Apple() + { + setModel( "health_apple.def" ); + } + +CLASS_DECLARATION( Health, Banana, NULL ); + +ResponseDef Banana::Responses[] = + { + { NULL, NULL } + }; + +Banana::Banana() + { + setModel( "health_banana.def" ); + } + +CLASS_DECLARATION( Health, Sandwich, NULL ); + +ResponseDef Sandwich::Responses[] = + { + { NULL, NULL } + }; + +Sandwich::Sandwich() + { + setModel( "health_sandwich.def" ); + } + +CLASS_DECLARATION( Health, Soda, NULL ); + +ResponseDef Soda::Responses[] = + { + { NULL, NULL } + }; + +Soda::Soda() + { + setModel( "health_soda.def" ); + } + diff --git a/health.h b/health.h new file mode 100644 index 0000000..559c2f4 --- /dev/null +++ b/health.h @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/health.h $ +// $Revision:: 6 $ +// $Author:: Aldie $ +// $Date:: 10/27/98 5:18p $ +// +// 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/health.h $ +// +// 6 10/27/98 5:18p Aldie +// Added a few items for health +// +// 5 8/27/98 2:32p Aldie +// Changed health adrenaline to megahealth +// +// 4 7/14/98 6:59p Aldie +// Updated healths +// +// 3 6/25/98 8:48p Markd +// Rewrote Item class, added keyed items to triggers, cleaned up item system +// +// 2 3/30/98 2:30p Jimdose +// Created file +// +// 1 3/30/98 1:47a Jimdose +// +// DESCRIPTION: +// Health powerup +// + +#ifndef __HEALTH_H__ +#define __HEALTH_H__ + +#include "g_local.h" +#include "item.h" +#include "sentient.h" +#include "item.h" + +class EXPORT_FROM_DLL Health : public Item + { + public: + CLASS_PROTOTYPE( Health ); + + Health(); + virtual void PickupHealth( Event *ev ); + }; + +class EXPORT_FROM_DLL SmallHealth : public Health + { + public: + CLASS_PROTOTYPE( SmallHealth ); + SmallHealth(); + }; + +class EXPORT_FROM_DLL LargeHealth : public Health + { + public: + CLASS_PROTOTYPE( LargeHealth ); + LargeHealth(); + }; + +class EXPORT_FROM_DLL MegaHealth : public Health + { + public: + CLASS_PROTOTYPE( MegaHealth ); + MegaHealth(); + }; + +class EXPORT_FROM_DLL Apple : public Health + { + public: + CLASS_PROTOTYPE( Apple ); + Apple(); + }; + +class EXPORT_FROM_DLL Banana : public Health + { + public: + CLASS_PROTOTYPE( Banana ); + Banana(); + }; + +class EXPORT_FROM_DLL Sandwich : public Health + { + public: + CLASS_PROTOTYPE( Sandwich ); + Sandwich(); + }; + +class EXPORT_FROM_DLL Soda : public Health + { + public: + CLASS_PROTOTYPE( Soda ); + Soda(); + }; + +#endif /* health.h */ diff --git a/heligun.cpp b/heligun.cpp new file mode 100644 index 0000000..d5c63f7 --- /dev/null +++ b/heligun.cpp @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/heligun.cpp $ +// $Revision:: 11 $ +// $Author:: Aldie $ +// $Date:: 8/06/98 10:53p $ +// +// Copyright (C) 1998 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/heligun.cpp $ +// +// 11 8/06/98 10:53p Aldie +// Added weapon tweaks and kickback. Also modified blast radius damage and rocket +// jumping. +// +// 10 8/01/98 3:24p Aldie +// Added server effects flag for specific weapons +// +// 9 8/01/98 3:03p Aldie +// Client side muzzle flash (dynamic light) +// +// 8 7/26/98 11:58a Aldie +// Remove tracers +// +// 7 7/26/98 4:15a Aldie +// Tracers +// +// 6 7/26/98 12:15a Markd +// removed world model from heli gun +// +// 5 7/25/98 4:36p Markd +// Doubled bullet damage +// +// 4 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 3 7/17/98 3:56p Markd +// changed world model +// +// 2 7/08/98 11:55p Markd +// first time +// +// 1 7/08/98 11:33p Markd +// +// DESCRIPTION: +// Helicopter gun +// + +#include "g_local.h" +#include "bullet.h" +#include "heligun.h" +#include "rocketlauncher.h" +#include "explosion.h" + +CLASS_DECLARATION( BulletWeapon, HeliGun, "weapon_heligun" ); + +ResponseDef HeliGun::Responses[] = + { + { &EV_Weapon_Shoot, ( Response )HeliGun::Shoot }, + { NULL, NULL } + }; + +HeliGun::HeliGun + ( + ) + + { + SetModels( NULL, "view_heligun.def" ); + SetAmmo( "Bullet50mm", 0, 0 ); + SetRank( 50, 50 ); + } + +void HeliGun::Shoot + ( + Event *ev + ) + + { + FireBullets( 1, "20 20 20", 16, 28, DAMAGE_BULLET, MOD_HELIGUN, false ); + NextAttack( 0 ); + } + diff --git a/heligun.h b/heligun.h new file mode 100644 index 0000000..115241e --- /dev/null +++ b/heligun.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/heligun.h $ +// $Revision:: 2 $ +// $Author:: Markd $ +// $Date:: 7/08/98 11:55p $ +// +// Copyright (C) 1998 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/heligun.h $ +// +// 2 7/08/98 11:55p Markd +// First time +// +// 1 7/08/98 11:33p Markd +// +// DESCRIPTION: +// Helicopter gun +// + +#ifndef __HELIGUN_H__ +#define __HELIGUN_H__ + +#include "g_local.h" +#include "item.h" +#include "weapon.h" +#include "bullet.h" +#include "misc.h" + +class EXPORT_FROM_DLL HeliGun : public BulletWeapon + { + public: + CLASS_PROTOTYPE( HeliGun ); + + HeliGun::HeliGun(); + virtual void Shoot( Event *ev ); + }; + +#endif /* HeliGun.h */ diff --git a/inventoryitem.cpp b/inventoryitem.cpp new file mode 100644 index 0000000..9528dd1 --- /dev/null +++ b/inventoryitem.cpp @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/inventoryitem.cpp $ +// $Revision:: 7 $ +// $Author:: Aldie $ +// $Date:: 10/09/98 2:06a $ +// +// Copyright (C) 1998 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/inventoryitem.cpp $ +// +// 7 10/09/98 2:06a Aldie +// Updated DMFLAGS +// +// 6 7/19/98 5:40p Markd +// Removed constructor again +// +// 5 7/19/98 5:38p Markd +// Made constructor process all initcommands +// +// 4 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// 3 6/24/98 1:36p Aldie +// Implementation of inventory system and picking stuff up +// +// 2 6/19/98 6:38p Aldie +// Inventory item subclass of Item +// +// DESCRIPTION: +// Inventory items + +#include "inventoryitem.h" + +CLASS_DECLARATION( Item, InventoryItem, NULL ); + +Event EV_InventoryItem_Use( "useinvitem" ); + +ResponseDef InventoryItem::Responses[] = + { + { &EV_InventoryItem_Use, (Response)InventoryItem::Use }, + { NULL, NULL } + }; + + +InventoryItem::InventoryItem + ( + ) + + { + // All powerups are inventory items + if ( DM_FLAG( DF_NO_POWERUPS ) ) + { + PostEvent( EV_Remove, 0 ); + return; + } + } + +void InventoryItem::Use + ( + Event *ev + ) + + { + } diff --git a/inventoryitem.h b/inventoryitem.h new file mode 100644 index 0000000..e0af5b1 --- /dev/null +++ b/inventoryitem.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/inventoryitem.h $ +// $Revision:: 5 $ +// $Author:: Aldie $ +// $Date:: 10/09/98 2:07a $ +// +// Copyright (C) 1998 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/inventoryitem.h $ +// +// 5 10/09/98 2:07a Aldie +// Updated DMFLAGS +// +// 4 6/25/98 8:48p Markd +// Rewrote Item class, added keyed items to triggers, cleaned up item system +// +// 3 6/24/98 1:38p Aldie +// Implementation of inventory system and picking stuff up +// +// 2 6/19/98 6:38p Aldie +// Inventory item first version +// +// DESCRIPTION: +// Items that are visible in the player's inventory + + +#ifndef __INVITEM_H__ +#define __INVITEM_H__ + +#include "item.h" + +class EXPORT_FROM_DLL InventoryItem : public Item + { + public: + CLASS_PROTOTYPE( InventoryItem ); + + InventoryItem(); + virtual void Use( Event *ev ); + }; + +extern Event EV_InventoryItem_Use; + + +#endif /* inventoryitem.h */ diff --git a/item.cpp b/item.cpp new file mode 100644 index 0000000..c5e5bf9 --- /dev/null +++ b/item.cpp @@ -0,0 +1,830 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/item.cpp $ +// $Revision:: 51 $ +// $Author:: Markd $ +// $Date:: 11/12/98 9:20p $ +// +// 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/item.cpp $ +// +// 51 11/12/98 9:20p Markd +// fixed null sounds for snd_pickup +// +// 50 10/24/98 2:07p Aldie +// Mutants can only pickup health +// +// 49 10/22/98 10:22p Markd +// Put in support for game and level item script variables +// +// 48 10/19/98 12:05a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// got rid of warning in Set +// +// 47 10/14/98 1:18a Jimdose +// Got cross-level persistant info working +// Added amount_override +// +// 46 10/11/98 8:57p Aldie +// Don't print out error message unecessarily +// +// 45 10/10/98 1:28a Jimdose +// items no longer drop to the floor when loading savegames +// +// 44 10/09/98 2:06a Aldie +// Updated DMFLAGS +// +// 43 10/07/98 5:51p Markd +// Fixed small sized enemies getting resized and causing fall out of level +// errors +// +// 42 10/04/98 6:15p Markd +// fixed pickup sounds +// +// 41 9/29/98 5:59p Markd +// Added dialog_needed stuff +// +// 40 9/29/98 11:05a Markd +// Added respawnsound support +// +// 39 9/15/98 6:37p Markd +// Added RotatedBounds flag support +// +// 38 9/08/98 9:29p Markd +// fixed picking things up by using them +// +// 37 9/02/98 11:53a Markd +// Put in pickup by pressing the use key +// +// 36 9/01/98 7:45p Aldie +// Added itemname +// +// 35 8/29/98 7:23p Aldie +// Added itemname to get around targetname problem. +// +// 34 8/18/98 11:08p Markd +// Added new Alias System +// +// 33 8/15/98 1:48p Aldie +// Don't turn off glow when objects are picked up +// +// 32 8/12/98 4:19p Aldie +// Fixed icons not showing up correctly +// +// 31 7/26/98 1:17a Markd +// Put in respawn sounds for items +// +// 30 7/14/98 6:57p Aldie +// Made dropped weapons fade out +// +// 29 7/14/98 3:53p Markd +// made pickup sounds work properly +// +// 28 7/12/98 4:34p Markd +// Fixed bounding boxes on items +// +// 27 7/11/98 8:19p Jimdose +// Drop now returns true or false depending upon whether the item can be +// dropped or not +// +// 26 7/10/98 2:48p Aldie +// Cleared targetname for all items to circumvent an error when removing an +// actor carrying a weapon of the same targetname as him. +// +// 25 6/26/98 11:30a Markd +// Changed ItemPickup +// +// 24 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// 23 6/24/98 1:36p Aldie +// Implementation of inventory system and picking stuff up +// +// 22 6/17/98 1:20a Jimdose +// Moved setOwner to Item. +// Added EV_Item_Pickup +// +// 21 6/16/98 9:37p Markd +// made items capable of being orientated +// +// 20 6/16/98 4:08p Jimdose +// Gave dropping weapons velocity +// +// 19 6/04/98 7:36p Jimdose +// PlaceItem now posts the EV_Remove instead of processing it +// +// 18 6/03/98 4:37p Markd +// When picking an item up, stop glowing, when dropping it start glowing +// +// 17 5/13/98 4:45p Jimdose +// droptofloor event is now posted with 0 delay +// +// 16 5/03/98 4:50p Jimdose +// Fixed bug where items fell through ground +// +// 15 5/03/98 4:44p Jimdose +// changed Vector class +// +// 14 5/02/98 12:39a Jimdose +// Changed PlaceItem so that items only test if they can drop to the floor. If +// they can, they're left where they were spawned so that they fall, otherwise +// they're removed +// +// 13 4/05/98 9:41p Markd +// Put in RF_GLOW +// +// 12 4/04/98 6:05p Jimdose +// ItemPickup no longer activates targets, since the item should alread have +// triggered its targets when it was touched +// +// 11 4/02/98 4:49p Jimdose +// changed droptofloor +// +// 10 3/30/98 2:32p Jimdose +// now shows model on placement +// +// 9 3/28/98 8:57p Jimdose +// changed bounding box +// +// 8 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 7 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. +// +// 6 2/19/98 2:35p Jimdose +// Updated to work with Q2 based progs +// +// 4 11/07/97 5:59p Markd +// Removed QUAKE specific sound effects +// +// 3 10/27/97 3:29p Jimdose +// Removed dependency on quakedef.h +// +// 2 9/26/97 5:30p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Base class for respawnable, carryable objects. +// + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "item.h" +#include "inventoryitem.h" +#include "scriptmaster.h" +#include "health.h" + +Event EV_Item_Pickup( "item_pickup" ); +Event EV_Item_DropToFloor( "droptofloor" ); +Event EV_Item_Respawn( "respawn" ); +Event EV_Item_SetAmount( "amount" ); +Event EV_Item_SetMaxAmount( "maxamount" ); +Event EV_Item_SetIconName( "iconname" ); +Event EV_Item_SetItemName( "itemname" ); +Event EV_Item_RespawnSound( "respawnsound" ); +Event EV_Item_DialogNeeded( "dialogneeded" ); + +CLASS_DECLARATION( Trigger, Item, NULL ); + +ResponseDef Item::Responses[] = + { + { &EV_Trigger_Effect, ( Response )Item::ItemTouch }, + { &EV_Item_DropToFloor, ( Response )Item::DropToFloor }, + { &EV_Item_Respawn, ( Response )Item::Respawn }, + { &EV_Item_SetAmount, ( Response )Item::SetAmount }, + { &EV_Item_SetMaxAmount, ( Response )Item::SetMaxAmount }, + { &EV_Item_SetIconName, ( Response )Item::SetIconName }, + { &EV_Item_SetItemName, ( Response )Item::SetItemName }, + { &EV_Item_Pickup, ( Response )Item::Pickup }, + { &EV_Use, ( Response )Item::TriggerStuff }, + { &EV_Item_RespawnSound, ( Response )Item::RespawnSound }, + { &EV_Item_DialogNeeded, ( Response )Item::DialogNeeded }, + { NULL, NULL } + }; + +Item::Item() + { + str fullname; + Vector defangles; + + setRespawnTime( 20 ); + setRespawn( false ); + + setSolidType( SOLID_NOT ); + + // Set default respawn behavior + // Derived classes should use setRespawn + // if they want to override the default behavior + if ( deathmatch->value ) + { + setRespawn( true ); + } + else + { + setRespawn( false ); + } + + edict->s.renderfx |= RF_GLOW; + + // angles + defangles = Vector( 0, G_GetFloatArg( "angle", 0 ), 0 ); + if (defangles.y == -1) + { + defangles = Vector( -90, 0, 0 ); + } + else if (defangles.y == -2) + { + defangles = Vector( 90, 0, 0 ); + } + angles = G_GetVectorArg( "angles", defangles ); + setAngles( angles ); + + // + // we want the bounds of this model auto-rotated + // + flags |= FL_ROTATEDBOUNDS; + + // + // rotate the mins and maxs for the model + // + if ( size.length() < 10 ) + setSize( "-10 -10 0", "10 10 20" ); + + // + // reset the mins and maxs to pickup the FL_ROTATEDBOUNDS flag + // + setSize( mins, maxs ); + + if ( !LoadingSavegame ) + { + // Items can't be immediately dropped to floor, because they might + // be on an entity that hasn't spawned yet. + PostEvent( EV_Item_DropToFloor, 0 ); + } + + respondto = TRIGGER_PLAYERS; + + icon_name = str(""); + icon_index = 0; + item_index = 0; + maximum_amount = 1; + playrespawn = false; + + amount_override = false; + amount = 1; + + // FIXME + // If the targetname is set by the spawn args, then this item + // will have a targetname. If we try to remove the owner of this + // item, then we will remove the owner, then try to remove the item + // which will already have been removed by the previous event. + // This doesn't allow any items to have a targetname. + SetTargetName( "" ); + + // Using itemname as a temporary fix to this problem + itemname = G_GetSpawnArg( "itemname", ""); + + if ( G_GetSpawnArg( "amount" ) ) + { + amount = G_GetIntArg( "amount" ); + if ( amount >= MaxAmount() ) + { + SetMax( amount ); + } + amount_override = true; + } + } + +Item::~Item() + { + if ( owner ) + { + owner->RemoveItem( this ); + owner = NULL; + } + } + +void Item::CreateSpawnArgs + ( + void + ) + + { + G_SetIntArg( "amount", amount ); + G_SetSpawnArg( "model", model.c_str() ); + } + +/* +============ +PlaceItem + +Puts an item back in the world +============ +*/ +void Item::PlaceItem + ( + void + ) + + { + setSolidType( SOLID_TRIGGER ); + setMoveType( MOVETYPE_TOSS ); + showModel(); + + edict->s.renderfx |= RF_GLOW; + groundentity = NULL; + } + +/* +============ +DropToFloor + +plants the object on the floor +============ +*/ +void Item::DropToFloor + ( + Event *ev + ) + + { + str fullname; + Vector save; + + PlaceItem(); + + setOrigin( origin + "0 0 1" ); + save = origin; + if ( !droptofloor( 8192 ) ) + { + gi.dprintf( "%s fell out of level at '%5.1f %5.1f %5.1f'\n", + getClassID(), origin.x, origin.y, origin.z ); + PostEvent( EV_Remove, 0 ); + return; + } + // + // if the our global variable doesn't exist, lets zero it out + // + fullname = str( "playeritem_" ) + getClassname(); + if ( !gameVars.VariableExists( fullname.c_str() ) ) + { + gameVars.SetVariable( fullname.c_str(), 0 ); + } + + if ( !levelVars.VariableExists( fullname.c_str() ) ) + { + levelVars.SetVariable( fullname.c_str(), 0 ); + } + + setOrigin( save ); + groundentity = NULL; + } + +qboolean Item::Drop + ( + void + ) + + { + if ( !owner ) + { + return false; + } + + setOrigin( owner->worldorigin + "0 0 40" ); + + // drop the item + PlaceItem(); + velocity = owner->velocity * 0.5 + Vector( G_CRandom( 50 ), G_CRandom( 50 ), 100 ); + setAngles( owner->angles ); + avelocity = Vector( 0, G_CRandom( 360 ), 0 ); + + trigger_time = level.time + 1; + + if ( owner->isClient() ) + { + spawnflags |= DROPPED_PLAYER_ITEM; + } + else + { + spawnflags |= DROPPED_ITEM; + } + + // Remove this from the owner's item list + owner->RemoveItem( this ); + owner = NULL; + + return true; + } + + +void Item::ItemTouch + ( + Event *ev + ) + + { + Entity *other; + Event *e; + + if ( owner ) + { + // Don't respond to trigger events after item is picked up. + gi.dprintf( "%s with targetname of %s was triggered unexpectedly.\n", getClassID(), TargetName() ); + return; + } + + other = ev->GetEntity( 1 ); + + e = new Event( EV_Item_Pickup ); + e->AddEntity( other ); + ProcessEvent( e ); + } + +void Item::SetOwner + ( + Sentient *ent + ) + + { + assert( ent ); + if ( !ent ) + { + // return to avoid any buggy behaviour + return; + } + + owner = ent; + setRespawn( false ); + + edict->s.renderfx &= ~RF_GLOW; + setSolidType( SOLID_NOT ); + hideModel(); + CancelEventsOfType( EV_Item_DropToFloor ); + CancelEventsOfType( EV_Remove ); +// ItemPickup( ent ); + } + +Item * Item::ItemPickup + ( + Entity *other + ) + + { + Sentient * sent; + Item * item; + str realname; + + if ( !Pickupable( other ) ) + { + return NULL; + } + + sent = ( Sentient * )other; + + item = sent->giveItem( getClassname(), Amount(), icon_index ); + + if ( !item ) + return NULL; + + realname = GetRandomAlias( "snd_pickup" ); + if ( realname.length() > 1 ) + sent->sound( realname, 1, CHAN_ITEM, ATTN_NORM ); + + if ( !Removable() ) + { + // leave the item for others to pickup + return item; + } + + CancelEventsOfType( EV_Item_DropToFloor ); + CancelEventsOfType( EV_Item_Respawn ); + CancelEventsOfType( EV_FadeOut ); + + setSolidType( SOLID_NOT ); + hideModel(); + + + if ( Respawnable() ) + { + PostEvent( EV_Item_Respawn, RespawnTime() ); + } + else + { + PostEvent( EV_Remove, 0.1 ); + } + + if ( DM_FLAG( DF_INSTANT_ITEMS ) ) + { + Event *ev; + + ev = new Event( EV_InventoryItem_Use ); + ev->AddEntity( other ); + + item->ProcessEvent( ev ); + } + + return item; + } + +void Item::Respawn + ( + Event *ev + ) + + { + showModel(); + + // allow it to be touched again + setSolidType( SOLID_TRIGGER ); + + // play respawn sound + if ( playrespawn ) + { + RandomGlobalSound( "snd_itemspawn" ); + } + + setOrigin( origin ); + }; + +void Item::setRespawn + ( + qboolean flag + ) + + { + respawnable = flag; + } + +qboolean Item::Respawnable + ( + void + ) + + { + return respawnable; + } + +void Item::setRespawnTime + ( + float time + ) + + { + respawntime = time; + } + +float Item::RespawnTime + ( + void + ) + + { + return respawntime; + } + +int Item::Amount + ( + void + ) + + { + return amount; + } + +int Item::MaxAmount + ( + void + ) + + { + return maximum_amount; + } + +qboolean Item::Pickupable + ( + Entity *other + ) + + { + if ( !other->isSubclassOf( Sentient ) ) + { + return false; + } + else + { + Sentient * sent; + Item * item; + + sent = ( Sentient * )other; + item = sent->FindItem( getClassname() ); + + if ( item && ( item->Amount() >= item->MaxAmount() ) ) + { + return false; + } + + // Mutants can't pick up anything but health + if ( other->flags & (FL_MUTANT|FL_SP_MUTANT) && !( this->isSubclassOf( Health ) ) ) + { + return false; + } + + // If deathmatch and already in a powerup, don't pickup anymore when DF_INSTANT_ITEMS is on + if ( DM_FLAG( DF_INSTANT_ITEMS ) && + this->isSubclassOf( InventoryItem ) && + sent->PowerupActive() + ) + { + return false; + } + } + return true; + } + +void Item::Pickup + ( + Event * ev + ) + + { + ItemPickup( ev->GetEntity( 1 ) ); + } + +void Item::setIcon + ( + const char *i + ) + + { + icon_name = i; + icon_index = gi.imageindex( i ); + } + +void Item::setName + ( + const char *i + ) + + { + item_name = i; + item_index = gi.itemindex( i ); + } + +int Item::Icon + ( + void + ) + + { + if ( icon_name.length() ) + return icon_index; + else + return -1; + } + +void Item::Set + ( + int startamount + ) + + { + if ( !amount_override ) + { + amount = startamount; + if ( amount >= MaxAmount() ) + SetMax( amount ); + } + } + +void Item::SetMax + ( + int maxamount + ) + + { + maximum_amount = maxamount; + } + +void Item::SetAmount + ( + Event *ev + ) + + { + Set( ev->GetInteger( 1 ) ); + } + +void Item::SetMaxAmount + ( + Event *ev + ) + + { + SetMax( ev->GetInteger( 1 ) ); + } + +void Item::SetIconName + ( + Event *ev + ) + + { + setIcon( ev->GetString( 1 ) ); + } + +void Item::SetItemName + ( + Event *ev + ) + + { + setName( ev->GetString( 1 ) ); + } + +void Item::Add + ( + int num + ) + + { + amount += num; + if ( amount >= MaxAmount() ) + amount = MaxAmount(); + } + +void Item::Remove + ( + int num + ) + { + amount -= num; + if (amount < 0) + amount = 0; + } + + +qboolean Item::Use + ( + int num + ) + + { + if ( num > amount ) + { + return false; + } + + amount -= num; + return true; + } + +qboolean Item::Removable + ( + void + ) + + { + return true; + } + +void Item::RespawnSound + ( + Event *ev + ) + + { + playrespawn = true; + } + +void Item::DialogNeeded + ( + Event *ev + ) + + { + // + // if this item is needed for a trigger, play this dialog + // + dialog_needed = ev->GetString( 1 ); + } + +str Item::GetDialogNeeded + ( + void + ) + + { + return dialog_needed; + } diff --git a/item.h b/item.h new file mode 100644 index 0000000..558a727 --- /dev/null +++ b/item.h @@ -0,0 +1,215 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/item.h $ +// $Revision:: 23 $ +// $Author:: Jimdose $ +// $Date:: 11/08/98 10:52p $ +// +// 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/item.h $ +// +// 23 11/08/98 10:52p Jimdose +// amountoverride wasn't archived +// made icon_index and item_index be calculated in unarchive +// +// 22 10/14/98 1:20a Jimdose +// Got cross-level persistant info working +// +// 21 9/29/98 5:59p Markd +// Added dialog_needed stuff +// +// 20 9/28/98 9:12p Markd +// Put in archive and unarchive functions +// +// 19 9/01/98 7:47p Aldie +// Added itemname to inventory stuff +// +// 18 8/29/98 7:23p Aldie +// Added itemname to get around targetname problem +// +// 17 7/14/98 3:55p Markd +// Got rid of pickup_sound +// +// 16 7/11/98 8:19p Jimdose +// Drop now returns true or false depending upon whether the item can be +// dropped or not +// +// 15 6/26/98 11:29a Markd +// Changed what ItemPickup returns +// +// 14 6/25/98 8:48p Markd +// Rewrote Item class, added keyed items to triggers, cleaned up item system +// +// 13 6/24/98 1:38p Aldie +// Implementation of inventory system and picking stuff up +// +// 12 6/19/98 6:38p Aldie +// Moved icon to inventory item +// +// 11 6/18/98 9:26p Aldie +// Started inventory system +// +// 10 6/17/98 1:16a Jimdose +// Moved setOwner to Item. +// Added EV_Item_Pickup +// +// 9 6/16/98 4:09p Jimdose +// Added DropToFloor +// +// 8 4/04/98 6:15p Jimdose +// Defined DROPPED_ITEM and DROPPED_PLAYER_ITEM +// +// 7 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 6 3/02/98 8:49p Jimdose +// Changed CLASS_PROTOTYPE to only take the classname +// +// 5 2/19/98 2:35p Jimdose +// Updated to work with Q2 based progs +// +// 3 10/27/97 2:59p Jimdose +// Removed dependency on quakedef.h +// +// 2 9/26/97 5:30p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Base class for respawnable, carryable objects. +// + +#ifndef __ITEM_H__ +#define __ITEM_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "sentient.h" + +extern Event EV_Item_Pickup; +extern Event EV_Item_DropToFloor; +extern Event EV_Item_Respawn; +extern Event EV_Item_SetAmount; +extern Event EV_Item_SetMaxAmount; +extern Event EV_Item_SetIconName; +extern Event EV_Item_RespawnSound; +extern Event EV_Item_DialogNeeded; + +#define DROPPED_ITEM 0x00008000 +#define DROPPED_PLAYER_ITEM 0x00010000 + +class EXPORT_FROM_DLL Item : public Trigger + { + protected: + SentientPtr owner; + qboolean respawnable; + qboolean playrespawn; + float respawntime; + str icon_name; + str dialog_needed; + str item_name; + int icon_index; + int item_index; + int maximum_amount; + int amount; + qboolean amount_override; + + + void ItemTouch( Event *ev ); + + public: + str itemname; + + CLASS_PROTOTYPE( Item ); + + Item::Item(); + Item::~Item(); + virtual void CreateSpawnArgs( void ); + virtual void PlaceItem( void ); + virtual void SetOwner( Sentient *ent ); + virtual void DropToFloor( Event *ev ); + virtual Item *ItemPickup( Entity *other ); + virtual void Respawn( Event *ev ); + virtual void setRespawn( qboolean flag ); + virtual qboolean Respawnable( void ); + virtual void setRespawnTime( float time ); + virtual float RespawnTime( void ); + virtual int GetIconIndex( void ) { return icon_index; }; + virtual int GetItemIndex( void ) { return item_index; }; + virtual int Amount( void ); + virtual int MaxAmount( void ); + virtual int Icon( void ); + virtual qboolean Pickupable( Entity *other ); + virtual void setIcon( const char *i ); + virtual void setName( const char *i ); + virtual void SetAmount( Event *ev ); + virtual void SetMaxAmount( Event *ev ); + virtual void SetIconName( Event *ev ); + virtual void SetItemName( Event *ev ); + virtual void Set( int startamount ); + virtual void SetMax( int maxamount ); + virtual void Add( int num ); + virtual void Remove( int num ); + virtual qboolean Use( int amount ); + virtual qboolean Removable( void ); + virtual void Pickup( Event *ev ); + virtual qboolean Drop( void ); + virtual void RespawnSound( Event *ev ); + virtual void DialogNeeded( Event *ev ); + virtual str GetDialogNeeded( void ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Item::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.WriteSafePointer( owner ); + arc.WriteBoolean( respawnable ); + arc.WriteBoolean( playrespawn ); + arc.WriteFloat( respawntime ); + arc.WriteString( icon_name ); + arc.WriteString( dialog_needed ); + arc.WriteString( item_name ); + arc.WriteInteger( maximum_amount ); + arc.WriteInteger( amount ); + arc.WriteBoolean( amount_override ); + arc.WriteString( itemname ); + } + +inline EXPORT_FROM_DLL void Item::Unarchive + ( + Archiver &arc + ) + { + Trigger::Unarchive( arc ); + + arc.ReadSafePointer( &owner ); + arc.ReadBoolean( &respawnable ); + arc.ReadBoolean( &playrespawn ); + arc.ReadFloat( &respawntime ); + + arc.ReadString( &icon_name ); + icon_index = gi.imageindex( icon_name.c_str() ); + + arc.ReadString( &dialog_needed ); + + arc.ReadString( &item_name ); + item_index = gi.itemindex( item_name.c_str() ); + + arc.ReadInteger( &maximum_amount ); + arc.ReadInteger( &amount ); + arc.ReadBoolean( &amount_override ); + arc.ReadString( &itemname ); + } + +#endif /* item.h */ diff --git a/keys.cpp b/keys.cpp new file mode 100644 index 0000000..729724f --- /dev/null +++ b/keys.cpp @@ -0,0 +1,784 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/keys.cpp $ +// $Revision:: 24 $ +// $Author:: Markd $ +// $Date:: 11/15/98 4:30p $ +// +// Copyright (C) 1998 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/keys.cpp $ +// +// 24 11/15/98 4:30p Markd +// don't play pulse part dialog if jc not present +// +// 23 10/26/98 2:17p Aldie +// Updated evidence.def +// +// 22 10/25/98 1:51a Aldie +// Added passcode2 +// +// 21 10/25/98 12:23a Markd +// Put in pulse parts +// +// 20 10/23/98 10:15p Aldie +// Changed class name of identity card +// +// 19 10/22/98 6:27p Aldie +// Fixed typo with identity card +// +// 18 10/20/98 3:00a Aldie +// Set oldorigin of pulseparts +// +// 17 10/19/98 12:05a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// moved ScubaGear to powerups.cpp +// +// 16 10/17/98 6:58p Aldie +// Made pulseparts work +// +// 15 10/16/98 11:01p Aldie +// Added Hand +// +// 14 10/12/98 3:49p Aldie +// More items +// +// 13 10/07/98 9:06p Aldie +// Added a bunch of inventory items +// +// 12 9/07/98 6:20p Markd +// added inventory_dollar +// +// 11 8/08/98 8:04p Markd +// Added money bag +// +// 10 7/20/98 3:52p Aldie +// Fixed the icons +// +// 9 7/19/98 5:40p Markd +// Added keyring +// +// 8 7/19/98 5:32p Markd +// Added KeyRing +// +// 7 7/19/98 3:43p Markd +// Added a setIcon for keys +// +// 6 6/27/98 8:13p Markd +// Added additional inventory items +// +// 5 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// DESCRIPTION: +// Access cards and keys + +#include "inventoryitem.h" +#include "player.h" + +class EXPORT_FROM_DLL BlueCard : public InventoryItem + { + public: + CLASS_PROTOTYPE( BlueCard ); + BlueCard(); + }; + +CLASS_DECLARATION( InventoryItem, BlueCard, "inventory_bluecard" ) + +ResponseDef BlueCard::Responses[] = + { + { NULL, NULL } + }; + +BlueCard::BlueCard + ( + ) + { + setModel( "card_blu.def" ); + } + + +class EXPORT_FROM_DLL OrangeCard : public InventoryItem + { + public: + CLASS_PROTOTYPE( OrangeCard ); + OrangeCard(); + }; + +CLASS_DECLARATION( InventoryItem, OrangeCard, "inventory_orangecard" ) + +ResponseDef OrangeCard::Responses[] = + { + { NULL, NULL } + }; + +OrangeCard::OrangeCard + ( + ) + { + setModel( "card_orng.def" ); + } + +class EXPORT_FROM_DLL YellowCard : public InventoryItem + { + public: + CLASS_PROTOTYPE( YellowCard ); + YellowCard(); + }; + +CLASS_DECLARATION( InventoryItem, YellowCard, "inventory_yellowcard" ) + +ResponseDef YellowCard::Responses[] = + { + { NULL, NULL } + }; + +YellowCard::YellowCard + ( + ) + { + setModel( "card_yel.def" ); + } + +class EXPORT_FROM_DLL GreenCard : public InventoryItem + { + public: + CLASS_PROTOTYPE( GreenCard ); + GreenCard(); + }; + +CLASS_DECLARATION( InventoryItem, GreenCard, "inventory_greencard" ) + +ResponseDef GreenCard::Responses[] = + { + { NULL, NULL } + }; + +GreenCard::GreenCard + ( + ) + { + setModel( "card_grn.def" ); + } + +class EXPORT_FROM_DLL IdentCard : public InventoryItem + { + public: + CLASS_PROTOTYPE( IdentCard ); + IdentCard(); + }; + +CLASS_DECLARATION( InventoryItem, IdentCard, "inventory_identcard" ) + +ResponseDef IdentCard::Responses[] = + { + { NULL, NULL } + }; + +IdentCard::IdentCard + ( + ) + { + setModel( "identcard.def" ); + } + +class EXPORT_FROM_DLL Cookies : public InventoryItem + { + public: + CLASS_PROTOTYPE( Cookies ); + Cookies(); + }; + +CLASS_DECLARATION( InventoryItem, Cookies, "inventory_cookies" ) + +ResponseDef Cookies::Responses[] = + { + { NULL, NULL } + }; + +Cookies::Cookies + ( + ) + { + setModel( "cookies.def" ); + } + +class EXPORT_FROM_DLL ComLink : public InventoryItem + { + public: + CLASS_PROTOTYPE( ComLink ); + ComLink(); + }; + +CLASS_DECLARATION( InventoryItem, ComLink, "inventory_comlink" ) + +ResponseDef ComLink::Responses[] = + { + { NULL, NULL } + }; + +ComLink::ComLink + ( + ) + { + setModel( "comlink.def" ); + } + +class EXPORT_FROM_DLL Coin : public InventoryItem + { + public: + CLASS_PROTOTYPE( Coin ); + Coin(); + }; + +CLASS_DECLARATION( InventoryItem, Coin, "inventory_coin" ) + +ResponseDef Coin::Responses[] = + { + { NULL, NULL } + }; + +Coin::Coin + ( + ) + { + setModel( "coin.def" ); + } + +class EXPORT_FROM_DLL Code : public InventoryItem + { + public: + CLASS_PROTOTYPE( Code ); + Code(); + }; + +CLASS_DECLARATION( InventoryItem, Code, "inventory_code" ) + +ResponseDef Code::Responses[] = + { + { NULL, NULL } + }; + +Code::Code + ( + ) + { + setModel( "code.def" ); + } + +class EXPORT_FROM_DLL KeyRing : public InventoryItem + { + public: + CLASS_PROTOTYPE( KeyRing ); + KeyRing(); + }; + +CLASS_DECLARATION( InventoryItem, KeyRing, "inventory_keyring" ) + +ResponseDef KeyRing::Responses[] = + { + { NULL, NULL } + }; + +KeyRing::KeyRing + ( + ) + { + setModel( "keys.def" ); + } + +class EXPORT_FROM_DLL MoneyBag : public InventoryItem + { + public: + CLASS_PROTOTYPE( MoneyBag ); + MoneyBag(); + }; + +CLASS_DECLARATION( InventoryItem, MoneyBag, "inventory_moneybag" ) + +ResponseDef MoneyBag::Responses[] = + { + { NULL, NULL } + }; + +MoneyBag::MoneyBag + ( + ) + { + setModel( "moneybag_inv.def" ); + } + +class EXPORT_FROM_DLL Dollar : public InventoryItem + { + public: + CLASS_PROTOTYPE( Dollar ); + Dollar(); + }; + +CLASS_DECLARATION( InventoryItem, Dollar, "inventory_dollar" ) + +ResponseDef Dollar::Responses[] = + { + { NULL, NULL } + }; + +Dollar::Dollar + ( + ) + { + setModel( "dollar.def" ); + } + +class EXPORT_FROM_DLL Evidence : public InventoryItem + { + public: + CLASS_PROTOTYPE( Evidence ); + Evidence(); + }; + +CLASS_DECLARATION( InventoryItem, Evidence, "inventory_evidence" ) + +ResponseDef Evidence::Responses[] = + { + { NULL, NULL } + }; + +Evidence::Evidence + ( + ) + { + setModel( "evidence.def" ); + } + +class EXPORT_FROM_DLL Decoder : public InventoryItem + { + public: + CLASS_PROTOTYPE( Decoder ); + Decoder(); + }; + +CLASS_DECLARATION( InventoryItem, Decoder, "inventory_decoder" ) + +ResponseDef Decoder::Responses[] = + { + { NULL, NULL } + }; + +Decoder::Decoder + ( + ) + { + setModel( "code.def" ); + } + +class EXPORT_FROM_DLL PulsePart1 : public InventoryItem + { + public: + CLASS_PROTOTYPE( PulsePart1 ); + PulsePart1(); + virtual Item *ItemPickup( Entity *other ); + }; + +CLASS_DECLARATION( InventoryItem, PulsePart1, "inventory_pulsepart1" ) + +ResponseDef PulsePart1::Responses[] = + { + { NULL, NULL } + }; + +PulsePart1::PulsePart1 + ( + ) + { + ExecuteThread( "global/pulse_parts.scr::precache", true ); + setModel( "pulsepart1.def" ); + } + +Item *PulsePart1::ItemPickup + ( + Entity *other + ) + + { + if ( !level.no_jc ) + ExecuteThread( "global/pulse_parts.scr::blade_finds_piece1", true ); + gameVars.CreateVariable( "pulse1", 1 ); + if ( other->isClient() ) + { + Player *player; + ScriptVariable *var1,*var2,*var3; + + var1 = gameVars.GetVariable( "pulse1" ); + var2 = gameVars.GetVariable( "pulse2" ); + var3 = gameVars.GetVariable( "pulse3" ); + + if ( var1 && var2 && var3 ) + { + player = ( Player * )other; + player->giveWeapon( "PulseRifle" ); + player->FreeInventoryOfType( "PulsePart2" ); + player->FreeInventoryOfType( "PulsePart3" ); + CancelEventsOfType( EV_Item_DropToFloor ); + CancelEventsOfType( EV_Item_Respawn ); + CancelEventsOfType( EV_FadeOut ); + PostEvent( EV_Remove, 0 ); + return NULL; + } + } + return Item::ItemPickup( other ); + } + +class EXPORT_FROM_DLL PulsePart2 : public InventoryItem + { + public: + CLASS_PROTOTYPE( PulsePart2 ); + PulsePart2(); + virtual Item *ItemPickup( Entity *other ); + }; + +CLASS_DECLARATION( InventoryItem, PulsePart2, "inventory_pulsepart2" ) + +ResponseDef PulsePart2::Responses[] = + { + { NULL, NULL } + }; + +PulsePart2::PulsePart2 + ( + ) + { + ExecuteThread( "global/pulse_parts.scr::precache", true ); + setModel( "pulsepart2.def" ); + } + +Item *PulsePart2::ItemPickup + ( + Entity *other + ) + + { + if ( !level.no_jc ) + ExecuteThread( "global/pulse_parts.scr::blade_finds_piece2", true ); + gameVars.CreateVariable( "pulse2", 1 ); + if ( other->isClient() ) + { + Player *player; + ScriptVariable *var1,*var2,*var3; + + var1 = gameVars.GetVariable( "pulse1" ); + var2 = gameVars.GetVariable( "pulse2" ); + var3 = gameVars.GetVariable( "pulse3" ); + + if ( var1 && var2 && var3 ) + { + player = ( Player * )other; + player->giveWeapon( "PulseRifle" ); + player->FreeInventoryOfType( "PulsePart1" ); + player->FreeInventoryOfType( "PulsePart3" ); + CancelEventsOfType( EV_Item_DropToFloor ); + CancelEventsOfType( EV_Item_Respawn ); + CancelEventsOfType( EV_FadeOut ); + PostEvent( EV_Remove, 0 ); + return NULL; + } + } + return Item::ItemPickup( other ); + } + +class EXPORT_FROM_DLL PulsePart3 : public InventoryItem + { + public: + CLASS_PROTOTYPE( PulsePart3 ); + PulsePart3(); + virtual Item *ItemPickup( Entity *other ); + }; + +CLASS_DECLARATION( InventoryItem, PulsePart3, "inventory_pulsepart3" ) + +ResponseDef PulsePart3::Responses[] = + { + { NULL, NULL } + }; + +PulsePart3::PulsePart3 + ( + ) + { + ExecuteThread( "global/pulse_parts.scr::precache", true ); + setModel( "pulsepart3.def" ); + } + +Item *PulsePart3::ItemPickup + ( + Entity *other + ) + + { + if ( !level.no_jc ) + ExecuteThread( "global/pulse_parts.scr::blade_finds_piece3", true ); + gameVars.CreateVariable( "pulse3", 1 ); + if ( other->isClient() ) + { + Player *player; + ScriptVariable *var1,*var2,*var3; + + var1 = gameVars.GetVariable( "pulse1" ); + var2 = gameVars.GetVariable( "pulse2" ); + var3 = gameVars.GetVariable( "pulse3" ); + + if ( var1 && var2 && var3 ) + { + player = ( Player * )other; + player->giveWeapon( "PulseRifle" ); + player->FreeInventoryOfType( "PulsePart1" ); + player->FreeInventoryOfType( "PulsePart2" ); + CancelEventsOfType( EV_Item_DropToFloor ); + CancelEventsOfType( EV_Item_Respawn ); + CancelEventsOfType( EV_FadeOut ); + PostEvent( EV_Remove, 0 ); + return NULL; + } + } + return Item::ItemPickup( other ); + } + +class EXPORT_FROM_DLL Chemsuit : public InventoryItem + { + public: + CLASS_PROTOTYPE( Chemsuit ); + Chemsuit(); + }; + +CLASS_DECLARATION( InventoryItem, Chemsuit, "inventory_chembiosuit" ) + +ResponseDef Chemsuit::Responses[] = + { + { NULL, NULL } + }; + +Chemsuit::Chemsuit + ( + ) + { + setModel( "chemsuit.def" ); + } + +class EXPORT_FROM_DLL Blueprints : public InventoryItem + { + public: + CLASS_PROTOTYPE( Blueprints ); + Blueprints(); + }; + +CLASS_DECLARATION( InventoryItem, Blueprints, "inventory_blueprints" ) + +ResponseDef Blueprints::Responses[] = + { + { NULL, NULL } + }; + +Blueprints::Blueprints + ( + ) + { + setModel( "blueprints.def" ); + } + +class EXPORT_FROM_DLL U4Sample : public InventoryItem + { + public: + CLASS_PROTOTYPE( U4Sample ); + U4Sample(); + }; + +CLASS_DECLARATION( InventoryItem, U4Sample, "inventory_u4sample" ) + +ResponseDef U4Sample::Responses[] = + { + { NULL, NULL } + }; + +U4Sample::U4Sample + ( + ) + { + setModel( "u4_sample.def" ); + } + +class EXPORT_FROM_DLL Envelope : public InventoryItem + { + public: + CLASS_PROTOTYPE( Envelope ); + Envelope(); + }; + +CLASS_DECLARATION( InventoryItem, Envelope, "inventory_envelope" ) + +ResponseDef Envelope::Responses[] = + { + { NULL, NULL } + }; + +Envelope::Envelope + ( + ) + { + setModel( "envelope.def" ); + } + +class EXPORT_FROM_DLL CandyBar : public InventoryItem + { + public: + CLASS_PROTOTYPE( CandyBar ); + CandyBar(); + }; + +CLASS_DECLARATION( InventoryItem, CandyBar, "inventory_candybar" ) + +ResponseDef CandyBar::Responses[] = + { + { NULL, NULL } + }; + +CandyBar::CandyBar + ( + ) + { + setModel( "CandyBar.def" ); + } + +class EXPORT_FROM_DLL PassCode : public InventoryItem + { + public: + CLASS_PROTOTYPE( PassCode ); + PassCode(); + }; + +CLASS_DECLARATION( InventoryItem, PassCode, "inventory_passcode" ) + +ResponseDef PassCode::Responses[] = + { + { NULL, NULL } + }; + +PassCode::PassCode + ( + ) + { + setModel( "password.def" ); + } + +class EXPORT_FROM_DLL PassCode2 : public InventoryItem + { + public: + CLASS_PROTOTYPE( PassCode2 ); + PassCode2(); + }; + +CLASS_DECLARATION( InventoryItem, PassCode2, "inventory_passcode2" ) + +ResponseDef PassCode2::Responses[] = + { + { NULL, NULL } + }; + +PassCode2::PassCode2 + ( + ) + { + setModel( "password2.def" ); + } + +class EXPORT_FROM_DLL Hand : public InventoryItem + { + public: + CLASS_PROTOTYPE( Hand ); + Hand(); + }; + +CLASS_DECLARATION( InventoryItem, Hand, "inventory_hand" ) + +ResponseDef Hand::Responses[] = + { + { NULL, NULL } + }; + +Hand::Hand + ( + ) + { + setModel( "hand.def" ); + } + + + +/*SINED inventory_genericpulsepart (.3 1 .3) (-8 -8 0) (8 8 16) NOT_SOLID NOT_DAMAGABLE +Pulse Weapon Part - Will always spawn as the next piece that the user needs +to complete the weapon. When the user picks up 3 of these, they will get the PulseRifle +*/ + +class EXPORT_FROM_DLL GenericPulsePart : public InventoryItem + { + + public: + CLASS_PROTOTYPE( GenericPulsePart ); + GenericPulsePart(); + }; + +CLASS_DECLARATION( InventoryItem, GenericPulsePart, "inventory_genericpulsepart" ) + +ResponseDef GenericPulsePart::Responses[] = + { + { NULL, NULL } + }; + +GenericPulsePart::GenericPulsePart + ( + ) + { + ScriptVariable *var1,*var2,*var3; + + var1 = gameVars.GetVariable( "pulse1" ); + var2 = gameVars.GetVariable( "pulse2" ); + var3 = gameVars.GetVariable( "pulse3" ); + + // Based on what pulserifle pieces that have already been found, + // spawn the correct model in the game. + + if ( !var1 ) + { + PulsePart1 *part1; + + part1 = new PulsePart1; + part1->setModel( "pulsepart1.def" ); + part1->setOrigin( origin ); + part1->worldorigin.copyTo(part1->edict->s.old_origin); + } + else if ( !var2 ) + { + PulsePart2 *part2; + + part2 = new PulsePart2; + part2->setModel( "pulsepart2.def" ); + part2->setOrigin( origin ); + part2->worldorigin.copyTo(part2->edict->s.old_origin); + } + else if ( !var3 ) + { + PulsePart3 *part3; + + part3 = new PulsePart3; + part3->setModel( "pulsepart3.def" ); + part3->setOrigin( origin ); + part3->worldorigin.copyTo(part3->edict->s.old_origin); + } + + PostEvent( EV_Remove, 0 ); + } diff --git a/launcher.cpp b/launcher.cpp new file mode 100644 index 0000000..52c6616 --- /dev/null +++ b/launcher.cpp @@ -0,0 +1,261 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/launcher.cpp $ +// $Revision:: 12 $ +// $Author:: Markd $ +// $Date:: 10/24/98 12:42a $ +// +// Copyright (C) 1998 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/launcher.cpp $ +// +// 12 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 11 10/13/98 5:04p Markd +// Fixed launcher sounds +// +// 10 10/13/98 12:55a Markd +// Fixed launcher that uses info_notnulls +// +// 9 10/01/98 4:01p Markd +// Added Archive and Unarchive functions +// +// 8 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 7 7/06/98 4:07p Aldie +// Removed some dependencies on some headers. +// +// 6 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 +// +// 5 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 4 5/20/98 11:11a Markd +// removed char * dependency +// +// 3 5/06/98 3:14p Aldie +// Added targeting of the launcher. +// +// 2 5/05/98 8:35p Aldie +// First version of Generic launcher +// +// DESCRIPTION: Generic Projectile Launcher +// + +#include "g_local.h" +#include "misc.h" +#include "specialfx.h" + +/**************************************************************************** +/*SINED func_launcher (0 1 0) ? +Creates an object that launches projectiles. + +"projectile" Sets the projectile that you want to launch (Examples: Rocket, Spear, etc...) +"launchsound" Sets the launching sound. +/*****************************************************************************/ + +class EXPORT_FROM_DLL GenericLauncher : public Entity + { + private: + Vector launchAngle; + float x_var; + float y_var; + float z_var; + str projectile; + str launchsound; + + public: + CLASS_PROTOTYPE( GenericLauncher ); + GenericLauncher( ); + void Launch(Event *ev); + void LaunchProjectile(Event *ev); + void Setdir(Event *ev); + void SetXdir(Event *ev); + void SetYdir(Event *ev); + void SetZdir(Event *ev); + void SetXvar(Event *ev); + void SetYvar(Event *ev); + void SetZvar(Event *ev); + void SetupDir(Event *ev); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +EXPORT_FROM_DLL void GenericLauncher::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteVector( launchAngle ); + arc.WriteFloat( x_var ); + arc.WriteFloat( y_var ); + arc.WriteFloat( z_var ); + arc.WriteString( projectile ); + arc.WriteString( launchsound ); + } + +EXPORT_FROM_DLL void GenericLauncher::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadVector( &launchAngle ); + arc.ReadFloat( &x_var ); + arc.ReadFloat( &y_var ); + arc.ReadFloat( &z_var ); + arc.ReadString( &projectile ); + arc.ReadString( &launchsound ); + } + +CLASS_DECLARATION( Entity, GenericLauncher, "func_launcher" ); + +Event EV_GenericLauncher_Launch( "launch" ); +Event EV_GenericLauncher_Setdir( "setdir" ); +Event EV_GenericLauncher_SetXdir( "setXdir" ); +Event EV_GenericLauncher_SetYdir( "setYdir" ); +Event EV_GenericLauncher_SetZdir( "setZdir" ); +Event EV_GenericLauncher_SetXvar( "setXvar" ); +Event EV_GenericLauncher_SetYvar( "setYvar" ); +Event EV_GenericLauncher_SetZvar( "setZvar" ); +Event EV_GenericLauncher_SetupDir( "setupdir" ); + +ResponseDef GenericLauncher::Responses[] = + { + { &EV_Activate, ( Response )GenericLauncher::LaunchProjectile }, + { &EV_GenericLauncher_Launch, ( Response )GenericLauncher::Launch }, + { &EV_GenericLauncher_SetXdir, ( Response )GenericLauncher::SetXdir }, + { &EV_GenericLauncher_SetYdir, ( Response )GenericLauncher::SetYdir }, + { &EV_GenericLauncher_SetZdir, ( Response )GenericLauncher::SetZdir }, + { &EV_GenericLauncher_SetXvar, ( Response )GenericLauncher::SetXvar }, + { &EV_GenericLauncher_SetYvar, ( Response )GenericLauncher::SetYvar }, + { &EV_GenericLauncher_SetZvar, ( Response )GenericLauncher::SetZvar }, + { &EV_GenericLauncher_SetupDir, ( Response )GenericLauncher::SetupDir }, + { NULL, NULL } + }; + +GenericLauncher::GenericLauncher( ) + { + showModel(); + setSolidType( SOLID_BSP ); + projectile = G_GetStringArg( "projectile" ); + launchsound = G_GetStringArg( "launchsound" ); + launchAngle = G_GetMovedir(); + + x_var = 0; + y_var = 0; + z_var = 0; + + PostEvent( EV_GenericLauncher_SetupDir, 0 ); + } + +void GenericLauncher::SetupDir( Event *ev ) + { + int num; + const char *target; + Entity *dest; + + target = Target(); + if (target) + { + if (num = G_FindTarget(0, target)) + { + dest = G_GetEntity( num ); + assert( dest ); + VectorSubtract(dest->worldorigin, origin, launchAngle); + } + } + } + +void GenericLauncher::SetXdir( Event *ev ) + { + launchAngle.x = ev->GetFloat( 1 ); + } + +void GenericLauncher::SetYdir( Event *ev ) + { + launchAngle.y = ev->GetFloat(1); + } + +void GenericLauncher::SetZdir( Event *ev ) + { + launchAngle.z = ev->GetFloat(1); + } + +void GenericLauncher::Setdir(Event *ev) + { + launchAngle = ev->GetVector(1); + } + +void GenericLauncher::SetXvar( Event *ev ) + { + x_var = ev->GetFloat(1); + } + +void GenericLauncher::SetYvar( Event *ev ) + { + y_var = ev->GetFloat(1); + } + +void GenericLauncher::SetZvar( Event *ev ) + { + z_var = ev->GetFloat(1); + } + +void GenericLauncher::LaunchProjectile(Event *ev) + { + Vector ang; + ClassDef *cls; + Projectile *obj; + + ang.x = launchAngle.x + G_CRandom(x_var); + ang.y = launchAngle.y + G_CRandom(y_var); + ang.z = launchAngle.z + G_CRandom(z_var); + + cls = getClass( projectile.c_str() ); + if (cls) + { + if ( launchsound.length() ) + { + this->sound( launchsound, 1, CHAN_WEAPON, ATTN_NORM ); + } + obj = (Projectile *)cls->newInstance(); + obj->Setup( this, worldorigin, ang.normalize()); + } + } + +void GenericLauncher::Launch(Event *ev) + { + Vector ang; + ClassDef *cls; + Projectile *obj; + + ang.x = launchAngle.x + G_CRandom(x_var); + ang.y = launchAngle.y + G_CRandom(y_var); + ang.z = launchAngle.z + G_CRandom(z_var); + + cls = getClass( ev->GetString(1)); + if (cls) + { + if ( launchsound.length() ) + { + this->sound( launchsound, 1, CHAN_WEAPON, ATTN_NORM ); + } + obj = (Projectile *)cls->newInstance(); + obj->Setup( this, worldorigin, ang.normalize()); + } + } + diff --git a/lensflare.cpp b/lensflare.cpp new file mode 100644 index 0000000..6b35875 --- /dev/null +++ b/lensflare.cpp @@ -0,0 +1,197 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/lensflare.cpp $ +// $Revision:: 17 $ +// $Author:: Jimdose $ +// $Date:: 10/19/98 12:07a $ +// +// Copyright (C) 1998 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/lensflare.cpp $ +// +// 17 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 16 10/09/98 8:58p Aldie +// Lightstyle on lensflare from Lights +// +// 15 10/09/98 5:23p Aldie +// Added some commands for lightstyle. +// +// 14 9/25/98 2:59p Aldie +// More lensflare stuff. Added lightstyles +// +// 13 5/03/98 4:34p Jimdose +// Changed Vector class +// +// 12 3/30/98 11:39p Markd +// Added modelIndex stuff +// +// 11 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 10 3/18/98 2:05p Aldie +// Changed the filename of the sprite model extension to .spr +// +// 9 3/14/98 4:40p Aldie +// Added scale to lensflare. +// +// 8 3/13/98 7:25p Aldie +// Expanded lensflare to have color, dlights, and radius. +// +// 7 3/12/98 5:37p Markd +// Changed model file for lensflare from sp3 file to def file +// +// 6 3/12/98 3:02p Aldie +// Cleaned up lensflare. +// +// 5 3/12/98 2:42p Aldie +// Random starting positions for lensflare rotation offset. +// +// 4 3/10/98 7:45p Aldie +// +// 3 3/07/98 5:16p Aldie +// Added new sprite. +// +// 2 3/06/98 7:16p Aldie +// First try at lensflare. +// +// DESCRIPTION:LensFlare effect +// +#include "g_local.h" +#include "entity.h" +#include "lensflare.h" +#include "light.h" + +CLASS_DECLARATION( Trigger, LensFlare, "lensflare" ); + +/*****************************************************************************/ +/*SINED lensflare (0 1 0) (-8 -8 -8) (8 8 8) + +"color" (r g b) 3 values between 0 and 1.0 (Default is 1.0 1.0 1.0) +"light" If set,make the flare emit a dynamic light. (Default is not set) +"radius" Radius of the dynamic light (Default is 200) +"scale" Factor to scale the lensflare (Default is 0.5) +"sprite" Sprite model to use for the flare (Default is sprites/glow.spr") +"lightstyle" Lightstyle of the lensflare (Default is none) +/*****************************************************************************/ + +Event EV_LensFlare_Activate( "activate" ); +Event EV_LensFlare_Deactivate( "deactivate" ); +Event EV_LensFlare_Lightstyle( "lightstyle" ); +Event EV_LensFlare_SetLightstyle( "setlightstyle" ); + +ResponseDef LensFlare::Responses[] = + { + { &EV_LensFlare_Activate, ( Response )LensFlare::Activate }, + { &EV_LensFlare_Deactivate, ( Response )LensFlare::Deactivate }, + { &EV_LensFlare_Lightstyle, ( Response )LensFlare::Lightstyle }, + { &EV_LensFlare_SetLightstyle, ( Response )LensFlare::SetLightstyle }, + { NULL, NULL } + }; + +LensFlare::LensFlare( void ) + { + Vector color; + Vector def; + int dlight,radius; + float scale; + int lightstyle; + + color = G_GetVectorArg("color",Vector(1,1,1)); + dlight = G_GetIntArg("light",0); + radius = G_GetIntArg("radius",0); + scale = G_GetFloatArg("scale", 0.5f); + model = G_GetSpawnArg("sprite", "sprites/glow.spr" ); + lightstyle = G_GetIntArg("lightstyle", 255 ); + + setModel( model ); + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_NOT ); + + edict->s.renderfx |= (RF_LENSFLARE); + + if (dlight) + edict->s.renderfx |= (RF_DLIGHT); + + edict->s.angles[ROLL] = rand() % 360; + edict->s.alpha = 1.0f; + edict->s.color_r = color.x; + edict->s.color_g = color.y; + edict->s.color_b = color.z; + edict->s.radius = radius; + edict->s.scale = scale; + edict->s.skinnum = lightstyle; + + PostEvent( EV_LensFlare_SetLightstyle, 0 ); + } + +void LensFlare::SetLightstyle + ( + Event *ev + ) + + { + int num; + const char *name; + Entity *ent; + + name = Target(); + if ( name && strcmp( name, "" ) ) + { + num = 0; + do + { + num = G_FindTarget( num, name ); + if ( !num ) + { + break; + } + + ent = G_GetEntity( num ); + assert( ent ); + if ( ent->isSubclassOf( Light ) ) + { + Light *light; + + light = ( Light * )ent; + edict->s.skinnum = light->GetStyle(); + } + } + while ( 1 ); + } + } + +void LensFlare::Activate + ( + Event *ev + ) + + { + setSolidType( SOLID_BSP ); + showModel(); + } + +void LensFlare::Deactivate + ( + Event *ev + ) + + { + hideModel(); + } + +void LensFlare::Lightstyle + ( + Event *ev + ) + + { + edict->s.skinnum = ev->GetInteger( 1 ); + } diff --git a/lensflare.h b/lensflare.h new file mode 100644 index 0000000..06d281a --- /dev/null +++ b/lensflare.h @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/lensflare.h $ +// $Revision:: 9 $ +// $Author:: Aldie $ +// $Date:: 10/09/98 9:02p $ +// +// Copyright (C) 1998 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/lensflare.h $ +// +// 9 10/09/98 9:02p Aldie +// Lightstyle on lensflare from Lights +// +// 8 10/09/98 5:23p Aldie +// Added some new commands (lightstyle) +// +// 7 9/28/98 9:12p Markd +// Put in archive and unarchive functions +// +// 6 9/25/98 3:00p Aldie +// Prototypes +// +// 5 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 4 3/13/98 7:25p Aldie +// Updated lensflare to have dlights, color, and radius. +// +// 3 3/10/98 7:45p Aldie +// +// 2 3/06/98 7:16p Aldie +// First try at LensFlare. +// +// DESCRIPTION:LensFlare effect +// + +#ifndef __LENSFLARE_H__ +#define __LENSFLARE_H__ + +#include "g_local.h" +#include "entity.h" + + +class EXPORT_FROM_DLL LensFlare : public Entity + { + public: + CLASS_PROTOTYPE( LensFlare ); + LensFlare( void ); + + void Activate( Event *ev ); + void Deactivate( Event *ev ); + void Lightstyle( Event *ev ); + void SetLightstyle( Event *ev ); + }; + + +#endif /* lensflare.h */ diff --git a/license.doc b/license.doc new file mode 100644 index 0000000..9820dfd --- /dev/null +++ b/license.doc @@ -0,0 +1,98 @@ +{\rtf1\ansi\ansicpg1252\uc1 \deff0\deflang1033\deflangfe1033{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{\nowidctlpar\adjustright \snext0 Normal;}{\s1\qc\keepn\nowidctlpar\widctlpar\tx0\tqc\tx4680\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright \b\fs22 \sbasedon0 \snext0 heading 1;}{\*\cs10 \additive Default Paragraph Font;}{\*\cs15 \additive footnote reference;}{\s16\qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright \fs22 \sbasedon0 \snext16 Body Text 2;}{\s17\qc\nowidctlpar\widctlpar\adjustright \b\fs22 \sbasedon0 \snext17 Title;}}{\info{\title ATTENTION}{\author Harry Miller}{\operator Mark Dochtermann}{\creatim\yr1998\mo10\dy18\hr18\min36}{\revtim\yr1998\mo10\dy18\hr18\min36}{\printim\yr1998\mo10\dy15\hr16\min36}{\version2}{\edmins0}{\nofpages7}{\nofwords3117}{\nofchars17768}{\*\company Ritual Entertainment}{\nofcharsws21820}{\vern71}}\margl1440\margr1440\margb288 \widowctrl\ftnbj\aenddoc\aftnnar\notabind\wraptrsp\transmf\truncatefontheight\subfontbysize\sprsbsp\wpjst\lytprtmet\hyphcaps0\viewkind4\viewscale100 \fet0\sectd \linex0\headery1440\footery288\sectdefaultcl {\footer \pard\plain \sl-240\slmult0\nowidctlpar\adjustright { +\par +\par }\pard \nowidctlpar\tx0\tqc\tx4680\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {Software License Agreement\tab }{\field{\*\fldinst {PAGE }}{\fldrslt {\lang1024 6}}}{ +\par }{\fs14 08656 00001 CORP 209544.3}{ +\par }}{\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang{\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}\pard\plain \s17\qc\nowidctlpar\widctlpar\adjustright \b\fs22 {ATTENTION +\par }\pard\plain \qj\fi720\nowidctlpar\widctlpar\adjustright {\fs22 +\par Here is a brief summary of certain of the terms and conditions in our Limited Software Warranty and License Agreement. You must read the full text of the agreement before using this product so that You understand all of the terms and conditions of our agreement regarding this product. +\par }\pard \qj\nowidctlpar\widctlpar\adjustright {\fs22 +\par WHAT IS OKAY FOR YOU TO DO: +\par +\par }\pard \qj\fi-720\li720\nowidctlpar\widctlpar\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 $\tab Playing and enjoying the shareware and/or registered/full retail version of the game, demo and/or level editor. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi-720\li720\nowidctlpar\widctlpar\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 $\tab Setting up a shareware, demo, and/or registered/full retail version based server on a not-for-profit and non-commercially exploited basis. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi-720\li720\nowidctlpar\widctlpar\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 $\tab Playing the shareware, demo, and/or registered/full retail version of the game and/or setting up a registered/full retail version of the game using user-developed levels on a not-for-profit and non-commercially exploited bases. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par WHAT IS NOT OKAY FOR YOU TO DO: +\par +\par }\pard \qj\fi-720\li720\nowidctlpar\widctlpar\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 $\tab You cannot sell or otherwise commercially exploit or utilize the shareware, registered/full retail version, demo or level editor in any way or sell user-developed levels and/or tools. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi-720\li720\nowidctlpar\widctlpar\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 $\tab Commercially exploiting or otherwise utilizing any copyrighted and/or trademarked property of Ritual Entertainment or any other party contained in or associated with the shareware, demo, level editor or registered/full retail version, demo, single player game and/or level editor, including without limitation game names, logos, graphics, characters, etc. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par \sect }\sectd \linex0\headery1440\footery288\sectdefaultcl \pard\plain \s1\qc\keepn\nowidctlpar\widctlpar\tqc\tx4680\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\outlinelevel0\adjustright \b\fs22 {LIMITED SOFTWARE WARRANTY AND LICENSE AGREEMENT +\par }\pard\plain \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard\plain \s16\qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright \fs22 {This LIMITED SOFTWARE WARRANTY AND LICENSE AGREEMENT (this \ldblquote Agreement\rdblquote ), including the Limited Warranty and other special provisions, is a legal agreement between You (either an individual or an entity) and Ritual Entertainment, Inc., a Texas corporation (the \ldblquote Owner\rdblquote ), regarding this software product and the materials contained therein and related thereto. Your act of downloading, installing and/or otherwise using the software constitutes Your agreement to be bound by the terms of this Agreement. If You do not agree to the terms of this Agreement cease loading or installing this product and, if applicable, promptly return the software packaging and the accompanying materials (including any hardware, manuals, other written materials and packaging) to the place You obtained them, along with Your receipt, for a full refund. +\par }\pard\plain \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Grant of Limited Non-Exclusive License}{\fs22 . (A) In the event that You are currently encountering this Agreement in conjunction with or as a result of Your downloading or otherwise installing the SOFTWARE (as defined herein), this Agreement permits You to use one (1) copy of the software program(s), in executable or object code form only, contained in the registered/full retail version of the game program entitled SiN, as contained in the disk(s) making up all or part of the registered/full retail software product, including without limitation the data files, images, level editors, death match levels, charters and screen displays (the \ldblquote SOFTWARE\rdblquote ), included in this download for Your personal use on a single home or portable computer. This license also permits You to use the SOFTWARE\rquote s level editor to create new game levels, weapons, characters and/or entities for non-commercial personal use, and to non-commercially distribute such game levels, weapons, characters, and/or entities to personal acquaintances for non-commercial use via the Internet pursuant to subparagraph (C) below. This license does NOT authorize You to sell, lease or otherwise profit from or commercially distribute the SOFTWARE (see \ldblquote Restrictions\rdblquote below). The SOFTWARE is in \ldblquote use\rdblquote on a computer when it is loaded into temporary memory (i.e., RAM) or installed into the permanent memory (e.g., hard disk, CD-ROM, or other storage device) of that computer. Installation on a network server is strictly prohibited, except under a special and separate network license obtained from Owner; this Agreement shall not serve as such necessary special network license. Installation on a network server constitutes \ldblquote use\rdblquote that must comply with the terms of this Agreement. This license is not a sale of the original SOFTWARE or any copy thereof. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 (B)\tab In the event that You are currently encountering this Agreement in conjunction with or as a result of Your downloading or otherwise installing a DEMO PRODUCT (as defined herein), this Agreement permits You to use the software program(s) included in and received solely as a result of the loaded or otherwise installed version of the demo, free standing level editor and/or shareware version of the game entitled Sin, as the case may be, for Your personal use (each such item referred to herein, individually as a \ldblquote DEMO PRODUCT\rdblquote and collectively as the \ldblquote DEMO PRODUCTS\rdblquote ). This license also permits You to use the level editor, as contained in and received solely as a result of the downloading or other installation or utilization of such DEMO PRODUCT, to create new game levels, weapons, characters and/or entities for non-commercial personal use, and to non-commercially distribute such games levels, weapons, characters, and/or entities to personal acquaintances for non-commercial use via the Internet pursuant to subparagraph (C) below. This license does NOT authorize You to sell, lease or otherwise profit from or commercially exploit a DEMO PRODUCT (see \ldblquote Restrictions\rdblquote below). A DEMO PRODUCT is in \ldblquote use\rdblquote on a computer when it is loaded into temporary memory (i.e., RAM) or installed into the permanent memory (e.g., hard disk, CD-ROM, or other storage device) of that computer or accessed via the Internet. Installation of any DEMO PRODUCTS on a network server for profit or other commercial benefit to You or any other person is strictly prohibited, except under a special and separate network license obtained from Owner; this Agreement shall not serve as such necessary special network license. Installation on a network server constitutes \ldblquote use\rdblquote that must comply with the terms of this Agreement. This license is not a sale of the original DEMO PRODUCTS or any copy thereof. +\par \sect }\sectd \sbknone\linex0\headery1440\footery288\sectdefaultcl \pard\plain \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 (C)\tab Subject to the terms and conditions of this Agreement, Owner grants to You the non-exclusive and limited right to create additional levels (the \ldblquote USER LEVELS\rdblquote ) which are operable with the SOFTWARE or DEMO PRODUCT. You may include within the USER LEVELS certain textures and other images (the \ldblquote Owner Images\rdblquote ) from the SOFTWARE or DEMO PRODUCT, as the case may be. You agree that the USER LEVELS will not be shipped, transferred or exported into any country in violation of the U.S. Export Administration Act (or any other law governing such matters) by You or anyone at Your direction and that You will not utilize and will not authorize anyone to utilize, in any other manner, the USER LEVELS in violation of any applicable law. The USER LEVELS may not be downloaded or otherwise exported or re-exported into (or to a national or resident of) any country to which the United States has embargoed goods or to anyone or unto any country who/which are prohibited by applicable law, from receiving such property. You shall not rent, sell, lease, lend, offer on a pay-per-play basis or otherwise commercially exploit or commercially distribute any USER LEVELS. You are only permitted to distribute for free, without any cost or charge, the USER LEVELS to other end-users. As noted below, in the event You commercially distribute or commercially exploit any USER LEVELS or commit any other breach of this Agreement, Your license, as granted in this Agreement, shall automatically terminate, without notice. IN ADDITION TO YOUR INDEMNIFICATION OBLIGATIONS AS SET FORTH BELOW, YOU HEREBY AGREE TO INDEMNIFY, DEFEND AND HOLD HARMLESS OWNER AND OWNER\rquote S RESPECTIVE OFFICERS, EMPLOYEES, DIRECTORS, AGENTS, LICENSEES (EXCLUDING YOU), SUBLICENSEES (EXCLUDING YOU), SUCCESSORS AND ASSIGNS FROM AND AGAINST ANY AND ALL DIRECT AND/OR INDIRECT LOSSES, LAWSUITS, DAMAGES, CAUSES OF ACTIONS AND CLAIMS RELATING TO AND/OR ARISING FROM THE USER LEVELS AND/OR THE DISTRIBUTION OR OTHER USE OF ANY USER LEVELS. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Intellectual Property Ownership}{\fs22 . Owner retains all right, title and interest to the SOFTWARE and/or DEMO PRODUCTS and any accompanying instructions or other documentation (collectively, the \ldblquote ACCOMPANYING MATERIALS\rdblquote ), including, but not limited to, all copyrights, trademarks, trade secrets, trade names, proprietary rights, patents, titles, computer codes, audiovisual effects, themes, characters, character names, stories, dialog, settings, artwork, sounds effects, musical works, and moral rights. The SOFTWARE, DEMO PRODUCTS and ACCOMPANYING MATERIALS are protected by United States copyright law and applicable copyright laws and treaties throughout the World. All rights are reserved. The SOFTWARE and ACCOMPANYING MATERIALS may not be copied or reproduced in any manner or medium, in whole or in part, without prior written consent from Owner except as specifically provided under \ldblquote Grant of Limited Non-Exclusive License\rdblquote above. Any persons copying or reproducing all or any portion of the SOFTWARE or ACCOMPANYING MATERIALS, except as specifically provided under \ldblquote Grant of Limited Non-Exclusive License\rdblquote above, in any manner or medium, will be willfully violating the copyright laws and may be subject to civil or criminal penalties. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul SOFTWARE Backup or Archiving}{\fs22 . After You install the SOFTWARE into the permanent memory of a computer, You may keep and use the original disk(s) and/or CD-ROM (the \ldblquote Storage Media\rdblquote ) only for backup or archival purposes. You are expressly prohibited from transmitting the SOFTWARE or ACCOMPANYING MATERIALS electronically or otherwise over the Internet or through any other media or to any other party. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par \sect }\sectd \sbknone\linex0\headery1440\footery288\sectdefaultcl \pard\plain \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Restrictions}{\fs22 . Other than as provided specifically in this Agreement, You are not permitted to copy or otherwise reproduce the SOFTWARE or ACCOMPANYING MATERIALS; modify or prepare derivative copies based on the SOFTWARE or ACCOMPANYING MATERIALS; distribute copies of the SOFTWARE or ACCOMPANYING MATERIALS by sale or other transfer of ownership; rent, lease, or lend the SOFTWARE, DEMO PRODUCTS or ACCOMPANYING MATERIALS; or to display the SOFTWARE or ACCOMPANYING MATERIALS publicly. You are expressly prohibited from selling, leasing, charging for access to, or otherwise using for profit or commercially exploiting any USER LEVELS, level packs, add-on packs, sequels, characters or other components or items created by utilization of the SOFTWARE\rquote s or DEMO PRODUCT\rquote s level editor and/or based upon or related to the SOFTWARE, DEMO PRODUCT or ACCOMPANYING MATERIALS. YOU ARE NOT PERMITTED TO REVERSE ENGINEER, DECOMPILE OR DISASSEMBLE THE SOFTWARE OR ANY DEMO PRODUCT IN ANY WAY. Any copying or distribution of the SOFTWARE or ACCOMPANYING MATERIALS not specifically allowed in this Agreement is a violation of this Agreement. You may create USER LEVELS for the SOFTWARE or any DEMO PRODUCT; provided, however, that such USER LEVELS are created in accordance with the non-exclusive license set forth above and no USER LEVELS may be sold or otherwise commercially exploited, whether by You or by any other person or entity, but You may exchange USER LEVELS at no charge with other end-users. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Limited Warranty and Warranty Disclaimers}{\fs22 . +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 LIMITED WARRANTY. Owner warrants that the original Storage Media holding the SOFTWARE is free from defects in materials and workmanship under normal use and service for a period of ninety (90) days from the date that You downloaded the SOFTWARE. If for any reason You find defects in the Storage Media, or if You are unable to install the SOFTWARE on Your home or portable computer, You may return the SOFTWARE, ACCOMPANYING MATERIALS and all packaging related thereto to the place you purchased such products for a full refund or replacement thereof. This limited warranty does not apply if You have damaged the SOFTWARE by accident or abuse. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 CUSTOMER\rquote S REMEDY. Your exclusive remedies, and the entire liability of Owner, shall be replacement of the SOFTWARE or DEMO PRODUCT, as the case may be. By downloading, installing and/or otherwise using the SOFTWARE, DEMO PRODUCT or ACCOMPANYING MATERIALS, as the case may be, You hereby agree to waive any and all other remedies You may have at law or in equity. Any such remedies You may not waive as a matter of public policy, You hereby assign, or shall assign as they become available, over to Owner. +\par +\par NOTWITHSTANDING ANYTHING TO THE CONTRARY IN THIS AGREEMENT, WITH RESPECT TO THE DEMO PRODUCTS, OWNER MAKES NO REPRESENTATIONS OR WARRANTIES (EXPRESS, IMPLIED OR OTHERWISE), INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND/OR NON-INFRINGEMENT. THE DEMO PRODUCTS ARE PROVIDED GRATUITOUSLY TO YOU \ldblquote AS IS\rdblquote AND YOU TAKE, INSTALL, LOAD OR OTHERWISE USE SUCH DEMO PRODUCTS AT YOUR OWN RISK. OWNER HAS NO LIABILITIES ARISING FROM OR RELATED TO YOUR USE OF ANY DEMO PRODUCTS. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par \sect }\sectd \sbknone\linex0\headery1440\footery288\sectdefaultcl \pard\plain \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul WARRANTY DISCLAIMERS}{\fs22 . EXCEPT FOR THE EXPRESS LIMITED WARRANTY SET FORTH ABOVE, OWNER MAKES NO WARRANTIES, EXPRESS OR IMPLIED, ORAL OR WRITTEN, CONCERNING THE PRODUCTS REFERENCED HEREIN OR ANY COMPONENT PART THEREOF. ANY IMPLIED WARRANTIES THAT MAY BE IMPOSED BY APPLICABLE LAW ARE LIMITED IN ALL RESPECTS TO THE FULLEST EXTENT ALLOWED AND TO THE DURATION OF THE LIMITED WARRANTY. OWNER DOES NOT REPRESENT, WARRANT OR GUARANTEE THE QUALITY OR THE PERFORMANCE OF THE SOFTWARE, DEMO PRODUCTS OR ACCOMPANYING MATERIALS OTHER THAN AS SET FORTH IN THE ABOVE LIMITED WARRANTY. OWNER ALSO DOES NOT REPRESENT, WARRANT OR GUARANTEE THAT THE SOFTWARE, DEMO PRODUCTS OR ACCOMPANYING MATERIALS CAPABILITIES WILL MEET YOUR NEEDS OR THAT THE SOFTWARE OR DEMO PRODUCTS WILL CONTINUOUSLY OPERATE, BE ERROR FREE, OR THAT PROBLEMS WILL BE CORRECTED. OWNER DOES NOT REPRESENT THAT THE SOFTWARE OR DEMO PRODUCTS WILL OPERATE IN A MULTI-USER ENVIRONMENT. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY OWNER, ITS DEALERS, DISTRIBUTORS, DIRECTORS, OFFICERS, EMPLOYEES, AGENTS, CONTRACTORS OR AFFILIATES SHALL CREATE ANY OTHER WARRANTY OR EXTEND OR EXPAND THE SCOPE OF THIS WARRANTY. YOU MAY NOT RELY ON ANY SUCH INFORMATION OR ADVICE. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 SOME STATES DO NOT ALLOW LIMITATIONS ON HOW LONG AN IMPLIED WARRANTY LASTS, SO THE ABOVE LIMITATION MAY NOT APPLY TO YOU. THIS LIMITED WARRANTY GIVES YOU SPECIFIC LEGAL RIGHTS AND YOU MAY ALSO HAVE OTHER RIGHTS WHICH MAY VARY FROM STATE TO STATE. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }{\fs22\ul LIABILITY LIMITATION}{\fs22 . To the maximum extent permitted by applicable law, and regardless of whether any remedy set forth herein fails of its essential purpose, +\par +\par IN NO EVENT WILL OWNER, ITS DIRECTORS, OFFICERS, EMPLOYEES, AGENTS, LICENSES (EXCLUDING YOU), SUBLICENSEES (EXCLUDING YOU) OR AFFILIATES NOR ANYONE ELSE INVOLVED IN THE DEVELOPMENT, MANUFACTURE OR DISTRIBUTION OF THE SOFTWARE, THE DEMO PRODUCTS, THE ACCOMPANYING MATERIALS OR THE USER LEVELS (OTHER THAN YOU) BE LIABLE FOR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, DIRECT OR INDIRECT; INCIDENTAL; OR CONSEQUENTIAL DAMAGES FOR PERSONAL INJURY, PERSONAL PROPERTY, LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, LOSS OF TEXT OR DATA STORED IN OR USED WITH SUCH PRODUCT INCLUDING THE COST OF RECOVERING OR REPRODUCING THE TEXT OR DATA, OR ANY OTHER PECUNIARY LOSS, ARISING FROM OR OUT OF THE USE OR INABILITY TO USE THIS SOFTWARE, THE DEMO PRODUCTS OR ANY USER LEVELS. THIS LIABILITY LIMITATION APPLIES EVEN IF YOU OR ANYONE ELSE HAS ADVISED OWNER OR ANY OF ITS AUTHORIZED REPRESENTATIVES OF THE POSSIBILITY OF SUCH DAMAGES. EVEN IF SUCH IS CAUSED BY, ARISES OUT OF OR RESULTS FROM THE ORDINARY, STRICT, SOLE OR CONTRIBUTORY NEGLIGENCE OF OWNER OR ITS DIRECTORS, OFFICERS, EMPLOYEES, AGENTS, CONTRACTORS OR AFFILIATES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE LIMITATION OR EXCLUSION MAY NOT APPLY TO YOU. +\par +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Product Support and Updates}{\fs22 . This SOFTWARE is intended to be user-friendly and limited product support is provided by Owner as specified in the ACCOMPANYING MATERIALS. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Jurisdiction}{\fs22 . TEXAS LAWS GOVERN THIS AGREEMENT, REGARDLESS OF SUCH STATE\rquote S CHOICE OF LAW PRINCIPLES, WITH A FORUM AND VENUE OF DALLAS COUNTY, TEXAS. This Agreement may be modified only by a written instrument specifying the modification and executed by both parties. In the event that any provision of this Agreement shall be held to be unenforceable, such provision shall be enforced to the greatest possible extent, with the other provisions of this Agreement to remain in full force and effect. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Entire Agreement}{\fs22 . This Agreement represents the entire agreement between the parties, and supersedes any oral or written communications, proposals or prior agreements between the parties or any dealers, distributors, agents or employees. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par \sect }\sectd \sbknone\linex0\headery1440\footery288\sectdefaultcl \pard\plain \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul U.S. Government Restricted Rights}{\fs22 . Each of the SOFTWARE, DEMO PRODUCTS and the ACCOMPANYING MATERIALS is provided with RESTRICTED RIGHTS (as found in 48 C.F.R. '52.227-7013). This provision only applies if the U.S. Government or any of its entities obtains the SOFTWARE or DEMO PRODUCTS either directly or indirectly. Owner created the SOFTWARE, DEMO PRODUCTS and the ACCOMPANYING MATERIALS exclusively with private funds. Additionally, information contained in the SOFTWARE, DEMO PRODUCTS and the ACCOMPANYING MATERIALS is a trade secret of Owner for all purposes of the Freedom of Information Act or otherwise. Furthermore, the SOFTWARE and the DEMO PRODUCTS are \ldblquote commercial computer software\rdblquote subject to limited use as set forth in any contract that may be entered into between the seller and the governmental entity. Owner owns, in all respects, the proprietary information and proprietary data found in the SOFTWARE, DEMO PRODUCTS and the ACCOMPANYING MATERIALS. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 U.S. DEPARTMENT OF DEFENSE PERSONNEL. Owner only sells this SOFTWARE, DEMO PRODUCTS and the ACCOMPANYING MATERIALS with \ldblquote Restricted Rights\rdblquote as defined in DFARS 52.227-7013 (also found at 48 C.F.R. '252.227-7013). Any U.S. Government use, duplication, or disclosure is subject to the restrictions including, but not limited to those found in the Rights in Technological Data clause at DFARS 52.227-7013 (48 C.F.R. '252.227-7013) that may be amended from time to time. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 NON-DEPARTMENT OF DEFENSE PERSONNEL. Other governmental personnel are on notice through this Agreement that any use of the SOFTWARE, DEMO PRODUCTS and/or the ACCOMPANYING MATERIALS is subject to similar limitations as those stated above, including but not limited to, those stated in Commercial Computer Software -- Restricted Rights found in 48 C.F.R. '52.227-19, that may also be amended from time to time. Manufacturer is Owner at the location listed below. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul U.S. Export Laws Prohibitions}{\fs22 . By downloading, installing or otherwise using the SOFTWARE, DEMO PRODUCTS and/or ACCOMPANYING MATERIALS, You also agree and confirm that the SOFTWARE, DEMO PRODUCTS and/or ACCOMPANYING MATERIALS and any of the SOFTWARE\rquote s or DEMO PRODUCTS\rquote direct products are not being and will not be transported, exported or re-exported (directly or indirectly through the Internet or otherwise) into (or to a national or resident of) any country forbidden to receive such SOFTWARE, DEMO PRODUCTS and/or ACCOMPANYING MATERIALS by any U.S. export laws or accompanying regulations or otherwise violate such laws or regulations, that may be amended from time to time. You also agree and confirm that the SOFTWARE, DEMO PRODUCTS and ACCOMPANYING MATERIALS will not be used for any purpose that may be restricted by the same laws and regulations. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Termination}{\fs22 . This Agreement is valid until terminated. This Agreement ceases automatically (without any form of notice) if You do not comply with any Agreement provision. You can also end this Agreement by destroying the SOFTWARE and ACCOMPANYING MATERIALS or DEMO PRODUCTS, as the case may be, and all copies and reproductions of the SOFTWARE and ACCOMPANYING MATERIALS or DEMO PRODUCTS, as the case may be, and deleting and permanently purging the SOFTWARE or DEMO PRODUCTS, as the case may be, from any client server or computer on which it has been installed. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Program Transfer}{\fs22 . You may permanently transfer all of Your rights under this Agreement, provided that the recipient agrees to all of the terms of this Agreement, and You agree to transfer all ACCOMPANYING MATERIALS and related documents and components and, if applicable, remove the SOFTWARE from Your computer prior thereto. With respect to the SOFTWARE and the ACCOMPANYING MATERIALS, transferring the SOFTWARE automatically terminates Your license under this Agreement. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par \sect }\sectd \sbknone\linex0\headery1440\footery288\sectdefaultcl \pard\plain \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Equitable Remedies.}{\fs22 You hereby agree that if the terms of this Agreement are not specifically enforced, Owner will be irreparably damaged, and therefore You agree that Owner shall be entitled, without bond, other security, proof of damages, to appropriate equitable remedies with respect to any breach(es) of this Agreement, in addition to any other available remedies. +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par }\pard \qj\fi720\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22\ul Owner.}{\fs22 If You have any questions regarding this Agreement, the enclosed materials, or otherwise, please contact in writing: +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par +\par }\pard \qj\li1440\nowidctlpar\widctlpar\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 Ritual Entertainment +\par Attn: Customer Service\tab \tab \tab \tab \tab +\par }\pard \qj\fi1440\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 2019 North Lamar Street, Suite 220\tab \tab \tab \tab +\par Dallas, Texas 75202-1744 +\par +\par Fax: (214) 871-7390\tab \tab \tab \tab \tab +\par +\par E-mail: legal@ritual.com\tab \tab \tab +\par }\pard \qj\nowidctlpar\widctlpar\tx0\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\tx9360\adjustright {\fs22 +\par +\par +\par +\par }{\b\fs22 Sin}{\fs22 , }{\b\fs22 Ritual }{\fs22 and}{\b\fs22 Ritual Entertainment }{\fs22 are trademarks of Ritual Entertainment, Inc.}{\b\fs22 +\par }{\fs22 Copyright © 1998 Ritual Entertainment, Inc. All Rights Reserved. +\par +\par }{\b\fs22 Microsoft }{\fs22 and}{\b\fs22 Windows 95, Windows 98 }{\fs22 and}{\b\fs22 Windows NT}{\fs22 are registered trademarks of Microsoft Corporation. All other trademarks and trade names are properties of their respective owners. +\par +\par U.S. Government Restricted Rights +\par Manufactured in the U.S.A.}{ +\par }} \ No newline at end of file diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..0775d25 --- /dev/null +++ b/license.txt @@ -0,0 +1,359 @@ +ATTENTION + +Here is a brief summary of certain of the terms and conditions in our Limited +Software Warranty and License Agreement. You must read the full text of the +agreement before using this product so that You understand all of the terms +and conditions of our agreement regarding this product. + +WHAT IS OKAY FOR YOU TO DO: + +$ Playing and enjoying the shareware and/or registered/full retail +version of the game, demo and/or level editor. + +$ Setting up a shareware, demo, and/or registered/full retail version +based server on a not-for-profit and non-commercially exploited basis. + +$ Playing the shareware, demo, and/or registered/full retail version of +the game and/or setting up a registered/full retail version of the game using +user-developed levels on a not-for-profit and non-commercially exploited +bases. + +WHAT IS NOT OKAY FOR YOU TO DO: + +$ You cannot sell or otherwise commercially exploit or utilize the +shareware, registered/full retail version, demo or level editor in any way or +sell user-developed levels and/or tools. + +$ Commercially exploiting or otherwise utilizing any copyrighted and/or +trademarked property of Ritual Entertainment or any other party contained in +or associated with the shareware, demo, level editor or registered/full retail +version, demo, single player game and/or level editor, including without +limitation game names, logos, graphics, characters, etc. + + +LIMITED SOFTWARE WARRANTY AND LICENSE AGREEMENT + +This LIMITED SOFTWARE WARRANTY AND LICENSE AGREEMENT (this "Agreement"), +including the Limited Warranty and other special provisions, is a legal +agreement between You (either an individual or an entity) and Ritual +Entertainment, Inc., a Texas corporation (the "Owner"), regarding this +software product and the materials contained therein and related thereto. Your +act of downloading, installing and/or otherwise using the software constitutes +Your agreement to be bound by the terms of this Agreement. If You do not agree +to the terms of this Agreement cease loading or installing this product and, +if applicable, promptly return the software packaging and the accompanying +materials (including any hardware, manuals, other written materials and +packaging) to the place You obtained them, along with Your receipt, for a full +refund. + +Grant of Limited Non-Exclusive License. (A) In the event that You are +currently encountering this Agreement in conjunction with or as a result of +Your downloading or otherwise installing the SOFTWARE (as defined herein), +this Agreement permits You to use one (1) copy of the software program(s), in +executable or object code form only, contained in the registered/full retail +version of the game program entitled SiN, as contained in the disk(s) making +up all or part of the registered/full retail software product, including +without limitation the data files, images, level editors, death match levels, +charters and screen displays (the "SOFTWARE"), included in this download for +Your personal use on a single home or portable computer. This license also +permits You to use the SOFTWARE's level editor to create new game levels, +weapons, characters and/or entities for non-commercial personal use, and to +non-commercially distribute such game levels, weapons, characters, and/or +entities to personal acquaintances for non-commercial use via the Internet +pursuant to subparagraph (C) below. This license does NOT authorize You to +sell, lease or otherwise profit from or commercially distribute the SOFTWARE +(see "Restrictions" below). The SOFTWARE is in "use" on a computer when it is +loaded into temporary memory (i.e., RAM) or installed into the permanent +memory (e.g., hard disk, CD-ROM, or other storage device) of that computer. +Installation on a network server is strictly prohibited, except under a +special and separate network license obtained from Owner; this Agreement shall +not serve as such necessary special network license. Installation on a network +server constitutes "use" that must comply with the terms of this Agreement. +This license is not a sale of the original SOFTWARE or any copy thereof. + +(B) In the event that You are currently encountering this Agreement in +conjunction with or as a result of Your downloading or otherwise installing a +DEMO PRODUCT (as defined herein), this Agreement permits You to use the +software program(s) included in and received solely as a result of the loaded +or otherwise installed version of the demo, free standing level editor and/or +shareware version of the game entitled Sin, as the case may be, for Your +personal use (each such item referred to herein, individually as a "DEMO +PRODUCT" and collectively as the "DEMO PRODUCTS"). This license also permits +You to use the level editor, as contained in and received solely as a result +of the downloading or other installation or utilization of such DEMO PRODUCT, +to create new game levels, weapons, characters and/or entities for +non-commercial personal use, and to non-commercially distribute such games +levels, weapons, characters, and/or entities to personal acquaintances for +non-commercial use via the Internet pursuant to subparagraph (C) below. This +license does NOT authorize You to sell, lease or otherwise profit from or +commercially exploit a DEMO PRODUCT (see "Restrictions" below). A DEMO PRODUCT +is in "use" on a computer when it is loaded into temporary memory (i.e., RAM) +or installed into the permanent memory (e.g., hard disk, CD-ROM, or other +storage device) of that computer or accessed via the Internet. Installation of +any DEMO PRODUCTS on a network server for profit or other commercial benefit +to You or any other person is strictly prohibited, except under a special and +separate network license obtained from Owner; this Agreement shall not serve +as such necessary special network license. Installation on a network server +constitutes "use" that must comply with the terms of this Agreement. This +license is not a sale of the original DEMO PRODUCTS or any copy thereof. + +(C) Subject to the terms and conditions of this Agreement, Owner grants to +You the non-exclusive and limited right to create additional levels (the "USER +LEVELS") which are operable with the SOFTWARE or DEMO PRODUCT. You may include +within the USER LEVELS certain textures and other images (the "Owner Images") +from the SOFTWARE or DEMO PRODUCT, as the case may be. You agree that the USER +LEVELS will not be shipped, transferred or exported into any country in +violation of the U.S. Export Administration Act (or any other law governing +such matters) by You or anyone at Your direction and that You will not utilize +and will not authorize anyone to utilize, in any other manner, the USER LEVELS +in violation of any applicable law. The USER LEVELS may not be downloaded or +otherwise exported or re-exported into (or to a national or resident of) any +country to which the United States has embargoed goods or to anyone or unto +any country who/which are prohibited by applicable law, from receiving such +property. You shall not rent, sell, lease, lend, offer on a pay-per- play +basis or otherwise commercially exploit or commercially distribute any USER +LEVELS. You are only permitted to distribute for free, without any cost or +charge, the USER LEVELS to other end-users. As noted below, in the event You +commercially distribute or commercially exploit any USER LEVELS or commit any +other breach of this Agreement, Your license, as granted in this Agreement, +shall automatically terminate, without notice. IN ADDITION TO YOUR +INDEMNIFICATION OBLIGATIONS AS SET FORTH BELOW, YOU HEREBY AGREE TO INDEMNIFY, +DEFEND AND HOLD HARMLESS OWNER AND OWNER'S RESPECTIVE OFFICERS, EMPLOYEES, +DIRECTORS, AGENTS, LICENSEES (EXCLUDING YOU), SUBLICENSEES (EXCLUDING YOU), +SUCCESSORS AND ASSIGNS FROM AND AGAINST ANY AND ALL DIRECT AND/OR INDIRECT +LOSSES, LAWSUITS, DAMAGES, CAUSES OF ACTIONS AND CLAIMS RELATING TO AND/OR +ARISING FROM THE USER LEVELS AND/OR THE DISTRIBUTION OR OTHER USE OF ANY USER +LEVELS. + +Intellectual Property Ownership. Owner retains all right, title and interest +to the SOFTWARE and/or DEMO PRODUCTS and any accompanying instructions or +other documentation (collectively, the "ACCOMPANYING MATERIALS"), including, +but not limited to, all copyrights, trademarks, trade secrets, trade names, +proprietary rights, patents, titles, computer codes, audiovisual effects, +themes, characters, character names, stories, dialog, settings, artwork, +sounds effects, musical works, and moral rights. The SOFTWARE, DEMO PRODUCTS +and ACCOMPANYING MATERIALS are protected by United States copyright law and +applicable copyright laws and treaties throughout the World. All rights are +reserved. The SOFTWARE and ACCOMPANYING MATERIALS may not be copied or +reproduced in any manner or medium, in whole or in part, without prior written +consent from Owner except as specifically provided under "Grant of Limited +Non- Exclusive License" above. Any persons copying or reproducing all or any +portion of the SOFTWARE or ACCOMPANYING MATERIALS, except as specifically +provided under "Grant of Limited Non-Exclusive License" above, in any manner +or medium, will be willfully violating the copyright laws and may be subject +to civil or criminal penalties. + +SOFTWARE Backup or Archiving. After You install the SOFTWARE into the +permanent memory of a computer, You may keep and use the original disk(s) +and/or CD-ROM (the "Storage Media") only for backup or archival purposes. You +are expressly prohibited from transmitting the SOFTWARE or ACCOMPANYING +MATERIALS electronically or otherwise over the Internet or through any other +media or to any other party. + + +Restrictions. Other than as provided specifically in this Agreement, You are +not permitted to copy or otherwise reproduce the SOFTWARE or ACCOMPANYING +MATERIALS; modify or prepare derivative copies based on the SOFTWARE or +ACCOMPANYING MATERIALS; distribute copies of the SOFTWARE or ACCOMPANYING +MATERIALS by sale or other transfer of ownership; rent, lease, or lend the +SOFTWARE, DEMO PRODUCTS or ACCOMPANYING MATERIALS; or to display the SOFTWARE +or ACCOMPANYING MATERIALS publicly. You are expressly prohibited from selling, +leasing, charging for access to, or otherwise using for profit or commercially +exploiting any USER LEVELS, level packs, add-on packs, sequels, characters or +other components or items created by utilization of the SOFTWARE's or DEMO +PRODUCT's level editor and/or based upon or related to the SOFTWARE, DEMO +PRODUCT or ACCOMPANYING MATERIALS. YOU ARE NOT PERMITTED TO REVERSE ENGINEER, +DECOMPILE OR DISASSEMBLE THE SOFTWARE OR ANY DEMO PRODUCT IN ANY WAY. Any +copying or distribution of the SOFTWARE or ACCOMPANYING MATERIALS not +specifically allowed in this Agreement is a violation of this Agreement. You +may create USER LEVELS for the SOFTWARE or any DEMO PRODUCT; provided, +however, that such USER LEVELS are created in accordance with the +non-exclusive license set forth above and no USER LEVELS may be sold or +otherwise commercially exploited, whether by You or by any other person or +entity, but You may exchange USER LEVELS at no charge with other end-users. + +Limited Warranty and Warranty Disclaimers. + +LIMITED WARRANTY. Owner warrants that the original Storage Media holding the +SOFTWARE is free from defects in materials and workmanship under normal use +and service for a period of ninety (90) days from the date that You downloaded +the SOFTWARE. If for any reason You find defects in the Storage Media, or if +You are unable to install the SOFTWARE on Your home or portable computer, You +may return the SOFTWARE, ACCOMPANYING MATERIALS and all packaging related +thereto to the place you purchased such products for a full refund or +replacement thereof. This limited warranty does not apply if You have damaged +the SOFTWARE by accident or abuse. + +CUSTOMER'S REMEDY. Your exclusive remedies, and the entire liability of Owner, +shall be replacement of the SOFTWARE or DEMO PRODUCT, as the case may be. By +downloading, installing and/or otherwise using the SOFTWARE, DEMO PRODUCT or +ACCOMPANYING MATERIALS, as the case may be, You hereby agree to waive any and +all other remedies You may have at law or in equity. Any such remedies You may +not waive as a matter of public policy, You hereby assign, or shall assign as +they become available, over to Owner. + +NOTWITHSTANDING ANYTHING TO THE CONTRARY IN THIS AGREEMENT, WITH RESPECT TO +THE DEMO PRODUCTS, OWNER MAKES NO REPRESENTATIONS OR WARRANTIES (EXPRESS, +IMPLIED OR OTHERWISE), INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND/OR NON-INFRINGEMENT. THE +DEMO PRODUCTS ARE PROVIDED GRATUITOUSLY TO YOU "AS IS" AND YOU TAKE, INSTALL, +LOAD OR OTHERWISE USE SUCH DEMO PRODUCTS AT YOUR OWN RISK. OWNER HAS NO +LIABILITIES ARISING FROM OR RELATED TO YOUR USE OF ANY DEMO PRODUCTS. + + +WARRANTY DISCLAIMERS. EXCEPT FOR THE EXPRESS LIMITED WARRANTY SET FORTH ABOVE, +OWNER MAKES NO WARRANTIES, EXPRESS OR IMPLIED, ORAL OR WRITTEN, CONCERNING THE +PRODUCTS REFERENCED HEREIN OR ANY COMPONENT PART THEREOF. ANY IMPLIED +WARRANTIES THAT MAY BE IMPOSED BY APPLICABLE LAW ARE LIMITED IN ALL RESPECTS +TO THE FULLEST EXTENT ALLOWED AND TO THE DURATION OF THE LIMITED WARRANTY. +OWNER DOES NOT REPRESENT, WARRANT OR GUARANTEE THE QUALITY OR THE PERFORMANCE +OF THE SOFTWARE, DEMO PRODUCTS OR ACCOMPANYING MATERIALS OTHER THAN AS SET +FORTH IN THE ABOVE LIMITED WARRANTY. OWNER ALSO DOES NOT REPRESENT, WARRANT OR +GUARANTEE THAT THE SOFTWARE, DEMO PRODUCTS OR ACCOMPANYING MATERIALS +CAPABILITIES WILL MEET YOUR NEEDS OR THAT THE SOFTWARE OR DEMO PRODUCTS WILL +CONTINUOUSLY OPERATE, BE ERROR FREE, OR THAT PROBLEMS WILL BE CORRECTED. OWNER +DOES NOT REPRESENT THAT THE SOFTWARE OR DEMO PRODUCTS WILL OPERATE IN A +MULTI-USER ENVIRONMENT. + +NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY OWNER, ITS DEALERS, +DISTRIBUTORS, DIRECTORS, OFFICERS, EMPLOYEES, AGENTS, CONTRACTORS OR +AFFILIATES SHALL CREATE ANY OTHER WARRANTY OR EXTEND OR EXPAND THE SCOPE OF +THIS WARRANTY. YOU MAY NOT RELY ON ANY SUCH INFORMATION OR ADVICE. + +SOME STATES DO NOT ALLOW LIMITATIONS ON HOW LONG AN IMPLIED WARRANTY LASTS, SO +THE ABOVE LIMITATION MAY NOT APPLY TO YOU. THIS LIMITED WARRANTY GIVES YOU +SPECIFIC LEGAL RIGHTS AND YOU MAY ALSO HAVE OTHER RIGHTS WHICH MAY VARY FROM +STATE TO STATE. + +LIABILITY LIMITATION. To the maximum extent permitted by applicable law, and +regardless of whether any remedy set forth herein fails of its essential +purpose, + +IN NO EVENT WILL OWNER, ITS DIRECTORS, OFFICERS, EMPLOYEES, AGENTS, LICENSES +(EXCLUDING YOU), SUBLICENSEES (EXCLUDING YOU) OR AFFILIATES NOR ANYONE ELSE +INVOLVED IN THE DEVELOPMENT, MANUFACTURE OR DISTRIBUTION OF THE SOFTWARE, THE +DEMO PRODUCTS, THE ACCOMPANYING MATERIALS OR THE USER LEVELS (OTHER THAN YOU) +BE LIABLE FOR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, DIRECT OR +INDIRECT; INCIDENTAL; OR CONSEQUENTIAL DAMAGES FOR PERSONAL INJURY, PERSONAL +PROPERTY, LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS +INFORMATION, LOSS OF TEXT OR DATA STORED IN OR USED WITH SUCH PRODUCT +INCLUDING THE COST OF RECOVERING OR REPRODUCING THE TEXT OR DATA, OR ANY OTHER +PECUNIARY LOSS, ARISING FROM OR OUT OF THE USE OR INABILITY TO USE THIS +SOFTWARE, THE DEMO PRODUCTS OR ANY USER LEVELS. THIS LIABILITY LIMITATION +APPLIES EVEN IF YOU OR ANYONE ELSE HAS ADVISED OWNER OR ANY OF ITS AUTHORIZED +REPRESENTATIVES OF THE POSSIBILITY OF SUCH DAMAGES. EVEN IF SUCH IS CAUSED BY, +ARISES OUT OF OR RESULTS FROM THE ORDINARY, STRICT, SOLE OR CONTRIBUTORY +NEGLIGENCE OF OWNER OR ITS DIRECTORS, OFFICERS, EMPLOYEES, AGENTS, CONTRACTORS +OR AFFILIATES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF +INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE LIMITATION OR EXCLUSION MAY +NOT APPLY TO YOU. + +Product Support and Updates. This SOFTWARE is intended to be user-friendly and +limited product support is provided by Owner as specified in the ACCOMPANYING +MATERIALS. + +Jurisdiction. TEXAS LAWS GOVERN THIS AGREEMENT, REGARDLESS OF SUCH STATE'S +CHOICE OF LAW PRINCIPLES, WITH A FORUM AND VENUE OF DALLAS COUNTY, TEXAS. This +Agreement may be modified only by a written instrument specifying the +modification and executed by both parties. In the event that any provision of +this Agreement shall be held to be unenforceable, such provision shall be +enforced to the greatest possible extent, with the other provisions of this +Agreement to remain in full force and effect. + +Entire Agreement. This Agreement represents the entire agreement between the +parties, and supersedes any oral or written communications, proposals or prior +agreements between the parties or any dealers, distributors, agents or +employees. + + +U.S. Government Restricted Rights. Each of the SOFTWARE, DEMO PRODUCTS and the +ACCOMPANYING MATERIALS is provided with RESTRICTED RIGHTS (as found in 48 +C.F.R. '52.227-7013). This provision only applies if the U.S. Government or +any of its entities obtains the SOFTWARE or DEMO PRODUCTS either directly or +indirectly. Owner created the SOFTWARE, DEMO PRODUCTS and the ACCOMPANYING +MATERIALS exclusively with private funds. Additionally, information contained +in the SOFTWARE, DEMO PRODUCTS and the ACCOMPANYING MATERIALS is a trade +secret of Owner for all purposes of the Freedom of Information Act or +otherwise. Furthermore, the SOFTWARE and the DEMO PRODUCTS are "commercial +computer software" subject to limited use as set forth in any contract that +may be entered into between the seller and the governmental entity. Owner +owns, in all respects, the proprietary information and proprietary data found +in the SOFTWARE, DEMO PRODUCTS and the ACCOMPANYING MATERIALS. + +U.S. DEPARTMENT OF DEFENSE PERSONNEL. Owner only sells this SOFTWARE, DEMO +PRODUCTS and the ACCOMPANYING MATERIALS with "Restricted Rights" as defined in +DFARS 52.227-7013 (also found at 48 C.F.R. '252.227-7013). Any U.S. Government +use, duplication, or disclosure is subject to the restrictions including, but +not limited to those found in the Rights in Technological Data clause at DFARS +52.227-7013 (48 C.F.R. '252.227-7013) that may be amended from time to time. + +NON-DEPARTMENT OF DEFENSE PERSONNEL. Other governmental personnel are on +notice through this Agreement that any use of the SOFTWARE, DEMO PRODUCTS +and/or the ACCOMPANYING MATERIALS is subject to similar limitations as those +stated above, including but not limited to, those stated in Commercial +Computer Software -- Restricted Rights found in 48 C.F.R. '52.227-19, that may +also be amended from time to time. Manufacturer is Owner at the location +listed below. + +U.S. Export Laws Prohibitions. By downloading, installing or otherwise using +the SOFTWARE, DEMO PRODUCTS and/or ACCOMPANYING MATERIALS, You also agree and +confirm that the SOFTWARE, DEMO PRODUCTS and/or ACCOMPANYING MATERIALS and any +of the SOFTWARE's or DEMO PRODUCTS' direct products are not being and will not +be transported, exported or re-exported (directly or indirectly through the +Internet or otherwise) into (or to a national or resident of) any country +forbidden to receive such SOFTWARE, DEMO PRODUCTS and/or ACCOMPANYING +MATERIALS by any U.S. export laws or accompanying regulations or otherwise +violate such laws or regulations, that may be amended from time to time. You +also agree and confirm that the SOFTWARE, DEMO PRODUCTS and ACCOMPANYING +MATERIALS will not be used for any purpose that may be restricted by the same +laws and regulations. + +Termination. This Agreement is valid until terminated. This Agreement ceases +automatically (without any form of notice) if You do not comply with any +Agreement provision. You can also end this Agreement by destroying the +SOFTWARE and ACCOMPANYING MATERIALS or DEMO PRODUCTS, as the case may be, and +all copies and reproductions of the SOFTWARE and ACCOMPANYING MATERIALS or +DEMO PRODUCTS, as the case may be, and deleting and permanently purging the +SOFTWARE or DEMO PRODUCTS, as the case may be, from any client server or +computer on which it has been installed. + +Program Transfer. You may permanently transfer all of Your rights under this +Agreement, provided that the recipient agrees to all of the terms of this +Agreement, and You agree to transfer all ACCOMPANYING MATERIALS and related +documents and components and, if applicable, remove the SOFTWARE from Your +computer prior thereto. With respect to the SOFTWARE and the ACCOMPANYING +MATERIALS, transferring the SOFTWARE automatically terminates Your license +under this Agreement. + + +Equitable Remedies. You hereby agree that if the terms of this Agreement are +not specifically enforced, Owner will be irreparably damaged, and therefore +You agree that Owner shall be entitled, without bond, other security, proof of +damages, to appropriate equitable remedies with respect to any breach(es) of +this Agreement, in addition to any other available remedies. + +Owner. If You have any questions regarding this Agreement, the enclosed +materials, or otherwise, please contact in writing: + + +Ritual Entertainment Attn: Customer Service 2019 North Lamar Street, Suite 220 +Dallas, Texas 75202-1744 + +Fax: (214) 871-7390 + +E-mail: legal@ritual.com + + + + +Sin, Ritual and Ritual Entertainment are trademarks of Ritual Entertainment, +Inc. Copyright c 1998 Ritual Entertainment, Inc. All Rights Reserved. + +Microsoft and Windows 95, Windows 98 and Windows NT are registered trademarks +of Microsoft Corporation. All other trademarks and trade names are properties +of their respective owners. + +U.S. Government Restricted Rights Manufactured in the U.S.A. + + +Software License Agreement 7 08656 00001 CORP 209544.3 \ No newline at end of file diff --git a/light.cpp b/light.cpp new file mode 100644 index 0000000..c16f0d0 --- /dev/null +++ b/light.cpp @@ -0,0 +1,386 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/light.cpp $ +// $Revision:: 21 $ +// $Author:: Aldie $ +// $Date:: 10/09/98 8:58p $ +// +// 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/light.cpp $ +// +// 21 10/09/98 8:58p Aldie +// Lightstyle on lensflare from Lights +// +// 20 9/22/98 5:19p Markd +// Fixed lights not removing themselves +// +// 19 9/22/98 4:23p Markd +// Fixed some targetname stuff for lights, doors and scriptobjects +// +// 18 9/14/98 11:20a Jimdose +// Made Light process EV_Remove so that we have more edicts at startup +// +// 17 8/30/98 7:05p Markd +// lights don't respond to Touch. +// +// 16 8/29/98 7:46p Markd +// Don't set a light style if style is < 32 +// +// 15 7/09/98 12:05a Jimdose +// made constructor post remove event instead of processing it +// +// 14 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// 13 6/17/98 7:18p Markd +// Added on_style and off_style support +// +// 12 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 +// +// 11 4/04/98 6:05p Jimdose +// Made response from EV_Trigger_ActivateTargets to EV_Trigger_Effect +// +// 10 3/27/98 9:47p Jimdose +// Fixed light so that it reacts to activate targets events +// +// 9 3/25/98 3:55p Jimdose +// fixed bug where lightramp wasn't responding to any events. +// +// 8 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 7 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. +// +// 6 3/02/98 5:41p Jimdose +// Created file +// +// DESCRIPTION: +// Classes for creating and controlling lights. +// + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "light.h" +#include "scriptmaster.h" + +CLASS_DECLARATION( Trigger, BaseLight, NULL ); + +Event EV_Light_TurnOn( "turnOn" ); +Event EV_Light_TurnOff( "turnOff" ); +Event EV_Light_SetLightStyle( "lightstyle" ); + +ResponseDef BaseLight::Responses[] = + { + { &EV_Light_TurnOn, ( Response )Light::TurnOn }, + { &EV_Light_TurnOff, ( Response )Light::TurnOff }, + { &EV_Light_SetLightStyle, ( Response )Light::EventSetLightStyle }, + { &EV_Touch, NULL }, + { NULL, NULL } + }; + +BaseLight::BaseLight() + { + hideModel(); + edict->svflags |= SVF_NOCLIENT; + setSolidType( SOLID_NOT ); + style = G_GetIntArg( "style" ); + on_style = G_GetStringArg( "onstyle", "m" ); + off_style = G_GetStringArg( "offstyle", "a" ); + respondto = TRIGGER_PLAYERS; + } + +int BaseLight::GetStyle + ( + void + ) + + { + return style; + } + +void BaseLight::SetLightStyle + ( + const char *stylestring + ) + + { + if ( style < 32 ) + return; + lightstyle = stylestring; + gi.configstring( CS_LIGHTS + style, lightstyle.c_str() ); + } + +void BaseLight::EventSetLightStyle + ( + Event *ev + ) + + { + SetLightStyle( ev->GetString( 1 ) ); + } + +void BaseLight::TurnOn + ( + Event *ev + ) + + { + SetLightStyle( on_style.c_str() ); + } + +void BaseLight::TurnOff + ( + Event *ev + ) + + { + SetLightStyle( off_style.c_str() ); + } + +/*****************************************************************************/ +/*SINED light_ramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE + +Non-displayed light that ramps its intensity from one level to another when trigger. + +time How many seconds the ramping will take (default 1.0) +startlevel Value between 0 and 2.0 (default 0) +endlevel Value between 0 and 2.0 (default 1.0) +"key" The item needed to activate this. (default nothing) + +/*****************************************************************************/ + +CLASS_DECLARATION( BaseLight, LightRamp, "light_ramp" ); + +Event EV_RampLight( "ramplight" ); + +ResponseDef LightRamp::Responses[] = + { + { &EV_Trigger_Effect, ( Response )LightRamp::StartRamp }, + { &EV_RampLight, ( Response )LightRamp::RampLight }, + { NULL, NULL } + }; + +void LightRamp::RampLight + ( + Event *ev + ) + + { + char st[ 2 ]; + + currentlevel += rate; + if ( currentlevel >= maxlevel ) + { + if ( !( spawnflags & 1 ) ) + { + rate = 0; + } + currentlevel = maxlevel; + } + else if ( currentlevel <= minlevel ) + { + if ( !( spawnflags & 1 ) ) + { + rate = 0; + } + currentlevel = minlevel; + } + else + { + PostEvent( EV_RampLight, FRAMETIME ); + } + + st[ 0 ] = 'L'; + st[ 1 ] = 'a' + ( int )( currentlevel * 12.5 ); + st[ 1 ] = min( 'z', st[ 1 ] ); + st[ 1 ] = max( 'a', st[ 1 ] ); + st[ 2 ] = 0; + + SetLightStyle( st ); + } + +void LightRamp::StartRamp + ( + Event *ev + ) + + { + if ( rate ) + { + rate = -rate; + CancelEventsOfType( EV_RampLight ); + ProcessEvent( EV_RampLight ); + } + + ActivateTargets( ev ); + } + +LightRamp::LightRamp() + { + float startlevel; + float endlevel; + float time; + char st[ 2 ]; + + startlevel = G_GetFloatArg( "startlevel", 1.0 ); + endlevel = G_GetFloatArg( "endlevel", 0 ); + time = G_GetFloatArg( "time", 1.0 ); + + minlevel = min( startlevel, endlevel ); + maxlevel = max( startlevel, endlevel ); + + minlevel = min( 2.0, minlevel ); + minlevel = max( 0, minlevel ); + + maxlevel = min( 2.0, maxlevel ); + maxlevel = max( 0, maxlevel ); + + rate = FRAMETIME * ( maxlevel - minlevel ) / time; + + currentlevel = startlevel; + + st[ 0 ] = 'a' + ( int )( currentlevel * 12.5 ); + st[ 0 ] = min( 'z', st[ 0 ] ); + st[ 0 ] = max( 'a', st[ 0 ] ); + st[ 1 ] = 0; + + SetLightStyle( st ); + } + +/*****************************************************************************/ +/*SINED trigger_lightramp (0 1 0) (-8 -8 -8) (8 8 8) TOGGLE + +Ramps light values on surface based light sources. +Set style to the identifier contained in groupname in the surfaces to control. + +time How many seconds the ramping will take (default 1.0) +startlevel Value between 0 and 2.0 (default 0) +endlevel Value between 0 and 2.0 (default 1.0) +"key" The item needed to activate this. (default nothing) + +Default style is 0. + +/*****************************************************************************/ + +CLASS_DECLARATION( LightRamp, TriggerLightRamp, "trigger_lightramp" ); + +ResponseDef TriggerLightRamp::Responses[] = + { + { NULL, NULL } + }; + +/*****************************************************************************/ +/*SINED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF + +Non-displayed light. + +Default light value is 300. +Default style is 0. + +If targeted, will toggle between on and off. +Default _cone value is 10 (used to set size of light for spotlights) +"on_style" light style to set to when "on" (default is "m") +"off_style" light style to set to when "off" (default is "a") +"key" The item needed to activate this. (default nothing) + +/*****************************************************************************/ + +#define START_OFF 1 + +CLASS_DECLARATION( BaseLight, Light, "light" ); + +ResponseDef Light::Responses[] = + { + { &EV_Trigger_Effect, ( Response )Light::ToggleLight }, + { NULL, NULL } + }; + +void Light::ToggleLight + ( + Event *ev + ) + + { + if ( style >= 32 ) + { + if ( spawnflags & START_OFF ) + { + ProcessEvent( EV_Light_TurnOn ); + spawnflags &= ~START_OFF; + } + else + { + ProcessEvent( EV_Light_TurnOff ); + spawnflags |= START_OFF; + } + } + + ActivateTargets( ev ); + } + +Light::Light() + { + const char * tname; + + tname = TargetName(); + if ( !tname || !tname[ 0 ] ) + { + const char *classname; + + classname = G_GetSpawnArg( "classname" ); + if ( classname && !strcmp( classname, "light" ) ) + { + ProcessEvent( EV_Remove ); + } + return; + } + + if ( style >= 32 ) + { + if ( spawnflags & START_OFF ) + { + SetLightStyle( off_style.c_str() ); + } + else + { + SetLightStyle( on_style.c_str() ); + } + } + } + +/*****************************************************************************/ +/*SINED trigger_SetLightStyle (0 1 0) (-8 -8 -8) (8 8 8) START_OFF + +Used for controlling surface based light sources. + +Set style to the identifier contained in groupname in the surfaces to control. + +Default style is 0. + +If targeted, will toggle between on and off. + +"on_style" light style to set to when "on" (default is "m") +"off_style" light style to set to when "off" (default is "a") +"key" The item needed to activate this. (default nothing) + +/*****************************************************************************/ + +CLASS_DECLARATION( Light, TriggerLightStyle, "trigger_SetLightStyle" ); + +ResponseDef TriggerLightStyle::Responses[] = + { + { NULL, NULL } + }; diff --git a/light.h b/light.h new file mode 100644 index 0000000..bdc49f6 --- /dev/null +++ b/light.h @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/light.h $ +// $Revision:: 12 $ +// $Author:: Aldie $ +// $Date:: 10/09/98 9:01p $ +// +// 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/light.h $ +// +// 12 10/09/98 9:01p Aldie +// Lightstyle on lensflare from Lights +// +// 11 9/28/98 9:12p Markd +// Put in archive and unarchive functions +// +// 10 6/17/98 7:18p Markd +// added off_style and on_style +// +// 9 4/30/98 9:24p Jimdose +// Changed use of string to str class +// +// 8 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 7 3/02/98 8:49p Jimdose +// Changed CLASS_PROTOTYPE to only take the classname +// +// 6 3/02/98 5:41p Jimdose +// Created file +// +// DESCRIPTION: +// Classes for creating and controlling lights. +// + +#ifndef __LIGHT_H__ +#define __LIGHT_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" + +extern Event EV_Light_TurnOn; +extern Event EV_Light_TurnOff; +extern Event EV_Light_SetLightStyle; + +class EXPORT_FROM_DLL BaseLight : public Trigger + { + protected: + int style; + str lightstyle; + str on_style; + str off_style; + + public: + CLASS_PROTOTYPE( BaseLight ); + + BaseLight(); + void SetLightStyle( const char *stylestring ); + void EventSetLightStyle( Event *ev ); + int GetStyle( void ); + void TurnOn( Event *ev ); + void TurnOff( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void BaseLight::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.WriteInteger( style ); + arc.WriteString( lightstyle ); + arc.WriteString( on_style ); + arc.WriteString( off_style ); + } + +inline EXPORT_FROM_DLL void BaseLight::Unarchive + ( + Archiver &arc + ) + { + Trigger::Unarchive( arc ); + + arc.ReadInteger( &style ); + arc.ReadString( &lightstyle ); + arc.ReadString( &on_style ); + arc.ReadString( &off_style ); + } + +class EXPORT_FROM_DLL LightRamp : public BaseLight + { + protected: + float minlevel; + float maxlevel; + float currentlevel; + float rate; + + public: + CLASS_PROTOTYPE( LightRamp ); + + LightRamp(); + void RampLight( Event *ev ); + void StartRamp( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void LightRamp::Archive + ( + Archiver &arc + ) + { + BaseLight::Archive( arc ); + + arc.WriteFloat( minlevel ); + arc.WriteFloat( maxlevel ); + arc.WriteFloat( currentlevel ); + arc.WriteFloat( rate ); + } + +inline EXPORT_FROM_DLL void LightRamp::Unarchive + ( + Archiver &arc + ) + { + BaseLight::Unarchive( arc ); + + arc.ReadFloat( &minlevel ); + arc.ReadFloat( &maxlevel ); + arc.ReadFloat( ¤tlevel ); + arc.ReadFloat( &rate ); + } + + +class EXPORT_FROM_DLL TriggerLightRamp : public LightRamp + { + public: + CLASS_PROTOTYPE( TriggerLightRamp ); + }; + +class EXPORT_FROM_DLL Light : public BaseLight + { + public: + CLASS_PROTOTYPE( Light ); + + void ToggleLight( Event *ev ); + Light(); + }; + +class EXPORT_FROM_DLL TriggerLightStyle : public Light + { + public: + CLASS_PROTOTYPE( TriggerLightStyle ); + }; + +#endif /* light.h */ diff --git a/linklist.h b/linklist.h new file mode 100644 index 0000000..c5f9cfc --- /dev/null +++ b/linklist.h @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Sin/LINKLIST.H $ +// $Revision:: 2 $ +// $Author:: Jimdose $ +// $Date:: 9/26/97 7:05p $ +// +// 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:: /Sin/LINKLIST.H $ +// +// 2 9/26/97 7:05p Jimdose +// Added standard Ritual Headers +// +// DESCRIPTION: +// + +#ifndef __linklist_h +#define __linklist_h +#ifdef __cplusplus +extern "C" { +#endif + + +#define NewNode(type) ((type *)Z_Malloc(sizeof(type))) + +#define LL_New(rootnode,type,next,prev) \ + { \ + (rootnode) = NewNode(type); \ + (rootnode)->prev = (rootnode); \ + (rootnode)->next = (rootnode); \ + } + +#define LL_Add(rootnode, newnode, next, prev) \ + { \ + (newnode)->next = (rootnode); \ + (newnode)->prev = (rootnode)->prev; \ + (rootnode)->prev->next = (newnode); \ + (rootnode)->prev = (newnode); \ + } +//MED +#define LL_AddFirst(rootnode, newnode, next, prev) \ + { \ + (newnode)->prev = (rootnode); \ + (newnode)->next = (rootnode)->next; \ + (rootnode)->next->prev = (newnode); \ + (rootnode)->next = (newnode); \ + } + +#define LL_Transfer(oldroot,newroot,next,prev) \ + { \ + if (oldroot->prev != oldroot) \ + { \ + oldroot->prev->next = newroot; \ + oldroot->next->prev = newroot->prev; \ + newroot->prev->next = oldroot->next; \ + newroot->prev = oldroot->prev; \ + oldroot->next = oldroot; \ + oldroot->prev = oldroot; \ + } \ + } + +#define LL_Reverse(root,type,next,prev) \ + { \ + type *newend,*trav,*tprev; \ + \ + newend = root->next; \ + for(trav = root->prev; trav != newend; trav = tprev) \ + { \ + tprev = trav->prev; \ + LL_Move(trav,newend,next,prev); \ + } \ + } + + +#define LL_Remove(node,next,prev) \ + { \ + node->prev->next = node->next; \ + node->next->prev = node->prev; \ + node->next = node; \ + node->prev = node; \ + } + +#define LL_SortedInsertion(rootnode,insertnode,next,prev,type,sortparm) \ + { \ + type *hoya; \ + \ + hoya = rootnode->next; \ + while((hoya != rootnode) && (insertnode->sortparm > hoya->sortparm)) \ + { \ + hoya = hoya->next; \ + } \ + LL_Add(hoya,insertnode,next,prev); \ + } + +#define LL_Move(node,newroot,next,prev) \ + { \ + LL_Remove(node,next,prev); \ + LL_Add(newroot,node,next,prev); \ + } + +#define LL_Empty(list,next,prev) \ + ( \ + ((list)->next == (list)) && \ + ((list)->prev == (list)) \ + ) + +#define LL_Free(list) Z_Free(list) +#define LL_Reset(list,next,prev) (list)->next = (list)->prev = (list) + +#ifdef __cplusplus +}; +#endif +#endif diff --git a/listener.cpp b/listener.cpp new file mode 100644 index 0000000..092128e --- /dev/null +++ b/listener.cpp @@ -0,0 +1,1955 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/listener.cpp $ +// $Revision:: 45 $ +// $Author:: Jimdose $ +// $Date:: 11/10/98 5:48p $ +// +// Copyright (C) 1998 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/listener.cpp $ +// +// 45 11/10/98 5:48p Jimdose +// Made SortEventList sort the list manually when TEMPLATE_EXPORT is not +// defined +// +// 44 10/26/98 4:27p Jimdose +// Sped up ValidEvent +// Added FindEvent( const char * ) +// +// 43 10/24/98 3:25a Jimdose +// added g_watch for only displaying events from a specific object +// +// 42 10/19/98 6:30p Jimdose +// increased g_eventlimit to 1500 +// +// 41 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 40 10/18/98 8:44p Jimdose +// Made commandList a Container of str * instead of str. Fixes problem of name +// pointer in event being invalidated when commandList is resized +// +// 39 10/18/98 3:22a Jimdose +// Added code for timing events +// +// 38 10/17/98 11:33p Jimdose +// Added the event name to Event to help with debugging +// +// 37 10/17/98 12:22a Jimdose +// upped g_eventlimit to 500 +// +// 36 10/16/98 7:19p Jimdose +// Added g_eventlimit +// G_ProcessPendingEvents now breaks out if the number of events executed +// during the call exceeds g_eventlimit. +// Removed g_response and old code from ProcessEvent +// +// 35 10/16/98 1:53a Jimdose +// Added FL_DONTSAVE to entities, so events that belong to entities with this +// flag aren't archived +// +// 34 10/10/98 1:28a Jimdose +// PostEvent kills any events posted during a loadgame +// +// 33 10/07/98 11:52p Jimdose +// Wrote Event archiving functions +// +// 32 9/24/98 1:46a Jimdose +// Made Event a subclass of Class +// +// 31 9/19/98 4:32p Jimdose +// Added ListCommands +// +// 30 8/27/98 9:03p Jimdose +// Moved a lot of small functions to the header as inline +// +// 29 8/26/98 6:42p Jimdose +// Added more info when g_showevents is set +// +// 28 8/20/98 4:42p Jimdose +// (Finally) added binary search for resolving event names +// +// 27 8/17/98 4:29p Jimdose +// added event reporting with g_showevents cvar +// +// 26 8/06/98 5:18p Jimdose +// Fixed check for default flags in Event constructor for str objects +// +// 25 8/01/98 7:58p Jimdose +// Fixed bug with cheats in dm +// +// 24 7/31/98 8:09p Jimdose +// Script commands now include flags to indicate cheats and console commands +// +// 23 7/31/98 4:19p Jimdose +// Added DepthOfEvent +// +// 22 7/20/98 5:09p Jimdose +// Added ProcessPendingEvents +// +// 21 7/10/98 10:00p Jimdose +// Made remove script command post remove event so that it doesn't cause +// problems during event callbacks which trigger the script +// +// 20 7/08/98 12:54p Jimdose +// Made error for vectors not including '(' ')' a developer 2 warning +// +// 19 6/30/98 6:05p Jimdose +// Added IsVectorAt, IsEntityAt, and IsNumericAt for doing type checking on +// args +// Changed format for storing vectors to make it easy to identify +// +// 18 6/27/98 9:18p Jimdose +// Made lookup for event responses for faster processing +// +// 17 6/24/98 6:48p Jimdose +// Made entity number based commands use "*" prefix consistantly +// +// 16 6/20/98 7:41p Jimdose +// Made GetEntity return NULL if the index is out of range +// +// 15 6/18/98 8:47p Jimdose +// Added better event error handling +// Added source info to events +// Added checks for event still in use when processing events (prevents +// deleting events twice) +// +// 14 6/17/98 3:03p Markd +// Changed NumArgs back to previous behavior +// +// 13 6/10/98 7:53p Markd +// Made NumArgs behave correctly like argc +// +// 12 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 +// +// 11 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 10 5/24/98 1:06a Jimdose +// added const to various char *s +// +// 9 5/08/98 2:51p Jimdose +// Moved archiving functions up to Class +// +// 8 5/07/98 10:42p Jimdose +// Added archive and unarchive +// +// 7 4/30/98 9:24p Jimdose +// Changed use of string to str class +// +// 6 4/06/98 5:44p Jimdose +// Added assertion for null entities in AddEntity. In the release version, it +// "safely" treats null ents as entnum 0 +// +// 5 4/02/98 4:48p Jimdose +// Added initCommandList to ensure that NullEvent is always initialized first +// +// 4 3/24/98 5:02p Jimdose +// Made GetToken return the exact string instead of resolving the value of +// variables +// PostponeEvent was not updating the position of the event in the list, which +// would cause a logjam of events that were following the event. +// +// 3 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 2 3/04/98 1:01p Jimdose +// Created file +// +// DESCRIPTION: +// + +#include "listener.h" +#include "scriptvariable.h" +#include "worldspawn.h" +#include "scriptmaster.h" + +Event EV_Remove( "immediateremove" ); +Event EV_ScriptRemove( "remove" ); + +typedef struct eventcache_s + { + Listener *obj; + Event *event; + float time; + + struct eventcache_s *next; + struct eventcache_s *prev; + } eventcache_t; + +#define MAX_EVENTS 2000 + +extern "C" + { + eventcache_t Events[ MAX_EVENTS ]; + int numEvents = 0; + cvar_t *g_numevents; + }; + +cvar_t *g_showevents; +cvar_t *g_eventlimit; +cvar_t *g_timeevents; +cvar_t *g_watch; + +eventcache_t FreeEventHead; +eventcache_t *FreeEvents = &FreeEventHead; +eventcache_t EventQueueHead; +eventcache_t *EventQueue = &EventQueueHead; + +Container *Event::commandList = NULL; +Container *Event::flagList = NULL; +Container *Event::sortedList = NULL; +qboolean Event::dirtylist = false; + +Event NullEvent; + +CLASS_DECLARATION( Class, Event, NULL ); + +ResponseDef Event::Responses[] = + { + { NULL, NULL } + }; + +EXPORT_FROM_DLL int Event::NumEventCommands + ( + void + ) + + { + if ( commandList ) + { + // Add 1 since container gives the inclusive number of objects + return commandList->NumObjects() + 1; + } + + return 0; + } + +EXPORT_FROM_DLL int Event::compareEvents + ( + const void *arg1, + const void *arg2 + ) + + { + int ev1; + int ev2; + + ev1 = *( int * )arg1; + ev2 = *( int * )arg2; + + return stricmp( commandList->ObjectAt( ev1 )->c_str(), commandList->ObjectAt( ev2 )->c_str() ); + } + +EXPORT_FROM_DLL void Event::SortEventList + ( + void + ) + + { + dirtylist = false; + + if ( sortedList && commandList ) + { +#ifndef EXPORT_TEMPLATE + qsort( ( void * )sortedList->AddressOfObjectAt( 1 ), + ( size_t )sortedList->NumObjects(), + sizeof( int ), compareEvents ); +#else + sortedList->Sort( compareEvents ); +#endif + } + } + +inline EXPORT_FROM_DLL int Event::FindEvent + ( + const char *name + ) + + { + int eventnum; + int index; + int l; + int r; + int diff; + + assert( name ); + if ( !name ) + { + return 0; + } + + if ( !commandList ) + { + return 0; + } + + if ( dirtylist ) + { + SortEventList(); + } + + l = 1; + r = sortedList->NumObjects(); + while( r >= l ) + { + index = ( l + r ) >> 1; + eventnum = sortedList->ObjectAt( index ); + diff = stricmp( name, commandList->ObjectAt( eventnum )->c_str() ); + if ( diff < 0 ) + { + r = index - 1; + } + else if ( diff > 0 ) + { + l = index + 1; + } + else + { + return eventnum; + } + } + + return 0; + } + +EXPORT_FROM_DLL int Event::FindEvent + ( + str &name + ) + + { + return FindEvent( name.c_str() ); + } + +EXPORT_FROM_DLL void Event::ListCommands + ( + const char *mask + ) + + { + str name; + int flags; + int eventnum; + int num; + int i; + int n; + int l; + int p; + int hidden; + str text; + + if ( !commandList ) + { + gi.printf( "No events.\n" ); + return; + } + + if ( dirtylist ) + { + SortEventList(); + } + + l = 0; + if ( mask ) + { + l = strlen( mask ); + } + + hidden = 0; + num = 0; + n = sortedList->NumObjects(); + for( i = 1; i <= n; i++ ) + { + eventnum = sortedList->ObjectAt( i ); + name = commandList->ObjectAt( eventnum )->c_str(); + flags = flagList->ObjectAt( eventnum ); + + if ( flags & EV_HIDE ) + { + hidden++; + continue; + } + + if ( mask && Q_strncasecmp( name.c_str(), mask, l ) ) + { + continue; + } + + num++; + + text = " "; + p = 0; + if ( flags & EV_CONSOLE ) + { + text[ p++ ] = '*'; + } + if ( flags & EV_CHEAT ) + { + text[ p++ ] = 'C'; + } + + gi.printf( "%4d : %s%s\n", eventnum, text.c_str(), name.c_str() ); + } + + gi.printf( "\n* = console command.\nC = cheat command.\n\n" + "Printed %d of %d total commands.\n", num, n - hidden ); + + if ( developer->value && hidden ) + { + gi.printf( "Suppressed %d commands.\n", hidden ); + } + } + +EXPORT_FROM_DLL void Event::initCommandList + ( + void + ) + + { + int flags; + str *n; + + flags = 0; + commandList = new Container; + + n = new str( "NULL" ); + NullEvent.eventnum = commandList->AddObject( n ); + + flagList = new Container; + flagList->AddObject( flags ); + + sortedList = new Container; + sortedList->AddObject( NullEvent.eventnum ); + + dirtylist = false; + + NullEvent.data = NULL; + NullEvent.info.inuse = 0; + NullEvent.info.source = EV_FROM_CODE; + NullEvent.info.flags = 0; + NullEvent.info.linenumber = 0; + } + +Event::Event() + { + info.inuse = 0; + info.source = EV_FROM_CODE; + info.flags = 0; + info.linenumber = 0; + threadnum = -1; + eventnum = 0; + data = NULL; + name = NULL; + } + +Event::Event + ( + int num + ) + + { + if ( !commandList ) + { + initCommandList(); + } + + assert( ( num > 0 ) && num <= commandList->NumObjects() ); + + if ( ( num <= 0 ) || ( num > commandList->NumObjects() ) ) + { + num = 0; + name = NULL; + info.flags = 0; + } + else + { + name = commandList->ObjectAt( num )->c_str(); + info.flags = flagList->ObjectAt( num ); + } + + eventnum = num; + data = NULL; + info.inuse = 0; + info.source = EV_FROM_CODE; + info.linenumber = 0; + threadnum = -1; + } + +Event::Event + ( + Event &ev + ) + + { + int num; + int i; + + eventnum = ( int )ev; + assert( ( eventnum > 0 ) && eventnum <= commandList->NumObjects() ); + data = NULL; + + name = commandList->ObjectAt( eventnum )->c_str(); + info.inuse = 0; + info.source = ev.info.source; + info.flags = ev.info.flags; + info.linenumber = ev.info.linenumber; + threadnum = ev.threadnum; + + if ( ev.data ) + { + num = ev.data->NumObjects(); + + data = new Container; + data->Resize( num ); + + for( i = 1; i <= num; i++ ) + { + data->AddObject( ev.data->ObjectAt( i ) ); + } + } + } + +Event::Event + ( + Event *ev + ) + + { + int num; + int i; + + assert( ev ); + if ( !ev ) + { + Class::error( "Event", "NULL Event\n" ); + } + + eventnum = ( int )*ev; + assert( ( eventnum > 0 ) && eventnum <= commandList->NumObjects() ); + data = NULL; + name = commandList->ObjectAt( eventnum )->c_str(); + info.inuse = 0; + info.source = ev->info.source; + info.flags = ev->info.flags; + info.linenumber = ev->info.linenumber; + threadnum = ev->threadnum; + if ( ev->data ) + { + num = ev->data->NumObjects(); + + data = new Container; + data->Resize( num ); + + for( i = 1; i <= num; i++ ) + { + data->AddObject( ev->data->ObjectAt( i ) ); + } + } + } + +Event::Event + ( + const char *command, + int flags + ) + + { + str c; + str *t; + + if ( !commandList ) + { + initCommandList(); + } + + c = command; + eventnum = FindEvent( c ); + if ( !eventnum ) + { + t = new str( c ); + eventnum = commandList->AddObject( t ); + // check for default flags + if ( flags == -1 ) + { + flags = 0; + } + flagList->AddObject( ( int )flags ); + sortedList->AddObject( eventnum ); + dirtylist = true; + } + + // Use the name stored in the command list in case the string passed in + // is not in static memory. + name = commandList->ObjectAt( eventnum )->c_str(); + + data = NULL; + info.inuse = 0; + info.source = EV_FROM_CODE; + info.linenumber = 0; + threadnum = -1; + + // If flags have changed, let the user know. It's probably a development bug. + int &flagobj = flagList->ObjectAt( eventnum ); + + // check for default flags + if ( flags == -1 ) + { + flags = flagobj; + } + + assert( flags == flagobj ); + if ( flags != flagobj ) + { + // Flags not equal. Use combined value. + flagobj |= flags; + } + + info.flags = flagobj; + } + +Event::Event + ( + str &command, + int flags + ) + + { + str *t; + + if ( !commandList ) + { + initCommandList(); + } + + eventnum = FindEvent( command ); + if ( !eventnum ) + { + t = new str( command ); + eventnum = commandList->AddObject( t ); + // check for default flags + if ( flags == -1 ) + { + flags = 0; + } + flagList->AddObject( flags ); + sortedList->AddObject( eventnum ); + dirtylist = true; + } + + // Use the name stored in the command list since the string passed in + // is not in static memory. + name = commandList->ObjectAt( eventnum )->c_str(); + data = NULL; + info.inuse = 0; + info.source = EV_FROM_CODE; + info.linenumber = 0; + threadnum = -1; + + // If flags have changed, let the user know. It's probably a development bug. + int &flagobj = flagList->ObjectAt( eventnum ); + + // check for default flags + if ( flags == -1 ) + { + flags = flagobj; + } + + assert( flags == flagobj ); + if ( flags != flagobj ) + { + // Flags not equal. Use combined value. + flagobj |= flags; + } + + info.flags = flagobj; + } + +EXPORT_FROM_DLL Event::~Event() + { + if ( data ) + { + delete data; + data = NULL; + } + } + +EXPORT_FROM_DLL void Event::SetThread + ( + ScriptThread *thread + ) + + { + if ( thread ) + { + threadnum = thread->ThreadNum(); + } + else + { + threadnum = -1; + } + } + +EXPORT_FROM_DLL ScriptThread *Event::GetThread + ( + void + ) + + { + if ( threadnum != -1 ) + { + return Director.GetThread( threadnum ); + } + + return NULL; + } + +EXPORT_FROM_DLL void Event::Error + ( + const char *fmt, + ... + ) + + { + va_list argptr; + char text[ 1024 ]; + ScriptThread *thread; + const char *filename; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + switch( GetSource() ) + { + default : + case EV_FROM_CODE : + gi.dprintf( "Game: '%s' : %s\n", getName().c_str(), text ); + break; + + case EV_FROM_SCRIPT : + thread = GetThread(); + filename = "Dead script"; + if ( thread ) + { + filename = thread->Filename(); + } + gi.dprintf( "%s(%d): '%s' :\n%s\n", filename, info.linenumber, getName().c_str(), text ); + break; + + case EV_FROM_CONSOLE : + gi.cprintf( GetConsoleEdict(), PRINT_HIGH, "Console: '%s' : %s\n", getName().c_str(), text ); + break; + } + } + +EXPORT_FROM_DLL void Event::AddEntity + ( + Entity *ent + ) + + { + char text[ 128 ]; + + assert( ent ); + if ( !ent ) + { + sprintf( text, "*0" ); + } + else + { + sprintf( text, "*%d", ent->entnum ); + } + AddString( text ); + } + +EXPORT_FROM_DLL qboolean Event::IsVectorAt + ( + int pos + ) + + { + const char *text; + ScriptVariable *var; + + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return false; + } + + text = data->ObjectAt( pos ).c_str(); + assert( text ); + + var = Director.GetExistingVariable( text ); + if ( var ) + { + text = var->stringValue(); + } + + if ( text[ 0 ] == '(' ) + { + // probably a vector, so say that it is + return true; + } + + // not a vector + return false; + } + +EXPORT_FROM_DLL qboolean Event::IsEntityAt + ( + int pos + ) + + { + const char *name; + int t; + ScriptVariable *var; + + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return false; + } + + name = data->ObjectAt( pos ).c_str(); + assert( name ); + + var = Director.GetExistingVariable( name ); + if ( var ) + { + name = var->stringValue(); + } + + if ( name[ 0 ] == '$' ) + { + t = G_FindTarget( 0, &name[ 1 ] ); + if ( !t ) + { + Error( "Entity with targetname of '%s' not found", &name[ 1 ] ); + + return false; + } + } + else + { + if ( name[ 0 ] != '*' ) + { + Error( "Expecting a '*'-prefixed entity number but found '%s'.", name ); + + return false; + } + + if ( !IsNumeric( &name[ 1 ] ) ) + { + Error( "Expecting a numeric value but found '%s'.", &name[ 1 ] ); + + return false; + } + else + { + t = atoi( &name[ 1 ] ); + } + } + + if ( ( t < 0 ) || ( t > game.maxentities ) ) + { + Error( "%d out of valid range for entity.", t ); + return false; + } + + // only return true if the entity exists + return ( G_GetEntity( t ) != NULL ); + } + +EXPORT_FROM_DLL qboolean Event::IsNumericAt + ( + int pos + ) + + { + const char *text; + ScriptVariable *var; + + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return false; + } + + text = data->ObjectAt( pos ).c_str(); + assert( text ); + + var = Director.GetExistingVariable( text ); + if ( var ) + { + text = var->stringValue(); + } + + return IsNumeric( text ); + } + +EXPORT_FROM_DLL const char *Event::GetString + ( + int pos + ) + + { + const char *text; + ScriptVariable *var; + + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return ""; + } + + text = data->ObjectAt( pos ).c_str(); + assert( text ); + + var = Director.GetExistingVariable( text ); + if ( var ) + { + return var->stringValue(); + } + return text; + } + +EXPORT_FROM_DLL int Event::GetInteger + ( + int pos + ) + + { + const char *text; + ScriptVariable *var; + + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return 0; + } + + text = data->ObjectAt( pos ).c_str(); + assert( text ); + + var = Director.GetExistingVariable( text ); + if ( var ) + { + if ( !IsNumeric( var->stringValue() ) ) + { + Error( "Variable %s contains non-numeric value '%s'", text, var->stringValue() ); + } + return var->intValue(); + } + + if ( !IsNumeric( text ) ) + { + Error( "Expecting a numeric value but found '%s'.", text ); + return 0; + } + + return atoi( text ); + } + +EXPORT_FROM_DLL double Event::GetDouble + ( + int pos + ) + + { + const char *text; + ScriptVariable *var; + + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return 0; + } + + text = data->ObjectAt( pos ).c_str(); + assert( text ); + + var = Director.GetExistingVariable( text ); + if ( var ) + { + if ( !IsNumeric( var->stringValue() ) ) + { + Error( "Variable %s contains non-numeric value '%s'", text, var->stringValue() ); + } + return ( double )var->floatValue(); + } + + if ( !IsNumeric( text ) ) + { + Error( "Expecting a numeric value but found '%s'.", text ); + return 0; + } + + return ( double )atof( text ); + } + +EXPORT_FROM_DLL float Event::GetFloat + ( + int pos + ) + + { + const char *text; + ScriptVariable *var; + + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return 0; + } + + text = data->ObjectAt( pos ).c_str(); + assert( text ); + + var = Director.GetExistingVariable( text ); + if ( var ) + { + if ( !IsNumeric( var->stringValue() ) ) + { + Error( "Variable %s contains non-numeric value '%s'", text, var->stringValue() ); + } + return var->floatValue(); + } + + if ( !IsNumeric( text ) ) + { + Error( "Expecting a numeric value but found '%s'.", text ); + return 0; + } + + return atof( text ); + } + +EXPORT_FROM_DLL Vector Event::GetVector + ( + int pos + ) + + { + const char *text; + ScriptVariable *var; + + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return vec_zero; + } + + text = data->ObjectAt( pos ).c_str(); + assert( text ); + + var = Director.GetExistingVariable( text ); + if ( var ) + { + text = var->stringValue(); + } + + // Check if this is a ()-based vector + // we accept both, but using parenthesis we can determine if it is a vector or not + if ( text[ 0 ] == '(' ) + { + return Vector( &text[ 1 ] ); + } + + // give an error, but try converting it anyways + if ( developer->value > 1 ) + { + Error( "Vector '%s' does not include '(' ')'.", text ); + } + return Vector( text ); + } + +EXPORT_FROM_DLL Entity *Event::GetEntity + ( + int pos + ) + + { + const char *name; + int t; + ScriptVariable *var; + + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return NULL; + } + + name = data->ObjectAt( pos ).c_str(); + assert( name ); + + var = Director.GetExistingVariable( name ); + if ( var ) + { + name = var->stringValue(); + } + + if ( name[ 0 ] == '$' ) + { + t = G_FindTarget( 0, &name[ 1 ] ); + if ( !t ) + { + Error( "Entity with targetname of '%s' not found", &name[ 1 ] ); + + return NULL; + } + } + else + { + if ( name[ 0 ] != '*' ) + { + Error( "Expecting a '*'-prefixed entity number but found '%s'.", name ); + + return NULL; + } + + if ( !IsNumeric( &name[ 1 ] ) ) + { + Error( "Expecting a numeric value but found '%s'.", &name[ 1 ] ); + + return NULL; + } + else + { + t = atoi( &name[ 1 ] ); + } + } + + if ( ( t < 0 ) || ( t > game.maxentities ) ) + { + Error( "%d out of valid range for entity.", t ); + return NULL; + } + + return G_GetEntity( t ); + } + +EXPORT_FROM_DLL ScriptVariable *Event::GetVariable + ( + int pos + ) + + { + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return NULL; + } + + return Director.GetVariable( data->ObjectAt( pos ).c_str() ); + } + +EXPORT_FROM_DLL void Event::Archive + ( + Archiver &arc + ) + + { + str name; + int num; + int i; + + name = getName(); + arc.WriteString( name ); + + arc.WriteRaw( &info, sizeof( info ) ); + arc.WriteInteger( threadnum ); + + if ( !data ) + { + arc.WriteInteger( 0 ); + } + else + { + num = data->NumObjects(); + + arc.WriteInteger( num ); + for( i = 1; i <= num; i++ ) + { + arc.WriteString( data->ObjectAt( i ) ); + } + } + } + +EXPORT_FROM_DLL void Event::Unarchive + ( + Archiver &arc + ) + + { + Event ev; + str name; + int i; + int num; + + if ( data ) + { + delete data; + data = NULL; + } + + arc.ReadString( &name ); + *this = Event( name ); + + arc.ReadRaw( &info, sizeof( info ) ); + arc.ReadInteger( &threadnum ); + + arc.ReadInteger( &num ); + if ( num ) + { + data = new Container; + data->Resize( num ); + for( i = 1; i <= num; i++ ) + { + arc.ReadString( &name ); + data->AddObject( name ); + } + } + } + +CLASS_DECLARATION( Class, Listener, NULL ); + +ResponseDef Listener::Responses[] = + { + { &EV_Remove, ( Response )Listener::Remove }, + { &EV_ScriptRemove, ( Response )Listener::ScriptRemove }, + { NULL, NULL } + }; + +EXPORT_FROM_DLL void Listener::Remove + ( + Event *e + ) + + { + delete this; + } + +EXPORT_FROM_DLL void Listener::ScriptRemove + ( + Event *e + ) + + { + // Forces the remove to be done at a safe time + PostEvent( EV_Remove, 0 ); + } + +#if 0 +EXPORT_FROM_DLL void Listener::FloatVar + ( + Event &e, + float *var, + float defaultvalue + ) + + { + } + +EXPORT_FROM_DLL void Listener::IntVar + ( + Event &e, + int *var, + float defaultvalue + ) + + { + } + +EXPORT_FROM_DLL void Listener::StringVar + ( + Event &e, + str *var, + const char *defaultvalue + ) + + { + } + +EXPORT_FROM_DLL void Listener::StringVar + ( + Event &e, + char **var, + const char *defaultvalue + ) + + { + } + +EXPORT_FROM_DLL void Listener::VectorVar + ( + Event &e, + Vector *var, + Vector defaultvalue + ) + + { + } +#endif + +EXPORT_FROM_DLL qboolean Listener::ValidEvent + ( + Event &e + ) + + { + ClassDef *c; + int ev; + + ev = ( int )e; + + c = this->classinfo(); + assert( ( ev >= 0 ) && ( ev < c->numEvents ) ); + if ( ( ev < 0 ) || ( ev >= c->numEvents ) ) + { + warning( "ValidEvent", "Event '%s' out of response range for class '%s'. " + "Event probably invalid or allocated late.\n", e.getName().c_str(), getClassname() ); + return false; + } + + return ( c->responseLookup[ ev ] != NULL ); + } + +EXPORT_FROM_DLL qboolean Listener::ValidEvent + ( + const char *name + ) + + { + ClassDef *c; + int ev; + + ev = Event::FindEvent( name ); + + c = this->classinfo(); + assert( ( ev >= 0 ) && ( ev < c->numEvents ) ); + if ( ( ev < 0 ) || ( ev >= c->numEvents ) ) + { + warning( "ValidEvent", "Event '%s' out of response range for class '%s'. " + "Event probably invalid or allocated late.\n", name, getClassname() ); + return false; + } + + return ( c->responseLookup[ ev ] != NULL ); + } + +EXPORT_FROM_DLL qboolean Listener::EventPending + ( + Event &ev + ) + + { + eventcache_t *event; + int eventnum; + + assert( EventQueue ); + assert( EventQueue->next ); + + event = EventQueue->next; + + eventnum = ( int )ev; + while( event != EventQueue ) + { + if ( ( event->obj == this ) && ( (int)*event->event == eventnum ) ) + { + return true; + } + event = event->next; + } + + return false; + } + +EXPORT_FROM_DLL inline qboolean Listener::CheckEventFlags + ( + Event *event + ) + + { + // Special handling of console events + if ( event->GetSource() == EV_FROM_CONSOLE ) + { + if ( !( event->info.flags & (EV_CONSOLE|EV_CHEAT) ) ) + { + if ( isSubclassOf( Entity ) ) + { + Entity *ent; + + ent = ( Entity * )this; + gi.cprintf( ent->edict, PRINT_HIGH, "Command not available from console.\n" ); + } + + // don't process + return false; + } + + // don't allow console cheats during deathmatch unless the server says it's ok. + if ( ( event->info.flags & EV_CHEAT ) && deathmatch->value && !sv_cheats->value ) + { + if ( isSubclassOf( Entity ) ) + { + Entity *ent; + + ent = ( Entity * )this; + gi.cprintf( ent->edict, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n" ); + } + + // don't process + return false; + } + } + + // ok to process + return true; + } + +EXPORT_FROM_DLL qboolean Listener::ProcessEvent + ( + Event *event + ) + + { + ClassDef *c; + int ev; + int i; + + // Prevent overflow of the inuse count + if ( event->info.inuse >= MAX_EVENT_USE ) + { + gi.error( "ProcessEvent : Event usage overflow on '%s' event. Possible infinite loop.\n", event->getName().c_str() ); + } + + if ( g_showevents->value ) + { + int n; + str text; + + text = va( "%.1f: %s", level.time, this->getClassname() ); + if ( isSubclassOf( Entity ) ) + { + text += va( " (*%d) ", ( ( Entity * )this )->entnum ); + if ( ( ( Entity * )this )->Targeted() ) + { + text += va( "'%s'", ( ( Entity * )this )->TargetName() ); + } + } + else if ( isSubclassOf( ScriptThread ) ) + { + text += va( " #%d:'%s'", ( ( ScriptThread * )this )->ThreadNum(), ( ( ScriptThread * )this )->ThreadName() ); + } + else if ( isSubclassOf( ScriptVariable ) ) + { + text += va( " '%s'", ( ( ScriptVariable * )this )->getName() ); + } + + switch( event->GetSource() ) + { + default : + case EV_FROM_CODE : + text += " : Code :"; + break; + + case EV_FROM_SCRIPT : + assert( event->GetThread() ); + text += va( " : %s(%d) :", event->GetThread()->Filename(), event->info.linenumber ); + break; + + case EV_FROM_CONSOLE : + text += " : Console :"; + break; + } + + text += event->getName().c_str(); + n = event->NumArgs(); + for( i = 1; i <= n; i++ ) + { + text += va( " %s", event->GetToken( i ) ); + } + + text += "\n"; + + if ( !g_watch->value || ( isSubclassOf( Entity ) && ( g_watch->value == ( ( Entity * )this )->entnum ) ) ) + { + if ( g_showevents->value == 2 ) + { + G_DebugPrintf( text.c_str() ); + } + else + { + gi.dprintf( "%s", text.c_str() ); + } + } + } + + ev = ( int )*event; + c = this->classinfo(); + if ( ev >= c->numEvents ) + { + event->Error( "Event out of response range for class '%s'. Event probably invalid or allocated late.\n", getClassname() ); + return false; + } + + if ( c->responseLookup[ ev ] ) + { + int start; + int end; + + event->info.inuse++; + + if ( !g_timeevents->value ) + { + // only process the event if we allow it + if ( CheckEventFlags( event ) ) + { + ( this->**c->responseLookup[ ev ] )( event ); + } + } + else + { + start = G_Milliseconds(); + + // only process the event if we allow it + if ( CheckEventFlags( event ) ) + { + ( this->**c->responseLookup[ ev ] )( event ); + } + + end = G_Milliseconds(); + if ( g_timeevents->value == 1 ) + { + gi.printf( "'%s' : %d\n", event->getName().c_str(), end - start ); + } + else + { + G_DebugPrintf( "'%s' : %d\n", event->getName().c_str(), end - start ); + } + } + + // Prevent an event from being freed twice, in case it's been re-used + event->info.inuse--; + if ( !event->info.inuse ) + { + delete event; + } + + return true; + } + + if ( !event->info.inuse ) + { + delete event; + } + + return false; + } + +EXPORT_FROM_DLL void Listener::PostEvent + ( + Event *ev, + float time + ) + + { + eventcache_t *newevent; + eventcache_t *event; + + if ( LoadingSavegame ) + { + if ( !ev->info.inuse ) + { + delete ev; + } + + return; + } + + if ( LL_Empty( FreeEvents, next, prev ) ) + { + gi.error( "PostEvent : No more free events on '%s' event.\n", ev->getName().c_str() ); + return; + } + + newevent = FreeEvents->next; + LL_Remove( newevent, next, prev ); + + // Probably don't have this many events, but it's a good safety precaution + if ( ev->info.inuse >= MAX_EVENT_USE ) + { + gi.error( "PostEvent : Event usage overflow on '%s' event. Possible infinite loop.\n", ev->getName().c_str() ); + return; + } + + ev->info.inuse++; + + newevent->obj = this; + newevent->event = ev; + newevent->time = level.time + time; + + event = EventQueue->next; + while( ( event != EventQueue ) && ( newevent->time >= event->time ) ) + { + event = event->next; + } + + LL_Add( event, newevent, next, prev ); + numEvents++; + } + +EXPORT_FROM_DLL qboolean Listener::PostponeEvent + ( + Event &ev, + float time + ) + + { + eventcache_t *event; + eventcache_t *node; + int eventnum; + + assert( EventQueue ); + assert( EventQueue->next ); + + eventnum = ( int )ev; + + event = EventQueue->next; + while( event != EventQueue ) + { + if ( ( event->obj == this ) && ( ( int )*event->event == eventnum ) ) + { + event->time += time; + + node = event->next; + while( ( node != EventQueue ) && ( event->time >= node->time ) ) + { + node = node->next; + } + + LL_Remove( event, next, prev ); + LL_Add( node, event, next, prev ); + + return true; + } + event = event->next; + } + + return false; + } + +EXPORT_FROM_DLL void Listener::CancelEventsOfType + ( + Event *ev + ) + + { + eventcache_t *event; + eventcache_t *next; + int eventnum; + + assert( EventQueue ); + assert( EventQueue->next ); + + event = EventQueue->next; + + eventnum = (int)*ev; + while( event != EventQueue ) + { + next = event->next; + if ( ( event->obj == this ) && ( (int)*event->event == eventnum ) ) + { + delete event->event; + event->event = NULL; + LL_Remove( event, next, prev ); + LL_Add( FreeEvents, event, next, prev ); + numEvents--; + } + event = next; + } + } + +EXPORT_FROM_DLL void Listener::CancelPendingEvents + ( + void + ) + + { + eventcache_t *event; + eventcache_t *next; + + assert( EventQueue ); + assert( EventQueue->next ); + + event = EventQueue->next; + + while( event != EventQueue ) + { + next = event->next; + if ( event->obj == this ) + { + delete event->event; + event->event = NULL; + LL_Remove( event, next, prev ); + LL_Add( FreeEvents, event, next, prev ); + numEvents--; + } + event = next; + } + } + +EXPORT_FROM_DLL qboolean Listener::ProcessPendingEvents + ( + void + ) + + { + eventcache_t *event; + qboolean processedEvents; + float t; + + assert( EventQueue ); + assert( EventQueue->next ); + assert( EventQueue->prev ); + + processedEvents = false; + + t = level.time + 0.001; + + event = EventQueue->next; + while( event != EventQueue ) + { + assert( event ); + assert( event->event ); + assert( event->obj ); + + if ( event->time > t ) + { + break; + } + + if ( event->obj != this ) + { + // traverse normally + event = event->next; + } + else + { + LL_Remove( event, next, prev ); + numEvents--; + + assert( event->obj ); + + // ProcessEvent increments the inuse count, so decrement it since we've already incremented it in PostEvent + event->event->info.inuse--; + + event->obj->ProcessEvent( event->event ); + + event->event = NULL; + LL_Add( FreeEvents, event, next, prev ); + + // start over, since can't guarantee that we didn't process any previous or following events + event = EventQueue->next; + + processedEvents = true; + } + } + + return processedEvents; + } + +EXPORT_FROM_DLL Listener::~Listener() + { + CancelPendingEvents(); + } + +EXPORT_FROM_DLL void G_ClearEventList + ( + void + ) + + { + int i; + eventcache_t *e; + + LL_Reset( FreeEvents, next, prev ); + LL_Reset( EventQueue, next, prev ); + memset( Events, 0, sizeof( Events ) ); + + for( e = &Events[ 0 ], i = 0; i < MAX_EVENTS; i++, e++ ) + { + LL_Add( FreeEvents, e, next, prev ); + } + + numEvents = 0; + } + +EXPORT_FROM_DLL void G_ProcessPendingEvents + ( + void + ) + + { + eventcache_t *event; + float t; + int num; + int maxevents; + + assert( EventQueue ); + assert( EventQueue->next ); + assert( EventQueue->prev ); + + maxevents = ( int )g_eventlimit->value; + + num = 0; + t = level.time + 0.001; + while( !LL_Empty( EventQueue, next, prev ) ) + { + event = EventQueue->next; + + assert( event ); + assert( event->event ); + assert( event->obj ); + + if ( event->time > t ) + { + break; + } + + LL_Remove( event, next, prev ); + numEvents--; + + assert( event->obj ); + + // ProcessEvent increments the inuse count, so decrement it since we've already incremented it in PostEvent + assert( event->event->info.inuse > 0 ); + event->event->info.inuse--; + + event->obj->ProcessEvent( event->event ); + + event->event = NULL; + LL_Add( FreeEvents, event, next, prev ); + + // Don't allow ourselves to stay in here too long. An abnormally high number + // of events being processed is evidence of an infinite loop of events. + num++; + if ( num > maxevents ) + { + gi.printf( "Event overflow. Possible infinite loop in script. " + "Enable g_showevents to trace. Aborting frame.\n" ); + break; + } + } + } + +EXPORT_FROM_DLL void G_ArchiveEvents + ( + Archiver &arc + ) + + { + eventcache_t *event; + int num; + + assert( EventQueue ); + assert( EventQueue->next ); + assert( EventQueue->prev ); + + num = 0; + for( event = EventQueue->next; event != EventQueue; event = event->next ) + { + assert( event ); + assert( event->event ); + assert( event->obj ); + + if ( event->obj->isSubclassOf( Entity ) && + ( ( ( Entity * )event->obj )->flags & FL_DONTSAVE ) ) + { + continue; + } + + num++; + } + + arc.WriteInteger( num ); + for( event = EventQueue->next; event != EventQueue; event = event->next ) + { + assert( event ); + assert( event->event ); + assert( event->obj ); + + if ( event->obj->isSubclassOf( Entity ) && + ( ( ( Entity * )event->obj )->flags & FL_DONTSAVE ) ) + { + continue; + } + + arc.WriteObjectPointer( event->obj ); + arc.WriteEvent( *event->event ); + arc.WriteFloat( event->time ); + } + } + +EXPORT_FROM_DLL void G_UnarchiveEvents + ( + Archiver &arc + ) + + { + eventcache_t *e; + int i; + + LL_Reset( FreeEvents, next, prev ); + LL_Reset( EventQueue, next, prev ); + memset( Events, 0, sizeof( Events ) ); + + arc.ReadInteger( &numEvents ); + for( e = &Events[ 0 ], i = 0; i < numEvents; i++, e++ ) + { + arc.ReadObjectPointer( ( Class ** )&e->obj ); + e->event = new Event(); + arc.ReadEvent( e->event ); + arc.ReadFloat( &e->time ); + + LL_Add( EventQueue, e, next, prev ); + } + + for( ; i < MAX_EVENTS; i++, e++ ) + { + LL_Add( FreeEvents, e, next, prev ); + } + } + +EXPORT_FROM_DLL void G_InitEvents + ( + void + ) + + { + g_numevents = gi.cvar ( "g_numevents", "0", 0 ); + g_showevents = gi.cvar ( "g_showevents", "0", 0 ); + g_eventlimit = gi.cvar ( "g_eventlimit", "1500", 0 ); + g_timeevents = gi.cvar ( "g_timeevents", "0", 0 ); + g_watch = gi.cvar ( "g_watch", "0", 0 ); + + BuildEventResponses(); + G_ClearEventList(); + + // Sort the list before we go on since we won't be adding any more events + Event::SortEventList(); + } diff --git a/listener.h b/listener.h new file mode 100644 index 0000000..5bb55c4 --- /dev/null +++ b/listener.h @@ -0,0 +1,662 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/listener.h $ +// $Revision:: 30 $ +// $Author:: Jimdose $ +// $Date:: 10/26/98 4:27p $ +// +// Copyright (C) 1998 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/listener.h $ +// +// 30 10/26/98 4:27p Jimdose +// Sped up ValidEvent +// Added FindEvent( const char * ) +// +// 29 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 28 10/18/98 8:45p Jimdose +// changed commandList to a str * container +// +// 27 10/18/98 3:21a Jimdose +// made the assert in getName only fire if eventnum is non-zero +// +// 26 10/17/98 11:33p Jimdose +// Added the event name to Event to help with debugging +// +// 25 10/08/98 12:39a Jimdose +// Added event archiving functions +// +// 24 9/25/98 4:41p Markd +// Changed Find functions to return an Event rather than a &Event, made the +// Exists function +// +// 23 9/24/98 1:45a Jimdose +// Made Event a subclass of Class +// +// 22 9/19/98 4:33p Jimdose +// Added ListCommands and EV_HIDE +// +// 21 9/02/98 7:48p Aldie +// Moved template around +// +// 20 8/27/98 9:04p Jimdose +// Moved a lot of small functions to the header as inline +// +// 19 8/01/98 7:58p Jimdose +// Fixed bug with cheats in dm +// +// 18 7/31/98 8:10p Jimdose +// Script commands now include flags to indicate cheats and console commands +// +// 17 7/31/98 4:19p Jimdose +// Added DepthOfEvent +// +// 16 7/20/98 5:08p Jimdose +// Added ProcessPendingEvents +// +// 15 7/10/98 10:00p Jimdose +// Made remove script command post remove event so that it doesn't cause +// problems during event callbacks which trigger the script +// +// 14 6/30/98 6:05p Jimdose +// Added IsVectorAt, IsEntityAt, and IsNumericAt for doing type checking on +// args +// Changed format for storing vectors to make it easy to identify +// +// 13 6/27/98 9:18p Jimdose +// Made lookup for event responses for faster processing +// +// 12 6/18/98 8:48p Jimdose +// Added better event error handling +// Added source info to events +// +// 11 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 +// +// 10 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 9 5/24/98 1:06a Jimdose +// added const to various char *s +// +// 8 5/08/98 2:51p Jimdose +// Moved archiving functions up to Class +// +// 7 5/07/98 10:42p Jimdose +// Added archive and unarchive +// +// 6 4/30/98 9:24p Jimdose +// Changed use of string to str class +// +// 5 4/02/98 4:47p Jimdose +// Added initCommandList +// +// 4 3/24/98 5:03p Jimdose +// Changed order of prototypes +// +// 3 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 2 3/04/98 1:01p Jimdose +// Created file +// +// DESCRIPTION: +// + +#include "g_local.h" +#include "class.h" +#include "container.h" + +#ifndef __LISTENER_H__ +#define __LISTENER_H__ + +class Entity; +class ScriptVariable; + +typedef enum + { + EV_FROM_CODE, + EV_FROM_CONSOLE, + EV_FROM_SCRIPT + } eventsource_t; + +// Event flags +#define EV_CONSOLE 1 // Allow entry from console +#define EV_CHEAT 2 // Only allow entry from console if cheats are enabled +#define EV_HIDE 4 // Hide from eventlist + +#define MAX_EVENT_USE ( ( 1 << 8 ) - 1 ) + +class ScriptThread; +class Archiver; + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL Event : public Class + { + private: + typedef struct EventInfo + { + unsigned inuse : 8; // must change MAX_EVENT_USE to reflect maximum number stored here + unsigned source : 2; + unsigned flags : 2; + unsigned linenumber : 20; // linenumber does double duty in the case of the console commands + }; + + int eventnum; + EventInfo info; + const char *name; + Container *data; + int threadnum; + + Event( int num ); + static void initCommandList( void ); + + friend class Listener; + + friend void EXPORT_FROM_DLL G_ProcessPendingEvents( void ); + friend void EXPORT_FROM_DLL G_ClearEventList( void ); + friend void EXPORT_FROM_DLL G_InitEvents( void ); + friend void EXPORT_FROM_DLL G_ArchiveEvents( Archiver &arc ); + friend void EXPORT_FROM_DLL G_UnarchiveEvents( Archiver &arc ); + + static Container *Event::commandList; + static Container *Event::flagList; + static Container *Event::sortedList; + static qboolean Event::dirtylist; + + static int compareEvents( const void *arg1, const void *arg2 ); + static void SortEventList( void ); + static int FindEvent( const char *name ); + static int FindEvent( str &name ); + + public: + + CLASS_PROTOTYPE( Event ); + + static int NumEventCommands( void ); + static void ListCommands( const char *mask = NULL ); + + Event(); + Event( Event &ev ); + Event( Event *ev ); + Event( const char *command, int flags = -1 ); + Event( str &command, int flags = -1 ); + ~Event(); + + str getName( void ); + + void SetSource( eventsource_t source ); + void SetLineNumber( int linenumber ); + void SetConsoleEdict( edict_t *consoleedict ); + void SetThread( ScriptThread *thread ); + + eventsource_t GetSource( void ); + ScriptThread *GetThread( void ); + edict_t *GetConsoleEdict( void ); + int GetLineNumber( void ); + + void Error( const char *fmt, ... ); + + static Event Find( const char *command ); + static qboolean Exists( const char *command ); + static Event Find( str &command ); + + Event& printInfo(void); + + operator int(); + operator const char *(); + + int NumArgs( void ); + + qboolean IsVectorAt( int pos ); + qboolean IsEntityAt( int pos ); + qboolean IsNumericAt( int pos ); + + const char *GetToken( int pos ); + const char *GetString( int pos ); + int GetInteger( int pos ); + double GetDouble( int pos ); + float GetFloat( int pos ); + Vector GetVector( int pos ); + Entity *GetEntity( int pos ); + ScriptVariable *GetVariable( int pos ); + + void AddToken( const char *text ); + void AddTokens( int argc, const char **argv ); + void AddString( const char *text ); + void AddString( str &text ); + void AddInteger( int val ); + void AddDouble( double val ); + void AddFloat( float val ); + void AddVector( Vector &vec ); + void AddEntity( Entity *ent ); + + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +extern Event NullEvent; +extern Event EV_Remove; + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +#endif + +class Listener; + +class EXPORT_FROM_DLL Listener : public Class + { + private: + void FloatVarEvent( Event *e ); + void IntVarEvent( Event *e ); + void StringVarEvent( Event *e ); + void CharPtrVarEvent( Event *e ); + void VectorVarEvent( Event *e ); + void ScriptRemove( Event *e ); + + protected: + void FloatVar( Event &e, float *var, float defaultvalue = 0 ); + void IntVar( Event &e, int *var, float defaultvalue = 0 ); + void StringVar( Event &e, str *var, const char *defaultvalue = "" ); + void StringVar( Event &e, char **var, const char *defaultvalue = "" ); + void VectorVar( Event &e, Vector *var, Vector defaultvalue = Vector( 0, 0, 0 ) ); + + qboolean CheckEventFlags( Event *event ); + + public: + CLASS_PROTOTYPE( Listener ); + + ~Listener(); + void Remove( Event *e ); + qboolean ValidEvent( Event &e ); + qboolean ValidEvent( const char *name ); + qboolean EventPending( Event &ev ); + qboolean ProcessEvent( Event *event ); + qboolean ProcessEvent( Event &event ); + void PostEvent( Event *event, float time ); + void PostEvent( Event &event, float time ); + qboolean PostponeEvent( Event &event, float time ); + qboolean PostponeEvent( Event *event, float time ); + void CancelEventsOfType( Event *event ); + void CancelEventsOfType( Event &event ); + void CancelPendingEvents( void ); + qboolean ProcessPendingEvents( void ); + }; + +inline EXPORT_FROM_DLL qboolean Event::Exists + ( + const char *command + ) + + { + int num; + str c; + + if ( !commandList ) + { + initCommandList(); + } + + c = command; + num = FindEvent( c ); + if ( num ) + { + return true; + } + + return false; + } + + +inline EXPORT_FROM_DLL Event Event::Find + ( + const char *command + ) + + { + int num; + str c; + + if ( !commandList ) + { + initCommandList(); + } + + c = command; + num = FindEvent( c ); + if ( num ) + { + return Event( num ); + } + + return NullEvent; + } + +inline EXPORT_FROM_DLL Event Event::Find + ( + str &command + ) + + { + int num; + + if ( !commandList ) + { + initCommandList(); + } + + num = FindEvent( command ); + if ( num ) + { + return Event( num ); + } + + return NullEvent; + } + +inline EXPORT_FROM_DLL void Event::SetSource + ( + eventsource_t source + ) + + { + info.source = ( unsigned )source; + } + +inline EXPORT_FROM_DLL void Event::SetLineNumber + ( + int linenumber + ) + + { + info.linenumber = linenumber; + } + +inline EXPORT_FROM_DLL void Event::SetConsoleEdict + ( + edict_t *consoleedict + ) + + { + assert( consoleedict ); + + // linenumber does double duty in the case of the console commands + if ( consoleedict ) + { + info.linenumber = consoleedict->s.number; + } + else + { + // default to player 1 + info.linenumber = 1; + } + } + +inline EXPORT_FROM_DLL eventsource_t Event::GetSource + ( + void + ) + + { + return ( eventsource_t )info.source; + } + +inline EXPORT_FROM_DLL edict_t *Event::GetConsoleEdict + ( + void + ) + + { + // linenumber does double duty in the case of the console commands + if ( ( info.source != EV_FROM_CONSOLE ) || ( info.linenumber < 1 ) || ( info.linenumber > game.maxclients ) ) + { + assert( NULL ); + + // default to player 1 for release + return &g_edicts[ 1 ]; + } + + return &g_edicts[ info.linenumber ]; + } + +inline EXPORT_FROM_DLL int Event::GetLineNumber + ( + void + ) + + { + // linenumber does double duty in the case of the console commands + if ( info.source == EV_FROM_SCRIPT ) + { + return info.linenumber; + } + + return 0; + } + +inline EXPORT_FROM_DLL str Event::getName + ( + void + ) + + { + assert( name || !eventnum ); + + if ( !name ) + { + return "NULL"; + } + + return name; + } + +inline EXPORT_FROM_DLL Event& Event::printInfo + ( + void + ) + + { + gi.dprintf( "event '%s' is number %d\n", getName().c_str(), eventnum ); + + return *this; + } + +inline EXPORT_FROM_DLL Event::operator int() + { + return eventnum; + } + +inline EXPORT_FROM_DLL Event::operator const char *() + { + return getName().c_str(); + } + +inline EXPORT_FROM_DLL int Event::NumArgs + ( + void + ) + + { + if ( !data ) + { + return 0; + } + + return ( data->NumObjects() ); + } + +inline EXPORT_FROM_DLL void Event::AddToken + ( + const char *text + ) + + { + AddString( text ); + } + +inline EXPORT_FROM_DLL void Event::AddTokens + ( + int argc, + const char **argv + ) + + { + int i; + + for( i = 0; i < argc; i++ ) + { + assert( argv[ i ] ); + AddString( argv[ i ] ); + } + } + +inline EXPORT_FROM_DLL void Event::AddString + ( + const char *text + ) + + { + if ( !data ) + { + data = new Container; + data->Resize( 1 ); + } + + data->AddObject( str( text ) ); + } + +inline EXPORT_FROM_DLL void Event::AddString + ( + str &text + ) + + { + if ( !data ) + { + data = new Container; + data->Resize( 1 ); + } + + data->AddObject( text ); + } + +inline EXPORT_FROM_DLL void Event::AddInteger + ( + int val + ) + + { + char text[ 128 ]; + + sprintf( text, "%d", val ); + AddString( text ); + } + +inline EXPORT_FROM_DLL void Event::AddDouble + ( + double val + ) + + { + char text[ 128 ]; + + sprintf( text, "%f", val ); + AddString( text ); + } + +inline EXPORT_FROM_DLL void Event::AddFloat + ( + float val + ) + + { + char text[ 128 ]; + + sprintf( text, "%f", val ); + AddString( text ); + } + +inline EXPORT_FROM_DLL void Event::AddVector + ( + Vector &vec + ) + + { + char text[ 128 ]; + + sprintf( text, "(%f %f %f)", vec[ 0 ], vec[ 1 ], vec[ 2 ] ); + AddString( text ); + } + +inline EXPORT_FROM_DLL const char *Event::GetToken + ( + int pos + ) + + { + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return ""; + } + + return data->ObjectAt( pos ).c_str(); + } + +inline EXPORT_FROM_DLL qboolean Listener::ProcessEvent + ( + Event &event + ) + + { + Event *ev; + + ev = new Event( event ); + return ProcessEvent( ev ); + } + +inline EXPORT_FROM_DLL void Listener::PostEvent + ( + Event &event, + float time + ) + + { + Event *ev; + + ev = new Event( event ); + PostEvent( ev, time ); + } + +inline EXPORT_FROM_DLL qboolean Listener::PostponeEvent + ( + Event *event, + float time + ) + + { + return PostponeEvent( *event, time ); + } + +inline EXPORT_FROM_DLL void Listener::CancelEventsOfType + ( + Event &event + ) + + { + CancelEventsOfType( &event ); + } + +#endif diff --git a/magnum.cpp b/magnum.cpp new file mode 100644 index 0000000..96ec137 --- /dev/null +++ b/magnum.cpp @@ -0,0 +1,206 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/magnum.cpp $ +// $Revision:: 46 $ +// $Author:: Markd $ +// $Date:: 11/13/98 3:30p $ +// +// Copyright (C) 1998 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/magnum.cpp $ +// +// 46 11/13/98 3:30p Markd +// put in more precaching on weapons +// +// 45 10/19/98 12:06a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// Added Drop +// +// 44 10/05/98 10:38p Aldie +// Fixed rank +// +// 43 10/05/98 10:18p Aldie +// Covnverted over to new silencer methods +// +// 42 8/06/98 10:53p Aldie +// Added weapon tweaks and kickback. Also modified blast radius damage and +// rocket jumping. +// +// 41 8/01/98 3:24p Aldie +// Added server effects flag for specific weapons +// +// 40 8/01/98 3:03p Aldie +// Client side muzzle flash (dynamic light) +// +// 39 7/22/98 9:57p Markd +// Defined weapon type +// +// 38 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 37 7/20/98 3:53p Aldie +// Fixed the icon +// +// 36 7/14/98 5:50p Aldie +// Tweaked the magnum +// +// 35 6/25/98 7:31p Aldie +// Changed the icon +// +// 34 6/24/98 1:36p Aldie +// Implementation of inventory system and picking stuff up +// +// 33 6/19/98 9:30p Jimdose +// Moved gun orientation code to Weapon +// +// 32 6/17/98 10:54a Aldie +// Updated silenced stuff +// +// 31 6/15/98 9:09p Aldie +// Added SilencedBullet class for silencers +// +// 30 6/10/98 7:53p Markd +// reduced time till next attack +// +// 29 6/10/98 2:10p Aldie +// Updated damage function. +// +// 28 6/10/98 1:19p Markd +// Made magnum use 10mm bullets instead of 357 +// +// 27 6/08/98 8:18p Markd +// set ammo_clip_size +// +// 26 5/08/98 2:56p Markd +// took out dbshotgn sound +// +// 25 4/20/98 1:56p Markd +// SINED decelration is now in def file +// +// 24 4/18/98 3:07p Markd +// Changed view weapon naming convention +// +// 23 4/16/98 7:46p Aldie +// Updated magnum to 100 bullets temporarily +// +// 22 4/09/98 3:28p Jimdose +// Removed sound from shoot since anim plays it +// +// 21 4/07/98 6:43p Jimdose +// Rewrote weapon code. +// Added order to rank +// +// 20 4/02/98 4:48p Jimdose +// Balanced for DM +// +// 19 3/30/98 9:55p Jimdose +// Changed location of .def files +// +// 18 3/30/98 2:33p Jimdose +// Moved firing to BulletWeapon to make more general +// Added Ammo +// Added world models +// +// 17 3/29/98 9:41p Jimdose +// Reduced the amount of damage bullets do +// +// 16 3/27/98 11:03p Jimdose +// Added muzzle flash +// +// 15 3/26/98 8:17p Jimdose +// Precached sounds +// +// 14 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 13 3/18/98 2:39p Jimdose +// Added the new model and made work with the new animation system +// +// 12 3/05/98 5:44p Aldie +// Removed gunshot temporarily. +// +// 11 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. +// +// 10 2/19/98 2:35p Jimdose +// Updated to work with Q2 based progs +// +// 8 12/11/97 7:41p Markd +// moved note processing into setmodel +// +// 7 12/09/97 7:47p Markd +// Moved some of the init stuff above the initial init call +// +// 6 11/18/97 5:31p Markd +// Changed Fire to Shoot +// Got rid of some thinking funciotns +// +// 5 10/31/97 4:28p Jimdose +// Removed redefinition of owner in base class Weapon, so any reference to +// gunoffset through owner had to use type overriding. +// +// 4 10/30/97 6:55p Jimdose +// Increased the damage for testing +// +// 3 10/28/97 8:36p Jimdose +// Moved model +// +// 2 10/24/97 8:13p Jimdose +// Created file. +// +// DESCRIPTION: +// Magnum. +// + +#include "g_local.h" +#include "magnum.h" + +CLASS_DECLARATION( BulletWeapon, Magnum, "weapon_magnum" ); + +ResponseDef Magnum::Responses[] = + { + { &EV_Weapon_Shoot, ( Response )Magnum::Shoot }, + { NULL, NULL } + }; + +Magnum::Magnum() + { + SetModels( "magnum.def", "view_magnum.def" ); + SetAmmo( "Bullet10mm", 1, 100 ); + SetRank( 20, 20 ); + SetType( WEAPON_1HANDED ); + modelIndex( "10mm.def" ); + silenced = true; + } + +void Magnum::Shoot + ( + Event *ev + ) + + { + NextAttack( 0.20 ); + FireBullets( 1, "10 10 10", 12, 24, DAMAGE_BULLET, MOD_MAGNUM, false ); + } + +qboolean Magnum::Drop + ( + void + ) + + { + // Don't leave magnums around + if ( owner && owner->deadflag && deathmatch->value ) + { + return false; + } + + return BulletWeapon::Drop(); + } diff --git a/magnum.h b/magnum.h new file mode 100644 index 0000000..45da263 --- /dev/null +++ b/magnum.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/magnum.h $ +// $Revision:: 13 $ +// $Author:: Jimdose $ +// $Date:: 10/19/98 12:06a $ +// +// 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/magnum.h $ +// +// 13 10/19/98 12:06a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// added Drop +// +// 12 10/05/98 10:38p Aldie +// Converted over to new silencer methods +// +// 11 6/17/98 10:56a Aldie +// Moved some functions to silenced bulletweapon. +// +// 10 6/15/98 9:12p Aldie +// Updated to silenced bullet +// +// 9 3/30/98 2:36p Jimdose +// Changed from subclass of Magnum to subclass of BulletWeapon +// +// 8 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 7 3/02/98 8:49p Jimdose +// Changed CLASS_PROTOTYPE to only take the classname +// +// 6 2/19/98 2:35p Jimdose +// Updated to work with Q2 based progs +// +// 4 11/18/97 5:30p Markd +// Changed Fire to Shoot +// got rid off a few thinking functions +// +// 3 10/27/97 2:59p Jimdose +// Removed dependency on quakedef.h +// +// 2 10/24/97 8:13p Jimdose +// Created file. +// +// DESCRIPTION: +// Magnum. +// + +#ifndef __MAGNUM_H__ +#define __MAGNUM_H__ + +#include "g_local.h" +#include "item.h" +#include "weapon.h" +#include "bullet.h" + +class EXPORT_FROM_DLL Magnum : public BulletWeapon + { + public: + CLASS_PROTOTYPE( Magnum ); + + Magnum::Magnum(); + virtual void Shoot( Event *ev ); + virtual qboolean Drop( void ); + }; + +#endif /* magnum.h */ diff --git a/misc.cpp b/misc.cpp new file mode 100644 index 0000000..ff736cb --- /dev/null +++ b/misc.cpp @@ -0,0 +1,2042 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/misc.cpp $ +// $Revision:: 152 $ +// $Author:: Jimdose $ +// $Date:: 11/19/98 9:29p $ +// +// 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/misc.cpp $ +// +// 152 11/19/98 9:29p Jimdose +// fixed telefrags +// +// 151 11/18/98 6:26p Markd +// Took out fov update for non zero gravaxis +// +// 150 11/16/98 11:15p Markd +// made shatter sound not use PHS +// +// 149 11/16/98 12:57a Markd +// made teleporters work with gravaxis +// +// 148 11/14/98 11:17p Markd +// bullet proofed glass sounds +// +// 147 11/14/98 7:57p Markd +// fixed some global alias problems +// +// 146 11/14/98 1:33a Markd +// fixed shatter using randomglobal sound instead of just normal sound +// +// 145 11/13/98 6:28p Aldie +// Make Oxygenator only respond to players +// +// 144 11/11/98 10:02p Jimdose +// added ClipBox +// +// 143 10/26/98 4:29p Aldie +// Changed misc_oxygen timer +// +// 142 10/25/98 9:11p Markd +// put in null other protection for activate targets +// +// 141 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 140 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 139 10/17/98 9:42p Markd +// got rid of event +// +// 138 10/17/98 8:14p Jimdose +// Changed Damage to DamgeEvent +// +// 137 10/16/98 12:58a Aldie +// Null check for shatter damage +// +// 136 10/14/98 12:12a Aldie +// Added intermission file ability +// +// 135 10/07/98 11:53p Jimdose +// Added BloodSplat +// +// 134 10/06/98 10:51p Aldie +// Created an oxygenator +// +// 133 10/05/98 11:29p Markd +// Added MakeBreakingSound +// +// 132 10/05/98 4:35p Markd +// Made Teleport check for NULL entity +// +// 131 9/18/98 8:14p Markd +// rewrote surface system so that surfaces are now damaged by surface name +// instead of by surfinfo +// +// 130 9/17/98 4:11p Aldie +// Teleport objects and a fix for damage_surfaces +// +// 129 9/15/98 6:37p Markd +// Added RotatedBounds flag support +// +// 128 9/14/98 3:12p Markd +// replaced zombie/z_hit with debris_generic +// +// 127 9/09/98 5:06p Markd +// change fov when teleporting +// +// 126 9/08/98 4:06p Markd +// Fixed damagable surfaces +// +// 125 9/02/98 12:54p Markd +// fixed teleporters for players +// +// 124 9/01/98 3:05p Markd +// Rewrote explosion code +// +// 123 8/31/98 7:45p Aldie +// Updated surface data structure and removed surfinfo field +// +// 122 8/29/98 9:44p Jimdose +// Added call info to G_Trace +// Moved bodyque to deadbody.cpp +// +// 121 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 120 8/29/98 2:53p Aldie +// Updated printing of location based damage +// +// 119 8/29/98 1:02p Markd +// objects won't shatter unless they hit the world +// +// 118 8/29/98 12:59p Markd +// Fixed func_explodingwall stuff +// +// 117 8/27/98 9:02p Jimdose +// Changed centroid to a variable +// +// 116 8/27/98 3:56p Jimdose +// Fixed NULL pointer bug in DamageSpecificSurface +// +// 115 8/26/98 5:37p Aldie +// Removed damage_threshold field from surfinfo +// +// 114 8/24/98 6:52p Jimdose +// Added SetGravityAxis +// +// 113 8/22/98 9:36p Jimdose +// Added support for alternate gravity axis +// +// 112 8/20/98 8:38p Jimdose +// DamageSpecificSurface now generates an AI sound event +// +// 111 8/19/98 8:49p Aldie +// Added random particles for server use +// +// 110 8/18/98 11:08p Markd +// Added new Alias System +// +// 109 8/17/98 4:34p Markd +// Added SendOverlay +// +// 108 8/08/98 9:03p Aldie +// Removed printf +// +// 107 8/08/98 8:51p Aldie +// Added func_spawnchain and changed spawns to entities +// +// 106 8/08/98 3:16p Aldie +// Added func_spawnoutofsight to spawn things out of sight of the player. +// +// 105 8/04/98 5:56p Markd +// Added FuncRemove +// +// 104 8/02/98 9:00p Markd +// Merged code 3.17 +// +// 103 7/29/98 2:31p Aldie +// Changed health to a float +// +// 102 7/26/98 5:16a Aldie +// Fixed angle on the func_spawn +// +// 101 7/25/98 8:00p Aldie +// Move lightstyle client side +// +// 100 7/25/98 2:09a Jimdose +// Added an assertion to Projectile::Setup +// +// 99 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 +// +// 98 7/22/98 11:44a Aldie +// Updated doc for func_spawn stuff +// +// 97 7/21/98 4:22p Aldie +// Put some error checking in func_spawn and func_respawn +// +// 96 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 95 7/20/98 5:45p Markd +// Put in new shooting out light behaviors +// +// 94 7/19/98 10:42p Aldie +// Check for weird happenings of dead bodies. +// +// 93 7/17/98 6:08p Aldie +// Changed func_spawn targetname to spawntargetname +// +// 92 7/17/98 4:40p Aldie +// Added targetnames to func_spawn +// +// 91 7/15/98 6:27p Aldie +// Changed func spawn to get a string arg. +// +// 90 7/15/98 6:18p Markd +// re-ordered SpawnTempDlight arguments +// +// 89 7/14/98 3:53p Markd +// fixed velocity[2] factor of exploding stuff +// +// 88 7/13/98 4:59p Aldie +// Added dead player bodies with gibbing +// +// 87 7/11/98 4:26p Aldie +// Sined fix for func_spawn and func_respawn +// +// 86 7/11/98 4:12p Aldie +// Dialog layouts +// +// 85 7/11/98 2:49p Markd +// Added SendDialog event +// +// 84 7/10/98 10:00p Markd +// made func_explodingwalls work better +// +// 83 7/10/98 7:07p Aldie +// fixed bug with other not being set for explosion +// +// 82 7/10/98 4:10p Markd +// Revamped func_explodingwall +// +// 81 7/08/98 3:11p Aldie +// Added a func_respawn that triggers when the thing it spawns is killed. +// +// 80 7/03/98 4:10p Jimdose +// Fixed Detail brushes so they aren't removed twice (causing a crash) +// +// 79 7/02/98 7:57p Markd +// redid func_explodingwall and func_glass +// +// 78 6/30/98 4:37p Markd +// Added "noise" to func_shatter and func_glass +// +// 77 6/29/98 8:18p Aldie +// +// 76 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// 75 6/24/98 5:08p Aldie +// Removed some warnings from func_spawn +// +// 74 6/24/98 4:50p Aldie +// Made func_spawn work. +// +// 73 6/24/98 12:39p Markd +// Added default tesselation percentage +// +// 72 6/18/98 6:20p Markd +// put in support for breakable lights +// +// 71 6/18/98 2:00p Markd +// rewrote tesselation code +// +// 70 6/17/98 7:40p Markd +// Put in ActivateTargets for both func_shatter and func_glass +// +// 69 6/17/98 6:31p Aldie +// Put start and end back into beams. +// +// 68 6/16/98 10:04p Markd +// Put in generic function for DamageSpecificSurface, reworked older two +// +// 67 6/15/98 12:25p Aldie +// Updated pulse beam +// +// 66 6/15/98 10:37a Aldie +// Added SpawnPulseBeam +// +// 65 6/10/98 2:10p Aldie +// Updated damage function. +// +// 64 6/08/98 7:22p Aldie +// Added in Damage surface by name +// +// 63 5/28/98 1:23p Aldie +// Added SpawnRocketExplosion to do particles. +// +// 62 5/27/98 6:56p Markd +// Forgot to remove smoke after creating tempmodel +// +// 61 5/27/98 6:10p Markd +// temporarily took out blood splats +// +// 60 5/27/98 6:06p Aldie +// +// 59 5/27/98 4:55p Markd +// stopped rotating blood splat +// +// 58 5/27/98 4:25a Markd +// added bloodspray to SpawnBlood +// +// 57 5/26/98 11:38p Aldie +// Updated func_spawn +// +// 56 5/26/98 9:26p Aldie +// Added func_spawn +// +// 55 5/25/98 12:22p Aldie +// Added func_electrocute +// +// 54 5/25/98 7:04p Markd +// Fixed up TempModel command +// +// 53 5/25/98 5:39p Markd +// Fixed SpawnBlood, SpawnSparks and SpawnDlight +// +// 52 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 +// +// 51 5/24/98 3:48p Jimdose +// maxhealth was supposed to be max_health +// +// 50 5/24/98 3:36p Markd +// Added impact sounds for glass +// +// 49 5/24/98 1:03a Jimdose +// Added sound events for ai +// +// 48 5/23/98 10:39p Markd +// Added TempModel call +// +// 47 5/20/98 1:33p Markd +// Changed func_shatter behavior +// +// 46 5/18/98 8:13p Jimdose +// Renamed Navigator back to PathManager +// +// 45 5/16/98 4:59p Markd +// Changed func_shatter +// +// 44 5/13/98 6:19p Markd +// Added BurnWall +// +// 43 5/13/98 4:46p Aldie +// Fixed damage surfaces. +// +// 42 5/12/98 1:21p Aldie +// Update damage surfaces +// +// 41 5/06/98 7:48p Markd +// Added state and light style support to damaged textures +// +// 40 5/05/98 8:37p Aldie +// Added virtual setup function to Projectile. +// +// 39 5/05/98 5:34p Aldie +// Updated damage surfaces +// +// 38 5/05/98 2:38p Jimdose +// Disabled spawnblood +// +// 37 5/03/98 8:10p Markd +// Added SpawnSparks and patched SpawnBlood +// +// 36 5/03/98 4:35p Jimdose +// Changed Vector class +// +// 35 5/02/98 8:48p Markd +// Added lightstyle for blood +// +// 34 5/01/98 11:09a Markd +// Added sound to tesselation event +// +// 33 4/28/98 4:04p Jimdose +// Fixed typo in docs for func_shatter and func_glass +// +// 32 4/27/98 3:52p Jimdose +// Teleporters inform Navigator when used so that paths may be generated +// through them +// +// 31 4/18/98 2:32p Jimdose +// Added ExplodingWall, Shatter, and Glass +// +// 30 4/10/98 4:57p Jimdose +// Made bubbles work better +// +// 29 4/07/98 11:54p Jimdose +// Changed beams to use color_r, color_g, and color_b +// +// 28 4/05/98 5:05a Jimdose +// fixed teleport not responding bug +// +// 27 4/05/98 1:57a Jimdose +// Fixed bug in DamageSurface where the surface was referenced without checking +// for a null pointer +// +// 26 4/04/98 6:06p Jimdose +// Added Bubble, Projectile, and Smoke +// Made response from EV_Trigger_ActivateTargets to EV_Trigger_Effect +// +// 25 4/01/98 6:05p Jimdose +// Made setBeam use setOrigin +// +// 24 3/30/98 2:40p Jimdose +// Made teleporters handle non-clients +// Reenabled spawnblood +// +// 23 3/29/98 9:41p Jimdose +// Changed killed to an event +// +// 22 3/28/98 6:39p Jimdose +// Left some code commented out. oops! :P +// +// 21 3/28/98 6:37p Jimdose +// Made teleporters work +// +// 20 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 19 3/11/98 2:23p Jimdose +// Moved AreaPortals to areaportal.cpp +// Breakaway walls now use areaportals +// +// 18 3/05/98 3:48p Aldie +// More damage surfaces stuff. +// +// 17 3/04/98 8:01p Aldie +// More support for damage surfaces. +// +// 16 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. +// +// 15 3/02/98 5:42p Jimdose +// Moved light entities to light.cpp +// +// 14 2/27/98 12:50p Jimdose +// Added Light, LightRamp, and TriggerLightStyle +// +// 13 2/21/98 1:07p Jimdose +// Added Beam class +// +// 12 2/17/98 8:09p Jimdose +// Changed Detail to print a warning if it has a null model +// +// 11 2/06/98 5:39p Jimdose +// Moved Sined definitions into file +// +// 9 2/03/98 10:43a Jimdose +// Updated to work with Quake 2 engine +// +// 7 11/11/97 10:22a Markd +// Added precache for zombie/z_hit.wav +// +// 6 10/29/97 4:18p Jimdose +// Bubbles can now only exist in water. +// +// 5 10/28/97 8:20p Jimdose +// added Bubble +// +// 4 10/27/97 3:29p Jimdose +// Removed dependency on quakedef.h +// +// 3 10/01/97 11:24a Markd +// moved viewthing to its own file +// +// 2 9/26/97 6:14p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Basically the big stew pot of the DLLs, or maybe a garbage bin, whichever +// metaphore you prefer. This really should be cleaned up. Anyway, this +// should contain utility functions that could be used by any entity. +// Right now it contains everything from entities that could be in their +// own file to my mother's pot roast recipes. +// + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "explosion.h" +#include "areaportal.h" +#include "misc.h" +#include "navigate.h" +#include "deadbody.h" +#include "specialfx.h" +#include "player.h" + +/* +================ +SendDialog +================ +*/ +void SendDialog + ( + const char *icon_name, + const char *dialog_text + ) + { + char imageindex; + char temp[ 1024 ]; + + imageindex = gi.imageindex( icon_name ); + Com_sprintf( temp, sizeof( temp ), "dia %d \"%s\"", imageindex, dialog_text ); + gi.WriteByte( svc_console_command ); + gi.WriteString( temp ); + gi.multicast( vec3_origin, MULTICAST_ALL ); + } + +/* +================ +SendOverlay +================ +*/ +void SendOverlay + ( + Entity *ent, + str overlayname + ) + { + if ( ent ) + { + gi.WriteByte( svc_console_command ); + gi.WriteString( va( "lo %s",overlayname.c_str() ) ); + gi.unicast ( ent->edict, true); + } + else + { + gi.WriteByte( svc_console_command ); + gi.WriteString( va( "lo %s",overlayname.c_str() ) ); + gi.multicast( NULL, MULTICAST_ALL ); + } + } + +/* +================ +SendIntermission +================ +*/ +void SendIntermission + ( + Entity *ent, + str intermissionname + ) + { + if ( ent ) + { + gi.WriteByte( svc_console_command ); + gi.WriteString( va( "imf %s",intermissionname.c_str() ) ); + gi.unicast ( ent->edict, true); + } + else + { + gi.WriteByte( svc_console_command ); + gi.WriteString( va( "imf %s",intermissionname.c_str() ) ); + gi.multicast( NULL, MULTICAST_ALL ); + } + } + +/*****************************************************************************/ +/*SINED func_group (0 0 0) ? + +Used to group brushes together just for editor convenience. + +/*****************************************************************************/ + +/*****************************************************************************/ +/*SINED func_remove (0 0.5 0) ? + +Used for lighting and such + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, FuncRemove, "func_remove" ); + +ResponseDef FuncRemove::Responses[] = + { + { NULL, NULL } + }; + +FuncRemove::FuncRemove() + { + ProcessEvent( EV_Remove ); + } + + +/*****************************************************************************/ +/*SINED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) + +Used as a positional target for spotlights, etc. + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, InfoNull, "info_null" ); + +ResponseDef InfoNull::Responses[] = + { + { NULL, NULL } + }; + +InfoNull::InfoNull() + { + ProcessEvent( EV_Remove ); + } + +/*****************************************************************************/ +/*SINED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) + +Used as a positional target for lightning. + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, InfoNotNull, "info_notnull" ); + +ResponseDef InfoNotNull::Responses[] = + { + { NULL, NULL } + }; + +/*****************************************************************************/ +/*SINED func_electrocute (0 .5 .8) ? +"radius" - range of the effect (Default is 500) +"key" The item needed to activate this. (default nothing) + Electrocutes everything it can see if it is in the water +/*****************************************************************************/ + +CLASS_DECLARATION( Trigger, Electrocute, "func_electrocute" ); + +ResponseDef Electrocute::Responses[] = + { + { &EV_Trigger_Effect, ( Response )Electrocute::KillSight }, + { NULL, NULL } + }; + +Electrocute::Electrocute() + { + setOrigin( origin ); + setSolidType( SOLID_NOT ); + setMoveType( MOVETYPE_NONE ); + + radius = G_GetFloatArg("radius", 500); + } + +void Electrocute::KillSight(Event *ev) + { + Entity *other = ev->GetEntity( 1 ); + Entity *ent; + + ent = findradius( NULL, worldorigin, radius ); + while( ent ) + { + if ( ( ent != this ) && ( !ent->deadflag ) ) + { + if (ent->waterlevel) + { + ent->Damage( this, other, ent->health, ent->worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_ELECTRIC, -1, -1, 1.0f ); + } + } + ent = findradius( ent, worldorigin, radius ); + } + } + + + +/*****************************************************************************/ +/*SINED func_spawn(0 .5 .8) (-8 -8 -8) (8 8 8) +"modelname" The name of the .def file you wish to spawn. (Required) +"spawntargetname" This will be the targetname of the spawned model. (default is null) +"key" The item needed to activate this. (default nothing) +"attackmode" Attacking mode of the spawned actor (default 0) +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, Spawn, "func_spawn" ); + +ResponseDef Spawn::Responses[] = + { + { &EV_Activate, ( Response )Spawn::DoSpawn }, + { NULL, NULL } + }; + +Spawn::Spawn() + { + setSolidType( SOLID_NOT ); + setMoveType( MOVETYPE_NONE ); + hideModel(); + modelname = G_GetStringArg( "modelname", NULL ); + angles = Vector( va( "0 %f 0", G_GetFloatArg( "angle", 0 ) ) ); + + if ( !modelname.length() ) + warning("Spawn", "modelname not set" ); + + spawntargetname = G_GetStringArg( "spawntargetname", NULL ); + attackmode = G_GetIntArg( "attackmode", 0 ); + } + +void Spawn::DoSpawn( Event *ev ) + { + char temp[ 128 ]; + + // Clear the spawn args + G_InitSpawnArguments(); + + sprintf( temp, "%f %f %f", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] ); + G_SetSpawnArg( "origin", temp ); + sprintf( temp, "%f", angles[ 1 ] ); + G_SetSpawnArg( "angle", temp ); + G_SetSpawnArg( "model", modelname.c_str() ); + G_SetSpawnArg( "targetname", spawntargetname.c_str() ); + G_SetSpawnArg( "attackmode", va( "%i",attackmode ) ); + + G_CallSpawn(); + // Clear the spawn args + G_InitSpawnArguments(); + } + +/*****************************************************************************/ +/*SINED func_respawn(0 .5 .8) (-8 -8 -8) (8 8 8) +When the thing that is spawned is killed, this func_respawn will get +triggered. +"modelname" The name of the .def file you wish to spawn. (Required) +"spawntargetname" This will be the targetname of the spawned model. (default is null) +"key" The item needed to activate this. (default nothing) +/*****************************************************************************/ + +CLASS_DECLARATION( Spawn, ReSpawn, "func_respawn" ); + +ResponseDef ReSpawn::Responses[] = + { + { NULL, NULL } + }; + +void ReSpawn::DoSpawn( Event *ev ) + { + char temp[ 128 ]; + + // Clear the spawn args + G_InitSpawnArguments(); + + sprintf( temp, "%f %f %f", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] ); + G_SetSpawnArg( "origin", temp ); + sprintf( temp, "%f", angles[ 1 ] ); + G_SetSpawnArg( "angle", temp ); + G_SetSpawnArg( "model", modelname.c_str() ); + + // This will trigger the func_respawn when the thing dies + G_SetSpawnArg( "targetname", TargetName() ); + G_SetSpawnArg( "target", TargetName() ); + + G_CallSpawn(); + // Clear the spawn args + G_InitSpawnArguments(); + } + +/*****************************************************************************/ +/*SINED func_spawnoutofsight(0 .5 .8) (-8 -8 -8) (8 8 8) +Will only spawn something out of sight of its targets. +"modelname" The name of the .def file you wish to spawn. (Required) +"spawntargetname" This will be the targetname of the spawned model. (default is null) +"key" The item needed to activate this. (default nothing) +/*****************************************************************************/ + +CLASS_DECLARATION( Spawn, SpawnOutOfSight, "func_spawnoutofsight" ); + +ResponseDef SpawnOutOfSight::Responses[] = + { + { NULL, NULL } + }; + +void SpawnOutOfSight::DoSpawn + ( + Event *ev + ) + + { + char temp[ 128 ]; + int i; + Entity *ent; + edict_t *ed; + trace_t trace; + qboolean seen = false; + + // Check to see if I can see any players before spawning + for( i = 0; i < game.maxclients; i++ ) + { + ed = &g_edicts[ 1 + i ]; + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + ent = ed->entity; + if ( ( ent->health < 0 ) || ( ent->flags & FL_NOTARGET ) ) + { + continue; + } + + trace = G_Trace( worldorigin, vec_zero, vec_zero, ent->centroid, this, MASK_OPAQUE, "SpawnOutOfSight::DoSpawn" ); + if ( trace.fraction == 1.0 ) + { + seen = true; + break; + } + } + + if ( seen ) + return; + + // Clear the spawn args + G_InitSpawnArguments(); + + sprintf( temp, "%f %f %f", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] ); + G_SetSpawnArg( "origin", temp ); + sprintf( temp, "%f", angles[ 1 ] ); + G_SetSpawnArg( "angle", temp ); + G_SetSpawnArg( "model", modelname.c_str() ); + G_SetSpawnArg( "targetname", spawntargetname.c_str() ); + + G_CallSpawn(); + // Clear the spawn args + G_InitSpawnArguments(); + } + + +/*****************************************************************************/ +/*SINED func_spawnchain(0 .5 .8) (-8 -8 -8) (8 8 8) +Tries to spawn something out of the sight of players. If it fails, it will +trigger its targets. +"modelname" The name of the .def file you wish to spawn. (Required) +"spawntargetname" This will be the targetname of the spawned model. (default is null) +"key" The item needed to activate this. (default nothing) +/*****************************************************************************/ + +CLASS_DECLARATION( Spawn, SpawnChain, "func_spawnchain" ); + +ResponseDef SpawnChain::Responses[] = + { + { NULL, NULL } + }; + +void SpawnChain::DoSpawn + ( + Event *ev + ) + + { + char temp[ 128 ]; + int i,num; + Entity *ent; + edict_t *ed; + trace_t trace; + qboolean seen = false; + const char *name; + Event *event; + + // Check to see if this can see any players before spawning + for( i = 0; i < game.maxclients; i++ ) + { + ed = &g_edicts[ 1 + i ]; + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + ent = ed->entity; + if ( ( ent->health < 0 ) || ( ent->flags & FL_NOTARGET ) ) + { + continue; + } + + trace = G_Trace( worldorigin, vec_zero, vec_zero, ent->centroid, this, MASK_OPAQUE, "SpawnChain::DoSpawn" ); + if ( trace.fraction == 1.0 ) + { + seen = true; + break; + } + } + + // Couldn't spawn anything, so activate targets + if ( seen ) + { + name = Target(); + if ( name && strcmp( name, "" ) ) + { + num = 0; + do + { + num = G_FindTarget( num, name ); + if ( !num ) + { + break; + } + ent = G_GetEntity( num ); + + event = new Event( EV_Activate ); + event->AddEntity( world ); + ent->PostEvent( event, 0 ); + } while ( 1 ); + } + return; + } + + // Can't see the player, so do the spawn + G_InitSpawnArguments(); + sprintf( temp, "%f %f %f", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] ); + G_SetSpawnArg( "origin", temp ); + sprintf( temp, "%f", angles[ 1 ] ); + G_SetSpawnArg( "angle", temp ); + G_SetSpawnArg( "model", modelname.c_str() ); + G_SetSpawnArg( "targetname", spawntargetname.c_str() ); + G_CallSpawn(); + G_InitSpawnArguments(); + } + +/*****************************************************************************/ +/*SINED func_wall (0 .5 .8) ? + +This is just a solid wall if not inhibitted + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, Wall, "func_wall" ); + +ResponseDef Wall::Responses[] = + { + { NULL, NULL } + }; + +Wall::Wall() + { + setOrigin( origin ); + setSolidType( SOLID_BSP ); + setMoveType( MOVETYPE_PUSH ); + } + +/*****************************************************************************/ +/*SINED func_illusionary (0 .5 .8) ? + +A simple entity that looks solid but lets you walk through it. + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, IllusionaryWall, "func_illusionary" ); + +ResponseDef IllusionaryWall::Responses[] = + { + { NULL, NULL } + }; + +IllusionaryWall::IllusionaryWall() + { + setSolidType( SOLID_NOT ); + setMoveType( MOVETYPE_NONE ); + } + +/*****************************************************************************/ +/*SINED func_breakawaywall (0 .5 .8) ? x x NOT_PLAYERS MONSTERS PROJECTILES + +Special walltype that removes itself when triggered. Will also trigger +any func_areaportals that it targets. + +"key" The item needed to activate this. (default nothing) + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +/*****************************************************************************/ + +CLASS_DECLARATION( TriggerOnce, BreakawayWall, "func_breakawaywall" ); + +Event EV_BreakawayWall_Setup( "BreakawayWall_Setup" ); + +ResponseDef BreakawayWall::Responses[] = + { + { &EV_Touch, NULL }, + { &EV_Trigger_Effect, ( Response )BreakawayWall::BreakWall }, + { &EV_BreakawayWall_Setup, ( Response )BreakawayWall::Setup }, + { NULL, NULL } + }; + +void BreakawayWall::BreakWall + ( + Event *ev + ) + + { + SetAreaPortals( Target(), true ); + ActivateTargets( ev ); + } + +void BreakawayWall::Setup + ( + Event *ev + ) + + { + SetAreaPortals( Target(), false ); + } + +BreakawayWall::BreakawayWall() + { + showModel(); + setMoveType( MOVETYPE_PUSH ); + setSolidType( SOLID_BSP ); + PostEvent( EV_BreakawayWall_Setup, 0.1 ); + respondto = spawnflags ^ TRIGGER_PLAYERS; + }; + +/*****************************************************************************/ +/*SINED func_explodingwall (0 .5 .8) ? RANDOMANGLES LANDSHATTER NOT_PLAYERS MONSTERS PROJECTILES INVISIBLE ACCUMALATIVE TWOSTAGE + +Blows up on activation or when attacked + +"explosions" number of explosions to spawn ( default 1 ) +"land_angles" The angles you want this piece to\ + orient to when it lands on the ground +"land_radius" The distance of the ground the piece\ + should be when on the ground ( default 0 ) +"anglespeed" Speed at which pieces rotate ( default 100 ) \ + if RANDOMANGLES ( default is 600 ) +"key" The item needed to activate this. (default nothing) + +IF RANDOMANGLES is set, object randomly spins while in the air. +IF LANDSHATTER is set, object shatters when it hits the ground. +IF TWOSTAGE is set, object can be shattered once it lands on the ground. +IF ACCUMALATIVE is set, damage is accumlative not threshold +IF INVISIBLE is set, these are invisible and not solid until triggered +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +/*****************************************************************************/ +#define RANDOMANGLES ( 1 << 0 ) +#define LANDSHATTER ( 1 << 1 ) +#define INVISIBLE ( 1 << 5 ) +#define ACCUMULATIVE ( 1 << 6 ) +#define TWOSTAGE ( 1 << 7 ) + +CLASS_DECLARATION( Trigger, ExplodingWall, "func_explodingwall" ); + +Event EV_ExplodingWall_StopRotating( "stoprotating" ); +Event EV_ExplodingWall_OnGround( "checkonground" ); + +ResponseDef ExplodingWall::Responses[] = + { + { &EV_Trigger_Effect, ( Response )Explode }, + { &EV_Damage, ( Response )DamageEvent }, + { &EV_Touch, ( Response )TouchFunc }, + { &EV_ExplodingWall_StopRotating, ( Response )StopRotating }, + { &EV_ExplodingWall_OnGround, ( Response )CheckOnGround }, + { NULL, NULL } + }; + +void ExplodingWall::Explode + ( + Event *ev + ) + + { + Entity *other; + Vector pos; + Vector mins, maxs; + int i; + + if ( spawnflags & INVISIBLE ) + { + showModel(); + setSolidType( SOLID_BSP ); + takedamage = DAMAGE_YES; + } + + if ( takedamage == DAMAGE_NO ) + { + return; + } + + other = ev->GetEntity( 1 ); + + health = 0; + takedamage = DAMAGE_NO; + + // Create explosions + for( i = 0; i < explosions; i++ ) + { + pos[ 0 ] = absmin[ 0 ] + G_Random( size[ 0 ] ); + pos[ 1 ] = absmin[ 1 ] + G_Random( size[ 1 ] ); + pos[ 2 ] = absmin[ 2 ] + G_Random( size[ 2 ] ); + + CreateExplosion( pos, dmg, 1.0f, true, this, other, this ); + } + + // throw itself + state = 1; + on_ground = false; + PostEvent( EV_ExplodingWall_OnGround, 0.1f ); + velocity[ 0 ] = G_CRandom( 70 ); + velocity[ 1 ] = G_CRandom( 70 ); + velocity[ 2 ] = 140 + G_Random( 70 ); + setMoveType( MOVETYPE_BOUNCE ); + setSolidType( SOLID_BBOX ); + if ( spawnflags & RANDOMANGLES ) + { + avelocity[ 0 ] = G_Random( angle_speed ); + avelocity[ 1 ] = G_Random( angle_speed ); + avelocity[ 2 ] = G_Random( angle_speed ); + } + else + { + Vector delta; + float most; + float time; + int t; + + delta = land_angles - worldangles; + if ( delta[ 0 ] > 180 ) + delta[ 0 ] -= 360; + if ( delta[ 0 ] < -180 ) + delta[ 0 ] += 360; + if ( delta[ 1 ] > 180 ) + delta[ 1 ] -= 360; + if ( delta[ 1 ] < -180 ) + delta[ 1 ] += 360; + if ( delta[ 2 ] > 180 ) + delta[ 2 ] -= 360; + if ( delta[ 2 ] < -180 ) + delta[ 2 ] += 360; + most = MaxValue( delta ); + if ( !angle_speed ) + angle_speed = 1; + t = 10 * most / angle_speed; + time = (float)t / 10; + delta = delta * (1.0/time); + avelocity = delta; + PostEvent( EV_ExplodingWall_StopRotating, time ); + state = 2; + } + + ActivateTargets( ev ); + + if ( land_radius > 0 ) + { + mins[0] = mins[1] = mins[2] = -land_radius; + maxs[0] = maxs[1] = maxs[2] = land_radius; + setSize( mins, maxs ); + } + + attack_finished = 0; + } + +void ExplodingWall::DamageEvent + ( + Event *ev + ) + + { + Event *event; + Entity *inflictor; + Entity *attacker; + int damage; + + if ( takedamage == DAMAGE_NO ) + { + return; + } + + if ( on_ground ) + { + GroundDamage( ev ); + return; + } + + damage = ev->GetInteger( 1 ); + inflictor = ev->GetEntity( 2 ); + attacker = ev->GetEntity( 3 ); + + if ( spawnflags & ACCUMULATIVE ) + { + health -= damage; + if ( health > 0 ) + return; + } + else + { + if ( damage < health ) + { + return; + } + } + + event = new Event( EV_Activate ); + event->AddEntity( attacker ); + ProcessEvent( event ); + } + +void ExplodingWall::GroundDamage + ( + Event *ev + ) + + { + Vector dir; + Entity *inflictor; + Entity *attacker; + Vector pos; + int damage; + + if ( takedamage == DAMAGE_NO ) + { + return; + } + + damage = ev->GetInteger( 1 ); + inflictor = ev->GetEntity( 2 ); + attacker = ev->GetEntity( 3 ); + + if ( spawnflags & ACCUMULATIVE ) + { + health -= damage; + if ( health > 0 ) + return; + } + else + { + if ( damage < health ) + { + return; + } + } + + if ( explosions ) + { + pos[ 0 ] = absmin[ 0 ] + G_Random( size[ 0 ] ); + pos[ 1 ] = absmin[ 1 ] + G_Random( size[ 1 ] ); + pos[ 2 ] = absmin[ 2 ] + G_Random( size[ 2 ] ); + + CreateExplosion( pos, damage, 1.0f, true, this, attacker, this ); + } + takedamage = DAMAGE_NO; + hideModel(); + dir = worldorigin - attacker->worldorigin; + TesselateModel + ( + this, + tess_min_size, + tess_max_size, + dir, + damage, + tess_percentage, + tess_thickness, + vec3_origin + ); + ProcessEvent( EV_BreakingSound ); + PostEvent( EV_Remove, 0 ); + } + +void ExplodingWall::SetupSecondStage + ( + void + ) + + { + health = max_health; + takedamage = DAMAGE_YES; + } + +void ExplodingWall::StopRotating + ( + Event *ev + ) + + { + avelocity = vec_zero; + setAngles( land_angles ); + if ( spawnflags & TWOSTAGE ) + SetupSecondStage(); + } + +void ExplodingWall::CheckOnGround + ( + Event *ev + ) + + { + if ( ( velocity == vec_zero ) && groundentity ) + { + Vector delta; + float most; + float time; + int t; + + delta = land_angles - worldangles; + if ( delta.length() > 1 ) + { + if ( delta[ 0 ] > 180 ) + delta[ 0 ] -= 360; + if ( delta[ 0 ] < -180 ) + delta[ 0 ] += 360; + if ( delta[ 1 ] > 180 ) + delta[ 1 ] -= 360; + if ( delta[ 1 ] < -180 ) + delta[ 1 ] += 360; + if ( delta[ 2 ] > 180 ) + delta[ 2 ] -= 360; + if ( delta[ 2 ] < -180 ) + delta[ 2 ] += 360; + most = MaxValue( delta ); + if ( angle_speed > 3 ) + t = 10.0f * most / ( angle_speed / 3 ); + else + t = 10.0f * most; + time = (float)t / 10; + delta = delta * (1.0/time); + avelocity = delta; + PostEvent( EV_ExplodingWall_StopRotating, time ); + } + state = 2; + setSize( orig_mins, orig_maxs ); + on_ground = true; + } + else + PostEvent( ev, 0.1f ); + } + +void ExplodingWall::TouchFunc + ( + Event *ev + ) + + { + Entity *other; + + if ( ( velocity == vec_zero ) || ( level.time < attack_finished ) ) + { + return; + } + + other = ev->GetEntity( 1 ); + + if ( ( spawnflags & LANDSHATTER ) && ( other == world ) ) + { + Vector pos; + + takedamage = DAMAGE_NO; + + if ( explosions ) + { + pos[ 0 ] = absmin[ 0 ] + G_Random( size[ 0 ] ); + pos[ 1 ] = absmin[ 1 ] + G_Random( size[ 1 ] ); + pos[ 2 ] = absmin[ 2 ] + G_Random( size[ 2 ] ); + + CreateExplosion( pos, dmg, 1.0f, true, this, other, this ); + } + hideModel(); + TesselateModel + ( + this, + tess_min_size, + tess_max_size, + vec_zero, + 100, + tess_percentage, + tess_thickness, + vec3_origin + ); + ProcessEvent( EV_BreakingSound ); + PostEvent( EV_Remove, 0 ); + return; + } + + if ( other->takedamage ) + { + other->Damage( this, activator, dmg, worldorigin, vec_zero, vec_zero, 20, 0, MOD_EXPLODEWALL, -1, -1, 1.0f ); + RandomGlobalSound( "debris_generic", 1, CHAN_WEAPON, ATTN_NORM ); + attack_finished = level.time + 0.1; + } + } + +ExplodingWall::ExplodingWall() + { + if ( spawnflags & INVISIBLE ) + { + if ( Targeted() ) + takedamage = DAMAGE_YES; + else + takedamage = DAMAGE_NO; + hideModel(); + setSolidType( SOLID_NOT ); + } + else + { + showModel(); + setSolidType( SOLID_BSP ); + takedamage = DAMAGE_YES; + } + setMoveType( MOVETYPE_PUSH ); + setOrigin( origin ); + + health = G_GetFloatArg( "health", 60 ); + max_health = health; + on_ground = false; + + state = 0; + if ( spawnflags & RANDOMANGLES ) + angle_speed = G_GetFloatArg( "anglespeed", 600 ); + else + angle_speed = G_GetFloatArg( "anglespeed", 100 ); + + land_radius = G_GetFloatArg( "land_radius", 0 ); + land_angles = G_GetVectorArg( "land_angles" ); + dmg = G_GetIntArg( "dmg", 10 ); + explosions = G_GetIntArg( "explosions", 1 ); + + orig_mins = mins; + orig_maxs = maxs; + + respondto = spawnflags ^ TRIGGER_PLAYERS; + } + +/*****************************************************************************/ +/*SINED detail (0.5 0 1.0) ? + +Used to fake details before the Quake 2 merge. + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, Detail, "detail" ); + +ResponseDef Detail::Responses[] = + { + { NULL, NULL } + }; + +Detail::Detail() + { + // Be an asshole to the level designers so that they make the change asap. + gi.dprintf( "Detail brushes are no longer needed. Use Surface attributes.\n" ); + + if ( !G_GetSpawnArg( "model" ) ) + { + gi.dprintf( "Detail brush with NULL model removed!!!\n" ); + ProcessEvent( EV_Remove ); + } + else + { + ProcessEvent( EV_Remove ); + } + } + +/*****************************************************************************/ +/*SINED misc_oxygen (1 0 0) ? VISIBLE + +Touching this entity will reset the drowning time - only +responds to players. + +"key" The item needed to activate this. (default nothing) +/*****************************************************************************/ + +CLASS_DECLARATION( Trigger, Oxygenator, "misc_oxygen" ); + +ResponseDef Oxygenator::Responses[] = + { + { &EV_Trigger_Effect, ( Response )Oxygenator::Oxygenate }, + { NULL, NULL } + }; + +Oxygenator::Oxygenator() + { + if ( spawnflags & 1 ) + { + showModel(); + } + + time = 20; + respondto = TRIGGER_PLAYERS; + } + +EXPORT_FROM_DLL void Oxygenator::Oxygenate + ( + Event *ev + ) + + { + Entity *other; + Player *player; + + other = ev->GetEntity( 1 ); + + if ( !other ) + return; + + player = ( Player * )( Sentient * )other; + player->GiveOxygen( time ); + } + +/*****************************************************************************/ +/*SINED misc_teleporter (1 0 0) ? VISIBLE x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES + +Touching this entity will teleport players to the targeted object. + +"key" The item needed to activate this. (default nothing) + +If NOT_PLAYERS is set, the teleporter does not teleport players +If NOT_MONSTERS is set, the teleporter does not teleport monsters +If NOT_PROJECTILES is set, the teleporter does not teleport projectiles (rockets, grenades, etc.) + +/*****************************************************************************/ + +CLASS_DECLARATION( Trigger, Teleporter, "misc_teleporter" ); + +ResponseDef Teleporter::Responses[] = + { + { &EV_Trigger_Effect, ( Response )Teleporter::Teleport }, + { NULL, NULL } + }; + +EXPORT_FROM_DLL void Teleporter::Teleport + ( + Event *ev + ) + + { + gclient_t *client; + Entity *dest; + int num; + int i; + Entity *other; + Vector mid; + + other = ev->GetEntity( 1 ); + + if ( !other || ( other == world ) ) + return; + + num = G_FindTarget( 0, Target() ); + if ( !num ) + { + warning( "Teleport", "Couldn't find destination\n" ); + return; + } + + dest = G_GetEntity( num ); + assert( dest ); + + other->RandomGlobalSound( "snd_teleport" ); + + // unlink to make sure it can't possibly interfere with KillBox + other->unlink(); + + if ( other->isSubclassOf( Sentient ) ) + { + PathManager.Teleport( other, other->worldorigin, dest->worldorigin ); + other->worldorigin = dest->worldorigin + Vector( 0, 0, 1 ); + other->velocity = vec_zero; + } + else + { + mid = ( absmax - absmin ) * 0.5; + other->worldorigin = dest->worldorigin + Vector( 0, 0, 1 ); + other->origin += mid; + } + + // draw the teleport splash at the destination + //other->edict->s.event = EV_PLAYER_TELEPORT; + + // set angles + other->setAngles( dest->angles ); + + // set their gravity axis + other->SetGravityAxis( gravaxis ); + + if ( other->client ) + { + Event * ev; + client = other->client; + + if ( !gravaxis ) + { + // clear the velocity and hold them in place briefly + client->ps.pmove.pm_time = 100; + client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + ev = new Event( EV_Player_SaveFov ); + other->ProcessEvent( ev ); + + ev = new Event( EV_Player_Fov ); + ev->AddFloat( 180 ); + other->ProcessEvent( ev ); + } + + /* + if ( gravaxis ) + { + ev = new Event( EV_Player_RestoreFov ); + other->PostEvent( ev, 0.1f ); + } + */ + + for( i = 0; i < 3; i++ ) + { + client->ps.pmove.delta_angles[ i ] = ANGLE2SHORT( dest->angles[ i ] - client->resp.cmd_angles[ i ] ); + } + + VectorCopy( angles.vec3(), client->ps.viewangles ); + } + + if ( dest->isSubclassOf( TeleporterDestination ) && !gravaxis ) + { + float len; + + len = other->velocity.length(); + // + // give them a bit of a push + // + if ( len < 400 ) + len = 400; + other->velocity = ( ( TeleporterDestination * )dest )->movedir * len; + } + + // kill anything at the destination + KillBox( other ); + + other->setOrigin( other->worldorigin ); + other->worldorigin.copyTo( other->edict->s.old_origin ); + } + +Teleporter::Teleporter() + { + if ( !Target() ) + { + gi.dprintf( "teleporter without a target.\n" ); + ProcessEvent( EV_Remove ); + return; + } + + if ( spawnflags & 1 ) + { + showModel(); + } + + respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES ); + } + +/*****************************************************************************/ +/*SINED misc_teleporter_dest (1 0 0) (-32 -32 0) (32 32 8) + +Point teleporters at these. + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, TeleporterDestination, "misc_teleporter_dest" ); + +ResponseDef TeleporterDestination::Responses[] = + { + { NULL, NULL } + }; + +TeleporterDestination::TeleporterDestination() + { + movedir = G_GetMovedir(); + setAngles( movedir.toAngles() ); + } + +/*****************************************************************************/ +/*SINED waypoint (0 0.5 0) (-8 -8 -8) (8 8 8) + +Used as a positioning device for objects + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, Waypoint, "waypoint" ); + +ResponseDef Waypoint::Responses[] = + { + { NULL, NULL } + }; + +/*****************************************************************************/ +/*SINED func_shatter (0 .5 .8) ? x x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES HURT_SHATTER THRESHOLD + +For shattering objects. Triggers only when a threshold of damage is exceeded. +Will also trigger any targeted func_areaportals when not invisible. + +"health" specifies how much damage must occur before trigger fires. Default is 20. +"percentage" specifies how much of the thing to shatter. Default is 50 +"minsize" specifies minsize for tesselation, default based off size +"maxsize" specifies maxsize for tesselation, default based off size +"thickness" specifies thickness for tesselation, default same as minsize +"key" The item needed to activate this. (default nothing) +"noise" sound to play when shattered, defaults to nothing + +HURT_SHATTER - when the thing gets hurt, spawn pieces of itself +THRESHOLD - damage threshold behavior + +If NOT_PLAYERS is set, the trigger does not respond to damage caused by players +If NOT_MONSTERS is set, the trigger does not respond to damage caused by monsters +If NOT_PROJECTILES is set, the trigger does not respond to damage caused by projectiles + +/*****************************************************************************/ + +CLASS_DECLARATION( Trigger, Shatter, "func_shatter" ); + +ResponseDef Shatter::Responses[] = + { + { &EV_Trigger_Effect, ( Response )Shatter ::DoShatter }, + { &EV_Damage, ( Response )Shatter ::DamageEvent }, + { NULL, NULL } + }; + +void Shatter::DamageEvent + ( + Event *ev + ) + + { + Event *event; + Entity *inflictor; + Entity *attacker; + int damage; + + if ( takedamage == DAMAGE_NO ) + { + return; + } + + damage = ev->GetInteger( 1 ); + inflictor = ev->GetEntity( 2 ); + attacker = ev->GetEntity( 3 ); + + if ( threshold && damage < health ) + { + return; + } + else if ( !threshold ) + { + health -= damage; + if (health > 0 ) + { + damage_taken += damage; + return; + } + } + + damage_taken += damage; + + if ( attacker ) + { + event = new Event( EV_Activate ); + event->AddEntity( attacker ); + ProcessEvent( event ); + } + else + { + warning("Damage", "Attacker is null\n" ); + } + } + +void Shatter::DoShatter + ( + Event *ev + ) + + { + Entity *other; + Event *event; + Vector dir; + + if ( takedamage == DAMAGE_NO ) + { + return; + } + + if ( noise.length() > 1 ) + { + sound( noise.c_str(), 1, CHAN_VOICE + CHAN_NO_PHS_ADD ); + } + + other = ev->GetEntity( 1 ); + + takedamage = DAMAGE_NO; + + dir = worldorigin - other->worldorigin; + + TesselateModel + ( + this, + tess_min_size, + tess_max_size, + dir, + damage_taken, + tess_percentage, + tess_thickness, + vec3_origin + ); + + SetAreaPortals( Target(), true ); + + event = new Event( EV_Trigger_ActivateTargets ); + event->AddEntity( other ); + ProcessEvent( event ); + + ProcessEvent( EV_BreakingSound ); + + PostEvent( EV_Remove, 0 ); + } + +Shatter::Shatter() + { + // + // Can only be used once + // + count = -1; + threshold = false; + + // Since we're a subclass of DamageThreshold, override the invisible behaviour + showModel(); + PostEvent( Event( "setup" ), 0 ); + + tess_percentage = G_GetFloatArg( "percentage", tess_percentage*100 ) / 100.0f; + tess_min_size = G_GetIntArg( "minsize", tess_min_size ); + tess_max_size = G_GetIntArg( "maxsize", tess_max_size ); + tess_thickness = G_GetIntArg( "thickness", tess_thickness ); + + health = G_GetFloatArg( "health", 20 ); + max_health = health; + + noise = str( G_GetSpawnArg( "noise", "" ) ); + + if ( spawnflags & (1<<5) ) + flags |= FL_TESSELATE; + + if ( spawnflags & (1<<6) ) + threshold = true; + + tess_thickness = 10; + + respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES ); + } + +/*****************************************************************************/ +/*SINED func_glass (0 .5 .8) ? x x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES HURT_SHATTER THRESHOLD + +For glass objects. Shatters when the accumulated damage is exceeded, or when activated + +"health" specifies how much damage must occur before the glass shatters. Default is 60. +"percentage" specifies how much of the thing to shatter. Default is 50 +"minsize" specifies minsize for tesselation, default based off size +"maxsize" specifies maxsize for tesselation, default based off size +"thickness" specifies thickness for tesselation, default same as minsize +"key" The item needed to activate this. (default nothing) +"noise" sound to play when shattered, defaults to glass breaking + +If NOT_PLAYERS is set, the trigger does not respond to events caused by players +If NOT_MONSTERS is set, the trigger does not respond to events caused by monsters +If NOT_PROJECTILES is set, the trigger does not respond to events caused by projectiles + +/*****************************************************************************/ + +CLASS_DECLARATION( Shatter, Glass, "func_glass" ); + +ResponseDef Glass::Responses[] = + { + { NULL, NULL } + }; + +Glass::Glass() + { + if ( !noise.length() ) + { + const char * realname; + + if (max_health <= 60) + { + realname = gi.GlobalAlias_FindRandom( "impact_smlglass" ); + if ( realname ) + noise = str( realname ); + } + else + { + realname = gi.GlobalAlias_FindRandom( "impact_lrgglass" ); + if ( realname ) + noise = str( realname ); + } + } + gi.soundindex( noise.c_str() ); + } + +// +// MadeBreakingSound +// +// Entity-less notifier for AI +// +void MadeBreakingSound + ( + Vector pos, + Entity * activator + ) + + { + Entity *ent; + Event *ev; + + // + // make sure activator is valid + // + + if ( !activator ) + activator = world; + + ent = NULL; + while( ent = findradius( ent, pos.vec3(), SOUND_BREAKING_RADIUS ) ) + { + if ( !ent->deadflag && ent->isSubclassOf( Sentient ) && ( ent != activator ) && + gi.inPHS( pos.vec3(), ent->centroid.vec3() ) + ) + { + ev = new Event( EV_HeardBreaking ); + ev->AddEntity( activator ); + ev->AddVector( pos ); + ent->PostEvent( ev, 0 ); + } + } + } + +CLASS_DECLARATION( Entity, BloodSplat, NULL ); + +ResponseDef BloodSplat::Responses[] = + { + { NULL, NULL } + }; + +int BloodSplat::numBloodSplats = 0; +Queue BloodSplat::queueBloodSplats; + +BloodSplat::BloodSplat + ( + Vector pos, + Vector ang, + float scale + ) + + { + BloodSplat *fadesplat; + + if ( numBloodSplats > sv_maxbloodsplats->value ) + { + // Fade one out of the list. + fadesplat = ( BloodSplat * )queueBloodSplats.Dequeue(); + fadesplat->ProcessEvent( EV_FadeOut ); + numBloodSplats--; + + // Don't spawn one until we others have faded + PostEvent( EV_Remove, 0 ); + return; + } + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_NOT ); + setModel( "sprites/bloodsplat.spr" ); + edict->s.frame = G_Random( 4 ); + setSize( "0 0 0", "0 0 0" ); + + queueBloodSplats.Enqueue( this ); + numBloodSplats++; + + edict->s.scale = scale * edict->s.scale; + setAngles( ang ); + setOrigin( pos ); + } + +BloodSplat::~BloodSplat() + { + if ( queueBloodSplats.Inqueue( this ) ) + { + queueBloodSplats.Remove( this ); + numBloodSplats--; + } + } + +/*****************************************************************************/ +/*SINED func_clipbox (0 .5 .8) (-16 -16 -16) (16 16 16) + +Invisible bounding box used like a clip brush. This is mainly used for blocking +off areas or improving clipping without having to recompile the map. Because of +this, it will most likely only be spawned via script. +age is exceeded, or when activated + +"mins" min point of the clip. +"maxs" max point of the clip. +"type" - +0 Monster and Player clip +1 Monster clip +2 Player clip + +/*****************************************************************************/ + +CLASS_DECLARATION( Entity, ClipBox, "func_clipbox" ); + +ResponseDef ClipBox::Responses[] = + { + { NULL, NULL } + }; + +ClipBox::ClipBox() + { + int type; + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_BBOX ); + hideModel(); + + type = G_GetIntArg( "type" ); + + edict->clipmask = MASK_SOLID; + switch( type ) + { + case 1 : + edict->svflags |= SVF_MONSTERCLIP; + break; + + case 2 : + edict->svflags |= SVF_PLAYERCLIP; + break; + + default : + edict->svflags |= SVF_PLAYERCLIP|SVF_MONSTERCLIP; + break; + } + + mins = G_GetVectorArg( "mins" ); + maxs = G_GetVectorArg( "maxs" ); + + origin = ( mins + maxs ) * 0.5; + + setSize( mins - origin, maxs - origin ); + setOrigin( origin ); + } diff --git a/misc.h b/misc.h new file mode 100644 index 0000000..18a1965 --- /dev/null +++ b/misc.h @@ -0,0 +1,648 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/misc.h $ +// $Revision:: 71 $ +// $Author:: Jimdose $ +// $Date:: 11/11/98 10:02p $ +// +// 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/misc.h $ +// +// 71 11/11/98 10:02p Jimdose +// added ClipBox +// +// 70 10/17/98 8:12p Jimdose +// Changed Damage to DamgeEvent +// +// 69 10/14/98 12:13a Aldie +// Proto for intermission +// +// 68 10/09/98 4:34p Aldie +// Added OnSameTeam +// +// 67 10/07/98 11:53p Jimdose +// Added BloodSplat +// +// 66 10/06/98 10:52p Aldie +// Created an oxygenator +// +// 65 10/05/98 11:23p Markd +// Added MadeBreakingSound +// +// 64 9/28/98 9:12p Markd +// Put in archive and unarchive functions +// +// 63 9/18/98 8:14p Markd +// rewrote surface system so that surfaces are now damaged by surface name +// instead of by surfinfo +// +// 62 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 61 8/29/98 2:53p Aldie +// Updated printing of location based damage +// +// 60 8/29/98 12:59p Markd +// Fixed func_explodingwall stuff +// +// 59 8/24/98 5:44p Aldie +// Added fov to projectiles +// +// 58 8/19/98 8:50p Aldie +// Added randomparticles +// +// 57 8/17/98 4:35p Markd +// Added SendOverlay +// +// 56 8/08/98 8:51p Aldie +// Added func_spawnchain and changed spawns to entities +// +// 55 8/08/98 6:24p Aldie +// Added spawnoutofsight +// +// 54 8/04/98 5:56p Markd +// Added FuncRemove +// +// 53 7/21/98 4:23p Aldie +// Make icons work for locks +// +// 52 7/17/98 6:08p Aldie +// Added spawntargetname to func_spawn +// +// 51 7/15/98 6:18p Markd +// re-ordered SpawnTempDlight arguments +// +// 50 7/13/98 5:01p Aldie +// Added dead player bodies with gibbing +// +// 49 7/11/98 2:49p Markd +// Added SendDialog event +// +// 48 7/10/98 4:11p Markd +// Revamped func_explodingwall +// +// 47 7/08/98 3:12p Aldie +// Added func_respawn +// +// 46 7/02/98 7:57p Markd +// Redid func_explodingwall and func_glass +// +// 45 6/30/98 4:37p Markd +// Added noise to func_glass and func_shatter +// +// 44 6/24/98 4:51p Aldie +// Updated func_spawn +// +// 43 6/24/98 12:38p Markd +// SpawnParticles conflicted with the entity function of the same name +// +// 42 6/24/98 12:23p Markd +// Added SpawnParticles +// +// 41 6/18/98 2:00p Markd +// rewrote tesselation code +// +// 40 6/15/98 12:25p Aldie +// Updated pulse beam parms +// +// 39 6/15/98 10:39a Aldie +// Added SpawnPulseBeam +// +// 38 6/08/98 7:23p Aldie +// Added DamageSurfaceByName +// +// 37 5/28/98 1:26p Aldie +// Added rocket explosion particles +// +// 36 5/27/98 4:54p Markd +// Changed Smoke Setup method +// +// 35 5/26/98 9:27p Aldie +// Typo +// +// 34 5/26/98 9:26p Aldie +// Added func_spawn +// +// 33 5/25/98 12:22p Aldie +// Added func_electrocute +// +// 32 5/25/98 7:15p Markd +// Changed TempModel stuff +// +// 31 5/25/98 5:42p Markd +// Added SpawnDlight and fixed other stuff +// +// 30 5/23/98 10:38p Markd +// Added TempModel call +// +// 29 5/20/98 1:32p Markd +// changed func_shatter a bit +// +// 28 5/13/98 6:22p Markd +// Added BurnWall +// +// 27 5/05/98 8:38p Aldie +// Added virtual setup to Projectile. +// +// 26 5/05/98 5:35p Aldie +// Updated damage surfaces +// +// 25 5/03/98 8:12p Markd +// Added SpawnSparks call +// +// 24 4/18/98 2:32p Jimdose +// Added ExplodingWall, Shatter, and Glass +// +// 23 4/07/98 11:55p Jimdose +// SetBeam now accepts r, g, b, and alpha for color +// +// 22 4/04/98 6:13p Jimdose +// Added Bubble, Smoke, and Projectile +// +// 21 3/30/98 2:39p Jimdose +// Made teleporters handle non-clients +// +// 20 3/29/98 9:39p Jimdose +// Changed killed to a damage event +// +// 19 3/28/98 6:37p Jimdose +// Made teleporters work +// +// 18 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 17 3/11/98 2:23p Jimdose +// Moved AreaPortals to areaportal.cpp +// +// 16 3/05/98 3:49p Aldie +// More support for damage surfaces. +// +// 15 3/04/98 8:00p Aldie +// More support for damage surfaces. +// +// 14 3/02/98 8:49p Jimdose +// Changed CLASS_PROTOTYPE to only take the classname +// +// 13 3/02/98 5:42p Jimdose +// Moved light entities to light.cpp +// +// 12 2/27/98 12:50p Jimdose +// Added Light, LightRamp, and TriggerLightStyle +// +// 11 2/21/98 1:07p Jimdose +// Added Beam class +// +// 10 2/06/98 5:43p Jimdose +// Added Teleporter and AreaPortal. +// Added constructor to Detail +// +// 8 2/03/98 10:43a Jimdose +// Updated to work with Quake 2 engine +// +// 6 10/31/97 4:26p Jimdose +// Removed owner from HackThinker since it was already defined in Entity +// +// 5 10/28/97 8:20p Jimdose +// added Bubble +// +// 4 10/27/97 2:59p Jimdose +// Removed dependency on quakedef.h +// +// 3 10/01/97 11:25a Markd +// moved viewthing to its own file +// +// 2 9/26/97 6:14p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Basically the big stew pot of the DLLs, or maybe a garbage bin, whichever +// metaphore you prefer. This really should be cleaned up. Anyway, this +// should contain utility functions that could be used by any entity. +// Right now it contains everything from entities that could be in their +// own file to my mother pot roast recipes. +// + +#ifndef __MISC_H__ +#define __MISC_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "queue.h" + +void SendOverlay + ( + Entity *ent, + str overlayname + ); + +void SendIntermission + ( + Entity *ent, + str intermissionname + ); + +void SendDialog + ( + const char *icon_name, + const char *dialog_text + ); + +const char *ExpandLocation + ( + const char *location + ); + +void MadeBreakingSound + ( + Vector pos, + Entity * activator + ); + +qboolean OnSameTeam + ( + Entity *ent1, + Entity *ent2 + ); + +class EXPORT_FROM_DLL InfoNull : public Entity + { + public: + CLASS_PROTOTYPE( InfoNull ); + + InfoNull(); + }; + +class EXPORT_FROM_DLL FuncRemove : public Entity + { + public: + CLASS_PROTOTYPE( FuncRemove ); + + FuncRemove(); + }; + +class EXPORT_FROM_DLL InfoNotNull : public Entity + { + public: + CLASS_PROTOTYPE( InfoNotNull ); + }; + +class EXPORT_FROM_DLL Wall : public Entity + { + private: + + public: + CLASS_PROTOTYPE( Wall ); + + Wall(); + }; + +class EXPORT_FROM_DLL IllusionaryWall : public Entity + { + private: + + public: + CLASS_PROTOTYPE( IllusionaryWall ); + IllusionaryWall(); + }; + +class EXPORT_FROM_DLL BreakawayWall : public TriggerOnce + { + private: + + public: + CLASS_PROTOTYPE( BreakawayWall ); + + void BreakWall( Event *ev ); + void Setup( Event *ev ); + BreakawayWall(); + }; + +class EXPORT_FROM_DLL ExplodingWall : public Trigger + { + protected: + int dmg; + int explosions; + float attack_finished; + Vector land_angles; + float land_radius; + float angle_speed; + int state; + Vector orig_mins, orig_maxs; + qboolean on_ground; + + public: + CLASS_PROTOTYPE( ExplodingWall ); + + ExplodingWall(); + virtual void SetupSecondStage( void ); + virtual void Explode( Event *ev ); + virtual void DamageEvent( Event *ev ); + virtual void GroundDamage( Event *ev ); + virtual void TouchFunc( Event *ev ); + virtual void StopRotating( Event *ev ); + virtual void CheckOnGround( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void ExplodingWall::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.WriteInteger( dmg ); + arc.WriteInteger( explosions ); + arc.WriteFloat( attack_finished ); + arc.WriteVector( land_angles ); + arc.WriteFloat( land_radius ); + arc.WriteFloat( angle_speed ); + arc.WriteInteger( state ); + arc.WriteVector( orig_mins ); + arc.WriteVector( orig_maxs ); + arc.WriteBoolean( on_ground ); + } + +inline EXPORT_FROM_DLL void ExplodingWall::Unarchive + ( + Archiver &arc + ) + { + Trigger::Unarchive( arc ); + + arc.ReadInteger( &dmg ); + arc.ReadInteger( &explosions ); + arc.ReadFloat( &attack_finished ); + arc.ReadVector( &land_angles ); + arc.ReadFloat( &land_radius ); + arc.ReadFloat( &angle_speed ); + arc.ReadInteger( &state ); + arc.ReadVector( &orig_mins ); + arc.ReadVector( &orig_maxs ); + arc.ReadBoolean( &on_ground ); + } + +class EXPORT_FROM_DLL Electrocute : public Trigger + { + private: + float radius; + + public: + CLASS_PROTOTYPE( Electrocute ); + + Electrocute(); + virtual void KillSight( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Electrocute::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.WriteFloat( radius ); + } + +inline EXPORT_FROM_DLL void Electrocute::Unarchive + ( + Archiver &arc + ) + { + Trigger::Unarchive( arc ); + + arc.ReadFloat( &radius ); + } + + +class EXPORT_FROM_DLL Detail : public Wall + { + public: + CLASS_PROTOTYPE( Detail ); + + Detail(); + }; + +class EXPORT_FROM_DLL Teleporter : public Trigger + { + public: + CLASS_PROTOTYPE( Teleporter ); + + Teleporter(); + virtual void Teleport( Event *ev ); + }; + +class EXPORT_FROM_DLL TeleporterDestination : public Entity + { + public: + Vector movedir; + + CLASS_PROTOTYPE( TeleporterDestination ); + + TeleporterDestination(); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void TeleporterDestination::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteVector( movedir ); + } + +inline EXPORT_FROM_DLL void TeleporterDestination::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadVector( &movedir ); + } + +class EXPORT_FROM_DLL Waypoint : public Entity + { + public: + CLASS_PROTOTYPE( Waypoint ); + }; + +class EXPORT_FROM_DLL Shatter : public DamageThreshold + { + protected: + qboolean threshold; + str noise; + public: + CLASS_PROTOTYPE( Shatter ); + + virtual void DamageEvent( Event *ev ); + virtual void DoShatter( Event *ev ); + Shatter(); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Shatter::Archive + ( + Archiver &arc + ) + { + DamageThreshold::Archive( arc ); + + arc.WriteBoolean( threshold ); + arc.WriteString( noise ); + } + +inline EXPORT_FROM_DLL void Shatter::Unarchive + ( + Archiver &arc + ) + { + DamageThreshold::Unarchive( arc ); + + arc.ReadBoolean( &threshold ); + arc.ReadString( &noise ); + } + +class EXPORT_FROM_DLL Glass : public Shatter + { + public: + CLASS_PROTOTYPE( Glass ); + Glass(); + }; + +class EXPORT_FROM_DLL Spawn : public Entity + { + protected: + str modelname; + str spawntargetname; + int attackmode; + + public: + CLASS_PROTOTYPE( Spawn ); + + virtual void DoSpawn( Event *ev ); + Spawn(); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Spawn::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.WriteString( modelname ); + arc.WriteString( spawntargetname ); + arc.WriteInteger( attackmode ); + } + +inline EXPORT_FROM_DLL void Spawn::Unarchive + ( + Archiver &arc + ) + { + Entity::Unarchive( arc ); + + arc.ReadString( &modelname ); + arc.ReadString( &spawntargetname ); + arc.ReadInteger( &attackmode ); + } + +class EXPORT_FROM_DLL ReSpawn : public Spawn + { + public: + CLASS_PROTOTYPE( ReSpawn ); + + virtual void DoSpawn( Event *ev ); + }; + +class EXPORT_FROM_DLL SpawnOutOfSight : public Spawn + { + public: + CLASS_PROTOTYPE( SpawnOutOfSight ); + + virtual void DoSpawn( Event *ev ); + }; + +class EXPORT_FROM_DLL SpawnChain : public Spawn + { + public: + CLASS_PROTOTYPE( SpawnChain ); + + virtual void DoSpawn( Event *ev ); + }; + +class EXPORT_FROM_DLL Oxygenator : public Trigger + { + private: + float time; + + public: + CLASS_PROTOTYPE( Oxygenator ); + + Oxygenator(); + virtual void Oxygenate( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Oxygenator::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.WriteFloat( time ); + } + +inline EXPORT_FROM_DLL void Oxygenator::Unarchive + ( + Archiver &arc + ) + { + Trigger::Unarchive( arc ); + + arc.ReadFloat( &time ); + } + +class EXPORT_FROM_DLL BloodSplat : public Entity + { + private: + // No archive function is declared since the class automaintains itself. + // Just spawning and freeing maintains the queue. + + static int numBloodSplats; + static Queue queueBloodSplats; + + public: + CLASS_PROTOTYPE( BloodSplat ); + + BloodSplat( Vector pos = "0 0 0", Vector ang = "0 0 0", float scale = 1 ); + ~BloodSplat(); + }; + +class EXPORT_FROM_DLL ClipBox : public Entity + { + public: + CLASS_PROTOTYPE( ClipBox ); + + ClipBox(); + }; + +#endif /* misc.h */ diff --git a/mover.cpp b/mover.cpp new file mode 100644 index 0000000..008cec5 --- /dev/null +++ b/mover.cpp @@ -0,0 +1,316 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/mover.cpp $ +// $Revision:: 18 $ +// $Author:: Jimdose $ +// $Date:: 10/24/98 8:30p $ +// +// 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/mover.cpp $ +// +// 18 10/24/98 8:30p Jimdose +// fixed door revolving bug +// +// 17 9/03/98 9:08p Jimdose +// Added checks for negative speeds in MoveTo +// +// 16 8/29/98 9:44p Jimdose +// Made SV_ commands begin with G_ +// +// 15 5/03/98 4:36p Jimdose +// Changed Vector class +// +// 14 4/06/98 6:42p Jimdose +// the minimum movetime is now FRAMETIME so that we don't get into any infinite +// loops with scripts +// +// 13 4/05/98 1:58a Jimdose +// Mover now only modifies avelocity if there's angular movement and velocity +// if there's translational movement. This allows us to move and have +// continual rotation +// +// 12 3/24/98 5:00p Jimdose +// Made MoveDone post a new EV_MoveDone instead of postponing the old one +// (which had already been executed, oops!) +// +// 11 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 10 3/11/98 2:24p Jimdose +// Now quantize movetimes to nearest multiple of FRAMETIME +// +// 9 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. +// +// 8 2/21/98 1:12p Jimdose +// Fixed bug where EVENT_MOVEDONE was allowing the event to pass to the +// superclass +// +// 7 2/18/98 8:01p Jimdose +// Pending EVENT_MOVEDONEs are cancelled when starting a new move. +// +// 6 2/16/98 2:12p Jimdose +// Made MoveDone do a PushMove instead of a setOrigin so that blocking works +// correctly at the end of moves +// +// 5 2/03/98 10:45a Jimdose +// Updated to work with Quake 2 engine +// Made changeover from hackthinker to events +// +// 3 10/27/97 3:29p Jimdose +// Removed dependency on quakedef.h +// +// 2 9/26/97 5:23p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Base class for any object that needs to move to specific locations over a +// period of time. This class is kept separate from most entities to keep +// class size down for objects that don't need such behavior. +// + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "mover.h" + +#define MOVE_ANGLES 1 +#define MOVE_ORIGIN 2 + +CLASS_DECLARATION( Trigger, Mover, "mover" ); + +ResponseDef Mover::Responses[] = + { + { &EV_MoveDone, ( Response )Mover::MoveDone }, + { NULL, NULL } + }; + +EXPORT_FROM_DLL void Mover::MoveDone + ( + Event *ev + ) + + { + Vector move; + Vector amove; + + // zero out the movement + if ( moveflags & MOVE_ANGLES ) + { + avelocity = vec_zero; + amove = angledest - angles; + } + else + { + amove = vec_zero; + } + + if ( moveflags & MOVE_ORIGIN ) + { + velocity = vec_zero; + move = finaldest - origin; + } + else + { + move = vec_zero; + } + + if ( !G_PushMove( this, move, amove ) ) + { + // Delay finish till we can move into the final position + PostEvent( EV_MoveDone, FRAMETIME ); + return; + } + + // + // After moving, set origin to exact final destination + // + if ( moveflags & MOVE_ORIGIN ) + { + setOrigin( finaldest ); + } + + if ( moveflags & MOVE_ANGLES ) + { + angles = angledest; + + if ( ( angles.x >= 360 ) || ( angles.x < 0 ) ) + { + angles.x -= ( (int)angles.x / 360 ) * 360; + } + if ( ( angles.y >= 360 ) || ( angles.y < 0 ) ) + { + angles.y -= ( (int)angles.y / 360 ) * 360; + } + if ( ( angles.z >= 360 ) || ( angles.z < 0 ) ) + { + angles.z -= ( (int)angles.z / 360 ) * 360; + } + } + + ProcessEvent( endevent ); + } + +/* +============= +MoveTo + +calculate self.velocity and self.nextthink to reach dest from +self.origin traveling at speed +=============== +*/ +EXPORT_FROM_DLL void Mover::MoveTo + ( + Vector tdest, + Vector angdest, + float tspeed, + Event &event + ) + + { + Vector vdestdelta; + Vector angdestdelta; + float len; + float traveltime; + + assert( tspeed >= 0 ); + + if ( !tspeed ) + { + error( "MoveTo", "No speed is defined!" ); + } + + if ( tspeed < 0 ) + { + error( "MoveTo", "Speed is negative!" ); + } + + // Cancel previous moves + CancelEventsOfType( EV_MoveDone ); + + moveflags = 0; + + endevent = event; + finaldest = tdest; + angledest = angdest; + + if ( finaldest != origin ) + { + moveflags |= MOVE_ORIGIN; + } + if ( angledest != angles ) + { + moveflags |= MOVE_ANGLES; + } + + if ( !moveflags ) + { + // stop the object from moving + velocity = vec_zero; + avelocity = vec_zero; + + // post the event so we don't wait forever + PostEvent( EV_MoveDone, FRAMETIME ); + return; + } + + // set destdelta to the vector needed to move + vdestdelta = tdest - origin; + angdestdelta = angdest - angles; + + if ( tdest == origin ) + { + // calculate length of vector based on angles + len = angdestdelta.length(); + } + else + { + // calculate length of vector based on distance + len = vdestdelta.length(); + } + + // divide by speed to get time to reach dest + traveltime = len / tspeed; + + // Quantize to FRAMETIME + traveltime *= ( 1 / FRAMETIME ); + traveltime = ( float )( (int)traveltime ) * FRAMETIME; + if ( traveltime < FRAMETIME ) + { + traveltime = FRAMETIME; + vdestdelta = vec_zero; + angdestdelta = vec_zero; + } + + // scale the destdelta vector by the time spent traveling to get velocity + if ( moveflags & MOVE_ORIGIN ) + { + velocity = vdestdelta * ( 1 / traveltime ); + } + + if ( moveflags & MOVE_ANGLES ) + { + avelocity = angdestdelta * ( 1 / traveltime ); + } + + PostEvent( EV_MoveDone, traveltime ); + } + +/* +============= +LinearInterpolate +=============== +*/ +EXPORT_FROM_DLL void Mover::LinearInterpolate + ( + Vector tdest, + Vector angdest, + float time, + Event &event + ) + + { + Vector vdestdelta; + Vector angdestdelta; + float t; + + endevent = event; + finaldest = tdest; + angledest = angdest; + + // Cancel previous moves + CancelEventsOfType( EV_MoveDone ); + + // Quantize to FRAMETIME + time *= ( 1 / FRAMETIME ); + time = ( float )( (int)time ) * FRAMETIME; + if ( time < FRAMETIME ) + { + time = FRAMETIME; + } + + moveflags = 0; + t = 1 / time; + // scale the destdelta vector by the time spent traveling to get velocity + if ( finaldest != origin ) + { + vdestdelta = tdest - origin; + velocity = vdestdelta * t; + moveflags |= MOVE_ORIGIN; + } + + if ( angledest != angles ) + { + angdestdelta = angdest - angles; + avelocity = angdestdelta * t; + moveflags |= MOVE_ANGLES; + } + + PostEvent( EV_MoveDone, time ); + } diff --git a/mover.h b/mover.h new file mode 100644 index 0000000..30c464f --- /dev/null +++ b/mover.h @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/mover.h $ +// $Revision:: 10 $ +// $Author:: Markd $ +// $Date:: 9/28/98 9:12p $ +// +// 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/mover.h $ +// +// 10 9/28/98 9:12p Markd +// Put in archive and unarchive functions +// +// 9 4/05/98 2:00a Jimdose +// added moveflags +// +// 8 3/23/98 1:33p Jimdose +// Revamped event and command system +// +// 7 3/02/98 8:49p Jimdose +// Changed CLASS_PROTOTYPE to only take the classname +// +// 6 2/16/98 2:13p Jimdose +// Changed EVENT_MOVER_MOVEDONE to EVENT_MOVEDONE so that physics can delay the +// movedone events +// Removed unused variables +// +// 5 2/03/98 10:45a Jimdose +// Updated to work with Quake 2 engine +// Made changeover from hackthinker to events +// +// 3 10/27/97 2:59p Jimdose +// Removed dependency on quakedef.h +// +// 2 9/26/97 5:23p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Base class for any object that needs to move to specific locations over a +// period of time. This class is kept separate from most entities to keep +// class size down for objects that don't need such behavior. +// + +#ifndef __MOVER_H__ +#define __MOVER_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" + +class EXPORT_FROM_DLL Mover : public Trigger + { + private: + Vector finaldest; + Vector angledest; + Event endevent; + int moveflags; + + public: + CLASS_PROTOTYPE( Mover ); + + void MoveDone( Event *ev ); + void MoveTo( Vector tdest, Vector angdest, float tspeed, Event &event ); + void LinearInterpolate( Vector tdest, Vector angdest, float time, Event &event ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Mover::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.WriteVector( finaldest ); + arc.WriteVector( angledest ); + arc.WriteEvent( endevent ); + arc.WriteInteger( moveflags ); + } + +inline EXPORT_FROM_DLL void Mover::Unarchive + ( + Archiver &arc + ) + { + Trigger::Unarchive( arc ); + + arc.ReadVector( &finaldest ); + arc.ReadVector( &angledest ); + arc.ReadEvent( &endevent ); + arc.ReadInteger( &moveflags ); + } + +#endif diff --git a/mutanthands.cpp b/mutanthands.cpp new file mode 100644 index 0000000..7e49d21 --- /dev/null +++ b/mutanthands.cpp @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/mutanthands.cpp $ +// $Revision:: 11 $ +// $Author:: Aldie $ +// $Date:: 10/11/98 5:32p $ +// +// Copyright (C) 1998 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/mutanthands.cpp $ +// +// 11 10/11/98 5:32p Aldie +// Tweak damage +// +// 10 10/10/98 11:44p Aldie +// Tweak damage and reach +// +// 9 9/22/98 12:49p Markd +// Put in archive and unarchive functions +// +// 8 8/06/98 6:58p Jimdose +// Added min/max range, and projectile speed +// +// 7 7/25/98 7:10p Markd +// Put in EV_Removes for demo +// +// 6 7/22/98 9:57p Markd +// Defined weapon type +// +// 5 6/19/98 9:30p Jimdose +// Moved gun orientation code to Weapon +// +// 4 6/10/98 10:02p Markd +// got em working with fists subclass +// +// 3 5/16/98 5:00p Markd +// Added RF_XFLIP stuff +// +// 2 5/11/98 11:24a Markd +// First time +// +// 1 5/11/98 10:18a Markd +// +// 1 5/11/98 9:55a Markd +// +// DESCRIPTION: +// Mutant Hands +// + +#include "g_local.h" +#include "mutanthands.h" + +CLASS_DECLARATION( Fists, MutantHands, NULL); + +ResponseDef MutantHands::Responses[] = + { + { NULL, NULL } + }; + +MutantHands::MutantHands() + { +#ifdef SIN_DEMO + PostEvent( EV_Remove, 0 ); + return; +#endif + SetModels( NULL, "view_mutanthands.def" ); + SetAmmo( NULL, 0, 0 ); + SetRank( 11, 11 ); + strike_reach = 75; + strike_damage = 100; + SetMaxRange( strike_reach ); + SetType( WEAPON_MELEE ); + kick = 25; + meansofdeath = MOD_MUTANTHANDS; + } + diff --git a/mutanthands.h b/mutanthands.h new file mode 100644 index 0000000..50b2128 --- /dev/null +++ b/mutanthands.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/mutanthands.h $ +// $Revision:: 5 $ +// $Author:: Markd $ +// $Date:: 9/22/98 12:49p $ +// +// Copyright (C) 1998 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/mutanthands.h $ +// +// 5 9/22/98 12:49p Markd +// Put in archive and unarchive functions +// +// 4 6/10/98 10:02p Markd +// made mutant hands subclass of fists +// +// 3 5/16/98 5:00p Markd +// Added flip variable +// +// 2 5/11/98 11:25a Markd +// First time +// +// 1 5/11/98 10:18a Markd +// +// 1 5/11/98 9:55a Markd +// +// DESCRIPTION: +// Mutant Hands +// + +#ifndef __MUTANTHANDS_H__ +#define __MUTANTHANDS_H__ + +#include "g_local.h" +#include "item.h" +#include "weapon.h" +#include "fists.h" + +class EXPORT_FROM_DLL MutantHands : public Fists + { + public: + CLASS_PROTOTYPE( MutantHands ); + + MutantHands::MutantHands(); + }; + +#endif /* MutantHands.h */ diff --git a/navigate.cpp b/navigate.cpp new file mode 100644 index 0000000..a51eb9c --- /dev/null +++ b/navigate.cpp @@ -0,0 +1,2548 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/navigate.cpp $ +// $Revision:: 68 $ +// $Author:: Markd $ +// $Date:: 11/18/98 7:47p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/navigate.cpp $ +// +// 68 11/18/98 7:47p Markd +// Allowed NearestNode to not use bounding box +// +// 67 11/12/98 8:58p Jimdose +// made creating paths message a developer only thing +// +// 66 11/12/98 2:31a Jimdose +// Added ReadFile. Archives now read from pak files +// +// 65 11/08/98 6:33p Jimdose +// added SetNodeFlagsEvent +// +// 64 10/27/98 3:52a Jimdose +// made AI_FindNode accept node numbers from scripts +// +// 63 10/26/98 8:23p Jimdose +// CalcPathEvent and DisconnectPathEvent weren't returning after call to +// Event::Error +// +// 62 10/26/98 6:32p Jimdose +// made CalcPathEvent do fast path calculation +// +// 61 10/26/98 5:16p Jimdose +// Added CalcPath and DisconnectPath +// +// 60 10/26/98 4:59p Jimdose +// added recalc paths +// +// 59 10/26/98 2:16p Markd +// Fixed pathnode target issue +// +// 58 10/24/98 3:26a Markd +// Put in tolerance for horizontal test +// +// 57 10/23/98 9:41p Jimdose +// Re-fixed the paths for monster clip brushes. Path traces no longer get +// blocked by monsters or players. +// +// 56 10/23/98 5:25p Jimdose +// Made paths work with monster clip brushes +// +// 55 10/19/98 12:10a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 54 10/18/98 3:22a Jimdose +// Added code for timing paths +// +// 53 10/17/98 12:22a Jimdose +// made it so paths could be saved in savegames +// +// 52 10/14/98 10:57p Jimdose +// mapname is now passed into PathSearch::Init +// +// 51 10/14/98 5:21p Markd +// Added jumping capabilities to the path nodes +// +// 50 9/18/98 10:58p Jimdose +// Started on swimming actors +// +// 49 9/14/98 5:39p Jimdose +// NearestNode now requires that you pass in the entity that is going to use +// the path. +// NearestNode no longer hard codes the size when testing moves +// Added ai_shownodenums +// Revamped tests for valid paths. Actors get stuck much less often. +// Made DrawAllConnections more usefull. +// ClearPathTo now uses binary search for finding the max height of a path. +// Upped version number +// Added FindEntities to fixup door entity numbers when loading pathfiles +// +// 48 9/08/98 9:50p Jimdose +// Enabled multi-size bounding box path code +// Got rid of skipstaticnodes +// Fixed bug with AI_ResetNodes where it never freed the nodes +// Added version checking to path file +// changed granularity of size info in paths from 16 to 8 +// path loading code now autosaves the pathnodes when the file version is wrong +// +// 47 9/03/98 9:11p Jimdose +// made path code aware of doors +// don't save out paths if an error occured while reading the paths +// +// 46 8/29/98 9:44p Jimdose +// Added call info to G_Trace +// +// 45 8/26/98 11:14p Jimdose +// Fixed off by 1 error in AI_GetNode and AI_RemoveNode +// Made path tests check for startsolid in traces +// +// 44 8/24/98 6:58p Jimdose +// Added array of heights for each width that a path supports +// +// 43 8/19/98 7:57p Jimdose +// Began support for ladder and jump nodes +// +// 42 8/18/98 10:01p Jimdose +// Separated checks for connection from checks for near nodes so that non-door +// nodes will not connect to nodes through doors +// +// 41 8/16/98 12:58a Jimdose +// Off by one error in AI_FindNode. doh! +// +// 40 8/15/98 5:33p Jimdose +// I made AI_FindNode use ai_maxnode instead of MAX_PATHNODES +// +// 39 7/31/98 8:09p Jimdose +// Script commands now include flags to indicate cheats and console commands +// +// 38 7/26/98 5:36a Jimdose +// ai_maxnodes now incremented +// added occupiedTime +// +// 37 7/26/98 4:07a Jimdose +// Added animnames, targetnames, angles, etc. to archive +// +// 36 7/26/98 2:41a Jimdose +// Path files moved to maps directory +// Path files now contain full connection information to improve load times +// +// 35 7/25/98 2:10a Jimdose +// Removed square root calculation from NearestNode +// Preparing for door nodes +// +// 34 7/19/98 8:27p Jimdose +// Made DrawAllConnections check if the node is in the pvs before drawing it +// +// 33 6/17/98 10:12p Jimdose +// Fixed height bug in ClearPathTo +// +// 32 6/17/98 3:03p Markd +// Changed NumArgs back to previous behavior +// +// 31 6/13/98 8:23p Jimdose +// Removed ai_trainlevel cvar +// Made search algorithm into it's own class +// Added maxwidth and maxheight to paths +// +// 30 6/10/98 7:53p Markd +// Made NumArgs behave correctly like argc +// +// 29 6/09/98 5:32p Jimdose +// added ai_maxnode +// +// 28 6/04/98 10:48p Jimdose +// Fixed a bunch of things that got broken just in time for E3. Paths and +// scripting actually work now. +// +// 27 5/27/98 5:11a Jimdose +// working on ai +// +// 26 5/25/98 7:51p Jimdose +// Made archive functions use AI_GetNode to get each node in the level +// +// 25 5/25/98 5:30p Jimdose +// Pathnodes are no longer a subclass of Entity. This was done to save on +// edicts +// +// 24 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 +// +// 23 5/24/98 1:03a Jimdose +// Removed unused variable +// +// 22 5/22/98 9:37p Jimdose +// Made paths check through doors +// +// 21 5/18/98 8:14p Jimdose +// Renamed Navigator back to PathManager +// +// 20 5/14/98 10:18p Jimdose +// Remove ai_shownodes +// No longer uses eon model +// Added UpdateNode for moving nodes +// +// 19 5/13/98 4:47p Jimdose +// RemoveFromGrid now disconnects node from other nodes +// enabled ClearNodes +// +// 18 5/09/98 9:48p Jimdose +// gi.FindFile was failing because it prepended the gamename to the filename. +// Changed it to a fopen +// +// 17 5/09/98 8:03p Jimdose +// Added automatic path saving and loading +// +// 16 5/08/98 2:51p Jimdose +// Worked on archiving +// +// 15 5/07/98 10:43p Jimdose +// added archiving +// +// 14 5/03/98 4:43p Jimdose +// changed Vector class +// +// 13 4/29/98 5:18p Jimdose +// Added ai_showroutes +// +// 12 4/27/98 6:09p Jimdose +// Changed alpha on debug lines +// +// 11 4/27/98 4:11p Jimdose +// nodes are now stored in a 2d grid allowing for more effiecient nearestnode +// queries. +// +// 10 4/20/98 5:44p Jimdose +// working on ai +// +// 9 4/20/98 2:45p Jimdose +// working on ai +// +// 8 4/18/98 4:06p Jimdose +// Working on ai +// +// 7 4/18/98 3:01p Jimdose +// Working on ai +// +// 6 4/16/98 3:15p Jimdose +// ClearOPEN and ClearCLOSED now clear OPEN and CLOSED respectively. This was +// the cause of the exit crash +// +// 5 4/16/98 2:07p Jimdose +// Major rewrite +// Cleaned up code a lot +// PathSearch uses premade adjacency map +// +// 4 4/07/98 11:55p Jimdose +// Changed beams to specify color +// +// 3 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 2 3/02/98 5:44p Jimdose +// Continued development. Now use Path class to represent finished paths. +// +// 1 2/25/98 2:27p Jimdose +// +// 9 2/21/98 1:07p Jimdose +// Updated for Q2 dlls +// +// 7 12/06/97 6:32p Jimdose +// Further evolution on path code. +// +// 6 11/17/97 5:26p Jimdose +// Added step up and step down values to call to testSlideMove. +// +// 5 11/12/97 2:11p Jimdose +// Simplified the interface to PathSearch +// +// 4 11/07/97 6:39p Jimdose +// More work on integrating with Sin +// +// 3 11/05/97 4:00p Jimdose +// More work converting to work with Sin. +// +// 2 9/26/97 6:14p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// C++ implementation of the A* search algorithm. +// + +#include "g_local.h" +#include "navigate.h" +#include "path.h" +#include "misc.h" +#include "doors.h" + +#define PATHFILE_VERSION 4 + +Event EV_AI_SavePaths( "ai_savepaths", EV_CHEAT ); +Event EV_AI_SaveNodes( "ai_save", EV_CHEAT ); +Event EV_AI_LoadNodes( "ai_load", EV_CHEAT ); +Event EV_AI_ClearNodes( "ai_clearnodes", EV_CHEAT ); +Event EV_AI_RecalcPaths( "ai_recalcpaths", EV_CHEAT ); +Event EV_AI_CalcPath( "ai_calcpath", EV_CHEAT ); +Event EV_AI_DisconnectPath( "ai_disconnectpath", EV_CHEAT ); +Event EV_AI_SetNodeFlags( "ai_setflags", EV_CHEAT ); + +cvar_t *ai_createnodes = NULL; +cvar_t *ai_showpath; +cvar_t *ai_debugpath; +cvar_t *ai_debuginfo; +cvar_t *ai_showroutes; +cvar_t *ai_shownodenums; +cvar_t *ai_timepaths; + +static Entity *IgnoreObjects[ MAX_EDICTS ]; +static int NumIgnoreObjects; + +static PathNode *pathnodes[ MAX_PATHNODES ]; +static qboolean pathnodesinitialized = false; +static qboolean loadingarchive = false; +int ai_maxnode; + +#define MASK_PATHSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_WINDOW|CONTENTS_FENCE) + +PathSearch PathManager; + +int path_checksthisframe; + +PathNode *AI_FindNode + ( + const char *name + ) + + { + int i; + + if ( !name ) + { + return NULL; + } + + if ( name[ 0 ] == '!' ) + { + name++; + return AI_GetNode( atof( name ) ); + } + + if ( name[ 0 ] == '$' ) + { + name++; + } + + for( i = 0; i <= ai_maxnode; i++ ) + { + if ( pathnodes[ i ] && ( pathnodes[ i ]->TargetName() == name ) ) + { + return pathnodes[ i ]; + } + } + + return NULL; + } + +PathNode *AI_GetNode + ( + int num + ) + + { + if ( ( num < 0 ) || ( num > MAX_PATHNODES ) ) + { + assert( false ); + return NULL; + } + + return pathnodes[ num ]; + } + +void AI_AddNode + ( + PathNode *node + ) + + { + int i; + + assert( node ); + + for( i = 0; i < MAX_PATHNODES; i++ ) + { + if ( pathnodes[ i ] == NULL ) + { + break; + } + } + + if ( i < MAX_PATHNODES ) + { + if ( i > ai_maxnode ) + { + ai_maxnode = i; + } + pathnodes[ i ] = node; + node->nodenum = i; + return; + } + + gi.error( "Exceeded MAX_PATHNODES!\n" ); + } + +void AI_RemoveNode + ( + PathNode *node + ) + + { + assert( node ); + if ( ( node->nodenum < 0 ) || ( node->nodenum > ai_maxnode ) ) + { + assert( false ); + gi.error( "Corrupt pathnode!\n" ); + } + + // If the nodenum is 0, the node was probably removed during initialization + // otherwise, it's a bug. + assert( ( pathnodes[ node->nodenum ] == node ) || ( node->nodenum == 0 ) ); + if ( pathnodes[ node->nodenum ] == node ) + { + pathnodes[ node->nodenum ] = NULL; + } + } + +void AI_ResetNodes + ( + void + ) + + { + int i; + + if ( !pathnodesinitialized ) + { + memset( pathnodes, 0, sizeof( pathnodes ) ); + pathnodesinitialized = true; + } + else + { + for( i = 0; i < MAX_PATHNODES; i++ ) + { + if ( pathnodes[ i ] ) + { + delete pathnodes[ i ]; + } + } + } + + ai_maxnode = 0; + } + +/*****************************************************************************/ +/*SINED info_pathnode (1 0 0) (-24 -24 0) (24 24 32) FLEE DUCK COVER DOOR JUMP LADDER + +FLEE marks the node as a safe place to flee to. Actor will be removed when it reaches a flee node and is not visible to a player. + +DUCK marks the node as a good place to duck behind during weapon fire. + +COVER marks the node as a good place to hide behind during weapon fire. + +DOOR marks the node as a door node. If an adjacent node has DOOR marked as well, the actor will only use the path if the door in between them is unlocked. + +JUMP marks the node as one to jump from when going to the node specified by target. +"target" the pathnode to jump to. + +/*****************************************************************************/ + +CLASS_DECLARATION( Listener, PathNode, "info_pathnode" ); + +Event EV_Path_FindChildren( "findchildren" ); +Event EV_Path_FindEntities( "findentities" ); + +ResponseDef PathNode::Responses[] = + { + { &EV_Path_FindChildren, ( Response )PathNode::FindChildren }, + { &EV_Path_FindEntities, ( Response )PathNode::FindEntities }, + { NULL, NULL } + }; + +static int numNodes = 0; +static PathNode *NodeList = NULL; + +PathNode::PathNode() + { + nodenum = 0; + if ( !loadingarchive ) + { + // our archive function will take care of this stuff + AI_AddNode( this ); + PostEvent( EV_Path_FindChildren, 0 ); + } + + occupiedTime = 0; + + nodeflags = G_GetIntArg( "spawnflags" ); + + setOrigin( G_GetSpawnArg( "origin" ) ); + + setangles = ( G_GetSpawnArg( "angle" ) || G_GetSpawnArg( "angles" ) ); + if ( setangles ) + { + float angle; + + angle = G_GetFloatArg( "angle", 0 ); + setAngles( G_GetVectorArg( "angles", Vector( 0, angle, 0 ) ) ); + } + + animname = G_GetStringArg( "anim" ); + targetname = G_GetStringArg( "targetname" ); + target = G_GetStringArg( "target" ); + + // crouch hieght + setSize( "-24 -24 0", "24 24 40" ); + + f = 0; + h = 0; + g = 0; + + inlist = NOT_IN_LIST; + + // reject is used to indicate that a node is unfit for ending on during a search + reject = false; + + numChildren = 0; + + Parent = NULL; + NextNode = NULL; + } + +PathNode::~PathNode() + { + PathManager.RemoveNode( this ); + + AI_RemoveNode( this ); + } + +str &PathNode::TargetName + ( + void + ) + + { + return targetname; + } + +void PathNode::setAngles + ( + Vector ang + ) + + { + worldangles = ang; + } + +void PathNode::setOrigin + ( + Vector org + ) + + { + worldorigin = org; + contents = gi.pointcontents( worldorigin.vec3() ); + } + +void PathNode::setSize + ( + Vector min, + Vector max + ) + + { + mins = min; + maxs = max; + } + +EXPORT_FROM_DLL void PathNode::Setup + ( + Vector pos + ) + + { + CancelEventsOfType( EV_Path_FindChildren ); + + setOrigin( pos ); + + ProcessEvent( EV_Path_FindChildren ); + } + +EXPORT_FROM_DLL void PathNode::Archive + ( + Archiver &arc + ) + + { + int i; + + arc.WriteInteger( nodenum ); + arc.WriteInteger( nodeflags ); + arc.WriteVector( worldorigin ); + arc.WriteVector( worldangles ); + arc.WriteBoolean( setangles ); + arc.WriteString( target ); + arc.WriteString( targetname ); + arc.WriteString( animname ); + + arc.WriteFloat( occupiedTime ); + arc.WriteInteger( entnum ); + + arc.WriteInteger( numChildren ); + for( i = 0; i < numChildren; i++ ) + { + arc.WriteShort( Child[ i ].node ); + arc.WriteShort( Child[ i ].moveCost ); + arc.WriteRaw( Child[ i ].maxheight, sizeof( Child[ i ].maxheight ) ); + arc.WriteInteger( Child[ i ].door ); + } + } + +EXPORT_FROM_DLL void PathNode::FindEntities + ( + Event *ev + ) + + { + int i; + Door *door; + PathNode *node; + + for( i = 0; i < numChildren; i++ ) + { + if ( Child[ i ].door ) + { + node = AI_GetNode( Child[ i ].node ); + + assert( node ); + + door = CheckDoor( node->worldorigin ); + if ( door ) + { + Child[ i ].door = door->entnum; + } + else + { + Child[ i ].door = 0; + } + } + } + } + +EXPORT_FROM_DLL void PathNode::Unarchive + ( + Archiver &arc + ) + + { + int i; + + nodenum = arc.ReadInteger(); + assert( nodenum <= MAX_PATHNODES ); + if ( nodenum > MAX_PATHNODES ) + { + arc.FileError( "Node exceeds max path nodes" ); + } + + nodeflags = arc.ReadInteger(); + + setOrigin( arc.ReadVector() ); + setAngles( arc.ReadVector() ); + + setangles = arc.ReadBoolean(); + target = arc.ReadString(); + targetname = arc.ReadString(); + animname = arc.ReadString(); + + occupiedTime = arc.ReadFloat(); + entnum = arc.ReadInteger(); + if ( !LoadingSavegame ) + { + occupiedTime = 0; + entnum = 0; + } + + numChildren = arc.ReadInteger(); + assert( numChildren <= NUM_PATHSPERNODE ); + if ( numChildren > NUM_PATHSPERNODE ) + { + arc.FileError( "Exceeded num paths per node" ); + } + + for( i = 0; i < numChildren; i++ ) + { + Child[ i ].node = arc.ReadShort(); + Child[ i ].moveCost = arc.ReadShort(); + arc.ReadRaw( Child[ i ].maxheight, sizeof( Child[ i ].maxheight ) ); + Child[ i ].door = arc.ReadInteger(); + } + + if ( !LoadingSavegame ) + { + // Fixup the doors + PostEvent( EV_Path_FindEntities, 0 ); + } + + pathnodes[ nodenum ] = this; + if ( ai_maxnode < nodenum ) + { + ai_maxnode = nodenum; + } + + PathManager.AddNode( this ); + } + +EXPORT_FROM_DLL void RestoreEnts + ( + void + ) + + { + int i; + + for( i = 0; i < NumIgnoreObjects; i++ ) + { + IgnoreObjects[ i ]->link(); + } + } + +EXPORT_FROM_DLL qboolean PathNode::TestMove + ( + Entity *ent, + Vector start, + Vector end, + Vector &min, + Vector &max, + qboolean allowdoors, + qboolean fulltest + ) + + { + // NOTE: TestMove may allow wide paths to succeed when they shouldn't since it + // may place the lower node above obstacles that actors can't step over. + // Since any path that's wide enough for large boxes must also allow + // thinner boxes to go through, you must ignore the results of TestMove + // when thinner checks have already failed. + trace_t trace; + Vector end_trace; + Vector pos; + Vector dir; + float t; + float dist; + + // By requiring that paths have STEPSIZE headroom above the path, we simplify the test + // to see if an actor can move to a node down to a simple trace. By stepping up the start + // and end points, we account for the actor's ability to step up any geometry lower than + // STEPSIZE in height. + start.z += STEPSIZE; + end.z += STEPSIZE; + + // Check the move + trace = G_Trace( start, min, max, end, ent, MASK_PATHSOLID, "PathNode::TestMove 1" ); + if ( trace.startsolid || ( trace.fraction != 1 ) ) + { + // No direct path available. The actor will most likely not be able to move through here. + return false; + } + + if ( !fulltest ) + { + // Since we're not doing a full test (full tests are only done when we're connecting nodes to save time), + // we test to see if the midpoint of the move would only cause a change in height of STEPSIZE + // from the predicted height. This prevents most cases where a dropoff lies between a actor and a node. + Vector pos; + + // Since we start and end are already STEPSIZE above the ground, we have to check twice STEPSIZE below + // the midpoint to see if the midpoint is on the ground. + dir = end - start; + pos = start + dir * 0.5; + end_trace = pos; + end_trace.z -= STEPSIZE * 2; + + // Check that the midpos is onground. This may fail on ok moves, but a true test would be too slow + // to do in real time. Also, we may miss cases where a dropoff exists before or after the midpoint. + // Once the actor is close enough to the drop off, it will discover the fall and hopefully try + // another route. + trace = G_Trace( pos, min, max, end_trace, ent, MASK_PATHSOLID, "PathNode::TestMove 2" ); + if ( trace.startsolid || ( trace.fraction == 1 ) ) + { + // We're not on the ground, so there's a good posibility that we can't make this move without falling. + return false; + } + } + else if ( !( contents & MASK_WATER ) ) + { + // When we're creating the paths during load time, we do a much more exhaustive test to see if the + // path is valid. This test takes a bounding box and moves it 8 units at a time closer to the goal, + // testing the ground after each move. The test involves checking whether we will fall more than + // STEPSIZE to the ground (since we've raised the start and end points STEPSIZE above the ground, + // we must actually test 2 * STEPSIZE down to see if we're on the ground). After each test, we set + // the new height of the box to be STEPSIZE above the ground. Each move closer to the goal is only + // done horizontally to simulate how the actors normally move. This method ensures that any actor + // wider than 8 units in X and Y will be able to move from start to end. + // + // NOTE: This may allow wide paths to succeed when they shouldn't since it + // may place the lower node above obstacles that actors can't step over. + // Since any path that's wide enough for large boxes must also allow + // thinner boxes to go through, you must ignore the results of TestMove + // when thinner checks have already failed. + + dir = end - start; + dir.z = 0; + dist = dir.length(); + dir *= 1 / dist; + + // check the entire move + pos = start; + for( t = 0; t < dist; t += 8 ) + { + // Move the box to our position along the path and make our downward trace vector + end_trace.x = pos.x = start.x + t * dir.x; + end_trace.y = pos.y = start.y + t * dir.y; + end_trace.z = pos.z - STEPSIZE * 2; + + // check the ground + trace = G_Trace( pos, min, max, end_trace, ent, MASK_PATHSOLID, "PathNode::TestMove 3" ); + if ( trace.startsolid || ( trace.fraction == 1 ) ) + { + // Either we're stuck in something solid, or we would fall farther than STEPSIZE to the ground, + // so the path is not acceptable. + return false; + } + + // move the box to STEPSIZE above the ground. + pos.z = trace.endpos[ 2 ] + STEPSIZE; + } + } + + return true; + } + +EXPORT_FROM_DLL qboolean PathNode::CheckMove + ( + Entity *ent, + Vector pos, + Vector &min, + Vector &max, + qboolean allowdoors, + qboolean fulltest + ) + + { + // Since we need to support actors of variable widths, we need to do some special checks when a potential + // path goes up or down stairs. Placed pathnodes are only 16x16 in width, so when they are dropped to the + // ground, they may end in a position where a larger box would not fit. Making the pathnodes larger + // would make it hard to place paths where smaller actors could go, and making paths of various sizes would + // be overkill (more work for the level designer, or a lot of redundant data). The solution is to treat + // paths with verticle movement differently than paths that are purely horizontal. For horizontal moves, + // a simple trace STEPSIZE above the ground will be sufficient to prove that we can travel from one node + // to another, in either direction. For moves that have some change in height, we can check that we have + // a clear path by tracing horizontally from the higher node to a point where larger bounding box actors + // could then move at a slope downward to the lower node. This fixes the problem where path points that + // are larger than the depth of a step would have to intersect with the step in order to get the center + // of the box on solid ground. If you're still confused, well, tough. :) Think about the problem of + // larger bounding boxes going up stairs for a bit and you should see the problem. You can also read + // section 8.4, "Translating a Convex Polygon", from Computational Geometry in C (O'Rourke) (a f'ing + // great book, BTW) for information on similar problems (which is also a good explanation of how + // Quake's collision detection works). + trace_t trace; + int height; + + height = ( int )fabs( pos.z - worldorigin.z ); + // Check if the path is strictly horizontal + if ( !height ) + { + // We do two traces for the strictly horizontal test. One normal, and one STEPSIZE + // above. The normal trace is needed because most doors in the game aren't tall enough + // to allow actors to trace STEPSIZE above the ground. This means that failed horizontal + // tests require two traces. Annoying. + trace = G_Trace( worldorigin, min, max, pos, ent, MASK_PATHSOLID, "PathNode::CheckMove 1" ); + if ( !trace.startsolid && ( trace.fraction == 1 ) ) + { + return true; + } + + // Do the step test + return TestMove( ent, pos, worldorigin, min, max, allowdoors, fulltest ); + } + + Vector size; + float width; + + size = max - min; + width = max( size.x, size.y ); + + // if our bounding box is smaller than that of the pathnode, we can do the standard trace. + if ( width <= 32 ) + { + return TestMove( ent, pos, worldorigin, min, max, allowdoors, fulltest ); + } + + Vector start; + Vector end; + Vector delta; + float radius; + float len; + + // We calculate the radius of the smallest cylinder that would contain the bounding box. + // In some cases, this would make the first horizontal move longer than it needs to be, but + // that shouldn't be a problem. + + // multiply the width by 1/2 the square root of 2 to get radius + radius = width * 1.415 * 0.5; + + // Make sure that our starting position is the higher node since it doesn't matter which + // direction the move is in. + if ( pos.z < worldorigin.z ) + { + start = worldorigin; + end = pos; + } + else + { + start = pos; + end = worldorigin; + } + + // If the distance between the two points is less than the radius of the bounding box, + // then we only have to do the horizontal test since larger bounding boxes would not fall. + delta = end - start; + len = delta.length(); + if ( len <= radius ) + { + end.z = start.z; + return TestMove( ent, start, end, min, max, allowdoors, fulltest ); + } + + Vector mid; + + // normalize delta and multiply by radius (saving a few multiplies by combining into one). + delta *= radius / len; + + mid = start; + mid.x += delta.x; + mid.y += delta.y; + + // Check the horizontal move + if ( !TestMove( ent, start, mid, min, max, allowdoors, fulltest ) ) + { + return false; + } + + // Calculate our new endpoint + end.z -= delta.z; + + // Check our new sloping move + return TestMove( ent, mid, end, min, max, allowdoors, fulltest ); + } + +EXPORT_FROM_DLL Door *PathNode::CheckDoor + ( + Vector pos + ) + + { + trace_t trace; + Entity *ent; + + trace = G_Trace( worldorigin, vec_zero, vec_zero, pos, NULL, MASK_PATHSOLID, "PathNode::CheckDoor" ); + + ent = trace.ent->entity; + if ( ent && ent->isSubclassOf( Door ) ) + { + return ( Door * )ent; + } + + return NULL; + } + +EXPORT_FROM_DLL qboolean PathNode::CheckMove + ( + Vector pos, + Vector min, + Vector max + ) + + { + return true; + } + +EXPORT_FROM_DLL qboolean PathNode::CheckPath + ( + PathNode *node, + Vector min, + Vector max, + qboolean fulltest + ) + + { + Vector delta; + qboolean allowdoors; + qboolean result; + + delta = node->worldorigin - worldorigin; + delta[ 2 ] = 0; + if ( delta.length() >= PATHMAP_CELLSIZE ) + { + return false; + } + + allowdoors = ( nodeflags & AI_DOOR ) && ( node->nodeflags & AI_DOOR ); + + result = CheckMove( NULL, node->worldorigin, min, max, allowdoors, fulltest ); + RestoreEnts(); + + return result; + } + +EXPORT_FROM_DLL qboolean PathNode::ClearPathTo + ( + PathNode *node, + byte maxheight[ NUM_WIDTH_VALUES ], + qboolean fulltest + ) + + { + int i; + int width; + int height; + int bottom; + int top; + Vector min; + Vector max; + Vector bmin; + Vector bmax; + qboolean path; + edict_t *touch[ MAX_EDICTS ]; + Entity *ent; + int num; + + path = false; + for( i = 0; i < NUM_WIDTH_VALUES; i++ ) + { + maxheight[ i ] = 0; + } + + width = NUM_WIDTH_VALUES * WIDTH_STEP * 0.5; + min = Vector( -width, -width, 0 ); + max = Vector( width, width, MAX_HEIGHT ); + G_CalcBoundsOfMove( worldorigin, node->worldorigin, min, max, &bmin, &bmax ); + + num = gi.BoxEdicts( bmin.vec3(), bmax.vec3(), touch, MAX_EDICTS, AREA_SOLID ); + for( i = 0; i < num; i++ ) + { + ent = touch[ i ]->entity; + if ( ent && ent->isSubclassOf( Door ) ) + { + ent->unlink(); + } + } + + for( i = 0; i < NUM_WIDTH_VALUES; i++ ) + { + width = ( i + 1 ) * WIDTH_STEP * 0.5; + + min.x = min.y = -width; + max.x = max.y = width; + + // Perform a binary search to find the height of the path. Neat, eh? :) + bottom = 0; + top = MAX_HEIGHT; + while( top >= bottom ) + { + height = ( ( bottom + top + 3 ) >> 1 ) & ~0x3; + if ( !height ) + { + break; + } + + max.z = ( float )height; + if ( !CheckPath( node, min, max, fulltest ) ) + { + top = height - 4; + } + else + { + bottom = height + 4; + maxheight[ i ] = height; + } + } + + if ( !maxheight[ i ] ) + { + // If no paths were available at this width, don't allow any wider paths. + // CheckPath uses TestMove which may allow wide paths to succeed when they + // shouldn't since it may place the lower node above obstacles that actors + // can't step over. Since any path that's wide enough for large boxes must + // also allow thinner boxes to go through, this check avoids the hole in + // TestMove's functioning. + break; + } + + path = true; + } + + // Restore the doors + for( i = 0; i < num; i++ ) + { + ent = touch[ i ]->entity; + if ( ent && ent->isSubclassOf( Door ) ) + { + ent->link(); + } + } + + return path; + } + +EXPORT_FROM_DLL qboolean PathNode::LadderTo + ( + PathNode *node, + byte maxheight[ NUM_WIDTH_VALUES ] + ) + + { + int i; + int j; + int m; + int width; + Vector min; + Vector max; + qboolean path; + + trace_t trace; + + if ( !( nodeflags & AI_LADDER ) || !( node->nodeflags & AI_LADDER ) ) + { + return false; + } + + if ( ( worldorigin.x != node->worldorigin.x ) || ( worldorigin.y != node->worldorigin.y ) ) + { + return false; + } + + path = false; + + for( i = 0; i < NUM_WIDTH_VALUES; i++ ) + { + width = ( i + 1 ) * WIDTH_STEP * 0.5; + min = Vector( -width, -width, 12 ); + max = Vector( width, width, 40 ); + + trace = G_Trace( worldorigin, min, max, node->worldorigin, NULL, MASK_PATHSOLID, "PathNode::LadderTo 1" ); + if ( ( trace.fraction != 1 ) || trace.startsolid ) + { + maxheight[ i ] = 0; + continue; + } + + path = true; + + m = 40; + for( j = 48; j < MAX_HEIGHT; j+= 8 ) + { + max.z = j; + trace = G_Trace( worldorigin, min, max, node->worldorigin, NULL, MASK_PATHSOLID, "PathNode::LadderTo 2" ); + if ( ( trace.fraction != 1 ) || trace.startsolid ) + { + break; + } + + m = j; + } + + maxheight[ i ] = m; + } + + return path; + } + +EXPORT_FROM_DLL qboolean PathNode::ConnectedTo + ( + PathNode *node + ) + + { + int i; + + for( i = 0; i < numChildren; i++ ) + { + if ( Child[ i ].node == node->nodenum ) + { + return true; + } + } + + return false; + } + +EXPORT_FROM_DLL void PathNode::ConnectTo + ( + PathNode *node, + byte maxheight[ NUM_WIDTH_VALUES ], + float cost, + Door *door + ) + + { + int i; + + if ( ( numChildren < NUM_PATHSPERNODE ) && ( node != this ) ) + { + Child[ numChildren ].node = node->nodenum; + for( i = 0; i < NUM_WIDTH_VALUES; i++ ) + { + Child[ numChildren ].maxheight[ i ] = maxheight[ i ]; + } + Child[ numChildren ].moveCost = ( int )cost; + Child[ numChildren ].door = door ? door->entnum : 0; + numChildren++; + } + else + { + warning( "ConnectTo", "Child overflow" ); + } + } + +EXPORT_FROM_DLL void PathNode::ConnectTo + ( + PathNode *node, + byte maxheight[ NUM_WIDTH_VALUES ] + ) + + { + Vector delta; + Door *door; + + door = CheckDoor( node->worldorigin ); + delta = node->worldorigin - worldorigin; + ConnectTo( node, maxheight, delta.length(), door ); + } + +EXPORT_FROM_DLL void PathNode::Disconnect + ( + PathNode *node + ) + + { + int i; + + for( i = 0; i < numChildren; i++ ) + { + if ( Child[ i ].node == node->nodenum ) + { + break; + } + } + + // Should never happen, but let it slide after release + assert( i != numChildren ); + if ( i == numChildren ) + { + return; + } + + numChildren--; + + // Since we're not worried about the order of the nodes, just + // move the last node into the slot occupied by the removed node. + Child[ i ] = Child[ numChildren ]; + Child[ numChildren ].node = 0; + Child[ numChildren ].moveCost = 0; + } + +EXPORT_FROM_DLL void PathNode::FindChildren + ( + Event *ev + ) + + { + trace_t trace; + Vector end; + Vector start; + + worldorigin.x = ( ( int )( worldorigin.x * 0.125 ) ) * 8; + worldorigin.y = ( ( int )( worldorigin.y * 0.125 ) ) * 8; + setOrigin( worldorigin ); + + if ( !( contents & MASK_WATER ) ) + { + start = worldorigin + "0 0 1"; + end = worldorigin; + end[ 2 ] -= 256; + + trace = G_Trace( start, mins, maxs, end, NULL, MASK_PATHSOLID, "PathNode::FindChildren" ); + if ( trace.fraction != 1 && !trace.allsolid ) + { + setOrigin( trace.endpos ); + } + } + + PathManager.AddNode( this ); + } + +EXPORT_FROM_DLL void PathNode::DrawConnections + ( + void + ) + + { + int i; + pathway_t *path; + PathNode *node; + + for( i = 0; i < numChildren; i++ ) + { + path = &Child[ i ]; + node = AI_GetNode( path->node ); + + G_DebugLine( worldorigin + "0 0 24", node->worldorigin + "0 0 24", 0.7, 0.7, 0, 1 ); + } + } + +EXPORT_FROM_DLL void DrawAllConnections + ( + void + ) + + { + int i; + pathway_t *path; + PathNode *node; + PathNode *n; + Vector down; + Vector up; + Vector dir; + Vector p1; + Vector p2; + Vector p3; + Vector playerorigin; + qboolean showroutes; + qboolean shownums; + qboolean draw; + int maxheight; + int pathnum; + + showroutes = ( ai_showroutes->value != 0 ); + shownums = ( ai_shownodenums->value != 0 ); + + if ( ai_showroutes->value == 1 ) + { + pathnum = ( 32 / WIDTH_STEP ) - 1; + } + else + { + pathnum = ( ( ( int )ai_showroutes->value ) / WIDTH_STEP ) - 1; + } + + if ( ( pathnum < 0 ) || ( pathnum >= MAX_WIDTH ) ) + { + gi.printf( "ai_showroutes: Value out of range\n" ); + gi.cvar_set( "ai_showroutes", "0" ); + return; + } + + // Figure out where the camera is + assert( g_edicts[ 1 ].client ); + playerorigin.x = g_edicts[ 1 ].client->ps.pmove.origin[ 0 ]; + playerorigin.y = g_edicts[ 1 ].client->ps.pmove.origin[ 1 ]; + playerorigin.z = g_edicts[ 1 ].client->ps.pmove.origin[ 2 ]; + playerorigin *= 0.125; + playerorigin += g_edicts[ 1 ].client->ps.viewoffset; + + for( node = NodeList; node != NULL; node = node->chain ) + { + if ( !gi.inPVS( playerorigin.vec3(), node->worldorigin.vec3() ) ) + { + continue; + } + + if ( shownums ) + { + G_DrawDebugNumber( node->worldorigin + Vector( 0, 0, 14 ), node->nodenum, 1.5, 1, 1, 0 ); + } + + draw = false; + for( i = 0; i < node->numChildren; i++ ) + { + path = &node->Child[ i ]; + n = AI_GetNode( path->node ); + + if ( !showroutes ) + { + continue; + } + + maxheight = path->maxheight[ pathnum ]; + if ( maxheight == 0 ) + { + continue; + } + + draw = true; + + // don't draw the path if it's already been drawn by the destination node + if ( n->drawtime < level.time ) + { + down.z = 2; + up.z = maxheight; + G_DebugLine( node->worldorigin + down, n->worldorigin + down, 0, 1, 0, 1 ); + G_DebugLine( n->worldorigin + down, n->worldorigin + up, 0, 1, 0, 1 ); + G_DebugLine( node->worldorigin + up, n->worldorigin + up, 0, 1, 0, 1 ); + G_DebugLine( node->worldorigin + up, node->worldorigin + down, 0, 1, 0, 1 ); + } + + // draw an arrow for the direction + dir.x = n->worldorigin.x - node->worldorigin.x; + dir.y = n->worldorigin.y - node->worldorigin.y; + dir.normalize(); + + p1 = node->worldorigin; + p1.z += maxheight * 0.5; + p2 = dir * 8; + p3 = p1 + p2 * 2; + + G_DebugLine( p1, p3, 0, 1, 0, 1 ); + + p2.z += 8; + G_DebugLine( p3, p3 - p2, 0, 1, 0, 1 ); + + p2.z -= 16; + G_DebugLine( p3, p3 - p2, 0, 1, 0, 1 ); + } + + if ( !draw ) + { + // Put a little X where the node is to show that it had no connections + p1 = node->worldorigin; + p1.z += 2; + + p2 = Vector( 12, 12, 0 ); + G_DebugLine( p1 - p2, p1 + p2, 1, 0, 0, 1 ); + + p2.x = -12; + G_DebugLine( p1 - p2, p1 + p2, 1, 0, 0, 1 ); + } + + node->drawtime = level.time; + } + } + +MapCell::MapCell() + { + Init(); + } + +MapCell::~MapCell() + { + Init(); + } + +EXPORT_FROM_DLL void MapCell::Init + ( + void + ) + + { + numnodes = 0; + memset( nodes, 0, sizeof( nodes ) ); + } + +EXPORT_FROM_DLL qboolean MapCell::AddNode + ( + PathNode *node + ) + + { + if ( numnodes >= PATHMAP_NODES ) + { + return false; + } + + nodes[ numnodes ] = ( short )node->nodenum; + numnodes++; + + return true; + } + +EXPORT_FROM_DLL qboolean MapCell::RemoveNode + ( + PathNode *node + ) + + { + int i; + int num; + + num = node->nodenum; + for( i = 0; i < numnodes; i++ ) + { + if ( num == ( int )nodes[ i ] ) + { + break; + } + } + + if ( i >= numnodes ) + { + return false; + } + + numnodes--; + + // Since we're not worried about the order of the nodes, just + // move the last node into the slot occupied by the removed node. + nodes[ i ] = nodes[ numnodes ]; + nodes[ numnodes ] = 0; + + return true; + } + +EXPORT_FROM_DLL PathNode *MapCell::GetNode + ( + int index + ) + + { + assert( index >= 0 ); + assert( index < numnodes ); + if ( index >= numnodes ) + { + return NULL; + } + + return AI_GetNode( nodes[ index ] ); + } + +EXPORT_FROM_DLL int MapCell::NumNodes + ( + void + ) + + { + return numnodes; + } + +/* All + work and no play + makes Jim a dull boy. All + work and no play makes Jim a + dull boy. All work and no play + makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work and + no play makes Jim a dull boy. All work and + no play makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no play makes + Jim a dull boy. All work and no play makes Jim a + dull boy. All work and no play makes Jim a dull boy. + All work and no play makes Jim a dull boy. All work + and no play makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no play makes Jim a + dull boy. All work and no play makes Jim a dull boy. All + work and no play makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work and no play makes Jim + a dull boy. All work and no play makes Jim a dull boy. + All work and no play makes Jim a dull boy. All work and + no play makes Jim a dull boy. All work and no play makes + Jim a dull boy. All work and no play makes Jim a dull + boy. All work and no play makes Jim a dull boy. All work + and no play makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no play makes Jim a + dull boy. All work and no play makes Jim a dull boy. All + work and no play makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work and no play makes + Jim a dull boy. All work and no play makes Jim a dull + boy. All work and no play makes Jim a dull boy. All + work and no play makes Jim a dull boy. All work and + no play makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work + and no play makes Jim a dull boy. + All work and no play makes + Jim a dull boy. All work + and no play makes + Jim a +*/ + +CLASS_DECLARATION( Class, PathSearch, NULL ); + +ResponseDef PathSearch::Responses[] = + { + { &EV_AI_SavePaths, ( Response )PathSearch::SavePathsEvent }, + { &EV_AI_LoadNodes, ( Response )PathSearch::LoadNodes }, + { &EV_AI_SaveNodes, ( Response )PathSearch::SaveNodes }, + { &EV_AI_ClearNodes, ( Response )PathSearch::ClearNodes }, + { &EV_AI_SetNodeFlags, ( Response )PathSearch::SetNodeFlagsEvent }, + { &EV_AI_RecalcPaths, ( Response )PathSearch::RecalcPathsEvent }, + { &EV_AI_CalcPath, ( Response )PathSearch::CalcPathEvent }, + { &EV_AI_DisconnectPath, ( Response )PathSearch::DisconnectPathEvent }, + + { NULL, NULL } + }; + +void PathSearch::AddToGrid + ( + PathNode *node, + int x, + int y + ) + + { + PathNode *node2; + MapCell *cell; + int numnodes; + int i; + int j; + byte maxheight[ NUM_WIDTH_VALUES ]; + + cell = GetNodesInCell( x, y ); + if ( !cell ) + { + return; + } + + if ( !cell->AddNode( node ) ) + { + warning( "AddToGrid", "Node overflow at ( %d, %d )\n", x, y ); + return; + } + + if ( !loadingarchive ) + { + // + // explicitly link up the targets and their destinations + // + if ( node->nodeflags & AI_JUMP ) + { + if ( node->target.length() > 1 ) + { + node2 = AI_FindNode( node->target.c_str() ); + if ( node2 ) + { + for( j = 0; j < NUM_WIDTH_VALUES; j++ ) + { + maxheight[ j ] = MAX_HEIGHT; + } + node->ConnectTo( node2, maxheight ); + } + } + } + + // Connect the node to its neighbors + numnodes = cell->NumNodes(); + for( i = 0; i < numnodes; i++ ) + { + node2 = ( PathNode * )cell->GetNode( i ); + if ( node2 == node ) + { + continue; + } + + if ( ( node->numChildren < NUM_PATHSPERNODE ) && !node->ConnectedTo( node2 ) ) + { + if ( node->ClearPathTo( node2, maxheight ) || node->LadderTo( node2, maxheight ) ) + { + node->ConnectTo( node2, maxheight ); + } + else if ( ( node->nodeflags & AI_JUMP ) && ( node->target == node2->targetname ) ) + { + //FIXME + // don't hardcode size + for( j = 0; j < NUM_WIDTH_VALUES; j++ ) + { + maxheight[ j ] = MAX_HEIGHT; + } + node->ConnectTo( node2, maxheight ); + } + } + + if ( ( node2->numChildren < NUM_PATHSPERNODE ) && !node2->ConnectedTo( node ) ) + { + if ( node2->ClearPathTo( node, maxheight ) || node2->LadderTo( node, maxheight ) ) + { + node2->ConnectTo( node, maxheight ); + } + else if ( ( node2->nodeflags & AI_JUMP ) && ( node2->target == node->targetname ) ) + { + //FIXME + // don't hardcode size + for( j = 0; j < NUM_WIDTH_VALUES; j++ ) + { + maxheight[ j ] = MAX_HEIGHT; + } + node2->ConnectTo( node, maxheight ); + } + } + } + } + } + +qboolean PathSearch::RemoveFromGrid + ( + PathNode *node, + int x, + int y + ) + + { + MapCell *cell; + PathNode *node2; + int numnodes; + int i; + + cell = GetNodesInCell( x, y ); + if ( !cell || !cell->RemoveNode( node ) ) + { + return false; + } + + // Disconnect the node from all nodes in the cell + numnodes = cell->NumNodes(); + for( i = 0; i < numnodes; i++ ) + { + node2 = ( PathNode * )cell->GetNode( i ); + if ( node2->ConnectedTo( node ) ) + { + node2->Disconnect( node ); + } + } + + return true; + } + +int PathSearch::NodeCoordinate + ( + float coord + ) + + { + return ( ( int )coord + 4096 - ( PATHMAP_CELLSIZE / 2 ) ) / PATHMAP_CELLSIZE; + } + +int PathSearch::GridCoordinate + ( + float coord + ) + + { + return ( ( int )coord + 4096 ) / PATHMAP_CELLSIZE; + } + +void PathSearch::AddNode + ( + PathNode *node + ) + + { + int x; + int y; + + assert( node ); + + numNodes++; + + if ( NodeList == NULL ) + { + NodeList = node; + node->chain = NULL; + } + else + { + node->chain = NodeList; + NodeList = node; + } + + x = NodeCoordinate( node->worldorigin[ 0 ] ); + y = NodeCoordinate( node->worldorigin[ 1 ] ); + + AddToGrid( node, x, y ); + AddToGrid( node, x + 1, y ); + AddToGrid( node, x, y + 1 ); + AddToGrid( node, x + 1, y + 1 ); + + node->gridX = x; + node->gridY = y; + } + +void PathSearch::RemoveNode + ( + PathNode *node + ) + + { + int x; + int y; + PathNode *n; + PathNode *p; + + assert( node ); + + x = node->gridX; + y = node->gridY; + + RemoveFromGrid( node, x, y ); + RemoveFromGrid( node, x + 1, y ); + RemoveFromGrid( node, x, y + 1 ); + RemoveFromGrid( node, x + 1, y + 1 ); + + p = NULL; + for( n = NodeList; n != node; p = n, n = n->chain ) + { + if ( !n ) + { + // Not in list. + return; + } + } + + if ( p ) + { + p->chain = n->chain; + } + else + { + NodeList = n->chain; + } + + n->chain = NULL; + numNodes--; + } + +void PathSearch::UpdateNode + ( + PathNode *node + ) + + { + int x; + int y; + int mx; + int my; + + assert( node ); + + x = NodeCoordinate( node->worldorigin[ 0 ] ); + y = NodeCoordinate( node->worldorigin[ 1 ] ); + + mx = node->gridX; + my = node->gridY; + + RemoveFromGrid( node, mx, my ); + RemoveFromGrid( node, mx + 1, my ); + RemoveFromGrid( node, mx, my + 1 ); + RemoveFromGrid( node, mx + 1, my + 1 ); + + node->numChildren = 0; + + AddToGrid( node, x, y ); + AddToGrid( node, x + 1, y ); + AddToGrid( node, x, y + 1 ); + AddToGrid( node, x + 1, y + 1 ); + + node->gridX = x; + node->gridY = y; + } + +MapCell *PathSearch::GetNodesInCell + ( + int x, + int y + ) + + { + if ( ( x < 0 ) || ( x >= PATHMAP_GRIDSIZE ) || ( y < 0 ) || ( y >= PATHMAP_GRIDSIZE ) ) + { + return NULL; + } + + return &PathMap[ x ][ y ]; + } + +MapCell *PathSearch::GetNodesInCell + ( + Vector pos + ) + + { + int x; + int y; + + x = GridCoordinate( pos[ 0 ] ); + y = GridCoordinate( pos[ 1 ] ); + + return GetNodesInCell( x, y ); + } + +EXPORT_FROM_DLL PathNode *PathSearch::NearestNode + ( + Vector pos, + Entity *ent, + qboolean usebbox + ) + + { + Vector delta; + PathNode *node; + PathNode *bestnode; + float bestdist; + float dist; + int n; + int i; + MapCell *cell; + Vector min; + Vector max; + + cell = GetNodesInCell( pos ); + if ( !cell ) + { + return NULL; + } + + if ( ent && usebbox ) + { + min = ent->mins; + max = ent->maxs; + } + else + { + min = Vector( -16, -16, 12 ); + max = Vector( 16, 16, 40 ); + } + + n = cell->NumNodes(); + + if ( ai_debugpath->value ) + { + gi.dprintf( "NearestNode: Checking %d nodes\n", n ); + } + + bestnode = NULL; + bestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + for( i = 0; i < n; i++ ) + { + node = ( PathNode * )cell->GetNode( i ); + if ( !node ) + { + continue; + } + + delta = node->worldorigin - pos; + + // get the distance squared (faster than getting real distance) + dist = delta * delta; + if ( ( dist < bestdist ) && node->CheckMove( ent, pos, min, max, false, false ) ) + { + bestnode = node; + bestdist = dist; + + // if we're close enough, early exit + if ( dist < 16 ) + { + break; + } + } + } + + return bestnode; + } + +EXPORT_FROM_DLL void PathSearch::Teleport + ( + Entity *teleportee, + Vector from, + Vector to + ) + + { + PathNode *node1; + PathNode *node2; + byte maxheight[ NUM_WIDTH_VALUES ]; + int j; + + if ( ai_createnodes->value ) + { + node1 = new PathNode; + node1->Setup( from ); + + node2 = new PathNode; + node2->Setup( to ); + + // FIXME + // shouldn't hard-code width and height + for( j = 0; j < NUM_WIDTH_VALUES; j++ ) + { + maxheight[ j ] = 72; + } + + // connect with 0 cost + node1->ConnectTo( node2, maxheight, 0 ); + } + } + +EXPORT_FROM_DLL void PathSearch::ShowNodes + ( + void + ) + + { + if ( ai_showroutes->value || ai_shownodenums->value ) + { + DrawAllConnections(); + } + } + +EXPORT_FROM_DLL int PathSearch::NumNodes + ( + void + ) + + { + return numNodes; + } + +EXPORT_FROM_DLL void PathSearch::Archive + ( + Archiver &arc + ) + + { + PathNode *node; + int i; + int num; + + num = 0; + for( i = 0; i < MAX_PATHNODES; i++ ) + { + node = AI_GetNode( i ); + if ( node ) + { + num++; + } + } + + arc.WriteInteger( num ); + for( i = 0; i < MAX_PATHNODES; i++ ) + { + node = AI_GetNode( i ); + if ( node ) + { + arc.WriteObject( node ); + } + } + + if ( ai_debuginfo->value ) + { + gi.dprintf( "Wrote %d path nodes\n", num ); + } + } + +EXPORT_FROM_DLL void PathSearch::ClearNodes + ( + Event *ev + ) + + { + PathNode *node; + int i; + int num; + + num = 0; + for( i = 0; i < MAX_PATHNODES; i++ ) + { + node = AI_GetNode( i ); + if ( node ) + { + node->PostEvent( EV_Remove, 0 ); + num++; + } + } + + if ( ai_debuginfo->value ) + { + gi.dprintf( "Deleted %d path nodes\n", num ); + } + } + +EXPORT_FROM_DLL void PathSearch::Unarchive + ( + Archiver &arc + ) + + { + int num; + int i; + int x; + int y; + + numNodes = 0; + NodeList = NULL; + loadingarchive = true; + + // Get rid of the nodes that were spawned by the map + AI_ResetNodes(); + + // Init the grid + for( x = 0; x < PATHMAP_GRIDSIZE; x++ ) + { + for( y = 0; y < PATHMAP_GRIDSIZE; y++ ) + { + PathMap[ x ][ y ].Init(); + } + } + + num = arc.ReadInteger(); + + assert( num <= MAX_PATHNODES ); + if ( num > MAX_PATHNODES ) + { + arc.FileError( "Exceeded max path nodes" ); + } + + for( i = 0; i < num; i++ ) + { + arc.ReadObject(); + } + + if ( ai_debuginfo->value ) + { + gi.dprintf( "Path nodes loaded: %d\n", NumNodes() ); + } + + loadingarchive = false; + } + +EXPORT_FROM_DLL void PathSearch::SetNodeFlagsEvent + ( + Event *ev + ) + + { + const char * token; + int i, argnum, flags; + int mask; + int action; + int nodenum; + PathNode *node; + +#define FLAG_IGNORE 0 +#define FLAG_SET 1 +#define FLAG_CLEAR 2 +#define FLAG_ADD 3 + + nodenum = ev->GetInteger( 1 ); + node = AI_GetNode( nodenum ); + + if ( !node ) + { + ev->Error( "Node not found." ); + return; + } + + 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, "flee")) + { + mask = AI_FLEE; + } + else if (!strcmpi (token, "duck")) + { + mask = AI_DUCK; + } + else if (!strcmpi (token, "cover")) + { + mask = AI_COVER; + } + else if (!strcmpi (token, "door")) + { + mask = AI_DOOR; + } + else if (!strcmpi (token, "jump")) + { + mask = AI_JUMP; + } + else if (!strcmpi (token, "ladder")) + { + mask = AI_LADDER; + } + else if (!strcmpi (token, "action")) + { + mask = AI_ACTION; + } + else + { + action = FLAG_IGNORE; + ev->Error( "Unknown token %s.", token ); + } + + switch (action) + { + case FLAG_SET: + node->nodeflags = 0; + + case FLAG_ADD: + node->nodeflags |= mask; + break; + + case FLAG_CLEAR: + node->nodeflags &= ~mask; + break; + + case FLAG_IGNORE: + break; + } + } + } + +EXPORT_FROM_DLL void PathSearch::CalcPathEvent + ( + Event *ev + ) + + { + int nodenum; + PathNode *node; + PathNode *node2; + int j; + byte maxheight[ NUM_WIDTH_VALUES ]; + + nodenum = ev->GetInteger( 1 ); + node = AI_GetNode( nodenum ); + + nodenum = ev->GetInteger( 2 ); + node2 = AI_GetNode( nodenum ); + + if ( !node || !node2 ) + { + ev->Error( "Node not found." ); + return; + } + + if ( ( node->numChildren < NUM_PATHSPERNODE ) && !node->ConnectedTo( node2 ) ) + { + if ( node->ClearPathTo( node2, maxheight, false ) || node->LadderTo( node2, maxheight ) ) + { + node->ConnectTo( node2, maxheight ); + } + else if ( ( node->nodeflags & AI_JUMP ) && ( node->target == node2->targetname ) ) + { + //FIXME + // don't hardcode size + for( j = 0; j < NUM_WIDTH_VALUES; j++ ) + { + maxheight[ j ] = MAX_HEIGHT; + } + node->ConnectTo( node2, maxheight ); + } + } + + if ( ( node2->numChildren < NUM_PATHSPERNODE ) && !node2->ConnectedTo( node ) ) + { + if ( node2->ClearPathTo( node, maxheight, false ) || node2->LadderTo( node, maxheight ) ) + { + node2->ConnectTo( node, maxheight ); + } + else if ( ( node2->nodeflags & AI_JUMP ) && ( node2->target == node->targetname ) ) + { + //FIXME + // don't hardcode size + for( j = 0; j < NUM_WIDTH_VALUES; j++ ) + { + maxheight[ j ] = MAX_HEIGHT; + } + node2->ConnectTo( node, maxheight ); + } + } + } + +EXPORT_FROM_DLL void PathSearch::DisconnectPathEvent + ( + Event *ev + ) + + { + int nodenum; + PathNode *node; + PathNode *node2; + + nodenum = ev->GetInteger( 1 ); + node = AI_GetNode( nodenum ); + + nodenum = ev->GetInteger( 2 ); + node2 = AI_GetNode( nodenum ); + + if ( !node || !node2 ) + { + ev->Error( "Node not found." ); + return; + } + + if ( node->ConnectedTo( node2 ) ) + { + node->Disconnect( node2 ); + } + + if ( node2->ConnectedTo( node ) ) + { + node2->Disconnect( node ); + } + } + +EXPORT_FROM_DLL void PathSearch::RecalcPathsEvent + ( + Event *ev + ) + + { + int nodenum; + PathNode *node; + + nodenum = ev->GetInteger( 1 ); + node = AI_GetNode( nodenum ); + if ( node ) + { + UpdateNode( node ); + } + else + { + ev->Error( "Node not found." ); + } + } + +EXPORT_FROM_DLL void PathSearch::SaveNodes + ( + Event *ev + ) + + { + Archiver arc; + str name; + + if ( ev->NumArgs() != 1 ) + { + gi.printf( "Usage: ai_save [filename]\n" ); + return; + } + + name = ev->GetString( 1 ); + + gi.printf( "Archiving\n" ); + + arc.Create( name ); + arc.WriteInteger( PATHFILE_VERSION ); + arc.WriteObject( this ); + arc.Close(); + + gi.printf( "done.\n" ); + } + +EXPORT_FROM_DLL void PathSearch::LoadNodes + ( + Event *ev + ) + + { + Archiver arc; + str name; + int version; + + if ( ev->NumArgs() != 1 ) + { + gi.printf( "Usage: ai_load [filename]\n" ); + return; + } + + gi.printf( "Loading nodes...\n" ); + + name = ev->GetString( 1 ); + + arc.Read( name ); + version = arc.ReadInteger(); + if ( version == PATHFILE_VERSION ) + { + arc.ReadObject( this ); + arc.Close(); + + gi.printf( "done.\n" ); + } + else + { + arc.Close(); + + gi.printf( "Expecting version %d path file. Path file is version %d.", PATHFILE_VERSION, version ); + + // Only replace the file if this event was called from our init function (as opposed to the user + // calling us from the console) and the version number is older than our current version. + if ( ( ev->GetSource() == EV_FROM_CODE ) && ( version < PATHFILE_VERSION ) ) + { + gi.printf( "Replacing file.\n\n" ); + + // At this point, the nodes are still scheduled to find their neighbors, because we posted this event + // before we the nodes were spawned. Post the event with 0 delay so that it gets processed after all + // the nodes find their neighbors. + PostEvent( EV_AI_SavePaths, 0 ); + } + else + { + // otherwise, just let them know that the path file needs to be replaced. + gi.printf( "Type 'ai_savepaths' at the console to replace the current path file.\n" ); + } + + // Print out something fairly obvious + gi.dprintf( "***********************************\n" + "***********************************\n" + "\n" + "Creating paths...\n" + "\n" + "***********************************\n" + "***********************************\n" ); + } + } + +EXPORT_FROM_DLL void PathSearch::SavePaths + ( + void + ) + + { + str filename; + Event *ev; + + if ( loadingarchive ) + { + // force it to zero since we probably had an error + gi.cvar_set( "ai_createnodes", "0" ); + } + + if ( !loadingarchive && ai_createnodes && ai_createnodes->value ) + { + filename = gi.GameDir(); + filename += "/maps/"; + filename += level.mapname; + filename += ".pth"; + + gi.dprintf( "Saving path nodes to '%s'\n", filename.c_str() ); + + ev = new Event( EV_AI_SaveNodes ); + ev->AddString( filename ); + ProcessEvent( ev ); + } + } + +EXPORT_FROM_DLL void PathSearch::SavePathsEvent + ( + Event *ev + ) + + { + str temp; + + temp = ai_createnodes->string; + gi.cvar_set( "ai_createnodes", "1" ); + + SavePaths(); + + gi.cvar_set( "ai_createnodes", temp.c_str() ); + } + +EXPORT_FROM_DLL void PathSearch::Init + ( + const char *mapname + ) + + { + int x; + int y; + str filename; + Event *ev; + + ai_createnodes = gi.cvar ("ai_createnodes", "0", 0); + ai_showpath = gi.cvar ("ai_showpath", "0", 0); + ai_debugpath = gi.cvar ("ai_debugpath", "0", 0); + ai_debuginfo = gi.cvar ("ai_debuginfo", "0", 0); + ai_showroutes = gi.cvar ("ai_showroutes", "0", 0); + ai_shownodenums = gi.cvar ("ai_shownodenums", "0", 0); + ai_timepaths = gi.cvar ("ai_timepaths", "0", 0); + + numNodes = 0; + NodeList = NULL; + loadingarchive = false; + + // Get rid of the nodes that were spawned by the map + AI_ResetNodes(); + + // Init the grid + for( x = 0; x < PATHMAP_GRIDSIZE; x++ ) + { + for( y = 0; y < PATHMAP_GRIDSIZE; y++ ) + { + PathMap[ x ][ y ].Init(); + } + } + + if ( mapname ) + { + filename = "maps/"; + filename += mapname; + filename += ".pth"; + if ( gi.LoadFile( filename.c_str(), NULL, 0 ) != -1 ) + { + ev = new Event( EV_AI_LoadNodes ); + ev->AddString( filename ); + + // This can't happen until the world is spawned + PostEvent( ev, 0 ); + } + else + { + // Print out something fairly obvious + gi.dprintf( "***********************************\n" + "***********************************\n" + "\n" + "No paths found. Creating paths...\n" + "\n" + "***********************************\n" + "***********************************\n" ); + } + } + } diff --git a/navigate.h b/navigate.h new file mode 100644 index 0000000..983566f --- /dev/null +++ b/navigate.h @@ -0,0 +1,947 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/navigate.h $ +// $Revision:: 42 $ +// $Author:: Markd $ +// $Date:: 11/18/98 7:47p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Quake 2 Engine/Sin/code/game/navigate.h $ +// +// 42 11/18/98 7:47p Markd +// Allowed nearestnode to ignore entity bbox +// +// 41 11/08/98 6:34p Jimdose +// added SetNodeFlagsEvent +// +// 40 10/26/98 6:32p Jimdose +// added fulltest to CheckPath and ClearPathTo +// +// 39 10/26/98 5:16p Jimdose +// Added CalcPath and DisconnectPath +// +// 38 10/26/98 4:42p Jimdose +// added recalcpaths +// +// 37 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 36 10/18/98 3:22a Jimdose +// Added code for timing paths +// +// 35 10/14/98 10:59p Jimdose +// map name is now passed into Init +// +// 34 10/14/98 5:22p Markd +// Changed jumptarget to target +// +// 33 9/22/98 12:49p Markd +// Put in archive and unarchive functions +// +// 32 9/18/98 10:59p Jimdose +// Started on swimming actors +// +// 31 9/14/98 5:40p Jimdose +// NearestNode now requires that you pass in the entity that is going to use +// the path. +// NearestNode no longer hard codes the size when testing moves +// Added ai_shownodenums +// Revamped tests for valid paths. Actors get stuck much less often. +// Made DrawAllConnections more usefull. +// ClearPathTo now uses binary search for finding the max height of a path. +// Upped version number +// Added FindEntities to fixup door entity numbers when loading pathfiles +// Fixed definition of MAX_WIDTH (it was still multiplied by 2 from the way it +// used to work). +// +// 30 9/08/98 9:51p Jimdose +// Changed NUM_PATHSPERNODE and NUM_WIDTH_VALUES +// Made CHECK_PATH use a granularity of 8 instead of 16 +// +// 29 9/03/98 9:12p Jimdose +// made paths aware of doors +// +// 28 8/29/98 9:53p Jimdose +// moved prototype of SV_TestMovestep to g_phys.h +// +// 27 8/24/98 6:58p Jimdose +// Added array of heights for each width that a path supports +// +// 26 8/19/98 7:59p Jimdose +// Began adding support for ladder and jump nodes +// +// 25 8/18/98 10:03p Jimdose +// Separated checks for connection from checks for near nodes so that non-door +// nodes will not connect to nodes through doors +// +// 24 8/15/98 5:32p Jimdose +// Increased MAX_PATHNODES +// +// 23 7/26/98 6:42a Jimdose +// Added entnum to pathnode +// +// 22 7/26/98 5:37a Jimdose +// added occupiedTime +// +// 21 7/26/98 2:41a Jimdose +// Path files moved to maps directory +// Path files now contain full connection information to improve load times +// +// 20 7/25/98 2:10a Jimdose +// Preparing for door nodes +// +// 19 7/17/98 8:34p Jimdose +// Fixed bug where CreatePath would overwrite the stack if the path was longer +// than MAX_PATH_LENGTH +// +// 18 6/13/98 8:21p Jimdose +// Moved search algorithm to separate object - PathFinder +// Removed optimize path stuff +// Added maxwidth and maxheight to paths +// +// 17 6/09/98 5:32p Jimdose +// added ai_maxnode +// +// 16 5/27/98 5:11a Jimdose +// working on ai +// +// 15 5/25/98 5:31p Jimdose +// Pathnodes are no longer a subclass of Entity. This was done to save on +// edicts +// +// 14 5/22/98 9:40p Jimdose +// Made paths check through Movers +// +// 13 5/18/98 8:14p Jimdose +// Renamed Navigator back to PathManager +// +// 12 5/14/98 10:26p Jimdose +// Added UpdateNode +// Made alot of info in PathNode private +// +// 11 5/13/98 4:50p Jimdose +// Added use of SafePtrs +// +// 10 5/09/98 8:04p Jimdose +// added path saving and loading +// PathSearch now a Listener instead of a Class object +// +// 9 5/07/98 10:43p Jimdose +// added archiving +// +// 8 4/29/98 5:39p Jimdose +// added ai_checkroutes +// +// 7 4/27/98 5:27p Jimdose +// working on ai +// +// 6 4/20/98 2:45p Jimdose +// working on ai +// +// 5 4/18/98 3:02p Jimdose +// Added ai_createnodes and ai_showpath +// working on ai +// +// 4 4/16/98 2:10p Jimdose +// Major rewrite +// +// 3 3/02/98 8:49p Jimdose +// Changed CLASS_PROTOTYPE to only take the classname +// +// 2 3/02/98 5:44p Jimdose +// Continued development. Now use Path class to represent finished paths. +// +// 1 2/25/98 2:27p Jimdose +// +// 8 2/21/98 1:07p Jimdose +// Updated for Q2 dlls +// +// 6 12/06/97 6:32p Jimdose +// Further evolution on path code. +// +// 5 11/12/97 2:11p Jimdose +// Simplified the interface to PathSearch +// +// 4 11/07/97 6:39p Jimdose +// More work on integrating with Sin +// +// 3 11/05/97 4:00p Jimdose +// More work converting to work with Sin. +// +// 2 9/26/97 6:14p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Potentially could be an C++ implementation of the A* search algorithm, but +// is currently unfinished. +// + +#ifndef __NAVIGATE_H__ +#define __NAVIGATE_H__ + +#include "g_local.h" +#include "class.h" +#include "entity.h" +#include "stack.h" +#include "container.h" +#include "doors.h" + +extern Event EV_AI_SavePaths; +extern Event EV_AI_SaveNodes; +extern Event EV_AI_LoadNodes; +extern Event EV_AI_ClearNodes; +extern Event EV_AI_RecalcPaths; +extern Event EV_AI_CalcPath; +extern Event EV_AI_DisconnectPath; + +extern cvar_t *ai_createnodes; +extern cvar_t *ai_showpath; +extern cvar_t *ai_shownodes; +extern cvar_t *ai_debugpath; +extern cvar_t *ai_debuginfo; +extern cvar_t *ai_showroutes; +extern cvar_t *ai_timepaths; + +extern int ai_maxnode; + +#define MAX_PATHCHECKSPERFRAME 4 + +extern int path_checksthisframe; + +#define MAX_PATH_LENGTH 128 // should be more than plenty +#define NUM_PATHSPERNODE 16 + +class Path; +class PathNode; + +#define NUM_WIDTH_VALUES 16 +#define WIDTH_STEP 8 +#define MAX_WIDTH ( WIDTH_STEP * NUM_WIDTH_VALUES ) +#define MAX_HEIGHT 128 + +#define CHECK_PATH( path, width, height ) \ + ( ( ( ( width ) >= MAX_WIDTH ) || ( ( width ) < 0 ) ) ? false : \ + ( ( int )( path )->maxheight[ ( ( width ) / WIDTH_STEP ) - 1 ] < ( int )( height ) ) ) + +typedef struct + { + short node; + short moveCost; + byte maxheight[ NUM_WIDTH_VALUES ]; + int door; + } pathway_t; + +typedef enum { NOT_IN_LIST, IN_OPEN, IN_CLOSED } pathlist_t; + +#define AI_FLEE 1 +#define AI_DUCK 2 +#define AI_COVER 4 +#define AI_DOOR 8 +#define AI_JUMP 16 +#define AI_LADDER 32 +#define AI_ACTION 64 + +void EXPORT_FROM_DLL DrawAllConnections( void ); + +class EXPORT_FROM_DLL PathNode : public Listener + { + public: + PathNode *chain; + pathway_t Child[ NUM_PATHSPERNODE ]; // these are the real connections between nodex + int numChildren; + + // These variables are all used during the search + int f; + int h; + int g; + + int gridX; + int gridY; + + float drawtime; + float occupiedTime; + int entnum; + + pathlist_t inlist; + + // reject is used to indicate that a node is unfit for ending on during a search + qboolean reject; + + PathNode *Parent; + + // For the open and closed lists + PathNode *NextNode; + + int nodeflags; + + friend class PathSearch; + friend void DrawAllConnections( void ); + + private : + qboolean TestMove( Entity *ent, Vector start, Vector end, Vector &min, Vector &max, qboolean allowdoors = false, qboolean fulltest = false ); + + qboolean ConnectedTo( PathNode *node ); + void ConnectTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ], float cost, Door *door = NULL ); + void ConnectTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ] ); + void Disconnect( PathNode *node ); + + void FindChildren( Event *ev ); + void FindEntities( Event *ev ); + + public: + CLASS_PROTOTYPE( PathNode ); + + int contents; + Vector worldorigin; + Vector worldangles; + Vector mins; + Vector maxs; + str targetname; + str target; + + int nodenum; + + qboolean setangles; + str animname; + + PathNode(); + ~PathNode(); + + void Setup( Vector pos ); + void setAngles( Vector ang ); + void setOrigin( Vector org ); + void setSize( Vector min, Vector max ); + str &TargetName( void ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + + qboolean CheckPath( PathNode *node, Vector min, Vector max, qboolean fulltest = true ); + Door *CheckDoor( Vector pos ); + + qboolean CheckMove( Entity *ent, Vector pos, Vector &min, Vector &max, qboolean allowdoors = false, qboolean fulltest = false ); + qboolean CheckMove( Vector pos, Vector min, Vector max ); + qboolean ClearPathTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ], qboolean fulltest = true ); + qboolean LadderTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ] ); + void DrawConnections( void ); + }; + +// +// Exported templated classes must be explicitly instantiated +// +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Stack; +template class EXPORT_FROM_DLL SafePtr; +#endif +typedef SafePtr PathNodePtr; + +#define PATHMAP_GRIDSIZE 32 +#define PATHMAP_CELLSIZE ( 8192 / PATHMAP_GRIDSIZE ) + +#define PATHMAP_NODES 126 // 128 - sizeof( int ) / sizeof( short ) + +class EXPORT_FROM_DLL MapCell + { + private : + int numnodes; + short nodes[ PATHMAP_NODES ]; + + public : + MapCell(); + ~MapCell(); + void Init( void ); + qboolean AddNode( PathNode *node ); + qboolean RemoveNode( PathNode *node ); + PathNode *GetNode( int index ); + int NumNodes( void ); + }; + +class EXPORT_FROM_DLL PathSearch : public Listener + { + private: + MapCell PathMap[ PATHMAP_GRIDSIZE ][ PATHMAP_GRIDSIZE ]; + + void AddToGrid( PathNode *node, int x, int y ); + qboolean RemoveFromGrid( PathNode *node, int x, int y ); + int NodeCoordinate( float coord ); + int GridCoordinate( float coord ); + void ClearNodes( Event *ev ); + void LoadNodes( Event *ev ); + void SaveNodes( Event *ev ); + void SavePathsEvent( Event *ev ); + void SetNodeFlagsEvent( Event *ev ); + void RecalcPathsEvent( Event *ev ); + void CalcPathEvent( Event *ev ); + void DisconnectPathEvent( Event *ev ); + + public: + CLASS_PROTOTYPE( PathSearch ); + + void Archive( Archiver &arc ); + void Unarchive( Archiver &arc ); + void AddNode( PathNode *node ); + void RemoveNode( PathNode *node ); + void UpdateNode( PathNode *node ); + MapCell *GetNodesInCell( int x, int y ); + MapCell *GetNodesInCell( Vector pos ); + PathNode *NearestNode( Vector pos, Entity *ent = NULL, qboolean usebbox = true ); + void Teleport( Entity *teleportee, Vector from, Vector to ); + void ShowNodes( void ); + int NumNodes( void ); + void SavePaths( void ); + void Init( const char *mapname ); + }; + +extern PathSearch PathManager; + +#define MAX_PATHNODES 2048 + +PathNode *AI_FindNode( const char *name ); +PathNode *AI_GetNode( int num ); +void AI_AddNode( PathNode *node ); +void AI_RemoveNode( PathNode *node ); +void AI_ResetNodes( void ); + +#include "path.h" + +template +class EXPORT_FROM_DLL PathFinder + { + private: + Stack stack; + PathNode *OPEN; + PathNode *CLOSED; + PathNode *endnode; + + void ClearPath( void ); + void ClearOPEN( void ); + void ClearCLOSED( void ); + PathNode *ReturnBestNode( void ); + void GenerateSuccessors( PathNode *BestNode ); + void Insert( PathNode *Successor ); + void PropagateDown( PathNode *Old ); + Path *CreatePath( PathNode *startnode ); + + public: + Heuristic heuristic; + + PathFinder(); + ~PathFinder(); + Path *FindPath( PathNode *from, PathNode *to ); + Path *FindPath( Vector start, Vector end ); + }; + +template +PathFinder::PathFinder() + { + OPEN = NULL; + CLOSED = NULL; + } + +template +PathFinder::~PathFinder() + { + ClearPath(); + } + +template +EXPORT_FROM_DLL void PathFinder::ClearOPEN + ( + void + ) + + { + PathNode *node; + + while( OPEN ) + { + node = OPEN; + OPEN = node->NextNode; + + node->inlist = NOT_IN_LIST; + node->NextNode = NULL; + node->Parent = NULL; + + // reject is used to indicate that a node is unfit for ending on during a search + node->reject = false; + } + } + +template +EXPORT_FROM_DLL void PathFinder::ClearCLOSED + ( + void + ) + + { + PathNode *node; + + while( CLOSED ) + { + node = CLOSED; + CLOSED = node->NextNode; + + node->inlist = NOT_IN_LIST; + node->NextNode = NULL; + node->Parent = NULL; + + // reject is used to indicate that a node is unfit for ending on during a search + node->reject = false; + } + } + +template +EXPORT_FROM_DLL void PathFinder::ClearPath + ( + void + ) + + { + stack.Clear(); + ClearOPEN(); + ClearCLOSED(); + } + +template +EXPORT_FROM_DLL Path *PathFinder::FindPath + ( + PathNode *from, + PathNode *to + ) + + { + Path *path; + PathNode *node; + int start; + int end; + qboolean checktime; + + checktime = false; + if ( ai_timepaths->value ) + { + start = G_Milliseconds(); + checktime = true; + } + + OPEN = NULL; + CLOSED = NULL; + + endnode = to; + + // Should always be NULL at this point + assert( !from->NextNode ); + + // make Open List point to first node + OPEN = from; + from->g = 0; + from->h = heuristic.dist( from, endnode ); + from->NextNode = NULL; + + node = ReturnBestNode(); + while( node && !heuristic.done( node, endnode ) ) + { + GenerateSuccessors( node ); + node = ReturnBestNode(); + } + + if ( !node ) + { + path = NULL; + if ( ai_debugpath->value ) + { + gi.dprintf( "Search failed--no path found.\n" ); + } + } + else + { + path = CreatePath( node ); + } + + ClearPath(); + + if ( checktime ) + { + end = G_Milliseconds(); + if ( ai_timepaths->value <= ( end - start ) ) + { + G_DebugPrintf( "%d: ent #%d : %d\n", level.framenum, heuristic.entnum, end - start ); + } + } + + return path; + } + +template +EXPORT_FROM_DLL Path *PathFinder::FindPath + ( + Vector start, + Vector end + ) + + { + PathNode *from; + PathNode *to; + Entity *ent; + + ent = G_GetEntity( heuristic.entnum ); + from = PathManager.NearestNode( start, ent ); + to = PathManager.NearestNode( end, ent ); + + if ( !from ) + { + if ( ai_debugpath->value ) + { + gi.dprintf( "Search failed--couldn't find closest source.\n" ); + } + return NULL; + } + + if ( !from || !to ) + { + if ( ai_debugpath->value ) + { + gi.dprintf( "Search failed--couldn't find closest destination.\n" ); + } + return NULL; + } + + return FindPath( from, to ); + } + +template +EXPORT_FROM_DLL Path *PathFinder::CreatePath + ( + PathNode *startnode + ) + + { + PathNode *node; + Path *p; + int i; + int n; + PathNode *reverse[ MAX_PATH_LENGTH ]; + + // unfortunately, the list goes goes from end to start, so we have to reverse it + for( node = startnode, n = 0; ( node != NULL ) && ( n < MAX_PATH_LENGTH ); node = node->Parent, n++ ) + { + assert( n < MAX_PATH_LENGTH ); + reverse[ n ] = node; + } + + p = new Path( n ); + for( i = n - 1; i >= 0; i-- ) + { + p->AddNode( reverse[ i ] ); + } + + if ( ai_debugpath->value ) + { + gi.dprintf( "%d nodes in path\n", n ); + gi.dprintf( "%d nodes allocated\n", PathManager.NumNodes() ); + } + + return p; + } + +template +EXPORT_FROM_DLL PathNode *PathFinder::ReturnBestNode + ( + void + ) + + { + PathNode *bestnode; + + if ( !OPEN ) + { + // No more nodes on OPEN + return NULL; + } + + // Pick node with lowest f, in this case it's the first node in list + // because we sort the OPEN list wrt lowest f. Call it BESTNODE. + + bestnode = OPEN; // point to first node on OPEN + OPEN = bestnode->NextNode; // Make OPEN point to nextnode or NULL. + + // Next take BESTNODE (or temp in this case) and put it on CLOSED + bestnode->NextNode = CLOSED; + CLOSED = bestnode; + + bestnode->inlist = IN_CLOSED; + + return( bestnode ); + } + +template +EXPORT_FROM_DLL void PathFinder::GenerateSuccessors + ( + PathNode *BestNode + ) + + { + int i; + int g; // total path cost - as stored in the linked lists. + PathNode *node; + pathway_t *path; + + for( i = 0; i < BestNode->numChildren; i++ ) + { + path = &BestNode->Child[ i ]; + node = AI_GetNode( path->node ); + + // g(Successor)=g(BestNode)+cost of getting from BestNode to Successor + g = BestNode->g + heuristic.cost( BestNode, i ); + + switch( node->inlist ) + { + case NOT_IN_LIST : + // Only allow this if it's valid + if ( heuristic.validpath( BestNode, i ) ) + { + node->Parent = BestNode; + node->g = g; + node->h = heuristic.dist( node, endnode ); + node->f = g + node->h; + + // Insert Successor on OPEN list wrt f + Insert( node ); + } + break; + + case IN_OPEN : + // if our new g value is < node's then reset node's parent to point to BestNode + if ( g < node->g ) + { + node->Parent = BestNode; + node->g = g; + node->f = g + node->h; + } + break; + + case IN_CLOSED : + // if our new g value is < Old's then reset Old's parent to point to BestNode + if ( g < node->g ) + { + node->Parent = BestNode; + node->g = g; + node->f = g + node->h; + + // Since we changed the g value of Old, we need + // to propagate this new value downwards, i.e. + // do a Depth-First traversal of the tree! + PropagateDown( node ); + } + break; + + default : + // shouldn't happen, but try to catch it during debugging phase + assert( NULL ); + gi.error( "Corrupted path node" ); + break; + } + } + } + +template +EXPORT_FROM_DLL void PathFinder::Insert + ( + PathNode *node + ) + + { + PathNode *prev; + PathNode *next; + int f; + + node->inlist = IN_OPEN; + f = node->f; + + // special case for if the list is empty, or it should go at head of list (lowest f) + if ( ( OPEN == NULL ) || ( f < OPEN->f ) ) + { + node->NextNode = OPEN; + OPEN = node; + return; + } + + // do sorted insertion into OPEN list in order of ascending f + prev = OPEN; + next = OPEN->NextNode; + while( ( next != NULL ) && ( next->f < f ) ) + { + prev = next; + next = next->NextNode; + } + + // insert it between the two nodes + node->NextNode = next; + prev->NextNode = node; + } + +template +EXPORT_FROM_DLL void PathFinder::PropagateDown + ( + PathNode *node + ) + + { + int c; + unsigned g; + unsigned movecost; + PathNode *child; + PathNode *parent; + pathway_t *path; + int n; + + g = node->g; + n = node->numChildren; + for( c = 0; c < n; c++ ) + { + path = &node->Child[ c ]; + child = AI_GetNode( path->node ); + + movecost = g + heuristic.cost( node, c ); + if ( movecost < child->g ) + { + child->g = movecost; + child->f = child->g + child->h; + child->Parent = node; + + // reset parent to new path. + // Now the Child's branch need to be + // checked out. Remember the new cost must be propagated down. + stack.Push( child ); + } + } + + while( !stack.Empty() ) + { + parent = stack.Pop(); + n = parent->numChildren; + for( c = 0; c < n; c++ ) + { + path = &parent->Child[ c ]; + child = AI_GetNode( path->node ); + + // we stop the propagation when the g value of the child is equal or better than + // the cost we're propagating + movecost = parent->g + path->moveCost; + if ( movecost < child->g ) + { + child->g = movecost; + child->f = child->g + child->h; + child->Parent = parent; + stack.Push( child ); + } + } + } + } + +class EXPORT_FROM_DLL StandardMovement + { + public: + int minwidth; + int minheight; + int entnum; + + inline void setSize + ( + Vector size + ) + + { + minwidth = max( size.x, size.y ); + minheight = size.z; + } + + inline int dist + ( + PathNode *node, + PathNode *end + ) + + { + Vector delta; + int d1; + int d2; + int d3; + int h; + + delta = node->worldorigin - end->worldorigin; + d1 = abs( ( int )delta[ 0 ] ); + d2 = abs( ( int )delta[ 1 ] ); + d3 = abs( ( int )delta[ 2 ] ); + h = max( d1, d2 ); + h = max( d3, h ); + + return h; + } + + inline qboolean validpath + ( + PathNode *node, + int i + ) + + { + pathway_t *path; + PathNode *n; + + path = &node->Child[ i ]; + if ( CHECK_PATH( path, minwidth, minheight ) ) + { + return false; + } + + if ( path->door ) + { + Door *door; + + door = ( Door * )G_GetEntity( path->door ); + if ( !door->CanBeOpenedBy( G_GetEntity( entnum ) ) ) + { + return false; + } + } + + n = AI_GetNode( path->node ); + if ( n && ( n->occupiedTime > level.time ) && ( n->entnum != entnum ) ) + { + return false; + } + + return true; + } + + inline int cost + ( + PathNode *node, + int i + ) + + { + return node->Child[ i ].moveCost; + } + + inline qboolean done + ( + PathNode *node, + PathNode *end + ) + + { + return node == end; + } + }; + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL PathFinder; +#endif +typedef PathFinder StandardMovePath; + +#endif /* navigate.h */ diff --git a/object.cpp b/object.cpp new file mode 100644 index 0000000..96bdac3 --- /dev/null +++ b/object.cpp @@ -0,0 +1,573 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/object.cpp $ +// $Revision:: 48 $ +// $Author:: Markd $ +// $Date:: 11/13/98 10:00p $ +// +// Copyright (C) 1998 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/object.cpp $ +// +// 48 11/13/98 10:00p Markd +// increased damage of throwobjects +// +// 47 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 46 10/19/98 11:50p Aldie +// Force objects to animate at least one frame, so the edict will have the +// proper value in them. +// +// 45 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 44 9/23/98 5:19p Markd +// Put DAMAGE_NO in killed functions of these classes +// +// 43 9/22/98 5:19p Markd +// Put in new consolidated gib function +// +// 42 9/22/98 3:21p Markd +// put in parentmode lockout for blood and gibs +// +// 41 9/15/98 6:37p Markd +// Added RotatedBounds flag support +// +// 40 9/01/98 3:05p Markd +// Rewrote explosion code +// +// 39 8/29/98 7:16p Markd +// Added FireBarrel +// +// 38 8/29/98 5:27p Markd +// added specialfx, replaced misc with specialfx where appropriate +// +// 37 8/09/98 6:18p Markd +// put in random yaw +// +// 36 8/09/98 5:50p Markd +// Rewrote ThrowObjects pickup and throw +// +// 35 8/08/98 8:47p Markd +// incremental check-in for building purposes +// +// 34 7/21/98 10:05p Markd +// Added explosion stuff to when things die +// +// 33 7/12/98 5:39p Markd +// fixed bug with animating world objects +// +// 32 7/09/98 12:08a Jimdose +// Changed process of remove to a post +// +// 31 6/24/98 12:39p Markd +// Added default tesselation percentage +// +// 30 6/18/98 2:00p Markd +// rewrote tesselation code +// +// 29 6/13/98 7:32p Markd +// Put in default tesselation of 10 thick +// +// 28 6/10/98 4:42p Markd +// Only tesselate 75% when killed +// +// 27 5/26/98 1:29a Markd +// fixed bounding box issues +// +// 26 5/25/98 11:32p Markd +// fixed bug with objects being not_shootable but still blocking bullets +// +// 25 5/25/98 1:08a Markd +// Fixed killtargets on objects +// +// 24 5/24/98 2:47p Markd +// Made char *'s into const char *'s +// +// 23 5/24/98 1:03a Jimdose +// Added sound events for ai +// +// 22 5/20/98 1:33p Markd +// Added target and killtarget behavior when dead +// +// 21 5/20/98 11:11a Markd +// removed char * dependency +// +// 20 5/19/98 9:48p Markd +// fixed object spawning with new spawn flags +// +// 19 5/13/98 6:19p Markd +// Rotate mins and maxs now in constructor +// +// 18 5/09/98 7:11p Markd +// Removed sound parameter from tesselate command +// +// 17 5/08/98 7:01p Markd +// Added FL_DARKEN support +// +// 16 5/03/98 8:10p Markd +// Took out set bounds code, it is already done in Entity +// +// 15 5/03/98 4:36p Jimdose +// Changed Vector class +// +// 14 5/01/98 11:09a Markd +// Added sound to tesselation event +// +// 13 4/25/98 5:09p Markd +// Added up and down support for angles +// +// 12 4/20/98 11:01a Markd +// Fixed client side prediction of non-solid shootables +// +// 11 4/14/98 6:56p Markd +// Added thickness to tesselation +// +// 10 4/10/98 1:23a Markd +// Got rid of damage function, added FL_TESSELATE and other flags +// +// 9 4/09/98 1:40p Markd +// Added in CONTENTS_SHOOTABLE stuff +// +// 8 4/07/98 8:00p Markd +// removed defhandle, changed all SINMDL calls to modelindex calls, removed +// SINMDL prefix +// +// 7 4/06/98 6:52p Markd +// Put in anim support and skin support +// +// 6 4/06/98 5:46p Jimdose +// Added "angles" spawn values +// +// 5 4/06/98 12:02a Markd +// Tweaked damage stuff +// +// 4 4/05/98 10:54p Markd +// Added Damage event +// +// 3 4/05/98 10:42p Markd +// Moved tesselate to Entity +// +// 2 4/05/98 9:41p Markd +// Initial +// +// 1 4/05/98 9:03p Markd +// +// 2 4/04/98 4:19p Aldie +// First version of skeet mod. +// +// DESCRIPTION: Skeet Entity +// + + +#include "g_local.h" +#include "object.h" +#include "misc.h" +#include "explosion.h" +#include "gibs.h" +#include "specialfx.h" + + +CLASS_DECLARATION( Entity, Object, "object" ); + +ResponseDef Object::Responses[] = + { + { &EV_Killed, ( Response )Object::Killed }, + { NULL, NULL } + }; + +Object::Object() + { + const char * animname; + const char * skinname; + Vector defangles; + + SetKillTarget( G_GetSpawnArg( "killtarget" ) ); + setSolidType( SOLID_BBOX ); + // if it isn't solid, lets still make it shootable + if (spawnflags & 1) + { + if ( !(spawnflags & 2) ) + { + edict->svflags |= SVF_SHOOTABLE; + setOrigin( origin ); + } + else + setSolidType( SOLID_NOT ); + } + if (!health) + { + health = (maxs-mins).length(); + max_health = health; + } + takedamage = DAMAGE_YES; + if ( spawnflags & 2 ) + { + takedamage = DAMAGE_NO; + } + + // angles + defangles = Vector( 0, G_GetFloatArg( "angle", 0 ), 0 ); + if (defangles.y == -1) + { + defangles = Vector( -90, 0, 0 ); + } + else if (defangles.y == -2) + { + defangles = Vector( 90, 0, 0 ); + } + angles = G_GetVectorArg( "angles", defangles ); + setAngles( angles ); + setOrigin( origin ); + + // + // we want the bounds of this model auto-rotated + // + flags |= FL_ROTATEDBOUNDS; + + // + // rotate the mins and maxs for the model + // + setSize( mins, maxs ); + + animname = G_GetSpawnArg( "anim" ); + if ( animname && strlen(animname) && gi.IsModel( edict->s.modelindex ) ) + { + int animnum; + + animnum = gi.Anim_NumForName( edict->s.modelindex, animname ); + if (animnum >= 0) + NextAnim( animnum ); + + // Sets up the edict + AnimateFrame(); + StopAnimating(); + // + // we only want to start animating if it was explicitly defined in the def file + // + //StartAnimating(); + } + skinname = G_GetSpawnArg( "skin" ); + if ( skinname && strlen(skinname) && gi.IsModel( edict->s.modelindex ) ) + { + int skinnum; + + skinnum = gi.Skin_NumForName( edict->s.modelindex, skinname ); + if (skinnum >= 0) + edict->s.skinnum = skinnum; + } + if ( parentmode->value ) + { + flags &= ~FL_BLOOD; + flags &= ~FL_DIE_GIBS; + } + + if ( !(flags & (FL_BLOOD|FL_SPARKS|FL_TESSELATE)) ) + { + flags |= FL_DARKEN; + flags |= FL_TESSELATE; + flags |= FL_DIE_TESSELATE; + } + } + +void Object::Killed(Event *ev) + { + Entity * ent; + Entity * attacker; + Vector dir; + Event * event; + const char * name; + int num; + + takedamage = DAMAGE_NO; + setSolidType( SOLID_NOT ); + hideModel(); + + attacker = ev->GetEntity( 1 ); + + if (flags & FL_DIE_TESSELATE) + { + dir = worldorigin - attacker->worldorigin; + TesselateModel + ( + this, + tess_min_size, + tess_max_size, + dir, + ev->GetInteger( 2 ), + tess_percentage, + tess_thickness, + vec3_origin + ); + ProcessEvent( EV_BreakingSound ); + } + + if (flags & FL_DIE_EXPLODE) + { + CreateExplosion( worldorigin, 50, 0.5f, true, this, this, this ); + } + + if (flags & FL_DIE_GIBS) + { + setSolidType( SOLID_NOT ); + hideModel(); + + CreateGibs( this, -150, edict->s.scale, 3 ); + } + +// +// kill the killtargets +// + name = KillTarget(); + if ( name && strcmp( name, "" ) ) + { + num = 0; + do + { + num = G_FindTarget( num, name ); + if ( !num ) + { + break; + } + + ent = G_GetEntity( num ); + ent->PostEvent( EV_Remove, 0 ); + } + while ( 1 ); + } + +// +// fire targets +// + name = Target(); + if ( name && strcmp( name, "" ) ) + { + num = 0; + do + { + num = G_FindTarget( num, name ); + if ( !num ) + { + break; + } + + ent = G_GetEntity( num ); + + event = new Event( EV_Activate ); + event->AddEntity( attacker ); + ent->ProcessEvent( event ); + } + while ( 1 ); + } + + PostEvent( EV_Remove, 0 ); + } + +/*****************************************************************************/ +/*SINED func_throwobject (0 .5 .8) (0 0 0) (0 0 0) + +This is an object you can pickup and throw at people +/*****************************************************************************/ +CLASS_DECLARATION( Object, ThrowObject, "func_throwobject" ); + +Event EV_ThrowObject_Pickup( "pickup" ); +Event EV_ThrowObject_Throw( "throw" ); +Event EV_ThrowObject_PickupOffset( "pickupoffset" ); +Event EV_ThrowObject_ThrowSound( "throwsound" ); + +ResponseDef ThrowObject::Responses[] = + { + { &EV_Touch, ( Response )ThrowObject::Touch }, + { &EV_ThrowObject_Pickup, ( Response )ThrowObject::Pickup }, + { &EV_ThrowObject_Throw, ( Response )ThrowObject::Throw }, + { &EV_ThrowObject_PickupOffset, ( Response )ThrowObject::PickupOffset }, + { &EV_ThrowObject_ThrowSound, ( Response )ThrowObject::ThrowSound }, + { NULL, NULL } + }; + +ThrowObject::ThrowObject() + { + pickup_offset = vec_zero; + } + +void ThrowObject::PickupOffset + ( + Event *ev + ) + { + pickup_offset = edict->s.scale * ev->GetVector( 1 ); + } + +void ThrowObject::ThrowSound + ( + Event *ev + ) + { + throw_sound = ev->GetString( 1 ); + } + +void ThrowObject::Touch + ( + Event *ev + ) + + { + Event * e; + Entity * other; + + if ( movetype != MOVETYPE_BOUNCE ) + return; + + other = ev->GetEntity( 1 ); + assert( other ); + + if ( other->isSubclassOf( Teleporter ) ) + { + return; + } + + if ( other->entnum == owner ) + { + return; + } + + if ( throw_sound.length() ) + { + e = new Event( EV_StopEntitySound ); + ProcessEvent( e ); + } + + if (other->takedamage) + other->Damage( this, G_GetEntity( owner ), size.length()*velocity.length()/400, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT, -1, -1, 1.0f ); + Damage( this, this, max_health, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT, -1, -1 , 1 ); + } + +void ThrowObject::Throw + ( + Event *ev + ) + { + Entity *owner; + Entity *targetent; + float speed; + float traveltime; + float vertical_speed; + Vector target, dir; + float grav; + Vector xydir; + Event * e; + + owner = ev->GetEntity( 1 ); + assert( owner ); + if (!owner) + return; + speed = ev->GetFloat( 2 ); + if ( !speed ) + speed = 1; + targetent = ev->GetEntity( 3 ); + assert( targetent ); + if (!targetent) + return; + if ( ev->NumArgs() == 4 ) + grav = ev->GetFloat( 4 ); + else + grav = 1; + + e = new Event( EV_Detach ); + ProcessEvent( e ); + + this->owner = owner->entnum; + edict->owner = owner->edict; + + gravity = grav; + + target = targetent->worldorigin; + target.z += targetent->viewheight; + setMoveType( MOVETYPE_BOUNCE ); + setSolidType( SOLID_BBOX ); + edict->clipmask = MASK_PROJECTILE; + + dir = target - worldorigin; + xydir = dir; + xydir.z = 0; + traveltime = xydir.length() / speed; + vertical_speed = ( dir.z / traveltime ) + ( 0.5f * gravity * sv_gravity->value * traveltime ); + xydir.normalize(); + + // setup ambient flying sound + if ( throw_sound.length() ) + { + e = new Event( EV_RandomEntitySound ); + e->AddString( throw_sound ); + ProcessEvent( e ); + } + + velocity = speed * xydir; + velocity.z = vertical_speed; + + angles = velocity.toAngles(); + angles[ PITCH ] = - angles[ PITCH ]; + setAngles( angles ); + + avelocity.x = crandom() * 200; + avelocity.y = crandom() * 200; + takedamage = DAMAGE_YES; + + } + +void ThrowObject::Pickup + ( + Event *ev + ) + { + Entity * ent; + Event * e; + str bone; + + ent = ev->GetEntity( 1 ); + assert( ent ); + if ( !ent ) + return; + bone = ev->GetString( 2 ); + + e = new Event( EV_Attach ); + e->AddEntity( ent ); + e->AddString( bone ); + setOrigin( pickup_offset ); + ProcessEvent( e ); + edict->s.renderfx &= ~RF_FRAMELERP; + } + +// +// Barrel with fire coming out of it +// +CLASS_DECLARATION( Object, FireBarrel, "world_firebarrel" ); + +ResponseDef FireBarrel::Responses[] = + { + { NULL, NULL } + }; + +FireBarrel::FireBarrel() + { + Vector offset; + + fire = new FireSprite; + // put the fire 3/4 of the way up the barrel + offset[ 2 ] = (3 * size[ 2 ]) / 4; + fire->setOrigin( worldorigin + offset ); + fire->setScale( edict->s.scale ); + } + +FireBarrel::~FireBarrel() + { + fire->PostEvent( EV_Remove, 0 ); + fire = NULL; + } \ No newline at end of file diff --git a/object.h b/object.h new file mode 100644 index 0000000..5fcf253 --- /dev/null +++ b/object.h @@ -0,0 +1,134 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/object.h $ +// $Revision:: 7 $ +// $Author:: Markd $ +// $Date:: 9/22/98 12:49p $ +// +// 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/object.h $ +// +// 7 9/22/98 12:49p Markd +// Put in archive and unarchive functions +// +// 6 8/29/98 7:16p Markd +// Added FireBarrel +// +// 5 8/09/98 5:50p Markd +// Rewrote ThrowObjects pickup and throw +// +// 4 8/08/98 8:24p Markd +// Added ThrowObject +// +// 3 4/10/98 1:24a Markd +// Got rid of damage func +// +// 2 4/05/98 10:53p Markd +// first time +// +// 1 4/05/98 10:51p Markd +// +// DESCRIPTION: +// Object class +// + +#ifndef __OBJECT_H__ +#define __OBJECT_H__ + +#include "g_local.h" +#include "entity.h" +#include "specialfx.h" + +class EXPORT_FROM_DLL Object : public Entity + { + private: + public: + CLASS_PROTOTYPE( Object ); + Object::Object(); + void Killed(Event *ev); + }; + +extern Event EV_ThrowObject_Pickup; +extern Event EV_ThrowObject_Throw; + +class EXPORT_FROM_DLL ThrowObject : public Object + { + private: + int owner; + Vector pickup_offset; + str throw_sound; + public: + CLASS_PROTOTYPE( ThrowObject ); + ThrowObject::ThrowObject(); + void Touch(Event *ev); + void Throw( Event * ev ); + void Pickup( Event * ev ); + void PickupOffset( Event * ev ); + void ThrowSound( Event * ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void ThrowObject::Archive + ( + Archiver &arc + ) + { + Object::Archive( arc ); + + arc.WriteInteger( owner ); + arc.WriteVector( pickup_offset ); + arc.WriteString( throw_sound ); + } + +inline EXPORT_FROM_DLL void ThrowObject::Unarchive + ( + Archiver &arc + ) + { + Object::Unarchive( arc ); + + arc.ReadInteger( &owner ); + arc.ReadVector( &pickup_offset ); + arc.ReadString( &throw_sound ); + } + + +class EXPORT_FROM_DLL FireBarrel : public Object + { + private: + FireSprite *fire; + public: + CLASS_PROTOTYPE( FireBarrel ); + FireBarrel::FireBarrel(); + FireBarrel::~FireBarrel(); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void FireBarrel::Archive + ( + Archiver &arc + ) + { + Object::Archive( arc ); + + arc.WriteObjectPointer( fire ); + } + +inline EXPORT_FROM_DLL void FireBarrel::Unarchive + ( + Archiver &arc + ) + { + Object::Unarchive( arc ); + + arc.ReadObjectPointer( ( Class ** )&fire ); + } + +#endif /* object.h */ diff --git a/path.cpp b/path.cpp new file mode 100644 index 0000000..b2b890c --- /dev/null +++ b/path.cpp @@ -0,0 +1,541 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/path.cpp $ +// $Revision:: 33 $ +// $Author:: Jimdose $ +// $Date:: 10/16/98 8:25p $ +// +// 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/path.cpp $ +// +// 33 10/16/98 8:25p Jimdose +// Added distanceToNextNode and dirToNextNode; +// Optimized ClosestPointOnPath, DistanceAlongPath, PointAtDistance +// +// 32 10/16/98 1:53a Jimdose +// Added another NextNode function for finding the next node after the +// specified node +// +// 31 10/14/98 10:18p Jimdose +// Made GetNode check for NULL nodes and error out +// +// 30 10/14/98 10:16p Jimdose +// Added assert to GetNode to help find any NULL pointers in the list +// +// 29 8/29/98 9:45p Jimdose +// Made SV_ commands begin with G_ +// +// 28 8/18/98 10:02p Jimdose +// Added NextNode +// +// 27 6/04/98 10:48p Jimdose +// Fixed a bunch of things that got broken just in time for E3. Paths and +// scripting actually work now. +// +// 26 5/25/98 5:30p Jimdose +// Pathnodes are no longer a subclass of Entity. This was done to save on +// edicts +// +// 25 5/22/98 9:38p Jimdose +// working on ai +// +// 24 5/20/98 6:37p Jimdose +// ClosestPointOnPath does a trace to see if the point is accessible +// +// 23 5/18/98 8:14p Jimdose +// Renamed Navigator back to PathManager +// +// 22 5/16/98 3:38p Jimdose +// Added ClosestPointOnPath, DistanceAlongPath, and PointAtDistance +// +// 21 5/13/98 4:48p Jimdose +// PathList now uses SafePtr +// +// 20 5/05/98 6:12p Jimdose +// removed spline test stuff +// +// 19 5/05/98 2:38p Jimdose +// testing splines +// +// 18 4/27/98 6:09p Jimdose +// Changed alpha on debug lines +// +// 17 4/27/98 4:12p Jimdose +// Now use debug lines for drawing path instead of beams to cut down on net +// traffic +// +// 16 4/16/98 2:08p Jimdose +// Rewrote to use new PathNode +// +// 15 4/07/98 11:55p Jimdose +// Changed beams to specify color +// +// 14 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 13 3/05/98 3:49p Jimdose +// Made the pathinfo_t == operator EXPORT_FROM_DLL +// +// 12 3/04/98 1:42p Jimdose +// Added pathinfo_t comparison function +// +// 11 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. +// +// 10 3/02/98 5:43p Jimdose +// Continued development on paths. Now uses Path class to represent a path. +// +// DESCRIPTION: +// + +#include "g_local.h" +#include "entity.h" +#include "path.h" +#include "container.h" +#include "navigate.h" +#include "misc.h" + +CLASS_DECLARATION( Class, Path, NULL ); + +ResponseDef Path::Responses[] = + { + { NULL, NULL } + }; + +Path::Path() + { + pathlength = 0; + from = NULL; + to = NULL; + nextnode = 1; + } + +Path::Path + ( + int numnodes + ) + + { + pathlength = 0; + from = NULL; + to = NULL; + nextnode = 1; + pathlist.Resize( numnodes ); + dirToNextNode.Resize( numnodes ); + distanceToNextNode.Resize( numnodes ); + } + +void Path::Clear + ( + void + ) + + { + nextnode = 1; + pathlength = 0; + from = NULL; + to = NULL; + pathlist.FreeObjectList(); + dirToNextNode.FreeObjectList(); + distanceToNextNode.FreeObjectList(); + } + +void Path::Reset + ( + void + ) + + { + nextnode = 1; + } + +PathNode *Path::Start + ( + void + ) + + { + return from; + } + +PathNode *Path::End + ( + void + ) + + { + return to; + } + +void Path::AddNode + ( + PathNode *node + ) + + { + Vector dir; + float len; + int num; + + if ( !from ) + { + from = node; + } + + to = node; + pathlist.AddObject( PathNodePtr( node ) ); + + len = 0; + distanceToNextNode.AddObject( len ); + dirToNextNode.AddObject( vec_zero ); + + num = NumNodes(); + if ( num > 1 ) + { + dir = node->worldorigin - GetNode( num - 1 )->worldorigin; + len = dir.length(); + dir *= 1 / len; + + distanceToNextNode.SetObjectAt( num - 1, len ); + dirToNextNode.SetObjectAt( num - 1, dir ); + + pathlength += len; + } + } + +PathNode *Path::GetNode + ( + int num + ) + + { + PathNode *node; + + node = pathlist.ObjectAt( num ); + assert( node != NULL ); + if ( node == NULL ) + { + error( "GetNode", "Null pointer in node list\n" ); + } + + return node; + } + +PathNode *Path::NextNode + ( + void + ) + + { + if ( nextnode <= NumNodes() ) + { + return pathlist.ObjectAt( nextnode++ ); + } + return NULL; + } + +PathNode *Path::NextNode + ( + PathNode *node + ) + + { + int i; + int num; + PathNode *n; + + num = NumNodes(); + + // NOTE: We specifically DON'T check the last object (hence the i < num instead + // of the usual i <= num, so don't go doing something stupid like trying to fix + // this without keeping this in mind!! :) + for( i = 1; i < num; i++ ) + { + n = pathlist.ObjectAt( i ); + if ( n == node ) + { + // Since we only check up to num - 1, it's ok to do this. + // We do this since the last node in the list has no next node (duh!). + return pathlist.ObjectAt( i + 1 ); + } + } + + return NULL; + } + +Vector Path::ClosestPointOnPath + ( + Vector pos + ) + + { + PathNode *s; + PathNode *e; + int num; + int i; + float bestdist; + Vector bestpoint; + float dist; + float segmentlength; + Vector delta; + Vector p1; + Vector p2; + Vector p3; + float t; + + num = NumNodes(); + s = GetNode( 1 ); + + bestpoint = s->worldorigin; + delta = bestpoint - pos; + bestdist = delta * delta; + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + // check if we're closest to the endpoint + delta = e->worldorigin - pos; + dist = delta * delta; + + if ( dist < bestdist ) + { + bestdist = dist; + bestpoint = e->worldorigin; + } + + // check if we're closest to the segment + segmentlength = distanceToNextNode.ObjectAt( i - 1 ); + p1 = dirToNextNode.ObjectAt( i - 1 ); + p2 = pos - s->worldorigin; + + t = p1 * p2; + if ( ( t > 0 ) && ( t < segmentlength ) ) + { + p3 = ( p1 * t ) + s->worldorigin; + + delta = p3 - pos; + dist = delta * delta; + if ( dist < bestdist ) + { + bestdist = dist; + bestpoint = p3; + } + } + + s = e; + } + + return bestpoint; + } + +float Path::DistanceAlongPath + ( + Vector pos + ) + + { + PathNode *s; + PathNode *e; + int num; + int i; + float bestdist; + float dist; + float segmentlength; + Vector delta; + Vector p1; + Vector p2; + Vector p3; + float t; + float pathdist; + float bestdistalongpath; + + pathdist = 0; + + num = NumNodes(); + s = GetNode( 1 ); + delta = s->worldorigin - pos; + bestdist = delta * delta; + bestdistalongpath = 0; + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + segmentlength = distanceToNextNode.ObjectAt( i - 1 ); + + // check if we're closest to the endpoint + delta = e->worldorigin - pos; + dist = delta * delta; + + if ( dist < bestdist ) + { + bestdist = dist; + bestdistalongpath = pathdist + segmentlength; + } + + // check if we're closest to the segment + p1 = dirToNextNode.ObjectAt( i - 1 ); + p2 = pos - s->worldorigin; + + t = p1 * p2; + if ( ( t > 0 ) && ( t < segmentlength ) ) + { + p3 = ( p1 * t ) + s->worldorigin; + + delta = p3 - pos; + dist = delta * delta; + if ( dist < bestdist ) + { + bestdist = dist; + bestdistalongpath = pathdist + t; + } + } + + s = e; + + pathdist += segmentlength; + } + + return bestdistalongpath; + } + +Vector Path::PointAtDistance + ( + float dist + ) + + { + PathNode *s; + PathNode *e; + int num; + int i; + float t; + float pathdist; + float segmentlength; + + num = NumNodes(); + s = GetNode( 1 ); + pathdist = 0; + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + segmentlength = distanceToNextNode.ObjectAt( i - 1 ); + if ( ( pathdist + segmentlength ) > dist ) + { + t = dist - pathdist; + return s->worldorigin + dirToNextNode.ObjectAt( i - 1 ) * t; + } + + s = e; + pathdist += segmentlength; + } + + // cap it off at start or end of path + return s->worldorigin; + } + +PathNode *Path::NextNode + ( + float dist + ) + + { + PathNode *s; + PathNode *e; + int num; + int i; + float pathdist; + float segmentlength; + + num = NumNodes(); + s = GetNode( 1 ); + pathdist = 0; + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + segmentlength = distanceToNextNode.ObjectAt( i - 1 ); + if ( ( pathdist + segmentlength ) > dist ) + { + return e; + } + + s = e; + pathdist += segmentlength; + } + + // cap it off at start or end of path + return s; + } + +void Path::DrawPath + ( + float r, + float g, + float b, + float time + ) + + { + Vector s; + Vector e; + Vector offset; + PathNode *node; + int num; + int i; + + num = NumNodes(); + + if ( ai_debugpath->value ) + { + gi.dprintf( "numnodes %d, len %d, nodes %d :", PathManager.NumNodes(), ( int )Length(), num ); + for( i = 1; i <= num; i++ ) + { + node = GetNode( i ); + gi.dprintf( " %d", node->nodenum ); + } + + gi.dprintf( "\n" ); + } + + node = GetNode( 1 ); + s = node->worldorigin; + + offset = Vector( r, g, b ) * 4 + Vector( 0, 0, 20 ); + for( i = 2; i <= num; i++ ) + { + node = GetNode( i ); + e = node->worldorigin; + + G_DebugLine( s + offset, e + offset, r, g, b, 1 ); + s = e; + } + } + +int Path::NumNodes + ( + void + ) + + { + return pathlist.NumObjects(); + } + +float Path::Length + ( + void + ) + + { + return pathlength; + } diff --git a/path.h b/path.h new file mode 100644 index 0000000..699bc92 --- /dev/null +++ b/path.h @@ -0,0 +1,197 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/path.h $ +// $Revision:: 22 $ +// $Author:: Jimdose $ +// $Date:: 10/25/98 11:53p $ +// +// 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/path.h $ +// +// 22 10/25/98 11:53p Jimdose +// added EXPORT_TEMPLATE +// +// 21 10/16/98 8:24p Jimdose +// Added distanceToNextNode and dirToNextNode; +// Optimized ClosestPointOnPath, DistanceAlongPath, PointAtDistance +// +// 20 10/16/98 1:54a Jimdose +// Added another NextNode function for finding the next node after the +// specified node +// +// 19 9/22/98 12:49p Markd +// Put in archive and unarchive functions +// +// 18 8/18/98 10:02p Jimdose +// Added NextNode +// +// 17 5/26/98 7:56p Jimdose +// added scripted cameras +// +// 16 5/20/98 6:39p Jimdose +// Made ClosestPointOnPath accept an Entity to do a trace for +// +// 15 5/16/98 3:38p Jimdose +// Added ClosestPointOnPath, DistanceAlongPath, and PointAtDistance +// +// +// 14 5/13/98 4:50p Jimdose +// Added use of SafePtrs +// +// 13 4/16/98 2:10p Jimdose +// Now uses PathNode as path point +// +// 12 3/05/98 3:49p Jimdose +// Made the pathinfo_t == operator EXPORT_FROM_DLL +// +// 11 3/04/98 1:42p Jimdose +// Added pathinfo_t comparison function +// +// 10 3/02/98 8:49p Jimdose +// Changed CLASS_PROTOTYPE to only take the classname +// +// 9 3/02/98 5:43p Jimdose +// Continued development on paths. Now uses Path class to represent a path. +// +// DESCRIPTION: +// + +#ifndef __PATH_H__ +#define __PATH_H__ + +#include "g_local.h" +#include "class.h" +#include "container.h" +#include "navigate.h" + +// +// Exported templated classes must be explicitly instantiated +// +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL Container; +template class EXPORT_FROM_DLL Container; +template class EXPORT_FROM_DLL Container; +#endif + +class EXPORT_FROM_DLL Path : public Class + { + private: + Container pathlist; + Container distanceToNextNode; + Container dirToNextNode; + float pathlength; + PathNodePtr from; + PathNodePtr to; + int nextnode; + + public: + CLASS_PROTOTYPE( Path ); + + Path(); + Path( int numnodes ); + void Clear( void ); + void Reset( void ); + void AddNode( PathNode *node ); + PathNode *GetNode( int num ); + PathNode *NextNode( void ); + PathNode *NextNode( PathNode *node ); + Vector ClosestPointOnPath( Vector pos ); + float DistanceAlongPath( Vector pos ); + Vector PointAtDistance( float dist ); + PathNode *NextNode( float dist ); + void DrawPath( float r, float g, float b, float time ); + int NumNodes( void ); + float Length( void ); + PathNode *Start( void ); + PathNode *End( void ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Path::Archive + ( + Archiver &arc + ) + { + PathNodePtr ptr; + int i, num; + + Class::Archive( arc ); + + num = pathlist.NumObjects(); + arc.WriteInteger( num ); + for ( i = 1; i <= num; i++ ) + { + ptr = pathlist.ObjectAt( i ); + arc.WriteSafePointer( ptr ); + } + + arc.WriteFloat( pathlength ); + arc.WriteSafePointer( from ); + arc.WriteSafePointer( to ); + arc.WriteInteger( nextnode ); + } + +inline EXPORT_FROM_DLL void Path::Unarchive + ( + Archiver &arc + ) + { + PathNodePtr *ptr; + PathNodePtr node; + float len; + Vector dir; + int i, num; + + Class::Unarchive( arc ); + + pathlist.FreeObjectList(); + distanceToNextNode.FreeObjectList(); + dirToNextNode.FreeObjectList(); + + arc.ReadInteger( &num ); + for( i = 1; i <= num; i++ ) + { + pathlist.AddObject( node ); + ptr = pathlist.AddressOfObjectAt( i ); + arc.ReadSafePointer( ptr ); + } + + // Recalculate the path distances and directions + // only go up to the node before the last node. + for( i = 1; i < num; i++ ) + { + dir = pathlist.ObjectAt( i + 1 )->worldorigin - pathlist.ObjectAt( i )->worldorigin; + len = dir.length(); + dir *= 1 / len; + + distanceToNextNode.SetObjectAt( i, len ); + dirToNextNode.SetObjectAt( i, dir ); + } + + if ( num ) + { + // special case for last node + len = 0; + distanceToNextNode.AddObject( len ); + dirToNextNode.AddObject( vec_zero ); + } + + arc.ReadFloat( &pathlength ); + arc.ReadSafePointer( &from ); + arc.ReadSafePointer( &to ); + arc.ReadInteger( &nextnode ); + } + + +#ifdef EXPORT_TEMPLATE +template class EXPORT_FROM_DLL SafePtr; +#endif +typedef SafePtr PathPtr; + +#endif /* path.h */ diff --git a/peon.cpp b/peon.cpp new file mode 100644 index 0000000..868a396 --- /dev/null +++ b/peon.cpp @@ -0,0 +1,299 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/peon.cpp $ +// $Revision:: 9 $ +// $Author:: Aldie $ +// $Date:: 11/18/98 7:56p $ +// +// Copyright (C) 1998 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/peon.cpp $ +// +// 9 11/18/98 7:56p Aldie +// dontsave goo +// +// 8 11/18/98 1:40a Jimdose +// goo goes away quicker +// +// 7 10/27/98 5:03p Markd +// Upped damage on goo +// +// 6 10/27/98 3:53a Markd +// Don't always put dynamic lights on goo +// +// 5 10/26/98 2:50p Aldie +// Fixed a bug with checking of NULL owners +// +// 4 10/26/98 3:50a Markd +// put in prediction +// +// 3 10/25/98 4:43a Markd +// incremental +// +// 2 10/23/98 10:03p Markd +// First check in +// +// 1 10/23/98 8:20p Markd +// +// DESCRIPTION: +// Peon +// + +#include "g_local.h" +#include "actor.h" +#include "specialfx.h" +#include "surface.h" +#include "peon.h" + + +class EXPORT_FROM_DLL Goo : public Projectile + { + public: + CLASS_PROTOTYPE( Goo ); + + Goo(); + virtual void Setup( Entity *owner, Vector pos, Vector vel ); + virtual void GooTouch( Event *ev ); + }; + +CLASS_DECLARATION( Projectile, Goo, NULL ); + +ResponseDef Goo::Responses[] = + { + { &EV_Touch, ( Response )Goo::GooTouch }, + { NULL, NULL } + }; + +Goo::Goo() + { + } + +void Goo::GooTouch + ( + Event *ev + ) + + { + Entity *other; + Entity *owner; + int damg; + Vector v; + Vector norm; + Vector shockangles; + + other = ev->GetEntity( 1 ); + assert( other ); + + if ( other->isSubclassOf( Teleporter ) ) + { + return; + } + + if ( other->entnum == this->owner ) + { + return; + } + + owner = G_GetEntity( this->owner ); + + if ( !owner ) + owner = world; + + setSolidType( SOLID_NOT ); + + // Hit the shy, so remove everything + if ( HitSky() ) + { + PostEvent( EV_Remove, 0 ); + return; + } + + damg = 40 + ( int )G_Random( 40 ); + + // Single player packs a bigger punch + if ( !deathmatch->value && owner->isClient() ) + damg *= 1.5; + + if ( other->takedamage ) + other->Damage( this, owner, damg, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_PULSE, -1, -1, 1.0f ); + + if ( other == world ) + { + RandomAnimate( "splat", NULL ); + PostEvent( EV_Remove, 5 ); + } + else + { + PostEvent( EV_Remove, 0 ); + } + } + +void Goo::Setup + ( + Entity *owner, + Vector pos, + Vector vel + ) + + { + this->owner = owner->entnum; + edict->owner = owner->edict; + + // Align the projectile + angles = vel.toAngles(); + angles[ PITCH ] = -angles[ PITCH ]; + setAngles( angles ); +// edict->s.angles[ROLL] = rand() % 360; + + // Flies like a grenade + setMoveType( MOVETYPE_TOSS ); + setSolidType( SOLID_BBOX ); + edict->clipmask = MASK_PROJECTILE; + setModel( "goo.def" ); + RandomAnimate( "goo", NULL ); + + // Set the flying velocity + velocity = vel; + + takedamage = DAMAGE_NO; + + if ( G_Random( 10 ) < 2 ) + { + // Set the light and effects + edict->s.renderfx |= RF_DLIGHT; + edict->s.radius = 100; + edict->s.color_r = 0.1f; + edict->s.color_g = 0.9f; + edict->s.color_b = 0.1f; + } + + flags |= FL_DONTSAVE; + + // Set size and origin + setSize( "-1 -1 -1", "1 1 1" ); + setOrigin( pos ); + worldorigin.copyTo(edict->s.old_origin); + + // Remove the projectile in the future + PostEvent( EV_Remove, 30 ); + } + +CLASS_DECLARATION( Actor, Peon, "monster_peon" ); + +Event EV_Peon_SpawnGoo( "spawngoo" ); + +ResponseDef Peon::Responses[] = + { + { &EV_Peon_SpawnGoo, ( Response )Peon::SpawnGoo }, + { NULL, NULL } + }; + +Peon::Peon() + { + gootime = 0; + setModel( "peon.def" ); + modelIndex( "goo.def" ); + } + +void Peon::SpawnGoo + ( + Event *ev + ) + + { + Goo * goo; + Vector vel; + Vector pos; + Vector target; + float speed; + char bonename[ 32 ]; + int num; + + if ( !currentEnemy ) + return; + + num = (int)G_Random( 6 ) + 1; + sprintf( bonename, "goo%d", num ); + + GetBone( bonename, &pos, NULL, NULL, NULL ); + + pos += worldorigin; + + speed = 900; + + target = G_PredictPosition( pos, currentEnemy->centroid, currentEnemy->velocity, speed ); + + vel = G_CalculateImpulse + ( + pos, + target, + speed, + 1 + ); + goo = new Goo; + goo->Setup( this, pos, vel ); + } + +void Peon::Prethink + ( + void + ) + { + if ( currentEnemy && ( gootime < level.time ) ) + { + Vector delta; + Vector dir; + ScriptVariable * stage; + int stagenum; + + stage = levelVars.GetVariable( "eonpeon_stage" ); + if ( stage ) + { + stagenum = stage->intValue(); + } + else + { + stagenum = 0; + } + if ( stagenum < 2 ) + { + gootime = level.time + 1; + } + else + { + dir = Vector( orientation[ 0 ] ); + delta = currentEnemy->centroid - centroid; + if ( delta.length() < 1000 ) + { +#if 0 + float dot; + + dot = delta * dir; + + if ( dot < -0.5f ) + { + gootime = level.time + 5; + DoAction( "rearattack", false ); + } +#else + if ( stagenum == 2 ) + gootime = level.time + 5; + else + gootime = level.time; + + DoAction( "rearattack", false ); +#endif + } + } + } + + // + // call our superclass + // + Actor::Prethink(); + } + diff --git a/peon.h b/peon.h new file mode 100644 index 0000000..7f45d4d --- /dev/null +++ b/peon.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/peon.h $ +// $Revision:: 3 $ +// $Author:: Jimdose $ +// $Date:: 11/08/98 10:52p $ +// +// Copyright (C) 1998 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/peon.h $ +// +// 3 11/08/98 10:52p Jimdose +// added archive functions +// +// 2 10/23/98 10:03p Markd +// First check in +// +// 1 10/23/98 8:20p Markd +// +// 2 10/23/98 3:41p Markd +// incremental check in +// +// 1 10/23/98 5:06a Markd +// +// DESCRIPTION: +// Eon and Peon +// + +#ifndef __PEON_H__ +#define __PEON_H__ + +#include "g_local.h" +#include "actor.h" + +class EXPORT_FROM_DLL Peon : public Actor + { + private: + float gootime; + + public: + CLASS_PROTOTYPE( Peon ); + + Peon::Peon(); + virtual void Prethink( void ); + virtual void SpawnGoo( Event *ev ); + virtual void Archive( Archiver &arc ); + virtual void Unarchive( Archiver &arc ); + }; + +inline EXPORT_FROM_DLL void Peon::Archive + ( + Archiver &arc + ) + + { + Actor::Archive( arc ); + + arc.WriteFloat( gootime ); + } + +inline EXPORT_FROM_DLL void Peon::Unarchive + ( + Archiver &arc + ) + + { + Actor::Unarchive( arc ); + + arc.ReadFloat( &gootime ); + } + +#endif /* peon.h */ diff --git a/player.cpp b/player.cpp new file mode 100644 index 0000000..a9a78f9 --- /dev/null +++ b/player.cpp @@ -0,0 +1,7283 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Quake 2 Engine/Sin/code/game/player.cpp $ +// $Revision:: 419 $ +// $Author:: Jimdose $ +// $Date:: 12/18/98 11:03p $ +// +// 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/player.cpp $ +// +// 419 12/18/98 11:03p Jimdose +// removed include of qcommon.h +// +// 418 12/16/98 5:42p Jimdose +// fixed a bunch of respawn issues for Sin Arcade +// +// 417 12/16/98 12:41a Jimdose +// Killed no longer deletes player's weapon in training mode 2 (arcade) +// +// 416 12/14/98 8:17p Aldie +// Took out #if 0 +// +// 415 12/14/98 8:07p Aldie +// Temporarily remove arcade stuff +// +// 414 12/14/98 6:53p Aldie +// Added some stuff for arcade when death occurs +// +// 413 12/14/98 6:03p Jimdose +// made GodCheat allow a value to set it on of off +// +// 412 11/18/98 3:47a Aldie +// Fix for mutants and inventory items showing up in inventory +// +// 411 11/18/98 3:02a Jimdose +// made mutant mode stay across levels +// +// 410 11/17/98 6:54p Aldie +// Don't do spawn particles for training +// +// 409 11/17/98 3:48a Jimdose +// gravpaths no longer pull you during waterjumps +// +// 408 11/16/98 9:05p Markd +// don't do flash blend if hit by MOD_FISTS +// +// 407 11/16/98 8:29p Jimdose +// added CheckWater +// +// 406 11/16/98 7:45p Markd +// made take, only take a certain amount +// +// 405 11/16/98 4:37a Markd +// don't set angles if player is frozen +// +// 404 11/16/98 12:42a Aldie +// spidermine camera +// +// 403 11/15/98 8:43p Markd +// Fixed third person crosshair issues and gravity axis and zooming +// +// 402 11/15/98 3:55a Markd +// fixed weapon visible in vehicles +// +// 401 11/14/98 7:57p Markd +// always reload when pressing use +// +// 400 11/14/98 2:52a Markd +// put in RF_DONTDRAW support on weapons +// +// 399 11/14/98 2:54a Aldie +// Added InitSkin to preserve custom skins in single player +// +// 398 11/13/98 11:02p Jimdose +// made fov persistant across levels and after zooming in and out +// +// 397 11/13/98 10:42p Aldie +// Free inventory of type when a take item is issued by the script +// +// 396 11/13/98 7:13p Jimdose +// health is now set to 1 if it's less than 1 when changing or entering levels +// +// 395 11/13/98 2:37a Aldie +// Fixed pain event to play sound and flash within bounds +// +// 394 11/11/98 11:12p Aldie +// Added custom gibbing +// +// 393 11/10/98 3:57p Jimdose +// fixed bug where player enters vehicle crouched and the view hieght is too +// low +// +// 392 11/09/98 3:31a Markd +// PM_trace should check for godmode +// +// 391 11/09/98 1:43a Markd +// put in falling protection for landing on ladder +// +// 390 11/09/98 12:55a Jimdose +// added sv_footsteps cvar so that server admins can turn footsteps off +// completely +// +// 389 11/08/98 10:48p Jimdose +// moved earthquake to level struct +// +// 388 11/08/98 8:29p Aldie +// Fix for some console bugs when moving while using them +// +// 387 11/07/98 10:46p Markd +// Fixed music stuff +// +// 386 11/07/98 10:15p Markd +// put in forcemusic support +// +// 385 11/07/98 10:00p Jimdose +// Made spawn and actor commands work with model names +// +// 384 11/07/98 8:01p Markd +// Added in damage_since_pain, fixed some camera stuff +// +// 383 11/07/98 5:29p Aldie +// Drop inventory in coop +// +// 382 11/07/98 5:27p Aldie +// Fixed coop death bug +// +// 381 11/07/98 4:39p Markd +// fixed music stuff +// +// 380 11/07/98 3:34p Markd +// reset music when coming back from action +// +// 379 11/06/98 10:09p Aldie +// Moved has_thought to constructor +// +// 378 11/06/98 9:38p Aldie +// Moved waitforplayer stuff to the think function +// +// 377 11/06/98 5:17p Jimdose +// added missionfailed and missionfailedtime to level vars +// when a mission has failed or the player is dead in single player, the game +// code immediately shows the loadmenu, preventing them from letting the game +// continue running if they exit the menu +// +// 376 11/05/98 2:06a Markd +// changed the way music works a bit, so that action would override anything +// else +// +// 375 10/27/98 9:46p Aldie +// Changed training cvar to level.training +// +// 374 10/27/98 5:10p Aldie +// Fixed take "all" for fists and spidermine +// +// 373 10/26/98 10:32p Aldie +// If onladder, don't get affected by gravitypaths +// +// 372 10/26/98 9:53p Markd +// Moved GotKill around, added gibfest sound +// +// 371 10/26/98 8:12p Aldie +// Fixed some weapon bugs with detaching from owner when removing +// +// 370 10/26/98 5:37p Markd +// expanded whatis functionality +// +// 369 10/26/98 4:30p Aldie +// Changed airtime default to 20 +// +// 368 10/26/98 2:17p Aldie +// Added AirClamp +// +// 367 10/26/98 2:18a Aldie +// Added FL_OXYGEN +// +// 366 10/26/98 12:28a Markd +// put in no jc support +// +// 365 10/25/98 11:55p Jimdose +// moved playerfrozen from game to level +// +// 364 10/25/98 10:15p Aldie +// Added training cvar +// +// 363 10/25/98 1:51a Aldie +// Bulletproof the floating inventory code +// +// 362 10/24/98 6:39p Markd +// Added in more dialog based on Blade killing things +// +// 361 10/24/98 6:21p Aldie +// Fixed spidermine error... +// +// 360 10/24/98 5:44p Markd +// Added killent, removent, killclass removeclass and added parameters to +// whatis +// +// 359 10/24/98 3:17p Aldie +// Take out gibs for parent mode +// +// 358 10/24/98 3:14p Aldie +// Addec check for silencer, removed blood checks +// +// 357 10/24/98 2:17p Markd +// Put in JC saying blade was dead +// +// 356 10/24/98 4:05a Markd +// fixed zoomed in in a vehicle +// +// 355 10/24/98 12:42a Markd +// changed origins to worldorigins where appropriate +// +// 354 10/22/98 9:28p Aldie +// Added support for deathgib animations +// +// 353 10/22/98 5:19p Aldie +// Fixed crash of using Powerups +// +// 352 10/22/98 5:04p Markd +// Blade will no longer taunt deaths of civilians or inanimate objects +// +// 351 10/22/98 4:21p Aldie +// Fixed spidermine and fov changing +// +// 350 10/22/98 2:39a Jimdose +// player no longer does crouching bob when in water +// +// 349 10/21/98 10:58p Jimdose +// Made TouchStuff not touch the world +// +// 348 10/21/98 5:28p Aldie +// Added a setskin command +// +// 347 10/21/98 1:45a Aldie +// Fixed silencer clearing out, updated gib test function +// +// 346 10/20/98 10:29p Jimdose +// Restore crosshair when respawning in 3rd person +// +// 345 10/20/98 7:42p Markd +// fixed weapons in vehicles +// +// 344 10/20/98 5:22p Aldie +// Added die_gibs flag +// +// 343 10/19/98 9:53p Jimdose +// Added blend for lightvolume +// changed slime variables to lightvolume +// +// 342 10/19/98 5:29p Aldie +// Increased air time to 20 +// +// 341 10/19/98 12:07a Jimdose +// made all code use fast checks for inheritance (no text lookups when +// possible) +// isSubclassOf no longer requires ::_classinfo() +// +// 340 10/18/98 10:55p Aldie +// Added check for useless body removal on bodies that you don't need anything +// from +// +// 339 10/18/98 9:01p Markd +// Added ShowWeapon support for vehicles and zooming in on vehicles +// +// 338 10/18/98 1:31a Aldie +// Added some more stuff to whatis command +// +// 337 10/17/98 12:23a Jimdose +// don't load persistant data during deathmatch +// +// 336 10/16/98 2:27a Jimdose +// Added Endlevel +// saved godmode and notarget on level change +// made cameras work cross level and in savegames +// +// 335 10/16/98 2:18a Aldie +// Took out mutant movement when in single player mutant mode +// +// 334 10/14/98 10:58p Jimdose +// player now archives fov, viewmode, and skin damage +// +// 333 10/14/98 6:29p Markd +// Put in STAT_EXITSIGN +// +// 332 10/14/98 5:34p Aldie +// Mutant stuff +// +// 331 10/14/98 1:19a Jimdose +// Got cross-level persistant info working +// +// 330 10/12/98 8:54p Aldie +// Process player killed events +// +// 329 10/12/98 8:44p Jimdose +// Rewrote init function +// started adding persistant functions +// +// 328 10/12/98 12:59p Markd +// Fixed vehicle entering which messed up intro +// +// 327 10/11/98 9:03p Aldie +// Updated Killed to clear mutant status +// +// 326 10/11/98 7:40p Aldie +// Mutate and restore commands for Richard +// +// 325 10/11/98 5:46p Aldie +// Obituaries +// +// 324 10/11/98 5:33p Aldie +// Fix a couple init bugs +// +// 323 10/11/98 12:02a Aldie +// Adrenaline effects +// +// 322 10/10/98 9:58p Aldie +// Fixed mutant mode bug and changed alpha for PODD. +// +// 321 10/10/98 9:14p Aldie +// Death message and fix camera in spidermines +// +// 320 10/10/98 5:58p Aldie +// More quantumdestab fixes +// +// 319 10/09/98 8:59p Aldie +// Moved oxygen to player +// +// 318 10/09/98 4:54p Markd +// Replaced misc script code with ExecuteThread +// +// 317 10/09/98 2:06a Aldie +// Updated DMFLAGS +// +// 316 10/08/98 7:24p Markd +// changed vehicle movetype from noclip to vehicle +// +// 315 10/07/98 11:57p Jimdose +// Added SetDeltaAngles +// Moved old_pmove out of client +// Moved PlayerFrozen to game +// Init isn't called until client enters game +// Got rid of MonsterGoal +// +// 314 10/03/98 1:09p Aldie +// Fix a bug with flashcolor +// +// 313 10/02/98 7:19p Aldie +// Added flash color to do blinding flashes +// +// 312 9/30/98 11:22p Markd +// Changed taunts from volume 2 to volume 1 +// +// 311 9/30/98 11:11p Markd +// Limited fov in deathmatch +// +// 310 9/30/98 5:39p Aldie +// Added showinfo command +// +// 309 9/30/98 3:42p Markd +// put in bulletproofing for start thread functions +// +// 308 9/30/98 1:25p Aldie +// Gib on ion destruct +// +// 307 9/29/98 5:34p Aldie +// Moved gravity nodes to it's own function to fix goddam compiler bug +// +// 306 9/29/98 2:59p Markd +// tried reformatting gravpath stuff +// +// 305 9/29/98 2:12p Aldie +// Trying to fix grav release bug +// +// 304 9/28/98 5:51p Markd +// +// 303 9/28/98 4:34p Markd +// added "player_in_vehicle" variable for scripting +// +// 302 9/28/98 4:07p Aldie +// Added oxygen powerup +// +// 301 9/27/98 6:28p Aldie +// Added water, slime, and lava colors to worldspawn +// +// 300 9/26/98 4:45p Aldie +// Added mutant mode +// +// 299 9/24/98 1:48a Markd +// Made taunts double volume +// +// 298 9/23/98 10:08p Aldie +// Gib on a ION_DESTRUCT +// +// 297 9/22/98 8:10p Aldie +// Initialize some variables causing head bobbing +// +// 296 9/22/98 5:19p Markd +// Put in new consolidated gib function +// +// 295 9/22/98 3:27p Markd +// Took out old variable +// +// 294 9/22/98 3:21p Markd +// put in parentmode lockout for blood and gibs +// +// 293 9/22/98 2:42p Aldie +// Fixed health ticking below 100 when over 100 in prethink +// +// 292 9/22/98 12:14p Aldie +// Added sentientFrozen and exit consoles when you take pain. +// +// 291 9/21/98 1:34a Aldie +// Only do obits in deathmatch +// +// 290 9/20/98 5:11p Aldie +// Fixed a crash with the "give" command +// +// 289 9/19/98 4:47p Markd +// fixed music stuff and added actionincrement to weapons +// +// 288 9/18/98 10:58p Jimdose +// Added spawnactor and actorinfo +// +// 287 9/17/98 8:02p Jimdose +// Removed blending for underwater +// +// 286 9/17/98 1:48p Markd +// Fixed swimmin animations +// +// 285 9/16/98 8:58p Aldie +// Added ability to do a hold down weapon charge +// +// 284 9/16/98 3:13p Aldie +// Initialize poweruptype +// +// 283 9/14/98 4:15p Markd +// zero out velocity when entering vehicles +// +// 282 9/12/98 12:13a Jimdose +// NearestNode was being called even when ai_createnodes was not set +// +// 281 9/11/98 5:03p Aldie +// Initialize draw overlay +// +// 280 9/11/98 2:49p Aldie +// Fixed death message causing crash and added release firing. +// +// 279 9/10/98 8:53p Markd +// put in proper falling and landing animation thresholds. +// +// 278 9/10/98 12:45p Markd +// Added whereami support +// +// 277 9/09/98 5:06p Markd +// Added savefov and restorefov, also added fov change when teleporting +// +// 276 9/09/98 3:03p Markd +// fixed deathmatch camera +// +// 275 9/07/98 6:21p Markd +// cleared out velocity when in vehicle. Also don't use death cam when in +// single player +// +// 274 9/07/98 5:28p Markd +// Fixed player kickangles while jumping, fixed prediciton while in the camera +// +// 273 9/05/98 6:20p Markd +// Fixed player bouncing with kick angles +// +// 272 9/05/98 6:03p Markd +// fixed next weapon and previous weapon in helicopter +// +// 271 9/05/98 12:11p Aldie +// Fixed up falling damage flags and init'ed poweruptimer/poweruptype +// +// 270 9/03/98 4:55p Jimdose +// Tweaked kick code +// +// 269 9/02/98 7:47p Aldie +// Added ValidPlayerModels list +// +// 268 9/02/98 12:54p Markd +// put in proper teleportation stuff +// +// 267 9/02/98 11:57a Markd +// Put in walk_fire +// +// 266 9/02/98 11:40a Markd +// really freeze the player when frozen +// +// 265 9/01/98 7:46p Aldie +// Added itemname to inventory stuff +// +// 264 9/01/98 12:10p Markd +// fixed run_firing while crouching +// +// 263 8/31/98 7:45p Aldie +// Updated surface data structure and removed surfinfo field +// +// 262 8/31/98 7:16p Markd +// Fixed player animation naming convention +// +// 261 8/31/98 5:42p Aldie +// Changed powerups timer, added cloak +// +// 260 8/30/98 7:29p Markd +// Put in auto-dead camera where after 10 seconds, player will follow the +// endnode1 path +// +// 259 8/29/98 9:45p Jimdose +// Added call info to G_Trace +// +// 258 8/28/98 8:06p Markd +// made crosshair change when targetting +// +// 257 8/28/98 7:54p Markd +// Put in taunttime govenor on taunting +// +// 256 8/28/98 4:13p Markd +// Made taunt into a VoiceSound so that AI would interact +// +// 255 8/27/98 9:03p Jimdose +// Made grav a reference +// +// 254 8/27/98 7:17p Aldie +// Don't draw the weapon when in a console. +// +// 253 8/27/98 6:23p Jimdose +// Fixed bug with player walking in place +// +// 252 8/27/98 4:50p Markd +// Fixed falling damage and detach things when dead +// +// 251 8/27/98 2:30p Aldie +// Added adrenaline and more gravity stuff +// +// 250 8/26/98 8:47p Aldie +// Removed a printf +// +// 249 8/26/98 8:46p Aldie +// Put in velocity limiter for the gravity path nodes +// +// 248 8/26/98 6:19p Aldie +// Fix for spidermine cameras +// +// 247 8/25/98 8:02p Jimdose +// Now get the gravaxis from the starting location +// +// 246 8/25/98 7:54p Markd +// Added third-person crosshair back into the game +// +// 245 8/25/98 6:18p Aldie +// New spidermine and detonator support +// +// 244 8/25/98 5:50p Markd +// Added THIRDPERSON support for client side crosshair +// +// 243 8/25/98 4:11p Markd +// Added taunt support +// +// 242 8/24/98 6:53p Jimdose +// Fixed falling damage on alternate gravity axis +// Fixed bug with inverted gravity axis +// +// 241 8/24/98 5:35p Aldie +// Added projectile camera functionality +// +// 240 8/24/98 3:49p Jimdose +// Fixed bug with inverted gravity axis +// Made fabric cause no falling damage +// +// 239 8/24/98 11:32a Markd +// Added Start method to threads, repladed all ProcessEvent( +// EV_ScriptThread_execute) with thread->Start( -1 ) +// +// 238 8/22/98 9:57p Markd +// Only clear fade if not a cinematic +// +// 237 8/22/98 9:36p Jimdose +// Added support for alternate gravity axis +// +// 236 8/22/98 1:22a Markd +// Added Director.PlayerSpawned call, also made skipthread make sure there is +// at least one character +// +// 235 8/21/98 7:38p Markd +// Fixed climbing code +// +// 234 8/21/98 6:36p Markd +// made it so climbing works when climbing down as well +// +// 233 8/21/98 3:47p Markd +// Only exit camera if strafing or movement are above a threshold +// +// 232 8/21/98 1:05a Jimdose +// Got the movement and the model angles working for the alt-gravity stuff +// +// 231 8/19/98 7:45p Jimdose +// Fixed viewhieght during noclip +// +// 230 8/19/98 4:48p Aldie +// Fixed a bug in falling damage +// +// 229 8/18/98 11:08p Markd +// Added new Alias System +// +// 228 8/18/98 10:02p Jimdose +// Changed ClearPathTo to CheckMove +// +// 227 8/17/98 8:59p Jimdose +// Added WhatIs +// +// 226 8/17/98 7:45p Markd +// Added lava and slime +// +// 225 8/17/98 6:19p Markd +// Changed SetCamera to a Player method +// Got out of cinematic if in Pain +// +// 224 8/17/98 4:34p Markd +// Put in camera exiting and no-falling-damage +// +// 223 8/17/98 3:28p Jimdose +// Started adding code for varying gravity vectors +// +// 222 8/17/98 2:56p Aldie +// Added weapon use command +// +// 221 8/15/98 3:00p Markd +// Fixed camera/console issues with weapons +// +// 220 8/14/98 8:14p Aldie +// Added generic overlay system +// +// 219 8/13/98 8:11p Markd +// Made sure invulnerability gets reset in Init function +// +// 218 8/13/98 8:11p Aldie +// Don't do pain animation if damage is below threshold +// +// 217 8/13/98 1:53p Aldie +// Fixed initialization of pm +// +// 216 8/12/98 6:03p Aldie +// Added a shield timer +// +// 215 8/07/98 6:46p Aldie +// If you're dead, don't do prediction +// +// 214 8/07/98 6:00p Aldie +// Credit frags to the correct player when falling damage kills someone. +// +// 213 8/06/98 10:29p Markd +// Scaled down debris damage +// +// 212 8/06/98 7:05p Markd +// Added crouch flag to effects +// +// 211 8/03/98 7:36p Markd +// Put in debris damage +// +// 210 8/03/98 1:15p Aldie +// Added QuantumD to give all +// +// 209 8/02/98 9:00p Markd +// Merged code 3.17 +// +// 208 7/31/98 8:09p Jimdose +// Script commands now include flags to indicate cheats and console commands +// +// 207 7/31/98 4:20p Jimdose +// Added GiveHealthCheat +// +// 206 7/30/98 9:28p Jimdose +// Allows the Give cheat if Cheats are enabled +// +// 205 7/29/98 2:32p Aldie +// Changed health to a float +// +// 204 7/26/98 2:14p Aldie +// Force respawns in single player +// +// 203 7/26/98 1:56p Aldie +// Respawn automatically +// +// 202 7/26/98 12:34p Jimdose +// Changed cheat codes +// +// 201 7/26/98 9:34a Markd +// Took out ToggleViewMode while in a vehicle +// +// 200 7/26/98 4:15a Aldie +// Exit console = show stats +// +// 199 7/26/98 1:18a Aldie +// Camera based consoles +// +// 198 7/26/98 12:50a Markd +// only do a reload on use, if not using something else +// +// 197 7/26/98 12:45a Aldie +// Try to fix player death stuck anims +// +// 196 7/25/98 7:13p Markd +// forgot to if_demo the spider mines +// +// 195 7/25/98 7:04p Markd +// Eliminated unused weapons for demo +// +// 194 7/25/98 5:42p Markd +// Fixed player runfire bug +// +// 193 7/25/98 5:23p Markd +// Added GotKill to player +// +// 192 7/25/98 4:36p Aldie +// Fade stuff +// +// 191 7/24/98 10:04p Aldie +// Gibs and fade tweaks +// +// 190 7/24/98 7:33p Aldie +// Changed method for hiding hud +// +// 189 7/24/98 6:22p Jimdose +// Falling damage is now based on surface type (grass, concrete, wood, etc.) +// +// 188 7/24/98 3:48p Aldie +// Fixed splashing sounds and hardcoded models, etc... +// +// 187 7/23/98 9:56p Aldie +// Fixed obits and added hud command. +// +// 186 7/23/98 7:12p Aldie +// Fun with gibs +// +// 185 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 +// +// 184 7/23/98 1:50p Aldie +// give armor in give all +// +// 183 7/23/98 12:29p Markd +// Put in climbing support +// +// 182 7/22/98 10:21p Markd +// Added support for multi-handed weapons and firing +// +// 181 7/22/98 7:03p Aldie +// More MOD messages and remove actors when we clean them out. +// +// 180 7/22/98 5:28p Markd +// FIxed third person camera stuff +// +// 179 7/21/98 9:04p Markd +// Added UpdateMusic and stuff +// +// 178 7/21/98 1:10p Aldie +// Added meansofdeath to obituaries +// +// 177 7/20/98 6:54p Aldie +// Moved cancellation of fades to init function +// +// 176 7/20/98 5:40p Aldie +// Fixed selected item stats. +// +// 175 7/20/98 12:09p Markd +// Added vehicle anim support +// +// 174 7/19/98 10:18p Aldie +// You should not be able to override the death animation. +// +// 173 7/19/98 7:38p Markd +// Made sure to get rid of ENVMAPPED and DLIGHT in Init function +// +// 172 7/19/98 5:40p Markd +// Fixed shields, added takeItem support +// +// 171 7/19/98 3:42p Markd +// Fixed fade out to all players +// +// 170 7/18/98 7:36p Aldie +// Added support for fades +// +// 169 7/18/98 4:02p Markd +// Added vehicle check for snapintial +// +// 168 7/18/98 3:53p Markd +// Put in vehicle code that prevents the player from going into other +// animations while in vehicle +// +// 167 7/17/98 4:01p Markd +// Added UseWeapon method and weapons for vehicle +// +// 166 7/17/98 11:31a Aldie +// Added clipammo and some reloading stuff when the player switches weaps +// +// 165 7/15/98 11:23p Markd +// Added FL_SHIELDS support +// +// 164 7/15/98 9:58p Markd +// Clear out environment mapping when respawning +// +// 163 7/15/98 12:00a Markd +// Added location based pain animations +// +// 162 7/14/98 9:54p Markd +// Fixed camera stuff +// +// 161 7/14/98 5:37a Jimdose +// Removed definition of player height (already in q_shared.h) +// +// 160 7/14/98 5:35a Jimdose +// Removed unused variable "bob" +// now initializing v_dmg_time. Should fix bug where player's view is screwy +// when starting a new map +// +// 159 7/13/98 5:00p Aldie +// Added dead player bodies with gibbing +// +// 158 7/12/98 9:48p Markd +// Fixed weird lerping problem when pausing and unpausing the game and viewing +// from a cinematic camera or third person. Turned out that +// client->ps.pmove.origin was being overwritten by incoming client movement +// commands. So when you have an external camera, the origin is saved and +// restored around pmove movement. +// +// 157 7/12/98 6:34p Markd +// Fixed camera stuff so that player automatically warps to camera properly +// +// 156 7/11/98 8:58p Markd +// Added testthread command +// +// 155 7/11/98 8:22p Jimdose +// Players are no longer non-solid when the respawn +// +// 154 7/10/98 11:18p Jimdose +// Removed third-person crosshair +// +// 153 7/10/98 11:11p Jimdose +// Players only swim when the water is deep enough +// no longer switch weapons after dropping them when killed +// +// 152 7/09/98 11:39p Aldie +// Don't draw scores when in DM +// +// 151 7/09/98 10:39p Aldie +// Moved bodyparts to game +// +// 150 7/08/98 3:11p Aldie +// First try at spectator mode +// +// 149 7/07/98 8:06p Aldie +// Spectator and dead stuff +// +// 148 7/07/98 6:08p Aldie +// Gibs, drop weaps, water swim +// +// 147 7/03/98 12:02p Aldie +// Made player init on the model specified in client data +// +// 146 7/01/98 7:02p Aldie +// Mission computer and removed some crosshair stuff for zoom +// +// 145 7/01/98 4:38p Aldie +// Moved some stats around +// +// 144 6/28/98 3:43p Markd +// removed precaching from player +// +// 143 6/27/98 4:25p Aldie +// Made a STAT_WEAPONLIST +// +// 142 6/25/98 8:47p Markd +// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup +// method +// +// 141 6/25/98 7:31p Aldie +// Changed the armor stats +// +// 140 6/24/98 1:37p Aldie +// Implementation of inventory system and picking stuff up +// +// 139 6/22/98 2:04p Jimdose +// Added vehicles back in +// Organized the control code a bit more. May want to make physics into a +// class +// +// 138 6/20/98 6:21p Aldie +// Added modelindex for inventory stat +// +// 137 6/20/98 2:13p Aldie +// Inventory system stats +// +// 136 6/19/98 6:36p Aldie +// Put some more inventory managment back in +// +// 135 6/18/98 9:25p Aldie +// Added inventory commands +// +// 134 6/18/98 3:57p Aldie +// Changed the zooming alpha belnd color +// +// 133 6/17/98 5:41p Aldie +// Returned scale of player back to normal +// +// 132 6/17/98 3:03p Markd +// Changed NumArgs back to previous behavior +// +// 131 6/17/98 2:47p Markd +// Inverted roll on player +// +// 130 6/17/98 10:54a Aldie +// Removed the scaling of player's pitch +// +// 129 6/15/98 10:48p Jimdose +// Added AddPathNode +// +// 128 6/13/98 8:20p Jimdose +// Removed optimize path stuff +// +// 127 6/10/98 7:53p Markd +// Made NumArgs behave correctly like argc +// +// 126 6/10/98 2:10p Aldie +// Updated damage function. +// +// 125 6/08/98 7:12p Markd +// put in assertions for ammo_types and armor_types so that the game would not +// crash +// +// 124 6/08/98 7:15p Aldie +// Added SpiderMines +// +// 123 6/08/98 11:34a Aldie +// Updated "give all" command +// +// 122 6/05/98 6:25p Aldie +// Added armor stats to player +// +// 121 6/04/98 4:35p Markd +// changed action_level decay +// +// 120 6/03/98 4:38p Markd +// Added gunoffset and gunangle, changed the way weapons work with the camera, +// removed weapon parameter from SetCamerablablah +// +// 119 5/28/98 8:01p Markd +// Clamped action_level at 80 +// +// 118 5/28/98 7:17p Markd +// Tweaked music and action_level +// +// 117 5/28/98 6:01p Markd +// moved action_level stuff around +// +// 116 5/27/98 8:36p Markd +// Put in swimming, got rid of some "Give All" weapons +// +// 115 5/27/98 7:04a Markd +// put in primitive action level +// +// 114 5/27/98 6:32a Markd +// cleared out damageskin state in respawn +// +// 113 5/27/98 5:21a Markd +// switched order of giveWeapon's +// +// 112 5/26/98 7:55p Jimdose +// added scripted cameras +// +// 111 5/26/98 4:45p Aldie +// Added take (object) from player +// +// 110 5/25/98 1:39p Aldie +// Added create console user +// +// 109 5/26/98 4:37a Jimdose +// Third person camera works again +// Added tracking crosshair +// +// 108 5/26/98 1:54a Markd +// Fixed up a bunch of player sounds +// +// 107 5/25/98 6:47p Jimdose +// Made animateframe, prethink and posthink into functions built into the base +// entity class +// +// 106 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 +// +// 105 5/24/98 4:48p Jimdose +// Made char *'s const +// +// 104 5/24/98 1:04a Jimdose +// Added sound events for ai +// +// 103 5/22/98 12:22p Aldie +// Removed unused variable from player struct +// +// 102 5/22/98 12:17p Aldie +// Added gravity paths and updated earthquake effect +// +// 101 5/20/98 10:22p Aldie +// Added earthquake effects +// +// 100 5/18/98 8:14p Jimdose +// Renamed Navigator back to PathManager +// +// 99 5/16/98 5:00p Markd +// Added RF_XFLIP stuff +// +// 98 5/13/98 4:49p Jimdose +// Now use SafePtrs for keeping track of nodes +// +// 97 5/11/98 3:19p Markd +// Changed zoom-in and zoom-out sounds +// +// 96 5/11/98 2:19p Markd +// Fixed randomsound stuff +// +// 95 5/11/98 11:25a Markd +// Added new weapons +// +// 94 5/09/98 7:44p Markd +// Added gunmodel, gunframe and gunindex for other side gun animations +// +// 93 5/08/98 7:01p Markd +// Took out RF_SHELL flags +// +// 92 5/07/98 11:23p Markd +// Added playerframe, playeranim and playerindex to playerstate +// +// 91 5/07/98 10:46p Aldie +// Fixed players exiting consoles. +// +// 90 5/07/98 10:43p Jimdose +// working on cameras and archiving +// +// 89 5/05/98 2:40p Jimdose +// Added support for new camera system. +// Rewrote a lot of stuff to allow a clean separation between the camera +// control variables in client and the Player entity. This allows a much +// easier interface for creating new camera views +// +// 88 5/03/98 4:46p Jimdose +// Added camera view +// changed Vector class. Aliased vectors via pointsTo no longer supported. +// +// 87 5/02/98 4:24p Jimdose +// Added FL_BLOOD to player +// +// 86 5/01/98 6:15p Jimdose +// Made Respawn an event so that Trigger_Hurt wouldn't rekill a player after +// respawn +// +// 85 5/01/98 5:55p Aldie +// Removed debugging printf's +// +// 84 4/29/98 5:38p Jimdose +// Added death messages +// Sniper rifle zooms out before changing weapon +// fov no longer a cheat and is persistant across respawns +// +// 83 4/27/98 6:09p Jimdose +// Changed alpha on debug lines +// +// 82 4/27/98 4:12p Jimdose +// Working on path placement +// +// 81 4/21/98 2:25p Aldie +// Added enterconsole and exit console events. +// +// 80 4/20/98 5:44p Jimdose +// working on ai +// +// 79 4/20/98 2:45p Jimdose +// working on ai +// +// 78 4/18/98 6:10p Aldie +// Updated ammo stats. +// +// 77 4/18/98 4:24p Aldie +// Added ammo bar to stats. +// +// 76 4/18/98 4:06p Jimdose +// Working on ai +// +// 75 4/18/98 3:17p Markd +// Changed view weapon naming convention +// +// 74 4/18/98 3:02p Jimdose +// Added ai_createnodes and ai_showpath +// working on ai +// +// 73 4/16/98 6:22p Jimdose +// +// 72 4/16/98 5:35p Aldie +// Change fov of sniperrifle +// +// 71 4/16/98 3:22p Jimdose +// +// 70 4/16/98 2:09p Jimdose +// Added prevframe support for guns +// No longer set SOLID_NOT in destructor (world may have been deleted, so the +// gi.link in setSolidType would crash) +// +// 69 4/09/98 8:44p Jimdose +// Added view blend effects +// added drowing +// +// 68 4/09/98 7:44p Aldie +// Added blending for sniper zoom. +// +// 67 4/08/98 5:32p Jimdose +// Fixed bug in SpawnEntity +// Added key/value pairs to spawn command +// +// 66 4/08/98 4:46p Jimdose +// Made weapon appear in third person view +// Secondary Use isn't sent until the weapon is ready +// Zoom mode overrides third person view. +// +// 65 4/07/98 11:55p Jimdose +// Changed beams to specify color +// +// 64 4/07/98 8:05p Jimdose +// Made use much more sensitive. Opens too many things at once, but, oh well. +// +// 63 4/07/98 6:41p Jimdose +// Fixed bugs in weapon code +// Added checks for being dead in various functions (changing weapons) +// working on making use more responsive +// +// 62 4/07/98 3:48p Aldie +// Added zooming crosshair +// +// 61 4/06/98 8:00p Jimdose +// Init resets your gravity to 1.0 +// +// 60 4/06/98 7:10p Aldie +// Added zooming for SniperRifle +// +// 59 4/05/98 2:57a Jimdose +// added respawn_time +// made firing anim work temporarily +// +// 58 4/04/98 6:07p Jimdose +// Added events for syncing firing to animation +// +// 57 4/02/98 4:56p Jimdose +// Added pain, obituary and showscores +// Fixed bug in Init where persistant data was always being cleared +// ammo stats are now kept up to date +// Added falling and landing animations +// +// 56 3/31/98 1:40p Jimdose +// Removed footstep stuff (it's in entity now) +// jump sound is now played by the model +// +// 55 3/31/98 12:47p Jimdose +// Fixed the gunoffset +// +// 54 3/30/98 11:39p Markd +// Added modelIndex stuff +// +// 53 3/30/98 9:55p Jimdose +// Changed location of .def files +// +// 52 3/30/98 2:41p Jimdose +// Worked on weapons +// Fixed footstep. There weren't any 'break's in the switch statement +// +// 51 3/29/98 9:42p Jimdose +// Moved animation stuff to sentient +// Changed killed to an event +// +// 50 3/29/98 5:57p Jimdose +// Added SpawnEntity command +// +// 49 3/28/98 8:55p Jimdose +// Added pulse rifle +// +// 48 3/28/98 6:51p Jimdose +// Made the angles in player starts work +// +// 47 3/28/98 4:35p Jimdose +// Added kill command +// Added deathmatch starts +// You can no longer move your view when you're dead +// +// 46 3/27/98 10:00p Jimdose +// Changed all worldangle references to use setAngles +// +// 45 3/27/98 6:36p Jimdose +// Precached weapon models +// Added assault rifle +// +// 44 3/27/98 5:40p Jimdose +// Added third person view mode +// respawn now calls FreeInventory so that weapons don't carry over when you +// die +// +// 43 3/26/98 8:25p Jimdose +// Added deathmatch +// Precache sounds +// Added animation control functions +// +// 42 3/23/98 1:31p Jimdose +// Revamped event and command system +// +// 41 3/18/98 7:22p Jimdose +// Added new-style weapons +// Fixed bug where orientation and v_angle wasn't being set +// +// 40 3/04/98 5:13p Aldie +// Added support for damage surfaces. +// +// 39 3/03/98 6:56p Aldie +// Removed a printf. +// +// 38 3/03/98 6:51p Aldie +// Added some more footstep sounds. +// +// 37 3/03/98 6:02p Aldie +// First pass at footsteps. +// +// 36 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. +// +// 35 3/02/98 5:41p Jimdose +// Added commands for testing paths +// Changed the trace that use does. +// +// 34 2/21/98 1:21p Jimdose +// Removed A LOT of unused varables and code. Anything from here on out should +// be code written specifically for Sin and not converted Quake code. Quake is +// not a blueprint. It's only a reference. +// +// 33 2/19/98 2:35p Jimdose +// Added weapons back in +// +// 32 2/17/98 8:34p Jimdose +// Enabled the use key +// +// 31 2/06/98 5:44p Jimdose +// replaced use of think and touch functions with events +// move client to Entity +// +// 30 2/03/98 10:59a Jimdose +// Updated to work with Quake 2 engine +// Moved initialization to constructor and removed Init function +// This will probably be rewritten to be simpler. This first rewrite sucks. +// +// 28 12/15/97 2:52p Markd +// Fixed default weapons +// +// 27 12/15/97 12:35p Aldie +// +// 26 12/14/97 8:51p Aldie +// Updated impulse 9 to give all weapons. +// +// 25 12/14/97 8:16p Aldie +// Modified give all weapons list. +// +// 24 12/14/97 5:33p Markd +// Made default weapon Shotgun instead of DoubleBarrel +// +// 23 12/13/97 6:53p Aldie +// Added shotgun to give all weapons list +// +// 22 12/06/97 1:31p Aldie +// Fixed give weapon handler so that "give all" is case insensitive. +// +// 21 12/06/97 12:34p Aldie +// Added Tracer Gun to give all weapons list. +// +// 20 12/05/97 4:08p Aldie +// Added event to player for giving weapons. +// +// 19 11/18/97 5:29p Markd +// Added magnum and spear gun to impulse 9, checked for NULL currentWeapon and +// updated weapon frames in PlayerPostThink +// +// 18 11/12/97 5:12p Jimdose +// Changed enter and exit vehicle messages to be defined in Player.h +// +// 17 11/12/97 2:10p Jimdose +// Simplified the interface to PathSearch +// +// 16 11/10/97 8:30p Jimdose +// Flags were being subtracted rather than masked off in Jump. +// +// 15 11/07/97 6:49p Jimdose +// Added code for testing path.cpp. Enable by defining TESTPATH +// +// 14 11/01/97 2:19p Jimdose +// looking +// +// 13 11/01/97 2:12p Jimdose +// jump in vehicles +// +// 12 10/31/97 9:02p Jimdose +// Added FL_PRETHINK and FL_POSTTHINK +// +// 11 10/30/97 11:48p Jimdose +// Worked on vehicle code +// +// 10 10/29/97 12:54p Markd +// made viewthing multiple spawnable +// +// 9 10/29/97 11:51a Markd +// Made viewthing always face you when spawned. +// +// 8 10/28/97 6:54p Markd +// Added SPAWN_VIEWTHING message. Also added "return true" to other handled +// messages +// +// 7 10/27/97 3:29p Jimdose +// Removed dependency on quakedef.h +// +// 6 10/22/97 12:24p Jimdose +// Disabled warning when player recieves a blocked event. +// +// 5 10/09/97 4:26p Jimdose +// Worked on vehicle code +// +// 4 10/08/97 9:30p Jimdose +// Refined Vehicle movement. +// +// 3 10/08/97 6:03p Jimdose +// Began vehicle support. +// +// 2 9/26/97 6:47p Jimdose +// Added standard Ritual headers +// +// DESCRIPTION: +// Class definition of the player. +// +#include "g_local.h" +#include "entity.h" +#include "player.h" +#include "worldspawn.h" +#include "weapon.h" +#include "trigger.h" +#include "scriptmaster.h" +#include "vehicle.h" +#include "path.h" +#include "navigate.h" +#include "misc.h" +#include "q_shared.h" +#include "console.h" +#include "earthquake.h" +#include "gravpath.h" +#include "armor.h" +#include "inventoryitem.h" +#include "gibs.h" +#include "spidermine.h" +#include "deadbody.h" +#include "actor.h" +#include + +#ifdef SIN_ARCADE +#include "arcade_comm.h" +#endif + +const Vector power_color( 0.0, 1.0, 0.0 ); +const Vector acolor( 1.0, 1.0, 1.0 ); +const Vector bcolor( 1.0, 0.0, 0.0 ); + +static const char *ammo_types[ NUM_AMMO_TYPES ] = + { + "Bullet357", "ShotgunClip", "Bullet10mm", "Bullet50mm", "BulletPulse", "BulletSniper", "Rockets", "SpiderMines" + }; + +static const char *armor_types[ NUM_ARMOR_TYPES ] = + { + "RiotHelmet", "FlakJacket", "FlakPants" + }; + + +Event EV_Player_GodCheat( "superfuzz", EV_CHEAT ); +Event EV_Player_NoTargetCheat( "wallflower", EV_CHEAT ); +Event EV_Player_NoClipCheat( "nocollision", EV_CHEAT ); +Event EV_Player_GiveAllCheat( "wuss", EV_CHEAT ); +Event EV_Player_EndLevel( "endlevel" ); + +Event EV_Player_DevGodCheat( "god", EV_CHEAT ); +Event EV_Player_DevNoTargetCheat( "notarget", EV_CHEAT ); +Event EV_Player_DevNoClipCheat( "noclip", EV_CHEAT ); + +Event EV_Player_PrevWeapon( "weapprev", EV_CONSOLE ); +Event EV_Player_NextWeapon( "weapnext", EV_CONSOLE ); +Event EV_Player_PrevItem( "invprev", EV_CONSOLE ); +Event EV_Player_NextItem( "invnext", EV_CONSOLE ); +Event EV_Player_UseInventoryItem( "invuse", EV_CONSOLE ); +Event EV_Player_GiveCheat( "give", EV_CHEAT ); +Event EV_Player_Take( "take" ); +Event EV_Player_UseItem( "use", EV_CONSOLE ); +Event EV_Player_Spectator( "spectator", EV_CONSOLE ); +Event EV_Player_GameVersion( "gameversion", EV_CONSOLE ); +Event EV_Player_Fov( "fov" ); +Event EV_Player_SaveFov( "savefov", EV_CONSOLE ); +Event EV_Player_RestoreFov( "restorefov", EV_CONSOLE ); +Event EV_Player_ToggleViewMode( "toggleviewmode", EV_CONSOLE ); +Event EV_Player_ToggleZoomMode( "togglezoommode", EV_CHEAT ); +Event EV_Player_ZoomOut( "zoomout" ); +Event EV_Player_Kill( "playerkill", EV_CONSOLE ); +Event EV_Player_Dead( "dead" ); +Event EV_Player_SpawnEntity( "spawn", EV_CHEAT ); +Event EV_Player_SpawnActor( "actor", EV_CHEAT ); +Event EV_Player_ShowInfo( "showinfo", EV_CONSOLE ); +Event EV_Player_ReadyToFire( "readytofire" ); +Event EV_Player_WaitingToFire( "waitingtofire" ); +Event EV_Player_AttackDone( "attackdone" ); +Event EV_Player_AddPathNode( "addnode", EV_CHEAT ); +Event EV_Player_Respawn( "respawn" ); +Event EV_Player_ClearFloatingInventory( "clear_flinv" ); +Event EV_Player_TestThread( "testthread", EV_CHEAT ); +Event EV_Player_PowerupTimer( "poweruptimer" ); +Event EV_Player_UpdatePowerupTimer( "updatepoweruptime" ); +Event EV_Player_DrawOverlay( "drawoverlay" ); +Event EV_Player_HideOverlay( "hideoverlay" ); +Event EV_Player_DrawStats( "drawstats" ); +Event EV_Player_HideStats( "hidestats" ); +Event EV_Player_SetFlashColor( "setflashcolor" ); +Event EV_Player_ClearFlashColor( "clearflashcolor" ); +Event EV_Player_Mutate( "mutate", EV_CHEAT ); +Event EV_Player_Human( "human", EV_CHEAT ); +Event EV_Player_Skin( "skin" ); + +Event EV_Player_WhatIs( "whatis", EV_CHEAT ); +Event EV_Player_ActorInfo( "actorinfo", EV_CHEAT ); +Event EV_Player_Taunt( "taunt", EV_CONSOLE ); +Event EV_Player_KillEnt( "killent", EV_CONSOLE ); +Event EV_Player_KillClass( "killclass", EV_CONSOLE ); +Event EV_Player_RemoveEnt( "removeent", EV_CONSOLE ); +Event EV_Player_RemoveClass( "removeclass", EV_CONSOLE ); + +#define UPRIGHT_SPEED 320.0f +#define CROUCH_SPEED 110.0f +#define ACCELERATION 10.0f +#define TAUNT_TIME 1.0f + +/* +============================================================================== + +PLAYER + +============================================================================== +*/ +// deadflag values + +cvar_t * s_debugmusic; +cvar_t * whereami; + +CLASS_DECLARATION( Sentient, Player, "player" ); + +ResponseDef Player::Responses[] = + { + { &EV_ClientMove, ( Response )Player::ClientThink }, + { &EV_ClientEndFrame, ( Response )Player::EndFrame }, + { &EV_Player_ShowInfo, ( Response )Player::ShowInfo }, + { &EV_Vehicle_Enter, ( Response )Player::EnterVehicle }, + { &EV_Vehicle_Exit, ( Response )Player::ExitVehicle }, + + { &EV_Player_EndLevel, ( Response )Player::EndLevel }, + + { &EV_Player_AddPathNode, ( Response )Player::AddPathNode }, + { &EV_Player_PrevWeapon, ( Response )Player::EventPreviousWeapon }, + { &EV_Player_NextWeapon, ( Response )Player::EventNextWeapon }, + { &EV_Player_PrevItem, ( Response )Player::EventPreviousItem }, + { &EV_Player_NextItem, ( Response )Player::EventNextItem }, + { &EV_Player_UseItem, ( Response )Player::EventUseItem }, + { &EV_Player_UseInventoryItem,( Response )Player::EventUseInventoryItem }, + { &EV_Player_GiveCheat, ( Response )Player::GiveCheat }, + { &EV_Player_GiveAllCheat, ( Response )Player::GiveAllCheat }, + { &EV_Player_Take, ( Response )Player::Take }, + { &EV_Player_GodCheat, ( Response )Player::GodCheat }, + { &EV_Player_DevGodCheat, ( Response )Player::GodCheat }, + { &EV_Player_Spectator, ( Response )Player::Spectator }, + { &EV_Player_NoTargetCheat, ( Response )Player::NoTargetCheat }, + { &EV_Player_DevNoTargetCheat,( Response )Player::NoTargetCheat }, + { &EV_Player_NoClipCheat, ( Response )Player::NoclipCheat }, + { &EV_Player_DevNoClipCheat, ( Response )Player::NoclipCheat }, + { &EV_Player_GameVersion, ( Response )Player::GameVersion }, + { &EV_Player_Fov, ( Response )Player::Fov }, + { &EV_Player_SaveFov, ( Response )Player::SaveFov }, + { &EV_Player_RestoreFov, ( Response )Player::RestoreFov }, + { &EV_Player_ToggleViewMode, ( Response )Player::ToggleViewMode }, + { &EV_Player_ToggleZoomMode, ( Response )Player::ToggleZoomMode }, + { &EV_Player_ZoomOut, ( Response )Player::ZoomOut }, + { &EV_EnterConsole, ( Response )Player::EnterConsole }, + { &EV_ExitConsole, ( Response )Player::ExitConsole }, + { &EV_KickFromConsole, ( Response )Player::KickConsole }, + { &EV_Player_Kill, ( Response )Player::Kill }, + { &EV_Player_Dead, ( Response )Player::Dead }, + { &EV_Player_SpawnEntity, ( Response )Player::SpawnEntity }, + { &EV_Player_SpawnActor, ( Response )Player::SpawnActor }, + { &EV_Player_Respawn, ( Response )Player::Respawn }, + + { &EV_Pain, ( Response )Player::Pain }, + { &EV_Killed, ( Response )Player::Killed }, + { &EV_Gib, ( Response )Player::GibEvent }, + { &EV_GotKill, ( Response )Player::GotKill }, + + { &EV_Player_ReadyToFire, ( Response )Player::ReadyToFire }, + { &EV_Player_WaitingToFire, ( Response )Player::WaitingToFire }, + { &EV_Player_AttackDone, ( Response )Player::DoneFiring }, + { &EV_Player_TestThread, ( Response )Player::TestThread }, + + { &EV_Player_ClearFloatingInventory, (Response)Player::ClearFloatingInventory }, + { &EV_Player_PowerupTimer, ( Response )Player::SetPowerupTimer }, + { &EV_Player_UpdatePowerupTimer, ( Response )Player::UpdatePowerupTimer }, + + { &EV_Player_WhatIs, ( Response )Player::WhatIs }, + { &EV_Player_ActorInfo, ( Response )Player::ActorInfo }, + { &EV_Player_Taunt, ( Response )Player::Taunt }, + { &EV_Player_KillEnt, ( Response )Player::KillEnt }, + { &EV_Player_RemoveEnt, ( Response )Player::RemoveEnt }, + { &EV_Player_KillClass, ( Response )Player::KillClass }, + { &EV_Player_RemoveClass, ( Response )Player::RemoveClass }, + + { &EV_Player_DrawOverlay, ( Response )Player::DrawOverlay }, + { &EV_Player_HideOverlay, ( Response )Player::HideOverlay }, + { &EV_Player_DrawStats, ( Response )Player::DrawStats }, + { &EV_Player_HideStats, ( Response )Player::HideStats }, + + { &EV_Player_SetFlashColor, ( Response )Player::SetFlashColor }, + { &EV_Player_ClearFlashColor, ( Response )Player::ClearFlashColor }, + { &EV_Player_Mutate, ( Response )Player::Mutate }, + { &EV_Player_Human, ( Response )Player::Human }, + { &EV_Player_Skin, ( Response )Player::SetSkin }, + + { NULL, NULL } + }; + +Player::Player() + { + path = NULL; + + respawn_time = -1; + + watchCamera = NULL; + thirdpersonCamera = NULL; + movieCamera = NULL; + spectatorCamera = NULL; + + damage_blood = 0; + damage_alpha = 0; + + action_level = 0; + drawoverlay = false; + + defaultViewMode = FIRST_PERSON; + + fov = atof( Info_ValueForKey( client->pers.userinfo, "fov" ) ); + if ( fov < 1 ) + { + fov = 90; + } + else if ( fov > 160 ) + { + fov = 160; + } + + savedfov = 0; + has_thought = false; + + s_debugmusic = gi.cvar ("s_debugmusic", "0", 0); + whereami = gi.cvar ("whereami", "0", 0); + + // Remove him from the world until we spawn him + unlink(); + } + +void Player::InitSkin + ( + void + ) + + { + + // If this is a new level, there will be no custom skin, so go back to the original blade skin. + if ( !LoadingSavegame && !deathmatch->value ) + { + int playernum = edict-g_edicts-1; + + strcpy( client->pers.skin, "blade_base" ); + + + // combine name, skin and model into a configstring + gi.configstring( CS_PLAYERSKINS+playernum, va( "%s\\%s\\%s", + client->pers.netname, + client->pers.model, + client->pers.skin ) ); + } + } + + +void Player::Init + ( + void + ) + + { + InitClient(); + InitPhysics(); + InitPowerups(); + InitWorldEffects(); + InitMusic(); + InitPath(); + InitView(); + InitState(); + InitEdict(); + InitModel(); + InitWeapons(); + InitSkin(); + + // don't call RestoeEnt when respawning on a training level + if ( !LoadingServer && ( deathmatch->value || ( level.training && ( respawn_time != -1 ) ) || + !PersistantData.RestoreEnt( this ) ) ) + { + InitInventory(); + InitHealth(); + } + + ChooseSpawnPoint(); + + if ( !LoadingSavegame && ( viewmode == THIRD_PERSON ) && !crosshair ) + { + cvar_t * chair; + + chair = gi.cvar("crosshair", "0", 0); + crosshair = new Entity; + crosshair->setModel( va( "sprites/crosshair%d.spr", (int)chair->value ) ); + crosshair->edict->svflags |= SVF_ONLYPARENT; + crosshair->edict->owner = this->edict; + } + + if ( !deathmatch->value ) + { + gi.AddCommandString( "con_clearfade\n" ); + } + + // make sure we put the player back into the world + link(); + } + +void Player::WritePersistantData + ( + SpawnArgGroup &group + ) + + { + str text; + char t[ 3 ]; + int hi; + int lo; + int i; + + // encode the damage states into a text string + t[ 2 ] = 0; + for( i = 0; i < MAX_MODEL_GROUPS; i++ ) + { + hi = ( edict->s.groups[ i ] >> 4 ) & 0xf; + lo = edict->s.groups[ i ] & 0xf; + t[ 0 ] = ( char )( 'A' + hi ); + t[ 1 ] = ( char )( 'A' + lo ); + text += t; + } + + G_SetSpawnArg( "savemodel", savemodel.c_str() ); + G_SetSpawnArg( "saveskin", saveskin.c_str() ); + G_SetSpawnArg( "damage_groups", text.c_str() ); + G_SetFloatArg( "fov", fov ); + G_SetIntArg( "defaultViewMode", defaultViewMode ); + G_SetIntArg( "flags", flags & ( FL_GODMODE | FL_NOTARGET | FL_SP_MUTANT ) ); + + Sentient::WritePersistantData( group ); + } + +void Player::RestorePersistantData + ( + SpawnArgGroup &group + ) + + { +#ifndef SIN_DEMO + SpiderMine *spidermine; + Detonator *detonator; +#endif + int i; + int len; + str text; + const char *ptr; + int hi; + int lo; + + Sentient::RestorePersistantData( group ); + + group.RestoreArgs( 1 ); + + // clear the damage states + memset( edict->s.groups, 0, sizeof( edict->s.groups ) ); + + savemodel = G_GetStringArg( "savemodel" ); + saveskin = G_GetStringArg( "saveskin" ); + + text = G_GetStringArg( "damage_groups" ); + len = text.length() >> 1; + if ( len > MAX_MODEL_GROUPS ) + { + len = MAX_MODEL_GROUPS; + } + + // decode the damage states from text string + ptr = text.c_str(); + for( i = 0; i < len; i++ ) + { + hi = ( *ptr++ - 'A' ) & 0xf; + lo = ( *ptr++ - 'A' ) & 0xf; + edict->s.groups[ i ] = ( hi << 4 ) + lo; + } + + flags |= G_GetIntArg( "flags" ); + fov = G_GetFloatArg( "fov", 90 ); + defaultViewMode = ( viewmode_t )G_GetIntArg( "defaultViewMode", FIRST_PERSON ); + +#ifndef SIN_DEMO + spidermine = ( SpiderMine * )FindItem( "SpiderMine" ); + detonator = ( Detonator * )FindItem( "Detonator" ); + + if ( spidermine ) + { + spidermine->SetDetonator( detonator ); + } + + if ( FindItem( "Silencer" ) ) + { + flags |= FL_SILENCER; + } + + if ( FindItem( "ScubaGear" ) ) + { + flags |= FL_OXYGEN; + } + + if ( flags & FL_SP_MUTANT ) + { + setModel( "manumit_pl.def" ); + SetAnim( "idle" ); + } +#endif + + if ( defaultViewMode == CAMERA_VIEW ) + { + defaultViewMode = FIRST_PERSON; + } + + SetViewMode( defaultViewMode ); + + // prevent the player from starting dead + if ( health < 1 ) + { + health = 1; + } + } + +void Player::InitEdict + ( + void + ) + + { + // entity state stuff + setSolidType( SOLID_BBOX ); + setMoveType( MOVETYPE_WALK ); + setSize( Vector( -16, -16, 0 ), Vector( 16, 16, STAND_HEIGHT ) ); + edict->clipmask = MASK_PLAYERSOLID; + edict->svflags &= ~SVF_DEADMONSTER; + edict->svflags &= ~SVF_HIDEOWNER; + edict->owner = NULL; + + // clear entity state values + edict->s.effects = 0; + edict->s.frame = 0; + } + +void Player::InitMusic + ( + void + ) + + { + // + // reset the music + // + client->ps.current_music_mood = mood_normal; + client->ps.fallback_music_mood = mood_normal; + ChangeMusic( "normal", "normal", false ); + music_forced = false; + } + +void Player::InitClient + ( + void + ) + + { + client_persistant_t saved; + client_respawn_t resp; + + // deathmatch wipes most client data every spawn + if ( deathmatch->value || level.training ) + { + char userinfo[ MAX_INFO_STRING ]; + + resp = client->resp; + memcpy( userinfo, client->pers.userinfo, sizeof( userinfo ) ); + G_InitClientPersistant( client ); + G_ClientUserinfoChanged( edict, userinfo ); + } + else + { + memset( &resp, 0, sizeof( resp ) ); + } + + // clear everything but the persistant data and fov + saved = client->pers; + memset( client, 0, sizeof( *client ) ); + client->pers = saved; + client->resp = resp; + } + +void Player::InitState + ( + void + ) + + { + in_console = false; + trappedInQuantum = false; + floating_owner = NULL; + gibbed = false; + enemy = NULL; + lastEnemyTime = 0; + lastTauntTime = 0; + + takedamage = DAMAGE_AIM; + deadflag = DEAD_NO; + flags &= ~FL_NO_KNOCKBACK; + flags |= ( FL_BLOOD | FL_DIE_GIBS ); + + if ( parentmode->value ) + { + flags &= ~FL_BLOOD; + flags &= ~FL_DIE_GIBS; + } + } + +void Player::InitHealth + ( + void + ) + + { + // Don't do anything if we're loading a server game. + // This is either a loadgame or a restart + if ( LoadingServer ) + { + return; + } + + // reset the health values + health = 100; + max_health = 100; + last_damage_time = 0; + + // clear the damage states + memset( edict->s.groups, 0, sizeof( edict->s.groups ) ); + } + +void Player::InitModel + ( + void + ) + + { + str model; + + // Model stuff + edict->s.renderfx |= RF_CUSTOMSKIN; + edict->s.skinnum = edict->s.number - 1; + + // Make sure that the model is allowed + model = client->pers.model; + if ( !game.ValidPlayerModels.ObjectInList( model ) ) + { + model = "pl_blade.def"; + } + + setModel( model.c_str() ); + TempAnim( "idle", NULL ); + showModel(); + } + +void Player::InitPhysics + ( + void + ) + + { + // Physics stuff + onladder = false; + sentientFrozen = false; + oldvelocity = vec_zero; + velocity = vec_zero; + gravity = 1.0; + falling = false; + fall_time = 0; + fall_value = 0; + fallsurface = NULL; + mass = 200; + xyspeed = 0; + } + +void Player::InitPowerups + ( + void + ) + + { + // powerups + poweruptimer = 0; + poweruptype = 0; + flags &= ~( FL_SHIELDS | FL_ADRENALINE | FL_CLOAK | FL_MUTANT | FL_SILENCER | FL_OXYGEN ); + edict->s.renderfx &= ~( RF_DLIGHT | RF_ENVMAPPED ); + } + +void Player::InitWorldEffects + ( + void + ) + + { + // world effects + next_drown_time = 0; + air_finished = level.time + 20; + old_waterlevel = 0; + drown_damage = 0; + } + +void Player::InitWeapons + ( + void + ) + + { + // weapon stuff + firing = false; + firedown = false; + firedowntime = 0; + gunoffset = Vector( 0, 0, STAND_HEIGHT ); + } + +void Player::InitInventory + ( + void + ) + + { + // Don't do anything if we're loading a server game. + // This is either a loadgame or a restart + if ( LoadingServer ) + { + return; + } + + // Give player the default weapon + if ( !FindItem( "Magnum" ) ) + { + giveWeapon( "Magnum" ); + } + + if ( !FindItem( "Fists" ) ) + { + giveWeapon( "Fists" ); + } + +#ifndef SIN_DEMO + if ( !FindItem( "SpiderMine" ) ) + { + giveWeapon( "SpiderMine" ); + } +#endif + } + +void Player::InitView + ( + void + ) + + { + // Camera stuff + zoom_mode = ZOOMED_OUT; + spectator = false; + currentCameraTarget = 0; + checked_dead_camera = false; + SetViewMode( defaultViewMode ); + + // view stuff + kick_angles = vec_zero; + kick_origin = vec_zero; + v_angle = vec_zero; + oldviewangles = vec_zero; + v_gunoffset = vec_zero; + v_gunangles = vec_zero; + viewheight = STAND_EYE_HEIGHT; + + // blend stuff + v_dmg_time = 0; + damage_blend = vec_zero; + flash_color[0] = flash_color[1] = flash_color[2] = flash_color[3] = 0; + + // hud + hidestats = false; + } + +void Player::ChooseSpawnPoint + ( + void + ) + + { + // set up the player's spawn location + SelectSpawnPoint( origin, angles, &gravaxis ); + setOrigin( origin + "0 0 17" ); + worldorigin.copyTo( edict->s.old_origin ); + edict->s.renderfx |= RF_FRAMELERP; + + KillBox( this ); + + // set gravity axis + SetGravityAxis( gravaxis ); + client->ps.pmove.gravity_axis = gravaxis; + + // setup the orientation + setAngles( angles ); + v_angle = worldangles; + + // set the delta angle + client->ps.pmove.delta_angles[0] = ANGLE2SHORT( angles.x - client->resp.cmd_angles[0] ); + client->ps.pmove.delta_angles[1] = ANGLE2SHORT( angles.y - client->resp.cmd_angles[1] ); + client->ps.pmove.delta_angles[2] = ANGLE2SHORT( angles.z - client->resp.cmd_angles[2] ); + + if ( deathmatch->value || coop->value ) + { + SpawnTeleportEffect( worldorigin, 124 ); + } + } + +void Player::InitPath + ( + void + ) + + { + // pathnode placement stuff + nearestNode = NULL; + + if ( ai_createnodes->value ) + { + nearestNode = new PathNode; + nearestNode->Setup( worldorigin ); + } + + goalNode = nearestNode; + lastNode = nearestNode; + lastVisible = worldorigin; + searchTime = level.time + 0.1; + } + +Player::~Player() + { + if ( client ) + { + gi.bprintf( PRINT_HIGH, "%s disconnected\n", client->pers.netname ); + } + + if ( watchCamera && ( ( watchCamera == thirdpersonCamera ) || ( watchCamera == spectatorCamera ) ) ) + { + delete watchCamera; + watchCamera = NULL; + thirdpersonCamera = NULL; + spectatorCamera = NULL; + } + + if ( crosshair ) + { + delete crosshair; + crosshair = NULL; + } + + if ( path ) + { + delete path; + path = NULL; + } + + edict->s.modelindex = 0; + + gi.configstring( CS_PLAYERSKINS + entnum - 1, "" ); + } + +void Player::EndLevel + ( + Event *ev + ) + + { + if ( flags & FL_MUTANT ) + { + Human( NULL ); + } + + InitPowerups(); + if ( health > max_health ) + { + health = max_health; + } + + if ( health < 1 ) + { + health = 1; + } + } + +void Player::Respawn + ( + Event *ev + ) + + { + if ( deathmatch->value || coop->value || level.training ) + { + assert ( deadflag == DEAD_DEAD ); + +#ifndef SIN_ARCADE + FreeInventory(); +#endif + + if ( !gibbed && !level.training ) + { + CopyToBodyQueue( edict ); + } + + respawn_time = level.time; + + Init(); + + // hold in place briefly + client->ps.pmove.pm_time = 50; + client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + + return; + } + + // force the load game menu to come up + level.missionfailed = true; + level.missionfailedtime = level.time - FRAMETIME; + } + +void Player::SetDeltaAngles + ( + void + ) + + { + int i; + + // Use v_angle since we may be in a camera + for( i = 0; i < 3; i++ ) + { + client->ps.pmove.delta_angles[ i ] = ANGLE2SHORT( v_angle[ i ] ); + } + } + +void Player::Spectator + ( + Event *ev + ) + + { + hideModel(); + setSolidType( SOLID_NOT ); + spectator = true; + FreeInventory(); + + // Send over a message that lets the player know they are in spectator mode. + + gi.WriteByte (svc_layout); + gi.WriteString ("jcx jt hstring 0 0 1 \"SPECTATOR MODE\""); + gi.unicast (edict, true); + + client->ps.stats[ STAT_LAYOUTS ] |= DRAW_SPECTATOR; + } + +void Player::Obituary + ( + Entity *attacker, + Entity *inflictor, + str location, + int meansofdeath + ) + + { + const char *message1 = NULL; + const char *message2 = ""; + int num; + + if ( !deathmatch->value ) + return; + + // Client killed themself + if ( attacker == this ) + { + switch ( meansofdeath ) + { + case MOD_FALLING: + message1 = "fell down"; + break; + case MOD_ADRENALINE: + message1 = "overdosed on adrenaline"; + break; + case MOD_ION_DESTRUCT: + message1 = "overloaded his gun"; + break; + default: + message1 = "cracked under the pressure"; + } + + if ( message1 ) + { + gi.bprintf( PRINT_MEDIUM, "%s %s.\n", client->pers.netname, message1 ); + } + else + { + gi.bprintf( PRINT_MEDIUM, "%s died.\n", client->pers.netname ); + } + client->resp.score--; + return; + } + + // Killed by another client + if ( attacker && attacker->isClient() ) + { + switch (meansofdeath) + { + case MOD_FISTS: // FISTS + { + num = G_Random(6); + switch (num) + { + case 0: + message1 = "was knocked out by"; + break; + case 1: + message1 = "was pummelled by"; + break; + case 2: + message1 = "was over come by"; + message2 = "'s ninja technique"; + break; + case 3: + message1 = "ate a knuckle sandwich courtesy of"; + break; + case 4: + message1 = "takes"; + message2 = "'s beating"; + break; + case 5: + message1 = "liked"; + message2 = "'s death touch"; + break; + default: + message1 = "was killed by"; + message2 = "'s fists"; + break; + } + } + break; + case MOD_MUTANTHANDS: + message1 = "was shredded by"; + break; + case MOD_MAGNUM: // Magnum + { + num = G_Random(5); + switch (num) + { + case 0: + message1 = "was capped in the ass by"; + break; + case 1: + message1 = "was gunned down in broad daylight by"; + break; + case 2: + message1 = "eats"; + message2 = "'s magnum"; + break; + case 3: + message1 = "was punked by"; + break; + case 4: + message1 = "was jacked by"; + break; + default: + message1 = "was killed by"; + message2 = "'s magnum"; + break; + } + break; + } + case MOD_SHOTGUN: // Shotgun + { + num = G_Random(5); + switch (num) + { + case 0: + message1 = "is a victim of"; + message2 = "'s shotgun"; + break; + case 1: + message1 = "was slaughtered by"; + break; + case 2: + message1 = "drowns in a rain of"; + message2 = "'s hot lead"; + break; + case 3: + message1 = "finds out why"; + message2 = " got an F in gun safety"; + break; + case 4: + message1 = "was shotgunned by"; + break; + default: + message1 = "was killed by"; + message2 = "'s shotgun"; + break; + } + break; + } + case MOD_ASSRIFLE: + { + num = G_Random(4); + switch (num) + { + case 0: + message1 = "was assaulted by"; + break; + case 1: + message1 = "was cut in half by"; + break; + case 2: + message1 = "forgot that"; + message2 = " had a bigger gun"; + break; + case 3: + message1 = "was machinegunned down by"; + break; + default: + message1 = "was killed by"; + message2 = "'s assault rifle"; + break; + } + } + break; + case MOD_CHAINGUN: + message1 = "was mowed down by"; + message2 = "'s chaingun"; + break; + case MOD_GRENADE: + message1 = "was fragged by"; + message2 = "'s grenade"; + break; + case MOD_ROCKET: + { + num = G_Random(5); + switch (num) + { + case 0: + message1 = "was blown up by"; + message2 = "'s direct rocket hit"; + break; + case 1: + message1 = "bows down to"; + message2 = "'s almighty rocket launcher"; + break; + case 2: + message1 = "was incinerated by"; + message2 = "'s rocket"; + break; + case 3: + message1 = "was obliterated by"; + message2 = "'s rocket"; + break; + case 4: + message1 = "likes walking into"; + message2 = "'s projectiles"; + break; + default: + message1 = "was killed by"; + message2 = "'s rocket launcher"; + } + } + break; + case MOD_ROCKETSPLASH: + { + num = G_Random(5); + switch (num) + { + case 0: + message1 = "was caught in"; + message2 = "'s explosion"; + break; + case 1: + message1 = "was burned to a crisp in"; + message2 = "'s blast"; + break; + case 2: + message1 = "is pulling shrapnel out of his ass thanks to"; + break; + case 3: + message1 = "couldn't dodge"; + message2 = "'s blast damage"; + break; + case 4: + message1 = "was drenched by"; + message2 = "'s splash damage"; + break; + default: + message1 = "was killed by"; + message2 = "'s explosion"; + } + break; + } + case MOD_SPIDERSPLASH: + message1 = "was bitten by"; + message2 = "'s spidermine"; + break; + case MOD_SPEARGUN: + message1 = "was stuck by"; + message2 = "'s spear"; + break; + case MOD_PULSE: + message1 = "was pulsed by"; + break; + case MOD_PULSELASER: + message1 = "was burned to a crisp by"; + message2 = "'s laser"; + break; + case MOD_ION: + case MOD_ION_DESTRUCT: + message1 = "Was ionized by"; + break; + case MOD_QUANTUM: + message1 = "Was particlized by"; + break; + case MOD_SNIPER: + { + num = G_Random(5); + switch (num) + { + case 0: + message1 = "was hollowpointed by"; + break; + case 1: + message1 = "was put to sleep by"; + break; + case 2: + message1 = "was sniped by"; + break; + case 3: + message1 = "never saw it coming from"; + break; + case 4: + message1 = "was killed by a precision shot from"; + break; + default: + message1 = "was killed by"; + message2 = "'s sniper rifle"; + } + break; + } + default: + message1 = "was killed by"; + } + gi.bprintf( PRINT_MEDIUM, "%s %s %s%s\n", client->pers.netname, + message1, + attacker->client->pers.netname, + message2 ); + attacker->client->resp.score++; + return; + } + + // Killed by a non-client + switch (meansofdeath) + { + case MOD_FALLING: + { + // Credit the last person who damaged me with the kill + if ( enemy && enemy->isClient() && ( enemy != this ) && level.time < lastEnemyTime ) + { + attacker = enemy; + message1 = "was pushed over the edge by"; + message2 = ""; + + // Give the last enemy a frag + enemy->client->resp.score++; + } + else + { + message1 = "fell down"; + message2 = ""; + } + break; + } + } + + // Killed indirectly by enemy + if (message1 && attacker->isClient() ) + { + gi.bprintf( PRINT_MEDIUM, "%s %s %s%s\n", client->pers.netname, + message1, + attacker->client->pers.netname, + message2 ); + } + // Killed by world somehow + else if (message1) + { + gi.bprintf( PRINT_MEDIUM, "%s %s\n", client->pers.netname, + message1); + client->resp.score--; + } + else + { + gi.bprintf( PRINT_MEDIUM, "%s died.\n", client->pers.netname ); + client->resp.score--; + } + } + +void Player::Dead + ( + Event *ev + ) + + { + deadflag = DEAD_DEAD; + StopAnimating(); + + // + // drop anything that might be attached to us + // + if (numchildren && ( level.training != 2 ) ) + { + Entity * child; + int i; + // + // detach all our children + // + for ( i=0; i < MAX_MODEL_CHILDREN; i++ ) + { + if (children[i]) + { + child = ( Entity * )G_GetEntity( children[ i ] ); + child->ProcessEvent( EV_Detach ); + } + } + } + + if ( !deathmatch->value && !coop->value && !level.training ) + { + // Fade to white + gi.WriteByte( svc_console_command ); + gi.WriteString( "fo 0.8 1 1 1" ); + gi.unicast( edict, false ); + PostEvent( EV_Player_Respawn, 3.0f ); + } + +#ifdef SIN_ARCADE + if ( !ARCADE_ComWriteByte( 0x03 ) ) + gi.error( "Could not send lost player life to communications port" ); +#endif + } + +void Player::Killed + ( + Event *ev + ) + + { + Entity *attacker; + Entity *inflictor; + str location; + str prefix; + str aname; + int meansofdeath; + int i; + qboolean deathgib=false; + str dname; + Event *ev1; + + attacker = ev->GetEntity( 1 ); + inflictor = ev->GetEntity( 3 ); + location = ev->GetString( 4 ); + meansofdeath = ev->GetInteger( 5 ); + + // Set the enemy to attacker if it is a client, otherwise + // some world object killed the player. We don't overwrite + // enemy with non-clients, so clients can get credit for + // kills they caused (i.e. shooting someone caused them to fall ) + if ( attacker->isClient() ) + { + enemy = attacker; + } + + // get rid of the third person crosshair if we have one + if ( crosshair ) + { + delete crosshair; + crosshair = NULL; + } + + Obituary( attacker, inflictor, location, meansofdeath ); + + StopAnimating(); + CancelPendingEvents(); + + // + // determine death animation + // + if ( DoGib( meansofdeath, inflictor ) ) + { + deathgib = true; + } + + prefix = AnimPrefixForPlayer(); + + if ( deathgib ) + { + str location; + + location = ev->GetString( 4 ); + + // Check for location first otherwise randomize + if ( location == "torso_upper" ) + dname += str( "gibdeath_upper" ); + else if ( location == "torso_lower" ) + dname += str( "gibdeath_lower" ); + else if ( strstr( location.c_str(), "leg" ) ) + dname += str( "gibdeath_lower" ); + else if ( strstr( location.c_str(), "arm" ) ) + dname += str( "gibdeath_upper" ); + else if ( strstr( location.c_str(), "head" ) ) + dname += str( "gibdeath_upper" ); + else if ( G_Random() > 0.5 ) + dname += str( "gibdeath_upper" ); + else + dname += str( "gibdeath_lower" ); + } + + if ( deathgib ) + aname = prefix + dname; + else + aname = prefix + str("death_") + location; + + i = gi.Anim_Random( edict->s.modelindex, aname.c_str() ); + + if ( i == -1 ) + { + aname = prefix + str("death"); + } + + deadflag = DEAD_DYING; + + respawn_time = level.time + 1.0; + + if ( currentWeapon ) + { + if ( !level.training ) + { + DropWeapon( currentWeapon ); + } + else if ( level.training != 2 ) + { + delete currentWeapon; + } + } + + if ( coop->value ) + { + DropInventoryItems(); + } + + edict->clipmask = MASK_DEADSOLID; + edict->svflags |= SVF_DEADMONSTER; + + ProcessEvent( EV_Player_ZoomOut ); + SetViewMode( THIRD_PERSON ); + setMoveType( MOVETYPE_NONE ); + + edict->s.renderfx &= ~RF_DLIGHT; + edict->s.renderfx &= ~RF_ENVMAPPED; + + angles.x = 0; + angles.z = 0; + setAngles( angles ); + + if ( deathmatch->value ) + { + client->showinfo = true; + G_DeathmatchScoreboard( this ); + } + + if ( flags & ( FL_MUTANT|FL_SP_MUTANT ) ) + { + Human( NULL ); + ev1 = new Event( EV_Gib ); + ev1->AddInteger( 0 ); + ProcessEvent( ev1 ); + ProcessEvent( EV_Player_Dead ); + } + else if ( strstr( aname.c_str(), "gibdeath" ) ) + { + ev1 = new Event( EV_Gib ); + ev1->AddInteger( 1 ); + ProcessEvent( ev1 ); + } + + RandomAnimate( aname.c_str(), EV_Player_Dead ); + animOverride = true; + + // + // change music + // + ChangeMusic( "failure", "normal", true ); + + if ( !deathmatch->value && !level.training && !level.no_jc ) + { + char name[ 128 ]; + int num; + + num = (int)G_Random( 4 ) + 1; + sprintf( name, "global/universal_script.scr::jc_talks_blade_dies%d", num ); + ExecuteThread( name, true ); + } + } + +void Player::Pain + ( + Event *ev + ) + + { + float damage,delta; + Entity *attacker; + int meansofdeath; + + damage = ev->GetFloat( 1 ); + attacker = ev->GetEntity( 2 ); + meansofdeath = ev->GetInteger( 4 ); + + if ( viewmode == CAMERA_VIEW ) + { + if ( level.cinematic ) + { + return; + } + else + { + ExitConsole( NULL ); + SetCamera( NULL ); + } + } + + // add to our damage time + v_dmg_time = level.time + DAMAGE_TIME; + + if ( attacker->isClient() ) + { + if ( !deadflag ) + { + enemy = attacker; + lastEnemyTime = level.time + ENEMY_TIME; + } + } + + // increase action level of game + action_level += damage; + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + damage_blood += damage; + + // white flash signifying no damage was taken. + if ( ( damage <= 0 ) && ( meansofdeath != MOD_FISTS ) ) + { + Event *ev1; + + ProcessEvent( EV_Player_ClearFlashColor ); + ev1 = new Event( EV_Player_SetFlashColor ); + + // Flash the player white + ev1->AddFloat( 1 ); + ev1->AddFloat( 1 ); + ev1->AddFloat( 1 ); + ev1->AddFloat( 0.3 ); + ProcessEvent( ev1 ); + } + + delta = level.time - last_damage_time; + + // Don' play more than one pain sound or animation a second + if ( delta < 1.0 ) + { + return; + } + + last_damage_time = level.time; + + // If damage > 5 then play a pain animation, otherwise play a pain sound + if ( !firing && !deadflag && damage > 5) + { + str prefix; + str aname; + int index; + + // + // determine pain animation + // + prefix = AnimPrefixForPlayer(); + aname = prefix + str("pain_") + str( ev->GetString( 3 ) ); + index = gi.Anim_Random( edict->s.modelindex, aname.c_str() ); + if ( index == -1 ) + { + aname = prefix + str("pain"); + } + + TempAnim( aname.c_str(), NULL ); + } + else + { + RandomSound( "snd_pain", 1 ); + ProcessEvent( EV_PainSound ); + } + } + +void Player::DoUse + ( + void + ) + + { + int i; + int num; + edict_t *touch[ MAX_EDICTS ]; + edict_t *hit; + Event *ev; + Vector min; + Vector max; + Vector offset; + trace_t trace; + Vector start; + Vector end; + float t; + + + // Pickup inventory if one exists + PickupFloatingInventory(); + + start = worldorigin + Vector( 0, 0, viewheight ); + end = start + Vector( orientation[ 0 ] ) * 64.0f; + + trace = G_Trace( start, vec_zero, vec_zero, end, this, MASK_SOLID, "Player::DoUse" ); + + t = 64 * trace.fraction - maxs[ 0 ]; + if ( t < 0 ) + { + t = 0; + } + + offset = Vector( orientation[ 0 ] ) * t; + + min = start + offset + "-16 -16 -16"; + max = start + offset + "16 16 16"; + + num = gi.BoxEdicts( min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_TRIGGERS ); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for( i = 0; i < num; i++ ) + { + hit = touch[ i ]; + if ( !hit->inuse ) + { + continue; + } + + assert( hit->entity ); + + ev = new Event( EV_Use ); + ev->AddEntity( this ); + hit->entity->ProcessEvent( ev ); + } + + num = gi.BoxEdicts( min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_SOLID ); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for( i = 0; i < num; i++ ) + { + hit = touch[ i ]; + if ( !hit->inuse ) + { + continue; + } + + assert( hit->entity ); + + ev = new Event( EV_Use ); + ev->AddEntity( this ); + hit->entity->ProcessEvent( ev ); + } + + // Force a reload on a weapon + if ( currentWeapon ) + { + currentWeapon->ForceReload(); + } + + } + +static Entity *pm_passent; + +// pmove doesn't need to know about passent and contentmask +extern "C" trace_t PM_trace + ( + vec3_t start, + vec3_t mins, + vec3_t maxs, + vec3_t end + ) + + { + if ( ( pm_passent->flags & FL_GODMODE ) || ( pm_passent->health > 0 ) ) + { + return G_Trace( start, mins, maxs, end, pm_passent->edict, MASK_PLAYERSOLID, "PM_trace 1" ); + } + else + { + return G_Trace( start, mins, maxs, end, pm_passent->edict, MASK_DEADSOLID, "PM_trace 1" ); + } + } + +EXPORT_FROM_DLL void Player::SetViewMode + ( + viewmode_t mode, + Entity * newCamera + ) + + { + Camera *c; + + viewmode = mode; + + if ( watchCamera && ( ( watchCamera == thirdpersonCamera ) || ( watchCamera == spectatorCamera ) ) ) + { + delete watchCamera; + watchCamera = NULL; + thirdpersonCamera = NULL; + spectatorCamera = NULL; + } + + if ( crosshair ) + { + delete crosshair; + crosshair = NULL; + } + + switch( viewmode ) + { + default : + case THIRD_PERSON : + { + Event * ev; + Vector pos; + + c = new Camera; + + ev = new Event( EV_Camera_MoveToPos ); + pos = worldorigin; + pos.z = absmax.z; + ev->AddVector( pos ); + c->ProcessEvent( ev ); + + ev = new Event( EV_Camera_TurnTo ); + ev->AddVector( v_angle ); + c->ProcessEvent( ev ); + + if ( vehicle ) + c->FollowEntity( this, 256, MASK_OPAQUE ); + else + c->FollowEntity( this, 128, MASK_PLAYERSOLID ); + watchCamera = c; + thirdpersonCamera = c; + + if ( !deadflag ) + { + cvar_t * chair; + + chair = gi.cvar("crosshair", "0", 0); + crosshair = new Entity; + crosshair->setModel( va( "sprites/crosshair%d.spr", (int)chair->value ) ); + crosshair->edict->svflags |= SVF_ONLYPARENT; + crosshair->edict->owner = this->edict; + } + } + break; + + case SPECTATOR : + { + Event * ev; + Vector pos; + edict_t *cl_ent; + + cl_ent = g_edicts + 1 + currentCameraTarget; + c = new Camera; + + ev = new Event( EV_Camera_MoveToPos ); + pos = cl_ent->entity->worldorigin; + pos.z = cl_ent->entity->absmax.z; + ev->AddVector( pos ); + c->ProcessEvent( ev ); + + ev = new Event( EV_Camera_TurnTo ); + ev->AddVector( cl_ent->entity->worldangles ); + c->ProcessEvent( ev ); + + c->FollowEntity( cl_ent->entity, 128, MASK_PLAYERSOLID ); + watchCamera = c; + spectatorCamera = c; + } + break; + + case FIRST_PERSON : + watchCamera = this; + break; + + case CAMERA_VIEW : + watchCamera = newCamera; + break; + } + } + +EXPORT_FROM_DLL void Player::CheckButtons + ( + void + ) + + { + // Only process buttons if you're not dying + if ( deadflag != DEAD_NO ) + { + return; + } + + if ( new_buttons & BUTTON_USE ) + { + DoUse(); + } + + if ( ( buttons & BUTTON_ATTACK ) && spectator ) + ChangeSpectator(); + + // Check for button release + if ( !( buttons & BUTTON_ATTACK ) ) + { + if ( firedown ) + { + Event *event; + event = new Event( EV_Sentient_ReleaseAttack ); + event->AddFloat( level.time - firedowntime ); + ProcessEvent( event ); + firedown = false; + } + } + + // fire weapon from final position if needed + if ( ( buttons & BUTTON_ATTACK ) && WeaponReady() ) + { + // + // raise the action level + // + if ( currentWeapon ) + action_level += currentWeapon->ActionLevelIncrement(); + + firedown = true; + firedowntime = level.time; + + if ( !vehicle ) + { + str name; + str prefix; + + prefix = AnimPrefixForPlayer(); + // + // append the prefix based on which weapon we are holding + // + prefix += str( AnimPrefixForWeapon() ); + + if ( ( xyspeed > 20 ) ) + { + int num; + int numframes; + + if ( ( waterlevel > 2 ) || ( xyspeed > 250 ) ) + name = prefix + str("run_fire"); + else + name = prefix + str("walk_fire"); + num = gi.Anim_Random( edict->s.modelindex, name.c_str() ); + if ( num != -1 ) + { + numframes = gi.Anim_NumFrames( edict->s.modelindex, num ); + if ( ( last_frame_in_anim + 1 ) == numframes ) + edict->s.anim = num; + else + TempAnim( name.c_str(), NULL ); + } + else + { + name = prefix + str("fire"); + TempAnim( name.c_str(), NULL ); + } + } + else + { + name = prefix + str("fire"); + TempAnim( name.c_str(), NULL ); + } + } + + ProcessEvent( EV_Sentient_Attack ); + + if ( ai_createnodes->value ) + { + goalNode = nearestNode; + if ( !goalNode ) + { + if ( ai_debugpath->value ) + { + gi.dprintf( "New path node\n" ); + } + nearestNode = new PathNode; + nearestNode->Setup( worldorigin ); + goalNode = nearestNode; + lastNode = nearestNode; + } + } + } + } + +EXPORT_FROM_DLL void Player::TouchStuff + ( + pmove_t *pm + ) + + { + edict_t *other; + Event *event; + int i; + int j; + + if ( getMoveType() != MOVETYPE_NOCLIP ) + { + G_TouchTriggers( this ); + } + + // touch other objects + for( i = 0; i < pm->numtouch; i++ ) + { + other = pm->touchents[ i ]; + for( j = 0; j < i ; j++ ) + { + if ( pm->touchents[ j ] == other ) + break; + } + + if ( j != i ) + { + // duplicated + continue; + } + + // Don't bother touching the world + if ( ( !other->entity ) || ( other->entity == world ) ) + { + continue; + } + + event = new Event( EV_Touch ); + event->AddEntity( this ); + other->entity->ProcessEvent( event ); + + event = new Event( EV_Touch ); + event->AddEntity( other->entity ); + ProcessEvent( event ); + } + } + +EXPORT_FROM_DLL void Player::EnterVehicle + ( + Event *ev + ) + + { + Entity *ent; + + ent = ev->GetEntity( 1 ); + if ( ent && ent->isSubclassOf( Vehicle ) ) + { + viewheight = STAND_EYE_HEIGHT; + levelVars.SetVariable( "player_in_vehicle", 1 ); + velocity = vec_zero; + vehicle = ( Vehicle * )ent; + if ( vehicle->IsDrivable() ) + setMoveType( MOVETYPE_VEHICLE ); + else + setMoveType( MOVETYPE_NOCLIP ); + if ( ev->NumArgs() > 1 ) + vehicleanim = ev->GetString( 2 ); + else + vehicleanim = "drive"; + } + } + +EXPORT_FROM_DLL void Player::ExitVehicle + ( + Event *ev + ) + + { + levelVars.SetVariable( "player_in_vehicle", 0 ); + setMoveType( MOVETYPE_WALK ); + vehicle = NULL; + } + +EXPORT_FROM_DLL void Player::GetMoveInfo + ( + pmove_t *pm + ) + + { + if ( !deadflag ) + { + v_angle[ 0 ] = pm->viewangles[ 0 ]; + v_angle[ 1 ] = pm->viewangles[ 1 ]; + v_angle[ 2 ] = pm->viewangles[ 2 ]; + AnglesToMat( v_angle.vec3(), orientation ); + } + + client->ps.pmove = pm->s; + old_pmove = pm->s; + + setOrigin( Vector( pm->s.origin[ 0 ], pm->s.origin[ 1 ], pm->s.origin[ 2 ] ) * 0.125 ); + velocity = Vector( pm->s.velocity[ 0 ], pm->s.velocity[ 1 ], pm->s.velocity[ 2 ] ) * 0.125; + + if ( + ( client->ps.pmove.pm_type == PM_FREEZE ) || + ( client->ps.pmove.pm_type == PM_INVEHICLE_ZOOM ) || + ( client->ps.pmove.pm_type == PM_INVEHICLE ) + ) + velocity = vec_zero; + + setSize( pm->mins, pm->maxs ); + + gunoffset = Vector( 0, 0, pm->viewheight ); + viewheight = pm->viewheight; + waterlevel = pm->waterlevel; + watertype = pm->watertype; + onladder = pm->onladder; + if ( pm->groundentity ) + { + groundentity = pm->groundentity; + groundsurface = pm->groundsurface; + groundplane = pm->groundplane; + groundcontents = pm->groundcontents; + groundentity_linkcount = pm->groundentity->linkcount; + } + else + { + groundentity = NULL; + groundsurface = NULL; + groundcontents = 0; + } + if ( pm->groundsurface ) + fallsurface = pm->groundsurface; + } + +EXPORT_FROM_DLL void Player::SetMoveInfo + ( + pmove_t *pm, + usercmd_t *ucmd + ) + + { + pm_passent = this; + + // set up for pmove + memset( pm, 0, sizeof( pmove_t ) ); + + pm->s = client->ps.pmove; + + pm->s.origin[ 0 ] = worldorigin.x * 8; + pm->s.origin[ 1 ] = worldorigin.y * 8; + pm->s.origin[ 2 ] = worldorigin.z * 8; + + pm->s.velocity[ 0 ] = velocity.x * 8; + pm->s.velocity[ 1 ] = velocity.y * 8; + pm->s.velocity[ 2 ] = velocity.z * 8; + + if ( !vehicle && memcmp( &old_pmove, &pm->s, sizeof( pm->s ) ) ) + { + pm->snapinitial = true; + } + + pm->cmd = *ucmd; + + pm->trace = PM_trace; // adds default parms + pm->pointcontents = gi.pointcontents; + } + +EXPORT_FROM_DLL pmtype_t Player::GetMovePlayerMoveType + ( + void + ) + + { + if ( level.playerfrozen || sentientFrozen ) + { + return PM_FREEZE; + } + else if ( viewmode == CAMERA_VIEW ) + { + return PM_LOCKVIEW; + } + else if ( vehicle ) + { + if ( zoom_mode == ZOOMED_IN ) + { + return PM_INVEHICLE_ZOOM; + } + else + { + return PM_INVEHICLE; + } + } + else if ( ( getMoveType() == MOVETYPE_NOCLIP ) || ( spectator ) ) + { + return PM_SPECTATOR; + } + else if ( deadflag ) + { + return PM_DEAD; + } + else if ( zoom_mode == ZOOMED_IN ) + { + return PM_ZOOM; + } + else if ( ( viewmode == THIRD_PERSON ) && ( !gravaxis ) ) + { + return PM_LOCKVIEW; + } + else + { + return PM_NORMAL; + } + } + +EXPORT_FROM_DLL void Player::ClientMove + ( + usercmd_t *ucmd + ) + + { + pmove_t pm; + + client->ps.pmove.pm_type = GetMovePlayerMoveType(); + if ( + ( client->ps.pmove.pm_type == PM_LOCKVIEW ) || + ( client->ps.pmove.pm_type == PM_INVEHICLE ) || + ( client->ps.pmove.pm_type == PM_INVEHICLE_ZOOM ) || + ( client->ps.pmove.pm_type == PM_FREEZE ) || + ( client->ps.pmove.pm_type == PM_DEAD ) + ) + client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + else + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + + if ( flags & FL_MUTANT ) + client->ps.pmove.pm_flags |= PMF_MUTANT; + else + client->ps.pmove.pm_flags &= ~PMF_MUTANT; + + if ( flags & FL_ADRENALINE ) + client->ps.pmove.pm_flags |= PMF_ADRENALINE; + else + client->ps.pmove.pm_flags &= ~PMF_ADRENALINE; + + if ( level.airclamp ) + client->ps.pmove.pm_flags &= ~PMF_NOAIRCLAMP; + else + client->ps.pmove.pm_flags |= PMF_NOAIRCLAMP; + + edict->s.effects &= ~EF_NOFOOTSTEPS; + if ( !sv_footsteps->value ) + { + edict->s.effects |= EF_NOFOOTSTEPS; + } + + client->ps.pmove.gravity = sv_gravity->value * gravity; + SetMoveInfo( &pm, ucmd ); + + // perform a pmove + gi.Pmove( &pm ); + + if ( !deadflag && !level.playerfrozen ) + { + if ( waterlevel == 3 && falling) + { + Event * ev; + + ev = new Event( EV_Sentient_AnimLoop ); + ProcessEvent( ev ); + falling = false; + } + if ( groundentity && !pm.groundentity && ( pm.cmd.upmove >= 10 ) && ( waterlevel <= 1 ) ) + { + TempAnim( "jump", NULL ); + falling = true; + } + if ( !groundentity && pm.groundentity && ( ( pm.s.velocity[ 2 ] < -1600 ) || falling ) ) + { + falling = false; + if ( waterlevel <= 2 ) + { + TempAnim( "land", NULL ); + } + } + if ( ( !onladder ) && !pm.groundentity && ( velocity[ 2 ] < -200 ) && ( pm.s.velocity[ 2 ] > -2000 ) ) + { + if ( !waterlevel ) + { + TempAnim( "fall", NULL ); + falling = true; + } + } + } + + // save results of pmove + GetMoveInfo( &pm ); + + TouchStuff( &pm ); + if ( !level.playerfrozen ) + CheckButtons(); + if ( whereami->value ) + { + static Vector last; + if ( worldorigin != last ) + { + last = worldorigin; + gi.dprintf("x %8.2f y%8.2f z %8.2f area %2d\n", worldorigin[0], worldorigin[1], worldorigin[2], edict->areanum ); + } + } + } + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame. +============== +*/ +EXPORT_FROM_DLL void Player::ClientThink + ( + Event *ev + ) + + { + if ( !has_thought ) + { + // let any threads waiting on us know they can go ahead + Director.PlayerSpawned(); + has_thought = true; + } + + new_buttons = current_ucmd->buttons & ~buttons; + buttons = current_ucmd->buttons; + + // set the current gravity axis + client->ps.pmove.gravity_axis = gravaxis; + + // save light level the player is standing on for + // monster sighting AI + light_level = current_ucmd->lightlevel; + + if ( level.intermissiontime ) + { + client->ps.pmove.pm_type = PM_FREEZE; + + // can exit intermission after five seconds + if ( level.time > level.intermissiontime + 5.0 && ( new_buttons & BUTTON_ANY ) ) + { + level.exitintermission = true; + } + + // Save cmd angles so that we can get delta angle movements next frame + client->resp.cmd_angles[ 0 ] = SHORT2ANGLE( current_ucmd->angles[ 0 ] ); + client->resp.cmd_angles[ 1 ] = SHORT2ANGLE( current_ucmd->angles[ 1 ] ); + client->resp.cmd_angles[ 2 ] = SHORT2ANGLE( current_ucmd->angles[ 2 ] ); + + return; + } + if ( current_ucmd->debris ) + { + float mult; + mult = SURFACE_DamageMultiplier( current_ucmd->debris << SURF_START_BIT ); + mult *= 2; + if ( mult > 0 ) + { + Damage( world, world, ( int )mult, worldorigin, vec_zero, vec_zero, 0, 0, MOD_DEBRIS, -1, -1, 1.0f ); + } + } + if ( + ( client->ps.pmove.pm_flags & PMF_TIME_TELEPORT ) && + ( savedfov ) && + ( client->ps.pmove.pm_time < 95 ) + ) + { + Event * ev; + + ev = new Event( EV_Player_RestoreFov ); + ProcessEvent( ev ); + } + + if ( !vehicle || !vehicle->Drive( current_ucmd ) ) + { + short temporigin[ 3 ]; + + if ( watchCamera != this ) + { + // + // we save off the origin so that camera's origins are not messed up + // + temporigin[ 0 ] = client->ps.pmove.origin[ 0 ]; + temporigin[ 1 ] = client->ps.pmove.origin[ 1 ]; + temporigin[ 2 ] = client->ps.pmove.origin[ 2 ]; + + ClientMove( current_ucmd ); + + client->ps.pmove.origin[ 0 ] = temporigin[ 0 ]; + client->ps.pmove.origin[ 1 ] = temporigin[ 1 ]; + client->ps.pmove.origin[ 2 ] = temporigin[ 2 ]; + } + else + { + ClientMove( current_ucmd ); + } + + } + else + { + CheckWater(); + if ( !level.playerfrozen ) + CheckButtons(); + } + + // If we're dying, check for respawn + + assert( !( deadflag && deadflag != DEAD_DEAD && !animating ) ); + + if ( ( deadflag == DEAD_DEAD && ( level.time > respawn_time ) ) || + ( deadflag && !animating ) ) + { + if ( ( deathmatch->value ) && ( !checked_dead_camera ) && ( level.time > ( respawn_time + 10 ) ) ) + { + int num; + + checked_dead_camera = true; + // Find the end node + num = G_FindTarget( 0, "endnode1" ); + + if ( num && thirdpersonCamera ) + { + Entity * path; + Event * event; + + event = new Event( EV_Camera_NormalAngles ); + thirdpersonCamera->ProcessEvent( event ); + event = new Event( EV_Camera_Orbit); + path = G_GetEntity( num ); + event->AddEntity( path ); + thirdpersonCamera->ProcessEvent( event ); + event = new Event( EV_Camera_JumpCut ); + thirdpersonCamera->ProcessEvent( event ); + } + } + // wait for any button just going down + if ( new_buttons || ( DM_FLAG( DF_FORCE_RESPAWN ) ) ) + { + ProcessEvent( EV_Player_Respawn ); + } + } + + // + // check to see if we want to get out of a cinematic or camera + // + if ( + ( viewmode == CAMERA_VIEW ) && + ( + ( buttons & BUTTON_ATTACK ) || + ( buttons & BUTTON_USE ) || + ( abs( current_ucmd->forwardmove ) >= 200 ) || + ( abs( current_ucmd->sidemove ) >= 200 ) || + ( current_ucmd->upmove > 0 ) + ) + ) + { + if ( level.cinematic ) + { + if ( + ( buttons & BUTTON_ATTACK ) || + ( buttons & BUTTON_USE ) || + ( current_ucmd->upmove > 0 ) + ) + { + if ( world->skipthread.length() > 1 ) + { + ExecuteThread( world->skipthread ); + } + } + } + else + { + if ( !( buttons & BUTTON_USE ) && + !( buttons & BUTTON_ATTACK ) && + !( trappedInQuantum ) && + !( in_console ) + ) + { + SetCamera( NULL ); + } + } + } + + // Save cmd angles so that we can get delta angle movements next frame + client->resp.cmd_angles[ 0 ] = SHORT2ANGLE( current_ucmd->angles[ 0 ] ); + client->resp.cmd_angles[ 1 ] = SHORT2ANGLE( current_ucmd->angles[ 1 ] ); + client->resp.cmd_angles[ 2 ] = SHORT2ANGLE( current_ucmd->angles[ 2 ] ); + } + +void Player::EventUseItem + ( + Event *ev + ) + + { + const char *name; + + name = ev->GetString( 1 ); + assert( name ); + + if ( deadflag ) + return; + + if ( flags & ( FL_MUTANT|FL_SP_MUTANT ) ) + return; + + if ( currentWeapon && !currentWeapon->ChangingWeapons() ) + { + if ( !Q_stricmp( name, currentWeapon->getClassname() ) ) + { + Event *event; + event = new Event(EV_Weapon_SecondaryUse); + event->AddEntity(this); + currentWeapon->ProcessEvent(event); + return; + } + } + + ProcessEvent( EV_Player_ZoomOut ); + + if ( checkInheritance( &Weapon::ClassInfo, name ) ) + useWeapon( name ); + } + +void Player::EventUseInventoryItem + ( + Event *ev + ) + + { + if ( deadflag ) + return; + + if ( !currentItem ) + return; + + if ( flags & ( FL_MUTANT|FL_SP_MUTANT ) ) + return; + + currentItem->ProcessEvent(EV_InventoryItem_Use); + } + +Weapon *Player::useWeapon + ( + const char *weaponname + ) + + { + if ( vehicle && vehicle->HasWeapon() ) + return NULL; + else + return Sentient::useWeapon( weaponname ); + } + +void Player::Take + ( + Event *ev + ) + + { + const char *name; + + name = ev->GetString( 1 ); + + assert( name ); + + if ( deadflag ) + { + return; + } + + if ( !stricmp( name, "all" ) ) + { + if ( currentWeapon ) + currentWeapon->DetachFromOwner(); + + SetCurrentWeapon( NULL ); + FreeInventoryOfType("Weapon"); + giveWeapon( "Fists" ); + giveWeapon( "SpiderMine" ); + } + else if ( checkInheritance( &Weapon::ClassInfo, name ) ) + { + takeWeapon( name ); + } + else if ( checkInheritance( &Ammo::ClassInfo, name ) ) + { + EventTakeAmmo( ev ); + } + else if ( checkInheritance( &Item::ClassInfo, name ) ) + { + EventTakeItem( ev ); + } + else + { + gi.cprintf( edict, PRINT_HIGH, "Unknown take object : %s\n", name ); + } + } + +void Player::GiveCheat + ( + Event *ev + ) + + { + const char *name; + + name = ev->GetString( 1 ); + + assert( name ); + + if ( deadflag ) + { + return; + } + + if ( checkInheritance( &Weapon::ClassInfo, name ) ) + { + giveWeapon( name ); + } + else if ( checkInheritance( &Ammo::ClassInfo, name ) ) + { + EventGiveAmmo( ev ); + } + else if ( checkInheritance( &InventoryItem::ClassInfo, name ) ) + { + if (ev->NumArgs() != 2) + { + gi.cprintf( edict, PRINT_HIGH, "Usage: give \n"); + return; + } + EventGiveInventoryItem( ev ); + } + else if ( checkInheritance( &Item::ClassInfo, name ) ) + { + EventGiveItem( ev ); + } + } + +void Player::GiveAllCheat + ( + Event *ev + ) + + { + if ( deadflag ) + { + return; + } + +#ifdef SIN_DEMO + giveWeapon( "Fists" ); + giveWeapon( "RocketLauncher" ); + giveWeapon( "Magnum" ); + giveWeapon( "SniperRifle" ); + giveWeapon( "AssaultRifle" ); + giveWeapon( "Shotgun" ); +#else + giveWeapon( "Fists" ); + giveWeapon( "RocketLauncher" ); + giveWeapon( "Magnum" ); + giveWeapon( "SniperRifle" ); + giveWeapon( "AssaultRifle" ); + giveWeapon( "ChainGun" ); + giveWeapon( "PulseRifle" ); + giveWeapon( "Shotgun" ); + giveWeapon( "SpearGun" ); + giveWeapon( "SpiderMine" ); + giveWeapon( "MutantHands" ); + giveWeapon( "QuantumDestabilizer" ); +#endif + giveItem( "RiotHelmet", 100); + giveItem( "FlakJacket", 100); + giveItem( "FlakPants", 100); + } + +void Player::GodCheat + ( + Event *ev + ) + + { + const char *msg; + + if ( ev->NumArgs() > 0 ) + { + if ( ev->GetInteger( 1 ) ) + { + flags |= FL_GODMODE; + } + else + { + flags &= ~FL_GODMODE; + } + } + else + { + flags ^= FL_GODMODE; + } + + if ( ev->GetSource() == EV_FROM_CONSOLE ) + { + if ( !( flags & FL_GODMODE ) ) + { + msg = "godmode OFF\n"; + } + else + { + msg = "godmode ON\n"; + } + + gi.cprintf( edict, PRINT_HIGH, msg ); + } + } + +void Player::Kill + ( + Event *ev + ) + + { + if ( ( level.time - respawn_time ) < 5 ) + { + return; + } + + flags &= ~FL_GODMODE; + health = 0; + Damage( this, this, 10, worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_PROTECTION, MOD_SUICIDE, -1, -1, 1.0f ); + } + +void Player::NoTargetCheat + ( + Event *ev + ) + + { + const char *msg; + + flags ^= FL_NOTARGET; + if ( !( flags & FL_NOTARGET ) ) + { + msg = "notarget OFF\n"; + } + else + { + msg = "notarget ON\n"; + } + + gi.cprintf( edict, PRINT_HIGH, msg ); + } + +void Player::NoclipCheat + ( + Event *ev + ) + + { + const char *msg; + + if ( vehicle ) + { + msg = "Must exit vehicle first\n"; + } + else if ( getMoveType() == MOVETYPE_NOCLIP ) + { + setMoveType( MOVETYPE_WALK ); + msg = "noclip OFF\n"; + } + else + { + setMoveType( MOVETYPE_NOCLIP ); + msg = "noclip ON\n"; + } + + gi.cprintf( edict, PRINT_HIGH, msg ); + } + +void Player::SpawnEntity + ( + Event *ev + ) + + { + Entity *ent; + const char *name; + ClassDef *cls; + str text; + Vector forward; + Vector right; + Vector up; + Vector delta; + Vector v; + int n; + int i; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "Usage: spawn entityname [keyname] [value]..." ); + return; + } + + name = ev->GetString( 1 ); + if ( !name || !name[ 0 ] ) + { + ev->Error( "Must specify an entity name" ); + return; + } + + // create a new entity + G_InitSpawnArguments(); + + if ( name ) + { + cls = getClassForID( name ); + if ( !cls ) + { + cls = getClass( name ); + } + + if ( !cls ) + { + str n; + + n = name; + if ( !strstr( n.c_str(), ".def" ) ) + { + n += ".def"; + } + G_SetSpawnArg( "model", n.c_str() ); + } + else + { + G_SetSpawnArg( "classname", name ); + } + } + + angles.AngleVectors( &forward, &right, &up ); + v = worldorigin + ( forward + up ) * 80; + text = va( "%f %f %f", v[ 0 ], v[ 1 ], v[ 2 ] ); + G_SetSpawnArg( "origin", text.c_str() ); + + delta = worldorigin - v; + v = delta.toAngles(); + text = va( "%f", v[ 1 ] ); + G_SetSpawnArg( "angle", text.c_str() ); + + if ( ev->NumArgs() > 2 ) + { + n = ev->NumArgs(); + for( i = 2; i <= n; i += 2 ) + { + G_SetSpawnArg( ev->GetString( i ), ev->GetString( i + 1 ) ); + } + } + + cls = G_GetClassFromArgs(); + if ( !cls ) + { + ev->Error( "'%s' is not a valid entity name", name ); + return; + } + + ent = ( Entity * )cls->newInstance(); + + G_InitSpawnArguments(); + } + +void Player::SpawnActor + ( + Event *ev + ) + + { + Entity *ent; + str name; + str text; + Vector forward; + Vector right; + Vector up; + Vector delta; + Vector v; + int n; + int i; + ClassDef *cls; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "Usage: actor [modelname] [keyname] [value]..." ); + return; + } + + name = ev->GetString( 1 ); + if ( !name[ 0 ] ) + { + ev->Error( "Must specify a model name" ); + return; + } + + if ( !strstr( name.c_str(), ".def" ) ) + { + name += ".def"; + } + + // create a new entity + G_InitSpawnArguments(); + + G_SetSpawnArg( "model", name.c_str() ); + + angles.AngleVectors( &forward, &right, &up ); + v = worldorigin + ( forward + up ) * 80; + text = va( "%f %f %f", v[ 0 ], v[ 1 ], v[ 2 ] ); + G_SetSpawnArg( "origin", text.c_str() ); + + delta = worldorigin - v; + v = delta.toAngles(); + text = va( "%f", v[ 1 ] ); + G_SetSpawnArg( "angle", text.c_str() ); + + if ( ev->NumArgs() > 2 ) + { + n = ev->NumArgs(); + for( i = 2; i <= n; i += 2 ) + { + G_SetSpawnArg( ev->GetString( i ), ev->GetString( i + 1 ) ); + } + } + + cls = G_GetClassFromArgs(); + if ( !cls ) + { + G_SetSpawnArg( "model", name.c_str() ); + ent = new Actor; + } + else + { + ent = ( Entity * )cls->newInstance(); + } + + G_InitSpawnArguments(); + } + +void Player::EventPreviousWeapon + ( + Event *ev + ) + + { + if ( deadflag ) + { + return; + } + + if ( vehicle && vehicle->HasWeapon() ) + return; + + if ( flags & (FL_MUTANT|FL_SP_MUTANT) ) + return; + + // Cycle backwards through weapons + if ( currentWeapon && currentWeapon->AttackDone() && !currentWeapon->ChangingWeapons() ) + { + ProcessEvent( EV_Player_ZoomOut ); + ChangeWeapon( PreviousWeapon( currentWeapon ) ); + } + } + +void Player::EventNextWeapon + ( + Event *ev + ) + + { + if ( deadflag ) + { + return; + } + + if ( vehicle && vehicle->HasWeapon() ) + return; + + if ( flags & (FL_MUTANT|FL_SP_MUTANT) ) + return; + + // Cycle forwards through weapons + if ( currentWeapon && currentWeapon->AttackDone() && !currentWeapon->ChangingWeapons() ) + { + ProcessEvent( EV_Player_ZoomOut ); + ChangeWeapon( NextWeapon( currentWeapon ) ); + } + } + +void Player::EventNextItem + ( + Event *ev + ) + + { + Item *nextItem; + + if ( deadflag ) + return; + + nextItem = NextItem( currentItem ); + + if (nextItem) + currentItem = (InventoryItem *)nextItem; + } + +void Player::EventPreviousItem + ( + Event *ev + ) + + { + Item *prevItem; + + if ( deadflag ) + return; + + prevItem = PrevItem( currentItem ); + + if (prevItem) + currentItem = (InventoryItem *)prevItem; + } + +void Player::GameVersion + ( + Event *ev + ) + + { + gi.cprintf( edict, PRINT_HIGH, "%s : %s\n", GAMEVERSION, __DATE__ ); + } + +void Player::Fov + ( + Event *ev + ) + + { + if ( ev->NumArgs() < 1 ) + { + gi.cprintf( edict, PRINT_HIGH, "Fov = %d\n", fov ); + return; + } + + fov = ev->GetFloat( 1 ); + + if ( ( fov < 90 ) && DM_FLAG( DF_FIXED_FOV ) ) + { + fov = 90; + return; + } + + if ( fov < 1 ) + fov = 90; + else if ( fov > 160 ) + fov = 160; + } + + +void Player::SaveFov + ( + Event *ev + ) + + { + savedfov = fov; + } + +void Player::RestoreFov + ( + Event *ev + ) + + { + if ( savedfov ) + fov = savedfov; + savedfov = 0; + } + +void Player::ToggleViewMode + ( + Event *ev + ) + + { + if ( viewmode == CAMERA_VIEW || ( vehicle && !vehicle->IsDrivable() ) ) + { + return; + } + + // Spectators don't need 3rd person + if ( spectator ) + { + defaultViewMode = SPECTATOR; + SetViewMode( SPECTATOR ); + return; + } + + if ( defaultViewMode == FIRST_PERSON ) + { + defaultViewMode = THIRD_PERSON; + SetViewMode( THIRD_PERSON ); + } + else + { + defaultViewMode = FIRST_PERSON; + SetViewMode( FIRST_PERSON ); + } + } + + +/* +========================================================================= +Player::ToggleZoomMode - Plays a zoom sound, sets the layout to display a +crosshair and calls the FOV event for the player. +========================================================================= +*/ + +void Player::ToggleZoomMode + ( + Event *ev + ) + + { + if ( zoom_mode == ZOOMED_IN ) + { + zoom_mode = ZOOMED_OUT; + + fov = atof( Info_ValueForKey( client->pers.userinfo, "fov" ) ); + if ( fov < 1 ) + { + fov = 90; + } + else if ( fov > 160 ) + { + fov = 160; + } + + RandomGlobalSound( "scope_zoomout", 1, CHAN_VOICE, ATTN_NORM ); + ProcessEvent( EV_MovementSound ); + } + else + { + RandomGlobalSound( "scope_zoomin", 1, CHAN_VOICE, ATTN_NORM ); + ProcessEvent( EV_MovementSound ); + zoom_mode = ZOOMED_IN; + fov = 20; + } + } + +void Player::ZoomOut + ( + Event *ev + ) + + { + if ( zoom_mode == ZOOMED_IN ) + { + zoom_mode = ZOOMED_OUT; + fov = atof( Info_ValueForKey( client->pers.userinfo, "fov" ) ); + if ( fov < 1 ) + { + fov = 90; + } + else if ( fov > 160 ) + { + fov = 160; + } + } + } + +void Player::EnterConsole + ( + Event *ev + ) + { + velocity = vec_zero; + + in_console = true; + con_name = ev->GetString(1); + + // Create a console variable with the entnum of this player + // who used it. + + Director.CreateConsoleUser(con_name.c_str(), entnum); + } + +void Player::ExitConsole + ( + Event *ev + ) + { + ProcessEvent(EV_KickFromConsole); + in_console = false; + con_name = ""; + SetCamera( NULL ); + + showModel(); + hidestats = false; + } + +void Player::KickConsole + ( + Event *ev + ) + { + char msg[ MAX_MSGLEN ]; + + in_console = false; + + // Send a message to the client to kick us out of this console. + if ( !consoleManager.ConsoleExists( con_name.c_str() ) ) + { + Event event = new Event( ev ); + PostEvent(event,1.0f); + return; + } + + sprintf( msg, "pku %s", con_name.c_str() ); + gi.WriteByte (svc_console_command); + gi.WriteString (msg); + gi.unicast (edict, true); + } + + +/* +=============== +CalcRoll + +=============== +*/ +float Player::CalcRoll + ( + void + ) + + { + float sign; + float side; + float value; + Vector r; + + angles.AngleVectors( NULL, &r, NULL ); + side = velocity * r; + sign = side < 0 ? -4 : 4; + side = fabs( side ); + + value = sv_rollangle->value; + + if ( side < sv_rollspeed->value ) + { + side = side * value / sv_rollspeed->value; + } + else + { + side = value; + } + + return side * sign; + } + +/* +=============== +CalcViewOffset + +Auto pitching on slopes? + + fall from 128: 400 = 160000 + fall from 256: 580 = 336400 + fall from 384: 720 = 518400 + fall from 512: 800 = 640000 + fall from 640: 960 = + + damage = deltavelocity*deltavelocity * 0.0001 + +=============== +*/ + +void Player::CalcViewOffset + ( + void + ) + + { + float bob; + float ratio; + float pitch; + float roll; + float delta; + Vector forward; + Vector right; + Vector ang; + + // Initialize kick for this frame + v_kick = vec_zero; + + // don't add any kick when dead + if ( !deadflag ) + { + // add angles based on weapon kick + v_kick = kick_angles; + + // add angles based on damage kick + ratio = ( v_dmg_time - level.time ) / DAMAGE_TIME; + if ( ratio < 0 ) + { + ratio = 0; + v_dmg_pitch = 0; + v_dmg_roll = 0; + } + + pitch = v_kick.pitch() + ratio * v_dmg_pitch; + roll = v_kick.roll() + ratio * v_dmg_roll; + + // add pitch based on fall kick + + ratio = ( fall_time - level.time ) / FALL_TIME; + if ( ratio < 0 ) + { + ratio = 0; + } + pitch += ratio * fall_value; + + // calculate forward and right based upon yaw + ang[ 1 ] = v_angle[ 1 ]; + ang.AngleVectors( &forward, &right, NULL ); + + //FIXME: need to handle alternate gravity axis + // add pitch based on forward velocity + delta = velocity * forward; + pitch += delta * run_pitch->value; + + // add roll based on side velocity + delta = -( velocity * right ); + roll += delta * run_pitch->value; + + // add angles based on bob + delta = bobfracsin * bob_pitch->value * xyspeed; + if ( ( waterlevel <= 1 ) && ( client->ps.pmove.pm_flags & PMF_DUCKED ) ) + { + delta *= 6; // crouching + } + pitch += delta; + delta = bobfracsin * bob_roll->value * xyspeed; + if ( ( waterlevel <= 1 ) && ( client->ps.pmove.pm_flags & PMF_DUCKED ) ) + { + delta *= 6; // crouching + } + if ( bobcycle & 1 ) + { + delta = -delta; + } + roll += delta; + + v_kick.setPitch( pitch ); + v_kick.setRoll( roll ); + } + + // add fall height + ratio = ( fall_time - level.time ) / FALL_TIME; + if ( ratio < 0 ) + { + ratio = 0; + } + + v_offset = Vector( 0, 0, viewheight - ratio * fall_value * 0.4 ); + + // add bob height + bob = bobfracsin * xyspeed * bob_up->value; + if ( bob > 6 ) + { + bob = 6; + } + + //gi.DebugGraph (bob *2, 255); + v_offset.z += bob; + + // add kick offset + v_offset += kick_origin; + + // absolutely bound offsets + // so the view can never be outside the player box + if ( v_offset[ 0 ] < -14 ) + { + v_offset[ 0 ] = -14; + } + else if ( v_offset[ 0 ] > 14 ) + { + v_offset[ 0 ] = 14; + } + + if ( v_offset[ 1 ] < -14 ) + { + v_offset[ 1 ] = -14; + } + else if ( v_offset[ 1 ] > 14 ) + { + v_offset[ 1 ] = 14; + } + + if ( v_offset[ 2 ] < 0 ) + { + v_offset[ 2 ] = 0; + } + else if ( v_offset[ 2 ] > STAND_HEIGHT - 2 ) + { + v_offset[ 2 ] = STAND_HEIGHT - 2; + } + + // clear weapon kicks + kick_origin = vec_zero; + kick_angles = vec_zero; + } + +/* +============== +CalcGunOffset +============== +*/ +void Player::CalcGunOffset + ( + void + ) + + { + int i; + float delta; + + // gun angles from bobbing + v_gunangles[ ROLL ] = xyspeed * bobfracsin * 0.005; + v_gunangles[ YAW ] = xyspeed * bobfracsin * 0.01; + if ( bobcycle & 1 ) + { + v_gunangles[ ROLL ] = -v_gunangles[ ROLL ]; + v_gunangles[ YAW ] = -v_gunangles[ YAW ]; + } + + v_gunangles[ PITCH ] = xyspeed * bobfracsin * 0.005; + + // gun angles from delta movement + for( i = 0; i < 3; i++ ) + { + delta = oldviewangles[ i ] - v_angle[ i ]; + if ( delta > 180 ) + { + delta -= 360; + } + if ( delta < -180 ) + { + delta += 360; + } + if ( delta > 45 ) + { + delta = 45; + } + if ( delta < -45 ) + { + delta = -45; + } + if ( i == YAW ) + { + v_gunangles[ ROLL ] += 0.1 * delta; + } + v_gunangles[ i ] += 0.2 * delta; + } + + oldviewangles = v_angle; + + // gun height + VectorClear( v_gunoffset ); + + // gun_x / gun_y / gun_z are development tools + for( i = 0; i < 3; i++ ) + { + v_gunoffset[ i ] += orientation[ 0 ][ i ] * gun_y->value; + v_gunoffset[ i ] += orientation[ 1 ][ i ] * gun_x->value; + v_gunoffset[ i ] += orientation[ 2 ][ i ] * -gun_z->value; + } + // + // put gun offset and angles into client playerstate + // + client->ps.gunoffset[ 0 ] = v_gunoffset[0]; + client->ps.gunoffset[ 1 ] = v_gunoffset[1]; + client->ps.gunoffset[ 2 ] = v_gunoffset[2]; + + client->ps.gunangles[ 0 ] = v_gunangles[0]; + client->ps.gunangles[ 1 ] = v_gunangles[1]; + client->ps.gunangles[ 2 ] = v_gunangles[2]; + + if ( currentWeapon ) + { + currentWeapon->SetGravityAxis( gravaxis ); + } + } + +void Player::GravityNodes + ( + void + ) + + { + Vector grav,gravnorm,velnorm; + float dot; + qboolean force; + + // + // Check for gravity pulling points + // + + // no pull on ladders or during waterjumps + if ( onladder || ( client->ps.pmove.pm_flags & PMF_TIME_WATERJUMP ) ) + { + return; + } + + grav = gravPathManager.CalculateGravityPull( *this, worldorigin, &force ); + + // Check for unfightable gravity. + if ( force && grav != vec_zero ) + { + velnorm = velocity; + velnorm.normalize(); + + gravnorm = grav; + gravnorm.normalize(); + + dot = gravnorm.x * velnorm.x + gravnorm.y * velnorm.y + gravnorm.z * velnorm.z; + + // This prevents the player from trying to swim upstream + if ( dot < 0 ) + { + float tempdot; + Vector tempvec; + + tempdot = 0.2f - dot; + tempvec = velocity * tempdot; + velocity = velocity - tempvec; + } + } + +#if 0 + OutputDebugString(va("DOT : %f\n", dot)); + OutputDebugString(va("GRAV: %f %f %f\n", grav.x, grav.y, grav.z)); + OutputDebugString(va("VELN: %f %f %f\n", velnorm.x,velnorm.y,velnorm.z)); + OutputDebugString(va("GRVN: %f %f %f\n", gravnorm.x,gravnorm.y,gravnorm.z)); +#endif + velocity = velocity + grav; + } + +/* +============= +CheckWater +============= +*/ +void Player::CheckWater + ( + void + ) + + { + Vector point; + int cont; + int sample1; + int sample2; + const gravityaxis_t &grav = gravity_axis[ gravaxis ]; + + unlink(); + if ( vehicle ) + { + vehicle->unlink(); + } + + // + // get waterlevel, accounting for ducking + // + waterlevel = 0; + watertype = 0; + + sample2 = viewheight - mins[ grav.z ]; + sample1 = sample2 / 2; + + point[ grav.x ] = worldorigin[ grav.x ]; + point[ grav.y ] = worldorigin[ grav.y ]; + point[ grav.z ] = worldorigin[ grav.z ] + mins[ grav.z ] + grav.sign; + cont = gi.pointcontents( point.vec3() ); + + if ( cont & MASK_WATER ) + { + watertype = cont; + waterlevel = 1; + point[ grav.z ] = worldorigin[ grav.z ] + mins[ grav.z ] + sample1; + cont = gi.pointcontents( point.vec3() ); + if ( cont & MASK_WATER ) + { + waterlevel = 2; + point[ grav.z ] = worldorigin[ grav.z ] + mins[ grav.z ] + sample2; + cont = gi.pointcontents( point.vec3() ); + if ( cont & MASK_WATER ) + { + waterlevel = 3; + } + } + } + + link(); + if ( vehicle ) + { + vehicle->link(); + } + } + +/* +============= +WorldEffects +============= +*/ +void Player::WorldEffects + ( + void + ) + + { + if ( movetype == MOVETYPE_NOCLIP ) + { + // don't need air + air_finished = level.time + 20; + return; + } + + // + // Check for earthquakes + // + if ( groundentity && ( level.earthquake > level.time ) ) + { + velocity += Vector(EARTHQUAKE_STRENGTH*G_CRandom(),EARTHQUAKE_STRENGTH*G_CRandom(),fabs(150*G_CRandom())); + } + + // + // if just entered a water volume, play a sound + // + if ( !old_waterlevel && waterlevel ) + { + if ( watertype & CONTENTS_LAVA ) + { + RandomSound( "snd_burn", 1, CHAN_BODY, ATTN_NORM ); + ProcessEvent( EV_MovementSound ); + } + else if ( ( watertype & CONTENTS_WATER ) && waterlevel < 3) + { + RandomGlobalSound( "impact_playersplash", 1, CHAN_BODY, ATTN_NORM ); + ProcessEvent( EV_MovementSound ); + } + + flags |= FL_INWATER; + } + + // + // if just completely exited a water volume, play a sound + // + if ( old_waterlevel && !waterlevel ) + { + RandomGlobalSound( "impact_playerleavewater", 1, CHAN_BODY, ATTN_NORM ); + ProcessEvent( EV_MovementSound ); + flags &= ~FL_INWATER; + } + + // + // check for head just going under water + // + if ( old_waterlevel && old_waterlevel != 3 && waterlevel == 3 ) + { + RandomGlobalSound( "impact_playersubmerge", 1, CHAN_BODY, ATTN_NORM ); + ProcessEvent( EV_MovementSound ); + } + + // + // check for head just coming out of water + // + if ( old_waterlevel == 3 && waterlevel != 3 ) + { + if ( air_finished < level.time ) + { + // gasp for air + RandomSound( "snd_gasp", 1, CHAN_VOICE, ATTN_NORM ); + ProcessEvent( EV_VoiceSound ); + } + else if ( air_finished < level.time + 11 ) + { + // just break surface + RandomSound( "snd_gasp", 1, CHAN_VOICE, ATTN_NORM ); + ProcessEvent( EV_VoiceSound ); + } + } + + // + // check for lava + // + if ( watertype & CONTENTS_LAVA ) + { + if ( next_drown_time < level.time ) + { + next_drown_time = level.time + 0.2f; + Damage( world, world, 10 * waterlevel, worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_LAVA, -1, -1, 1.0f ); + } + } + + // + // check for drowning + // + if (waterlevel == 3) + { + // if out of air, start drowning + if ( ( air_finished < level.time ) && !( flags & FL_OXYGEN ) && !( flags & FL_GODMODE ) ) + { + // drown! + if ( next_drown_time < level.time && health > 0 ) + { + next_drown_time = level.time + 1; + + // take more damage the longer underwater + drown_damage += 2; + if ( drown_damage > 15 ) + { + drown_damage = 15; + } + + // play a gurp sound instead of a normal pain sound + if ( health <= drown_damage ) + { + RandomSound( "snd_drown", 1, CHAN_VOICE, ATTN_NORM ); + ProcessEvent( EV_PainSound ); + } + else if ( rand() & 1 ) + { + RandomSound( "snd_choke", 1, CHAN_VOICE, ATTN_NORM ); + ProcessEvent( EV_PainSound ); + } + else + { + RandomSound( "snd_choke", 1, CHAN_VOICE, ATTN_NORM ); + ProcessEvent( EV_PainSound ); + } + + Damage( world, world, drown_damage, worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_DROWN, -1, -1, 1.0f ); + } + } + } + else + { + air_finished = level.time + 20; + drown_damage = 2; + } + + GravityNodes(); + + old_waterlevel = waterlevel; + } + +/* +============= +AddBlend +============= +*/ +void Player::AddBlend + ( + float r, + float g, + float b, + float a + ) + + { + float a2; + float a3; + + if ( a <= 0 ) + { + return; + } + + // new total alpha + a2 = blend[ 3 ] + ( 1 - blend[ 3 ] ) * a; + + // fraction of color from old + a3 = blend[ 3 ] / a2; + + blend[ 0 ] = blend[ 0 ] * a3 + r * ( 1 - a3 ); + blend[ 1 ] = blend[ 1 ] * a3 + g * ( 1 - a3 ); + blend[ 2 ] = blend[ 2 ] * a3 + b * ( 1 - a3 ); + blend[ 3 ] = a2; + } + +/* +============= +CalcBlend +============= +*/ +void Player::CalcBlend + ( + void + ) + + { + int contents; + Vector vieworg; + const gravityaxis_t &grav = gravity_axis[ gravaxis ]; + + blend[ 0 ] = blend[ 1 ] = blend[ 2 ] = blend[ 3 ] = 0; + + // add for contents + vieworg[ grav.x ] = worldorigin[ grav.x ] + v_offset[ 0 ]; + vieworg[ grav.y ] = worldorigin[ grav.y ] + v_offset[ 1 ] * grav.sign; + vieworg[ grav.z ] = worldorigin[ grav.z ] + v_offset[ 2 ] * grav.sign; + + contents = gi.pointcontents( vieworg.vec3() ); + + if ( contents & CONTENTS_SOLID ) + { + // Outside of world + AddBlend( 0.8, 0.5, 0.0, 0.2 ); + } + else if ( contents & CONTENTS_LAVA ) + { + AddBlend( level.lava_color[0], level.lava_color[1], level.lava_color[2], level.lava_alpha ); + } + else if ( contents & CONTENTS_WATER ) + { + AddBlend( level.water_color[0], level.water_color[1], level.water_color[2], level.water_alpha ); + } + + if ( contents & CONTENTS_LIGHTVOLUME ) + { + AddBlend( level.lightvolume_color[0], level.lightvolume_color[1], level.lightvolume_color[2], level.lightvolume_alpha ); + } + + // Flashes + AddBlend( flash_color[0], flash_color[1], flash_color[2], flash_color[3] ); + + // add for zoom + if ( zoom_mode == ZOOMED_IN ) + { + AddBlend( 0.4, 1, 0.1, 0.1 ); + } + + // add for damage + if ( damage_alpha > 0 ) + { + AddBlend( damage_blend[ 0 ], damage_blend[ 1 ], damage_blend[ 2 ], damage_alpha ); + } + + if ( flags & FL_ADRENALINE ) + { + AddBlend( 1.0f, 1.0f, 0.0f, 0.2f ); + } + + // drop the damage value + damage_alpha -= 0.06; + if ( damage_alpha < 0 ) + { + damage_alpha = 0; + } + + // Drop the flash + flash_color[3] -= 0.06; + if ( flash_color[3] < 0 ) + { + flash_color[3] = 0; + } + } + +/* +=============== +P_DamageFeedback + +Handles color blends and view kicks +=============== +*/ + +void Player::DamageFeedback + ( + void + ) + + { + float realcount; + float count; + + // flash the backgrounds behind the status numbers + client->ps.stats[ STAT_FLASHES ] = 0; + + if ( damage_blood ) + { + client->ps.stats[ STAT_FLASHES ] |= 1; + } + + // total points of damage shot at the player this frame + if ( !damage_blood ) + { + // didn't take any damage + return; + } + + count = damage_blood; + realcount = count; + if ( count < 10 ) + { + // always make a visible effect + count = 10; + } + + // the total alpha of the blend is always proportional to count + if ( damage_alpha < 0 ) + { + damage_alpha = 0; + } + + damage_alpha += count * 0.01; + if ( damage_alpha < 0.2 ) + { + damage_alpha = 0.2; + } + if ( damage_alpha > 0.6 ) + { + // don't go too saturated + damage_alpha = 0.6; + } + + // the color of the blend will vary based on how much was absorbed + // by different armors + damage_blend = vec_zero; + if ( damage_blood ) + { + damage_blend += ( damage_blood / realcount ) * bcolor; + } + + // + // clear totals + // + damage_blood = 0; + } + +EXPORT_FROM_DLL const char * Player::AnimPrefixForWeapon + ( + void + ) + + { + if ( currentWeapon ) + { + switch ( currentWeapon->GetType() ) + { + case WEAPON_MELEE: + return ""; + break; + case WEAPON_1HANDED: + return "1hand_"; + break; + case WEAPON_2HANDED_HI: + return "hi2hand_"; + break; + case WEAPON_2HANDED_LO: + return "lo2hand_"; + break; + default: + return ""; + break; + } + } + else + { + return ""; + } + return ""; + } + +EXPORT_FROM_DLL void Player::ChooseAnim + ( + void + ) + + { + str prefix; + str aname; + + if ( deadflag ) + { + return; + } + + if ( vehicle ) + { + SetAnim( vehicleanim.c_str() ); + return; + } + + if ( onladder ) + { + if ( falling ) + { + Event * ev; + + ev = new Event( EV_Sentient_AnimLoop ); + ProcessEvent( ev ); + falling = false; + } + if ( fabs( velocity[ 2 ] ) > 0 ) + { + SetAnim( "climb" ); + } + else if (!animOverride) + { + StopAnimating(); + } + return; + } + + if ( client->ps.pmove.pm_flags & PMF_DUCKED ) + { + if ( !( old_pmove.pm_flags & PMF_DUCKED ) ) + { + SetAnim( "crouch" ); + return; + } + } + else if ( old_pmove.pm_flags & PMF_DUCKED ) + { + SetAnim( "uncrouch" ); + return; + } + // + // put the appropriate prefix on the animation + prefix = AnimPrefixForPlayer(); + + if ( currentWeapon ) + { + if ( currentWeapon->WeaponRaising() ) + { + aname = prefix + str( "readyweapon" ); + SetAnim( aname.c_str() ); + return; + } + else if ( currentWeapon->WeaponPuttingAway() ) + { + aname = prefix + str( "putaway" ); + SetAnim( aname.c_str() ); + return; + } + else if ( currentWeapon->Reloading() ) + { + aname = prefix + str( "reload" ); + SetAnim( aname.c_str() ); + return; + } + } + // + // append the prefix based on which weapon we are holding + // + prefix += str( AnimPrefixForWeapon() ); + + if ( waterlevel > 2 || (!groundentity && waterlevel ) ) + { + if ( xyspeed > 20 ) + { + aname = prefix + str( "run" ); + } + else + { + aname = prefix + str( "idle" ); + } + } + else + { + if ( xyspeed > 250 ) + { + aname = prefix + str( "run" ); + } + else if ( xyspeed > 20 ) + { + if ( client->ps.pmove.pm_flags & PMF_DUCKED ) + { + aname = prefix + str( "run" ); + } + else + { + aname = prefix + str( "walk" ); + } + } + else + { + aname = prefix + str( "idle" ); + } + } + + SetAnim( aname.c_str() ); + } + +void Player::SetCameraValues + ( + Vector position, + Vector cameraoffset, + Vector ang, + Vector camerakick, + Vector vel, + float camerablend[ 4 ], + float camerafov + ) + + { + client->ps.viewangles[ 0 ] = ang[ 0 ]; + client->ps.viewangles[ 1 ] = ang[ 1 ]; + client->ps.viewangles[ 2 ] = ang[ 2 ]; + + client->ps.viewoffset[ 0 ] = cameraoffset[ 0 ]; + client->ps.viewoffset[ 1 ] = cameraoffset[ 1 ]; + client->ps.viewoffset[ 2 ] = cameraoffset[ 2 ]; + + client->ps.pmove.origin[ 0 ] = position[ 0 ] * 8; + client->ps.pmove.origin[ 1 ] = position[ 1 ] * 8; + client->ps.pmove.origin[ 2 ] = position[ 2 ] * 8; + + client->ps.pmove.velocity[ 0 ] = vel[ 0 ] * 8.0; + client->ps.pmove.velocity[ 1 ] = vel[ 1 ] * 8.0; + client->ps.pmove.velocity[ 2 ] = vel[ 2 ] * 8.0; + + client->ps.blend[ 0 ] = camerablend[ 0 ]; + client->ps.blend[ 1 ] = camerablend[ 1 ]; + client->ps.blend[ 2 ] = camerablend[ 2 ]; + client->ps.blend[ 3 ] = camerablend[ 3 ]; + + client->ps.fov = camerafov; + + client->ps.kick_angles[ 0 ] = camerakick[ 0 ]; + client->ps.kick_angles[ 1 ] = camerakick[ 1 ]; + client->ps.kick_angles[ 2 ] = camerakick[ 2 ]; + } + +void Player::SetCameraEntity + ( + Entity *cameraEnt + ) + + { + assert( cameraEnt ); + + // In release, we should never be without a camera + if ( !cameraEnt ) + { + cameraEnt = this; + } + + // should we see the player's body? + if ( cameraEnt == this ) + { + edict->s.renderfx |= RF_VIEWERMODEL; + if ( currentWeapon ) + { + if ( !vehicle || vehicle->ShowWeapon() ) + { + currentWeapon->edict->s.renderfx &= ~RF_DONTDRAW; + currentWeapon->edict->s.renderfx |= RF_VIEWERMODEL; + } + else + { + currentWeapon->edict->s.renderfx |= RF_DONTDRAW; + } + } + if ( vehicle && vehicle->IsDrivable() ) + { + SetCameraValues( worldorigin, v_offset, v_angle, v_kick, velocity, blend, fov*1.25 ); + } + else + { + SetCameraValues( worldorigin, v_offset, v_angle, v_kick, velocity, blend, fov ); + } + } + else + { + float noblend[ 4 ]; + float camerafov; + Vector pos; + +/* + if ( vehicle && cameraEnt == thirdpersonCamera ) + { + Event * event; + + event = new Event( EV_Camera_SetDistance ); + event->AddFloat( 256 ); + cameraEnt->ProcessEvent( event ); + } +*/ + + edict->s.renderfx &= ~RF_VIEWERMODEL; + + if ( currentWeapon ) + { + // Take the weapon out of the view + currentWeapon->edict->s.renderfx &= ~RF_VIEWERMODEL; + } + + if ( in_console ) + { + // Make the client act like a viewmodel so it doesn't block + // the camera to the console. + edict->s.renderfx |= RF_VIEWERMODEL; + } + + // FIXME + // should add all these fields to entity or camera or whatever. + noblend[ 0 ] = 0; + noblend[ 1 ] = 0; + noblend[ 2 ] = 0; + noblend[ 3 ] = 0; + pos = vec_zero; + + camerafov = 90; + if ( cameraEnt->isSubclassOf( Mine ) ) + { + Vector dir; + vec3_t mat[3]; + + camerafov = ( ( Projectile * )cameraEnt )->fov; + + AnglesToMat( cameraEnt->worldangles.vec3(), mat ); + dir = mat[0]; + if ( cameraEnt->velocity == vec_zero ) + { + pos = dir * 30; + } + else + { + pos = pos + Vector(0,0,1) * 30; + } + } + else if ( cameraEnt->isSubclassOf( Camera ) ) + { + camerafov = ( ( Camera * )cameraEnt )->fov; + if ( cameraEnt->isSubclassOf( SecurityCamera ) ) + cameraEnt->edict->s.renderfx |= RF_DONTDRAW; + } + else if ( trappedInQuantum ) + { + Sentient *sent; + + if ( cameraEnt->isSubclassOf( Sentient ) ) + { + Vector dir; + + sent = ( Sentient * )cameraEnt; + sent->GetMuzzlePositionAndDirection( &pos, &dir ); + } + } + else if ( cameraEnt->isSubclassOf( Sentient ) ) + { + pos = ( ( Sentient * )cameraEnt )->EyePosition() - cameraEnt->worldorigin; + } + + SetCameraValues( cameraEnt->worldorigin, pos, cameraEnt->worldangles, vec_zero, + cameraEnt->velocity, noblend, camerafov ); + //SetCameraValues( "0 0 64", pos, "0 270 0", vec_zero, + // cameraEnt->velocity, noblend, camerafov ); + } + + if ( flags & FL_SHIELDS ) + { + edict->s.renderfx |= RF_ENVMAPPED; + } + else + edict->s.renderfx &= ~RF_ENVMAPPED; + + if ( flags & FL_CLOAK ) + { + setAlpha( 0 ); + } + else + setAlpha( 1.0f ); + + if ( currentWeapon ) + { + if ( flags & FL_SHIELDS ) + currentWeapon->edict->s.renderfx |= RF_ENVMAPPED; + else + currentWeapon->edict->s.renderfx &= ~RF_ENVMAPPED; + + if ( flags & FL_CLOAK ) + { + currentWeapon->edict->s.alpha = 0.1f; + currentWeapon->edict->s.renderfx |= RF_TRANSLUCENT; + } + else + { + currentWeapon->edict->s.alpha = 1.0f; + currentWeapon->edict->s.renderfx &= ~RF_TRANSLUCENT; + } + if ( cameraEnt != this ) + { + if ( edict->s.renderfx & RF_DONTDRAW ) + { + currentWeapon->edict->s.renderfx |= RF_DONTDRAW; + } + else + { + currentWeapon->edict->s.renderfx &= ~RF_DONTDRAW; + } + } + } + + if ( ( viewmode == THIRD_PERSON ) && crosshair && currentWeapon ) + { + Vector dir, src, end; + trace_t trace; + + currentWeapon->GetMuzzlePosition( &src, &dir ); + + end = src + dir * 8192; + trace = G_FullTrace( src, vec_zero, vec_zero, end, 5, this, MASK_SHOT, "Player::SetCameraEntity" ); + if ( trace.intersect.valid ) + { + crosshair->edict->s.frame = 1; + } + else + { + crosshair->edict->s.frame = 0; + } + + crosshair->setOrigin( trace.endpos - dir * 16 ); + crosshair->edict->s.scale = ( crosshair->worldorigin - worldorigin ).length() / 256; + } + } + +EXPORT_FROM_DLL void Player::CalcBob + ( + void + ) + + { + float time; + Vector hvel; + const gravityaxis_t &grav = gravity_axis[ gravaxis ]; + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + hvel[ grav.x ] = velocity[ grav.x ]; + hvel[ grav.y ] = velocity[ grav.y ]; + hvel[ grav.z ] = 0; + xyspeed = hvel.length(); + + if ( xyspeed < 5 ) + { + // start at beginning of cycle again + bobmove = 0; + bobtime = 0; + } + else if ( groundentity ) + { + // so bobbing only cycles when on ground + if ( xyspeed > 210 ) + { + bobmove = 0.25; + } + else if ( xyspeed > 100 ) + { + bobmove = 0.125; + } + else + { + bobmove = 0.0625; + } + } + + bobtime += bobmove; + time = bobtime; + if ( client->ps.pmove.pm_flags & PMF_DUCKED ) + { + time *= 4; + } + + bobcycle = ( int )time; + bobfracsin = fabs( sin( time * M_PI ) ); + } + +/* +================= +P_FallingDamage +================= +*/ +void Player::FallingDamage + ( + void + ) + + { + float delta; + float damage; + csurface_t *surf; + int z; + + z = gravity_axis[ gravaxis ].z; + + if ( deadflag ) + { + return; + } + + if ( flags & (FL_MUTANT|FL_SP_MUTANT|FL_ADRENALINE) ) + { + return; + } + + if ( getMoveType() == MOVETYPE_NOCLIP ) + { + return; + } + if ( !fallsurface ) + { + return; + } + surf = fallsurface; + fallsurface = NULL; + delta = velocity[ z ] - oldvelocity[ z ]; + + delta = delta * delta * 0.0001; + + // never take falling damage if completely underwater + switch( waterlevel ) + { + case 1 : + delta *= 0.5; + break; + + case 2 : + delta *= 0.25; + break; + + case 3 : + return; + break; + } + + if ( delta < 3 ) + { + return; + } + + fall_value = delta * 0.5; + if ( fall_value > 40 ) + { + fall_value = 40; + } + + fall_time = level.time + FALL_TIME; + + if ( delta > 30 ) + { + damage = ( delta - 30 ) / 2; + + if ( surf ) + { + // modify damage based on surface + switch ( surf->flags & MASK_SURF_TYPE ) + { + // Can this happen? + case SURF_TYPE_WATER: + break; + + // no damage + case SURF_TYPE_FABRIC: + damage *= 0; + break; + + // low damage + case SURF_TYPE_FLESH: + case SURF_TYPE_VEGETATION: + damage *= 0.5; + break; + + // medium damage + case SURF_TYPE_DIRT: + damage *= 0.6; + break; + + case SURF_TYPE_WOOD: + damage *= 0.8; + break; + + // full damage + case SURF_TYPE_GRAVEL: + case SURF_TYPE_GRILL: + case SURF_TYPE_METAL: + case SURF_TYPE_STONE: + case SURF_TYPE_CONCRETE: + case SURF_TYPE_DUCT: + default: + break; + } + } + + if ( surf && surf->frequency != 0 ) + { + damage = 0; + } + + if ( ( damage >= 1 ) && ( !DM_FLAG( DF_NO_FALLING ) ) ) + { + Damage( world, world, ( int )damage, worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_FALLING, -1, -1, 1.0f ); + } + } + if ( surf && surf->frequency != 0 ) + { + fall_time = 0; + fall_value = 0; + } + } + +EXPORT_FROM_DLL void Player::FinishMove + ( + void + ) + + { + Vector t[ 3 ]; + Vector forward; + Vector right; + Vector up; + Vector temp; + + if ( ai_createnodes->value ) + { + AddPathNodes(); + } + + // + // If the origin or velocity have changed since ClientThink(), + // update the pmove values. This will happen when the client + // is pushed by a bmodel or kicked by an explosion. + // + // If it wasn't updated here, the view position would lag a frame + // behind the body position when pushed -- "sinking into plats" + // + if ( + ( GetMovePlayerMoveType() != PM_LOCKVIEW ) && + ( GetMovePlayerMoveType() != PM_FREEZE ) + ) + { + client->ps.pmove.origin[ 0 ] = worldorigin.x * 8.0; + client->ps.pmove.origin[ 1 ] = worldorigin.y * 8.0; + client->ps.pmove.origin[ 2 ] = worldorigin.z * 8.0; + client->ps.pmove.velocity[ 0 ] = velocity.x * 8.0; + client->ps.pmove.velocity[ 1 ] = velocity.y * 8.0; + client->ps.pmove.velocity[ 2 ] = velocity.z * 8.0; + } + + // + // set model angles from view angles so other things in + // the world can tell which direction you are looking + // + if ( waterlevel >= 3 ) + { + angles[ PITCH ] = v_angle[ PITCH ]; + } + else if ( v_angle[ PITCH ] > 180 ) + { + angles[ PITCH ] = ( -360 + v_angle[ PITCH ] ) / 12; + } + else + { + angles[ PITCH ] = v_angle[ PITCH ] / 12; + } + + angles[ YAW ] = v_angle[ YAW ]; + angles[ ROLL ] = CalcRoll(); + + // Orient the angles to coincide with our special gravity vector + const gravityaxis_t &grav = gravity_axis[ gravaxis ]; + + angles.AngleVectors( &t[0], &t[1], &t[2] ); + forward[ grav.x ] = t[ 0 ][ 0 ]; + forward[ grav.y ] = t[ 0 ][ 1 ] * grav.sign; + forward[ grav.z ] = t[ 0 ][ 2 ] * grav.sign; + right[ grav.x ] = t[ 1 ][ 0 ]; + right[ grav.y ] = t[ 1 ][ 1 ] * grav.sign; + right[ grav.z ] = t[ 1 ][ 2 ] * grav.sign; + up[ grav.x ] = t[ 2 ][ 0 ]; + up[ grav.y ] = t[ 2 ][ 1 ] * grav.sign; + up[ grav.z ] = t[ 2 ][ 2 ] * grav.sign; + VectorsToEulerAngles( forward.vec3(), right.vec3(), up.vec3(), angles.vec3() ); + + if ( !level.playerfrozen ) + { + setAngles( angles ); + AnglesToMat( v_angle.vec3(), orientation ); + } + + CalcBob(); + + // check if we're over the limit health-wise + if ( ( (int)health > (int)max_health ) && ( ( float )( ( int )level.time ) == level.time ) ) + { + health -= 1; + } + + // mutant mode + if ( ( flags & FL_SP_MUTANT ) && !( flags & FL_GODMODE ) && ( ( float )( ( int )level.time ) == level.time ) ) + { + Damage( world, world, 1, worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_MUTANT_DRAIN, -1, -1, 1.0f ); + } + + // Check for silencer + if ( !(flags & FL_SILENCER) && FindItem( "Silencer" ) ) + { + flags |= FL_SILENCER; + } + + // Check for O2 + if ( !(flags & FL_OXYGEN) && FindItem( "ScubaGear" ) ) + { + flags |= FL_OXYGEN; + } + + // burn from lava, etc + WorldEffects(); + FallingDamage(); + + ChooseAnim(); + + // determine the view offsets + CalcViewOffset(); + CalcGunOffset(); + DamageFeedback(); + CalcBlend(); + + oldvelocity = velocity; + } + +EXPORT_FROM_DLL qboolean Player::CanMoveTo + ( + Vector pos + ) + + { + Vector min; + trace_t trace; + Vector start; + Vector end; + Vector s; + + s = Vector( 0, 0, 20 ); + start = worldorigin + s; + end = pos + s; + trace = G_Trace( start, mins, maxs, end, this, MASK_SOLID, "Player::CanMoveTo" ); + if ( trace.fraction == 1 ) + { + return true; + } + return false; + } + +EXPORT_FROM_DLL qboolean Player::ClearPathTo + ( + Vector pos + ) + + { + Vector dir; + Vector midpos; + Vector end_trace; + Vector delta; + Vector start; + Vector min; + Vector max; + trace_t trace; + float dist; + float t; + + min = Vector( -16, -16, 12 ); + max = Vector( 16, 16, 40 ); + start = worldorigin; + dir = pos - worldorigin; + + delta = dir; + delta[ 2 ] = 0; + if ( delta.length() >= PATHMAP_CELLSIZE ) + { + return false; + } + + if ( !CanMoveTo( pos ) ) + { + return false; + } + + // only do a full test when we train a level + if ( !ai_createnodes->value ) + { + midpos = start + dir * 0.5; + end_trace = midpos - Vector( 0, 0, 40 ); + + // check that the midpos is onground (down 28 units) + trace = G_Trace( midpos, min, max, end_trace, this, MASK_SOLID, "Player::ClearPathTo 1" ); + if ( trace.fraction == 1 ) + { + return false; + } + + return true; + } + + dist = dir.length(); + if ( dist < 32 ) + { + return true; + } + + dir[ 2 ] = 0; + dist = dir.length(); + dir.normalize(); + + min = Vector( -16, -16, 0 ); + // check the entire move + midpos = start; + for( t = 0; t < dist; t += 8 ) + { + midpos[ 0 ] = start[ 0 ] + t * dir[ 0 ]; + midpos[ 1 ] = start[ 1 ] + t * dir[ 1 ]; + midpos[ 2 ] += 18; + end_trace = midpos - Vector( 0, 0, 2048 );//140 );//128 ); + + trace = G_Trace( midpos, min, max, end_trace, this, MASK_SOLID, "Player::ClearPathTo 2" ); + if ( ( trace.fraction == 1 ) || ( trace.startsolid ) ) + { + return false; + } + + midpos = trace.endpos; + } + + // Check if we're close enough + delta = midpos - pos; + return ( delta.length() < 32 ); + } + +EXPORT_FROM_DLL void Player::AddPathNode + ( + Event *ev + ) + + { + if ( ai_createnodes->value ) + { + lastNode = new PathNode; + lastNode->Setup( worldorigin ); + } + } + +EXPORT_FROM_DLL void Player::AddPathNodes + ( + void + ) + + { + StandardMovePath find; + + if ( ai_createnodes->value && groundentity ) + { + if ( !lastNode || !lastNode->CheckMove( worldorigin, mins, maxs ) || + !ClearPathTo( lastNode->worldorigin ) ) + { + gi.dprintf( "Can't see lastNode\n" ); + lastNode = new PathNode; + lastNode->Setup( lastVisible ); + + // so that we don't just keep putting nodes in the same place + lastVisible = worldorigin; + } + + nearestNode = PathManager.NearestNode( worldorigin ); + if ( !nearestNode ) + { + gi.dprintf( "Can't see NearestNode\n" ); + lastNode = new PathNode; + lastNode->Setup( worldorigin ); + lastVisible = worldorigin; + } + else if ( lastNode && ( !lastNode->CheckPath( nearestNode, mins, maxs ) + || !nearestNode->CheckPath( lastNode, mins, maxs ) ) ) + { + gi.dprintf( "Nearest can't see last\n" ); + lastNode = new PathNode; + lastNode->Setup( worldorigin ); + lastVisible = worldorigin; + } + + if ( !lastNode || ( lastNode->CheckMove( worldorigin, mins, maxs ) && ClearPathTo( lastNode->worldorigin ) ) ) + { + lastVisible = worldorigin; + } + + if ( nearestNode ) + { + lastNode = nearestNode; + } + + //gi.dprintf( "Num Nodes %d\n", PathManager.NumNodes() ); + } + + if ( searchTime <= level.time ) + { + nearestNode = PathManager.NearestNode( worldorigin ); + + if ( path ) + { + delete path; + path = NULL; + } + + if ( ai_showpath->value ) + { + if ( nearestNode ) + { + if ( !goalNode ) + { + goalNode = nearestNode; + } + + find.heuristic.setSize( size ); + path = find.FindPath( nearestNode, goalNode ); + } + else if ( ai_debugpath->value ) + { + gi.dprintf( "%d : No nearest node\n", level.framenum ); + } + } + + searchTime = level.time + 0.1; + + if ( ai_showpath->value ) + { + if ( path ) + { + path->DrawPath( 0, 0.7, 0, 0.1 ); + } + + if ( nearestNode ) + { + G_DebugLine( nearestNode->worldorigin + "0 0 16", worldorigin + "0 0 16", 0, 0, 0.7, 1 ); + } + } + } + } + +EXPORT_FROM_DLL void Player::UpdateStats + ( + void + ) + + { + Ammo *ammo; + Armor *armor; + int i; + + // Current Ammo + if ( currentWeapon ) + { + client->ps.stats[ STAT_AMMO ] = currentWeapon->AmmoAvailable(); + client->ps.stats[ STAT_CLIPAMMO ] = currentWeapon->ClipAmmo(); + } + else + { + client->ps.stats[ STAT_AMMO ] = 0; + } + + // All ammo types + for( i = 0; i < NUM_AMMO_TYPES; i++ ) + { + assert( ammo_types[ i ] ); + ammo = ( Ammo * )FindItem( ammo_types[ i ] ); + if ( ammo ) + { + client->ps.stats[ STAT_AMMO_BASE + i ] = ammo->Amount(); + } + else + { + client->ps.stats[ STAT_AMMO_BASE + i ] = 0; + } + } + + // + // Armor + // + client->ps.stats[ STAT_ARMOR ] = 0; + + for( i = 0; i < NUM_ARMOR_TYPES; i++ ) + { + assert( armor_types[ i ] ); + armor = ( Armor * )FindItem( armor_types[ i ] ); + if ( armor ) + { + client->ps.stats[ STAT_ARMOR_BASE + i ] = armor->Amount(); + client->ps.stats[ STAT_ARMOR ] += armor->Amount(); + } + else + { + client->ps.stats[ STAT_ARMOR_BASE + i ] = 0; + } + } + + // Average the armor into a single value + client->ps.stats[ STAT_ARMOR ] /= 3; + + if ( currentWeapon ) + { + client->ps.stats[ STAT_CURRENT_WEAPON ] = currentWeapon->GetIconIndex(); + } + else + { + client->ps.stats[ STAT_CURRENT_WEAPON ] = 0; + } + + // + // Weapon list + // + client->ps.stats[ STAT_WEAPONLIST ] = 0; + + // + // Inventory + // + if ( currentItem && ( !( flags & ( FL_SP_MUTANT | FL_MUTANT ) ) ) ) + { + InventoryItem *nextItem; + InventoryItem *prevItem; + + nextItem = ( InventoryItem * )NextItem( currentItem ); + prevItem = ( InventoryItem * )PrevItem( currentItem ); + + client->ps.stats[ STAT_SELECTED_ICON ] = currentItem->GetIconIndex(); + client->ps.stats[ STAT_SELECTED_NAME ] = currentItem->GetItemIndex(); + client->ps.stats[ STAT_SELECTED_AMOUNT ] = currentItem->Amount(); + client->ps.stats[ STAT_SELECTED_MODELINDEX ] = currentItem->edict->s.modelindex; + + if ( prevItem ) + client->ps.stats[ STAT_PREVIOUS_ICON ] = prevItem->GetIconIndex(); + else + client->ps.stats[ STAT_PREVIOUS_ICON ] = 0; + + if ( nextItem ) + client->ps.stats[ STAT_NEXT_ICON ] = nextItem->GetIconIndex(); + else + client->ps.stats[ STAT_NEXT_ICON ] = 0; + } + else + { + client->ps.stats[ STAT_SELECTED_ICON ] = 0; + client->ps.stats[ STAT_PREVIOUS_ICON ] = 0; + client->ps.stats[ STAT_NEXT_ICON ] = 0; + client->ps.stats[ STAT_SELECTED_AMOUNT ] = 0; + } + + // + // Health + // + if ( ( health < 1 ) && ( health > 0 ) ) + client->ps.stats[ STAT_HEALTH ] = 1; + else + client->ps.stats[ STAT_HEALTH ] = health; + + // + // Frags + // + client->ps.stats[ STAT_FRAGS ] = client->resp.score; + + if ( spectator ) + { + client->ps.stats[ STAT_LAYOUTS ] = DRAW_SPECTATOR; + } + else + { + if ( !hidestats ) + client->ps.stats[ STAT_LAYOUTS ] = DRAW_STATS; + else + client->ps.stats[ STAT_LAYOUTS ] = 0; + } + + // + // Overlays + // + if ( drawoverlay ) + { + client->ps.stats[ STAT_LAYOUTS ] |= DRAW_OVERLAY; + } + + // + // Show scores in dealthmatch during intermission or if the client wants to see them + // + if ( deathmatch->value && ( level.intermissiontime || client->showinfo ) ) + { + client->ps.stats[ STAT_LAYOUTS ] |= DRAW_SCORES; + } + + // + // Powerups timer + // + if ( poweruptimer > 0 ) + { + client->ps.stats[ STAT_POWERUPTIMER ] = poweruptimer; + client->ps.stats[ STAT_POWERUPTYPE ] = poweruptype; + } + else + { + client->ps.stats[ STAT_POWERUPTIMER ] = 0; + client->ps.stats[ STAT_POWERUPTYPE ] = 0; + } + + // + // Mission Computer + // + if ( client->showinfo && !deathmatch->value ) + { + client->ps.stats[ STAT_LAYOUTS ] |= DRAW_MISSIONCPU; + } + + // + // Clear out the exit sign + // if we are in the trigger_exit field, than this will constantly be set + // so this will clear it out on subsequent calls to UpdateStats + // + if ( client->ps.stats[ STAT_EXITSIGN ] > 0 ) + { + client->ps.stats[ STAT_EXITSIGN ]--; + } + + // + // if the scoreboard is up, update it + // + if ( client->showinfo && deathmatch->value && !( level.framenum & 31 ) ) + { + G_DeathmatchScoreboardMessage( this, enemy ); + gi.unicast( edict, false ); + } + } + + +EXPORT_FROM_DLL void Player::UpdateMusic + ( + void + ) + { + if ( music_forced ) + { + client->ps.current_music_mood = music_current_mood; + client->ps.fallback_music_mood = music_fallback_mood; + } + else if ( action_level > 30 ) + { + music_current_mood = mood_normal; + music_fallback_mood = mood_normal; + client->ps.current_music_mood = mood_action; + client->ps.fallback_music_mood = mood_normal; + } + else if ( ( action_level < 15 ) && ( client->ps.current_music_mood == mood_action ) ) + { + music_current_mood = mood_normal; + music_fallback_mood = mood_normal; + client->ps.current_music_mood = music_current_mood; + client->ps.fallback_music_mood = music_fallback_mood; + } + else if ( client->ps.current_music_mood != mood_action ) + { + client->ps.current_music_mood = music_current_mood; + client->ps.fallback_music_mood = music_fallback_mood; + } + + if (action_level > 0) + { + action_level -= 0.2f; + if (action_level > 80) + action_level = 80; + } + else + action_level = 0; + + // + // set the music + // naturally decay the action level + // + if (s_debugmusic->value) + { + warning("DebugMusic","%s's action_level = %4.2f\n", client->pers.netname, action_level); + } + } + + +/* +================= +EndFrame + +Called for each player at the end of the server frame +and right after spawning +================= +*/ +EXPORT_FROM_DLL void Player::EndFrame + ( + Event *ev + ) + + { + FinishMove(); + UpdateStats(); + UpdateMusic(); + + // set the crouch flag + if ( client->ps.pmove.pm_flags & PMF_DUCKED ) + { + edict->s.effects |= EF_CROUCH; + } + else + { + edict->s.effects &= ~EF_CROUCH; + } + + if ( movieCamera != CinematicCamera ) + { + if ( movieCamera && movieCamera->isSubclassOf( SecurityCamera ) ) + { + movieCamera->edict->s.renderfx &= ~RF_DONTDRAW; + } + movieCamera = CinematicCamera; + if ( movieCamera ) + { + Entity * ent; + Camera * cam; + SetViewMode( CAMERA_VIEW, movieCamera ); + + if ( movieCamera->isSubclassOf( Camera ) ) + { + ent = movieCamera; + + cam = ( Camera * )ent; + if ( cam->Overlay().length() ) + { + SendOverlay( this, cam->Overlay() ); + drawoverlay = true; + hidestats = true; + } + } + } + else + { + drawoverlay = false; + hidestats = false; + SetViewMode( defaultViewMode ); + } + } + + if ( ( viewmode == THIRD_PERSON ) && ( zoom_mode == ZOOMED_IN ) ) + { + SetCameraEntity( this ); + if ( crosshair ) + { + crosshair->hideModel(); + } + } + else if ( ( viewmode == THIRD_PERSON ) && ( gravaxis ) ) + { + SetCameraEntity( this ); + if ( crosshair ) + { + crosshair->hideModel(); + } + } + else + { + if ( crosshair ) + { + crosshair->showModel(); + } + assert( watchCamera ); + if ( !watchCamera ) + { + // fix it in release + watchCamera = this; + } + SetCameraEntity( watchCamera ); + } + } + +EXPORT_FROM_DLL void Player::ShowInfo + ( + Event *ev + ) + + { + client->showinfo = !client->showinfo; + if ( deathmatch->value && client->showinfo ) + { + G_DeathmatchScoreboard( this ); + return; + } + } + +void Player::ReadyToFire + ( + Event *ev + ) + + { + int frame; + + frame = ev->GetInteger( 1 ); + if ( ( buttons & BUTTON_ATTACK ) && WeaponReady() ) + { + NextFrame( frame ); + } + } + +void Player::WaitingToFire + ( + Event *ev + ) + + { + int frame; + + frame = ev->GetInteger( 1 ); + if ( ( buttons & BUTTON_ATTACK ) && !WeaponReady() ) + { + NextFrame( frame ); + } + } + +void Player::DoneFiring + ( + Event *ev + ) + + { + firing = false; + } + +void Player::AddItemToFloatingInventory + ( + Item *item + ) + + { + floating_inventory.AddObject( item->entnum ); + } + +void Player::SendFloatingInventory + ( + void + ) + + { + int i,n; + + // Send over the floating inventory icons + n = floating_inventory.NumObjects(); + assert( n < MAX_ITEMS ); + gi.WriteByte( svc_inventory ); + gi.WriteShort( n ); + for( i=1; i<=n; i++) + { + Item *item; + item = ( Item * )G_GetEntity( floating_inventory.ObjectAt( i ) ); + assert( item ); + if ( item ) + { + gi.WriteShort( item->GetIconIndex() ); + gi.WriteShort( item->Amount() ); + } + } + gi.unicast( this->edict, false ); + } + +void Player::ClearFloatingInventory + ( + Event *ev + ) + + { + // Clear the items + floating_inventory.ClearObjectList(); + // Clear the owner; + floating_owner = NULL; + // Clear the client's list + SendFloatingInventory(); + } + +void Player::SetFloatingOwner + ( + Sentient *deceased_owner + ) + + { + floating_owner = deceased_owner; + } + +Sentient *Player::GetFloatingOwner + ( + void + ) + + { + return floating_owner; + } + +void Player::PickupFloatingInventory + ( + void + ) + + { + int i,n; + Event *event; + qboolean inventory_changed = false; + + // Make sure there is an inventory to pickup + if (!floating_owner) + return; + + n = floating_inventory.NumObjects(); + + if (!n) + return; + + assert( n < MAX_ITEMS ); + + for( i=n; i>0; i--) + { + Item *item; + item = ( Item * )G_GetEntity( floating_inventory.ObjectAt( i ) ); + assert( item ); + + // Check to see if we want to pick this up + if ( !item->Pickupable( this ) ) + continue; + + // Remove it from the dead body + item->Drop(); + + // Add item to player's inventory + event = new Event( EV_Item_Pickup ); + event->AddEntity( this ); + item->ProcessEvent( event ); + item->ProcessEvent( EV_Trigger_StartThread ); + + // Remove it from the floating inventory + floating_inventory.RemoveObjectAt( i ); + + inventory_changed = true; + } + + if ( inventory_changed ) + { + // Update the client if the inventory changed + SendFloatingInventory(); + } + else + { + // Make this guy useless in 60 seconds + Event *re; + re = new Event( "remove_useless" ); + floating_owner->PostEvent( re, 60 ); + + RandomSound("snd_refusepickup"); + } + + if ( !floating_inventory.NumObjects() ) + { + Event *re; + re = new Event( "remove_useless" ); + floating_owner->ProcessEvent( re ); + } + + } + +void Player::ChangeSpectator + ( + void + ) + + { + edict_t *cl_ent; + + currentCameraTarget = ( currentCameraTarget + 1 ) % game.maxclients; + cl_ent = g_edicts + 1 + currentCameraTarget; + + while ( !cl_ent->inuse ) + { + currentCameraTarget = ( currentCameraTarget + 1 ) % game.maxclients; + cl_ent = g_edicts + 1 + currentCameraTarget; + } + SetViewMode( SPECTATOR ); + } + +void Player::TestThread + ( + Event *ev + ) + + { + const char *scriptfile; + const char *label = NULL; + ScriptThread * thread; + + if ( ev->NumArgs() < 1 ) + { + gi.cprintf( edict, PRINT_HIGH, "Syntax: testthread scriptfile