Files
bearstonem--openmohaa-vr/code/fgame/weapon.cpp
T
bearstonemandClaude Opus 5 711cad20e6 feat(vr): reach into the world - use, holsters, ladders and thrown grenades
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>
2026-08-01 15:49:25 -07:00

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;
}