2337 lines
No EOL
49 KiB
C++
2337 lines
No EOL
49 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Logfile:: /Quake 2 Engine/Sin/code/game/weapon.cpp $
|
|
// $Revision:: 143 $
|
|
// $Author:: Jimdose $
|
|
// $Date:: 12/16/98 5:43p $
|
|
//
|
|
// 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/weapon.cpp $
|
|
//
|
|
// 143 12/16/98 5:43p Jimdose
|
|
// infiniterockets no longer gives infinite mines
|
|
//
|
|
// 142 12/14/98 7:00p Jimdose
|
|
// added sv_infinitesniper
|
|
//
|
|
// 141 12/09/98 8:06p Jimdose
|
|
// added infinite ammo vars
|
|
//
|
|
// 140 11/19/98 9:29p Jimdose
|
|
// fixed dropping weapons when on different gravaxis
|
|
//
|
|
// 139 11/18/98 12:21a Jimdose
|
|
// Fixed gravaxis stuff.
|
|
//
|
|
// 138 11/15/98 11:19p Jimdose
|
|
// Fixed bug where reloading only worked if you had less ammo in your clip than
|
|
// in your inventory
|
|
//
|
|
// 137 11/13/98 1:47a Markd
|
|
// Put in FL_ROTATEDBOUNDS modification when picked up and then dropped
|
|
//
|
|
// 136 11/07/98 10:46p Markd
|
|
// don't reload weapons while in cinematic
|
|
//
|
|
// 135 10/22/98 7:57p Markd
|
|
// put in proper pre-caching in all the classes
|
|
//
|
|
// 134 10/22/98 12:19a Jimdose
|
|
// Added aimanim and aimframe
|
|
// Added SetAimAnim
|
|
//
|
|
// 133 10/21/98 5:29p Aldie
|
|
// Moved around mutant checks for picking up weapons
|
|
//
|
|
// 132 10/21/98 1:30a Jimdose
|
|
// DoneReloading now sends a weapon ready event to sentient
|
|
//
|
|
// 131 10/20/98 8:23p Jimdose
|
|
// no longer pickup weapons when you already have that weapon and enough ammo
|
|
// no longer switch to weapon when picking up if you already have it
|
|
//
|
|
// 130 10/19/98 12:07a Jimdose
|
|
// made all code use fast checks for inheritance (no text lookups when
|
|
// possible)
|
|
// isSubclassOf no longer requires ::_classinfo()
|
|
// moved Magnum-specific Drop code to magnum.cpp
|
|
//
|
|
// 129 10/18/98 6:42p Markd
|
|
// Added notdroppable event to weapons
|
|
//
|
|
// 128 10/16/98 9:50p Aldie
|
|
// Added SecondaryAmmo command
|
|
//
|
|
// 127 10/16/98 12:59a Aldie
|
|
// Fixed ammo types
|
|
//
|
|
// 126 10/14/98 1:46a Markd
|
|
// Moved RandomGlobalSound to null out weaponidle before detach
|
|
//
|
|
// 125 10/14/98 1:20a Jimdose
|
|
// Got cross-level persistant info working
|
|
//
|
|
// 124 10/11/98 7:41p Aldie
|
|
// Mutate and restore commands for Richard
|
|
//
|
|
// 123 10/10/98 5:58p Aldie
|
|
// More quantumdestab fixes
|
|
//
|
|
// 122 10/09/98 2:07a Aldie
|
|
// Updated DMFLAGS
|
|
//
|
|
// 121 10/08/98 5:47p Aldie
|
|
// Be able to pickup up dropped weapons in DF_WEAPONSSTAY mode
|
|
//
|
|
// 120 10/08/98 12:36a Jimdose
|
|
// Added ForceIdle
|
|
//
|
|
// 119 10/06/98 5:24p Markd
|
|
// put in bullet-proofing of owner variable
|
|
//
|
|
// 118 10/05/98 10:43p Aldie
|
|
// Converted over to new silencer methods
|
|
//
|
|
// 117 10/04/98 10:25p Markd
|
|
// Took out annoying warning, made the trace for gun position to muzzle
|
|
// position more bullet proof
|
|
//
|
|
// 116 10/03/98 7:14p Markd
|
|
// fixed muzzle position stuff
|
|
//
|
|
// 115 10/01/98 8:00p Aldie
|
|
// Don't pickup a weapon when mutant
|
|
//
|
|
// 114 9/19/98 4:47p Markd
|
|
// fixed music stuff and added actionincrement to weapons
|
|
//
|
|
// 113 9/12/98 11:10p Aldie
|
|
// Don't detach in putaway
|
|
//
|
|
// 112 9/12/98 5:16p Jimdose
|
|
// Added call to DetachGun in PutAway
|
|
//
|
|
// 111 9/09/98 6:24p Markd
|
|
// Added IsDroppable method
|
|
//
|
|
// 110 9/07/98 7:49p Markd
|
|
// went back to previous attaching of weapons
|
|
//
|
|
// 109 9/07/98 6:21p Markd
|
|
// don't attach if no worldmodel
|
|
//
|
|
// 108 9/05/98 12:13p Aldie
|
|
// Fixed a bug that 2015 found.
|
|
//
|
|
// 107 9/02/98 11:08a Markd
|
|
// Put in setModel into Sentient so that weapon could be properly detached and
|
|
// re-attached again.
|
|
//
|
|
// 106 8/31/98 7:45p Aldie
|
|
// Updated surface data structure and removed surfinfo field
|
|
//
|
|
// 105 8/31/98 4:19p Markd
|
|
// Made GetMuzzlePosition behave a bit more rationally.
|
|
//
|
|
// 104 8/29/98 9:47p Jimdose
|
|
// Added call info to G_Trace
|
|
//
|
|
// 103 8/29/98 5:27p Markd
|
|
// added specialfx, replaced misc with specialfx where appropriate
|
|
//
|
|
// 102 8/19/98 4:56p Jimdose
|
|
// Added check for "gun" bone to MuzzleOffset
|
|
//
|
|
// 101 8/18/98 8:12p Aldie
|
|
// Added dual mode weapons to base class
|
|
//
|
|
// 100 8/18/98 7:39p Aldie
|
|
// Added dual weapon modes to base class
|
|
//
|
|
// 99 8/14/98 5:48p Aldie
|
|
// Removed bloodsplat completely from weapon
|
|
//
|
|
// 98 8/13/98 7:29p Aldie
|
|
// Fixed magnum dropping when it gets shot out of your hand.
|
|
//
|
|
// 97 8/10/98 6:53p Aldie
|
|
// Added a better scaling for bloodsplats.
|
|
//
|
|
// 96 8/06/98 10:53p Aldie
|
|
// Added weapon tweaks and kickback. Also modified blast radius damage and
|
|
// rocket jumping.
|
|
//
|
|
// 95 8/06/98 6:54p Jimdose
|
|
// Added min/max range, and projkectile speed
|
|
//
|
|
// 94 7/26/98 2:13p Aldie
|
|
// Don't put bulletholes on hidden objects
|
|
//
|
|
// 93 7/26/98 9:22a Jimdose
|
|
// Bulletholes are now bound to entities
|
|
//
|
|
// 92 7/25/98 6:35p Aldie
|
|
// Bulletholes only in single player
|
|
//
|
|
// 91 7/24/98 3:50p Aldie
|
|
// Fix for deathmatch flag weapons staying around
|
|
//
|
|
// 90 7/22/98 9:58p Markd
|
|
// Added Raising, PuttingAway and Reloading functions
|
|
//
|
|
// 89 7/22/98 8:18p Aldie
|
|
// Made sure reloading only happens when weapon is ready.
|
|
//
|
|
// 88 7/22/98 7:03p Aldie
|
|
// Fixed reloading while reloading :-)
|
|
//
|
|
// 87 7/22/98 5:16p Aldie
|
|
// Fixed NextAttack time
|
|
//
|
|
// 86 7/20/98 6:43p Markd
|
|
// Put in new muzzle offset stuff
|
|
//
|
|
// 85 7/20/98 3:54p Aldie
|
|
// Made weapons post the init commands in the future.
|
|
//
|
|
// 84 7/19/98 5:41p Markd
|
|
// Added DetachFromOwner method
|
|
//
|
|
// 83 7/17/98 4:04p Markd
|
|
// Added HasAnim support to putaway and ready
|
|
//
|
|
// 82 7/17/98 11:32a Aldie
|
|
// Added clipammo and forcereload stuff
|
|
//
|
|
// 81 7/15/98 9:59p Markd
|
|
// Made muzzle flashes into TEMP_DLIGHT's
|
|
//
|
|
// 80 7/14/98 6:58p Aldie
|
|
// Made dropped weapons fade out
|
|
//
|
|
// 79 7/14/98 5:52p Aldie
|
|
// Don't drop magnums in deathmatch. Made the delay to pick up dropped weapons
|
|
// a little longer for the owner.
|
|
//
|
|
// 78 7/14/98 3:54p Markd
|
|
// made weapons cycle through their models when spawning
|
|
//
|
|
// 77 7/12/98 7:27p Jimdose
|
|
// Removed code setting scale and frame
|
|
//
|
|
// 76 7/11/98 8:20p Jimdose
|
|
// Drop now returns true or false depending upon whether the item can be
|
|
// dropped or not
|
|
//
|
|
// 75 7/10/98 5:39p Aldie
|
|
// Fixed printf of a warning
|
|
//
|
|
// 74 7/10/98 12:06a Aldie
|
|
// Autochange override when you pickup a new weapon. Currently only the
|
|
// sniperrifle will not be putaway if you are using it.
|
|
//
|
|
// 73 7/09/98 10:41p Aldie
|
|
// If no barrel in weapon, look for barrel bone in owner
|
|
//
|
|
// 72 7/01/98 7:04p Aldie
|
|
// Fixed assert with weapon reloading after death
|
|
//
|
|
// 71 6/26/98 12:24p Markd
|
|
// Changed SetAmmoAmount fixed picking upweapons
|
|
//
|
|
// 70 6/26/98 11:30a Markd
|
|
// Fixed weapon pickup bug
|
|
//
|
|
// 69 6/25/98 8:47p Markd
|
|
// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup
|
|
// method
|
|
//
|
|
// 68 6/25/98 12:43p Aldie
|
|
// Changed bloodsplat scale and made a triggerdelay for dropping weapons
|
|
//
|
|
// 67 6/24/98 1:38p Aldie
|
|
// Implementation of inventory system and picking stuff up
|
|
//
|
|
// 66 6/19/98 9:28p Jimdose
|
|
// Added general weapon muzzle code
|
|
//
|
|
// 65 6/17/98 1:20a Jimdose
|
|
// Moved setOwner to Item.
|
|
// Added EV_Item_Pickup
|
|
//
|
|
// 64 6/16/98 4:08p Jimdose
|
|
// Gave dropping weapons velocity
|
|
//
|
|
// 63 6/15/98 10:39a Aldie
|
|
// Created MuzzleOffset derived from MuzzlePosition
|
|
//
|
|
// 62 6/11/98 12:40a Markd
|
|
// Added BasePosition and MuzzlePosition
|
|
//
|
|
// 61 6/10/98 9:49p Markd
|
|
// Made Finish attack set attack_finished to level.time not 0
|
|
//
|
|
// 60 6/10/98 7:45p Aldie
|
|
// Updated some reload functions.
|
|
//
|
|
// 59 6/10/98 3:46p Markd
|
|
// replaced instance of AmmoAvailable with HasAmmor to make monsters work right
|
|
//
|
|
// 58 6/10/98 2:10p Aldie
|
|
// Updated damage function.
|
|
//
|
|
// 57 6/10/98 1:18p Markd
|
|
// Added SetAmmoClipSize event removed NextAttack(0) from AttackDone
|
|
//
|
|
// 56 6/09/98 7:37p Markd
|
|
// Put in "0" ammo_clip_size support
|
|
//
|
|
// 55 6/09/98 3:58p Markd
|
|
// Fixed bug with double pickup of ammos from dropped guns
|
|
//
|
|
// 54 6/09/98 12:54p Aldie
|
|
// Updated ReadyToFire function
|
|
//
|
|
// 53 6/08/98 8:39p Markd
|
|
// Fixed some reload issues
|
|
//
|
|
// 52 6/08/98 8:19p Markd
|
|
// Added reloading support and ReadyToUse function
|
|
//
|
|
// 51 6/08/98 5:43p Markd
|
|
// removed "shoot" command from Fire function
|
|
//
|
|
// 50 6/05/98 6:21p Markd
|
|
// When picking up weapons, ammo is given if you already have that weapon
|
|
//
|
|
// 49 6/05/98 6:26p Aldie
|
|
// Added location to apply multi damage
|
|
//
|
|
// 48 6/04/98 7:36p Jimdose
|
|
// Drop now posts EV_Item_DropToFloor instead of processing it.
|
|
//
|
|
// 47 6/03/98 4:39p Markd
|
|
// Removed dummy weapon, there is now just a single weapon that gets sent over
|
|
//
|
|
// 46 5/27/98 5:03a Aldie
|
|
// Added queue for bulletsplats
|
|
//
|
|
// 45 5/27/98 3:20a Markd
|
|
// zeroed out animations for dropped weapons
|
|
//
|
|
// 44 5/27/98 2:33a Markd
|
|
// When dropping weapons, made sure to zero out the anim and frame for the
|
|
// weapon
|
|
//
|
|
// 43 5/25/98 8:51a Aldie
|
|
// Fixed sprayblood with masked textures
|
|
//
|
|
// 42 5/25/98 6:52a Aldie
|
|
// Added maxbulletholes to stop from overflowing.
|
|
//
|
|
// 41 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
|
|
//
|
|
// 40 5/24/98 1:05a Jimdose
|
|
// Changed worldmodel, viewmodel, and ammotype to str objects
|
|
// Added sound events for ai
|
|
//
|
|
// 39 5/23/98 10:20p Aldie
|
|
// Made some changes to bulletholes to try to get them to work with ents, but
|
|
// no luck.
|
|
//
|
|
// 38 5/23/98 4:48p Aldie
|
|
// Added bulletholes
|
|
//
|
|
// 37 5/14/98 4:27p Markd
|
|
// Fixed NULL weapon model issue
|
|
//
|
|
// 36 5/13/98 4:55p Jimdose
|
|
// now uses SafePtrs
|
|
// got rid of weapon_owner. replaced with owner which is a SafePtr.
|
|
//
|
|
// 35 5/11/98 11:25a Markd
|
|
// Changed some worldmodel stuff
|
|
//
|
|
// 34 5/03/98 4:34p Jimdose
|
|
// Changed Vector class
|
|
//
|
|
// 33 4/14/98 9:58p Jimdose
|
|
// Made SetModels set the dummy model if it exists
|
|
//
|
|
// 32 4/10/98 12:36a Jimdose
|
|
// Fire now cancels EV_Weapon_DoneFiring
|
|
//
|
|
// 31 4/09/98 3:30p Jimdose
|
|
// Added gettype and settype
|
|
// added sound so that anim sounds play from the owner
|
|
//
|
|
// 30 4/08/98 4:55p Jimdose
|
|
// Made ShowDummyOnClient and HideDummyOnClient check if dummy is NULL
|
|
// Fixed the bug with weapons not being shown on monsters. The dummy's origin
|
|
// was non 0,0,0
|
|
//
|
|
// 29 4/08/98 4:46p Jimdose
|
|
// Added ShowDummyOnClient and HideDummyOnClient
|
|
//
|
|
// 28 4/07/98 8:19p Jimdose
|
|
// Made assaultrifle fire at normal speed
|
|
//
|
|
// 27 4/07/98 8:00p Markd
|
|
// removed defhandle, changed all SINMDL calls to modelindex calls, removed
|
|
// SINMDL prefix
|
|
//
|
|
// 26 4/07/98 6:45p Jimdose
|
|
// Rewrote weapon code.
|
|
// Fixed weapon switching bugs
|
|
// fixed crash from freeing weapons at level changes
|
|
// Added order to rank
|
|
//
|
|
// 25 4/06/98 7:10p Aldie
|
|
// Added zooming for SniperRifle
|
|
//
|
|
// 24 4/05/98 11:17p Jimdose
|
|
// No longer see the gun lerping around
|
|
//
|
|
// 23 4/05/98 5:29p Jimdose
|
|
// Weapon attaching is back in (previous bug was probably related to not
|
|
// freeing the ents after a map change...go figure).
|
|
//
|
|
// 22 4/04/98 7:28p Jimdose
|
|
// Can't spray blood against sky anymore
|
|
//
|
|
// 21 4/04/98 6:11p Jimdose
|
|
// Made response from EV_Trigger_ActivateTargets to EV_Trigger_Effect
|
|
// Added attachable guns (currently disabled)
|
|
//
|
|
// 20 4/02/98 4:53p Jimdose
|
|
// Added AmmoAvailable
|
|
// Made SprayBlood work
|
|
// Added check for valid owner in AnimateFlash
|
|
//
|
|
// 19 3/31/98 1:05a Jimdose
|
|
// Made monsters always have ammo
|
|
//
|
|
// 18 3/30/98 2:35p Jimdose
|
|
// Added Ammo
|
|
// Added world models
|
|
//
|
|
// 17 3/27/98 11:03p Jimdose
|
|
// Added muzzle flash
|
|
//
|
|
// 16 3/26/98 8:17p Jimdose
|
|
// Temporarily disabled spray blood function
|
|
//
|
|
// 15 3/23/98 1:31p Jimdose
|
|
// Revamped event and command system
|
|
//
|
|
// 14 3/18/98 7:24p Jimdose
|
|
// Added code for handling readying and putting away weapons
|
|
// Made work with new animation system
|
|
//
|
|
// 13 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.
|
|
//
|
|
// 12 3/02/98 5:36p Jimdose
|
|
// Fixed setOwner so that it handles NULL being passed in when entites are
|
|
// freed or when the weapon is dropped.
|
|
//
|
|
// 11 2/19/98 2:35p Jimdose
|
|
// Updated to work with Q2 based progs
|
|
//
|
|
// 9 12/15/97 12:04p Aldie
|
|
// Fixed error message from coming up when checking for reload animations when
|
|
// there was none (I.e. Melee weapons).
|
|
//
|
|
// 8 12/14/97 7:49p Jimdose
|
|
// Fixed bug where if a weapon didn't have a "reload" animation, every other
|
|
// time the player tried to fire would fail.
|
|
//
|
|
// 7 12/12/97 2:10p Markd
|
|
// Changed NextAttacks in CheckReload and Fire to 1 second instead of 5
|
|
//
|
|
// 6 12/11/97 3:31p Markd
|
|
// Added SprayBlood function
|
|
//
|
|
// 5 12/09/97 7:46p Markd
|
|
// Added range variable. Added different behavior when firing...if there is no
|
|
// model, then go ahead and shoot right away
|
|
//
|
|
// 4 11/18/97 5:28p Markd
|
|
// Put in CheckTwitch, CheckIdle, put in clip support and other goodies
|
|
//
|
|
// 3 10/27/97 3:30p Jimdose
|
|
// Removed dependency on quakedef.h
|
|
//
|
|
// 2 9/26/97 4:46p Jimdose
|
|
// Added standard Ritual header
|
|
//
|
|
// DESCRIPTION:
|
|
// Source file for Weapon class. The weapon class is the base class for
|
|
// all weapons in Sin. Any entity created from a class derived from the weapon
|
|
// class will be usable by any Sentient (players and monsters) as a weapon.
|
|
//
|
|
|
|
#include "g_local.h"
|
|
#include "entity.h"
|
|
#include "item.h"
|
|
#include "weapon.h"
|
|
#include "scriptmaster.h"
|
|
#include "sentient.h"
|
|
#include "misc.h"
|
|
#include "specialfx.h"
|
|
|
|
#ifdef SIN_ARCADE
|
|
static ScriptVariablePtr sv_infinitebullets;
|
|
static ScriptVariablePtr sv_infiniterockets;
|
|
static ScriptVariablePtr sv_infiniteplasma;
|
|
static ScriptVariablePtr sv_infinitespears;
|
|
static ScriptVariablePtr sv_infinitesniper;
|
|
#endif
|
|
|
|
CLASS_DECLARATION( Item, Weapon, NULL );
|
|
|
|
Event EV_Weapon_Shoot( "shoot" );
|
|
Event EV_Weapon_FinishAttack( "attack_finished" );
|
|
Event EV_Weapon_DoneLowering( "putaway" );
|
|
Event EV_Weapon_DoneRaising( "ready" );
|
|
Event EV_Weapon_DoneFiring( "donefiring" );
|
|
Event EV_Weapon_Idle( "idle" );
|
|
Event EV_Weapon_MuzzleFlash( "muzzleflash" );
|
|
Event EV_Weapon_SecondaryUse( "secondaryuse" );
|
|
Event EV_Weapon_DoneReloading( "donereloading" );
|
|
Event EV_Weapon_SetAmmoClipSize( "ammoclipsize" );
|
|
Event EV_Weapon_ProcessModelCommands( "process_mdl_cmds" );
|
|
Event EV_Weapon_SetMaxRange( "maxrange" );
|
|
Event EV_Weapon_SetMinRange( "minrange" );
|
|
Event EV_Weapon_SetProjectileSpeed( "projectilespeed" );
|
|
Event EV_Weapon_SetKick( "kick" );
|
|
Event EV_Weapon_PrimaryMode( "primarymode" );
|
|
Event EV_Weapon_SecondaryMode( "secondarymode" );
|
|
Event EV_Weapon_ActionIncrement( "actionincrement" );
|
|
Event EV_Weapon_PutAwayAndRaise( "putawaythenraise" );
|
|
Event EV_Weapon_Raise( "raise" );
|
|
Event EV_Weapon_NotDroppable( "notdroppable" );
|
|
Event EV_Weapon_SetAimAnim( "setaimanim" );
|
|
|
|
ResponseDef Weapon::Responses[] =
|
|
{
|
|
{ &EV_Item_Pickup, ( Response )Weapon::PickupWeapon },
|
|
{ &EV_Weapon_FinishAttack, ( Response )Weapon::FinishAttack },
|
|
{ &EV_Weapon_DoneLowering, ( Response )Weapon::DoneLowering },
|
|
{ &EV_Weapon_DoneRaising, ( Response )Weapon::DoneRaising },
|
|
{ &EV_Weapon_DoneFiring, ( Response )Weapon::DoneFiring },
|
|
{ &EV_Weapon_Idle, ( Response )Weapon::Idle },
|
|
{ &EV_Weapon_MuzzleFlash, ( Response )Weapon::EventMuzzleFlash },
|
|
{ &EV_WeaponSound, ( Response )Weapon::WeaponSound },
|
|
{ &EV_Weapon_DoneReloading, ( Response )Weapon::DoneReloading },
|
|
{ &EV_Weapon_SetAmmoClipSize, ( Response )Weapon::SetAmmoClipSize },
|
|
{ &EV_Weapon_ProcessModelCommands, ( Response )Weapon::ProcessWeaponCommandsEvent },
|
|
{ &EV_Weapon_SetMaxRange, ( Response )Weapon::SetMaxRangeEvent },
|
|
{ &EV_Weapon_SetMinRange, ( Response )Weapon::SetMinRangeEvent },
|
|
{ &EV_Weapon_SetProjectileSpeed, ( Response )Weapon::SetProjectileSpeedEvent },
|
|
{ &EV_Weapon_SetKick, ( Response )Weapon::SetKick },
|
|
{ &EV_Weapon_SecondaryUse, ( Response )Weapon::SecondaryUse },
|
|
{ &EV_Weapon_PrimaryMode, ( Response )Weapon::PrimaryMode },
|
|
{ &EV_Weapon_SecondaryMode, ( Response )Weapon::SecondaryMode },
|
|
{ &EV_Weapon_ActionIncrement, ( Response )Weapon::SetActionLevelIncrement },
|
|
{ &EV_Weapon_PutAwayAndRaise, ( Response )Weapon::PutAwayAndRaise },
|
|
{ &EV_Weapon_Raise, ( Response )Weapon::Raise },
|
|
{ &EV_Weapon_NotDroppable, ( Response )Weapon::NotDroppableEvent },
|
|
{ &EV_Weapon_SetAimAnim, ( Response )Weapon::SetAimAnim },
|
|
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static int numBulletHoles = 0;
|
|
static Queue queueBulletHoles;
|
|
|
|
void ResetBulletHoles(void)
|
|
{
|
|
numBulletHoles = 0;
|
|
queueBulletHoles.Clear();
|
|
}
|
|
|
|
Weapon::Weapon()
|
|
{
|
|
attack_finished = 0;
|
|
owner = NULL;
|
|
ammorequired = 0;
|
|
secondary_ammorequired = 0;
|
|
startammo = 0;
|
|
rank = 0;
|
|
ammo_clip_size = 0;
|
|
ammo_in_clip = G_GetIntArg( "ammo_in_clip", 0 );
|
|
kick = 0;
|
|
last_attack_time = level.time-10;
|
|
nextweaponsoundtime = 0;
|
|
weaponstate = WEAPON_HOLSTERED;
|
|
weaponmode = ( weaponmode_t )G_GetIntArg( "weaponmode", PRIMARY );
|
|
dualmode = false;
|
|
silenced = G_GetIntArg( "silenced", false );
|
|
notdroppable = false;
|
|
|
|
aimanim = -1;
|
|
aimframe = 0;
|
|
|
|
// handles most cases
|
|
weapontype = WEAPON_2HANDED_HI;
|
|
|
|
// start off unattached
|
|
attached = false;
|
|
|
|
// maximum effective firing distance (for AI)
|
|
maxrange = 8192 * 2; // FIXME grr... magic number...
|
|
|
|
// minimum safe firing distance (for AI)
|
|
minrange = 0;
|
|
|
|
// speed of the projectile (0 == infinite speed)
|
|
projectilespeed = 0;
|
|
|
|
// default action_level_increment
|
|
action_level_increment = 2;
|
|
}
|
|
|
|
Weapon::~Weapon()
|
|
{
|
|
DetachGun();
|
|
}
|
|
|
|
void Weapon::CreateSpawnArgs
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
Item::CreateSpawnArgs();
|
|
G_SetIntArg( "ammo_in_clip", ammo_in_clip );
|
|
G_SetIntArg( "silenced", silenced );
|
|
G_SetIntArg( "weaponmode", weaponmode );
|
|
}
|
|
|
|
int Weapon::Rank
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return rank;
|
|
}
|
|
|
|
int Weapon::Order
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return order;
|
|
}
|
|
|
|
void Weapon::SetRank
|
|
(
|
|
int order,
|
|
int rank
|
|
)
|
|
|
|
{
|
|
this->order = order;
|
|
this->rank = rank;
|
|
}
|
|
|
|
void Weapon::SetType
|
|
(
|
|
weapontype_t type
|
|
)
|
|
|
|
{
|
|
weapontype = type;
|
|
}
|
|
|
|
weapontype_t Weapon::GetType
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return weapontype;
|
|
}
|
|
|
|
void Weapon::SetAmmo
|
|
(
|
|
const char *type,
|
|
int amount,
|
|
int startamount
|
|
)
|
|
|
|
{
|
|
if ( type )
|
|
{
|
|
ammotype = type;
|
|
primary_ammo_type = type;
|
|
}
|
|
else
|
|
{
|
|
ammotype = "";
|
|
primary_ammo_type = "";
|
|
}
|
|
|
|
ammorequired = amount;
|
|
startammo = startamount;
|
|
}
|
|
|
|
void Weapon::SetSecondaryAmmo
|
|
(
|
|
const char *type,
|
|
int amount,
|
|
int startamount
|
|
)
|
|
|
|
{
|
|
if ( type )
|
|
{
|
|
secondary_ammo_type = type;
|
|
}
|
|
else
|
|
{
|
|
secondary_ammo_type = "";
|
|
}
|
|
|
|
secondary_ammorequired = amount;
|
|
}
|
|
|
|
void Weapon::SetAmmoAmount
|
|
(
|
|
int amount
|
|
)
|
|
|
|
{
|
|
if ( ammo_clip_size )
|
|
ammo_in_clip = amount;
|
|
}
|
|
|
|
void Weapon::UseAmmo
|
|
(
|
|
int amount
|
|
)
|
|
|
|
{
|
|
if ( ammo_clip_size )
|
|
{
|
|
ammo_in_clip -= amount;
|
|
if (ammo_in_clip < 0)
|
|
{
|
|
warning("UseAmmo","Used more ammo than in clip.\n");
|
|
ammo_in_clip = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Ammo * ammo;
|
|
assert( owner );
|
|
if ( owner && owner->isClient() && !UnlimitedAmmo() )
|
|
{
|
|
if ( ammotype.length() )
|
|
{
|
|
ammo = ( Ammo * )owner->FindItem( ammotype.c_str() );
|
|
|
|
if ( weaponmode == PRIMARY )
|
|
{
|
|
if ( !ammo || !ammo->Use( ammorequired ) )
|
|
{
|
|
SetAmmoAmount( 0 );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !ammo || !ammo->Use( secondary_ammorequired ) )
|
|
{
|
|
SetAmmoAmount( 0 );
|
|
return;
|
|
}
|
|
}
|
|
SetAmmoAmount( ammo->Amount() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector Weapon::MuzzleOffset
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
vec3_t trans[ 3 ];
|
|
vec3_t orient;
|
|
int groupindex;
|
|
int tri_num;
|
|
Vector offset = vec_zero;
|
|
int useanim;
|
|
int useframe;
|
|
|
|
// get the bone information
|
|
if ( ( !edict->s.gunmodelindex ) || ( owner && owner->isClient() ) )
|
|
{
|
|
if ( gi.GetBoneInfo( edict->s.modelindex, "barrel", &groupindex, &tri_num, orient ) )
|
|
{
|
|
if ( aimanim == -1 )
|
|
{
|
|
useanim = edict->s.anim;
|
|
useframe = edict->s.frame;
|
|
}
|
|
else
|
|
{
|
|
useanim = aimanim;
|
|
useframe = aimframe;
|
|
}
|
|
if ( gi.GetBoneTransform( edict->s.modelindex, groupindex, tri_num, orient, useanim, useframe,
|
|
edict->s.scale, trans, offset.vec3() ) )
|
|
{
|
|
//
|
|
// we scale the pos by 0.3 because of RF_DEPTHHACK
|
|
//
|
|
offset *= 0.3f;
|
|
if ( owner && owner->isClient() )
|
|
{
|
|
switch( owner->client->pers.hand )
|
|
{
|
|
case LEFT_HANDED:
|
|
offset[ 1 ] *= -1.0f;
|
|
break;
|
|
case CENTER_HANDED:
|
|
offset[ 1 ] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// if it is a non-client, than get the information from the world model of the gun
|
|
//
|
|
else if ( gi.GetBoneInfo( edict->s.gunmodelindex, "barrel", &groupindex, &tri_num, orient ) )
|
|
{
|
|
gi.GetBoneTransform( edict->s.gunmodelindex, groupindex, tri_num, orient, 0, 0,
|
|
edict->s.scale, trans, offset.vec3() );
|
|
}
|
|
// Gun doesn't have a barrel, so search the owner for a barrel bone
|
|
else if ( owner && gi.GetBoneInfo( owner->edict->s.modelindex,
|
|
"barrel",
|
|
&groupindex,
|
|
&tri_num,
|
|
orient ) )
|
|
{
|
|
gi.GetBoneTransform( owner->edict->s.modelindex,
|
|
groupindex,
|
|
tri_num,
|
|
orient,
|
|
owner->edict->s.anim,
|
|
owner->edict->s.frame,
|
|
owner->edict->s.scale,
|
|
trans,
|
|
offset.vec3() );
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
void Weapon::GetMuzzlePosition
|
|
(
|
|
Vector *position,
|
|
Vector *forward,
|
|
Vector *right,
|
|
Vector *up
|
|
)
|
|
|
|
{
|
|
Vector offset;
|
|
Vector f, r, u;
|
|
Vector pos;
|
|
Vector end;
|
|
Vector dir;
|
|
Vector gunpos;
|
|
trace_t trace;
|
|
|
|
assert( owner );
|
|
|
|
// technically, we should never not have an owner when firing.
|
|
if ( !owner )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// get the position of the owners gun bone
|
|
//
|
|
gunpos = owner->GunPosition();
|
|
pos = gunpos;
|
|
|
|
owner->GetGunOrientation( pos, &f, &r, &u );
|
|
|
|
// get the bone information
|
|
offset = MuzzleOffset();
|
|
|
|
pos += f * offset[ 0 ];
|
|
pos -= r * offset[ 1 ];
|
|
pos += u * offset[ 2 ];
|
|
|
|
// prevent the creature from firing through walls
|
|
trace = G_Trace( gunpos, vec_zero, vec_zero, pos, owner, MASK_PROJECTILE, "Weapon::GetMuzzlePosition" );
|
|
if ( ( trace.fraction < 1 ) || ( trace.startsolid ) || ( trace.allsolid ) )
|
|
{
|
|
pos = gunpos;
|
|
pos -= r * offset[ 1 ];
|
|
pos += u * offset[ 2 ];
|
|
}
|
|
|
|
//
|
|
// calculate where this projectile is going to hit
|
|
//
|
|
end = gunpos + f*2048;
|
|
trace = G_FullTrace( gunpos, vec_zero, vec_zero, end, 10, owner, MASK_SHOT, "Weapon::GetMuzzlePosition" );
|
|
dir = trace.endpos - pos;
|
|
dir.normalize();
|
|
if ( dir*f < 0.707 )
|
|
dir = f;
|
|
|
|
if ( position )
|
|
{
|
|
*position = pos;
|
|
}
|
|
|
|
if ( forward )
|
|
{
|
|
*forward = dir;
|
|
}
|
|
|
|
if ( right )
|
|
{
|
|
*right = r;
|
|
}
|
|
|
|
if ( up )
|
|
{
|
|
*up = u;
|
|
}
|
|
}
|
|
|
|
void Weapon::SetAmmoClipSize
|
|
(
|
|
Event * ev
|
|
)
|
|
|
|
{
|
|
ammo_clip_size = ev->GetInteger( 1 );
|
|
}
|
|
|
|
void Weapon::SetModels
|
|
(
|
|
const char *world,
|
|
const char *view
|
|
)
|
|
|
|
{
|
|
Event *ev;
|
|
assert( view );
|
|
|
|
viewmodel = view;
|
|
modelIndex( view );
|
|
|
|
if ( world )
|
|
{
|
|
worldmodel = world;
|
|
modelIndex( world );
|
|
}
|
|
else
|
|
{
|
|
worldmodel = "";
|
|
}
|
|
|
|
if ( owner )
|
|
{
|
|
setModel( viewmodel );
|
|
}
|
|
else if ( worldmodel.length() )
|
|
{
|
|
setModel( worldmodel );
|
|
}
|
|
else
|
|
{
|
|
setModel( viewmodel );
|
|
}
|
|
|
|
if ( worldmodel.length() )
|
|
{
|
|
ev = new Event( EV_Weapon_ProcessModelCommands );
|
|
ev->AddInteger( modelIndex( worldmodel.c_str() ) );
|
|
PostEvent( ev, 0 );
|
|
}
|
|
ev = new Event( EV_Weapon_ProcessModelCommands );
|
|
ev->AddInteger( modelIndex( viewmodel.c_str() ) );
|
|
PostEvent( ev, 0 );
|
|
}
|
|
|
|
void Weapon::SetAimAnim
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
str anim;
|
|
|
|
anim = ev->GetString( 1 );
|
|
aimanim = gi.Anim_NumForName( edict->s.modelindex, anim.c_str() );
|
|
aimframe = ev->GetInteger( 2 );
|
|
}
|
|
|
|
void Weapon::SetOwner
|
|
(
|
|
Sentient *ent
|
|
)
|
|
|
|
{
|
|
assert( ent );
|
|
if ( !ent )
|
|
{
|
|
// return to avoid any buggy behaviour
|
|
return;
|
|
}
|
|
|
|
Item::SetOwner( ent );
|
|
|
|
setOrigin( vec_zero );
|
|
setAngles( vec_zero );
|
|
|
|
if ( !viewmodel.length() )
|
|
{
|
|
error( "setOwner", "Weapon without viewmodel set" );
|
|
}
|
|
|
|
setModel( viewmodel );
|
|
|
|
if ( ent->isClient() && ammotype.length() && startammo && !G_GetSpawnArg( "savegame" ) )
|
|
{
|
|
ent->giveItem( ammotype.c_str(), startammo );
|
|
}
|
|
}
|
|
|
|
int Weapon::AmmoAvailable
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
Ammo *ammo;
|
|
|
|
if ( owner )
|
|
{
|
|
if ( ammotype.length() )
|
|
{
|
|
ammo = ( Ammo * )owner->FindItem( ammotype.c_str() );
|
|
if ( ammo )
|
|
{
|
|
return ammo->Amount();
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
qboolean Weapon::UnlimitedAmmo
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if ( !owner )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( !owner->isClient() || ( owner->flags & FL_GODMODE ) || DM_FLAG( DF_INFINITE_AMMO ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#ifdef SIN_ARCADE
|
|
if ( !sv_infinitebullets || !sv_infiniterockets || !sv_infiniteplasma || !sv_infinitespears || !sv_infinitesniper )
|
|
{
|
|
sv_infinitebullets = gameVars.CreateVariable( "infinitebullets", 0 );
|
|
sv_infiniterockets = gameVars.CreateVariable( "infiniterockets", 0 );
|
|
sv_infiniteplasma = gameVars.CreateVariable( "infiniteplasma", 0 );
|
|
sv_infinitespears = gameVars.CreateVariable( "infinitespears", 0 );
|
|
sv_infinitesniper = gameVars.CreateVariable( "infinitesniper", 0 );
|
|
}
|
|
|
|
if ( sv_infinitebullets->intValue() && ( ( !Q_strncasecmp( ammotype.c_str(), "bullet", 6 ) && ( ammotype != "BulletPulse" ) &&
|
|
( ammotype != "BulletSniper" ) ) || ( ammotype == "ShotgunClip" ) ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( sv_infiniterockets->intValue() && ( ammotype == "Rockets" ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( sv_infiniteplasma->intValue() && ( ammotype == "BulletPulse" ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( sv_infinitesniper->intValue() && ( ammotype == "BulletSniper" ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( sv_infinitespears->intValue() && ( ammotype == "Spears" ) )
|
|
{
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
qboolean Weapon::HasAmmo
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if ( !owner )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( UnlimitedAmmo() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( weaponmode == PRIMARY )
|
|
{
|
|
if ( ( ammo_clip_size && ammo_in_clip >= ammorequired ) || AmmoAvailable() >= ammorequired )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ( ammo_clip_size && ammo_in_clip >= secondary_ammorequired ) || AmmoAvailable() >= secondary_ammorequired )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
qboolean Weapon::HasAmmoInClip
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if ( ammo_clip_size )
|
|
{
|
|
if ( weaponmode == PRIMARY )
|
|
{
|
|
if ( ammo_in_clip >= ammorequired )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ammo_in_clip >= secondary_ammorequired )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return HasAmmo();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Weapon::ForceState
|
|
(
|
|
weaponstate_t state
|
|
)
|
|
|
|
{
|
|
weaponstate = state;
|
|
}
|
|
|
|
qboolean Weapon::AttackDone
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return ( attack_finished <= level.time );
|
|
}
|
|
|
|
qboolean Weapon::ReadyToFire
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return ( weaponstate == WEAPON_READY ) && AttackDone();
|
|
}
|
|
|
|
qboolean Weapon::ReadyToChange
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return ( weaponstate == WEAPON_READY );
|
|
}
|
|
|
|
qboolean Weapon::ReadyToUse
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return true;
|
|
}
|
|
|
|
qboolean Weapon::ChangingWeapons
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return ( weaponstate == WEAPON_LOWERING ) || ( weaponstate == WEAPON_RAISING );
|
|
}
|
|
|
|
qboolean Weapon::WeaponRaising
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return ( weaponstate == WEAPON_RAISING );
|
|
}
|
|
|
|
qboolean Weapon::WeaponPuttingAway
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return ( weaponstate == WEAPON_LOWERING );
|
|
}
|
|
|
|
qboolean Weapon::Reloading
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return ( weaponstate == WEAPON_RELOADING );
|
|
}
|
|
|
|
void Weapon::PutAway
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if ( weaponstate != WEAPON_READY )
|
|
{
|
|
return;
|
|
}
|
|
|
|
weaponstate = WEAPON_LOWERING;
|
|
|
|
if ( !HasAnim( "putaway" ) || ( deathmatch->value && ( ( int )dmflags->value & DF_FAST_WEAPONS ) ) )
|
|
{
|
|
ProcessEvent( EV_Weapon_DoneLowering );
|
|
return;
|
|
}
|
|
|
|
if ( dualmode )
|
|
{
|
|
if ( weaponmode == PRIMARY )
|
|
RandomAnimate( "primaryputaway", EV_Weapon_DoneLowering );
|
|
else
|
|
RandomAnimate( "secondaryputaway", EV_Weapon_DoneLowering );
|
|
}
|
|
else
|
|
if ( ( owner ) && ( owner->flags & FL_SILENCER ) && ( silenced ) )
|
|
RandomAnimate( "silputaway", EV_Weapon_DoneLowering );
|
|
else
|
|
RandomAnimate( "putaway", EV_Weapon_DoneLowering );
|
|
|
|
//weaponmode = PRIMARY;
|
|
}
|
|
|
|
void Weapon::ReadyWeapon
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
str animname;
|
|
|
|
if ( weaponstate != WEAPON_HOLSTERED )
|
|
{
|
|
return;
|
|
}
|
|
|
|
weaponstate = WEAPON_RAISING;
|
|
|
|
AttachGun();
|
|
|
|
if ( ( owner ) && ( owner->flags & FL_SILENCER ) && ( silenced ) )
|
|
animname = "silready";
|
|
else
|
|
{
|
|
if ( weaponmode == SECONDARY )
|
|
animname = "secondaryready";
|
|
else
|
|
animname = "ready";
|
|
|
|
}
|
|
|
|
if ( !HasAnim( animname.c_str() ) || ( deathmatch->value && ( ( int )dmflags->value & DF_FAST_WEAPONS ) ) )
|
|
{
|
|
ProcessEvent( EV_Weapon_DoneRaising );
|
|
return;
|
|
}
|
|
|
|
RandomAnimate( animname.c_str(), EV_Weapon_DoneRaising );
|
|
}
|
|
|
|
void Weapon::DetachFromOwner
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
StopAnimating();
|
|
DetachGun();
|
|
weaponstate = WEAPON_HOLSTERED;
|
|
}
|
|
|
|
void Weapon::AttachToOwner
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
AttachGun();
|
|
ForceIdle();
|
|
}
|
|
|
|
qboolean Weapon::Drop
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
float radius;
|
|
Vector temp;
|
|
|
|
if ( !owner )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( !IsDroppable() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DetachGun();
|
|
|
|
const gravityaxis_t &grav = gravity_axis[ gravaxis ];
|
|
|
|
temp[ grav.z ] = 40 * grav.sign;
|
|
if ( owner )
|
|
{
|
|
setOrigin( owner->worldorigin + temp );
|
|
}
|
|
else
|
|
{
|
|
setOrigin( worldorigin + temp );
|
|
}
|
|
setModel( worldmodel );
|
|
|
|
// hack to fix the bounds when the gun is dropped
|
|
|
|
//
|
|
// once dropped reset the rotated bounds
|
|
//
|
|
flags |= FL_ROTATEDBOUNDS;
|
|
|
|
gi.CalculateBounds( edict->s.modelindex, edict->s.scale, mins.vec3(), maxs.vec3() );
|
|
radius = ( mins - maxs ).length() * 0.25f;
|
|
mins.x = mins.y = -radius;
|
|
maxs.x = maxs.y = radius;
|
|
setSize( mins, maxs );
|
|
|
|
StopAnimating();
|
|
edict->s.frame = 0;
|
|
edict->s.anim = 0;
|
|
|
|
// drop the weapon
|
|
PlaceItem();
|
|
if ( owner )
|
|
{
|
|
temp[ grav.x ] = G_CRandom( 50 );
|
|
temp[ grav.y ] = G_CRandom( 50 );
|
|
temp[ grav.z ] = 100 * grav.sign;
|
|
velocity = owner->velocity * 0.5 + temp;
|
|
setAngles( owner->angles );
|
|
}
|
|
|
|
avelocity = Vector( 0, G_CRandom( 360 ), 0 );
|
|
|
|
if ( owner && owner->isClient() )
|
|
{
|
|
spawnflags |= DROPPED_PLAYER_ITEM;
|
|
if ( ammo_clip_size )
|
|
startammo = ammo_in_clip;
|
|
else
|
|
startammo = 0;
|
|
|
|
// If owner is dead, put all his ammo of that type in the gun.
|
|
if ( owner->deadflag )
|
|
{
|
|
startammo = AmmoAvailable();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
spawnflags |= DROPPED_ITEM;
|
|
if ( ammo_clip_size && ammo_in_clip )
|
|
startammo = ammo_in_clip;
|
|
else
|
|
startammo >>= 2;
|
|
|
|
if ( startammo == 0 )
|
|
{
|
|
startammo = 1;
|
|
}
|
|
}
|
|
|
|
// Wait some time before the last owner can pickup this weapon
|
|
last_owner = owner;
|
|
last_owner_trigger_time = level.time + 2.5f;
|
|
|
|
// Cancel reloading events
|
|
CancelEventsOfType( EV_Weapon_DoneReloading );
|
|
|
|
// Remove this from the owner's item list
|
|
if ( owner )
|
|
{
|
|
owner->RemoveItem( this );
|
|
}
|
|
|
|
owner = NULL;
|
|
|
|
// Fade out dropped weapons, to keep down the clutter
|
|
PostEvent( EV_FadeOut, 30 );
|
|
return true;
|
|
}
|
|
|
|
|
|
void Weapon::Fire
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if ( !ReadyToFire() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !HasAmmoInClip() )
|
|
{
|
|
CheckReload();
|
|
return;
|
|
}
|
|
|
|
if ( weaponmode == PRIMARY )
|
|
UseAmmo( ammorequired );
|
|
else
|
|
UseAmmo( secondary_ammorequired );
|
|
|
|
weaponstate = WEAPON_FIRING;
|
|
|
|
CancelEventsOfType( EV_Weapon_DoneFiring );
|
|
// this is just a precaution that we can re-trigger
|
|
NextAttack( 5 );
|
|
|
|
if ( dualmode )
|
|
{
|
|
if ( weaponmode == PRIMARY )
|
|
{
|
|
RandomAnimate( "primaryfire", EV_Weapon_DoneFiring );
|
|
}
|
|
else
|
|
{
|
|
RandomAnimate( "secondaryfire", EV_Weapon_DoneFiring );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ( owner ) && ( owner->flags & FL_SILENCER ) && ( silenced ) )
|
|
RandomAnimate( "silfire", EV_Weapon_DoneFiring );
|
|
else
|
|
RandomAnimate( "fire", EV_Weapon_DoneFiring );
|
|
}
|
|
|
|
last_attack_time = level.time;
|
|
}
|
|
|
|
//**********************************************************************************/
|
|
//
|
|
// Non-public Weapon functions
|
|
//
|
|
//**********************************************************************************/
|
|
|
|
void Weapon::DetachGun
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if ( attached )
|
|
{
|
|
RandomGlobalSound("null_sound", 1, CHAN_WEAPONIDLE );
|
|
attached = false;
|
|
detach();
|
|
hideModel();
|
|
edict->s.gunmodelindex = 0;
|
|
edict->s.gunanim = 0;
|
|
edict->s.gunframe = 0;
|
|
edict->s.effects &= ~EF_SMOOTHANGLES;
|
|
}
|
|
}
|
|
|
|
//
|
|
// attach and detach the gun from the owner
|
|
//
|
|
|
|
void Weapon::AttachGun
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
int groupindex;
|
|
int tri_num;
|
|
Vector orient;
|
|
|
|
if ( !owner )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (attached)
|
|
DetachGun();
|
|
|
|
if ( gi.GetBoneInfo( owner->edict->s.modelindex, "gun", &groupindex, &tri_num, orient.vec3() ) )
|
|
{
|
|
attached = true;
|
|
attach( owner->entnum, groupindex, tri_num, orient );
|
|
showModel();
|
|
setOrigin( vec_zero );
|
|
edict->s.gunmodelindex = modelIndex( worldmodel.c_str() );
|
|
if ( edict->s.gunmodelindex )
|
|
{
|
|
edict->s.gunanim = gi.Anim_Random( edict->s.gunmodelindex, "idle" );
|
|
if ( edict->s.gunanim < 0 )
|
|
edict->s.gunanim = 0;
|
|
edict->s.gunframe = 0;
|
|
}
|
|
else
|
|
{
|
|
edict->s.gunanim = 0;
|
|
edict->s.gunframe = 0;
|
|
}
|
|
edict->s.effects |= EF_SMOOTHANGLES;
|
|
}
|
|
else
|
|
{
|
|
gi.dprintf( "attach failed\n" );
|
|
}
|
|
}
|
|
|
|
void Weapon::PickupWeapon
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Sentient *sen;
|
|
Entity *other;
|
|
Weapon *weapon;
|
|
Weapon *current;
|
|
qboolean hasweapon;
|
|
qboolean giveammo;
|
|
Ammo *ammo;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
|
|
assert( other );
|
|
|
|
if ( !other->isSubclassOf( Sentient ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
sen = ( Sentient * )other;
|
|
|
|
// If this is the last owner, check to see if he can pick it up
|
|
if ( ( sen == last_owner ) && ( level.time < last_owner_trigger_time ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
hasweapon = sen->HasItem( getClassname() );
|
|
giveammo = ( sen->isClient() && ammotype.length() && startammo );
|
|
|
|
// if he already has the weapon, don't pick it up if he doesn't need the ammo
|
|
if ( hasweapon )
|
|
{
|
|
if ( !giveammo )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// check if he needs the ammo
|
|
ammo = ( Ammo * )sen->FindItem( ammotype.c_str() );
|
|
if ( ammo && ( ammo->Amount() >= ammo->MaxAmount() ) )
|
|
{
|
|
// doesn't need the ammo or the weapon, so return.
|
|
return;
|
|
}
|
|
}
|
|
|
|
weapon = ( Weapon * )ItemPickup( other );
|
|
if ( !weapon )
|
|
{
|
|
// Item Pickup failed, so don't give ammo either.
|
|
return;
|
|
}
|
|
|
|
//
|
|
// once picked up we don't want rotated bounds
|
|
//
|
|
flags &= ~FL_ROTATEDBOUNDS;
|
|
|
|
//FIXME
|
|
// Sentient should probably handle this when itempickup is called
|
|
// Check if we should switch his weapon
|
|
current = sen->CurrentWeapon();
|
|
if ( !hasweapon && current && ( current != weapon ) && ( current->AutoChange() ) &&
|
|
( ( current->Rank() < weapon->Rank() ) || ( !current->HasAmmo() && weapon->HasAmmo() ) ) )
|
|
{
|
|
sen->ChangeWeapon( weapon );
|
|
}
|
|
|
|
// check if we should give him ammo
|
|
if ( giveammo )
|
|
{
|
|
sen->giveItem( ammotype.c_str(), startammo );
|
|
}
|
|
}
|
|
|
|
void Weapon::ForceIdle
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
weaponstate = WEAPON_READY;
|
|
|
|
if ( dualmode )
|
|
{
|
|
if ( weaponmode == PRIMARY )
|
|
RandomAnimate( "primaryidle", EV_Weapon_Idle );
|
|
else
|
|
RandomAnimate( "secondaryidle", EV_Weapon_Idle );
|
|
}
|
|
else
|
|
{
|
|
if ( owner && ( owner->flags & FL_SILENCER ) && ( silenced ) )
|
|
RandomAnimate( "silidle", EV_Weapon_Idle );
|
|
else
|
|
RandomAnimate( "idle", EV_Weapon_Idle );
|
|
}
|
|
}
|
|
|
|
void Weapon::DoneLowering
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
weaponstate = WEAPON_HOLSTERED;
|
|
|
|
DetachGun();
|
|
|
|
if ( owner )
|
|
{
|
|
owner->ProcessEvent( EV_Sentient_WeaponPutAway );
|
|
}
|
|
StopAnimating();
|
|
}
|
|
|
|
void Weapon::DoneRaising
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
weaponstate = WEAPON_READY;
|
|
|
|
if ( !ForceReload() )
|
|
{
|
|
ForceIdle();
|
|
}
|
|
|
|
if ( !owner )
|
|
{
|
|
PostEvent( EV_Remove, 0 );
|
|
return;
|
|
}
|
|
|
|
// Set the ammo type
|
|
if (weaponmode == PRIMARY)
|
|
{
|
|
ammotype = primary_ammo_type;
|
|
}
|
|
else if ( weaponmode == SECONDARY )
|
|
{
|
|
ammotype = secondary_ammo_type;
|
|
}
|
|
|
|
if ( owner )
|
|
owner->ProcessEvent( EV_Sentient_WeaponReady );
|
|
}
|
|
|
|
void Weapon::DoneFiring
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if ( !CheckReload() )
|
|
{
|
|
ForceIdle();
|
|
}
|
|
|
|
if ( owner )
|
|
{
|
|
owner->ProcessEvent( EV_Sentient_WeaponDoneFiring );
|
|
}
|
|
}
|
|
|
|
void Weapon::DoneReloading
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Ammo *ammo;
|
|
int amount;
|
|
|
|
amount = ammo_clip_size - ammo_in_clip;
|
|
assert( owner );
|
|
if ( owner && owner->isClient() && !UnlimitedAmmo() )
|
|
{
|
|
if ( ammotype.length() )
|
|
{
|
|
ammo = ( Ammo * )owner->FindItem( ammotype.c_str() );
|
|
if ( ammo )
|
|
{
|
|
if ( ammo->Amount() < amount )
|
|
amount = ammo->Amount();
|
|
if ( !ammo->Use( amount ) )
|
|
{
|
|
amount = 0;
|
|
}
|
|
}
|
|
SetAmmoAmount( amount + ammo_in_clip );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetAmmoAmount( ammo_clip_size );
|
|
}
|
|
|
|
ForceIdle();
|
|
|
|
if ( owner )
|
|
{
|
|
owner->ProcessEvent( EV_Sentient_WeaponReady );
|
|
}
|
|
}
|
|
|
|
qboolean Weapon::ForceReload
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if ( weaponstate != WEAPON_READY )
|
|
return false;
|
|
|
|
if ( level.cinematic && owner && owner->isClient() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// do a reload if the ammo in clip is not full,
|
|
// and there is some ammo available out of clip
|
|
if (
|
|
( ammo_clip_size != ammo_in_clip ) &&
|
|
( AmmoAvailable() > 0 )
|
|
)
|
|
{
|
|
weaponstate = WEAPON_RELOADING;
|
|
if ( ( owner ) && ( owner->flags & FL_SILENCER ) && ( silenced ) )
|
|
RandomAnimate( "silreload", EV_Weapon_DoneReloading );
|
|
else
|
|
RandomAnimate( "reload", EV_Weapon_DoneReloading );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
qboolean Weapon::CheckReload
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if ( weaponstate != WEAPON_READY )
|
|
return false;
|
|
|
|
if ( level.cinematic && owner && owner->isClient() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( ammo_clip_size && !ammo_in_clip && HasAmmo() )
|
|
{
|
|
weaponstate = WEAPON_RELOADING;
|
|
if ( ( owner ) && ( owner->flags & FL_SILENCER ) && ( silenced ) )
|
|
RandomAnimate( "silreload", EV_Weapon_DoneReloading );
|
|
else
|
|
RandomAnimate( "reload", EV_Weapon_DoneReloading );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Weapon::Idle
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if ( ammo_clip_size && ( !ammo_in_clip || level.time > last_attack_time + 2 ) )
|
|
{
|
|
if (CheckReload())
|
|
return;
|
|
}
|
|
|
|
ForceIdle();
|
|
}
|
|
|
|
void Weapon::FinishAttack
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
attack_finished = level.time;
|
|
}
|
|
|
|
void Weapon::NextAttack
|
|
(
|
|
double rate
|
|
)
|
|
|
|
{
|
|
attack_finished = level.time + (float)rate;
|
|
}
|
|
|
|
float Weapon::GetMaxRange
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return maxrange;
|
|
}
|
|
|
|
float Weapon::GetMinRange
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return minrange;
|
|
}
|
|
|
|
float Weapon::GetProjectileSpeed
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return projectilespeed;
|
|
}
|
|
|
|
void Weapon::SetMaxRangeEvent
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
maxrange = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Weapon::SetMinRangeEvent
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
minrange = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Weapon::SetProjectileSpeedEvent
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
projectilespeed = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Weapon::NotDroppableEvent
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
notdroppable = true;
|
|
}
|
|
|
|
void Weapon::SetMaxRange
|
|
(
|
|
float val
|
|
)
|
|
|
|
{
|
|
maxrange = val;
|
|
}
|
|
|
|
void Weapon::SetMinRange
|
|
(
|
|
float val
|
|
)
|
|
|
|
{
|
|
minrange = val;
|
|
}
|
|
|
|
void Weapon::SetProjectileSpeed
|
|
(
|
|
float val
|
|
)
|
|
|
|
{
|
|
projectilespeed = val;
|
|
}
|
|
|
|
void Weapon::EventMuzzleFlash
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if ( !owner )
|
|
{
|
|
return;
|
|
}
|
|
|
|
SpawnTempDlight
|
|
(
|
|
owner->worldorigin,
|
|
ev->GetFloat( 1 ),
|
|
ev->GetFloat( 2 ),
|
|
ev->GetFloat( 3 ),
|
|
ev->GetFloat( 4 ),
|
|
ev->GetFloat( 5 ),
|
|
ev->GetFloat( 6 )
|
|
);
|
|
}
|
|
|
|
void Weapon::MuzzleFlash
|
|
(
|
|
float r,
|
|
float g,
|
|
float b,
|
|
float radius,
|
|
float decay,
|
|
float life
|
|
)
|
|
|
|
{
|
|
if ( !owner )
|
|
{
|
|
return;
|
|
}
|
|
|
|
SpawnTempDlight
|
|
(
|
|
owner->worldorigin,
|
|
r,
|
|
g,
|
|
b,
|
|
radius,
|
|
decay,
|
|
life
|
|
);
|
|
}
|
|
|
|
void Weapon::BulletHole
|
|
(
|
|
trace_t *trace
|
|
)
|
|
{
|
|
Entity *hole;
|
|
Vector norm,norm2;
|
|
Entity *hit;
|
|
|
|
hit = trace->ent->entity;
|
|
|
|
if ( !hit )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( trace->surface &&
|
|
( trace->surface->flags & SURF_NODRAW ) )
|
|
return;
|
|
|
|
if ( hit->hidden() )
|
|
return;
|
|
|
|
// FIXME: Make Bullet holes client side.
|
|
if ( deathmatch->value )
|
|
return;
|
|
|
|
hole = new Entity;
|
|
hole->setMoveType( MOVETYPE_PUSH );
|
|
hole->setSolidType( SOLID_NOT );
|
|
hole->setModel( "sprites/bullethole.spr" );
|
|
hole->setSize( "0 0 0", "0 0 0" );
|
|
|
|
norm = trace->plane.normal;
|
|
norm2.x = -norm.x;
|
|
norm2.y = -norm.y;
|
|
norm2.z = norm.z;
|
|
|
|
hole->angles = norm2.toAngles();
|
|
hole->setAngles( hole->angles );
|
|
hole->setOrigin( Vector( trace->endpos ) + ( norm * 0.2 ) ) ;
|
|
|
|
if ( ( trace->ent->solid == SOLID_BSP ) && ( hit != world ) )
|
|
{
|
|
hole->bind( hit );
|
|
}
|
|
|
|
queueBulletHoles.Enqueue(hole);
|
|
numBulletHoles++;
|
|
|
|
if (numBulletHoles > sv_maxbulletholes->value)
|
|
{
|
|
// Fade one out of the list.
|
|
Entity *fadehole;
|
|
fadehole = (Entity *) queueBulletHoles.Dequeue();
|
|
fadehole->ProcessEvent(EV_Remove);
|
|
numBulletHoles--;
|
|
}
|
|
}
|
|
|
|
EXPORT_FROM_DLL void Weapon::WeaponSound
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Event *e;
|
|
|
|
// Broadcasting a sound can be time consuming. Only do it once in a while on really fast guns.
|
|
if ( nextweaponsoundtime > level.time )
|
|
{
|
|
if ( owner )
|
|
{
|
|
owner->BroadcastSound( ev, CHAN_WEAPON, NullEvent, 0 );
|
|
}
|
|
else
|
|
{
|
|
BroadcastSound( ev, CHAN_WEAPON, EV_HeardWeapon, 0 );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( owner )
|
|
{
|
|
e = new Event( ev );
|
|
owner->ProcessEvent( e );
|
|
}
|
|
else
|
|
{
|
|
Item::WeaponSound( ev );
|
|
}
|
|
|
|
// give us some breathing room
|
|
nextweaponsoundtime = level.time + 0.4;
|
|
}
|
|
|
|
qboolean Weapon::Removable
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if (
|
|
( ( int )( dmflags->value ) & DF_WEAPONS_STAY ) &&
|
|
!( spawnflags & ( DROPPED_ITEM | DROPPED_PLAYER_ITEM ) )
|
|
)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
qboolean Weapon::Pickupable
|
|
(
|
|
Entity *other
|
|
)
|
|
|
|
{
|
|
Sentient *sen;
|
|
|
|
if ( !other->isSubclassOf( Sentient ) )
|
|
{
|
|
return false;
|
|
}
|
|
else if ( !other->isClient() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
sen = ( Sentient * )other;
|
|
|
|
//FIXME
|
|
// This should be in player
|
|
|
|
// Mutants can't pickup weapons
|
|
if ( other->flags & (FL_MUTANT|FL_SP_MUTANT) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If we have the weapon and weapons stay, then don't pick it up
|
|
if ( ( ( int )( dmflags->value ) & DF_WEAPONS_STAY ) && !( spawnflags & ( DROPPED_ITEM | DROPPED_PLAYER_ITEM ) ) )
|
|
{
|
|
Weapon *weapon;
|
|
|
|
weapon = ( Weapon * )sen->FindItem( getClassname() );
|
|
|
|
if ( weapon )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
qboolean Weapon::AutoChange
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int Weapon::ClipAmmo
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if (ammo_clip_size)
|
|
return ammo_in_clip;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
void Weapon::ProcessWeaponCommandsEvent
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
int index;
|
|
|
|
index = ev->GetInteger( 1 );
|
|
ProcessInitCommands( index );
|
|
}
|
|
|
|
void Weapon::SetKick
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
kick = ev->GetInteger( 1 );
|
|
}
|
|
|
|
void Weapon::SecondaryUse
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if ( !dualmode )
|
|
return;
|
|
|
|
// Switch to the secondary mode of the weapon
|
|
|
|
if (weaponstate != WEAPON_READY)
|
|
return;
|
|
|
|
weaponstate = WEAPON_CHANGING;
|
|
|
|
if (weaponmode == PRIMARY)
|
|
{
|
|
RandomAnimate( "primary2secondary", EV_Weapon_SecondaryMode );
|
|
weaponmode = SECONDARY;
|
|
}
|
|
else
|
|
{
|
|
RandomAnimate( "secondary2primary", EV_Weapon_PrimaryMode );
|
|
weaponmode = PRIMARY;
|
|
}
|
|
}
|
|
|
|
void Weapon::PrimaryMode
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
RandomAnimate( "primaryidle", EV_Weapon_Idle );
|
|
weaponstate = WEAPON_READY;
|
|
ammotype = primary_ammo_type;
|
|
}
|
|
|
|
void Weapon::SecondaryMode
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
RandomAnimate( "secondaryidle", EV_Weapon_Idle );
|
|
weaponstate = WEAPON_READY;
|
|
ammotype = secondary_ammo_type;
|
|
}
|
|
|
|
void Weapon::SetActionLevelIncrement
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
action_level_increment = ev->GetInteger( 1 );
|
|
}
|
|
|
|
int Weapon::ActionLevelIncrement
|
|
(
|
|
void
|
|
)
|
|
{
|
|
return action_level_increment;
|
|
}
|
|
|
|
qboolean Weapon::IsDroppable
|
|
(
|
|
void
|
|
)
|
|
{
|
|
if ( notdroppable || !worldmodel.length() )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
qboolean Weapon::IsSilenced
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
return silenced;
|
|
}
|
|
|
|
void Weapon::Raise
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
weaponstate = WEAPON_HOLSTERED;
|
|
ReadyWeapon();
|
|
}
|
|
|
|
void Weapon::PutAwayAndRaise
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
weaponstate = WEAPON_LOWERING;
|
|
|
|
if ( deathmatch->value && ( ( int )dmflags->value & DF_FAST_WEAPONS ) )
|
|
{
|
|
ProcessEvent( EV_Weapon_DoneRaising );
|
|
return;
|
|
}
|
|
|
|
RandomAnimate( "putaway", EV_Weapon_Raise );
|
|
} |