mirror of
https://github.com/bearstonem/openmohaa-vr.git
synced 2026-10-18 09:00:21 +02:00
Four interactions that all needed the same thing: the hands' positions and velocities on the other side of the game module boundary. They share the plumbing, so they share a commit. use moves to B on the weapon hand, where the reference puts activate. The objectives list keeps the left hand's button; it had been bound to both, which was a button spent twice on one rarely opened screen. The trace also starts from the head's real position now rather than the body's notional eye - the two come apart exactly when leaning through a doorway to reach the thing on the far side. Weapons live on the body, the way Half-Life: Alyx does it: pistol at the right hip, grenades at the left, rifle and smg over the shoulders. Reaching back to where a weapon came from puts it away, reaching anywhere else swaps - holstering on any zone would have cost two gestures for every swap. Zones are measured from the head and rotated by the head's heading, so they stay on the body rather than wandering as the player turns. Ladders are climbed by pulling on them. MOHAA moves a climbing player by the root motion of the climb animation, with pmove skipped entirely, so this runs after the animation rather than instead of it: the animation still plays and the distance travelled is the distance the hand pulled. Both grips, averaged when both are held, and the grips stop meaning reload and rifle-steady while on a ladder. Grenades leave along the arm. Which instant of a swing to read is the whole problem and took several passes: averaging across the release blends the wind up with the follow through; the peak of the whole swing is usually the *backswing*, which threw grenades over the player's shoulder. What works is the natural gesture's own shape - take the direction the hand had as it let go, then find the fastest sample that agrees with it, which excludes the wind up by construction. Strength is a fraction of the game's own throw so all of MOHAA's grenade tuning survives. The sideways damping is the one deliberate lie. An underhand swing crosses the body, so the hand really is moving sideways at release - measured, lobs drifted left by up to 0.73, more sideways than forward. Damped in proportion to how gentle the throw is, so hard throws keep their true direction. A weapon wheel is included and is not finished: its input works and a tap of the same stick holsters, but the ring never appeared on screen and the drawing was not chased down. It is harmless - a hold selects nothing and does nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4654 lines
135 KiB
C++
4654 lines
135 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 2025 the OpenMoHAA team
|
|
|
|
This file is part of OpenMoHAA source code.
|
|
|
|
OpenMoHAA source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
OpenMoHAA source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with OpenMoHAA source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
// weapon.cpp: 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 "g_phys.h"
|
|
#include "entity.h"
|
|
#include "item.h"
|
|
#include "weapon.h"
|
|
#include "scriptmaster.h"
|
|
#include "sentient.h"
|
|
#include "misc.h"
|
|
#include "specialfx.h"
|
|
#include "actor.h"
|
|
#include "weaputils.h"
|
|
#include "player.h"
|
|
#include "vehicleturret.h"
|
|
#include "debuglines.h"
|
|
#include "g_spawn.h"
|
|
|
|
Event EV_Weapon_Shoot("shoot", EV_DEFAULT, "S", "mode", "Shoot the weapon", EV_NORMAL);
|
|
Event EV_Weapon_DoneRaising(
|
|
"ready", EV_DEFAULT, NULL, NULL, "Signals the end of the ready animation so the weapon can be used", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_DoneFiring("donefiring", EV_DEFAULT, NULL, NULL, "Signals the end of the fire animation", EV_NORMAL);
|
|
Event EV_Weapon_Idle("idle", EV_DEFAULT, NULL, NULL, "Puts the weapon into an idle state", EV_NORMAL);
|
|
Event EV_Weapon_IdleInit(
|
|
"idleinit", EV_DEFAULT, NULL, NULL, "Puts the weapon into an idle state and clears all the anim slots", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SecondaryUse(
|
|
"secondaryuse", EV_DEFAULT, NULL, NULL, "Puts the weapon into its secondary mode of operation", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_DoneReloading(
|
|
"donereloading", EV_DEFAULT, NULL, NULL, "Signals the end of the reload animation", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetAmmoClipSize(
|
|
"clipsize", EV_DEFAULT, "i", "ammoClipSize", "Set the amount of rounds a clip of the weapon holds", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetAmmoInClip(
|
|
"ammo_in_clip", EV_DEFAULT, "i", "ammoInClip", "Set the amount of ammo in the clip", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetShareClip(
|
|
"shareclip",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"shareClip",
|
|
"Sets the weapon to share the same clip between all fire modes",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_FillClip(
|
|
"clip_fill", EV_DEFAULT, NULL, NULL, "Fills the weapons ammo clip with ammo from its owner", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_EmptyClip(
|
|
"clip_empty",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Empties the weapon's clip of ammo,"
|
|
"returning it to the owner",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_AddToClip(
|
|
"clip_add", EV_DEFAULT, "i", "ammoAmount", "Add to the weapons ammo clip with ammo from its owner", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetMaxRange(
|
|
"maxrange",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"maxRange",
|
|
"Set the maximum range of a weapon so the AI knows how to use it",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetMinRange(
|
|
"minrange",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"minRange",
|
|
"Set the minimum range of a weapon so the AI knows how to use it",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_FireDelay(
|
|
"firedelay", EV_DEFAULT, "f", "fFireDelay", "Set the minimum time between shots from the weapon", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_DMSetFireDelay(
|
|
"dmfiredelay", EV_DEFAULT, "f", "fFireDelay", "Set the minimum time between shots from the weapon", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_NotDroppable("notdroppable", EV_DEFAULT, NULL, NULL, "Makes a weapon not droppable");
|
|
Event EV_Weapon_SetAimAnim(
|
|
"setaimanim",
|
|
EV_DEFAULT,
|
|
"si",
|
|
"aimAnimation aimFrame",
|
|
"Set the aim animation and frame for when a weapon fires",
|
|
EV_NORMAL
|
|
);
|
|
Event
|
|
EV_Weapon_SetRank("rank", EV_DEFAULT, "ii", "iOrder iRank", "Set the order value and power ranking for the weapon");
|
|
Event EV_Weapon_SetFireType(
|
|
"firetype", EV_DEFAULT, "s", "firingType", "Set the firing type of the weapon (projectile or bullet)", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetAIRange(
|
|
"airange",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"airange",
|
|
"Set the range of this gun for the ai: short, medium, long, sniper",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetProjectile(
|
|
"projectile",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"projectileModel",
|
|
"Set the model of the projectile that this weapon fires",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetDMProjectile(
|
|
"dmprojectile",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"projectileModel",
|
|
"Set the model of the projectile that this weapon fires",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetBulletDamage(
|
|
"bulletdamage", EV_DEFAULT, "f", "bulletDamage", "Set the damage that the bullet causes", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetDMBulletDamage(
|
|
"dmbulletdamage", EV_DEFAULT, "f", "bulletDamage", "Set the damage that the bullet causes", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetBulletLarge(
|
|
"bulletlarge",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"bulletType",
|
|
"Set if the bullets fired are rifle bullets(1), artillery(2) or larger tracers(3)",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetTracerSpeed(
|
|
"tracerspeed",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"speed",
|
|
"Scale factor of how fast a tracer should travel (valid ranges 0-2)",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetBulletKnockback(
|
|
"bulletknockback", EV_DEFAULT, "f", "bulletKnockback", "Set the knockback that the bullet causes", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetDMBulletKnockback(
|
|
"dmbulletknockback", EV_DEFAULT, "f", "bulletKnockback", "Set the knockback that the bullet causes", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetBulletThroughWood(
|
|
"throughwood", EV_DEFAULT, "f", "dist", "Sets how far the bullets can go through wood", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetBulletThroughMetal(
|
|
"throughmetal", EV_DEFAULT, "f", "dist", "Sets how far the bullets can go through metal", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetBulletCount(
|
|
"bulletcount", EV_DEFAULT, "f", "bulletCount", "Set the number of bullets this weapon shoots when fired", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetDMBulletCount(
|
|
"dmbulletcount",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"bulletCount",
|
|
"Set the number of bullets this weapon shoots when fired",
|
|
EV_NORMAL
|
|
);
|
|
Event
|
|
EV_Weapon_SetBulletRange("bulletrange", EV_DEFAULT, "f", "bulletRange", "Set the range of the bullets", EV_NORMAL);
|
|
Event EV_Weapon_SetDMBulletRange(
|
|
"dmbulletrange", EV_DEFAULT, "f", "bulletRange", "Set the range of the bullets", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetBulletSpread(
|
|
"bulletspread",
|
|
EV_DEFAULT,
|
|
"ffFF",
|
|
"bulletSpreadX bulletSpreadY bulletSpreadXmax bulletSpreadYmax",
|
|
"Set the min & optional max spread of the bullet in the x and y axis",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetDMBulletSpread(
|
|
"dmbulletspread",
|
|
EV_DEFAULT,
|
|
"ffFF",
|
|
"bulletSpreadX bulletSpreadY bulletSpreadXmax bulletSpreadYmax",
|
|
"Set the min & optional max spread of the bullet in the x and y axis",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetZoomSpreadMult(
|
|
"zoomspreadmult",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"scale",
|
|
"Sets the spread multiplier for when using the zoom on a zooming weapon",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetDMZoomSpreadMult(
|
|
"dmzoomspreadmult",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"scale",
|
|
"Sets the spread multiplier for when using the zoom on a zooming weapon",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetFireSpreadMult(
|
|
"firespreadmult",
|
|
EV_DEFAULT,
|
|
"ffff",
|
|
"scaleadd falloff cap maxtime",
|
|
"Sets a time decayed multiplyer to spread when the weapon is fired",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetDMFireSpreadMult(
|
|
"dmfirespreadmult",
|
|
EV_DEFAULT,
|
|
"ffff",
|
|
"scaleadd falloff cap maxtime",
|
|
"Sets a time decayed multiplyer to spread when the weapon is fired",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetTracerFrequency(
|
|
"tracerfrequency", EV_DEFAULT, "f", "frequenct", "Set the frequency of making tracers", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetRange("range", EV_DEFAULT, "f", "range", "Set the range of the weapon", EV_NORMAL);
|
|
Event EV_Weapon_Secondary(
|
|
"secondary",
|
|
EV_DEFAULT,
|
|
"SSSSSSSS",
|
|
"arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8",
|
|
"Set the secondary mode of the weapon, by passing commands through",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_AmmoType("ammotype", EV_DEFAULT, "s", "name", "Set the type of ammo this weapon uses", EV_NORMAL);
|
|
Event EV_Weapon_StartAmmo("startammo", EV_DEFAULT, "i", "amount", "Set the starting ammo of this weapon", EV_NORMAL);
|
|
Event
|
|
EV_Weapon_DMStartAmmo("dmstartammo", EV_DEFAULT, "i", "amount", "Set the starting ammo of this weapon", EV_NORMAL);
|
|
Event EV_Weapon_AmmoRequired(
|
|
"ammorequired", EV_DEFAULT, "i", "amount", "Set the amount of ammo this weapon requires to fire", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_DMAmmoRequired(
|
|
"dmammorequired", EV_DEFAULT, "i", "amount", "Set the amount of ammo this weapon requires to fire", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_MaxChargeTime(
|
|
"maxchargetime", EV_DEFAULT, "i", "time", "Set the maximum time the weapon may be charged up", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_MinChargeTime(
|
|
"minchargetime", EV_DEFAULT, "i", "time", "Set the minimum time the weapon must be charged up", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_AddAdditionalStartAmmo(
|
|
"additionalstartammo",
|
|
EV_DEFAULT,
|
|
"si",
|
|
"ammotype amount",
|
|
"Gives some additional start ammo of the specified type",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_AddStartItem(
|
|
"startitem", EV_DEFAULT, "s", "itemname", "Adds an item to the starting loadout of the weapon", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_GiveStartingAmmo(
|
|
"startingammotoowner",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Internal event used to give ammo to the owner of the weapon",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_AutoAim("autoaim", EV_DEFAULT, NULL, NULL, "Turn on auto aiming for the weapon", EV_NORMAL);
|
|
Event EV_Weapon_Crosshair("crosshair", EV_DEFAULT, "b", "bool", "Turn on/off the crosshair for this weapon", EV_NORMAL);
|
|
Event EV_Weapon_DMCrosshair(
|
|
"dmcrosshair", EV_DEFAULT, "b", "bool", "Turn on/off the crosshair for this weapon", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetQuiet("quiet", EV_DEFAULT, NULL, NULL, "Makes the weapon make no noise.", EV_NORMAL);
|
|
Event EV_Weapon_SetSecondaryAmmoInHud(
|
|
"secondaryammoinhud", EV_DEFAULT, NULL, NULL, "Makes the weapon show its secondary ammo to the hud.", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetLoopFire(
|
|
"loopfire", EV_DEFAULT, NULL, NULL, "Makes the weapon fire by looping the fire animation.", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_OffHandAttachToTag(
|
|
"offhandattachtotag",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"tagname",
|
|
"Set the name of the tag to attach this to it's owner's off hand.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_MainAttachToTag(
|
|
"mainattachtotag",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"tagname",
|
|
"Set the name of the tag to attach this to it's owner when being used.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_HolsterTag(
|
|
"holstertag",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"tagname",
|
|
"Set the name of the tag to attach this to when the weapon is holstered.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_HolsterOffset(
|
|
"holsteroffset", EV_DEFAULT, "v", "offset", "Set the positional offset when it is holstered", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_HolsterAngles(
|
|
"holsterangles", EV_DEFAULT, "v", "angles", "Set the angles of this weapon when it is holstered", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_HolsterScale(
|
|
"holsterscale", EV_DEFAULT, "f", "scale", "Set the scale of this weapon when it is holstered", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_AutoPutaway(
|
|
"autoputaway", EV_DEFAULT, "b", "bool", "Set the weapon to be automatically put away when out of ammo", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_UseNoAmmo(
|
|
"usenoammo", EV_DEFAULT, "b", "bool", "Set the weapon to be able to be used when it's out of ammo", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetMeansOfDeath(
|
|
"meansofdeath", EV_DEFAULT, "s", "meansOfDeath", "Set the meansOfDeath of the weapon.", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetWorldHitSpawn(
|
|
"worldhitspawn",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"modelname",
|
|
"Set a model to be spawned when the weapon strikes the world->",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_MakeNoise(
|
|
"makenoise", EV_DEFAULT, "FB", "noise_radius force", "Makes the weapon make noise that actors can hear.", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetType("weapontype", EV_DEFAULT, "s", "weapon_type", "Sets the weapon type", EV_NORMAL);
|
|
Event EV_Weapon_SetGroup(
|
|
"weapongroup",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"weapon_group",
|
|
"Sets the weapon group, a set of animations for actor animations scripts to use",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetZoom(
|
|
"zoom", EV_DEFAULT, "iI", "zoomfov autozoom", "Sets fov to zoom to on a secondary fire", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetSemiAuto("semiauto", EV_DEFAULT, NULL, NULL, "Sets the weapon to fire semi-auto", EV_NORMAL);
|
|
Event EV_Weapon_AttachToHand(
|
|
"attachtohand", EV_DEFAULT, "s", "weapon_hand", "Attaches an active weapon to the specified hand", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_CantPartialReload(
|
|
"cantpartialreload",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Prevents the weapon from being reloaded part way through a clip",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_DMCantPartialReload(
|
|
"dmcantpartialreload",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Prevents the weapon from being reloaded part way through a clip for DM",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_FallingAngleAdjust(
|
|
"fallingangleadjust", EV_DEFAULT, 0, 0, "Adjusts the weapons angles as it falls to the ground", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetViewKick(
|
|
"viewkick",
|
|
EV_DEFAULT,
|
|
"ffFF",
|
|
"pitchmin pitchmax yawmin yawmax",
|
|
"Adds kick to the view of the owner when fired.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_MovementSpeed(
|
|
"movementspeed",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"speedmult",
|
|
"Alters the movement speed of the player when he has the weapon out",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_DMMovementSpeed(
|
|
"dmmovementspeed",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"speedmult",
|
|
"Alters the movement speed of the player when he has the weapon out",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_MaxFireMovement(
|
|
"maxfiremovement",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"speedmult",
|
|
"Sets the max speed the player can be moving to fire the weapon (fraction of weapon's running speed)",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_ZoomMovement(
|
|
"zoommovement",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"speedmult",
|
|
"Sets the max speed the player can move while zoomed (fraction of weapon's running speed)",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Weapon_AmmoPickupSound(
|
|
"ammopickupsound", EV_DEFAULT, "s", "name", "sets the weapon's ammo pickup sound alias", EV_NORMAL
|
|
);
|
|
Event
|
|
EV_Weapon_NoAmmoSound("noammosound", EV_DEFAULT, "s", "name", "sets the weapon's dry fire sound alias", EV_NORMAL);
|
|
Event EV_Weapon_MaxMovementSound(
|
|
"maxmovementsound", EV_DEFAULT, "s", "name", "sets the weapon's movement fire prevention sound alias", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_NumFireAnims(
|
|
"numfireanims", EV_DEFAULT, "i", "value", "Sets the number of fire animations this weapon uses.", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SetCurrentFireAnim(
|
|
"setcurrentfireanim", EV_DEFAULT, "i", "value", "Sets the current firing animation.", EV_NORMAL
|
|
);
|
|
Event EV_Weapon_SubType(
|
|
"weaponsubtype", EV_DEFAULT, "i", "subtype", "sets the weapon's sub-type. Used by smoke grenades.", EV_NORMAL
|
|
);
|
|
Event EV_SetCookTime("cooktime", EV_DEFAULT, "f", "cooktime", "sets weapons cook time.", EV_NORMAL);
|
|
Event EV_OverCooked(
|
|
"overcooked", EV_DEFAULT, NULL, NULL, "used when the cookable weapon has been over cooked.", EV_NORMAL
|
|
);
|
|
Event EV_OverCooked_Warning(
|
|
"overcooked_warning",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"causes a warning sound to play that the grenade is about to be overcooked.",
|
|
EV_NORMAL
|
|
);
|
|
|
|
//
|
|
// Added in OPM
|
|
//
|
|
|
|
Event EV_Weapon_DoneAnimating("doneanimating", EV_DEFAULT, NULL, NULL, "Signals the end of animation", EV_NORMAL);
|
|
|
|
Event EV_Weapon_GetPutaway(
|
|
"putaway", EV_DEFAULT, NULL, NULL, "Returns whether or not the given weapon is being put away", EV_GETTER
|
|
);
|
|
|
|
CLASS_DECLARATION(Item, Weapon, NULL) {
|
|
{&EV_SetAnim, &Weapon::SetWeaponAnimEvent },
|
|
{&EV_Item_Pickup, &Weapon::PickupWeapon },
|
|
{&EV_Weapon_DoneRaising, &Weapon::DoneRaising },
|
|
{&EV_Weapon_DoneFiring, &Weapon::DoneFiring },
|
|
{&EV_Weapon_Idle, &Weapon::Idle },
|
|
{&EV_Weapon_IdleInit, &Weapon::IdleInit },
|
|
{&EV_BroadcastAIEvent, &Weapon::WeaponSound },
|
|
{&EV_Weapon_DoneReloading, &Weapon::DoneReloading },
|
|
{&EV_Weapon_SetAmmoClipSize, &Weapon::SetAmmoClipSize },
|
|
{&EV_Weapon_SetAmmoInClip, &Weapon::SetAmmoInClip },
|
|
{&EV_Weapon_SetShareClip, &Weapon::SetShareClip },
|
|
{&EV_Weapon_FillClip, &Weapon::FillAmmoClip },
|
|
{&EV_Weapon_EmptyClip, &Weapon::EmptyAmmoClip },
|
|
{&EV_Weapon_AddToClip, &Weapon::AddToAmmoClip },
|
|
{&EV_Weapon_SetMaxRange, &Weapon::SetMaxRangeEvent },
|
|
{&EV_Weapon_SetMinRange, &Weapon::SetMinRangeEvent },
|
|
{&EV_Weapon_FireDelay, &Weapon::SetFireDelay },
|
|
{&EV_Weapon_NotDroppable, &Weapon::NotDroppableEvent },
|
|
{&EV_Weapon_SetAimAnim, &Weapon::SetAimAnim },
|
|
{&EV_Weapon_Shoot, &Weapon::Shoot },
|
|
{&EV_Weapon_SetRank, &Weapon::SetRankEvent },
|
|
{&EV_Weapon_SetFireType, &Weapon::SetFireType },
|
|
{&EV_Weapon_SetAIRange, &Weapon::SetAIRange },
|
|
{&EV_Weapon_SetProjectile, &Weapon::SetProjectile },
|
|
{&EV_Weapon_SetDMProjectile, &Weapon::SetDMProjectile },
|
|
{&EV_Weapon_SetBulletDamage, &Weapon::SetBulletDamage },
|
|
{&EV_Weapon_SetBulletLarge, &Weapon::SetBulletLarge },
|
|
{&EV_Weapon_SetTracerSpeed, &Weapon::SetTracerSpeed },
|
|
{&EV_Weapon_SetBulletCount, &Weapon::SetBulletCount },
|
|
{&EV_Weapon_SetBulletKnockback, &Weapon::SetBulletKnockback },
|
|
{&EV_Weapon_SetBulletThroughWood, &Weapon::SetBulletThroughWood },
|
|
{&EV_Weapon_SetBulletThroughMetal, &Weapon::SetBulletThroughMetal },
|
|
{&EV_Weapon_SetBulletRange, &Weapon::SetBulletRange },
|
|
{&EV_Weapon_SetRange, &Weapon::SetRange },
|
|
{&EV_Weapon_SetBulletSpread, &Weapon::SetBulletSpread },
|
|
{&EV_Weapon_SetTracerFrequency, &Weapon::SetTracerFrequency },
|
|
{&EV_Weapon_Secondary, &Weapon::Secondary },
|
|
{&EV_Weapon_AmmoType, &Weapon::SetAmmoType },
|
|
{&EV_Weapon_StartAmmo, &Weapon::SetStartAmmo },
|
|
{&EV_Weapon_AmmoRequired, &Weapon::SetAmmoRequired },
|
|
{&EV_Weapon_MaxChargeTime, &Weapon::SetMaxChargeTime },
|
|
{&EV_Weapon_MinChargeTime, &Weapon::SetMinChargeTime },
|
|
{&EV_Weapon_AddAdditionalStartAmmo, &Weapon::AddAdditionalStartAmmo },
|
|
{&EV_Weapon_AddStartItem, &Weapon::AddStartItem },
|
|
{&EV_Weapon_GiveStartingAmmo, &Weapon::GiveStartingAmmoToOwner},
|
|
{&EV_Weapon_AutoAim, &Weapon::AutoAim },
|
|
{&EV_Weapon_Crosshair, &Weapon::Crosshair },
|
|
{&EV_Weapon_MainAttachToTag, &Weapon::MainAttachToTag },
|
|
{&EV_Weapon_OffHandAttachToTag, &Weapon::OffHandAttachToTag },
|
|
{&EV_Weapon_HolsterTag, &Weapon::HolsterAttachToTag },
|
|
{&EV_Weapon_HolsterOffset, &Weapon::SetHolsterOffset },
|
|
{&EV_Weapon_HolsterAngles, &Weapon::SetHolsterAngles },
|
|
{&EV_Weapon_HolsterScale, &Weapon::SetHolsterScale },
|
|
{&EV_Weapon_SetQuiet, &Weapon::SetQuiet },
|
|
{&EV_Weapon_SetLoopFire, &Weapon::SetLoopFire },
|
|
{&EV_Weapon_AutoPutaway, &Weapon::SetAutoPutaway },
|
|
{&EV_Weapon_UseNoAmmo, &Weapon::SetUseNoAmmo },
|
|
{&EV_Weapon_SetMeansOfDeath, &Weapon::SetMeansOfDeath },
|
|
{&EV_Weapon_SetWorldHitSpawn, &Weapon::SetWorldHitSpawn },
|
|
{&EV_Weapon_MakeNoise, &Weapon::MakeNoise },
|
|
{&EV_Weapon_SetType, &Weapon::SetWeaponType },
|
|
{&EV_Weapon_SetGroup, &Weapon::SetWeaponGroup },
|
|
{&EV_Weapon_SetZoom, &Weapon::SetZoom },
|
|
{&EV_Weapon_SetSemiAuto, &Weapon::SetSemiAuto },
|
|
{&EV_Weapon_AttachToHand, &Weapon::AttachToHand },
|
|
{&EV_Weapon_CantPartialReload, &Weapon::SetCantPartialReload },
|
|
{&EV_Weapon_FallingAngleAdjust, &Weapon::FallingAngleAdjust },
|
|
{&EV_Weapon_SetViewKick, &Weapon::SetViewKick },
|
|
{&EV_Weapon_DMSetFireDelay, &Weapon::SetDMFireDelay },
|
|
{&EV_Weapon_SetDMBulletDamage, &Weapon::SetDMBulletDamage },
|
|
{&EV_Weapon_SetDMBulletCount, &Weapon::SetDMBulletCount },
|
|
{&EV_Weapon_SetDMBulletSpread, &Weapon::SetDMBulletSpread },
|
|
{&EV_Weapon_SetZoomSpreadMult, &Weapon::SetZoomSpreadMult },
|
|
{&EV_Weapon_SetDMZoomSpreadMult, &Weapon::SetDMZoomSpreadMult },
|
|
{&EV_Weapon_SetFireSpreadMult, &Weapon::SetFireSpreadMult },
|
|
{&EV_Weapon_SetDMFireSpreadMult, &Weapon::SetDMFireSpreadMult },
|
|
{&EV_Weapon_DMAmmoRequired, &Weapon::SetDMAmmoRequired },
|
|
{&EV_Weapon_DMCantPartialReload, &Weapon::SetDMCantPartialReload },
|
|
{&EV_Weapon_DMStartAmmo, &Weapon::SetDMStartAmmo },
|
|
{&EV_Weapon_SetDMBulletRange, &Weapon::SetDMBulletRange },
|
|
{&EV_Weapon_DMCrosshair, &Weapon::DMCrosshair },
|
|
{&EV_Weapon_MovementSpeed, &Weapon::SetMovementSpeed },
|
|
{&EV_Weapon_DMMovementSpeed, &Weapon::SetDMMovementSpeed },
|
|
{&EV_Weapon_MaxFireMovement, &Weapon::SetMaxFireMovement },
|
|
{&EV_Weapon_ZoomMovement, &Weapon::SetZoomMovement },
|
|
{&EV_Weapon_AmmoPickupSound, &Weapon::EventAmmoPickupSound },
|
|
{&EV_Weapon_NoAmmoSound, &Weapon::EventNoAmmoSound },
|
|
{&EV_Weapon_MaxMovementSound, &Weapon::EventMaxMovementSound },
|
|
{&EV_Weapon_NumFireAnims, &Weapon::SetNumFireAnims },
|
|
{&EV_Weapon_NumFireAnims, &Weapon::SetWeaponSubtype },
|
|
{&EV_SetCookTime, &Weapon::SetCookTime },
|
|
{&EV_OverCooked, &Weapon::OnOverCooked },
|
|
{&EV_OverCooked_Warning, &Weapon::OnOverCookedWarning },
|
|
{&EV_Weapon_SetCurrentFireAnim, &Weapon::SetCurrentFireAnim },
|
|
{&EV_Weapon_SetSecondaryAmmoInHud, &Weapon::SetSecondaryAmmoInHud },
|
|
//
|
|
// Added in OPM
|
|
//
|
|
{&EV_Weapon_DoneAnimating, &Weapon::DoneAnimating },
|
|
{&EV_Weapon_GetPutaway, &Weapon::EventGetPutaway },
|
|
{NULL, NULL }
|
|
};
|
|
|
|
//======================
|
|
//Weapon::Weapon
|
|
//======================
|
|
Weapon::Weapon()
|
|
{
|
|
entflags |= ECF_WEAPON;
|
|
|
|
mAIRange = RANGE_SHORT;
|
|
|
|
if (LoadingSavegame) {
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
|
|
// Set the weapon class to item by default
|
|
weapon_class = WEAPON_CLASS_ITEM;
|
|
order = 0;
|
|
|
|
// Owner of the weapon
|
|
owner = NULL;
|
|
|
|
// Fixed in 2.0
|
|
// Clear fire spread multiplier values
|
|
// Maximum spread multiplier while firing
|
|
m_fFireSpreadMultTimeCap[0] = 0;
|
|
m_fFireSpreadMultTimeCap[1] = 0;
|
|
|
|
// Starting rank of the weapon
|
|
rank = 0;
|
|
|
|
// Amount of ammo required for weapon
|
|
INITIALIZE_WEAPONMODE_VAR(ammorequired, 0);
|
|
|
|
// Starting ammo of the weapon
|
|
INITIALIZE_WEAPONMODE_VAR(startammo, 0);
|
|
|
|
// Amount of ammo the clip can hold
|
|
INITIALIZE_WEAPONMODE_VAR(ammo_clip_size, 0);
|
|
|
|
// Amount of ammo in clip
|
|
INITIALIZE_WEAPONMODE_VAR(ammo_in_clip, 0);
|
|
|
|
// Amount of time to pass before broadcasting a weapon sound again
|
|
nextweaponsoundtime = 0;
|
|
|
|
// Last fire state
|
|
m_fLastFireTime = 0;
|
|
m_eLastFireMode = (firemode_t)-11;
|
|
|
|
// The initial state of the weapon
|
|
weaponstate = WEAPON_HOLSTERED;
|
|
|
|
// Is the weapon droppable when the owner is killed
|
|
notdroppable = false;
|
|
|
|
// Aim animation for behavior of monsters
|
|
aimanim = -1;
|
|
aimframe = 0;
|
|
|
|
m_iAnimSlot = 0;
|
|
|
|
// start off unattached
|
|
attached = false;
|
|
|
|
// maximum effective firing distance (for autoaim)
|
|
maxrange = 1000;
|
|
|
|
// minimum safe firing distance (for AI)
|
|
minrange = 0;
|
|
|
|
// speed of the projectile (0 == infinite speed)
|
|
memset(projectilespeed, 0, sizeof(projectilespeed));
|
|
|
|
// Weapons don't move
|
|
setMoveType(MOVETYPE_NONE);
|
|
|
|
// What type of ammo this weapon fires
|
|
INITIALIZE_WEAPONMODE_VAR(firetype, FT_NONE);
|
|
|
|
INITIALIZE_WEAPONMODE_VAR(fire_delay, 0.1f);
|
|
|
|
// Init the bullet specs
|
|
INITIALIZE_WEAPONMODE_VAR(projectilespeed, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(bulletdamage, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(bulletlarge, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(bulletcount, 1);
|
|
INITIALIZE_WEAPONMODE_VAR(bulletrange, 4096);
|
|
INITIALIZE_WEAPONMODE_VAR(bulletknockback, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(bulletthroughwood, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(bulletthroughmetal, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(ammo_type, "");
|
|
INITIALIZE_WEAPONMODE_VAR(loopfire, false);
|
|
INITIALIZE_WEAPONMODE_VAR(quiet, qfalse);
|
|
INITIALIZE_WEAPONMODE_VAR(loopfire, qfalse);
|
|
INITIALIZE_WEAPONMODE_VAR(tracercount, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(tracerfrequency, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(tracerspeed, 1);
|
|
|
|
for (int i = 0; i < MAX_FIREMODES; i++) {
|
|
INITIALIZE_WEAPONMODE_VAR(viewkickmin[i], 0);
|
|
INITIALIZE_WEAPONMODE_VAR(viewkickmax[i], 0);
|
|
}
|
|
|
|
INITIALIZE_WEAPONMODE_VAR(m_fFireSpreadMultAmount, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(m_fFireSpreadMultFalloff, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(m_fFireSpreadMultCap, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(m_fFireSpreadMultTime, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(m_fFireSpreadMult, 0);
|
|
|
|
// Init the max amount of time a weapon may be charged (5 seconds)
|
|
INITIALIZE_WEAPONMODE_VAR(min_charge_time, 0);
|
|
INITIALIZE_WEAPONMODE_VAR(max_charge_time, 5);
|
|
charge_fraction = 1.0f;
|
|
|
|
// Tag to attach this weapon to on its owner when used in the left hand and in the right hand
|
|
attachToTag_offhand = "tag_weapon_left";
|
|
attachToTag_main = "tag_weapon_right";
|
|
|
|
// putaway is flagged true when the weapon should be put away by state machine
|
|
putaway = false;
|
|
|
|
// This is used for setting alternate fire functionality when initializing stuff
|
|
firemodeindex = FIRE_PRIMARY;
|
|
|
|
// Name and index
|
|
setName("Unnamed Weapon");
|
|
|
|
m_bCanPartialReload = qtrue;
|
|
m_bShareClip = qfalse;
|
|
|
|
// do better lighting on all weapons
|
|
edict->s.renderfx |= RF_EXTRALIGHT;
|
|
|
|
// Weapons do not auto aim automatically
|
|
autoaim = false;
|
|
|
|
// No crosshair visible
|
|
crosshair = false;
|
|
|
|
m_iZoom = 0;
|
|
m_bAutoZoom = false;
|
|
m_fZoomSpreadMult = 1.0f;
|
|
m_bSemiAuto = false;
|
|
|
|
// Weapons default to making noise
|
|
next_noise_time = 0;
|
|
next_noammo_time = 0;
|
|
next_maxmovement_time = 0;
|
|
|
|
// Obviously mustn't reload at first
|
|
m_bShouldReload = false;
|
|
|
|
// Used to keep track of last angles and scale before holstering
|
|
lastValid = qfalse;
|
|
lastScale = 1.0f;
|
|
holsterScale = 1.0f;
|
|
|
|
// Weapon will not be putaway by default when out of ammo
|
|
auto_putaway = qfalse;
|
|
|
|
// Weapon will be able to be used when it has no ammo
|
|
use_no_ammo = qtrue;
|
|
|
|
INITIALIZE_WEAPONMODE_VAR(meansofdeath, MOD_NONE);
|
|
|
|
// Set the stats
|
|
m_iNumShotsFired = 0;
|
|
m_iNumHits = 0;
|
|
m_iNumGroinShots = 0;
|
|
m_iNumHeadShots = 0;
|
|
m_iNumLeftArmShots = 0;
|
|
m_iNumRightArmShots = 0;
|
|
m_iNumLeftLegShots = 0;
|
|
m_iNumRightLegShots = 0;
|
|
m_iNumTorsoShots = 0;
|
|
|
|
// Set the default weapon group
|
|
m_csWeaponGroup = STRING_EMPTY;
|
|
m_fMovementSpeed = 1.0f;
|
|
m_fMaxFireMovement = 1.0f;
|
|
m_fZoomMovement = 1.0f;
|
|
|
|
m_sAmmoPickupSound = "snd_pickup_";
|
|
m_NoAmmoSound = "snd_noammo";
|
|
m_sMaxMovementSound = "snd_maxmovement";
|
|
|
|
// Always has a fire animation
|
|
m_iNumFireAnims = 1;
|
|
m_iCurrentFireAnim = 0;
|
|
|
|
// Default tag to use for muzzle and special effects
|
|
m_sTagBarrel = "tag_barrel";
|
|
|
|
m_iWeaponSubtype = 0;
|
|
|
|
// Not cooking by default
|
|
m_fCookTime = 0;
|
|
m_eCookModeIndex = FIRE_PRIMARY;
|
|
// Defaults to no secondary HUD
|
|
m_bSecondaryAmmoInHud = false;
|
|
|
|
PostEvent(EV_Weapon_IdleInit, 0);
|
|
|
|
last_owner_trigger_time = 0;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::Weapon
|
|
//======================
|
|
Weapon::Weapon(const char *file)
|
|
{
|
|
// The tik file holds all the information available for a weapon
|
|
Weapon();
|
|
}
|
|
|
|
//======================
|
|
//Weapon::~Weapon
|
|
//======================
|
|
Weapon::~Weapon()
|
|
{
|
|
DetachGun();
|
|
|
|
if (owner) {
|
|
RemoveFromOwner();
|
|
}
|
|
|
|
entflags &= ~ECF_WEAPON;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::Delete
|
|
//======================
|
|
void Weapon::Delete(void)
|
|
{
|
|
if (g_iInThinks) {
|
|
DetachGun();
|
|
}
|
|
|
|
Item::Delete();
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetRankEvent
|
|
//======================
|
|
void Weapon::SetRankEvent(Event *ev)
|
|
{
|
|
SetRank(ev->GetInteger(1), ev->GetInteger(2));
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetWeaponType
|
|
//======================
|
|
void Weapon::SetWeaponType(Event *ev)
|
|
{
|
|
weapon_class = G_WeaponClassNameToNum(ev->GetString(1));
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetWeaponGroup
|
|
//======================
|
|
void Weapon::SetWeaponGroup(Event *ev)
|
|
{
|
|
m_csWeaponGroup = ev->GetConstString(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetZoom
|
|
//======================
|
|
void Weapon::SetZoom(Event *ev)
|
|
{
|
|
m_iZoom = ev->GetInteger(1);
|
|
|
|
if (ev->NumArgs() > 1) {
|
|
int autozoom = ev->GetInteger(2);
|
|
|
|
if (autozoom) {
|
|
m_bAutoZoom = qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetSemiAuto
|
|
//======================
|
|
void Weapon::SetSemiAuto(Event *ev)
|
|
{
|
|
m_bSemiAuto = qtrue;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetAutoPutaway
|
|
//======================
|
|
void Weapon::SetAutoPutaway(Event *ev)
|
|
{
|
|
auto_putaway = ev->GetBoolean(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetUseNoAmmo
|
|
//======================
|
|
void Weapon::SetUseNoAmmo(Event *ev)
|
|
{
|
|
use_no_ammo = ev->GetBoolean(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetStartAmmo
|
|
//======================
|
|
void Weapon::SetStartAmmo(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
startammo[firemodeindex] = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMStartAmmo
|
|
//======================
|
|
void Weapon::SetDMStartAmmo(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
startammo[firemodeindex] = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetMaxChargeTime
|
|
//======================
|
|
void Weapon::SetMaxChargeTime(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
max_charge_time[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetMaxChargeTime
|
|
//======================
|
|
void Weapon::SetMinChargeTime(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
min_charge_time[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetMinChargeTime
|
|
//======================
|
|
float Weapon::GetMinChargeTime(firemode_t mode)
|
|
{
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
return min_charge_time[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetMinChargeTime
|
|
//======================
|
|
float Weapon::GetMaxChargeTime(firemode_t mode)
|
|
{
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
return max_charge_time[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetAmmoRequired
|
|
//======================
|
|
void Weapon::SetAmmoRequired(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
ammorequired[firemodeindex] = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMAmmoRequired
|
|
//======================
|
|
void Weapon::SetDMAmmoRequired(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
ammorequired[firemodeindex] = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetAmmoType
|
|
//======================
|
|
str Weapon::GetAmmoType(firemode_t mode)
|
|
{
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
|
|
mode = m_bShareClip ? FIRE_PRIMARY : mode;
|
|
return ammo_type[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetAmmoType
|
|
//======================
|
|
void Weapon::SetAmmoType(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
|
|
if ((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES)) {
|
|
ammo_type[firemodeindex] = ev->GetString(1);
|
|
} else {
|
|
warning("Weapon::SetAmmoType", "Invalid mode %d\n", firemodeindex);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetAmmoAmount
|
|
//======================
|
|
void Weapon::SetAmmoAmount(int amount, firemode_t mode)
|
|
{
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
|
|
mode = m_bShareClip ? FIRE_PRIMARY : mode;
|
|
|
|
// If the clip can hold ammo, then set the amount in the clip to the specified amount
|
|
if (ammo_clip_size[mode]) {
|
|
ammo_in_clip[mode] = amount;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetClipSize
|
|
//======================
|
|
int Weapon::GetClipSize(firemode_t mode)
|
|
{
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
|
|
mode = m_bShareClip ? FIRE_PRIMARY : mode;
|
|
|
|
if ((mode >= 0) && (mode < MAX_FIREMODES)) {
|
|
return ammo_clip_size[mode];
|
|
} else {
|
|
warning("Weapon::GetClipSize", "Invalid mode %d\n", mode);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::UseAmmo
|
|
//======================
|
|
void Weapon::UseAmmo(int amount, firemode_t mode)
|
|
{
|
|
mode = m_bShareClip ? FIRE_PRIMARY : mode;
|
|
|
|
if (UnlimitedAmmo(mode) && (!owner || !owner->isClient())) {
|
|
return;
|
|
}
|
|
|
|
// Remove ammo from the clip if it's available
|
|
if (ammo_clip_size[mode]) {
|
|
ammo_in_clip[mode] -= amount;
|
|
if (ammo_in_clip[mode] < 0) {
|
|
warning("UseAmmo", "Used more ammo than in clip.\n");
|
|
ammo_in_clip[mode] = 0;
|
|
}
|
|
if (!ammo_in_clip[mode]) {
|
|
SetShouldReload(qtrue);
|
|
}
|
|
owner->AmmoAmountInClipChanged(ammo_type[mode], ammo_in_clip[mode]);
|
|
} else {
|
|
assert(owner);
|
|
if (owner && owner->isClient() && !UnlimitedAmmo(mode)) {
|
|
// Remove ammo from the player's inventory
|
|
owner->UseAmmo(ammo_type[mode], ammorequired[mode]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
//======================
|
|
//Weapon::GetMuzzlePosition
|
|
//======================
|
|
void Weapon::GetMuzzlePosition
|
|
(
|
|
Vector *position,
|
|
Vector *forward,
|
|
Vector *right,
|
|
Vector *up
|
|
)
|
|
|
|
{
|
|
orientation_t weap_or, barrel_or, orient;
|
|
Vector pos;
|
|
vec3_t mat[3]={0,0,0,0,0,0,0,0,0};
|
|
vec3_t orient[3];
|
|
int i, mi, tagnum;
|
|
|
|
assert( owner );
|
|
|
|
// We should always have an owner
|
|
if ( !owner )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get the owner's weapon orientation ( this is custom code and doesn't use the GetTag function
|
|
// because we need to use the saved off fire_frame and fire_animation indexes from the owner
|
|
mi = owner->edict->s.modelindex;
|
|
|
|
tagnum = gi.Tag_NumForName( mi, current_attachToTag.c_str() );
|
|
|
|
if ( tagnum < 0 )
|
|
{
|
|
warning( "Weapon::GetMuzzlePosition", "Could not find tag \"%s\"", current_attachToTag.c_str() );
|
|
pos = owner->centroid;
|
|
AnglesToAxis( owner->angles, mat );
|
|
goto out;
|
|
}
|
|
|
|
// Get the orientation based on the frame and anim stored off in the owner.
|
|
// This is to prevent weird timing with getting orientations on different frames of firing
|
|
// animations and the orientations will not be consistent.
|
|
|
|
AnglesToAxis( owner->angles, owner->orientation );
|
|
|
|
orient = gi.Tag_OrientationEx( mi,
|
|
owner->CurrentAnim( legs ),
|
|
owner->CurrentFrame( legs ),
|
|
tagnum & TAG_MASK,
|
|
owner->edict->s.scale,
|
|
owner->edict->s.bone_tag,
|
|
owner->edict->s.bone_quat,
|
|
0,
|
|
0,
|
|
1.0f,
|
|
( owner->edict->s.anim & ANIM_BLEND ) != 0,
|
|
( owner->edict->s.torso_anim & ANIM_BLEND ) != 0,
|
|
owner->GetFiringAnim(),
|
|
owner->GetFiringFrame(),
|
|
0,
|
|
0,
|
|
1.0f
|
|
);
|
|
|
|
// Transform the weapon's orientation through the owner's orientation
|
|
// Player orientation is normally based on the player's view, but we need
|
|
// it to be based on the model's orientation, so we calculate it here.
|
|
AnglesToAxis( owner->angles, orient );
|
|
VectorCopy( owner->origin, weap_or.origin );
|
|
for ( i=0; i<3; i++ )
|
|
{
|
|
VectorMA( weap_or.origin, orient.origin[i], orient[i], weap_or.origin );
|
|
}
|
|
|
|
MatrixMultiply( orient.axis, orient, weap_or.axis );
|
|
|
|
// For debugging
|
|
G_DrawCoordSystem( weap_or.origin, weap_or.axis[0], weap_or.axis[1], weap_or.axis[2], 50 );
|
|
|
|
// Get the tag_barrel orientation from the weapon
|
|
if ( !this->GetRawTag( "tag_barrel", &barrel_or ) )
|
|
{
|
|
//warning( "Weapon::GetMuzzlePosition", "Could not find tag_barrel\n" );
|
|
pos = owner->centroid;
|
|
AnglesToAxis( owner->angles, mat );
|
|
goto out;
|
|
}
|
|
|
|
//gi.DPrintf( "anim:%d frame:%d\n", this->CurrentAnim(), this->CurrentFrame() );
|
|
|
|
// Translate the barrel's orientation through the weapon's orientation
|
|
VectorCopy( weap_or.origin, pos );
|
|
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
VectorMA( pos, barrel_or.origin[i], weap_or.axis[i], pos );
|
|
}
|
|
|
|
MatrixMultiply( barrel_or.axis, weap_or.axis, mat );
|
|
|
|
#if 0
|
|
gi.DPrintf( "own_angles: %0.2f %0.2f %0.2f\n", owner->angles[0], owner->angles[1], owner->angles[2] );
|
|
gi.DPrintf( "own_orient: %0.2f %0.2f %0.2f\n", owner->orientation[0][0], owner->orientation[0][1], owner->orientation[0][2] );
|
|
gi.DPrintf( "bone forward: %0.2f %0.2f %0.2f\n", orient.axis[0][0], orient.axis[0][1], orient.axis[0][2] );
|
|
gi.DPrintf( "barrel forward: %0.2f %0.2f %0.2f\n", barrel_or.axis[0][0], barrel_or.axis[0][1], barrel_or.axis[0][2] );
|
|
gi.DPrintf( "weapon forward: %0.2f %0.2f %0.2f\n", weap_or.axis[0][0], weap_or.axis[0][1], weap_or.axis[0][2] );
|
|
gi.DPrintf( "mat forward: %0.2f %0.2f %0.2f\n \n", mat[0][0], mat[0][1], mat[0][2] );
|
|
#endif
|
|
|
|
// For debugging
|
|
G_DrawCoordSystem( pos, mat[0], mat[1], mat[2], 30 );
|
|
|
|
// Ok - we now have a position, forward, right, and up
|
|
out:
|
|
if ( position )
|
|
{
|
|
*position = pos;
|
|
}
|
|
|
|
if ( forward )
|
|
{
|
|
*forward = mat[0];
|
|
}
|
|
|
|
if ( right )
|
|
{
|
|
*right = mat[1];
|
|
}
|
|
|
|
if ( up )
|
|
{
|
|
*up = mat[2];
|
|
}
|
|
}
|
|
*/
|
|
|
|
//======================
|
|
//Weapon::GetMuzzlePosition
|
|
//======================
|
|
void Weapon::GetMuzzlePosition(vec3_t position, vec3_t vBarrelPos, vec3_t forward, vec3_t right, vec3_t up)
|
|
{
|
|
orientation_t weap_or, barrel_or, orient;
|
|
Vector delta;
|
|
Vector aim_angles;
|
|
int tagnum;
|
|
int i;
|
|
|
|
owner = (Player *)this->owner.Pointer();
|
|
|
|
// We should always have an owner
|
|
if (!owner) {
|
|
if (IsSubclassOfVehicleTurretGun()) {
|
|
VehicleTurretGun *t = (VehicleTurretGun *)this;
|
|
owner = (Player *)t->GetRemoteOwner().Pointer();
|
|
}
|
|
}
|
|
|
|
if (owner) {
|
|
tagnum = gi.Tag_NumForName(owner->edict->tiki, current_attachToTag.c_str());
|
|
|
|
// Get the orientation based on the frame and anim stored off in the owner.
|
|
// This is to prevent weird timing with getting orientations on different frames of firing
|
|
// animations and the orientations will not be consistent.
|
|
AnglesToAxis(owner->angles, owner->orientation);
|
|
|
|
orient = G_TIKI_Orientation(owner->edict, tagnum);
|
|
|
|
if (owner->IsSubclassOfPlayer()) {
|
|
Player *player;
|
|
|
|
// Use the player's torso angles to determine direction, but use the actual barrel to determine position
|
|
|
|
player = static_cast<Player *>(owner.Pointer());
|
|
|
|
if (forward || right || up) {
|
|
Vector aimAng = player->m_vViewAng;
|
|
vec3_t vrAim;
|
|
|
|
//
|
|
// Added in OPM
|
|
//
|
|
// In VR the shot follows the weapon hand, not the head. The
|
|
// camera stays where the player is looking - moving it to the
|
|
// controller would be unusable - so only the trace moves.
|
|
//
|
|
// Pitch comes straight from the controller; the head
|
|
// contributes nothing to it, and blending the two would fight
|
|
// the player's own wrist. Yaw is the view yaw plus how far the
|
|
// hand leads the head, because the controller's absolute yaw
|
|
// means nothing to the game: the player can turn on the spot
|
|
// with the stick, and the play space has an arbitrary forward.
|
|
//
|
|
// This is what RTCWQuest does, at rtcw/src/game/g_weapon.c:1904.
|
|
//
|
|
if (gi.VR_GetWeaponAim && gi.VR_GetWeaponAim(vrAim)) {
|
|
aimAng[PITCH] = vrAim[PITCH];
|
|
aimAng[YAW] = AngleNormalize360(aimAng[YAW] + vrAim[YAW]);
|
|
}
|
|
|
|
AngleVectors(aimAng, forward, right, up);
|
|
}
|
|
|
|
VectorCopy(player->m_vViewPos, position);
|
|
|
|
if (this->GetRawTag(GetTagBarrel(), &barrel_or) && vBarrelPos) {
|
|
VectorCopy(owner->origin, vBarrelPos);
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(vBarrelPos, orient.origin[i], owner->orientation[i], vBarrelPos);
|
|
}
|
|
|
|
MatrixMultiply(orient.axis, owner->orientation, weap_or.axis);
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(vBarrelPos, barrel_or.origin[i], weap_or.axis[i], vBarrelPos);
|
|
}
|
|
} else if (vBarrelPos) {
|
|
VectorCopy(position, vBarrelPos);
|
|
}
|
|
} else {
|
|
VectorCopy(owner->origin, position);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(position, orient.origin[i], owner->orientation[i], position);
|
|
}
|
|
|
|
MatrixMultiply(orient.axis, owner->orientation, weap_or.axis);
|
|
|
|
if (this->GetRawTag(GetTagBarrel(), &barrel_or)) {
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(position, barrel_or.origin[i], weap_or.axis[i], position);
|
|
}
|
|
}
|
|
|
|
if (vBarrelPos) {
|
|
VectorCopy(position, vBarrelPos);
|
|
}
|
|
|
|
delta = owner->GunTarget(false, position, weap_or.axis[0]) - position;
|
|
aim_angles = delta.toAngles();
|
|
|
|
if (forward || right || up) {
|
|
AngleVectors(aim_angles, forward, right, up);
|
|
}
|
|
}
|
|
} else {
|
|
if (forward || right || up) {
|
|
AngleVectors(angles, forward, right, up);
|
|
}
|
|
|
|
VectorCopy(origin, position);
|
|
|
|
if (this->GetRawTag(GetTagBarrel(), &barrel_or)) {
|
|
AnglesToAxis(angles, weap_or.axis);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(position, barrel_or.origin[i], weap_or.axis[i], position);
|
|
}
|
|
}
|
|
|
|
if (vBarrelPos) {
|
|
VectorCopy(position, vBarrelPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetAmmoClipSize
|
|
//======================
|
|
void Weapon::SetAmmoClipSize(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
ammo_clip_size[firemodeindex] = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetAmmoInClip
|
|
//======================
|
|
void Weapon::SetAmmoInClip(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
ammo_in_clip[firemodeindex] = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetShareClip
|
|
//======================
|
|
void Weapon::SetShareClip(Event *ev)
|
|
{
|
|
m_bShareClip = qtrue;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetTagBarrel
|
|
//======================
|
|
void Weapon::SetTagBarrel(const char *tagBarrel)
|
|
{
|
|
m_sTagBarrel = tagBarrel;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetTagBarrel
|
|
//======================
|
|
str Weapon::GetTagBarrel() const
|
|
{
|
|
return m_sTagBarrel;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::Shoot
|
|
//======================
|
|
void Weapon::Shoot(Event *ev)
|
|
{
|
|
Vector pos, forward, right, up, vBarrel, delta;
|
|
firemode_t mode = FIRE_PRIMARY;
|
|
qboolean mc;
|
|
|
|
if (ev->NumArgs() > 0) {
|
|
mode = WeaponModeNameToNum(ev->GetString(1));
|
|
|
|
if (mode == FIRE_ERROR) {
|
|
return;
|
|
}
|
|
|
|
if (ev->NumArgs() > 1) {
|
|
SetTagBarrel(ev->GetString(2));
|
|
}
|
|
}
|
|
|
|
if (owner && owner->IsSubclassOfSentient() && owner->m_bOvercookDied) {
|
|
owner->m_bOvercookDied = false;
|
|
return;
|
|
}
|
|
|
|
mc = MuzzleClear();
|
|
|
|
// If we are in loopfire, we need to keep checking ammo and using it up
|
|
if (loopfire[mode]) {
|
|
if (HasAmmo(mode) && mc) {
|
|
// Use up the appropriate amount of ammo, it's already been checked that we have enough
|
|
UseAmmo(ammorequired[mode], mode);
|
|
} else {
|
|
ForceIdle();
|
|
}
|
|
}
|
|
|
|
// If the muzzle is not clear, then change to a clear animation, otherwise punt out.
|
|
if (!mc) {
|
|
ForceIdle();
|
|
return;
|
|
}
|
|
|
|
GetMuzzlePosition(pos, vBarrel, forward, right, up);
|
|
ApplyFireKickback(forward, 1000.0);
|
|
|
|
if (firetype[mode] != FT_LANDMINE || CanPlaceLandmine(pos, owner)) {
|
|
if (m_fFireSpreadMultAmount[mode] != 0.0f) {
|
|
float fTime = level.time - m_fFireSpreadMultTime[mode];
|
|
|
|
if (fTime <= m_fFireSpreadMultTimeCap[mode]) {
|
|
float fDecay = fTime * m_fFireSpreadMultFalloff[mode];
|
|
|
|
if (m_fFireSpreadMult[mode] <= 0.0f) {
|
|
m_fFireSpreadMult[mode] -= fDecay;
|
|
|
|
if (m_fFireSpreadMult[mode] > 0.0f) {
|
|
m_fFireSpreadMult[mode] = 0.0f;
|
|
}
|
|
} else {
|
|
m_fFireSpreadMult[mode] -= fDecay;
|
|
|
|
if (m_fFireSpreadMult[mode] < 0.0f) {
|
|
m_fFireSpreadMult[mode] = 0.0f;
|
|
}
|
|
}
|
|
} else {
|
|
m_fFireSpreadMult[mode] = 0.0f;
|
|
}
|
|
|
|
m_fFireSpreadMultTime[mode] = level.time;
|
|
}
|
|
|
|
switch (firetype[mode]) {
|
|
case FT_PROJECTILE:
|
|
{
|
|
//
|
|
// Added in OPM
|
|
//
|
|
// A thrown weapon in VR goes where the arm sent it.
|
|
//
|
|
// The flat game throws along the view at a speed set by how
|
|
// long the button was held, because a mouse has no other way
|
|
// to say "over there, gently". A hand does: the swing carries
|
|
// both the direction and the strength, and using anything
|
|
// else means the player winds up, lets go, and watches the
|
|
// grenade leave along their nose instead.
|
|
//
|
|
// Only for grenades. Rockets and the like are projectiles
|
|
// too, and they are fired rather than thrown - those keep the
|
|
// weapon's own aim.
|
|
//
|
|
Vector vThrowDir = forward;
|
|
float fThrowFraction = 0.0f;
|
|
qboolean bThrown = qfalse;
|
|
|
|
if ((GetWeaponClass() & WEAPON_CLASS_GRENADE) && gi.VR_GetThrow) {
|
|
vec3_t vVel;
|
|
float fBaseYaw = 0.0f;
|
|
|
|
if (gi.VR_GetThrow(vVel, &fBaseYaw)) {
|
|
Sentient *pOwner = (Sentient *)(Entity *)owner;
|
|
vec3_t vBodyAngles, vBodyAxis[3], vWorld;
|
|
float fSpeed;
|
|
int i;
|
|
|
|
// Into the world the same way every other play space
|
|
// vector here is: rotated by the heading the body has
|
|
// that the head did not give it.
|
|
VectorSet(vBodyAngles, 0.0f,
|
|
(pOwner ? pOwner->GetViewAngles()[YAW] : 0.0f) - fBaseYaw, 0.0f);
|
|
AnglesToAxis(vBodyAngles, vBodyAxis);
|
|
|
|
VectorClear(vWorld);
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(vWorld, vVel[i], vBodyAxis[i], vWorld);
|
|
}
|
|
|
|
fSpeed = VectorLength(vWorld);
|
|
|
|
if (fSpeed > 1.0f) {
|
|
static cvar_t *vr_throwFull = NULL;
|
|
float fReference;
|
|
|
|
VectorScale(vWorld, 1.0f / fSpeed, vWorld);
|
|
|
|
//
|
|
// Both the direction and the strength come from
|
|
// the swing. This is a physical throw: the grenade
|
|
// leaves along the line the hand was travelling,
|
|
// at a speed set by how fast it was going.
|
|
//
|
|
// Which instant of the swing to read is the whole
|
|
// problem, and it took three tries to get right.
|
|
// Averaging across the release blends the wind up
|
|
// with the follow through and aims between them.
|
|
// Taking the peak of the whole swing catches the
|
|
// backswing - measured on device, four throws all
|
|
// came out around 0.95 *backwards*. What works is
|
|
// the peak of a short window ending at release,
|
|
// which is what the hand was doing as it let go;
|
|
// the same four throws then measured 0.89 to 0.94
|
|
// straight ahead, and an underhand lob came out
|
|
// genuinely upward at 0.58.
|
|
//
|
|
// RTCWQuest does not do this at all - its grenade
|
|
// (VrInputDefault.c, the backpack mode) is only a
|
|
// selection gesture, thrown afterwards by the
|
|
// game's own fire along the controller. Aiming by
|
|
// the controller is steadier and it is not what a
|
|
// throw is. This port does the harder thing on
|
|
// purpose.
|
|
//
|
|
|
|
//
|
|
// Strength as a fraction of the game's own throw,
|
|
// not as an absolute speed.
|
|
//
|
|
// An absolute number would have to be invented
|
|
// here and would then disagree with whatever the
|
|
// grenade was authored to do - and every thrown
|
|
// weapon has its own. The fraction is the knob the
|
|
// engine already uses for a held throw, so feeding
|
|
// it from the arm keeps all of that tuning and
|
|
// only changes what decides it.
|
|
//
|
|
// The reference is the peak speed of a hard throw,
|
|
// measured on device: four throws came in between
|
|
// 298 and 338 units a second, so 340 is about what
|
|
// a full effort looks like. Below that the throw
|
|
// scales down; a gentle lob stays a gentle lob.
|
|
//
|
|
if (!vr_throwFull) {
|
|
vr_throwFull = gi.Cvar_Get("vr_throwFull", "340", CVAR_ARCHIVE);
|
|
}
|
|
|
|
fReference = (vr_throwFull && vr_throwFull->value > 1.0f)
|
|
? vr_throwFull->value : 340.0f;
|
|
|
|
//
|
|
// Linear, with a floor.
|
|
//
|
|
// A square root curve was tried here and made
|
|
// everything stronger, including the overhand
|
|
// throw that was already right: the same swing
|
|
// that measured 89% linear came out at 94%, and
|
|
// softer ones moved far more - 192 units a second
|
|
// went from 56% to 75%. It read as the throw
|
|
// having become too sharp, because it had.
|
|
//
|
|
// The strong end was never the problem. Linear is
|
|
// what the arm expects, and a floor lifts the
|
|
// gentle throws that would otherwise drop at the
|
|
// player's feet without touching anything above
|
|
// it.
|
|
//
|
|
//
|
|
// Raised to a power, and the power is above one on
|
|
// purpose.
|
|
//
|
|
// A lob leaves at around 45 degrees, which is the
|
|
// angle that carries furthest - so a gentle
|
|
// underhand at half strength outruns a hard flat
|
|
// throw, and lobs sailed past where they were
|
|
// aimed. Linear strength is not enough to stop
|
|
// that, because the angle is doing the work.
|
|
//
|
|
// An exponent above one bends the gentle end down
|
|
// hard while barely touching the top, which is the
|
|
// reverse of the square root tried earlier - that
|
|
// lifted the bottom and made everything too sharp.
|
|
//
|
|
{
|
|
static cvar_t *vr_throwCurve = NULL;
|
|
float fCurve;
|
|
|
|
if (!vr_throwCurve) {
|
|
vr_throwCurve = gi.Cvar_Get("vr_throwCurve", "1.5", CVAR_ARCHIVE);
|
|
}
|
|
|
|
fCurve = (vr_throwCurve && vr_throwCurve->value > 0.1f)
|
|
? vr_throwCurve->value : 1.5f;
|
|
|
|
fThrowFraction = pow(fSpeed / fReference, fCurve);
|
|
}
|
|
|
|
// A floor rather than zero: the player has decided
|
|
// to throw, and a grenade that drops at their feet
|
|
// because the swing was polite is not a mechanic.
|
|
// Tunable, because where it belongs is a question
|
|
// about how it feels and every guess so far has
|
|
// cost a rebuild.
|
|
{
|
|
static cvar_t *vr_throwMin = NULL;
|
|
float fMin;
|
|
|
|
if (!vr_throwMin) {
|
|
vr_throwMin = gi.Cvar_Get("vr_throwMin", "0.2", CVAR_ARCHIVE);
|
|
}
|
|
|
|
fMin = vr_throwMin ? vr_throwMin->value : 0.2f;
|
|
|
|
if (fThrowFraction < fMin) {
|
|
fThrowFraction = fMin;
|
|
}
|
|
}
|
|
|
|
if (fThrowFraction > 1.0f) {
|
|
fThrowFraction = 1.0f;
|
|
}
|
|
|
|
//
|
|
// Damp the sideways component of a gentle throw.
|
|
//
|
|
// This is the one place here that deliberately
|
|
// does not report what the arm did. An underhand
|
|
// swing comes across the body, so the hand really
|
|
// is moving sideways as it lets go - measured on
|
|
// device, lobs drifted left by 0.35 and once by
|
|
// 0.73, more sideways than forward. That is honest
|
|
// physics and it is not what the player meant.
|
|
//
|
|
// Scaled by how gentle the throw is, so a hard
|
|
// overhand keeps its true direction - those were
|
|
// already landing where they were aimed and must
|
|
// not be touched - while a soft lob is pulled back
|
|
// towards where the player is facing.
|
|
//
|
|
{
|
|
static cvar_t *vr_throwDamp = NULL;
|
|
float fDamp, fViewYaw, fRad, fC, fS;
|
|
float fAheadC, fLeftC;
|
|
|
|
if (!vr_throwDamp) {
|
|
vr_throwDamp = gi.Cvar_Get("vr_throwDamp", "0.8", CVAR_ARCHIVE);
|
|
}
|
|
|
|
fDamp = (vr_throwDamp ? vr_throwDamp->value : 0.8f)
|
|
* (1.0f - fThrowFraction);
|
|
|
|
if (fDamp < 0.0f) {
|
|
fDamp = 0.0f;
|
|
} else if (fDamp > 0.95f) {
|
|
fDamp = 0.95f;
|
|
}
|
|
|
|
fViewYaw = pOwner ? pOwner->GetViewAngles()[YAW] : 0.0f;
|
|
fRad = -DEG2RAD(fViewYaw);
|
|
fC = cos(fRad);
|
|
fS = sin(fRad);
|
|
|
|
// Into the player's frame, damp across, and
|
|
// back out again.
|
|
fAheadC = vWorld[0] * fC - vWorld[1] * fS;
|
|
fLeftC = vWorld[0] * fS + vWorld[1] * fC;
|
|
|
|
fLeftC *= (1.0f - fDamp);
|
|
|
|
vWorld[0] = fAheadC * fC + fLeftC * fS;
|
|
vWorld[1] = -fAheadC * fS + fLeftC * fC;
|
|
|
|
VectorNormalize(vWorld);
|
|
}
|
|
|
|
vThrowDir = Vector(vWorld);
|
|
|
|
{
|
|
// The direction as it reaches the world, which
|
|
// is the only one worth reading. The play
|
|
// space vector logged on the VR side is
|
|
// relative to whichever way the room was
|
|
// facing when tracking started, so forward and
|
|
// backward cannot be told apart in it.
|
|
static cvar_t *vr_traceThrow = NULL;
|
|
|
|
if (!vr_traceThrow) {
|
|
vr_traceThrow = gi.Cvar_Get("vr_traceThrow", "0", 0);
|
|
}
|
|
|
|
if (vr_traceThrow && vr_traceThrow->integer) {
|
|
// Relative to where the player is facing,
|
|
// which is the only frame that answers the
|
|
// question being asked. World axes belong
|
|
// to the map and play space axes to the
|
|
// room; in neither can "straight ahead" be
|
|
// told from "ninety degrees off". Here
|
|
// ahead is +1 0 0, the player's left is
|
|
// 0 +1 0, and up is up.
|
|
const float fViewYaw = pOwner ? pOwner->GetViewAngles()[YAW] : 0.0f;
|
|
const float fRad = -DEG2RAD(fViewYaw);
|
|
const float fC = cos(fRad);
|
|
const float fS = sin(fRad);
|
|
const float fAhead = vWorld[0] * fC - vWorld[1] * fS;
|
|
const float fLeft = vWorld[0] * fS + vWorld[1] * fC;
|
|
|
|
gi.Printf("VR throw: ahead %.2f left %.2f up %.2f | speed %.0f -> %.0f%% of full\n",
|
|
fAhead, fLeft, vWorld[2], fSpeed, fThrowFraction * 100.0f);
|
|
}
|
|
}
|
|
|
|
bThrown = qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
ProjectileAttack(pos, vThrowDir, owner, projectileModel[mode],
|
|
bThrown ? fThrowFraction : charge_fraction, 0, this);
|
|
}
|
|
break;
|
|
case FT_LANDMINE:
|
|
PlaceLandmine(pos, owner, projectileModel[mode], this);
|
|
break;
|
|
case FT_DEFUSE:
|
|
DefuseObject(forward, owner, bulletrange[mode]);
|
|
break;
|
|
case FT_BULLET:
|
|
{
|
|
Vector vSpread;
|
|
float fSpreadFactor;
|
|
int tracerFrequency;
|
|
SafePtr<Sentient> ownerPtr;
|
|
|
|
if (owner) {
|
|
if (owner->client) {
|
|
Player *player = (Player *)owner.Pointer();
|
|
|
|
fSpreadFactor = GetSpreadFactor(mode);
|
|
|
|
vSpread = bulletspreadmax[mode] * fSpreadFactor;
|
|
fSpreadFactor = 1.0f - fSpreadFactor;
|
|
vSpread += bulletspread[mode] * fSpreadFactor;
|
|
vSpread *= m_fFireSpreadMult[mode] + 1.0f;
|
|
|
|
if (m_iZoom) {
|
|
if (player->IsSubclassOfPlayer() && player->IsZoomed()) {
|
|
vSpread *= 1.0f + fSpreadFactor * (m_fZoomSpreadMult - 1.0f);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
vSpread = (bulletspreadmax[mode] + bulletspread[mode]) * 0.5f;
|
|
}
|
|
|
|
// Don't display tracers if the owner is a player for non-turret weapons
|
|
// It would have some artifacts for the first person model, as the tracer is created from the world model
|
|
if ((g_target_game == TG_MOH || g_gametype->integer == GT_SINGLE_PLAYER) && owner
|
|
&& owner->IsSubclassOfPlayer()) {
|
|
if (IsSubclassOfTurretGun()) {
|
|
tracerFrequency = 3;
|
|
} else {
|
|
tracerFrequency = 0;
|
|
}
|
|
} else {
|
|
tracerFrequency = tracerfrequency[mode];
|
|
}
|
|
|
|
ownerPtr = owner;
|
|
|
|
if (!owner && IsSubclassOfVehicleTurretGun()) {
|
|
VehicleTurretGun *turretGun = static_cast<VehicleTurretGun *>(this);
|
|
ownerPtr = turretGun->GetRemoteOwner();
|
|
}
|
|
|
|
BulletAttack(
|
|
pos,
|
|
vBarrel,
|
|
forward,
|
|
right,
|
|
up,
|
|
bulletrange[mode],
|
|
bulletdamage[mode],
|
|
bulletlarge[mode],
|
|
bulletknockback[mode],
|
|
0,
|
|
GetMeansOfDeath(mode),
|
|
vSpread,
|
|
bulletcount[mode],
|
|
ownerPtr,
|
|
tracerFrequency,
|
|
&tracercount[mode],
|
|
bulletthroughwood[mode],
|
|
bulletthroughmetal[mode],
|
|
this,
|
|
tracerspeed[mode]
|
|
);
|
|
}
|
|
break;
|
|
case FT_FAKEBULLET:
|
|
{
|
|
Vector vSpread;
|
|
float fSpreadFactor;
|
|
|
|
if (owner) {
|
|
if (owner->client) {
|
|
Player *player = (Player *)owner.Pointer();
|
|
|
|
fSpreadFactor = player->velocity.length() / sv_runspeed->integer;
|
|
|
|
if (fSpreadFactor > 1.0f) {
|
|
fSpreadFactor = 1.0f;
|
|
}
|
|
|
|
vSpread = bulletspreadmax[mode] * fSpreadFactor;
|
|
fSpreadFactor = 1.0f - fSpreadFactor;
|
|
vSpread += bulletspread[mode] * fSpreadFactor;
|
|
vSpread *= m_fFireSpreadMult[mode] + 1.0f;
|
|
|
|
if (m_iZoom) {
|
|
if (player->IsSubclassOfPlayer() && player->IsZoomed()) {
|
|
vSpread *= 1.0f + fSpreadFactor * (m_fZoomSpreadMult - 1.0f);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
vSpread = (bulletspreadmax[mode] + bulletspread[mode]) * 0.5f;
|
|
}
|
|
|
|
FakeBulletAttack(
|
|
pos,
|
|
vBarrel,
|
|
forward,
|
|
right,
|
|
up,
|
|
bulletrange[mode],
|
|
bulletdamage[mode],
|
|
bulletlarge[mode],
|
|
vSpread,
|
|
bulletcount[mode],
|
|
owner,
|
|
tracerfrequency[mode],
|
|
&tracercount[mode],
|
|
tracerspeed[mode]
|
|
);
|
|
}
|
|
break;
|
|
case FT_SPECIAL_PROJECTILE:
|
|
SpecialFireProjectile(pos, forward, right, up, owner, projectileModel[mode], charge_fraction);
|
|
break;
|
|
case FT_CLICKITEM:
|
|
ClickItemAttack(pos, forward, bulletrange[mode], owner);
|
|
break;
|
|
case FT_HEAVY:
|
|
if (owner || !IsSubclassOfVehicleTurretGun()) {
|
|
HeavyAttack(pos, forward, projectileModel[mode], 0, owner, this);
|
|
} else {
|
|
VehicleTurretGun *turret = (VehicleTurretGun *)this;
|
|
|
|
if (turret->UseRemoteControl() && turret->GetRemoteOwner()) {
|
|
HeavyAttack(pos, forward, projectileModel[mode], 0, turret->GetRemoteOwner(), this);
|
|
} else {
|
|
HeavyAttack(pos, forward, projectileModel[mode], 0, this, this);
|
|
}
|
|
}
|
|
break;
|
|
case FT_MELEE:
|
|
{
|
|
Vector melee_pos, melee_end;
|
|
Vector dir;
|
|
float damage;
|
|
meansOfDeath_t meansofdeath;
|
|
float knockback;
|
|
|
|
if (owner) {
|
|
dir = owner->centroid - pos;
|
|
} else {
|
|
dir = centroid - pos;
|
|
}
|
|
|
|
dir.z = 0;
|
|
|
|
melee_pos = pos - forward * dir.length();
|
|
melee_end = melee_pos + forward * bulletrange[mode];
|
|
|
|
damage = bulletdamage[mode];
|
|
knockback = 0;
|
|
|
|
meansofdeath = GetMeansOfDeath(mode);
|
|
|
|
Container<Entity *> victimlist;
|
|
|
|
m_iNumShotsFired++;
|
|
if (MeleeAttack(
|
|
melee_pos, melee_end, damage, owner, meansofdeath, 8, -8, 8, knockback, true, &victimlist
|
|
)) {
|
|
m_iNumHits++;
|
|
m_iNumTorsoShots++;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
// none or unsupported
|
|
break;
|
|
}
|
|
|
|
if (!quiet[mode] && next_noise_time <= level.time) {
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
BroadcastAIEvent(AI_EVENT_WEAPON_FIRE, 1500);
|
|
} else {
|
|
//
|
|
// Added in OPM
|
|
// Weapon firing sounds can be heard up to 8000 units away
|
|
// (from ubersound.scr)
|
|
BroadcastAIEvent(AI_EVENT_WEAPON_FIRE, Q_clamp_float(world->GetRadius() * 0.5, 1500, 8000));
|
|
}
|
|
next_noise_time = level.time + 1;
|
|
}
|
|
|
|
if (owner && owner->client) {
|
|
Vector vAngles = owner->GetViewAngles();
|
|
|
|
if (viewkickmin[mode][0] != 0.0f || viewkickmax[mode][0] != 0.0f) {
|
|
vAngles[0] += random() * (viewkickmax[mode][0] - viewkickmin[mode][0]) + viewkickmin[mode][0];
|
|
}
|
|
|
|
if (viewkickmin[1][0] != 0.0f || viewkickmax[1][0] != 0.0f) {
|
|
vAngles[1] += random() * (viewkickmax[mode][1] - viewkickmin[mode][1]) + viewkickmin[mode][1];
|
|
}
|
|
|
|
owner->SetViewAngles(vAngles);
|
|
}
|
|
|
|
if (m_fFireSpreadMultAmount[mode]) {
|
|
m_fFireSpreadMult[mode] += m_fFireSpreadMultAmount[mode];
|
|
|
|
if (m_fFireSpreadMultCap[mode] > 0) {
|
|
if (m_fFireSpreadMult[mode] > m_fFireSpreadMultCap[mode]) {
|
|
m_fFireSpreadMult[mode] = m_fFireSpreadMultCap[mode];
|
|
} else if (m_fFireSpreadMult[mode] < 0) {
|
|
m_fFireSpreadMult[mode] = 0;
|
|
}
|
|
} else if (m_fFireSpreadMultCap[mode] < 0) {
|
|
if (m_fFireSpreadMult[mode] < m_fFireSpreadMultCap[mode]) {
|
|
m_fFireSpreadMult[mode] = m_fFireSpreadMultCap[mode];
|
|
} else if (m_fFireSpreadMult[mode] > 0) {
|
|
m_fFireSpreadMult[mode] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_fLastFireTime = level.time;
|
|
m_eLastFireMode = mode;
|
|
} else {
|
|
// Landmine weapon
|
|
if (ammo_clip_size[mode]) {
|
|
UseAmmo(-ammorequired[mode], mode);
|
|
} else if (owner && owner->isClient() && !UnlimitedAmmo(mode)) {
|
|
owner->UseAmmo(ammo_type[mode], -ammorequired[mode]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::ApplyFireKickback
|
|
//======================
|
|
void Weapon::ApplyFireKickback(const Vector& org, float kickback) {}
|
|
|
|
//======================
|
|
//Weapon::SetAimAnim
|
|
//======================
|
|
void Weapon::SetAimAnim(Event *ev)
|
|
{
|
|
str anim;
|
|
|
|
anim = ev->GetString(1);
|
|
aimanim = gi.Anim_NumForName(edict->tiki, anim.c_str());
|
|
aimframe = ev->GetInteger(2);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetOwner
|
|
//======================
|
|
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);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::AmmoAvailable
|
|
//======================
|
|
int Weapon::AmmoAvailable(firemode_t mode)
|
|
{
|
|
// Returns the amount of ammo the owner has that is available for use
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
|
|
mode = m_bShareClip ? FIRE_PRIMARY : mode;
|
|
|
|
// Make sure there is an owner before querying the amount of ammo
|
|
if (owner) {
|
|
return owner->AmmoCount(ammo_type[mode]);
|
|
} else {
|
|
if (ammo_clip_size[mode]) {
|
|
return ammo_clip_size[mode];
|
|
} else {
|
|
return ammorequired[mode];
|
|
}
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::UnlimitedAmmo
|
|
//======================
|
|
qboolean Weapon::UnlimitedAmmo(firemode_t mode)
|
|
{
|
|
if (!owner) {
|
|
return true;
|
|
}
|
|
|
|
if (!owner->isClient() || DM_FLAG(DF_INFINITE_AMMO)) {
|
|
return true;
|
|
} else if (!Q_stricmp(ammo_type[mode], "None")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::HasAmmo
|
|
//======================
|
|
qboolean Weapon::HasAmmo(firemode_t mode)
|
|
{
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
|
|
if (!((mode >= 0) && (mode < MAX_FIREMODES))) {
|
|
warning("Weapon::HasAmmo", "Invalid mode %d\n", mode);
|
|
return false;
|
|
}
|
|
|
|
if (m_bShareClip) {
|
|
// share the clip
|
|
mode = FIRE_PRIMARY;
|
|
}
|
|
|
|
if (UnlimitedAmmo(mode)) {
|
|
return true;
|
|
}
|
|
|
|
// If the weapon uses a clip, check for ammo in the right clip
|
|
if (ammo_clip_size[mode] && HasAmmoInClip(mode)) {
|
|
return true;
|
|
} else // Otherwise check if ammo is available in general
|
|
{
|
|
if (!ammorequired[mode]) {
|
|
return true;
|
|
}
|
|
|
|
return (AmmoAvailable(mode) >= ammorequired[mode]);
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::HasAmmoInClip
|
|
//======================
|
|
qboolean Weapon::HasAmmoInClip(firemode_t mode)
|
|
{
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
|
|
if (!((mode >= 0) && (mode < MAX_FIREMODES))) {
|
|
warning("Weapon::HasAmmoInClip", "Invalid mode %d\n", mode);
|
|
}
|
|
|
|
mode = m_bShareClip ? FIRE_PRIMARY : mode;
|
|
|
|
if (ammo_clip_size[mode]) {
|
|
if (ammo_in_clip[mode] >= ammorequired[mode]) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (!ammorequired[mode]) {
|
|
return true;
|
|
}
|
|
|
|
return (AmmoAvailable(mode) >= ammorequired[mode]);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::ReadyToFire
|
|
//======================
|
|
qboolean Weapon::ReadyToFire(firemode_t mode, qboolean playsound)
|
|
{
|
|
if (owner && owner->IsSubclassOfSentient()) {
|
|
// Clear the cook flag
|
|
owner->m_bOvercookDied = false;
|
|
}
|
|
|
|
if (!level.playerfrozen && m_iZoom && mode == FIRE_SECONDARY) {
|
|
return qtrue;
|
|
}
|
|
|
|
// Make sure the weapon is in the ready state and the weapon has ammo
|
|
if (m_eLastFireMode != mode || level.time > (m_fLastFireTime + FireDelay(mode))) {
|
|
if (HasAmmoInClip(mode)) {
|
|
if (m_fMaxFireMovement >= 1.f) {
|
|
return qtrue;
|
|
}
|
|
|
|
if (!owner) {
|
|
return qtrue;
|
|
}
|
|
|
|
if ((owner->velocity.lengthXY() / sv_runspeed->value) <= (m_fMovementSpeed * m_fMaxFireMovement)) {
|
|
return qtrue;
|
|
}
|
|
|
|
if (playsound) {
|
|
if (level.time > next_maxmovement_time) {
|
|
Sound(m_sMaxMovementSound);
|
|
}
|
|
next_maxmovement_time = level.time + level.frametime + 0.5;
|
|
}
|
|
} else if (playsound) {
|
|
if (level.time > next_noammo_time) {
|
|
Sound(m_NoAmmoSound);
|
|
}
|
|
next_noammo_time = level.time + level.frametime + FireDelay(mode);
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::PutAway
|
|
//======================
|
|
void Weapon::PutAway(void)
|
|
{
|
|
// set the putaway flag to true, so the state machine know to put this weapon away
|
|
putaway = true;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::DetachFromOwner
|
|
//======================
|
|
void Weapon::DetachFromOwner(void)
|
|
{
|
|
DetachGun();
|
|
weaponstate = WEAPON_HOLSTERED;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::AttachToOwner
|
|
//======================
|
|
void Weapon::AttachToOwner(weaponhand_t hand)
|
|
{
|
|
AttachGun(hand);
|
|
ForceIdle();
|
|
}
|
|
|
|
//======================
|
|
//Weapon::AttachToHolster
|
|
//======================
|
|
void Weapon::AttachToHolster(weaponhand_t hand)
|
|
{
|
|
AttachGun(hand, qtrue);
|
|
SetWeaponAnim("holster", EV_Weapon_Idle);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::Drop
|
|
//======================
|
|
qboolean Weapon::Drop(void)
|
|
{
|
|
Vector temp;
|
|
|
|
if (!owner) {
|
|
return false;
|
|
}
|
|
|
|
if (!IsDroppable()) {
|
|
return false;
|
|
}
|
|
|
|
if (attached) {
|
|
Vector vAng;
|
|
orientation_t oTag;
|
|
Entity *pParent;
|
|
|
|
if (edict->s.parent == ENTITYNUM_NONE || edict->s.tag_num < 0) {
|
|
pParent = owner;
|
|
vAng = pParent->angles;
|
|
} else {
|
|
vAng = vec_zero;
|
|
pParent = G_GetEntity(edict->s.parent);
|
|
|
|
AnglesToAxis(pParent->angles, pParent->orientation);
|
|
pParent->GetTag(edict->s.tag_num, &oTag);
|
|
MatrixToEulerAngles(oTag.axis, vAng);
|
|
}
|
|
|
|
setAngles(vAng);
|
|
DetachGun();
|
|
} else {
|
|
temp[2] = 40;
|
|
|
|
setOrigin(owner->origin + temp);
|
|
setAngles(owner->angles);
|
|
}
|
|
|
|
setSize(Vector(-12, -12, -2), Vector(12, 12, 12));
|
|
|
|
// stop animating
|
|
StopWeaponAnim();
|
|
|
|
// drop the weapon
|
|
PlaceItem();
|
|
|
|
temp = centroid - owner->centroid;
|
|
temp[2] = 0;
|
|
|
|
VectorNormalize(temp);
|
|
|
|
temp *= 75.0f;
|
|
|
|
temp[0] += G_CRandom(25);
|
|
temp[1] += G_CRandom(25);
|
|
temp[2] = G_CRandom(50) + 150;
|
|
velocity = owner->velocity * 0.5 + temp;
|
|
|
|
avelocity = Vector(0, G_CRandom(120), 0);
|
|
spawnflags |= DROPPED_PLAYER_ITEM;
|
|
|
|
if (owner && owner->isClient()) {
|
|
if (owner->deadflag && g_dropclips->integer > 0) {
|
|
int ammo;
|
|
|
|
if (ammo_clip_size[FIRE_PRIMARY]) {
|
|
// Fixed in OPM
|
|
// Always drop at least 1 because of grenades
|
|
startammo[FIRE_PRIMARY] = g_dropclips->integer * Q_max(ammo_in_clip[FIRE_PRIMARY], 1);
|
|
} else {
|
|
startammo[FIRE_PRIMARY] = g_dropclips->integer;
|
|
}
|
|
|
|
// Fixed in OPM
|
|
// Use the max between the available ammo and the ammo in clip
|
|
ammo = Q_max(AmmoAvailable(FIRE_PRIMARY), ammo_in_clip[FIRE_PRIMARY]);
|
|
if (startammo[FIRE_PRIMARY] > ammo) {
|
|
startammo[FIRE_PRIMARY] = ammo;
|
|
}
|
|
|
|
if (ammo_clip_size[FIRE_SECONDARY]) {
|
|
startammo[FIRE_SECONDARY] = g_dropclips->integer * Q_max(ammo_in_clip[FIRE_SECONDARY], 1);
|
|
} else {
|
|
startammo[FIRE_SECONDARY] = g_dropclips->integer;
|
|
}
|
|
|
|
ammo = Q_max(AmmoAvailable(FIRE_SECONDARY), ammo_in_clip[FIRE_SECONDARY]);
|
|
if (startammo[FIRE_SECONDARY] > ammo) {
|
|
startammo[FIRE_SECONDARY] = ammo;
|
|
}
|
|
|
|
ammo_in_clip[FIRE_PRIMARY] = 0;
|
|
ammo_in_clip[FIRE_SECONDARY] = 0;
|
|
} else {
|
|
startammo[FIRE_PRIMARY] = AmmoAvailable(FIRE_PRIMARY);
|
|
owner->takeAmmoType(ammo_type[FIRE_PRIMARY]);
|
|
}
|
|
} else {
|
|
if (ammo_clip_size[FIRE_PRIMARY] && ammo_in_clip[FIRE_PRIMARY]) {
|
|
startammo[FIRE_PRIMARY] = ammo_in_clip[FIRE_PRIMARY];
|
|
} else {
|
|
startammo[FIRE_PRIMARY] >>= 2;
|
|
}
|
|
|
|
if (ammo_clip_size[FIRE_SECONDARY] && ammo_in_clip[FIRE_SECONDARY]) {
|
|
startammo[FIRE_SECONDARY] = ammo_in_clip[FIRE_SECONDARY];
|
|
} else {
|
|
startammo[FIRE_SECONDARY] >>= 2;
|
|
}
|
|
|
|
if (startammo[FIRE_PRIMARY] == 0) {
|
|
startammo[FIRE_PRIMARY] = 1;
|
|
}
|
|
|
|
if (startammo[FIRE_SECONDARY] == 0) {
|
|
startammo[FIRE_SECONDARY] = 1;
|
|
}
|
|
}
|
|
|
|
// Wait some time before the last owner can pickup this weapon
|
|
last_owner = owner;
|
|
last_owner_trigger_time = level.time + 2.0f;
|
|
|
|
// Cancel reloading events
|
|
CancelEventsOfType(EV_Weapon_DoneReloading);
|
|
|
|
// Remove this from the owner's item list
|
|
RemoveFromOwner();
|
|
|
|
if (g_droppeditemlife->value > 0) {
|
|
PostEvent(EV_Remove, g_droppeditemlife->value);
|
|
}
|
|
|
|
PostEvent(EV_Weapon_FallingAngleAdjust, level.frametime);
|
|
return true;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::Charge
|
|
//======================
|
|
void Weapon::Charge(firemode_t mode)
|
|
{
|
|
if (mode == FIRE_PRIMARY) {
|
|
if (m_fCookTime > 0) {
|
|
m_eCookModeIndex = mode;
|
|
//
|
|
// Post cook event
|
|
//
|
|
PostEvent(EV_OverCooked, m_fCookTime);
|
|
PostEvent(EV_OverCooked_Warning, m_fCookTime - 1);
|
|
}
|
|
SetWeaponAnim("charge");
|
|
} else if (mode == FIRE_SECONDARY) {
|
|
if (m_fCookTime > 0) {
|
|
m_eCookModeIndex = mode;
|
|
//
|
|
// Post cook event
|
|
//
|
|
PostEvent(EV_OverCooked, m_fCookTime);
|
|
PostEvent(EV_OverCooked_Warning, m_fCookTime - 1);
|
|
}
|
|
SetWeaponAnim("secondarycharge");
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::OnOverCookedWarning
|
|
//======================
|
|
void Weapon::OnOverCookedWarning(Event *ev) {}
|
|
|
|
//======================
|
|
//Weapon::OnOverCooked
|
|
//======================
|
|
void Weapon::OnOverCooked(Event *ev)
|
|
{
|
|
if (!owner) {
|
|
return;
|
|
}
|
|
|
|
if (projectileModel[m_eCookModeIndex].length()) {
|
|
Entity *spawnedEnt;
|
|
|
|
SpawnArgs sp;
|
|
sp.setArg("model", projectileModel[m_eCookModeIndex]);
|
|
|
|
spawnedEnt = static_cast<Entity *>(sp.Spawn());
|
|
if (spawnedEnt && spawnedEnt->IsSubclassOfProjectile()) {
|
|
Event *newev;
|
|
Projectile *proj = static_cast<Projectile *>(spawnedEnt);
|
|
trace_t trace;
|
|
|
|
proj->origin = owner->origin;
|
|
proj->angles = owner->angles;
|
|
proj->owner = owner->entnum;
|
|
proj->edict->r.ownerNum = owner->entnum;
|
|
|
|
trace = G_Trace(
|
|
proj->origin,
|
|
vec_zero,
|
|
vec_zero,
|
|
owner->origin,
|
|
static_cast<Entity *>(owner.Pointer()),
|
|
owner->edict->clipmask,
|
|
qfalse,
|
|
"Weapon::OnOverCooked"
|
|
);
|
|
|
|
if (trace.ent && trace.ent->entity->entnum == world->entnum) {
|
|
proj->origin = owner->origin;
|
|
}
|
|
|
|
newev = new Event(EV_Projectile_Explode);
|
|
newev->AddEntity(NULL);
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
newev->AddFloat(1000.0);
|
|
}
|
|
|
|
if (owner->IsSubclassOfSentient()) {
|
|
owner->m_bOvercookDied = true;
|
|
}
|
|
|
|
proj->ProcessEvent(newev);
|
|
}
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::ReleaseFire
|
|
//======================
|
|
void Weapon::ReleaseFire(firemode_t mode, float charge_time)
|
|
{
|
|
// Make sure to stop the wepaon from cooking
|
|
CancelEventsOfType(EV_OverCooked);
|
|
CancelEventsOfType(EV_OverCooked_Warning);
|
|
|
|
// Calculate and store off the charge fraction to use when the weapon actually shoots
|
|
|
|
// Clamp to max_charge_time
|
|
if (charge_time - min_charge_time[mode] >= 0.0f) {
|
|
if (charge_time <= max_charge_time[mode]) {
|
|
charge_fraction = charge_time / max_charge_time[mode];
|
|
} else {
|
|
charge_fraction = 1.0f;
|
|
}
|
|
} else {
|
|
charge_fraction = 0.0f;
|
|
}
|
|
|
|
// Call regular fire function
|
|
Fire(mode);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetFireAnim
|
|
//======================
|
|
const char *Weapon::GetFireAnim() const
|
|
{
|
|
if (m_iNumFireAnims > 1) {
|
|
static char tagname[256];
|
|
|
|
Com_sprintf(tagname, sizeof(tagname), "fire_%d", m_iCurrentFireAnim + 1);
|
|
return tagname;
|
|
} else {
|
|
return "fire";
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::Fire
|
|
//======================
|
|
void Weapon::Fire(firemode_t mode)
|
|
{
|
|
Event *done_event = NULL;
|
|
Vector pos;
|
|
|
|
// Sanity check the mode
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
|
|
// If we are in loopfire mode, then we don't pass a DoneFiring event
|
|
if (!loopfire[mode]) {
|
|
// The DoneFiring event requires to know the firing mode so save that off in the event
|
|
done_event = new Event(EV_Weapon_DoneFiring);
|
|
done_event->AddInteger(mode);
|
|
}
|
|
|
|
if (!MuzzleClear()) {
|
|
SetWeaponAnim("clear", done_event);
|
|
weaponstate = WEAPON_READY;
|
|
return;
|
|
}
|
|
|
|
// Use up the appropriate amount of ammo, it's already been checked that we have enough
|
|
UseAmmo(ammorequired[mode], mode);
|
|
|
|
// Set the state of the weapon to FIRING
|
|
weaponstate = WEAPON_FIRING;
|
|
|
|
// Cancel any old done firing events
|
|
CancelEventsOfType(EV_Weapon_DoneFiring);
|
|
|
|
// Play the correct animation
|
|
if (mode == FIRE_PRIMARY) {
|
|
if (m_iNumFireAnims > 1) {
|
|
m_iCurrentFireAnim++;
|
|
|
|
if (m_iCurrentFireAnim >= m_iNumFireAnims) {
|
|
m_iCurrentFireAnim = 0;
|
|
}
|
|
}
|
|
if (ammo_clip_size[FIRE_PRIMARY] && !ammo_in_clip[FIRE_PRIMARY] && HasAnim("fire_empty")) {
|
|
SetWeaponAnim("fire_empty", done_event);
|
|
} else {
|
|
SetWeaponAnim(GetFireAnim(), done_event);
|
|
}
|
|
} else if (mode == FIRE_SECONDARY) {
|
|
if (((!m_bShareClip && ammo_clip_size[FIRE_SECONDARY] && !ammo_in_clip[FIRE_SECONDARY])
|
|
|| (m_bShareClip && ammo_clip_size[FIRE_PRIMARY] && !ammo_in_clip[FIRE_PRIMARY]))
|
|
&& HasAnim("secondaryfire_empty")) {
|
|
SetWeaponAnim("secondaryfire_empty", done_event);
|
|
} else {
|
|
SetWeaponAnim("secondaryfire", done_event);
|
|
}
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::DetachGun
|
|
//======================
|
|
void Weapon::DetachGun(void)
|
|
{
|
|
if (attached) {
|
|
if (m_iZoom && owner && owner->IsSubclassOfPlayer()) {
|
|
Player *p = (Player *)owner.Pointer();
|
|
p->ZoomOff();
|
|
}
|
|
StopSound(CHAN_WEAPONIDLE);
|
|
attached = false;
|
|
detach();
|
|
hideModel();
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::AttachGun
|
|
//======================
|
|
void Weapon::AttachGun(weaponhand_t hand, qboolean holstering)
|
|
{
|
|
int tag_num;
|
|
Vector vOffset;
|
|
|
|
if (!owner) {
|
|
current_attachToTag = "";
|
|
return;
|
|
}
|
|
|
|
if (attached) {
|
|
DetachGun();
|
|
}
|
|
|
|
if (holstering) {
|
|
// Save off these values if we are holstering the weapon. We will restore them when
|
|
// the users raises the weapons again.
|
|
lastAngles = this->angles;
|
|
lastScale = this->edict->s.scale;
|
|
lastValid = qtrue;
|
|
|
|
current_attachToTag = holster_attachToTag;
|
|
|
|
vOffset = holsterOffset;
|
|
setAngles(holsterAngles);
|
|
setScale(holsterScale);
|
|
} else {
|
|
if (lastValid) {
|
|
// Restore the last
|
|
setScale(lastScale);
|
|
setAngles(lastAngles);
|
|
lastValid = qfalse;
|
|
}
|
|
|
|
vOffset.setXYZ(0, 0, 0);
|
|
|
|
switch (hand) {
|
|
case WEAPON_MAIN:
|
|
current_attachToTag = attachToTag_main;
|
|
break;
|
|
case WEAPON_OFFHAND:
|
|
current_attachToTag = attachToTag_offhand;
|
|
break;
|
|
default:
|
|
warning("Weapon::AttachGun", "Invalid hand for attachment of weapon specified");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!current_attachToTag.length()) {
|
|
return;
|
|
}
|
|
|
|
if (owner->edict->tiki) {
|
|
tag_num = gi.Tag_NumForName(owner->edict->tiki, this->current_attachToTag.c_str());
|
|
|
|
NoLerpThisFrame();
|
|
if (tag_num >= 0) {
|
|
attached = true;
|
|
attach(owner->entnum, tag_num, qtrue, vOffset);
|
|
showModel();
|
|
setOrigin();
|
|
} else {
|
|
warning(
|
|
"Weapon::AttachGun",
|
|
"Attachment of weapon to tag \"%s\": Tag Not Found\n",
|
|
this->current_attachToTag.c_str()
|
|
);
|
|
}
|
|
}
|
|
|
|
if (m_bAutoZoom && owner->IsSubclassOfPlayer()) {
|
|
Player *p = (Player *)owner.Pointer();
|
|
p->ToggleZoom(m_iZoom);
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::AttachToHand
|
|
//======================
|
|
void Weapon::AttachToHand(Event *ev)
|
|
{
|
|
str tag;
|
|
|
|
if (!owner || !attached) {
|
|
return;
|
|
}
|
|
|
|
weaponhand_t hand = WeaponHandNameToNum(ev->GetString(1));
|
|
|
|
if (hand == WEAPON_ERROR) {
|
|
return;
|
|
}
|
|
|
|
if (hand == WEAPON_OFFHAND) {
|
|
tag = attachToTag_offhand;
|
|
} else {
|
|
tag = attachToTag_main;
|
|
}
|
|
|
|
current_attachToTag = tag;
|
|
|
|
int tagnum = gi.Tag_NumForName(owner->edict->tiki, tag);
|
|
if (tagnum < 0) {
|
|
warning(
|
|
"Weapon::AttachToHand",
|
|
"Attachment of weapon '%s' to tag \"%s\": Tag Not Found\n",
|
|
getName().c_str(),
|
|
tag.c_str()
|
|
);
|
|
} else {
|
|
attach(owner->entnum, tagnum);
|
|
setOrigin();
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetCantPartialReload
|
|
//======================
|
|
void Weapon::SetCantPartialReload(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
m_bCanPartialReload = qfalse;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMCantPartialReload
|
|
//======================
|
|
void Weapon::SetDMCantPartialReload(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
m_bCanPartialReload = qfalse;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::AddAdditionalStartAmmo
|
|
//======================
|
|
void Weapon::AddAdditionalStartAmmo(Event *ev)
|
|
{
|
|
m_additionalStartAmmoTypes.AddObject(ev->GetString(1));
|
|
m_additionalStartAmmoAmounts.AddObject(ev->GetInteger(2));
|
|
}
|
|
|
|
//======================
|
|
//Weapon::AddStartItem
|
|
//======================
|
|
void Weapon::AddStartItem(Event *ev)
|
|
{
|
|
m_startItems.AddObject(ev->GetString(1));
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GiveStartingAmmoToOwner
|
|
//======================
|
|
void Weapon::GiveStartingAmmoToOwner(Event *ev)
|
|
{
|
|
str ammotype;
|
|
int mode;
|
|
int i;
|
|
|
|
if (!owner) {
|
|
return;
|
|
}
|
|
|
|
// Give the player the starting ammo
|
|
for (mode = FIRE_PRIMARY; mode < MAX_FIREMODES; mode++) {
|
|
ammotype = GetAmmoType((firemode_t)(mode));
|
|
if (ammotype.length()) {
|
|
int start_ammo = this->GetStartAmmo((firemode_t)mode);
|
|
|
|
if (ammo_clip_size[mode]) {
|
|
int ammo = ammo_clip_size[mode] - ammo_in_clip[mode];
|
|
|
|
if (ammo > 0) {
|
|
if (ammo < start_ammo) {
|
|
start_ammo -= ammo;
|
|
ammo_in_clip[mode] = ammo + ammo_in_clip[mode];
|
|
} else {
|
|
ammo_in_clip[mode] = start_ammo + ammo_in_clip[mode];
|
|
start_ammo = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (start_ammo) {
|
|
owner->GiveAmmo(ammotype, start_ammo);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_additionalStartAmmoTypes.NumObjects()) {
|
|
for (i = 1; i <= m_additionalStartAmmoTypes.NumObjects(); i++) {
|
|
const str& type = m_additionalStartAmmoTypes.ObjectAt(i);
|
|
int startAmmoAmount = m_additionalStartAmmoAmounts.ObjectAt(i);
|
|
|
|
if (type.length() && startAmmoAmount) {
|
|
owner->GiveAmmo(type, startAmmoAmount);
|
|
}
|
|
}
|
|
|
|
m_additionalStartAmmoTypes.ClearObjectList();
|
|
m_additionalStartAmmoAmounts.ClearObjectList();
|
|
}
|
|
|
|
if (m_startItems.NumObjects()) {
|
|
for (i = 1; i <= m_startItems.NumObjects(); i++) {
|
|
const str& itemName = m_startItems.ObjectAt(i);
|
|
|
|
if (itemName.length()) {
|
|
owner->giveItem(itemName);
|
|
}
|
|
}
|
|
|
|
m_startItems.ClearObjectList();
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::IsSecondaryWeapon
|
|
//======================
|
|
qboolean Weapon::IsSecondaryWeapon(void)
|
|
{
|
|
return (weapon_class & WEAPON_CLASS_SECONDARY);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::PickupWeapon
|
|
//======================
|
|
void Weapon::PickupWeapon(Event *ev)
|
|
{
|
|
Sentient *sen;
|
|
Entity *other;
|
|
qboolean hasweapon;
|
|
qboolean hasclass;
|
|
int iGiveAmmo;
|
|
str realname;
|
|
|
|
other = ev->GetEntity(1);
|
|
assert(other);
|
|
|
|
if (!other->IsSubclassOfSentient()) {
|
|
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(item_name);
|
|
hasclass = sen->HasWeaponClass(weapon_class);
|
|
|
|
if ((g_gametype->integer || g_realismmode->integer) && !hasclass && !IsSecondaryWeapon()
|
|
&& sen->HasPrimaryWeapon()) {
|
|
// Make sure the sentient doesn't have a primary weapon on DM modes
|
|
return;
|
|
}
|
|
|
|
if ((g_gametype->integer || g_realismmode->integer) && other->IsSubclassOfPlayer()
|
|
&& (weapon_class & WEAPON_CLASS_GRENADE) && !hasweapon && hasclass) {
|
|
hasclass = false;
|
|
}
|
|
|
|
if (!hasweapon && !hasclass) {
|
|
if (other->IsSubclassOfPlayer()) {
|
|
gi.SendServerCommand(
|
|
other->edict - g_entities,
|
|
"print \"" HUD_MESSAGE_YELLOW "%s\n\"",
|
|
gi.LV_ConvertString(va("Picked Up %s", item_name.c_str()))
|
|
);
|
|
}
|
|
|
|
if (!(spawnflags & DROPPED_PLAYER_ITEM) && !(spawnflags & DROPPED_ITEM)) {
|
|
ItemPickup(other);
|
|
return;
|
|
}
|
|
|
|
if (!Pickupable(other)) {
|
|
return;
|
|
}
|
|
|
|
setMoveType(MOVETYPE_NONE);
|
|
setSolidType(SOLID_NOT);
|
|
|
|
hideModel();
|
|
|
|
velocity = vec_zero;
|
|
avelocity = vec_zero;
|
|
|
|
CancelEventsOfType(EV_Remove);
|
|
CancelEventsOfType(EV_Weapon_FallingAngleAdjust);
|
|
|
|
DetachFromOwner();
|
|
current_attachToTag = "";
|
|
lastValid = qfalse;
|
|
edict->s.tag_num = -1;
|
|
edict->s.attach_use_angles = qfalse;
|
|
VectorClear(edict->s.attach_offset);
|
|
|
|
setOrigin(vec_zero);
|
|
setAngles(vec_zero);
|
|
SetOwner(sen);
|
|
|
|
sen->AddItem(this);
|
|
sen->ReceivedItem(this);
|
|
|
|
iGiveAmmo = startammo[FIRE_PRIMARY];
|
|
|
|
Sound(sPickupSound);
|
|
} else {
|
|
bool bSameAmmo[MAX_FIREMODES] = {false};
|
|
|
|
if (sen->AmmoCount(ammo_type[FIRE_PRIMARY]) == sen->MaxAmmoCount(ammo_type[FIRE_PRIMARY])) {
|
|
bSameAmmo[FIRE_PRIMARY] = true;
|
|
if (ammo_type[0] == ammo_type[1]) {
|
|
bSameAmmo[FIRE_SECONDARY] = true;
|
|
}
|
|
}
|
|
|
|
if (!bSameAmmo[FIRE_SECONDARY]) {
|
|
if (sen->AmmoCount(ammo_type[FIRE_SECONDARY]) == sen->MaxAmmoCount(ammo_type[FIRE_SECONDARY])) {
|
|
bSameAmmo[FIRE_SECONDARY] = true;
|
|
}
|
|
}
|
|
|
|
if (bSameAmmo[FIRE_PRIMARY] && bSameAmmo[FIRE_SECONDARY]) {
|
|
return;
|
|
}
|
|
|
|
if (bSameAmmo[FIRE_PRIMARY]) {
|
|
startammo[FIRE_PRIMARY] = 0;
|
|
}
|
|
|
|
if (bSameAmmo[FIRE_SECONDARY]) {
|
|
startammo[FIRE_SECONDARY] = 0;
|
|
}
|
|
|
|
iGiveAmmo = ammo_in_clip[FIRE_PRIMARY] + startammo[FIRE_PRIMARY];
|
|
|
|
if (!iGiveAmmo && !startammo[FIRE_SECONDARY]) {
|
|
// Fixed in OPM
|
|
// Don't let the sentient pick the ammo up if it already has the weapon in inventory
|
|
// and the player would get no ammo.
|
|
// This can only be picked up as a new weapon.
|
|
return;
|
|
}
|
|
|
|
setSolidType(SOLID_NOT);
|
|
hideModel();
|
|
|
|
CancelEventsOfType(EV_Item_DropToFloor);
|
|
CancelEventsOfType(EV_Item_Respawn);
|
|
CancelEventsOfType(EV_FadeOut);
|
|
CancelEventsOfType(EV_Remove);
|
|
CancelEventsOfType(EV_Weapon_FallingAngleAdjust);
|
|
|
|
if (Respawnable()) {
|
|
PostEvent(EV_Item_Respawn, 0);
|
|
} else {
|
|
PostEvent(EV_Remove, 0);
|
|
}
|
|
|
|
Sound(m_sAmmoPickupSound);
|
|
}
|
|
|
|
if (startammo[FIRE_PRIMARY] && ammo_type[FIRE_PRIMARY].length() && other->isClient()) {
|
|
str sMessage;
|
|
const str& sAmmoType = ammo_type[FIRE_PRIMARY];
|
|
|
|
sen->GiveAmmo(sAmmoType, iGiveAmmo);
|
|
|
|
if (!sAmmoType.icmp("grenade") || !sAmmoType.icmp("agrenade")) {
|
|
if (iGiveAmmo == 1) {
|
|
sMessage = gi.LV_ConvertString("Got 1 Grenade");
|
|
} else {
|
|
sMessage = gi.LV_ConvertString(va("Got %i Grenades", iGiveAmmo));
|
|
}
|
|
} else {
|
|
sMessage = gi.LV_ConvertString(va("Got %i %s Rounds", iGiveAmmo, GetCapitalized(sAmmoType).c_str()));
|
|
}
|
|
|
|
gi.SendServerCommand(other->edict - g_entities, "print \"" HUD_MESSAGE_YELLOW "%s\n\"", sMessage.c_str());
|
|
}
|
|
|
|
if (ammo_type[FIRE_SECONDARY] != ammo_type[FIRE_PRIMARY] && str::icmp(ammo_type[FIRE_SECONDARY], "none")) {
|
|
if (startammo[FIRE_SECONDARY] && ammo_type[FIRE_SECONDARY].length() && other->isClient()) {
|
|
str sMessage;
|
|
const str& sAmmoType = ammo_type[FIRE_SECONDARY];
|
|
|
|
iGiveAmmo = startammo[FIRE_SECONDARY];
|
|
|
|
sen->GiveAmmo(sAmmoType, iGiveAmmo);
|
|
|
|
if (!sAmmoType.icmp("grenade") || !sAmmoType.icmp("agrenade")) {
|
|
if (iGiveAmmo == 1) {
|
|
sMessage = gi.LV_ConvertString("Got 1 Grenade");
|
|
} else {
|
|
sMessage = gi.LV_ConvertString(va("Got %i Grenades", iGiveAmmo));
|
|
}
|
|
} else if (!sAmmoType.icmp("riflegrenade")) {
|
|
if (iGiveAmmo == 1) {
|
|
sMessage = gi.LV_ConvertString("Got 1 Rifle Grenade");
|
|
} else {
|
|
sMessage = gi.LV_ConvertString(va("Got %i Rifle Grenades", iGiveAmmo));
|
|
}
|
|
} else {
|
|
sMessage = gi.LV_ConvertString(
|
|
va("Got %i %s Rounds", startammo[FIRE_PRIMARY], GetCapitalized(sAmmoType).c_str())
|
|
);
|
|
}
|
|
|
|
gi.SendServerCommand(other->edict - g_entities, "print \"" HUD_MESSAGE_YELLOW "%s\n\"", sMessage.c_str());
|
|
}
|
|
}
|
|
|
|
Unregister(STRING_PICKUP);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::ForceIdle
|
|
//======================
|
|
void Weapon::ForceIdle(void)
|
|
{
|
|
SetWeaponIdleAnim();
|
|
|
|
// Force the weapon to the idle animation
|
|
weaponstate = WEAPON_READY;
|
|
|
|
// Fixed in OPM
|
|
// Stop the weapon from charging/cooking
|
|
CancelEventsOfType(EV_OverCooked);
|
|
CancelEventsOfType(EV_OverCooked_Warning);
|
|
charge_fraction = 0;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetWeaponIdleAnim
|
|
//======================
|
|
void Weapon::SetWeaponIdleAnim(void)
|
|
{
|
|
if (m_bShareClip) {
|
|
if (ammo_clip_size[FIRE_PRIMARY] && !ammo_in_clip[FIRE_PRIMARY]) {
|
|
if (SetWeaponAnim("idle_empty")) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
SetWeaponAnim("idle");
|
|
} else {
|
|
if (ammo_clip_size[FIRE_PRIMARY] && !ammo_in_clip[FIRE_PRIMARY]) {
|
|
if (SetWeaponAnim("idle_empty")) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
SetWeaponAnim("idle");
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetWeaponIdleAnimEvent
|
|
//======================
|
|
void Weapon::SetWeaponIdleAnimEvent(Event *ev)
|
|
{
|
|
SetWeaponIdleAnim();
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetWeaponAnimEvent
|
|
//======================
|
|
void Weapon::SetWeaponAnimEvent(Event *ev)
|
|
{
|
|
//SetWeaponAnim(ev->GetString(1));
|
|
SetWeaponAnim(ev->GetString(1), EV_Weapon_DoneAnimating);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetWeaponAnim
|
|
//======================
|
|
qboolean Weapon::SetWeaponAnim(const char *anim, Event *ev)
|
|
{
|
|
int animnum = gi.Anim_NumForName(edict->tiki, anim);
|
|
|
|
if (animnum == -1) {
|
|
if (ev) {
|
|
delete ev;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
StopAnimating(m_iAnimSlot);
|
|
RestartAnimSlot(m_iAnimSlot);
|
|
|
|
int idleanim = gi.Anim_NumForName(edict->tiki, "idle");
|
|
|
|
edict->s.frameInfo[m_iAnimSlot].index = idleanim;
|
|
m_iAnimSlot = (m_iAnimSlot + 1) % MAX_WEAPON_ANIM_SLOTS;
|
|
edict->s.frameInfo[m_iAnimSlot].index = idleanim;
|
|
|
|
if (ev) {
|
|
NewAnim(animnum, ev, m_iAnimSlot);
|
|
} else {
|
|
NewAnim(animnum, m_iAnimSlot);
|
|
}
|
|
|
|
SetOnceType(m_iAnimSlot);
|
|
RestartAnimSlot(m_iAnimSlot);
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetWeaponAnim
|
|
//======================
|
|
qboolean Weapon::SetWeaponAnim(const char *anim, Event& ev)
|
|
{
|
|
Event *event = new Event(ev);
|
|
|
|
return SetWeaponAnim(anim, event);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::StopWeaponAnim
|
|
//======================
|
|
void Weapon::StopWeaponAnim(void)
|
|
{
|
|
int animnum;
|
|
|
|
RestartAnimSlot(m_iAnimSlot);
|
|
StopAnimating(m_iAnimSlot);
|
|
|
|
animnum = gi.Anim_NumForName(edict->tiki, "idle");
|
|
StartAnimSlot(m_iAnimSlot, animnum, 1.f);
|
|
|
|
m_iAnimSlot = (m_iAnimSlot + 1) % MAX_WEAPON_ANIM_SLOTS;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::DoneRaising
|
|
//======================
|
|
void Weapon::DoneRaising(Event *ev)
|
|
{
|
|
weaponstate = WEAPON_READY;
|
|
ForceIdle();
|
|
|
|
if (!owner) {
|
|
PostEvent(EV_Remove, 0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::ClientFireDone
|
|
//======================
|
|
void Weapon::ClientFireDone(void)
|
|
{
|
|
// This is called when the client's firing animation is done
|
|
}
|
|
|
|
//======================
|
|
//Weapon::DoneFiring
|
|
//======================
|
|
void Weapon::DoneFiring(Event *ev)
|
|
{
|
|
// This is called when the weapon's firing animation is done
|
|
ForceIdle();
|
|
|
|
// Check to see if the auto_putaway flag is set, and the weapon is out of ammo. If so, then putaway the
|
|
// weapon.
|
|
if (!HasAmmo(FIRE_PRIMARY) && auto_putaway) {
|
|
PutAway();
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::FillAmmoClip
|
|
//======================
|
|
void Weapon::FillAmmoClip(Event *ev)
|
|
{
|
|
int amount;
|
|
int amount_used;
|
|
|
|
if (!ammo_clip_size[0] || !owner) {
|
|
return;
|
|
}
|
|
|
|
if (UnlimitedAmmo(FIRE_PRIMARY)) {
|
|
// reload directly
|
|
ammo_in_clip[FIRE_PRIMARY] = ammo_clip_size[0];
|
|
} else {
|
|
// Calc the amount the clip should get
|
|
amount = ammo_clip_size[FIRE_PRIMARY] - ammo_in_clip[FIRE_PRIMARY];
|
|
|
|
// use up the ammo from the player
|
|
amount_used = owner->UseAmmo(ammo_type[FIRE_PRIMARY], amount);
|
|
|
|
// Stick it in the clip
|
|
ammo_in_clip[FIRE_PRIMARY] = amount_used + ammo_in_clip[FIRE_PRIMARY];
|
|
}
|
|
|
|
owner->AmmoAmountInClipChanged(ammo_type[FIRE_PRIMARY], ammo_in_clip[FIRE_PRIMARY]);
|
|
|
|
SetShouldReload(qfalse);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::EmptyAmmoClip
|
|
//======================
|
|
void Weapon::EmptyAmmoClip(Event *ev)
|
|
{
|
|
if (!ammo_clip_size[FIRE_PRIMARY]) {
|
|
return;
|
|
}
|
|
|
|
if (!owner) {
|
|
return;
|
|
}
|
|
|
|
owner->GiveAmmo(ammo_type[FIRE_PRIMARY], ammo_in_clip[FIRE_PRIMARY]);
|
|
ammo_in_clip[FIRE_PRIMARY] = 0;
|
|
|
|
SetShouldReload(qtrue);
|
|
|
|
owner->AmmoAmountInClipChanged(ammo_type[FIRE_PRIMARY], ammo_in_clip[FIRE_PRIMARY]);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::AddToAmmoClip
|
|
//======================
|
|
void Weapon::AddToAmmoClip(Event *ev)
|
|
{
|
|
int amount;
|
|
int amount_used;
|
|
|
|
if (!ammo_clip_size[FIRE_PRIMARY] || !owner) {
|
|
return;
|
|
}
|
|
|
|
// Calc the amount the clip should get
|
|
amount = ev->GetInteger(1);
|
|
|
|
if (amount > ammo_clip_size[FIRE_PRIMARY] - ammo_in_clip[FIRE_PRIMARY]) {
|
|
amount = ammo_clip_size[FIRE_PRIMARY] - ammo_in_clip[FIRE_PRIMARY];
|
|
}
|
|
|
|
if (UnlimitedAmmo(FIRE_PRIMARY)) {
|
|
// Stick it in the clip
|
|
ammo_in_clip[FIRE_PRIMARY] = amount + ammo_in_clip[FIRE_PRIMARY];
|
|
} else {
|
|
// use up the ammo from the player
|
|
amount_used = owner->UseAmmo(ammo_type[FIRE_PRIMARY], amount);
|
|
|
|
// Stick it in the clip
|
|
ammo_in_clip[FIRE_PRIMARY] = amount_used + ammo_in_clip[FIRE_PRIMARY];
|
|
}
|
|
|
|
owner->AmmoAmountInClipChanged(ammo_type[FIRE_PRIMARY], ammo_in_clip[FIRE_PRIMARY]);
|
|
|
|
SetShouldReload(qfalse);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetIdleState
|
|
//======================
|
|
void Weapon::SetIdleState(int state)
|
|
{
|
|
if (!owner) {
|
|
return;
|
|
}
|
|
|
|
switch (state) {
|
|
case 0:
|
|
default:
|
|
SetWeaponAnim("idle0");
|
|
break;
|
|
case 1:
|
|
SetWeaponAnim("idle1");
|
|
break;
|
|
case 2:
|
|
SetWeaponAnim("idle2");
|
|
break;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::StartReloading
|
|
//======================
|
|
void Weapon::StartReloading(void)
|
|
{
|
|
if (!ammo_clip_size[0] || !owner) {
|
|
return;
|
|
}
|
|
|
|
if (SetWeaponAnim("reload", EV_Weapon_DoneReloading)) {
|
|
weaponstate = WEAPON_RELOADING;
|
|
} else {
|
|
ProcessEvent(EV_Weapon_FillClip);
|
|
ProcessEvent(EV_Weapon_DoneReloading);
|
|
}
|
|
|
|
m_fFireSpreadMult[FIRE_PRIMARY] = 0;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::DoneReloading
|
|
//======================
|
|
void Weapon::DoneReloading(Event *ev)
|
|
{
|
|
SetShouldReload(qfalse);
|
|
weaponstate = WEAPON_READY;
|
|
|
|
// Added in OPM
|
|
// Set to idle when done reloading
|
|
SetWeaponIdleAnim();
|
|
}
|
|
|
|
//======================
|
|
//Weapon::CheckReload
|
|
//======================
|
|
qboolean Weapon::CheckReload(firemode_t mode)
|
|
{
|
|
// Check to see if the weapon needs to be reloaded
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
|
|
mode = m_bShareClip ? FIRE_PRIMARY : mode;
|
|
|
|
if (putaway) {
|
|
return false;
|
|
}
|
|
|
|
if (ammo_in_clip[mode] < ammo_clip_size[mode] && AmmoAvailable(mode)
|
|
&& (m_bCanPartialReload || ammo_in_clip[mode] <= 0)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetShouldReload
|
|
//======================
|
|
void Weapon::SetShouldReload(qboolean should_reload)
|
|
{
|
|
m_bShouldReload = should_reload;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::ShouldReload
|
|
//======================
|
|
qboolean Weapon::ShouldReload(void)
|
|
{
|
|
if (m_bShouldReload) {
|
|
return qtrue;
|
|
} else {
|
|
if (ammo_clip_size[FIRE_PRIMARY] && !ammo_in_clip[FIRE_PRIMARY] && AmmoAvailable(FIRE_PRIMARY)) {
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::Idle
|
|
//======================
|
|
void Weapon::Idle(Event *ev)
|
|
{
|
|
ForceIdle();
|
|
}
|
|
|
|
//======================
|
|
//Weapon::IdleInit
|
|
//======================
|
|
void Weapon::IdleInit(Event *ev)
|
|
{
|
|
for (int i = 7; i >= 0; i--) {
|
|
SetWeaponAnim("idle");
|
|
}
|
|
|
|
weaponstate = WEAPON_READY;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetMaxRange
|
|
//======================
|
|
float Weapon::GetMaxRange(void)
|
|
{
|
|
return maxrange;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetMinRange
|
|
//======================
|
|
float Weapon::GetMinRange(void)
|
|
{
|
|
return minrange;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetMaxRangeEvent
|
|
//======================
|
|
void Weapon::SetMaxRangeEvent(Event *ev)
|
|
{
|
|
maxrange = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetMinRangeEvent
|
|
//======================
|
|
void Weapon::SetMinRangeEvent(Event *ev)
|
|
{
|
|
minrange = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::NotDroppableEvent
|
|
//======================
|
|
void Weapon::NotDroppableEvent(Event *ev)
|
|
{
|
|
notdroppable = true;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetMaxRange
|
|
//======================
|
|
void Weapon::SetMaxRange(float val)
|
|
{
|
|
maxrange = val;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetMinRange
|
|
//======================
|
|
void Weapon::SetMinRange(float val)
|
|
{
|
|
minrange = val;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::WeaponSound
|
|
//======================
|
|
void Weapon::WeaponSound(Event *ev)
|
|
{
|
|
// 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->BroadcastAIEvent(AI_EVENT_WEAPON_FIRE);
|
|
} else {
|
|
BroadcastAIEvent(AI_EVENT_WEAPON_FIRE);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (owner) {
|
|
owner->ProcessEvent(*ev);
|
|
} else {
|
|
Item::BroadcastAIEvent(AI_EVENT_WEAPON_FIRE);
|
|
}
|
|
|
|
// give us some breathing room
|
|
nextweaponsoundtime = level.time + 0.4;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::Removable
|
|
//======================
|
|
qboolean Weapon::Removable(void)
|
|
{
|
|
if (((int)(dmflags->integer) & DF_WEAPONS_STAY) && !(spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::Pickupable
|
|
//======================
|
|
qboolean Weapon::Pickupable(Entity *other)
|
|
{
|
|
Sentient *sen;
|
|
|
|
if (!other->IsSubclassOfSentient()) {
|
|
return false;
|
|
} else if (!other->isClient()) {
|
|
return false;
|
|
}
|
|
|
|
sen = (Sentient *)other;
|
|
|
|
// If we have the weapon and weapons stay, then don't pick it up
|
|
if (((int)(dmflags->integer) & DF_WEAPONS_STAY) && !(spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) {
|
|
Weapon *weapon;
|
|
|
|
weapon = (Weapon *)sen->FindItem(getName());
|
|
|
|
if (weapon) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::AutoChange
|
|
//======================
|
|
qboolean Weapon::AutoChange(void)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::ClipAmmo
|
|
//======================
|
|
int Weapon::ClipAmmo(firemode_t mode)
|
|
{
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
|
|
mode = m_bShareClip ? FIRE_PRIMARY : mode;
|
|
|
|
if (ammo_clip_size[mode]) {
|
|
return ammo_in_clip[mode];
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetFireDelay
|
|
//======================
|
|
void Weapon::SetFireDelay(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
fire_delay[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMFireDelay
|
|
//======================
|
|
void Weapon::SetDMFireDelay(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
fire_delay[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::FireDelay
|
|
//======================
|
|
float Weapon::FireDelay(firemode_t mode)
|
|
{
|
|
return fire_delay[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::IsDroppable
|
|
//======================
|
|
qboolean Weapon::IsDroppable(void)
|
|
{
|
|
if (notdroppable) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetFireType
|
|
//======================
|
|
void Weapon::SetFireType(Event *ev)
|
|
{
|
|
str ftype;
|
|
|
|
ftype = ev->GetString(1);
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
|
|
if (!ftype.icmp("projectile")) {
|
|
firetype[firemodeindex] = FT_PROJECTILE;
|
|
} else if (!ftype.icmp("bullet")) {
|
|
firetype[firemodeindex] = FT_BULLET;
|
|
} else if (!ftype.icmp("fakebullet")) {
|
|
firetype[firemodeindex] = FT_FAKEBULLET;
|
|
} else if (!ftype.icmp("melee")) {
|
|
firetype[firemodeindex] = FT_MELEE;
|
|
} else if (!ftype.icmp("special_projectile")) {
|
|
firetype[firemodeindex] = FT_SPECIAL_PROJECTILE;
|
|
} else if (!ftype.icmp("clickitem")) {
|
|
firetype[firemodeindex] = FT_CLICKITEM;
|
|
} else if (!ftype.icmp("heavy")) {
|
|
firetype[firemodeindex] = FT_HEAVY;
|
|
} else if (!ftype.icmp("landmine")) {
|
|
firetype[firemodeindex] = FT_LANDMINE;
|
|
} else if (!ftype.icmp("defuse")) {
|
|
firetype[firemodeindex] = FT_DEFUSE;
|
|
} else if (!ftype.icmp("none")) {
|
|
firetype[firemodeindex] = FT_NONE;
|
|
} else {
|
|
warning("Weapon::SetFireType", "unknown firetype: %s\n", ftype.c_str());
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetFireType
|
|
//======================
|
|
firetype_t Weapon::GetFireType(firemode_t mode)
|
|
{
|
|
return firetype[mode];
|
|
}
|
|
|
|
firemode_t Weapon::GetFireMode()
|
|
{
|
|
return firemodeindex;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetProjectile
|
|
//======================
|
|
void Weapon::SetProjectile(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
projectileModel[firemodeindex] = ev->GetString(1);
|
|
CacheResource(projectileModel[firemodeindex].c_str());
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMProjectile
|
|
//======================
|
|
void Weapon::SetDMProjectile(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
projectileModel[firemodeindex] = ev->GetString(1);
|
|
CacheResource(projectileModel[firemodeindex].c_str());
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetBulletDamage
|
|
//======================
|
|
void Weapon::SetBulletDamage(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
bulletdamage[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetBulletLarge
|
|
//======================
|
|
void Weapon::SetBulletLarge(Event *ev)
|
|
{
|
|
bulletlarge[firemodeindex] = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetBulletLarge
|
|
//======================
|
|
void Weapon::SetTracerSpeed(Event *ev)
|
|
{
|
|
tracerspeed[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMBulletDamage
|
|
//======================
|
|
void Weapon::SetDMBulletDamage(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
bulletdamage[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetBulletKnockback
|
|
//======================
|
|
void Weapon::SetBulletKnockback(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
bulletknockback[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMBulletKnockback
|
|
//======================
|
|
void Weapon::SetDMBulletKnockback(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
bulletknockback[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetBulletThroughWood
|
|
//======================
|
|
void Weapon::SetBulletThroughWood(Event *ev)
|
|
{
|
|
bulletthroughwood[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetBulletThroughMetal
|
|
//======================
|
|
void Weapon::SetBulletThroughMetal(Event *ev)
|
|
{
|
|
bulletthroughmetal[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetBulletRange
|
|
//======================
|
|
void Weapon::SetBulletRange(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
bulletrange[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMBulletRange
|
|
//======================
|
|
void Weapon::SetDMBulletRange(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
bulletrange[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetRange
|
|
//======================
|
|
void Weapon::SetRange(Event *ev)
|
|
{
|
|
SetBulletRange(ev);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetBulletCount
|
|
//======================
|
|
void Weapon::SetBulletCount(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
bulletcount[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMBulletCount
|
|
//======================
|
|
void Weapon::SetDMBulletCount(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
bulletcount[firemodeindex] = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetBulletSpread
|
|
//======================
|
|
void Weapon::SetBulletSpread(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
bulletspread[firemodeindex].x = ev->GetFloat(1);
|
|
bulletspread[firemodeindex].y = ev->GetFloat(2);
|
|
|
|
if (ev->NumArgs() > 2) {
|
|
bulletspreadmax[firemodeindex].x = ev->GetFloat(3);
|
|
bulletspreadmax[firemodeindex].y = ev->GetFloat(4);
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMBulletSpread
|
|
//======================
|
|
void Weapon::SetDMBulletSpread(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
bulletspread[firemodeindex].x = ev->GetFloat(1);
|
|
bulletspread[firemodeindex].y = ev->GetFloat(2);
|
|
|
|
if (ev->NumArgs() > 2) {
|
|
bulletspreadmax[firemodeindex].x = ev->GetFloat(3);
|
|
bulletspreadmax[firemodeindex].y = ev->GetFloat(4);
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetZoomSpreadMult
|
|
//======================
|
|
void Weapon::SetZoomSpreadMult(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
m_fZoomSpreadMult = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMZoomSpreadMult
|
|
//======================
|
|
void Weapon::SetDMZoomSpreadMult(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
m_fZoomSpreadMult = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetZoomSpreadMult
|
|
//======================
|
|
void Weapon::SetFireSpreadMult(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
m_fFireSpreadMultAmount[firemodeindex] = ev->GetFloat(1);
|
|
m_fFireSpreadMultFalloff[firemodeindex] = ev->GetFloat(2);
|
|
m_fFireSpreadMultCap[firemodeindex] = ev->GetFloat(3);
|
|
m_fFireSpreadMultTimeCap[firemodeindex] = ev->GetFloat(4);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMZoomSpreadMult
|
|
//======================
|
|
void Weapon::SetDMFireSpreadMult(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
m_fFireSpreadMultAmount[firemodeindex] = ev->GetFloat(1);
|
|
m_fFireSpreadMultFalloff[firemodeindex] = ev->GetFloat(2);
|
|
m_fFireSpreadMultCap[firemodeindex] = ev->GetFloat(3);
|
|
m_fFireSpreadMultTimeCap[firemodeindex] = ev->GetFloat(4);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetTracerFrequency
|
|
//======================
|
|
void Weapon::SetTracerFrequency(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
tracerfrequency[firemodeindex] = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::Secondary
|
|
//======================
|
|
void Weapon::Secondary(Event *ev)
|
|
{
|
|
int i;
|
|
|
|
Event *altev = new Event(ev->GetToken(1));
|
|
|
|
firemodeindex = FIRE_SECONDARY;
|
|
|
|
for (i = 2; i <= ev->NumArgs(); i++) {
|
|
altev->AddToken(ev->GetToken(i));
|
|
}
|
|
|
|
ProcessEvent(altev);
|
|
firemodeindex = FIRE_PRIMARY;
|
|
}
|
|
|
|
//====================
|
|
//Weapon::AutoAim
|
|
//====================
|
|
void Weapon::AutoAim(Event *ev)
|
|
{
|
|
autoaim = true;
|
|
}
|
|
|
|
//====================
|
|
//Weapon::Crosshair
|
|
//====================
|
|
void Weapon::Crosshair(Event *ev)
|
|
{
|
|
crosshair = ev->GetBoolean(1);
|
|
}
|
|
|
|
//====================
|
|
//Weapon::DMCrosshair
|
|
//====================
|
|
void Weapon::DMCrosshair(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
crosshair = ev->GetBoolean(1);
|
|
}
|
|
|
|
//====================
|
|
//Weapon::MainAttachToTag
|
|
//====================
|
|
void Weapon::MainAttachToTag(Event *ev)
|
|
{
|
|
attachToTag_main = ev->GetString(1);
|
|
}
|
|
|
|
//====================
|
|
//Weapon::OffHandAttachToTag
|
|
//====================
|
|
void Weapon::OffHandAttachToTag(Event *ev)
|
|
{
|
|
attachToTag_offhand = ev->GetString(1);
|
|
}
|
|
|
|
//====================
|
|
//Weapon::HolsterAttachToTag
|
|
//====================
|
|
void Weapon::HolsterAttachToTag(Event *ev)
|
|
{
|
|
holster_attachToTag = ev->GetString(1);
|
|
}
|
|
|
|
//====================
|
|
//Weapon::SetHolsterOffset
|
|
//====================
|
|
void Weapon::SetHolsterOffset(Event *ev)
|
|
{
|
|
holsterOffset = ev->GetVector(1);
|
|
}
|
|
|
|
//====================
|
|
//Weapon::SetHolsterAngles
|
|
//====================
|
|
void Weapon::SetHolsterAngles(Event *ev)
|
|
{
|
|
holsterAngles = ev->GetVector(1);
|
|
}
|
|
|
|
//====================
|
|
//Weapon::SetHolsterScale
|
|
//====================
|
|
void Weapon::SetHolsterScale(Event *ev)
|
|
{
|
|
holsterScale = ev->GetFloat(1);
|
|
}
|
|
|
|
//====================
|
|
//Weapon::SetQuiet
|
|
//====================
|
|
void Weapon::SetQuiet(Event *ev)
|
|
{
|
|
quiet[firemodeindex] = true;
|
|
}
|
|
|
|
//====================
|
|
//Weapon::SetLoopFire
|
|
//====================
|
|
void Weapon::SetLoopFire(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
loopfire[firemodeindex] = true;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetMeansOfDeath
|
|
//======================
|
|
void Weapon::SetMeansOfDeath(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
meansofdeath[firemodeindex] = (meansOfDeath_t)MOD_string_to_int(ev->GetString(1));
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetMeansOfDeath
|
|
//======================
|
|
meansOfDeath_t Weapon::GetMeansOfDeath(firemode_t mode)
|
|
{
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
|
|
if ((mode >= 0) && (mode < MAX_FIREMODES)) {
|
|
return meansofdeath[mode];
|
|
} else {
|
|
warning("Weapon::GetMeansOfDeath", "Invalid mode %d\n", mode);
|
|
return MOD_NONE;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetAimTarget
|
|
//======================
|
|
void Weapon::SetAimTarget(Entity *ent)
|
|
{
|
|
aim_target = ent;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::WorldHitSpawn
|
|
//======================
|
|
void Weapon::WorldHitSpawn(firemode_t mode, Vector origin, Vector angles, float life) {}
|
|
|
|
//======================
|
|
//Weapon::SetWorldHitSpawn
|
|
//======================
|
|
void Weapon::SetWorldHitSpawn(Event *ev)
|
|
{
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
worldhitspawn[firemodeindex] = ev->GetString(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::MakeNoise
|
|
//======================
|
|
void Weapon::MakeNoise(Event *ev)
|
|
{
|
|
float radius = 500;
|
|
qboolean force = false;
|
|
|
|
if (ev->NumArgs() > 0) {
|
|
radius = ev->GetFloat(1);
|
|
}
|
|
|
|
if (ev->NumArgs() > 1) {
|
|
force = ev->GetBoolean(2);
|
|
}
|
|
|
|
if (attached && (next_noise_time <= level.time || force)) {
|
|
BroadcastAIEvent(AI_EVENT_MISC, radius);
|
|
next_noise_time = level.time + 1;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::ShowInfo
|
|
//======================
|
|
void Weapon::ShowInfo(float fDot, float fDist) {}
|
|
|
|
//======================
|
|
//Weapon::FallingAngleAdjust
|
|
//======================
|
|
void Weapon::FallingAngleAdjust(Event *ev)
|
|
{
|
|
Vector vTmp;
|
|
Vector vDir;
|
|
trace_t trace;
|
|
|
|
if (owner) {
|
|
return;
|
|
}
|
|
|
|
if (groundentity && (groundentity->entity == world || groundentity->r.bmodel)) {
|
|
Vector vEnd = origin - Vector(0, 0, 128);
|
|
|
|
trace = G_Trace(
|
|
origin, vec_zero, vec_zero, vEnd, last_owner, edict->clipmask, qfalse, "Weapon::FallingAngleAdjust"
|
|
);
|
|
|
|
if (trace.fraction < 1.0f) {
|
|
Vector vAng = Vector(0, angles[1], 0);
|
|
Vector vCross;
|
|
|
|
/*vAng.AngleVectorsLeft( &vDir );
|
|
|
|
CrossProduct( vAng, vDir, vCross );
|
|
CrossProduct( vDir, vCross, vAng );
|
|
vAng = vAng.toAngles();*/
|
|
|
|
if (angles[2] <= 0.0f) {
|
|
vAng[2] = anglemod(vAng[2] - 90.0f);
|
|
} else {
|
|
vAng[2] = anglemod(vAng[2] + 90.0f);
|
|
}
|
|
|
|
setAngles(vAng);
|
|
}
|
|
|
|
if (weapon_class >= 0) {
|
|
Sound(G_WeaponClassNumToName(weapon_class) + "_drop");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
angles[0] = anglemod(angles[0]);
|
|
if (angles[0] > 180) {
|
|
angles[0] -= 180;
|
|
}
|
|
|
|
if (fabs(angles[0]) >= 90) {
|
|
if (angles[0] > 0) {
|
|
angles[0] += level.frametime * 160;
|
|
if (angles[0] > 180) {
|
|
angles[0] = 180;
|
|
}
|
|
} else {
|
|
angles[0] -= level.frametime * 160;
|
|
if (angles[0] < -180) {
|
|
angles[0] = 180;
|
|
}
|
|
}
|
|
} else {
|
|
if (angles[0] > 0.0f) {
|
|
angles[0] -= level.frametime * 160;
|
|
if (angles[0] < 0) {
|
|
angles[0] = 0;
|
|
}
|
|
} else {
|
|
angles[0] += level.frametime * 160;
|
|
if (angles[0] > 0) {
|
|
angles[0] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
angles[2] = anglemod(angles[2]);
|
|
if (angles[2] > 180) {
|
|
angles[2] -= 180;
|
|
}
|
|
|
|
if (angles[2] < 0) {
|
|
if (angles[2] > -90) {
|
|
angles[2] -= level.frametime * 160;
|
|
if (angles[2] < -90) {
|
|
angles[2] = -90;
|
|
}
|
|
} else {
|
|
angles[2] += level.frametime * 160;
|
|
if (angles[2] > -90) {
|
|
angles[2] = -90;
|
|
}
|
|
}
|
|
} else {
|
|
if (angles[2] > 90.0f) {
|
|
angles[2] -= level.frametime * 160;
|
|
if (angles[2] < 90) {
|
|
angles[2] = 90;
|
|
}
|
|
} else {
|
|
angles[2] += level.frametime * 160;
|
|
if (angles[2] > 90) {
|
|
angles[2] = 90;
|
|
}
|
|
}
|
|
}
|
|
|
|
angles[0] = anglemod(angles[0]);
|
|
angles[2] = anglemod(angles[2]);
|
|
setAngles(angles);
|
|
PostEvent(EV_Weapon_FallingAngleAdjust, level.frametime);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetUseCrosshair
|
|
//======================
|
|
qboolean Weapon::GetUseCrosshair() const
|
|
{
|
|
if (g_protocol < protocol_e::PROTOCOL_MOHTA_MIN) {
|
|
// Always show the crosshair in 1.11 and below
|
|
return qtrue;
|
|
}
|
|
|
|
if (m_fMaxFireMovement >= 1.f) {
|
|
return crosshair;
|
|
}
|
|
|
|
if (!owner || (owner->velocity.lengthXY() / sv_runspeed->value) <= (m_fMaxFireMovement * m_fMovementSpeed)) {
|
|
return crosshair;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetAIRange
|
|
//======================
|
|
void Weapon::SetAIRange(Event *ev)
|
|
{
|
|
str s = ev->GetString(1);
|
|
|
|
if (!s.icmp("short")) {
|
|
mAIRange = RANGE_SHORT;
|
|
} else if (!s.icmp("medium")) {
|
|
mAIRange = RANGE_MEDIUM;
|
|
} else if (!s.icmp("long")) {
|
|
mAIRange = RANGE_LONG;
|
|
} else if (!s.icmp("sniper")) {
|
|
mAIRange = RANGE_SNIPER;
|
|
} else {
|
|
warning("Weapon::SetAIRange", "unknown range: %s. Should be short, medium, long, or sniper\n", s.c_str());
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetViewKick
|
|
//======================
|
|
void Weapon::SetViewKick(Event *ev)
|
|
{
|
|
float pitchmin, pitchmax;
|
|
float yawmin, yawmax;
|
|
|
|
assert((firemodeindex >= 0) && (firemodeindex < MAX_FIREMODES));
|
|
|
|
pitchmin = ev->GetFloat(1);
|
|
pitchmax = ev->GetFloat(2);
|
|
|
|
if (pitchmin <= pitchmax) {
|
|
viewkickmin[firemodeindex][0] = pitchmin;
|
|
viewkickmax[firemodeindex][0] = pitchmax;
|
|
} else {
|
|
viewkickmin[firemodeindex][0] = pitchmax;
|
|
viewkickmax[firemodeindex][0] = pitchmin;
|
|
}
|
|
|
|
if (ev->NumArgs() > 2) {
|
|
yawmin = ev->GetFloat(3);
|
|
yawmax = ev->GetFloat(4);
|
|
|
|
if (pitchmin <= pitchmax) {
|
|
viewkickmin[firemodeindex][1] = yawmin;
|
|
viewkickmax[firemodeindex][1] = yawmax;
|
|
} else {
|
|
viewkickmin[firemodeindex][1] = yawmax;
|
|
viewkickmax[firemodeindex][1] = yawmin;
|
|
}
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetMovementSpeed
|
|
//======================
|
|
void Weapon::SetMovementSpeed(Event *ev)
|
|
{
|
|
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
m_fMovementSpeed = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetDMMovementSpeed
|
|
//======================
|
|
void Weapon::SetDMMovementSpeed(Event *ev)
|
|
{
|
|
if (g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
return;
|
|
}
|
|
|
|
m_fMovementSpeed = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetMaxFireMovement
|
|
//======================
|
|
void Weapon::SetMaxFireMovement(Event *ev)
|
|
{
|
|
m_fMaxFireMovement = ev->GetFloat(1);
|
|
if (m_fMaxFireMovement > 1.0) {
|
|
// Cap at 1.0
|
|
m_fMaxFireMovement = 1.0;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetZoomMovement
|
|
//======================
|
|
void Weapon::SetZoomMovement(Event *ev)
|
|
{
|
|
m_fZoomMovement = ev->GetFloat(1);
|
|
if (m_fZoomMovement > 1.0) {
|
|
// Cap at 1.0
|
|
m_fZoomMovement = 1.0;
|
|
}
|
|
}
|
|
|
|
//======================
|
|
//Weapon::EventAmmoPickupSound
|
|
//======================
|
|
void Weapon::EventAmmoPickupSound(Event *ev)
|
|
{
|
|
m_sAmmoPickupSound = ev->GetString(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::EventNoAmmoSound
|
|
//======================
|
|
void Weapon::EventNoAmmoSound(Event *ev)
|
|
{
|
|
m_NoAmmoSound = ev->GetString(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::EventMaxMovementSound
|
|
//======================
|
|
void Weapon::EventMaxMovementSound(Event *ev)
|
|
{
|
|
m_sMaxMovementSound = ev->GetString(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetNumFireAnims
|
|
//======================
|
|
void Weapon::SetNumFireAnims(Event *ev)
|
|
{
|
|
m_iNumFireAnims = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetWeaponSubtype
|
|
//======================
|
|
void Weapon::SetWeaponSubtype(Event *ev)
|
|
{
|
|
m_iWeaponSubtype = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetCookTime
|
|
//======================
|
|
void Weapon::SetCookTime(Event *ev)
|
|
{
|
|
m_fCookTime = ev->GetFloat(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetCurrentFireAnim
|
|
//======================
|
|
void Weapon::SetCurrentFireAnim(Event *ev)
|
|
{
|
|
m_iCurrentFireAnim = ev->GetInteger(1);
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetSecondaryAmmoInHud
|
|
//======================
|
|
void Weapon::SetSecondaryAmmoInHud(Event *ev)
|
|
{
|
|
m_bSecondaryAmmoInHud = qtrue;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetRank
|
|
//======================
|
|
void Weapon::SetRank(int order, int rank)
|
|
{
|
|
this->order = order;
|
|
this->rank = rank;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetRank
|
|
//======================
|
|
int Weapon::GetRank(void)
|
|
{
|
|
return rank;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetOrder
|
|
//======================
|
|
int Weapon::GetOrder(void)
|
|
{
|
|
return order;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetWeaponClass
|
|
//======================
|
|
int Weapon::GetWeaponClass(void) const
|
|
{
|
|
return weapon_class;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetWeaponGroup
|
|
//======================
|
|
const_str Weapon::GetWeaponGroup(void) const
|
|
{
|
|
return m_csWeaponGroup;
|
|
};
|
|
|
|
//======================
|
|
//Weapon::SetItemSlot
|
|
//======================
|
|
void Weapon::SetItemSlot(int slot)
|
|
{
|
|
weapon_class &= ~WEAPON_CLASS_ITEM_SLOT_BITS;
|
|
weapon_class |= slot & WEAPON_CLASS_ITEM_SLOT_BITS;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetPutaway
|
|
//======================
|
|
qboolean Weapon::GetPutaway(void)
|
|
{
|
|
return putaway;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetPutAway
|
|
//======================
|
|
void Weapon::SetPutAway(qboolean p)
|
|
{
|
|
putaway = p;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetState
|
|
//======================
|
|
weaponstate_t Weapon::GetState(void)
|
|
{
|
|
return weaponstate;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SpecialFireProjectile
|
|
//======================
|
|
void Weapon::SpecialFireProjectile(
|
|
Vector pos, Vector forward, Vector right, Vector up, Entity *owner, str projectileModel, float charge_fraction
|
|
)
|
|
{
|
|
// Does nothing
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetCurrentAttachToTag
|
|
//======================
|
|
str Weapon::GetCurrentAttachToTag(void)
|
|
{
|
|
return current_attachToTag;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::SetCurrentAttachToTag
|
|
//======================
|
|
void Weapon::SetCurrentAttachToTag(str s)
|
|
{
|
|
current_attachToTag = s;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetHolsterTag
|
|
//======================
|
|
str Weapon::GetHolsterTag(void)
|
|
{
|
|
return holster_attachToTag;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetUseNoAmmo
|
|
//======================
|
|
qboolean Weapon::GetUseNoAmmo(void)
|
|
{
|
|
return use_no_ammo;
|
|
}
|
|
|
|
//====================
|
|
//Weapon::GetZoom
|
|
//====================
|
|
int Weapon::GetZoom(void)
|
|
{
|
|
return m_iZoom;
|
|
}
|
|
|
|
//====================
|
|
//Weapon::GetAutoZoom
|
|
//====================
|
|
qboolean Weapon::GetAutoZoom(void)
|
|
{
|
|
return m_bAutoZoom;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetMovementSpeed
|
|
//======================
|
|
float Weapon::GetMovementSpeed() const
|
|
{
|
|
return m_fMovementSpeed;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::IsCarryableTurret
|
|
//======================
|
|
bool Weapon::IsCarryableTurret() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetStartAmmo
|
|
//======================
|
|
int Weapon::GetStartAmmo(firemode_t mode)
|
|
{
|
|
assert((mode >= 0) && (mode < MAX_FIREMODES));
|
|
return startammo[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::IsSemiAuto
|
|
//======================
|
|
qboolean Weapon::IsSemiAuto(void)
|
|
{
|
|
return m_bSemiAuto;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::ForceState
|
|
//======================
|
|
void Weapon::ForceState(weaponstate_t state)
|
|
{
|
|
weaponstate = state;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::MuzzleClear
|
|
//======================
|
|
qboolean Weapon::MuzzleClear(void)
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetBulletRange
|
|
//======================
|
|
float Weapon::GetBulletRange(firemode_t mode)
|
|
{
|
|
return bulletrange[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetSpreadFactor
|
|
//======================
|
|
float Weapon::GetSpreadFactor(firemode_t mode)
|
|
{
|
|
float fSpreadFactor;
|
|
|
|
if (owner && owner->client) {
|
|
Player *player = (Player *)owner.Pointer();
|
|
|
|
fSpreadFactor = player->velocity.length() / sv_runspeed->integer;
|
|
|
|
if (fSpreadFactor > 1.0f) {
|
|
fSpreadFactor = 1.0f;
|
|
}
|
|
} else {
|
|
fSpreadFactor = 0.5f;
|
|
}
|
|
|
|
return fSpreadFactor;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetChargeFraction
|
|
//======================
|
|
float Weapon::GetChargeFraction(void) const
|
|
{
|
|
return charge_fraction;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetCurrentFireSpreadMult
|
|
//======================
|
|
float Weapon::GetCurrentFireSpreadMult(firemode_t mode) const
|
|
{
|
|
const float fTime = level.time - m_fFireSpreadMultTime[mode];
|
|
|
|
if (m_fFireSpreadMultAmount[mode] != 0.0f) {
|
|
if (fTime > m_fFireSpreadMultTimeCap[mode]) {
|
|
return 0;
|
|
}
|
|
|
|
const float fDecay = fTime * m_fFireSpreadMultFalloff[mode];
|
|
if (m_fFireSpreadMult[mode] <= 0.0f) {
|
|
if (m_fFireSpreadMult[mode] > 0) {
|
|
return 0;
|
|
}
|
|
|
|
return m_fFireSpreadMult[mode] - fDecay;
|
|
} else {
|
|
if (m_fFireSpreadMult[mode] < 0.0f) {
|
|
return 0;
|
|
}
|
|
|
|
return m_fFireSpreadMult[mode] - fDecay;
|
|
}
|
|
}
|
|
|
|
if (fTime >= m_fFireSpreadMultTimeCap[mode]) {
|
|
return 0;
|
|
}
|
|
|
|
return m_fFireSpreadMult[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetCurrentFireSpreadMultTime
|
|
//======================
|
|
float Weapon::GetCurrentFireSpreadMultTime(firemode_t mode) const
|
|
{
|
|
return m_fFireSpreadMultTime[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetFireSpreadMultAmount
|
|
//======================
|
|
float Weapon::GetFireSpreadMultAmount(firemode_t mode) const
|
|
{
|
|
return m_fFireSpreadMultAmount[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetFireSpreadMultFalloff
|
|
//======================
|
|
float Weapon::GetFireSpreadMultFalloff(firemode_t mode) const
|
|
{
|
|
return m_fFireSpreadMultFalloff[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetFireSpreadMultCap
|
|
//======================
|
|
float Weapon::GetFireSpreadMultCap(firemode_t mode) const
|
|
{
|
|
return m_fFireSpreadMultCap[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetFireSpreadMultTimecap
|
|
//======================
|
|
float Weapon::GetFireSpreadMultTimeCap(firemode_t mode) const
|
|
{
|
|
return m_fFireSpreadMultTimeCap[mode];
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetScriptOwner
|
|
//======================
|
|
Listener *Weapon::GetScriptOwner(void)
|
|
{
|
|
return owner;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::DoneAnimating
|
|
//======================
|
|
void Weapon::DoneAnimating(Event *ev)
|
|
{
|
|
// Added in OPM
|
|
// Set to idle when done reloading
|
|
SetWeaponIdleAnim();
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetMaxFireMovementMult
|
|
//======================
|
|
void Weapon::EventGetPutaway(Event *ev)
|
|
{
|
|
// Added in OPM
|
|
// Set to idle when done reloading
|
|
ev->AddInteger(GetPutaway());
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetMaxFireMovementMult
|
|
//======================
|
|
float Weapon::GetMaxFireMovementMult() const
|
|
{
|
|
return m_fMaxFireMovement * m_fMovementSpeed;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetMaxFireMovementMult
|
|
//======================
|
|
float Weapon::GetZoomMovement() const
|
|
{
|
|
return m_fZoomMovement * m_fMovementSpeed;
|
|
}
|
|
|
|
//======================
|
|
//Weapon::GetMaxFireMovement
|
|
//======================
|
|
float Weapon::GetMaxFireMovement() const
|
|
{
|
|
return m_fMaxFireMovement;
|
|
}
|