//----------------------------------------------------------------------------- // // $Logfile:: /Quake 2 Engine/Sin/code/game/sentient.cpp $ // $Revision:: 198 $ // $Author:: Aldie $ // $Date:: 4/16/99 5:00p $ // // 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/sentient.cpp $ // // 198 4/16/99 5:00p Aldie // Added higher rocket jump // // 197 3/26/99 6:26p Aldie // Fixed more CTF bugs, probably the last ones // // 196 3/18/99 6:45p Aldie // Empath shield damage is posted 0 time not FRAMETIME // // 195 3/17/99 4:00p Aldie // CTF Update // // 194 3/12/99 10:19p Aldie // Added MOD_DEATHQUAD // // 193 3/12/99 8:13p Aldie // Moved damage code around // // 192 3/11/99 8:49p Markd // Fixed minor weapon bugs with setting new weapon and new weapon being null // already. // // 191 3/11/99 3:47p Aldie // Added some damage changes // // 190 3/02/99 9:15p Aldie // Added CTF game code // // 2 2/16/99 4:09p Aldie // // 1 2/11/99 1:39p Aldie // // 189 11/17/98 6:56p Jimdose // made EventGiveHealth clear the damage skin on the player // // 188 11/16/98 9:05p Markd // added meansofdeath to pain // // 187 11/16/98 7:46p Markd // Hooked up takeItem to an event, for some reason this was never done? // // 186 11/14/98 8:07p Jimdose // added DF_NO_DROP_WEAPONS // // 185 11/13/98 10:20p Aldie // Added event take item // // 184 11/13/98 2:36a Aldie // Fixed typo for MOD_SPEARGUN and gibbing. Also removed pain for MUTANT_DRAIN // // 183 11/12/98 11:29p Aldie // No blood for mutant drain // // 182 11/09/98 6:22p Markd // Added dropweaponnow event // // 181 11/08/98 10:49p Jimdose // sentientFrozen wasn't being archived // // 180 11/08/98 8:30p Aldie // Added a couple of strings to archive for sentients // // 179 10/27/98 6:20a Aldie // Fixed drop inventory items bug // // 178 10/27/98 3:50a Jimdose // made armordamage not do Ion tesselation when FL_NOION is set // // 177 10/26/98 9:53p Markd // Moved GotKill around, added gibfest sound // // 176 10/26/98 8:12p Aldie // Fixed some weapon bugs with detaching from owner when removing // // 175 10/26/98 2:21a Aldie // Added MOD_HELIGUN to DoGib // // 174 10/25/98 10:15p Aldie // Fix dropweapon code (again) // // 173 10/25/98 4:37a Aldie // Put an assertion to help us find a bug with giving items // // 172 10/25/98 1:51a Aldie // Fixed an inventory bug ( free inventory ) and lowered percentage of gun // getting shot out of your hands // // 171 10/24/98 11:47p Jimdose // fixed RestorePersistantData so that setowner works after G_CallSpawn // // 170 10/24/98 6:38p Markd // Added gibbed to GotKill, also added GotKill to gibbed section of ArmorDamage // // 169 10/24/98 4:34p Aldie // Added a warning message if we could not find the item in // "EventGiveTargetname" - hopefully this will help find the problem. // // 168 10/24/98 2:08p Aldie // Fixed crash bug with floating inventories // // 167 10/24/98 12:42a Markd // changed origins to worldorigins where appropriate // // 166 10/23/98 9:58p Aldie // Fixed dropweapon bug // // 165 10/23/98 7:11p Aldie // Damage tweak for singleplayer // // 164 10/23/98 2:37a Aldie // Added MOD_VEHICLE // // 163 10/22/98 11:11p Markd // Put item variables into Add and Remove to keep track of inventory // // 162 10/22/98 10:22p Markd // Put in support for game and level item script variables // // 161 10/22/98 9:29p Aldie // Added support for death gib animations // // 160 10/22/98 4:58p Aldie // Only do rocket jump in deathmatch // // 159 10/22/98 2:38a Aldie // Added setdropweapon command for sentients // // 158 10/22/98 1:40a Aldie // Don't put blood on ents // // 157 10/21/98 8:41p Markd // Fixed currentWeapon bug with WeaponUse // // 156 10/21/98 7:50p Aldie // Fixed a function declaration // // 155 10/20/98 11:23p Jimdose // made weapons switch properly if you pickup a weapon while reloading // made drop weapon and take weapon use the best weapon when switching // // 154 10/20/98 8:05p Aldie // Added a DoGib function to ease readability // // 153 10/20/98 1:18a Markd // Made fists and adrenaline a gibbable act // // 152 10/19/98 12:07a Jimdose // made all code use fast checks for inheritance (no text lookups when // possible) // isSubclassOf no longer requires ::_classinfo() // // 151 10/18/98 6:43p Markd // Allowed other actors to have their weapons knocked from their hands // // 150 10/17/98 8:14p Jimdose // Changed G_CallSpawn2 to G_CallSpawn // // 149 10/17/98 5:15p Markd // rotate centroid by orientation when making force field // // 148 10/16/98 3:10a Aldie // Removed global dmg multipliers // // 147 10/15/98 3:37p Markd // Added forcefield support // // 146 10/15/98 3:11p Aldie // Damage skill multiplier ( for testing purposes ) // // 145 10/14/98 1:19a Jimdose // Got cross-level persistant info working // // 144 10/12/98 3:49p Aldie // Init a couple vars // // 143 10/11/98 5:34p Aldie // Gib from MUTANTHANDS // // 142 10/11/98 12:03a Aldie // Mutant mode takes less dmg // // 141 10/10/98 5:58p Aldie // More quantumdestab fixes // // 140 10/09/98 5:23p Aldie // Teamplay // // 139 10/08/98 12:02a Jimdose // Bloodsplats are now a separate class // // 138 10/05/98 10:18p Aldie // Covnverted over to new silencer methods // // 137 10/04/98 10:28p Aldie // Added multiple weapon changes. Damage, flashes, quantum stuff // // 136 10/03/98 7:27p Markd // Added pos tio get gun orientation // // 135 10/03/98 5:13p Jimdose // changed the way Unarchive reads the event pointer // // 134 10/01/98 8:00p Aldie // Consolidated cprintf // // 133 9/27/98 4:36p Aldie // Modified armor damage and clipped velocity.z for bulletweapons in // armordamage // // 132 9/26/98 4:45p Aldie // Added mutant mode // // 131 9/25/98 4:37p Markd // Made two sentient functions not inline, so that it would work with vc 6.0 // // 130 9/23/98 12:37p Markd // added tesselate sound when ionized // // 129 9/22/98 3:21p Markd // put in parentmode lockout for blood and gibs // // 128 9/22/98 12:14p Aldie // Added sentientFrozen // // 127 9/21/98 6:05p Markd // added archive and unarchive functions // // 126 9/20/98 9:08p Aldie // Fixed a particle bug with RandomParticles // // 125 9/20/98 7:11p Aldie // Added flags to particles // // 124 9/18/98 9:55p Markd // Added noshadow event // // 123 9/18/98 8:40p Aldie // Ion tesselate effect // // 122 9/16/98 8:58p Aldie // Added ability to do a hold down weapon charge // // 121 9/12/98 11:08p Aldie // Fixed armor damage bug // // 120 9/11/98 2:50p Aldie // Added releasefiring functionality // // 119 9/09/98 6:49p Markd // Check to see if weapon is droppable before dropping it // // 118 9/05/98 12:12p Aldie // Tweaked armor damage that does 50% damage to player and 50% damage to armor // in deathmatch. Also inited poweruptype and poweruptimer // // 117 9/02/98 11:08a Markd // Put in setModel into Sentient so that weapon could be properly detached and // re-attached again. // // 116 9/01/98 7:46p Aldie // Changed blood around so dead bodies bleed // // 115 8/31/98 7:16p Markd // Fixed player animation naming convention // // 114 8/31/98 5:45p Aldie // Powerup stuff // // 113 8/31/98 1:55p Markd // weapons no longer double trigger animations // // 112 8/29/98 9:46p Jimdose // Added call info to G_Trace // // 111 8/29/98 7:23p Aldie // Made a givetargetname function for giving monsters targeted items. // // 110 8/29/98 2:53p Aldie // Updated printing of location based damage // // 109 8/27/98 9:03p Jimdose // made grav a reference // // 108 8/27/98 6:43p Markd // re-added StartAnimating calls in TempAnim and SetANim // // 107 8/27/98 6:00p Aldie // Drop inventory items if the actors are gibbed. // // 106 8/27/98 5:29p Markd // Took out start animating calls // // 105 8/27/98 2:30p Aldie // Added DAMAGE_NO_SKILL flag // // 104 8/24/98 6:52p Jimdose // Fixed inverted gravity axis // // 103 8/22/98 9:35p Jimdose // Added support for alternate gravity axis // // 102 8/21/98 7:38p Markd // Fixed climbing code // // 101 8/19/98 8:50p Aldie // Show damage locations and use random particles for blood. // // 100 8/17/98 2:58p Aldie // Rearranged blood and spark particles // // 99 8/15/98 4:07p Aldie // Made NextWeapon and PreviousWeapon obey the AutoChange function. So the // sniperrifle will not be auto used. // // 98 8/14/98 5:48p Aldie // Moved more of blood splat stuff here. // // 97 8/14/98 5:39p Aldie // Moved spray blood to entity // // 96 8/13/98 8:10p Aldie // Added sparks and blood to armordamage // // 95 8/12/98 4:20p Aldie // Fixed icons not showing up correctly // // 94 8/06/98 10:53p Aldie // Added weapon tweaks and kickback. Also modified blast radius damage and // rocket jumping. // // 93 8/04/98 6:14p Aldie // Updated armor damage stuff // // 92 8/04/98 5:10p Markd // Took out "Monster" clause in ArmorDamage // // 91 7/31/98 8:09p Jimdose // Script commands now include flags to indicate cheats and console commands // // 90 7/29/98 2:32p Aldie // Changed health to a float // // 89 7/26/98 4:15a Aldie // Check for in vehicle so the player can kill himself // // 88 7/26/98 2:59a Markd // guys don't get their guns knocked out of their hands for now // // 87 7/26/98 2:54a Markd // changed player damage multipliers // // 86 7/26/98 12:46a Aldie // Trying to fix stuck dead anims // // 85 7/25/98 3:57p Markd // Added EV_GotKill // // 84 7/25/98 2:11a Jimdose // Added SentientList // // 83 7/24/98 9:58p Aldie // Gib stuff // // 82 7/24/98 6:19p Aldie // Check for inventory of type // // 81 7/24/98 3:49p Aldie // Made sentients check for uselessness in the future // // 80 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 // // 79 7/23/98 1:51p Aldie // Print damage to attacker and client // // 78 7/23/98 12:17p Aldie // Print damage reports to victims. // // 77 7/21/98 9:09p Aldie // Process commands on given weapons too. Seems to work ok. // // 76 7/21/98 1:10p Aldie // Added meansofdeath to obituaries // // 75 7/20/98 7:42p Aldie // Set owner of given items before processing their commands. // // 74 7/20/98 6:54p Markd // Added damage support based on skill level // // 73 7/20/98 5:40p Aldie // When giving items to sentients process the commands immediately. // // 72 7/19/98 10:34p Aldie // Added DAMAGE_NO_ARMOR functionality // // 71 7/19/98 7:38p Markd // Added Shield support for 1/4 damage // // 70 7/19/98 7:35p Markd // Fixed ForceWeaponChange so that it doesn't squash newWeapon // // 69 7/19/98 5:41p Markd // Added ForceWeaponChange, added takeItem // // 68 7/18/98 7:37p Aldie // Updated showdamage printing. // // 67 7/18/98 5:08p Aldie // Added showdamage // // 66 7/17/98 4:01p Markd // Added CanChangeWeapons // // 65 7/14/98 6:57p Aldie // Updated health commands // // 64 7/13/98 5:00p Aldie // Added dead player bodies with gibbing // // 63 7/11/98 8:22p Jimdose // When dropping a weapon, if the weapon can't be dropped, it's deleted if the // player is dying. This fixes the crash when the player dies while using the // fists. // // 62 7/11/98 5:55p Aldie // Undid when we send icons to the player for picking stuff up. // // 61 7/10/98 11:11p Jimdose // characters no longer switch weapons after dropping them when killed // // 60 7/10/98 12:05a Aldie // Fixed icon sending if you don't pickup a weapon // // 59 7/01/98 7:03p Aldie // Floating inventory mods for empty inventories // // 58 6/30/98 6:48p Aldie // Gib a dead body, and don't display items that have 0 amount. // // 57 6/25/98 8:47p Markd // Added keyed items for Triggers, Rewrote Item class, rewrote every pickup // method // // 56 6/25/98 12:42p Aldie // Updated some inventory and weapon swtiching stuff // // 55 6/24/98 4:44p Aldie // Made it so you can only view one inventory at a time // // 54 6/24/98 1:37p Aldie // Implementation of inventory system and picking stuff up // // 53 6/21/98 5:54p Jimdose // Moved call to GetBoneGroupName from the constructor to SetCurrentWeapon // since the model isn't set yet when the constructor is called. // // 52 6/20/98 7:49p Markd // Added location to pain and killed events, also added gun_bone_group_name // variable // // 51 6/20/98 2:20p Aldie // Removed sone printfs // // 50 6/19/98 9:28p Jimdose // Added GetGunOrientation // // 49 6/19/98 6:36p Aldie // Put some more inventory managment in // // 48 6/18/98 9:25p Aldie // Started inventory system // // 47 6/18/98 2:00p Markd // rewrote tesselation code // // 46 6/17/98 1:20a Jimdose // Changed Attack to FireWeapon // // 45 6/15/98 9:09p Aldie // Added a generic give and an updater for silenced weapons, when you pick it // up // // 44 6/15/98 3:36p Aldie // Updated armor damage calculations // // 43 6/10/98 2:10p Aldie // Updated damage function. // // 42 6/09/98 4:22p Jimdose // AnimLoop went into an infinite loop when the anim didn't exist // // 41 6/08/98 9:04p Aldie // Updated checks for weapon changing when no ammo available // // 40 6/08/98 8:35p Markd // put in code to put away weapon when readytouse becomes false // // 39 6/08/98 8:19p Markd // Put in ReadyToUse call when switching weapons // // 38 6/08/98 11:34a Aldie // Updated damage based location checks // // 37 6/05/98 6:25p Aldie // Added give and take armor as well as ArmorDamage calculations based on // location hit // // 36 5/27/98 12:59a Markd // Put in shadows flag // // 35 5/26/98 4:46p Aldie // added take functions // // 34 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 // // 33 5/24/98 4:48p Jimdose // Made char *'s const // // 32 5/20/98 6:38p Jimdose // Made anim functions use const char * // // 31 5/08/98 2:57p Markd // Added weapon pickup sound // // 30 5/03/98 4:37p Jimdose // Changed Vector class // // 29 4/25/98 5:09p Markd // Added up and down support for angles // // 28 4/07/98 6:47p Jimdose // Fixed weapon switching bugs // // 27 4/06/98 5:46p Jimdose // Added "angles" spawn values // // 26 4/05/98 2:57a Jimdose // Made checks for anim and classname case insensitive // // 25 3/30/98 2:37p Jimdose // Added Ammo // Added weapon dropping // // 24 3/29/98 10:01p Jimdose // Added animation control functions // // 23 3/27/98 5:37p Jimdose // Added FreeInventory for when players respawn // // 22 3/24/98 4:57p Jimdose // Changed usage of GetToken to GetString so that script variables can be used // // 21 3/23/98 1:31p Jimdose // Revamped event and command system // // 20 3/18/98 7:23p Jimdose // Added code for handling readying and putting away weapons // // 19 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. // // 18 3/02/98 5:38p Jimdose // Added destructor so that all held weapons are released from being owned. // Temporariliy disabled type checking on Weapons. // // 17 2/19/98 2:36p Jimdose // Added weapons back in // // 16 2/17/98 6:59p Jimdose // no longer pass script into interpretCommand // // 15 2/03/98 10:51a Jimdose // Updated to work with Quake 2 engine // Moved initialization to constructor and removed Init function // // 13 12/15/97 4:39p Aldie // Added support for gunoffset in def files. // // 12 12/15/97 1:37a Jimdose // Added assertions in nextWeapon and previousWeapon to help track down a bug. // While changing weapons, I got a NULL pointer when I retrieved a entity // number from the inventory. Weapons in the inventory should never be freed // during the game without being removed from the inventory first. // // 11 12/11/97 3:36p Markd // Moved ClearInventory List to before Init call to super class // // 10 12/09/97 7:41p Markd // Added NumWeapons and WeaponNumber functions // // 9 11/20/97 9:02p Jimdose // Changed Container class to use entity # instead of void * // // 8 11/18/97 5:27p Markd // Changed "ammo" command and also fixed some weapon checking // // 7 11/15/97 6:54p Markd // Added InterpretCommand, Added "fire", "ammo" and "weapon" command // // 6 10/28/97 6:05p Jimdose // Sentients now pick up nails from nail weapons. // // 5 10/27/97 3:29p Jimdose // Removed dependency on quakedef.h // // 4 10/24/97 2:58p Jimdose // Fixed bug where sentient was bound to weapon instead of weapon bound to // sentient. // // 3 10/01/97 2:04p Jimdose // Weapons are now bound to their owners when they are picked up. // // 2 9/26/97 6:47p Jimdose // Added standard Ritual headers // // DESCRIPTION: // Base class of entity that can carry other entities, and use weapons. // #include "g_local.h" #include "entity.h" #include "sentient.h" #include "weapon.h" #include "scriptmaster.h" #include "ammo.h" #include "armor.h" #include "misc.h" #include "inventoryitem.h" #include "player.h" #include "actor.h" #include "ctf.h" CLASS_DECLARATION( Entity, Sentient, NULL ); Event EV_Sentient_Attack( "fire" ); Event EV_Sentient_ReleaseAttack( "releasefire" ); Event EV_Sentient_GiveWeapon( "weapon" ); Event EV_Sentient_GiveAmmo( "ammo" ); Event EV_Sentient_GiveArmor( "armor" ); Event EV_Sentient_GiveItem( "item" ); Event EV_Sentient_GiveTargetname( "give" ); Event EV_Sentient_GiveInventoryItem( "invitem" ); Event EV_Sentient_GiveHealth( "health", EV_CHEAT ); Event EV_Sentient_TakeWeapon( "take_weapon" ); Event EV_Sentient_TakeAmmo( "take_ammo" ); Event EV_Sentient_TakeArmor( "take_armor" ); Event EV_Sentient_TakeItem( "take_item" ); Event EV_Sentient_WeaponPutAway( "weapon_putaway" ); Event EV_Sentient_WeaponReady( "weapon_ready" ); Event EV_Sentient_WeaponDoneFiring( "weapon_donefiring" ); Event EV_Sentient_AnimLoop( "animloop" ); Event EV_Sentient_UselessCheck( "useless_check" ); Event EV_Sentient_TurnOffShadow( "noshadow" ); Event EV_Sentient_Freeze( "freeze" ); Event EV_Sentient_UnFreeze( "unfreeze" ); Event EV_Sentient_ImpactDamage( "impact_damage" ); Event EV_Sentient_WeaponUse( "weaponuse", EV_CONSOLE ); Event EV_Sentient_SetDropWeapon( "dropweapon" ); Event EV_Sentient_DropWeaponNow( "dropweaponnow" ); ResponseDef Sentient::Responses[] = { { &EV_Sentient_Attack, ( Response )Sentient::FireWeapon }, { &EV_Sentient_ReleaseAttack, ( Response )Sentient::ReleaseFireWeapon }, { &EV_Sentient_GiveWeapon, ( Response )Sentient::EventGiveWeapon }, { &EV_Sentient_TakeWeapon, ( Response )Sentient::EventTakeWeapon }, { &EV_Sentient_GiveAmmo, ( Response )Sentient::EventGiveAmmo }, { &EV_Sentient_TakeAmmo, ( Response )Sentient::EventTakeAmmo }, { &EV_Sentient_GiveArmor, ( Response )Sentient::EventGiveArmor }, { &EV_Sentient_TakeArmor, ( Response )Sentient::EventTakeArmor }, { &EV_Sentient_GiveItem, ( Response )Sentient::EventGiveItem }, { &EV_Sentient_GiveInventoryItem,( Response )Sentient::EventGiveInventoryItem }, { &EV_Sentient_GiveHealth, ( Response )Sentient::EventGiveHealth }, { &EV_Sentient_GiveTargetname, ( Response )Sentient::EventGiveTargetname }, { &EV_Sentient_WeaponPutAway, ( Response )Sentient::WeaponPutAway }, { &EV_Sentient_WeaponReady, ( Response )Sentient::WeaponReady }, { &EV_Sentient_WeaponDoneFiring, ( Response )Sentient::WeaponDoneFiring }, { &EV_Sentient_AnimLoop, ( Response )Sentient::AnimLoop }, { &EV_Damage, ( Response )Sentient::ArmorDamage }, { &EV_Touch, ( Response )Sentient::SearchBody }, { &EV_Sentient_UselessCheck, ( Response )Sentient::UselessCheck }, { &EV_Sentient_TurnOffShadow, ( Response )Sentient::TurnOffShadow }, { &EV_Sentient_Freeze, ( Response )Sentient::Freeze }, { &EV_Sentient_UnFreeze, ( Response )Sentient::UnFreeze }, { &EV_Sentient_ImpactDamage, ( Response )Sentient::ImpactDamage }, { &EV_Sentient_WeaponUse, ( Response )Sentient::WeaponUse }, { &EV_Sentient_SetDropWeapon, ( Response )Sentient::SetDropWeapon }, { &EV_Sentient_DropWeaponNow, ( Response )Sentient::DropWeaponNowEvent }, { &EV_Sentient_TakeItem, ( Response )Sentient::EventTakeItem }, { NULL, NULL } }; Container SentientList; Sentient::Sentient() { Vector defangles; SentientList.AddObject( ( Sentient * )this ); inventory.ClearObjectList(); currentWeapon = NULL; currentItem = NULL; newWeapon = NULL; animOverride = false; dropweapon = true; tempAnimEvent = NULL; eyeposition = "0 0 64"; gunoffset = "0 0 56"; firedown = false; firedowntime = 0; // 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 ); stopanimating_tillchange = false; poweruptype = 0; poweruptimer = 0; sentientFrozen = false; // HACK FIXME // // temporary shadow flag // edict->s.renderfx |= RF_XFLIP; } Sentient::~Sentient() { SentientList.RemoveObject( ( Sentient * )this ); FreeInventory(); } Vector Sentient::EyePosition ( void ) { Vector pos; pos = worldorigin; pos[ gravity_axis[ gravaxis ].x ] += eyeposition[ 0 ]; pos[ gravity_axis[ gravaxis ].y ] += eyeposition[ 1 ] * gravity_axis[ gravaxis ].sign; pos[ gravity_axis[ gravaxis ].z ] += eyeposition[ 2 ] * gravity_axis[ gravaxis ].sign; return pos; } Vector Sentient::GunPosition ( void ) { Vector pos; pos = worldorigin; pos[ gravity_axis[ gravaxis ].x ] += gunoffset[ 0 ]; pos[ gravity_axis[ gravaxis ].y ] += gunoffset[ 1 ] * gravity_axis[ gravaxis ].sign; pos[ gravity_axis[ gravaxis ].z ] += gunoffset[ 2 ] * gravity_axis[ gravaxis ].sign; return pos; } inline void Sentient::GetGunOrientation ( Vector pos, Vector *forward, Vector *right, Vector *up ) { const gravityaxis_t &grav = gravity_axis[ gravaxis ]; if ( forward ) { ( *forward )[ grav.x ] = orientation[ 0 ][ 0 ]; ( *forward )[ grav.y ] = orientation[ 0 ][ 1 ] * grav.sign; ( *forward )[ grav.z ] = orientation[ 0 ][ 2 ] * grav.sign; } if ( right ) { ( *right )[ grav.x ] = -orientation[ 1 ][ 0 ]; ( *right )[ grav.y ] = - orientation[ 1 ][ 1 ] * grav.sign; ( *right )[ grav.z ] = - orientation[ 1 ][ 2 ] * grav.sign; } if ( up ) { ( *up )[ grav.x ] = orientation[ 2 ][ 0 ]; ( *up )[ grav.y ] = orientation[ 2 ][ 1 ] * grav.sign; ( *up )[ grav.z ] = orientation[ 2 ][ 2 ] * grav.sign; } } void Sentient::GetMuzzlePositionAndDirection ( Vector *pos, Vector *dir ) { *pos = Vector( "0 0 0" ); *dir = Vector( "0 0 0" ); if ( currentWeapon ) currentWeapon->GetMuzzlePosition( pos, dir ); } void Sentient::EventGiveHealth ( Event *ev ) { int group_num; health = ev->GetFloat( 1 ); if ( isClient() ) { for( group_num = 0; group_num < edict->s.numgroups ; group_num++ ) { edict->s.groups[ group_num ] &= ~MDL_GROUP_SKINOFFSET_BIT0; } } } void Sentient::FireWeapon ( Event *ev ) { if ( ( currentWeapon ) && currentWeapon->ReadyToFire() && currentWeapon->HasAmmo() ) { currentWeapon->Fire(); } } void Sentient::ReleaseFireWeapon ( Event *ev ) { // Only need to check if currentWeapon because FireWeapon checks for // the weapon ready and if it has ammo. if ( currentWeapon ) { float fireholdtime = ev->GetFloat( 1 ); currentWeapon->ReleaseFire( fireholdtime ); } } void Sentient::AddItem ( Item *object ) { inventory.AddObject( object->entnum ); if ( object->isSubclassOf( InventoryItem ) ) { currentItem = (InventoryItem *)object; } // // set our global game variables if client // if ( isClient() ) { str fullname; ScriptVariable * var; fullname = str( "playeritem_" ) + object->getClassname(); var = gameVars.GetVariable( fullname.c_str() ); if ( !var ) { gameVars.SetVariable( fullname.c_str(), 1 ); } else { int amount; amount = var->intValue() + 1; var->setIntValue( amount ); } var = levelVars.GetVariable( fullname.c_str() ); if ( !var ) { levelVars.SetVariable( fullname.c_str(), 1 ); } else { int amount; amount = var->intValue() + 1; var->setIntValue( amount ); } } } void Sentient::RemoveItem ( Item *object ) { Weapon *weapon; inventory.RemoveObject( object->entnum ); if ( currentWeapon == object ) { currentWeapon = NULL; if ( newWeapon == object ) { newWeapon = NULL; weapon = BestWeapon(); } else { weapon = newWeapon; } SetCurrentWeapon( weapon ); } // // set our global game variables if client // if ( isClient() ) { str fullname; ScriptVariable * var; fullname = str( "playeritem_" ) + object->getClassname(); var = levelVars.GetVariable( fullname.c_str() ); if ( var ) { int amount; amount = var->intValue() - 1; if ( amount < 0 ) amount = 0; var->setIntValue( amount ); } } if ( currentItem == (InventoryItem *) object ) { currentItem = NULL; // Try to set the the current item currentItem = (InventoryItem *)NextItem( NULL ); if (!currentItem) { currentItem = (InventoryItem *)PrevItem( NULL ); } } } Item *Sentient::FindItem ( const char *itemname ) { int num; int i; Item *item; num = inventory.NumObjects(); for( i = 1; i <= num; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( !Q_stricmp( item->getClassname(), itemname ) ) { return item; } } return NULL; } Item *Sentient::HasItemOfSuperclass ( const char *superclassname ) { int num; int i; Item *item; num = inventory.NumObjects(); for( i = 1; i <= num; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( !Q_stricmp( item->getSuperclass(), superclassname ) ) { return item; } } return NULL; } void Sentient::FreeInventory ( void ) { int num; int i; Item *item; if ( currentWeapon ) currentWeapon->DetachFromOwner(); SetCurrentWeapon( NULL ); newWeapon = NULL; num = inventory.NumObjects(); for( i = num; i > 0; i-- ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); delete item; } inventory.ClearObjectList(); currentItem = NULL; } void Sentient::FreeInventoryOfType ( const char *type ) { int num; int i; Item *item; num = inventory.NumObjects(); for( i = num; i > 0; i-- ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( checkInheritance( type, item->getClassname() ) ) { inventory.RemoveObject( item->entnum ); // If the current item is about to be deleted, then look for // another item to be current if ( currentItem == item ) { currentItem = NULL; // Try to set the the current item currentItem = (InventoryItem *)NextItem( NULL ); if (!currentItem) { currentItem = (InventoryItem *)PrevItem( NULL ); } } delete item; } } } qboolean Sentient::HasItem ( const char *itemname ) { return ( FindItem( itemname ) != NULL ); } int Sentient::NumWeapons ( void ) { int num; int i; Item *item; int numweaps; numweaps = 0; num = inventory.NumObjects(); for( i = 1; i <= num; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( checkInheritance( &Weapon::ClassInfo, item->getClassname() ) ) { numweaps++; } } return numweaps; } Weapon * Sentient::WeaponNumber ( int weaponnum ) { int num; int i; Item *item; // make it so that 0 is our first weapon rather than 1 weaponnum++; num = inventory.NumObjects(); for( i = 1; i <= num; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( item->isSubclassOf( Weapon ) ) { weaponnum--; if ( !weaponnum ) { return ( Weapon * )item; } } } return NULL; } void Sentient::ChangeWeapon ( Weapon *weapon ) { if ( weapon == currentWeapon ) { return; } if ( currentWeapon ) { if ( !weapon->ReadyToUse() ) { return; } newWeapon = weapon; if ( !currentWeapon->ReadyToChange() ) { return; } currentWeapon->PutAway(); } else { SetCurrentWeapon( weapon ); } } void Sentient::ForceChangeWeapon ( Weapon *weapon ) { if ( newWeapon == weapon ) { return; } if ( weapon == currentWeapon ) { return; } newWeapon = NULL; if ( currentWeapon ) { currentWeapon->DetachFromOwner(); } SetCurrentWeapon( weapon ); } qboolean Sentient::CanChangeWeapons ( void ) { return !( ( newWeapon != NULL ) || ( currentWeapon && !currentWeapon->ReadyToChange() ) || ( flags & FL_MUTANT ) ); } void Sentient::SetCurrentWeapon ( Weapon *weapon ) { const char *name; newWeapon = NULL; currentWeapon = weapon; if ( currentWeapon ) { currentWeapon->ReadyWeapon(); // get the name of the group that the gun bone belongs to name = gi.GetBoneGroupName( edict->s.modelindex, "gun" ); if ( name ) { gun_bone_group_name = name; } else { gun_bone_group_name = ""; } } } Weapon *Sentient::CurrentWeapon ( void ) { return currentWeapon; } Weapon *Sentient::BestWeapon ( Weapon *ignore ) { Item *next; int n; int j; int bestrank; Weapon *bestweapon; n = inventory.NumObjects(); // Search forewards until we find a weapon bestweapon = NULL; bestrank = -999999; for( j = 1; j <= n; j++ ) { next = ( Item * )G_GetEntity( inventory.ObjectAt( j ) ); assert( next ); if ( ( next != ignore ) && next->isSubclassOf( Weapon ) && ( ( ( Weapon * )next )->Rank() > bestrank ) && ( ( ( Weapon * )next )->HasAmmo() ) ) { bestweapon = ( Weapon * )next; bestrank = bestweapon->Rank(); } } return bestweapon; } Weapon *Sentient::NextWeapon ( Weapon *weapon ) { Item *item; int i; int n; int weaponorder; Weapon *choice; int choiceorder; Weapon *bestchoice; int bestorder; Weapon *worstchoice; int worstorder; if ( !inventory.ObjectInList( weapon->entnum ) ) { error( "NextWeapon", "Weapon not in list" ); } weaponorder = weapon->Order(); bestchoice = weapon; bestorder = 65535; worstchoice = weapon; worstorder = weaponorder; n = inventory.NumObjects(); for( i = 1; i <= n; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( item ); if ( item->isSubclassOf( Weapon ) ) { choice = ( Weapon * )item; if ( !choice->HasAmmo() || !choice->AutoChange() ) { continue; } choiceorder = choice->Order(); if ( ( choiceorder > weaponorder ) && ( choiceorder < bestorder ) ) { bestorder = choiceorder; bestchoice = choice; } if ( choiceorder < worstorder ) { worstorder = choiceorder; worstchoice = choice; } } } if ( bestchoice == weapon ) { return worstchoice; } return bestchoice; } Weapon *Sentient::PreviousWeapon ( Weapon *weapon ) { Item *item; int i; int n; int weaponorder; Weapon *choice; int choiceorder; Weapon *bestchoice; int bestorder; Weapon *worstchoice; int worstorder; if ( !inventory.ObjectInList( weapon->entnum ) ) { error( "PreviousWeapon", "Weapon not in list" ); } weaponorder = weapon->Order(); bestchoice = weapon; bestorder = -65535; worstchoice = weapon; worstorder = weaponorder; n = inventory.NumObjects(); for( i = 1; i <= n; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( item ); if ( item->isSubclassOf( Weapon ) ) { choice = ( Weapon * )item; if ( !choice->HasAmmo() || !choice->AutoChange() ) { continue; } choiceorder = choice->Order(); if ( ( choiceorder < weaponorder ) && ( choiceorder > bestorder ) ) { bestorder = choiceorder; bestchoice = choice; } if ( choiceorder > worstorder ) { worstorder = choiceorder; worstchoice = choice; } } } if ( bestchoice == weapon ) { return worstchoice; } return bestchoice; } qboolean Sentient::WeaponReady ( void ) { Weapon *best; if ( !currentWeapon ) { return false; } if (!currentWeapon->ReadyToUse() ) { best = BestWeapon(); if ( best ) { ChangeWeapon( best ); } return false; } if ( !currentWeapon->ReadyToFire() ) { return false; } if ( !currentWeapon->HasAmmo() ) { best = BestWeapon(); if ( best ) { ChangeWeapon( best ); } return false; } return true; } void Sentient::DropWeapon ( Weapon *weapon ) { if ( weapon == currentWeapon ) { if ( deadflag ) { SetCurrentWeapon( NULL ); } else { // Choose the best weapon that's not the current SetCurrentWeapon( BestWeapon( currentWeapon ) ); } } // check if the sentient can drop the weapon if ( dropweapon ) { // This one checks if the weapon is droppable if ( !weapon->Drop() && deadflag ) { // can't drop it, probably because there's no worldmodel // since we're dead, just delete the weapon. weapon->PostEvent( EV_Remove, 0 ); } } else { weapon->PostEvent( EV_Remove, 0 ); } } void Sentient::DropCurrentWeapon ( void ) { if ( currentWeapon ) DropWeapon( currentWeapon ); } void Sentient::takeWeapon ( const char *weaponname ) { Weapon *weapon; assert( weaponname ); weapon = ( Weapon * )FindItem( weaponname ); if ( !weapon ) { return; } if ( weapon == newWeapon ) { newWeapon = NULL; } if ( weapon == currentWeapon ) { SetCurrentWeapon( BestWeapon( weapon ) ); } weapon->DetachFromOwner(); weapon->PostEvent( EV_Remove, 0 ); } void Sentient::EventGiveTargetname ( Event *ev ) { str name; Item *item=NULL; edict_t *from; name = ev->GetString( 1 ); for ( from = &g_edicts[ 1 ]; from < &g_edicts[ globals.num_edicts ] ; from++ ) { if ( !from->inuse ) continue; if ( from->entity->isSubclassOf( Item ) ) { item = ( Item * ) from->entity; if ( item->itemname == name ) break; } } // Item should be found assert( item ); if ( item ) { item->SetOwner( this ); item->ProcessPendingEvents(); AddItem( item ); } else { ev->Error( "Could not give item %s to %s.\n", name, targetname ); } } Item *Sentient::giveItem ( const char * itemname, int amount, int icon_index ) { ClassDef *cls; Item *item; int index; item = ( Item * )FindItem( itemname ); if ( item ) { item->Add( amount ); } else { cls = getClass( itemname ); if ( !cls ) { gi.dprintf( "No item named '%s'\n", itemname ); return NULL; } item = ( Item * )cls->newInstance(); item->SetOwner( this ); item->ProcessPendingEvents(); item->Set( amount ); AddItem( item ); } // Send icon to the player if ( this->isSubclassOf( Player ) ) { if ( icon_index >= 0 ) index = icon_index; else index = item->Icon(); if ( index >= 0 ) { gi.WriteByte( svc_console_command ); gi.WriteString( va( "pl %d %d", index, amount ) ); gi.unicast( edict, NULL ); } } return item; } void Sentient::takeItem ( const char *itemname, int amount ) { Item *item; item = ( Item * )FindItem( itemname ); if ( item ) { item->Remove( amount ); if ( !item->Amount() ) item->ProcessEvent( EV_Remove ); } } void Sentient::EventTakeItem ( Event *ev ) { int amount; if ( ev->NumArgs() > 1 ) amount = ev->GetInteger( 2 ); else amount = 1; takeItem( ev->GetString( 1 ), amount ); } Weapon *Sentient::giveWeapon ( const char *weaponname ) { Weapon *weapon; assert( weaponname ); if ( !checkInheritance( &Weapon::ClassInfo, weaponname ) ) { gi.dprintf( "A %s is not usable as a weapon\n", weaponname ); return NULL; } weapon = ( Weapon * )giveItem( weaponname, 1 ); if ( !currentWeapon || ( weapon->Rank() > currentWeapon->Rank() ) ) { ChangeWeapon( weapon ); } return weapon; } Weapon *Sentient::useWeapon ( const char *weaponname ) { Weapon *weapon; assert( weaponname ); weapon = ( Weapon * )FindItem( weaponname ); if ( weapon ) { ChangeWeapon( weapon ); } return currentWeapon; } void Sentient::EventGiveWeapon ( Event *ev ) { ChangeWeapon( giveWeapon( ev->GetString( 1 ) ) ); } void Sentient::EventTakeWeapon ( Event *ev ) { takeWeapon( ev->GetString( 1 ) ); } void Sentient::EventTakeAmmo ( Event *ev ) { int amount; const char *type; Ammo *ammo; Weapon *best; type = ev->GetString( 1 ); amount = ev->GetInteger( 2 ); ammo = ( Ammo * )FindItem( type ); if ( ammo ) { ammo->Remove( amount ); } if ( currentWeapon && !currentWeapon->HasAmmo() ) { best = BestWeapon(); if ( best ) { ChangeWeapon( best ); } } } void Sentient::EventGiveAmmo ( Event *ev ) { int amount; const char *type; type = ev->GetString( 1 ); amount = ev->GetInteger( 2 ); if ( !checkInheritance( &Ammo::ClassInfo, type ) ) { gi.dprintf( "%s is not usable as ammo\n", type ); return; } giveItem( type, amount ); if ( currentWeapon && !currentWeapon->HasAmmo() ) { Weapon *best; best = BestWeapon(); if ( best ) { ChangeWeapon( best ); } } } void Sentient::EventTakeArmor ( Event *ev ) { int amount; const char *type; Armor *armor; type = ev->GetString( 1 ); amount = ev->GetInteger( 2 ); armor = ( Armor * )FindItem( type ); if ( armor ) { armor->Remove( amount ); } } void Sentient::EventGiveArmor ( Event *ev ) { int amount; const char *type; Armor *armor; type = ev->GetString( 1 ); amount = ev->GetInteger( 2 ); if ( !checkInheritance( &Armor::ClassInfo, type ) ) { gi.dprintf( "%s is not usable as armor\n", type ); return; } armor = ( Armor * )FindItem( type ); if ( armor ) { armor->Set( amount ); } else { giveItem( type, amount ); } } void Sentient::EventGiveItem ( Event *ev ) { const char *type; float amount; type = ev->GetString( 1 ); amount = ev->GetInteger( 2 ); giveItem( type, amount ); } void Sentient::EventGiveInventoryItem ( Event *ev ) { const char *type; int amount; type = ev->GetString( 1 ); amount = ev->GetInteger( 2 ); if ( !checkInheritance( &InventoryItem::ClassInfo, type ) ) { gi.dprintf( "item '%s' is not a InventoryItem\n", type ); return; } giveItem( type, amount ); } void Sentient::WeaponPutAway ( Event *ev ) { Weapon *best; if ( newWeapon ) { SetCurrentWeapon( newWeapon ); } else { best = BestWeapon(); if ( best ) { SetCurrentWeapon( best ); } } } void Sentient::WeaponReady ( Event *ev ) { if ( newWeapon ) { ChangeWeapon( newWeapon ); } } void Sentient::WeaponDoneFiring ( Event *ev ) { Weapon *best; if ( newWeapon ) { ChangeWeapon( newWeapon ); } else if ( !currentWeapon || !currentWeapon->HasAmmo() || !currentWeapon->ReadyToUse() ) { best = BestWeapon(); if ( best ) { ChangeWeapon( best ); } } } EXPORT_FROM_DLL void Sentient::AnimLoop ( Event *ev ) { Event *t; animOverride = false; if ( deadflag ) { StopAnimating(); } else if ( animating ) { RandomAnimate( currentAnim.c_str(), NULL ); } else { stopanimating_tillchange = true; } if ( tempAnimEvent ) { t = tempAnimEvent; tempAnimEvent = NULL; ProcessEvent( t ); } } EXPORT_FROM_DLL void Sentient::SetAnim ( const char *anim ) { assert( anim ); assert ( !deadflag ); if ( str( anim ) == currentAnim ) { if (!animating && !stopanimating_tillchange) StartAnimating(); return; } stopanimating_tillchange = false; currentAnim = str( anim ); if ( animOverride ) { return; } RandomAnimate( currentAnim.c_str(), EV_Sentient_AnimLoop ); if (!animating) StartAnimating(); } EXPORT_FROM_DLL void Sentient::TempAnim ( const char *anim, Event *event ) { assert ( !deadflag ); assert( anim ); animOverride = true; tempAnimEvent = event; RandomAnimate( anim, EV_Sentient_AnimLoop ); } void Sentient::TempAnim ( const char *anim, Event &event ) { Event *ev; ev = new Event( event ); TempAnim( anim, ev ); } void Sentient::PrintDamageLocationToAttacker ( edict_s *attacker, const char *victim_name, const char *location ) { const char *expanded_location; expanded_location = ExpandLocation( location ); if ( expanded_location ) gi.cprintf ( attacker, PRINT_MEDIUM, "You hit %s in the %s.\n", victim_name, expanded_location ); } void Sentient::PrintDamageLocationToVictim ( edict_s *victim, const char *location ) { const char *expanded_location; expanded_location = ExpandLocation( location ); if ( expanded_location ) gi.cprintf ( victim, PRINT_MEDIUM, "%s damage\n", expanded_location ); } qboolean Sentient::DoGib ( int meansofdeath, Entity *inflictor ) { if ( !( flags & FL_DIE_GIBS ) || parentmode->value ) { return false; } if ( ( meansofdeath == MOD_TELEFRAG ) || ( meansofdeath == MOD_MUTANTHANDS ) || ( meansofdeath == MOD_HELIGUN ) || ( meansofdeath == MOD_CTFTURRET ) || ( meansofdeath == MOD_LAVA ) || ( meansofdeath == MOD_SNIPER ) || ( ( meansofdeath == MOD_FISTS ) && ( inflictor->flags & FL_ADRENALINE ) ) || ( ( meansofdeath == MOD_SHOTGUN ) && ( G_Random() < 0.5 ) ) ) { return true; } if ( health > -75 ) { return false; } // Impact and Crush < -75 health if ( ( meansofdeath == MOD_IMPACT ) || ( meansofdeath == MOD_CRUSH ) || ( meansofdeath == MOD_VEHICLE ) ) { return true; } // Any projectiles except spear if ( inflictor->isSubclassOf( Projectile ) ) { if ( meansofdeath != MOD_SPEARGUN ) { return true; } } // Shotgun only 10% of the time if ( meansofdeath == MOD_SHOTGUN ) { if ( G_Random() > 0.5 ) { return true; } } return false; } void Sentient::ArmorDamage ( Event *ev ) { Armor *armor=0; Entity *inflictor; Entity *attacker; float damage; float armor_damage=0; Vector momentum; Vector position; Vector normal; Vector direction; Event *event; int dflags; int meansofdeath; int knockback; float damage_mult; int groupnum; int trinum; const char *location=""; float unmodified_damage=0; if ( ( takedamage == DAMAGE_NO ) || ( movetype == MOVETYPE_NOCLIP ) ) { if ( isClient () ) { Player *player; player = ( Player * )this; if ( !player->GetVehicle() ) return; } else { return; } } damage = ev->GetFloat ( 1 ); inflictor = ev->GetEntity ( 2 ); attacker = ev->GetEntity ( 3 ); position = ev->GetVector ( 4 ); direction = ev->GetVector ( 5 ); normal = ev->GetVector ( 6 ); knockback = ev->GetInteger( 7 ); dflags = ev->GetInteger( 8 ); meansofdeath = ev->GetInteger( 9 ); groupnum = ev->GetInteger( 10 ); trinum = ev->GetInteger( 11 ); damage_mult = ev->GetFloat ( 12 ); // Make sure attacker is there if ( !attacker ) return; // Forcefields make objects invulnerable if ( flags & FL_FORCEFIELD ) { float alpha; float radius; Vector delta; Vector org; Entity *forcefield; // // spawn forcefield // forcefield = new Entity; delta = centroid - worldorigin; radius = delta.length(); // // transform the centroid around by the current orientation // MatrixTransformVector( delta.vec3(), orientation, org.vec3() ); org += worldorigin; forcefield->setModel( "sphere2.def" ); forcefield->setOrigin( org ); forcefield->worldorigin.copyTo(forcefield->edict->s.old_origin); forcefield->setMoveType( MOVETYPE_NONE ); forcefield->setSolidType( SOLID_NOT ); forcefield->edict->s.scale = radius / 16; alpha = ( damage * damage_mult ) / 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; } // if enabled you can't hurt teammates (but you can hurt yourself) // knockback still occurs if ( ( attacker != this ) && ( DM_FLAG( DF_MODELTEAMS | DF_SKINTEAMS ) || ctf->value ) ) { if ( OnSameTeam ( this, attacker ) ) { // check for friendly fire if ( DM_FLAG( DF_FRIENDLY_FIRE ) || ( dflags & DAMAGE_NO_PROTECTION ) ) meansofdeath = MOD_FRIENDLY_FIRE; else damage = 0; } } if (groupnum == -1) location = "all"; else location = gi.Group_NumToName( edict->s.modelindex, groupnum ); // Adjust damage for skill level if ( isClient() && !deathmatch->value && !(dflags & DAMAGE_NO_SKILL)) { // Clamp the damage multiplier if ( damage_mult > 2 ) damage_mult = 2; switch( ( int )skill->value ) { case 0: damage *= 0.2f; break; case 1: damage *= 0.4f; break; case 2: damage *= 0.6f; break; default: damage *= 0.8f; break; } } // Shields reduce damage by 75% if ( flags & FL_SHIELDS ) { damage *= 0.25f; } else if ( flags & FL_MUTANT ) { damage *= 0.33f; } // If in deathmatch, client takes a minimum of 33% damage from source if ( deathmatch->value ) { damage *= 0.66f; unmodified_damage = damage * 0.33f; } // check to see if we have armor in this location to reduce the damage if (location[0] && !(dflags & DAMAGE_NO_ARMOR) ) { if ( !strncmp( location,"all",3 ) ) { char *armortypes[] = {"RiotHelmet", "FlakJacket", "FlakPants", NULL}; int i = 0; float reduced_damage = 0; // Go through all the armor types while (armortypes[i]) { float partial_damage = damage / 3; float odam = partial_damage; armor = ( Armor * )FindItem( armortypes[i] ); // Check for armor in this spot if ( armor && ( armor->Amount() > 0 ) ) { // Reduce the parital damage by the amount of armor partial_damage -= armor->Amount(); if ( partial_damage < 0 ) partial_damage = 0; // Remove the amount absorbed by the armor from the armor. armor->Remove( odam - partial_damage ); // Keep track of the damage done to the armor for testing purposes armor_damage += (odam - partial_damage ); } // Keep track of damage done after armor has been applied reduced_damage += partial_damage; i++; } // This is the damage after all armor is done damage = reduced_damage; } // else check for specific location based else if ( !strncmp( location,"head",4 ) ) armor = ( Armor * )FindItem( "RiotHelmet" ); else if ( !strncmp(location,"torso",4 ) ) armor = ( Armor * )FindItem( "FlakJacket" ); // else if ( !strncmp( location,"arm",3 ) ) // armor = ( Armor * )FindItem( "FlakJacket" ); else if ( !strncmp(location,"leg",3 ) ) armor = ( Armor * )FindItem( "FlakPants" ); // If armor is there, remove the appropriate amount if ( armor && ( armor->Amount() > 0 ) && !(dflags & DAMAGE_NO_ARMOR) ) { float odam = damage; damage -= armor->Amount(); if ( damage < 0 ) damage = 0; armor->Remove( odam-damage ); armor_damage += (odam - damage ); } } damage += unmodified_damage; // Damage multiplier damage *= damage_mult; if ( ctf->value ) { // Check to see if the attacker had double if ( attacker->isClient() ) { Player *pl_attacker; pl_attacker = ( Player * )( Sentient * )attacker; if ( pl_attacker->HasItem( "CTF_Tech_Double" ) ) // DOUBLE damage *= 2; else if ( pl_attacker->HasItem( "CTF_Tech_DeathQuad" ) ) // QUAD damage *= 4; } // Check to see if the client has the shield or empathy if ( this->isClient() ) { Player *pl_defender; Event *ctfev; pl_defender = ( Player * )this; if ( pl_defender->HasItem( "CTF_Tech_Half" ) && !( dflags & DAMAGE_NO_ARMOR ) ) { damage *= 0.3f; ctfev = new Event( EV_Player_CTF_SoundEvent ); ctfev->AddInteger( DAMAGE ); pl_defender->ProcessEvent( ctfev ); } else if ( pl_defender->HasItem( "CTF_Tech_Empathy" ) ) // EMPATHY { ctfev = new Event( EV_Player_CTF_SoundEvent ); ctfev->AddInteger( DAMAGE ); pl_defender->ProcessEvent( ctfev ); // Send the damage back to the attacker if it is a client if ( ( attacker != this ) && attacker->isClient() ) { Event *ev2; ev2 = new Event( EV_Damage ); ev2->AddInteger( damage ); ev2->AddEntity ( this ); ev2->AddEntity ( this ); ev2->AddVector ( vec_zero ); ev2->AddVector ( vec_zero ); ev2->AddVector ( vec_zero ); ev2->AddInteger( 0 ); ev2->AddInteger( DAMAGE_NO_ARMOR ); ev2->AddInteger( MOD_EMPATHY ); ev2->AddInteger( -1 ); ev2->AddInteger( -1 ); ev2->AddFloat ( 1 ); attacker->PostEvent( ev2, 0 ); //attacker->Damage( this, this, damage, vec_zero, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_EMPATHY, -1, -1, 1.0f ); } } } } // Damage skins if armor didn't absorb all damage if ( ( damage > 0 ) && ( groupnum != -1 ) && ( flags & FL_BLOOD ) ) if ( !edict->s.groups[ groupnum ] ) edict->s.groups[ groupnum ]++; // Show location based damage. if ( sv_showdamagelocation->value && deathmatch->value ) { // Send message to attacker if ( attacker->isClient() ) { if ( this->client ) { PrintDamageLocationToAttacker( attacker->edict, this->client->pers.netname, location ); } } // Send message to victim if ( this->isClient() && attacker != this ) { PrintDamageLocationToVictim(this->edict, location); } } // Shows detailed damage messages if ( sv_showdamage->value ) { // Send message to attacker if ( attacker->isClient() ) { if ( this->client ) { gi.cprintf ( attacker->edict, PRINT_HIGH, "TARG:%s ARMOR_DAM:%0.2f TOT_DAM:%0.2f LOC:%s\n", this->client->pers.netname, armor_damage, damage, location ); } else { gi.cprintf ( attacker->edict, PRINT_HIGH, "TARG:%s ARMOR_DAM:%0.2f TOT_DAM:%0.2f LOC:%s\n", getClassname(), armor_damage, damage, location ); } } // Send message to victim if ( this->isClient() && attacker != this ) { gi.cprintf ( this->edict, PRINT_HIGH, "TARG:%s ARMOR_DAM:%0.2f TOT_DAM:%0.2f LOC:%s\n", this->client->pers.netname, armor_damage, damage, location ); } } // Blood and sparks effects if ( ( damage > 0 ) && ( meansofdeath != MOD_DROWN ) && ( meansofdeath != MOD_MUTANT_DRAIN ) ) { // Blood particles if ( flags & FL_BLOOD ) { Particles( position, normal, __min( 150,damage ), 127, PARTICLE_RANDOM ); // Blood splat if ( dflags & DAMAGE_BULLET ) SprayBlood( position, direction, damage ); } // Sparks from metal else if ( flags & FL_SPARKS ) { SpawnSparks (position, normal, __min(damage, 75) ); } } else if ( armor_damage > 0 ) { // Sparks off armor Particles( position, normal, __min(armor_damage, 75), 122, 0 ); } // Gib if we are dead and get hit by a rocket or shotgun if ( deadflag == DEAD_DEAD ) { health -= damage; if ( DoGib( meansofdeath, inflictor ) ) { Event *gibEv; gibEv = new Event( EV_Gib ); gibEv->AddInteger( 0 ); ProcessEvent( gibEv ); } return; } else if ( deadflag ) { health -= damage; return; } // Do the kick if (!(dflags & DAMAGE_NO_KNOCKBACK)) { if ((knockback) && (movetype != MOVETYPE_NONE) && (movetype != MOVETYPE_BOUNCE) && (movetype != MOVETYPE_PUSH) && (movetype != MOVETYPE_STOP)) { float m; if (mass < 50) m = 50; else m = mass; direction.normalize(); if ( isClient() && ( attacker == this ) && deathmatch->value ) momentum = direction * ( 2100.0f * ( float )knockback / m ); // the rocket jump hack... else momentum = direction * ( 500.0f * ( float )knockback / m ); if ( dflags & DAMAGE_BULLET ) { // Clip the z velocity for bullet weapons if ( momentum.z > 75) momentum.z = 75; } velocity += momentum; } } // check for godmode or invincibility if ( flags & FL_GODMODE ) { return; } // do the damage health -= damage; if ( ctf->value ) CTF_CheckHurtCarrier( this, attacker ); // He's dead jim, send out a bunch of events if ( health <= 0 ) { if ( ( meansofdeath == MOD_ION ) && !( flags & FL_NOION ) ) { flags |= FL_DIE_TESSELATE; RandomGlobalSound( "snd_tesselate" ); } event = new Event( EV_Killed ); event->AddEntity( attacker ); event->AddInteger( damage ); event->AddEntity( inflictor ); event->AddString( location ); event->AddInteger( meansofdeath ); ProcessEvent( event ); return; } if ( ( damage > 0 ) && ( meansofdeath == MOD_ION ) && !( flags & FL_NOION ) ) { // Do the ion particlizer effect TesselateModel ( this, 1, 1000, direction, damage, 1.0f, 0, vec3_origin, TESSELATE_EXPANDANDSHRINK, 126 ); } // Tesselate from damage if (flags & FL_TESSELATE) { TesselateModel ( this, tess_min_size, tess_max_size, direction, damage, 1.0f, tess_thickness, vec3_origin ); } // Darken if we are hurt 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; } // Drop your weapon if you get hit in the hands if ( location == gun_bone_group_name && ( ( damage > 50 ) || ( G_Random( 1 ) > 0.97f ) ) ) { // don't drop weapons when in deathmatch and DF_NO_DROP_WEAPONS is set if ( currentWeapon && currentWeapon->IsDroppable() && ( !deathmatch->value || !DM_FLAG( DF_NO_DROP_WEAPONS ) ) ) { DropCurrentWeapon(); WeaponKnockedFromHands(); } } if ( ( meansofdeath == MOD_MUTANT_DRAIN ) || ( meansofdeath == MOD_DEATHQUAD ) ) return; // Send pain event event = new Event( EV_Pain ); event->AddFloat( damage ); event->AddEntity( attacker ); event->AddString( location ); event->AddInteger( meansofdeath ); ProcessEvent( event ); } void Sentient::UpdateSilencedWeapons ( void ) { // If the current weapon is silencable, then change it. if ( currentWeapon && currentWeapon->IsSilenced() ) { currentWeapon->ProcessEvent(EV_Weapon_PutAwayAndRaise); } } int Sentient::NumInventoryItems ( void ) { return inventory.NumObjects(); } Item *Sentient::NextItem ( Item *item ) { Item *next_item; int i; int n; qboolean item_found = false; if ( !item ) { item_found = true; } else if ( !inventory.ObjectInList( item->entnum ) ) { error( "NextItem", "Item not in list" ); } n = inventory.NumObjects(); for( i = 1; i <= n; i++ ) { next_item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( next_item ); if ( next_item->isSubclassOf( InventoryItem ) && item_found ) return next_item; if ( next_item == item ) item_found = true; } return NULL; } Item *Sentient::PrevItem ( Item *item ) { Item *prev_item; int i; int n; qboolean item_found = false; if ( !item ) { item_found = true; } else if ( !inventory.ObjectInList( item->entnum ) ) { error( "NextItem", "Item not in list" ); } n = inventory.NumObjects(); for( i = n; i >= 1; i-- ) { prev_item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( prev_item ); if ( prev_item->isSubclassOf( InventoryItem ) && item_found) return prev_item; if ( prev_item == item ) item_found = true; } return NULL; } // This will search a dead body and create a floating inventory for that player // which will be displayed on the client as a list of icons. If the user presses // use, then he will pick up the items in that floating inventory, and the body // will be faded out. void Sentient::SearchBody ( Event *ev ) { int i,n; Item *item; Entity *other; Player *player; Event *event; qboolean inventory_created = false; other = ev->GetEntity( 1 ); assert( other ); // Only players can touch this if ( !other->isSubclassOf( Player ) ) return; // Can't touch yourself if ( other == this ) return; // Make sure the body is dead if ( !deadflag ) return; n = inventory.NumObjects(); // No items, get out of here if ( n == 0 ) { return; } player = ( Player * )other; // Check to see if this inventory is already being displayed for // this body. if ( player->GetFloatingOwner() == this ) { // Still touching the body, so extend the clear time player->CancelEventsOfType( EV_Player_ClearFloatingInventory ); event = new Event( EV_Player_ClearFloatingInventory ); player->PostEvent( event, 0.4f ); return; } else if ( player->GetFloatingOwner() ) { // Currently looking at an inventory, so don't bother with this one. return; } else { // Make sure the inventory is cleared out before making a new one. // Could have some left over items in there from other bodies player->ProcessEvent( EV_Player_ClearFloatingInventory ); } // Create the inventory for( i = 1; i <= n; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( item ); if ( item->Amount() ) { player->AddItemToFloatingInventory( item ); inventory_created = true; } } if (!inventory_created) return; // Set the current owner of the inventory player->SetFloatingOwner( this ); // Send the inventory to the client player->SendFloatingInventory(); // Clear the inventory in the future event = new Event( EV_Player_ClearFloatingInventory ); player->PostEvent( event, 0.3f ); } void Sentient::UselessCheck( Event *ev ) { Item *item; int n; int i; Event *event; // Check to see if we have any inventory and // remove if we don't n = inventory.NumObjects(); for ( i = 1; i <= n; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( item ); if ( item->Amount() ) { PostEvent ( EV_Sentient_UselessCheck, 1.0f + G_Random() ); return; } } event = new Event( "remove_useless" ); ProcessEvent( event ); } qboolean Sentient::HasInventoryOfType ( const char *type ) { int num; int i; Item *item; num = inventory.NumObjects(); for( i = num; i > 0; i-- ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( checkInheritance( type, item->getClassname() ) ) { return true; } } return false; } void Sentient::DropInventoryItems ( void ) { int num; int i; Item *item; // Drop any inventory items num = inventory.NumObjects(); for( i = num; i >= 1; i-- ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( item->isSubclassOf( InventoryItem ) ) { item->Drop(); } } } qboolean Sentient::PowerupActive ( void ) { if ( poweruptype && this->client ) { gi.cprintf( edict, PRINT_HIGH, "You are already using a powerup\n" ); } return poweruptype; } void Sentient::SprayBlood ( Vector src, Vector dir, float damage ) { trace_t trace; Vector norm; Vector end; Vector ang; float dist; float scale; BloodSplat *splat; dir.normalize(); end = src + dir * 2048; trace = G_Trace( src, vec_zero, vec_zero, end, this, MASK_SOLIDNONFENCE, "Sentient::SprayBlood" ); if ( HitSky( &trace ) || ( trace.ent->solid != SOLID_BSP ) || ( trace.ent->s.number != 0 ) ) { return; } dist = ( Vector( trace.endpos ) - src ).length(); scale = damage / ( dist * 0.3 ); if ( scale > 0.6 ) { scale = 0.6; } if ( scale < 0.02 ) { return; } 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 ); } EXPORT_FROM_DLL void Sentient::setModel ( const char *mdl ) { if ( currentWeapon ) { // rebind the current gun currentWeapon->DetachFromOwner(); Entity::setModel( mdl ); currentWeapon->AttachToOwner(); } else { Entity::setModel( mdl ); } } void Sentient::TurnOffShadow ( Event *ev ) { // HACK FIXME // // temporary shadow flag // edict->s.renderfx &= ~RF_XFLIP; } EXPORT_FROM_DLL void Sentient::Archive ( Archiver &arc ) { int i; int num; Entity::Archive( arc ); num = inventory.NumObjects(); arc.WriteInteger( num ); for( i = 1; i <= num; i++ ) { arc.WriteInteger( inventory.ObjectAt( i ) ); } arc.WriteObjectPointer( currentWeapon ); arc.WriteObjectPointer( currentItem ); arc.WriteObjectPointer( newWeapon ); arc.WriteString( currentAnim ); arc.WriteBoolean( animOverride ); arc.WriteEvent( *tempAnimEvent ); arc.WriteString( gun_bone_group_name ); arc.WriteBoolean( stopanimating_tillchange ); arc.WriteInteger( poweruptype ); arc.WriteInteger( poweruptimer ); arc.WriteBoolean( sentientFrozen ); arc.WriteBoolean( dropweapon ); arc.WriteVector( gunoffset ); arc.WriteVector( eyeposition ); arc.WriteFloat( firedowntime ); arc.WriteBoolean( firedown ); arc.WriteString( saveskin ); arc.WriteString( savemodel ); } EXPORT_FROM_DLL void Sentient::Unarchive ( Archiver &arc ) { int i; int num; int temp; Entity::Unarchive( arc ); inventory.FreeObjectList(); num = arc.ReadInteger(); for( i = 1; i <= num; i++ ) { temp = arc.ReadInteger(); inventory.AddObject( temp ); } arc.ReadObjectPointer( ( Class ** )¤tWeapon ); arc.ReadObjectPointer( ( Class ** )¤tItem ); arc.ReadObjectPointer( ( Class ** )&newWeapon ); arc.ReadString( ¤tAnim ); arc.ReadBoolean( &animOverride ); //FIXME // make a ReadEventPointer function tempAnimEvent = new Event( arc.ReadEvent() ); arc.ReadString( &gun_bone_group_name ); arc.ReadBoolean( &stopanimating_tillchange ); arc.ReadInteger( &poweruptype ); arc.ReadInteger( &poweruptimer ); arc.ReadBoolean( &sentientFrozen ); arc.ReadBoolean( &dropweapon ); arc.ReadVector( &gunoffset ); arc.ReadVector( &eyeposition ); arc.ReadFloat( &firedowntime ); arc.ReadBoolean( &firedown ); arc.ReadString( &saveskin ); arc.ReadString( &savemodel ); } void Sentient::WritePersistantData ( SpawnArgGroup &group ) { int i; int num; Entity *ent; G_SetSpawnArg( "classname", getClassname() ); if ( currentWeapon ) { G_SetSpawnArg( "current_weapon", currentWeapon->getClassname() ); } if ( currentItem ) { G_SetSpawnArg( "current_item", currentItem->getClassname() ); } G_SetIntArg( "health", health ); G_SetIntArg( "max_health", max_health ); G_SetIntArg( "savegame", 1 ); group.AddArgs(); num = inventory.NumObjects(); for( i = 1; i <= num; i++ ) { ent = G_GetEntity( inventory.ObjectAt( i ) ); if ( ent && ent->isSubclassOf( Item ) ) { G_InitSpawnArguments(); ( ( Item * )ent )->CreateSpawnArgs(); G_SetSpawnArg( "classname", ent->getClassname() ); G_SetIntArg( "savegame", 1 ); group.AddArgs(); } } G_InitSpawnArguments(); } void Sentient::RestorePersistantData ( SpawnArgGroup &group ) { str weap; str item; Entity *ent; int i; int num; Item *it; num = group.NumInGroup(); for( i = 2; i <= num; i++ ) { group.RestoreArgs( i ); game.force_entnum = false; ent = G_CallSpawn(); if ( !ent->isSubclassOf( Item ) ) { error( "RestorePersistantData", "Loaded non-Item object of class %s\n", ent->getClassname() ); } // G_CallSpawn clears all the spawnargs group.RestoreArgs( i ); it = ( Item * )ent; it->SetOwner( this ); it->ProcessPendingEvents(); AddItem( it ); } group.RestoreArgs( 1 ); weap = G_GetStringArg( "current_weapon" ); item = G_GetStringArg( "current_item" ); health = G_GetIntArg( "health", 100 ); max_health = G_GetIntArg( "max_health", 100 ); if ( weap.length() ) { it = FindItem( weap.c_str() ); assert( it ); if ( it ) { if ( !it->isSubclassOf( Weapon ) ) { error( "RestorePersistantData", "Tried to set non-weapon of type %s as current weapon\n", it->getClassname() ); } SetCurrentWeapon( ( Weapon * )it ); } } if ( item.length() ) { it = FindItem( item.c_str() ); assert( it ); if ( it ) { if ( !it->isSubclassOf( InventoryItem ) ) { error( "RestorePersistantData", "Tried to set non-inventory item of type %s as current item\n", it->getClassname() ); } currentItem = ( InventoryItem * )it; } } } EXPORT_FROM_DLL void Sentient::Freeze ( Event *ev ) { sentientFrozen = true; } EXPORT_FROM_DLL void Sentient::UnFreeze ( Event *ev ) { sentientFrozen = false; } void Sentient::ImpactDamage ( Event *ev ) { float damg; if ( deathmatch->value ) { damg = velocity.length(); damg /= 20.0f; } else { damg = 100; } setMoveType( MOVETYPE_STEP ); Damage(world, enemy, damg, worldorigin, vec_zero, vec_zero, 0, 0, MOD_IMPACT, -1, -1, 1.0f ); } void Sentient::WeaponUse ( Event *ev ) { Event *event; if ( !currentWeapon ) return; currentWeapon->ForceState( WEAPON_READY ); event = new Event( EV_Weapon_SecondaryUse ); event->AddEntity( this ); currentWeapon->ProcessEvent( event ); } void Sentient::DoubleArmor ( void ) { int i,n; n = inventory.NumObjects(); for( i = 1; i <= n; i++ ) { Item *item; item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( item->isSubclassOf( Armor ) ) item->Set( item->Amount() * 2 ); } } void Sentient::WeaponKnockedFromHands ( void ) { str realname; realname = GetRandomAlias( "snd_lostweapon" ); if ( realname.length() > 1 ) { sound( realname, 1, CHAN_VOICE ); } else { RandomSound( "snd_pain", 1, CHAN_VOICE ); } } void Sentient::SetDropWeapon ( Event *ev ) { dropweapon = ev->GetInteger( 1 ); } void Sentient::DropWeaponNowEvent ( Event *ev ) { DropCurrentWeapon(); }