sin-2015/weapon.cpp
1999-04-22 00:00:00 +00:00

2033 lines
No EOL
39 KiB
C++

// Copyright (C) 1997 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// Source file for Weapon class. The weapon class is the base class for
// all weapons in Sin. Any entity created from a class derived from the weapon
// class will be usable by any Sentient (players and monsters) as a weapon.
//
#include "g_local.h"
#include "entity.h"
#include "item.h"
#include "weapon.h"
#include "scriptmaster.h"
#include "sentient.h"
#include "misc.h"
#include "specialfx.h"
#include "chaingun.h"
#include "assaultrifle.h"
//###
#include "player.h" // added for guided missile
#include "rocketlauncher.h"
#include "guidedmissile.h"
#include "informergun.h"
#include "magnum.h"
#include "dualmagnum.h"
//###
#ifdef SIN_ARCADE
static ScriptVariablePtr sv_infinitebullets;
static ScriptVariablePtr sv_infiniterockets;
static ScriptVariablePtr sv_infiniteplasma;
static ScriptVariablePtr sv_infinitespears;
static ScriptVariablePtr sv_infinitesniper;
#endif
CLASS_DECLARATION( Item, Weapon, NULL );
Event EV_Weapon_Shoot( "shoot" );
Event EV_Weapon_FinishAttack( "attack_finished" );
Event EV_Weapon_DoneLowering( "putaway" );
Event EV_Weapon_DoneRaising( "ready" );
Event EV_Weapon_DoneFiring( "donefiring" );
Event EV_Weapon_Idle( "idle" );
Event EV_Weapon_MuzzleFlash( "muzzleflash" );
Event EV_Weapon_SecondaryUse( "secondaryuse" );
Event EV_Weapon_DoneReloading( "donereloading" );
Event EV_Weapon_SetAmmoClipSize( "ammoclipsize" );
Event EV_Weapon_ProcessModelCommands( "process_mdl_cmds" );
Event EV_Weapon_SetMaxRange( "maxrange" );
Event EV_Weapon_SetMinRange( "minrange" );
Event EV_Weapon_SetProjectileSpeed( "projectilespeed" );
Event EV_Weapon_SetKick( "kick" );
Event EV_Weapon_PrimaryMode( "primarymode" );
Event EV_Weapon_SecondaryMode( "secondarymode" );
Event EV_Weapon_ActionIncrement( "actionincrement" );
Event EV_Weapon_PutAwayAndRaise( "putawaythenraise" );
Event EV_Weapon_Raise( "raise" );
Event EV_Weapon_NotDroppable( "notdroppable" );
Event EV_Weapon_SetAimAnim( "setaimanim" );
//### added ability for weapons to add view jitter to their owners
Event EV_Weapon_SetAngleJitter("anglejitter");
Event EV_Weapon_SetOffsetJitter("offsetjitter");
//###
ResponseDef Weapon::Responses[] =
{
{ &EV_Item_Pickup, ( Response )Weapon::PickupWeapon },
{ &EV_Weapon_FinishAttack, ( Response )Weapon::FinishAttack },
{ &EV_Weapon_DoneLowering, ( Response )Weapon::DoneLowering },
{ &EV_Weapon_DoneRaising, ( Response )Weapon::DoneRaising },
{ &EV_Weapon_DoneFiring, ( Response )Weapon::DoneFiring },
{ &EV_Weapon_Idle, ( Response )Weapon::Idle },
{ &EV_Weapon_MuzzleFlash, ( Response )Weapon::EventMuzzleFlash },
{ &EV_WeaponSound, ( Response )Weapon::WeaponSound },
{ &EV_Weapon_DoneReloading, ( Response )Weapon::DoneReloading },
{ &EV_Weapon_SetAmmoClipSize, ( Response )Weapon::SetAmmoClipSize },
{ &EV_Weapon_ProcessModelCommands, ( Response )Weapon::ProcessWeaponCommandsEvent },
{ &EV_Weapon_SetMaxRange, ( Response )Weapon::SetMaxRangeEvent },
{ &EV_Weapon_SetMinRange, ( Response )Weapon::SetMinRangeEvent },
{ &EV_Weapon_SetProjectileSpeed, ( Response )Weapon::SetProjectileSpeedEvent },
{ &EV_Weapon_SetKick, ( Response )Weapon::SetKick },
{ &EV_Weapon_SecondaryUse, ( Response )Weapon::SecondaryUse },
{ &EV_Weapon_PrimaryMode, ( Response )Weapon::PrimaryMode },
{ &EV_Weapon_SecondaryMode, ( Response )Weapon::SecondaryMode },
{ &EV_Weapon_ActionIncrement, ( Response )Weapon::SetActionLevelIncrement },
{ &EV_Weapon_PutAwayAndRaise, ( Response )Weapon::PutAwayAndRaise },
{ &EV_Weapon_Raise, ( Response )Weapon::Raise },
{ &EV_Weapon_NotDroppable, ( Response )Weapon::NotDroppableEvent },
{ &EV_Weapon_SetAimAnim, ( Response )Weapon::SetAimAnim },
//### added ability for weapons to add view jitter to their owners
{&EV_Weapon_SetAngleJitter, (Response)Weapon::AngleJitterEvent},
{&EV_Weapon_SetOffsetJitter, (Response)Weapon::OffsetJitterEvent},
//###
{ NULL, NULL }
};
static int numBulletHoles = 0;
static Queue queueBulletHoles;
void ResetBulletHoles(void)
{
numBulletHoles = 0;
queueBulletHoles.Clear();
}
Weapon::Weapon()
{
attack_finished = 0;
owner = NULL;
ammorequired = 0;
secondary_ammorequired = 0;
startammo = 0;
rank = 0;
ammo_clip_size = 0;
ammo_in_clip = G_GetIntArg( "ammo_in_clip", 0 );
kick = 0;
last_attack_time = level.time-10;
nextweaponsoundtime = 0;
weaponstate = WEAPON_HOLSTERED;
weaponmode = ( weaponmode_t )G_GetIntArg( "weaponmode", PRIMARY );
dualmode = false;
silenced = G_GetIntArg( "silenced", false );
notdroppable = false;
aimanim = -1;
aimframe = 0;
// handles most cases
weapontype = WEAPON_2HANDED_HI;
// start off unattached
attached = false;
// maximum effective firing distance (for AI)
maxrange = 8192 * 2; // FIXME grr... magic number...
// minimum safe firing distance (for AI)
minrange = 0;
// speed of the projectile (0 == infinite speed)
projectilespeed = 0;
// default action_level_increment
action_level_increment = 2;
}
Weapon::~Weapon()
{
DetachGun();
}
void Weapon::CreateSpawnArgs
(
void
)
{
Item::CreateSpawnArgs();
G_SetIntArg( "ammo_in_clip", ammo_in_clip );
G_SetIntArg( "silenced", silenced );
G_SetIntArg( "weaponmode", weaponmode );
}
int Weapon::Rank
(
void
)
{
return rank;
}
int Weapon::Order
(
void
)
{
return order;
}
void Weapon::SetRank
(
int order,
int rank
)
{
this->order = order;
this->rank = rank;
}
void Weapon::SetType
(
weapontype_t type
)
{
weapontype = type;
}
weapontype_t Weapon::GetType
(
void
)
{
return weapontype;
}
void Weapon::SetAmmo
(
const char *type,
int amount,
int startamount
)
{
if ( type )
{
ammotype = type;
primary_ammo_type = type;
}
else
{
ammotype = "";
primary_ammo_type = "";
}
ammorequired = amount;
startammo = startamount;
}
void Weapon::SetSecondaryAmmo
(
const char *type,
int amount,
int startamount
)
{
if ( type )
{
secondary_ammo_type = type;
}
else
{
secondary_ammo_type = "";
}
secondary_ammorequired = amount;
}
void Weapon::SetAmmoAmount
(
int amount
)
{
if ( ammo_clip_size )
ammo_in_clip = amount;
}
void Weapon::UseAmmo
(
int amount
)
{
if ( ammo_clip_size )
{
ammo_in_clip -= amount;
if (ammo_in_clip < 0)
{
warning("UseAmmo","Used more ammo than in clip.\n");
ammo_in_clip = 0;
}
}
else
{
Ammo * ammo;
assert( owner );
if ( owner && owner->isClient() && !UnlimitedAmmo() )
{
if ( ammotype.length() )
{
ammo = ( Ammo * )owner->FindItem( ammotype.c_str() );
//### it should use the amount of ammo passed in
if ( !ammo || !ammo->Use( amount ) )
{
SetAmmoAmount( 0 );
return;
}
/*
if ( weaponmode == PRIMARY )
{
if ( !ammo || !ammo->Use( ammorequired ) )
{
SetAmmoAmount( 0 );
return;
}
}
else
{
if ( !ammo || !ammo->Use( secondary_ammorequired ) )
{
SetAmmoAmount( 0 );
return;
}
}
*/
//###
SetAmmoAmount( ammo->Amount() );
}
}
}
}
Vector Weapon::MuzzleOffset
(
void
)
{
vec3_t trans[ 3 ];
vec3_t orient;
int groupindex;
int tri_num;
Vector offset = vec_zero;
int useanim;
int useframe;
// get the bone information
if ( ( !edict->s.gunmodelindex ) || ( owner && owner->isClient() ) )
{
if ( gi.GetBoneInfo( edict->s.modelindex, "barrel", &groupindex, &tri_num, orient ) )
{
if ( aimanim == -1 )
{
useanim = edict->s.anim;
useframe = edict->s.frame;
}
else
{
useanim = aimanim;
useframe = aimframe;
}
if ( gi.GetBoneTransform( edict->s.modelindex, groupindex, tri_num, orient, useanim, useframe,
edict->s.scale, trans, offset.vec3() ) )
{
//
// we scale the pos by 0.3 because of RF_DEPTHHACK
//
offset *= 0.3f;
if ( owner && owner->isClient() )
{
switch( owner->client->pers.hand )
{
case LEFT_HANDED:
offset[ 1 ] *= -1.0f;
break;
case CENTER_HANDED:
offset[ 1 ] = 0;
break;
}
}
}
}
}
//
// if it is a non-client, than get the information from the world model of the gun
//
else if ( gi.GetBoneInfo( edict->s.gunmodelindex, "barrel", &groupindex, &tri_num, orient ) )
{
gi.GetBoneTransform( edict->s.gunmodelindex, groupindex, tri_num, orient, 0, 0,
edict->s.scale, trans, offset.vec3() );
}
// Gun doesn't have a barrel, so search the owner for a barrel bone
else if ( owner && gi.GetBoneInfo( owner->edict->s.modelindex,
"barrel",
&groupindex,
&tri_num,
orient ) )
{
gi.GetBoneTransform( owner->edict->s.modelindex,
groupindex,
tri_num,
orient,
owner->edict->s.anim,
owner->edict->s.frame,
owner->edict->s.scale,
trans,
offset.vec3() );
}
return offset;
}
void Weapon::GetMuzzlePosition
(
Vector *position,
Vector *forward,
Vector *right,
Vector *up
)
{
Vector offset;
Vector f, r, u;
Vector pos;
Vector end;
Vector dir;
Vector gunpos;
trace_t trace;
assert( owner );
// technically, we should never not have an owner when firing.
if ( !owner )
{
return;
}
//
// get the position of the owners gun bone
//
gunpos = owner->GunPosition();
pos = gunpos;
owner->GetGunOrientation( pos, &f, &r, &u );
// get the bone information
offset = MuzzleOffset();
pos += f * offset[ 0 ];
pos -= r * offset[ 1 ];
pos += u * offset[ 2 ];
// prevent the creature from firing through walls
trace = G_Trace( gunpos, vec_zero, vec_zero, pos, owner, MASK_PROJECTILE, "Weapon::GetMuzzlePosition" );
if ( ( trace.fraction < 1 ) || ( trace.startsolid ) || ( trace.allsolid ) )
{
pos = gunpos;
pos -= r * offset[ 1 ];
pos += u * offset[ 2 ];
}
//
// calculate where this projectile is going to hit
//
end = gunpos + f*2048;
trace = G_FullTrace( gunpos, vec_zero, vec_zero, end, 10, owner, MASK_SHOT, "Weapon::GetMuzzlePosition" );
dir = trace.endpos - pos;
dir.normalize();
if ( dir*f < 0.707 )
dir = f;
if ( position )
{
*position = pos;
}
if ( forward )
{
*forward = dir;
}
if ( right )
{
*right = r;
}
if ( up )
{
*up = u;
}
}
void Weapon::SetAmmoClipSize
(
Event * ev
)
{
ammo_clip_size = ev->GetInteger( 1 );
}
void Weapon::SetModels
(
const char *world,
const char *view
)
{
Event *ev;
assert( view );
viewmodel = view;
modelIndex( view );
if ( world )
{
worldmodel = world;
modelIndex( world );
}
else
{
worldmodel = "";
}
if ( owner )
{
setModel( viewmodel );
}
else if ( worldmodel.length() )
{
setModel( worldmodel );
}
else
{
setModel( viewmodel );
}
if ( worldmodel.length() )
{
ev = new Event( EV_Weapon_ProcessModelCommands );
ev->AddInteger( modelIndex( worldmodel.c_str() ) );
PostEvent( ev, 0 );
}
ev = new Event( EV_Weapon_ProcessModelCommands );
ev->AddInteger( modelIndex( viewmodel.c_str() ) );
PostEvent( ev, 0 );
}
void Weapon::SetAimAnim
(
Event *ev
)
{
str anim;
anim = ev->GetString( 1 );
aimanim = gi.Anim_NumForName( edict->s.modelindex, anim.c_str() );
aimframe = ev->GetInteger( 2 );
}
void Weapon::SetOwner
(
Sentient *ent
)
{
assert( ent );
if ( !ent )
{
// return to avoid any buggy behaviour
return;
}
Item::SetOwner( ent );
setOrigin( vec_zero );
setAngles( vec_zero );
if ( !viewmodel.length() )
{
error( "setOwner", "Weapon without viewmodel set" );
}
setModel( viewmodel );
if ( ent->isClient() && ammotype.length() && startammo && !G_GetSpawnArg( "savegame" ) )
{
ent->giveItem( ammotype.c_str(), startammo );
}
}
int Weapon::AmmoAvailable
(
void
)
{
Ammo *ammo;
if ( owner )
{
if ( ammotype.length() )
{
ammo = ( Ammo * )owner->FindItem( ammotype.c_str() );
if ( ammo )
{
return ammo->Amount();
}
}
}
return 0;
}
qboolean Weapon::UnlimitedAmmo
(
void
)
{
if ( !owner )
{
return false;
}
if ( !owner->isClient() || ( owner->flags & FL_GODMODE ) || DM_FLAG( DF_INFINITE_AMMO ) )
{
return true;
}
#ifdef SIN_ARCADE
if ( !sv_infinitebullets || !sv_infiniterockets || !sv_infiniteplasma || !sv_infinitespears || !sv_infinitesniper )
{
sv_infinitebullets = gameVars.CreateVariable( "infinitebullets", 0 );
sv_infiniterockets = gameVars.CreateVariable( "infiniterockets", 0 );
sv_infiniteplasma = gameVars.CreateVariable( "infiniteplasma", 0 );
sv_infinitespears = gameVars.CreateVariable( "infinitespears", 0 );
sv_infinitesniper = gameVars.CreateVariable( "infinitesniper", 0 );
}
if ( sv_infinitebullets->intValue() && ( ( !Q_strncasecmp( ammotype.c_str(), "bullet", 6 ) && ( ammotype != "BulletPulse" ) &&
( ammotype != "BulletSniper" ) ) || ( ammotype == "ShotgunClip" ) ) )
{
return true;
}
if ( sv_infiniterockets->intValue() && ( ammotype == "Rockets" ) )
{
return true;
}
if ( sv_infiniteplasma->intValue() && ( ammotype == "BulletPulse" ) )
{
return true;
}
if ( sv_infinitesniper->intValue() && ( ammotype == "BulletSniper" ) )
{
return true;
}
if ( sv_infinitespears->intValue() && ( ammotype == "Spears" ) )
{
return true;
}
#endif
return false;
}
qboolean Weapon::HasAmmo
(
void
)
{
if ( !owner )
{
return false;
}
if ( UnlimitedAmmo() )
{
return true;
}
if ( weaponmode == PRIMARY )
{
if ( ( ammo_clip_size && ammo_in_clip >= ammorequired ) || AmmoAvailable() >= ammorequired )
{
return true;
}
}
else
{
if ( ( ammo_clip_size && ammo_in_clip >= secondary_ammorequired ) || AmmoAvailable() >= secondary_ammorequired )
{
return true;
}
}
return false;
}
qboolean Weapon::HasAmmoInClip
(
void
)
{
if ( ammo_clip_size )
{
if ( weaponmode == PRIMARY )
{
if ( ammo_in_clip >= ammorequired )
{
return true;
}
}
else
{
if ( ammo_in_clip >= secondary_ammorequired )
{
return true;
}
}
}
else
{
return HasAmmo();
}
return false;
}
void Weapon::ForceState
(
weaponstate_t state
)
{
weaponstate = state;
}
qboolean Weapon::AttackDone
(
void
)
{
//### added check for player controlling a missile
if(owner->isSubclassOf(Player))
return (attack_finished <= level.time) && (((Player *)owner.Ptr())->ViewMode() != MISSILE_VIEW);
else
return ( attack_finished <= level.time );
//###
}
qboolean Weapon::ReadyToFire
(
void
)
{
return ( weaponstate == WEAPON_READY ) && AttackDone();
}
qboolean Weapon::ReadyToChange
(
void
)
{
//###
// can never change from the informer gun
if((deathmatch->value == DEATHMATCH_MFD || deathmatch->value == DEATHMATCH_MOB) &&
this->isSubclassOf(InformerGun))
{
return false;
}
//###
return ( weaponstate == WEAPON_READY );
}
qboolean Weapon::ReadyToUse
(
void
)
{
return true;
}
qboolean Weapon::ChangingWeapons
(
void
)
{
return ( weaponstate == WEAPON_LOWERING ) || ( weaponstate == WEAPON_RAISING );
}
qboolean Weapon::WeaponRaising
(
void
)
{
return ( weaponstate == WEAPON_RAISING );
}
qboolean Weapon::WeaponPuttingAway
(
void
)
{
return ( weaponstate == WEAPON_LOWERING );
}
qboolean Weapon::Reloading
(
void
)
{
return ( weaponstate == WEAPON_RELOADING );
}
void Weapon::PutAway
(
void
)
{
if ( weaponstate != WEAPON_READY )
{
return;
}
weaponstate = WEAPON_LOWERING;
if ( !HasAnim( "putaway" ) || ( deathmatch->value && ( ( int )dmflags->value & DF_FAST_WEAPONS ) ) )
{
ProcessEvent( EV_Weapon_DoneLowering );
return;
}
if ( dualmode )
{
if ( weaponmode == PRIMARY )
RandomAnimate( "primaryputaway", EV_Weapon_DoneLowering );
else
RandomAnimate( "secondaryputaway", EV_Weapon_DoneLowering );
}
else
if ( ( owner ) && ( owner->flags & FL_SILENCER ) && ( silenced ) )
RandomAnimate( "silputaway", EV_Weapon_DoneLowering );
else
RandomAnimate( "putaway", EV_Weapon_DoneLowering );
//weaponmode = PRIMARY;
}
void Weapon::ReadyWeapon
(
void
)
{
str animname;
if ( weaponstate != WEAPON_HOLSTERED )
{
return;
}
weaponstate = WEAPON_RAISING;
AttachGun();
if ( ( owner ) && ( owner->flags & FL_SILENCER ) && ( silenced ) )
animname = "silready";
else
{
if ( weaponmode == SECONDARY )
animname = "secondaryready";
else
animname = "ready";
}
if ( !HasAnim( animname.c_str() ) || ( deathmatch->value && ( ( int )dmflags->value & DF_FAST_WEAPONS ) ) )
{
ProcessEvent( EV_Weapon_DoneRaising );
return;
}
RandomAnimate( animname.c_str(), EV_Weapon_DoneRaising );
}
void Weapon::DetachFromOwner
(
void
)
{
StopAnimating();
DetachGun();
weaponstate = WEAPON_HOLSTERED;
}
void Weapon::AttachToOwner
(
void
)
{
AttachGun();
ForceIdle();
}
qboolean Weapon::Drop
(
void
)
{
float radius;
Vector temp;
if ( !owner )
{
return false;
}
if ( !IsDroppable() )
{
return false;
}
DetachGun();
const gravityaxis_t &grav = gravity_axis[ gravaxis ];
temp[ grav.z ] = 40 * grav.sign;
if ( owner )
{
setOrigin( owner->worldorigin + temp );
}
else
{
setOrigin( worldorigin + temp );
}
setModel( worldmodel );
// hack to fix the bounds when the gun is dropped
//
// once dropped reset the rotated bounds
//
flags |= FL_ROTATEDBOUNDS;
gi.CalculateBounds( edict->s.modelindex, edict->s.scale, mins.vec3(), maxs.vec3() );
radius = ( mins - maxs ).length() * 0.25f;
mins.x = mins.y = -radius;
maxs.x = maxs.y = radius;
setSize( mins, maxs );
StopAnimating();
edict->s.frame = 0;
edict->s.anim = 0;
// drop the weapon
PlaceItem();
if ( owner )
{
temp[ grav.x ] = G_CRandom( 50 );
temp[ grav.y ] = G_CRandom( 50 );
temp[ grav.z ] = 100 * grav.sign;
velocity = owner->velocity * 0.5 + temp;
setAngles( owner->angles );
}
avelocity = Vector( 0, G_CRandom( 360 ), 0 );
if ( owner && owner->isClient() )
{
spawnflags |= DROPPED_PLAYER_ITEM;
if ( ammo_clip_size )
startammo = ammo_in_clip;
else
startammo = 0;
// If owner is dead, put all his ammo of that type in the gun.
if ( owner->deadflag )
{
startammo = AmmoAvailable();
}
}
else
{
spawnflags |= DROPPED_ITEM;
if ( ammo_clip_size && ammo_in_clip )
startammo = ammo_in_clip;
else
startammo >>= 2;
if ( startammo == 0 )
{
startammo = 1;
}
}
// Wait some time before the last owner can pickup this weapon
last_owner = owner;
last_owner_trigger_time = level.time + 2.5f;
// Cancel reloading events
CancelEventsOfType( EV_Weapon_DoneReloading );
// Remove this from the owner's item list
if ( owner )
{
owner->RemoveItem( this );
}
owner = NULL;
// Fade out dropped weapons, to keep down the clutter
PostEvent( EV_FadeOut, 30 );
return true;
}
void Weapon::Fire
(
void
)
{
qboolean skipframefix;
if ( !ReadyToFire() )
{
return;
}
if ( !HasAmmoInClip() )
{
CheckReload();
return;
}
if ( weaponmode == PRIMARY )
UseAmmo( ammorequired );
else
UseAmmo( secondary_ammorequired );
weaponstate = WEAPON_FIRING;
CancelEventsOfType( EV_Weapon_DoneFiring );
// this is just a precaution that we can re-trigger
NextAttack( 5 );
skipframefix = false;
if(owner && owner->isClient() && !isSubclassOf(ChainGun) && !isSubclassOf(AssaultRifle))
{
skipframefix = true;
StopAnimating();
}
if ( dualmode )
{
if ( weaponmode == PRIMARY )
{
RandomAnimate( "primaryfire", EV_Weapon_DoneFiring );
}
else
{
RandomAnimate( "secondaryfire", EV_Weapon_DoneFiring );
}
}
else
{
if ( ( owner ) && ( owner->flags & FL_SILENCER ) && ( silenced ) )
RandomAnimate( "silfire", EV_Weapon_DoneFiring );
else
RandomAnimate( "fire", EV_Weapon_DoneFiring );
}
if(skipframefix)
{
last_animation_time = (level.framenum + 1) * FRAMETIME;
}
last_attack_time = level.time;
}
//**********************************************************************************/
//
// Non-public Weapon functions
//
//**********************************************************************************/
void Weapon::DetachGun
(
void
)
{
if ( attached )
{
RandomGlobalSound("null_sound", 1, CHAN_WEAPONIDLE );
RandomGlobalSound("null_sound", 1, CHAN_WEAPON ); //###
attached = false;
detach();
hideModel();
edict->s.gunmodelindex = 0;
edict->s.gunanim = 0;
edict->s.gunframe = 0;
edict->s.effects &= ~EF_SMOOTHANGLES;
edict->s.effects &= ~EF_WARM;
}
}
//
// attach and detach the gun from the owner
//
void Weapon::AttachGun
(
void
)
{
int groupindex;
int tri_num;
Vector orient;
if ( !owner )
{
return;
}
if (attached)
DetachGun();
if ( gi.GetBoneInfo( owner->edict->s.modelindex, "gun", &groupindex, &tri_num, orient.vec3() ) )
{
attached = true;
attach( owner->entnum, groupindex, tri_num, orient );
showModel();
setOrigin( vec_zero );
edict->s.gunmodelindex = modelIndex( worldmodel.c_str() );
if ( edict->s.gunmodelindex )
{
edict->s.gunanim = gi.Anim_Random( edict->s.gunmodelindex, "idle" );
if ( edict->s.gunanim < 0 )
edict->s.gunanim = 0;
edict->s.gunframe = 0;
}
else
{
edict->s.gunanim = 0;
edict->s.gunframe = 0;
}
edict->s.effects |= EF_SMOOTHANGLES;
edict->s.effects |= EF_WARM; //###
}
else
{
gi.dprintf( "attach failed\n" );
}
}
//### changed so that weapon does not
// change if player already has the weapon
// also doesn't change the player's weapon if on a hoverbike.
// Added support for togglable auto weapon switching
void Weapon::PickupWeapon
(
Event *ev
)
{
Sentient *sen;
Entity *other;
Weapon *weapon;
Weapon *current;
qboolean hasweapon;
qboolean giveammo;
Ammo *ammo;
other = ev->GetEntity( 1 );
assert( other );
if ( !other->isSubclassOf( Sentient ) )
{
return;
}
sen = ( Sentient * )other;
// If this is the last owner, check to see if he can pick it up
if ( ( sen == last_owner ) && ( level.time < last_owner_trigger_time ) )
{
return;
}
hasweapon = sen->HasItem( getClassname() );
giveammo = ( sen->isClient() && ammotype.length() && startammo );
// if he already has the weapon, don't pick it up if he doesn't need the ammo
if ( hasweapon )
{
//### added for dual magnum
if(isSubclassOf(Magnum) && !sen->HasItem("DualMagnum"))
{
// give a dual magnum
weapon = sen->giveWeapon("DualMagnum");
current = sen->CurrentWeapon();
if ( !hasweapon && current && ( current != weapon ) && ( current->AutoChange() ) &&
( ( current->Rank() < weapon->Rank() ) || ( !current->HasAmmo() && weapon->HasAmmo() ) ) )
{
if(sen->IsOnBike() && sen->isClient())
{
Hoverbike *bike;
if(sen->autoweaponswitch ||
stricmp(current->getClassname(), "Magnum"))
{
bike = ((Player *)sen)->GetHoverbike();
bike->oldweapon = weapon;
}
}
else if(sen->autoweaponswitch)
{
sen->ChangeWeapon( weapon );
}
else
{
sen->NonAutoChangeWeapon( weapon );
}
}
}
//###
if ( !giveammo )
{
return;
}
// check if he needs the ammo
ammo = ( Ammo * )sen->FindItem( ammotype.c_str() );
if ( ammo && ( ammo->Amount() >= ammo->MaxAmount() ) )
{
// doesn't need the ammo or the weapon, so return.
return;
}
}
weapon = ( Weapon * )ItemPickup( other );
if ( !weapon )
{
// Item Pickup failed, so don't give ammo either.
return;
}
//
// once picked up we don't want rotated bounds
//
flags &= ~FL_ROTATEDBOUNDS;
//FIXME
// Sentient should probably handle this when itempickup is called
// Check if we should switch his weapon
current = sen->CurrentWeapon();
if ( !hasweapon && current && ( current != weapon ) && ( current->AutoChange() ) &&
( ( current->Rank() < weapon->Rank() ) || ( !current->HasAmmo() && weapon->HasAmmo() ) ) )
{
//### added hoverbike condition stuff
if(sen->IsOnBike())
{
Hoverbike *bike;
if(sen->autoweaponswitch)
{
bike = ((Player *)sen)->GetHoverbike();
bike->oldweapon = weapon;
}
}
else if(sen->autoweaponswitch)
{
sen->ChangeWeapon( weapon );
}
else
{
sen->NonAutoChangeWeapon( weapon );
}
//###
}
//### added give stuff for rocket & missile launchers in deathmatch
if(deathmatch->value)
{
if(isSubclassOf(RocketLauncher))
{
sen->giveWeapon("MissileLauncher");
}
else if(isSubclassOf(MissileLauncher))
{
sen->giveWeapon("RocketLauncher");
}
}
//###
// check if we should give him ammo
if ( giveammo )
{
sen->giveItem( ammotype.c_str(), startammo );
}
}
void Weapon::ForceIdle
(
void
)
{
weaponstate = WEAPON_READY;
if ( dualmode )
{
if ( weaponmode == PRIMARY )
RandomAnimate( "primaryidle", EV_Weapon_Idle );
else
RandomAnimate( "secondaryidle", EV_Weapon_Idle );
}
else
{
if ( owner && ( owner->flags & FL_SILENCER ) && ( silenced ) )
RandomAnimate( "silidle", EV_Weapon_Idle );
else
RandomAnimate( "idle", EV_Weapon_Idle );
}
}
void Weapon::DoneLowering
(
Event *ev
)
{
weaponstate = WEAPON_HOLSTERED;
DetachGun();
if ( owner )
{
owner->ProcessEvent( EV_Sentient_WeaponPutAway );
}
StopAnimating();
}
void Weapon::DoneRaising
(
Event *ev
)
{
weaponstate = WEAPON_READY;
if ( !ForceReload() )
{
ForceIdle();
}
if ( !owner )
{
PostEvent( EV_Remove, 0 );
return;
}
// Set the ammo type
if (weaponmode == PRIMARY)
{
ammotype = primary_ammo_type;
}
else if ( weaponmode == SECONDARY )
{
ammotype = secondary_ammo_type;
}
if ( owner )
owner->ProcessEvent( EV_Sentient_WeaponReady );
}
void Weapon::DoneFiring
(
Event *ev
)
{
if ( !CheckReload() )
{
ForceIdle();
}
if ( owner )
{
owner->ProcessEvent( EV_Sentient_WeaponDoneFiring );
}
}
void Weapon::DoneReloading
(
Event *ev
)
{
Ammo *ammo;
int amount;
amount = ammo_clip_size - ammo_in_clip;
assert( owner );
if ( owner && owner->isClient() && !UnlimitedAmmo() )
{
if ( ammotype.length() )
{
ammo = ( Ammo * )owner->FindItem( ammotype.c_str() );
if ( ammo )
{
if ( ammo->Amount() < amount )
amount = ammo->Amount();
if ( !ammo->Use( amount ) )
{
amount = 0;
}
}
SetAmmoAmount( amount + ammo_in_clip );
}
}
else
{
SetAmmoAmount( ammo_clip_size );
}
ForceIdle();
if ( owner )
{
owner->ProcessEvent( EV_Sentient_WeaponReady );
}
}
qboolean Weapon::ForceReload
(
void
)
{
if ( weaponstate != WEAPON_READY )
return false;
if ( level.cinematic && owner && owner->isClient() )
{
return false;
}
// do a reload if the ammo in clip is not full,
// and there is some ammo available out of clip
if (
( ammo_clip_size != ammo_in_clip ) &&
( AmmoAvailable() > 0 )
)
{
weaponstate = WEAPON_RELOADING;
if ( ( owner ) && ( owner->flags & FL_SILENCER ) && ( silenced ) )
RandomAnimate( "silreload", EV_Weapon_DoneReloading );
else
RandomAnimate( "reload", EV_Weapon_DoneReloading );
return true;
}
return false;
}
qboolean Weapon::CheckReload
(
void
)
{
if ( weaponstate != WEAPON_READY )
return false;
if ( level.cinematic && owner && owner->isClient() )
{
return false;
}
if ( ammo_clip_size && !ammo_in_clip && HasAmmo() )
{
weaponstate = WEAPON_RELOADING;
if ( ( owner ) && ( owner->flags & FL_SILENCER ) && ( silenced ) )
RandomAnimate( "silreload", EV_Weapon_DoneReloading );
else
RandomAnimate( "reload", EV_Weapon_DoneReloading );
return true;
}
return false;
}
void Weapon::Idle
(
Event *ev
)
{
if ( ammo_clip_size && ( !ammo_in_clip || level.time > last_attack_time + 2 ) )
{
if (CheckReload())
return;
}
ForceIdle();
}
void Weapon::FinishAttack
(
Event *ev
)
{
attack_finished = level.time;
}
void Weapon::NextAttack
(
double rate
)
{
attack_finished = level.time + (float)rate;
}
float Weapon::GetMaxRange
(
void
)
{
return maxrange;
}
float Weapon::GetMinRange
(
void
)
{
return minrange;
}
float Weapon::GetProjectileSpeed
(
void
)
{
return projectilespeed;
}
void Weapon::SetMaxRangeEvent
(
Event *ev
)
{
maxrange = ev->GetFloat( 1 );
}
void Weapon::SetMinRangeEvent
(
Event *ev
)
{
minrange = ev->GetFloat( 1 );
}
void Weapon::SetProjectileSpeedEvent
(
Event *ev
)
{
projectilespeed = ev->GetFloat( 1 );
}
void Weapon::NotDroppableEvent
(
Event *ev
)
{
notdroppable = true;
}
void Weapon::SetMaxRange
(
float val
)
{
maxrange = val;
}
void Weapon::SetMinRange
(
float val
)
{
minrange = val;
}
void Weapon::SetProjectileSpeed
(
float val
)
{
projectilespeed = val;
}
void Weapon::EventMuzzleFlash
(
Event *ev
)
{
if ( !owner )
{
return;
}
SpawnTempDlight
(
owner->worldorigin,
ev->GetFloat( 1 ),
ev->GetFloat( 2 ),
ev->GetFloat( 3 ),
ev->GetFloat( 4 ),
ev->GetFloat( 5 ),
ev->GetFloat( 6 )
);
}
void Weapon::MuzzleFlash
(
float r,
float g,
float b,
float radius,
float decay,
float life
)
{
if ( !owner )
{
return;
}
SpawnTempDlight
(
owner->worldorigin,
r,
g,
b,
radius,
decay,
life
);
}
void Weapon::BulletHole
(
trace_t *trace
)
{
Entity *hole;
Vector norm,norm2;
Entity *hit;
hit = trace->ent->entity;
if ( !hit )
{
return;
}
if ( trace->surface &&
( trace->surface->flags & SURF_NODRAW ) )
return;
if ( hit->hidden() )
return;
// FIXME: Make Bullet holes client side.
if ( deathmatch->value )
return;
hole = new Entity;
hole->setMoveType( MOVETYPE_PUSH );
hole->setSolidType( SOLID_NOT );
hole->setModel( "sprites/bullethole.spr" );
hole->setSize( "0 0 0", "0 0 0" );
norm = trace->plane.normal;
norm2.x = -norm.x;
norm2.y = -norm.y;
norm2.z = norm.z;
hole->angles = norm2.toAngles();
hole->setAngles( hole->angles );
hole->setOrigin( Vector( trace->endpos ) + ( norm * 0.2 ) ) ;
if ( ( trace->ent->solid == SOLID_BSP ) && ( hit != world ) )
{
hole->bind( hit );
}
queueBulletHoles.Enqueue(hole);
numBulletHoles++;
if (numBulletHoles > sv_maxbulletholes->value)
{
// Fade one out of the list.
Entity *fadehole;
fadehole = (Entity *) queueBulletHoles.Dequeue();
fadehole->ProcessEvent(EV_Remove);
numBulletHoles--;
}
}
EXPORT_FROM_DLL void Weapon::WeaponSound
(
Event *ev
)
{
Event *e;
// Broadcasting a sound can be time consuming. Only do it once in a while on really fast guns.
if ( nextweaponsoundtime > level.time )
{
if ( owner )
{
owner->BroadcastSound( ev, CHAN_WEAPON, NullEvent, 0 );
}
else
{
BroadcastSound( ev, CHAN_WEAPON, EV_HeardWeapon, 0 );
}
return;
}
if ( owner )
{
e = new Event( ev );
owner->ProcessEvent( e );
}
else
{
Item::WeaponSound( ev );
}
// give us some breathing room
nextweaponsoundtime = level.time + 0.4;
}
qboolean Weapon::Removable
(
void
)
{
if (
( ( int )( dmflags->value ) & DF_WEAPONS_STAY ) &&
!( spawnflags & ( DROPPED_ITEM | DROPPED_PLAYER_ITEM ) )
)
return false;
else
return true;
}
qboolean Weapon::Pickupable
(
Entity *other
)
{
Sentient *sen;
if ( !other->isSubclassOf( Sentient ) )
{
return false;
}
else if ( !other->isClient() )
{
return false;
}
sen = ( Sentient * )other;
//FIXME
// This should be in player
// Mutants can't pickup weapons
if ( other->flags & (FL_MUTANT|FL_SP_MUTANT) )
{
return false;
}
// If we have the weapon and weapons stay, then don't pick it up
if ( ( ( int )( dmflags->value ) & DF_WEAPONS_STAY ) && !( spawnflags & ( DROPPED_ITEM | DROPPED_PLAYER_ITEM ) ) )
{
Weapon *weapon;
weapon = ( Weapon * )sen->FindItem( getClassname() );
if ( weapon )
return false;
}
return true;
}
qboolean Weapon::AutoChange
(
void
)
{
return true;
}
int Weapon::ClipAmmo
(
void
)
{
if (ammo_clip_size)
return ammo_in_clip;
else
return -1;
}
void Weapon::ProcessWeaponCommandsEvent
(
Event *ev
)
{
int index;
index = ev->GetInteger( 1 );
ProcessInitCommands( index );
}
void Weapon::SetKick
(
Event *ev
)
{
kick = ev->GetInteger( 1 );
}
void Weapon::SecondaryUse
(
Event *ev
)
{
if ( !dualmode )
return;
// Switch to the secondary mode of the weapon
if (weaponstate != WEAPON_READY)
return;
weaponstate = WEAPON_CHANGING;
if (weaponmode == PRIMARY)
{
RandomAnimate( "primary2secondary", EV_Weapon_SecondaryMode );
weaponmode = SECONDARY;
}
else
{
RandomAnimate( "secondary2primary", EV_Weapon_PrimaryMode );
weaponmode = PRIMARY;
}
}
void Weapon::PrimaryMode
(
Event *ev
)
{
RandomAnimate( "primaryidle", EV_Weapon_Idle );
weaponstate = WEAPON_READY;
ammotype = primary_ammo_type;
}
void Weapon::SecondaryMode
(
Event *ev
)
{
RandomAnimate( "secondaryidle", EV_Weapon_Idle );
weaponstate = WEAPON_READY;
ammotype = secondary_ammo_type;
}
void Weapon::SetActionLevelIncrement
(
Event *ev
)
{
action_level_increment = ev->GetInteger( 1 );
}
int Weapon::ActionLevelIncrement
(
void
)
{
return action_level_increment;
}
qboolean Weapon::IsDroppable
(
void
)
{
if ( notdroppable || !worldmodel.length() )
{
return false;
}
else
{
return true;
}
}
qboolean Weapon::IsSilenced
(
void
)
{
return silenced;
}
void Weapon::Raise
(
Event *ev
)
{
weaponstate = WEAPON_HOLSTERED;
ReadyWeapon();
}
void Weapon::PutAwayAndRaise
(
Event *ev
)
{
weaponstate = WEAPON_LOWERING;
if ( deathmatch->value && ( ( int )dmflags->value & DF_FAST_WEAPONS ) )
{
ProcessEvent( EV_Weapon_DoneRaising );
return;
}
RandomAnimate( "putaway", EV_Weapon_Raise );
}
//### 2015 added stuffs
void Weapon::AngleJitterEvent(Event *ev)
{
Event *e;
if(!owner)
return;
if(!owner->isClient())
return;
e = new Event(EV_Player_SetAngleJitter);
e->AddFloat(ev->GetFloat(1));
e->AddFloat(ev->GetFloat(2));
e->AddFloat(ev->GetFloat(3));
owner->ProcessEvent(e);
}
void Weapon::OffsetJitterEvent(Event *ev)
{
Event *e;
if(!owner)
return;
if(!owner->isClient())
return;
e = new Event(EV_Player_SetOffsetJitter);
e->AddFloat(ev->GetFloat(1));
e->AddFloat(ev->GetFloat(2));
e->AddFloat(ev->GetFloat(3));
owner->ProcessEvent(e);
}
//###