3334 lines
88 KiB
C++
3334 lines
88 KiB
C++
// RAVEN BEGIN
|
|
// bdube: note that this file is no longer merged with Doom3 updates
|
|
//
|
|
// MERGE_DATE 09/30/2004
|
|
|
|
#include "../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "Game_local.h"
|
|
|
|
#include "Weapon.h"
|
|
#include "Projectile.h"
|
|
#include "ai/AI.h"
|
|
#include "ai/AI_Manager.h"
|
|
#include "client/ClientEffect.h"
|
|
//#include "../renderer/tr_local.h"
|
|
|
|
/***********************************************************************
|
|
|
|
rvViewWeapon
|
|
|
|
***********************************************************************/
|
|
|
|
// class def
|
|
CLASS_DECLARATION( idAnimatedEntity, rvViewWeapon )
|
|
EVENT( EV_CallFunction, rvViewWeapon::Event_CallFunction )
|
|
END_CLASS
|
|
|
|
/***********************************************************************
|
|
|
|
init
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::rvViewWeapon()
|
|
================
|
|
*/
|
|
rvViewWeapon::rvViewWeapon() {
|
|
modelDefHandle = -1;
|
|
weapon = NULL;
|
|
|
|
Clear();
|
|
|
|
fl.networkSync = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::~rvViewWeapon()
|
|
================
|
|
*/
|
|
rvViewWeapon::~rvViewWeapon() {
|
|
Clear();
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::Spawn
|
|
================
|
|
*/
|
|
void rvViewWeapon::Spawn( void ) {
|
|
GetPhysics()->SetContents( 0 );
|
|
GetPhysics()->SetClipMask( 0 );
|
|
GetPhysics()->SetClipModel( NULL, 1.0f );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::Save
|
|
================
|
|
*/
|
|
void rvViewWeapon::Save( idSaveGame *savefile ) const {
|
|
int i;
|
|
savefile->WriteInt ( pendingGUIEvents.Num() );
|
|
for ( i = 0; i < pendingGUIEvents.Num(); i ++ ) {
|
|
savefile->WriteString ( pendingGUIEvents[i] );
|
|
}
|
|
|
|
// TOSAVE: const idDeclSkin * saveSkin;
|
|
// TOSAVE: const idDeclSkin * invisSkin;
|
|
// TOSAVE: const idDeclSkin * saveWorldSkin;
|
|
// TOSAVE: const idDeclSkin * worldInvisSkin;
|
|
// TOSAVE: const idDeclSkin * saveHandsSkin;
|
|
// TOSAVE: const idDeclSkin * handsSkin;
|
|
|
|
// TOSAVE: friend rvWeapon;
|
|
// TOSAVE: rvWeapon* weapon;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::Restore
|
|
================
|
|
*/
|
|
void rvViewWeapon::Restore( idRestoreGame *savefile ) {
|
|
int i;
|
|
int num;
|
|
savefile->ReadInt ( num );
|
|
pendingGUIEvents.SetNum ( num );
|
|
for ( i = 0; i < num; i ++ ) {
|
|
savefile->ReadString ( pendingGUIEvents[i] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
rvViewWeapon::ClientPredictionThink
|
|
===============
|
|
*/
|
|
void rvViewWeapon::ClientPredictionThink( void ) {
|
|
UpdateAnimation();
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Weapon definition management
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::Clear
|
|
================
|
|
*/
|
|
void rvViewWeapon::Clear( void ) {
|
|
DeconstructScriptObject();
|
|
scriptObject.Free();
|
|
|
|
StopAllEffects( );
|
|
|
|
// TTimo - the weapon doesn't get a proper Event_DisableWeapon sometimes, so the sound sticks
|
|
// typically, client side instance join in tourney mode just wipes all ents
|
|
StopSound( SND_CHANNEL_ANY, false );
|
|
|
|
memset( &renderEntity, 0, sizeof( renderEntity ) );
|
|
renderEntity.entityNum = entityNumber;
|
|
|
|
renderEntity.noShadow = true;
|
|
renderEntity.noSelfShadow = true;
|
|
renderEntity.customSkin = NULL;
|
|
|
|
// set default shader parms
|
|
renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f;
|
|
renderEntity.shaderParms[ SHADERPARM_GREEN ]= 1.0f;
|
|
renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f;
|
|
renderEntity.shaderParms[3] = 1.0f;
|
|
renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = 0.0f;
|
|
renderEntity.shaderParms[5] = 0.0f;
|
|
renderEntity.shaderParms[6] = 0.0f;
|
|
renderEntity.shaderParms[7] = 0.0f;
|
|
|
|
memset( &refSound, 0, sizeof( refSound_t ) );
|
|
refSound.referenceSoundHandle = -1;
|
|
|
|
// setting diversity to 0 results in no random sound. -1 indicates random.
|
|
refSound.diversity = -1.0f;
|
|
|
|
if ( weapon && weapon->GetOwner ( ) ) {
|
|
// don't spatialize the weapon sounds
|
|
refSound.listenerId = weapon->GetOwner( )->GetListenerId();
|
|
}
|
|
|
|
animator.ClearAllAnims( gameLocal.time, 0 );
|
|
|
|
FreeModelDef();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvViewWeapon::GetDebugInfo
|
|
=====================
|
|
*/
|
|
void rvViewWeapon::GetDebugInfo( debugInfoProc_t proc, void* userData ) {
|
|
// Base class first
|
|
idAnimatedEntity::GetDebugInfo( proc, userData );
|
|
weapon->GetDebugInfo( proc, userData );
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
GUIs
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::PostGUIEvent
|
|
================
|
|
*/
|
|
void rvViewWeapon::PostGUIEvent( const char* event ) {
|
|
pendingGUIEvents.Append ( event );
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Model and muzzleflash
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::SetPowerUpSkin
|
|
================
|
|
*/
|
|
void rvViewWeapon::SetPowerUpSkin( const char *name ) {
|
|
/* FIXME
|
|
saveSkin = renderEntity.customSkin;
|
|
renderEntity.customSkin = invisSkin;
|
|
if ( worldModel.GetEntity() ) {
|
|
saveWorldSkin = worldModel.GetEntity()->GetSkin();
|
|
worldModel.GetEntity()->SetSkin( worldInvisSkin );
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::UpdateSkin
|
|
================
|
|
*/
|
|
void rvViewWeapon::UpdateSkin( void ) {
|
|
/* FIXME
|
|
renderEntity.customSkin = saveSkin;
|
|
if ( worldModel.GetEntity() ) {
|
|
worldModel.GetEntity()->SetSkin( saveWorldSkin );
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::SetModel
|
|
================
|
|
*/
|
|
void rvViewWeapon::SetModel( const char *modelname, int mods ) {
|
|
assert( modelname );
|
|
|
|
if ( modelDefHandle >= 0 ) {
|
|
gameRenderWorld->RemoveDecals( modelDefHandle );
|
|
}
|
|
|
|
renderEntity.hModel = animator.SetModel( modelname );
|
|
if ( renderEntity.hModel ) {
|
|
renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin();
|
|
animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints );
|
|
} else {
|
|
renderEntity.customSkin = NULL;
|
|
renderEntity.callback = NULL;
|
|
renderEntity.numJoints = 0;
|
|
renderEntity.joints = NULL;
|
|
}
|
|
|
|
// hide the model until an animation is played
|
|
Hide();
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
State control/player interface
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::Think
|
|
================
|
|
*/
|
|
void rvViewWeapon::Think( void ) {
|
|
// do nothing because the present is called from the player through PresentWeapon
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvViewWeapon::ConvertLocalToWorldTransform
|
|
=====================
|
|
*/
|
|
void rvViewWeapon::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) {
|
|
if( !weapon ) {
|
|
idAnimatedEntity::ConvertLocalToWorldTransform( offset, axis );
|
|
return;
|
|
}
|
|
|
|
offset = GetPhysics()->GetOrigin() + offset * weapon->ForeshortenAxis( GetPhysics()->GetAxis() );
|
|
axis *= GetPhysics()->GetAxis();
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::UpdateModelTransform
|
|
================
|
|
*/
|
|
void rvViewWeapon::UpdateModelTransform( void ) {
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
|
|
if( !weapon ) {
|
|
idAnimatedEntity::UpdateModelTransform();
|
|
return;
|
|
}
|
|
|
|
if ( GetPhysicsToVisualTransform( origin, axis ) ) {
|
|
renderEntity.axis = axis * weapon->ForeshortenAxis( GetPhysics()->GetAxis() );
|
|
renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis;
|
|
} else {
|
|
renderEntity.axis = weapon->ForeshortenAxis( GetPhysics()->GetAxis() );
|
|
renderEntity.origin = GetPhysics()->GetOrigin();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::PresentWeapon
|
|
================
|
|
*/
|
|
void rvViewWeapon::PresentWeapon( bool showViewModel ) {
|
|
// Dont do anything with the weapon while its stale
|
|
if ( fl.networkStale ) {
|
|
return;
|
|
}
|
|
|
|
// RAVEN BEGIN
|
|
// rjohnson: cinematics should never be done from the player's perspective, so don't think the weapon ( and their sounds! )
|
|
if ( gameLocal.inCinematic ) {
|
|
return;
|
|
}
|
|
// RAVEN END
|
|
|
|
// only show the surface in player view
|
|
renderEntity.allowSurfaceInViewID = weapon->GetOwner()->entityNumber + 1;
|
|
|
|
// crunch the depth range so it never pokes into walls this breaks the machine gun gui
|
|
renderEntity.weaponDepthHackInViewID = weapon->GetOwner()->entityNumber + 1;
|
|
|
|
weapon->Think();
|
|
|
|
// present the model
|
|
if ( showViewModel && !(weapon->wsfl.zoom && weapon->GetZoomGui() ) ) {
|
|
Present();
|
|
} else {
|
|
FreeModelDef();
|
|
}
|
|
|
|
UpdateSound();
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::WriteToSnapshot
|
|
================
|
|
*/
|
|
void rvViewWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const {
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::ReadFromSnapshot
|
|
================
|
|
*/
|
|
void rvViewWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::ClientStale
|
|
================
|
|
*/
|
|
bool rvViewWeapon::ClientStale ( void ) {
|
|
StopSound( SND_CHANNEL_ANY, false );
|
|
|
|
if ( weapon ) {
|
|
weapon->ClientStale( );
|
|
}
|
|
|
|
idEntity::ClientStale( );
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::ClientReceiveEvent
|
|
================
|
|
*/
|
|
bool rvViewWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
|
|
if ( idEntity::ClientReceiveEvent( event, time, msg ) ) {
|
|
return true;
|
|
}
|
|
if ( weapon ) {
|
|
return weapon->ClientReceiveEvent ( event, time, msg );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Script events
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
=====================
|
|
rvViewWeapon::Event_CallFunction
|
|
=====================
|
|
*/
|
|
void rvViewWeapon::Event_CallFunction( const char *funcname ) {
|
|
if ( weapon ) {
|
|
stateParms_t parms = {0};
|
|
if ( weapon->ProcessState ( funcname, parms ) == SRESULT_ERROR ) {
|
|
gameLocal.Error ( "Unknown function '%s' on entity '%s'", funcname, GetName() );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::SetSkin
|
|
================
|
|
*/
|
|
void rvViewWeapon::SetSkin( const char *skinname ) {
|
|
const idDeclSkin *skinDecl;
|
|
|
|
if ( !skinname || !skinname[ 0 ] ) {
|
|
skinDecl = NULL;
|
|
} else {
|
|
skinDecl = declManager->FindSkin( skinname );
|
|
}
|
|
|
|
renderEntity.customSkin = skinDecl;
|
|
UpdateVisuals();
|
|
|
|
// Set the skin on the world model as well
|
|
if ( weapon->GetWorldModel() ) {
|
|
weapon->GetWorldModel()->SetSkin( skinDecl );
|
|
}
|
|
}
|
|
|
|
void rvViewWeapon::SetSkin( const idDeclSkin* skin ) {
|
|
renderEntity.customSkin = skin;
|
|
UpdateVisuals();
|
|
|
|
if( weapon && weapon->GetWorldModel() ) {
|
|
weapon->GetWorldModel()->SetSkin( skin );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvViewWeapon::GetPosition
|
|
================
|
|
*/
|
|
void rvViewWeapon::GetPosition( idVec3& origin, idMat3& axis ) const {
|
|
origin = GetPhysics()->GetOrigin();
|
|
axis = GetPhysics()->GetAxis();
|
|
}
|
|
|
|
void rvViewWeapon::SetOverlayShader( const idMaterial* material ) {
|
|
renderEntity.overlayShader = material;
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
rvWeapon
|
|
|
|
***********************************************************************/
|
|
|
|
CLASS_DECLARATION( idClass, rvWeapon )
|
|
END_CLASS
|
|
|
|
/*
|
|
================
|
|
rvWeapon::rvWeapon
|
|
================
|
|
*/
|
|
rvWeapon::rvWeapon ( void ) {
|
|
viewModel = NULL;
|
|
worldModel = NULL;
|
|
weaponDef = NULL;
|
|
|
|
#ifdef _XENON
|
|
aimAssistFOV = 10.0f;
|
|
#endif
|
|
|
|
memset ( &animDoneTime, 0, sizeof(animDoneTime) );
|
|
memset ( &wsfl, 0, sizeof(wsfl) );
|
|
memset ( &wfl, 0, sizeof(wfl) );
|
|
|
|
hitscanAttackDef = -1;
|
|
|
|
forceGUIReload = false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::~rvWeapon
|
|
================
|
|
*/
|
|
rvWeapon::~rvWeapon( void ) {
|
|
int i;
|
|
|
|
// Free all current light defs
|
|
for ( i = 0; i < WPLIGHT_MAX; i ++ ) {
|
|
FreeLight ( i );
|
|
}
|
|
|
|
// Disassociate with the view model
|
|
if ( viewModel ) {
|
|
viewModel->weapon = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Init
|
|
================
|
|
*/
|
|
void rvWeapon::Init( idPlayer* _owner, const idDeclEntityDef* def, int _weaponIndex, bool _isStrogg ) {
|
|
int i;
|
|
|
|
viewModel = _owner->GetWeaponViewModel( );
|
|
worldModel = _owner->GetWeaponWorldModel( );
|
|
weaponDef = def;
|
|
owner = _owner;
|
|
scriptObject = &viewModel->scriptObject;
|
|
weaponIndex = _weaponIndex;
|
|
mods = owner->inventory.weaponMods[ weaponIndex ];
|
|
isStrogg = _isStrogg;
|
|
|
|
spawnArgs = weaponDef->dict;
|
|
|
|
#ifdef _XENON
|
|
aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" );
|
|
#endif
|
|
|
|
// Apply the mod dictionaries
|
|
for ( i = 0; i < MAX_WEAPONMODS; i ++ ) {
|
|
const idDict* modDict;
|
|
if ( !(mods & (1<<i) ) ) {
|
|
continue;
|
|
}
|
|
|
|
const char* mod;
|
|
if ( !spawnArgs.GetString ( va("def_mod%d", i+1), "", &mod ) || !*mod ) {
|
|
continue;
|
|
}
|
|
|
|
modDict = gameLocal.FindEntityDefDict ( mod, false );
|
|
if ( !modDict ) {
|
|
continue;
|
|
}
|
|
|
|
spawnArgs.Copy ( *modDict );
|
|
}
|
|
|
|
// Associate the weapon with the view model
|
|
viewModel->weapon = this;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::FindViewModelPositionStyle
|
|
================
|
|
*/
|
|
void rvWeapon::FindViewModelPositionStyle( idVec3& viewOffset, idAngles& viewAngles ) const {
|
|
int viewStyle = g_gunViewStyle.GetInteger();
|
|
const char* styleDefName = spawnArgs.GetString( va("def_viewStyle%d", viewStyle) );
|
|
const idDict* styleDef = gameLocal.FindEntityDefDict( styleDefName, false );
|
|
if( !styleDef ) {
|
|
styleDefName = spawnArgs.GetString( "def_viewStyle" );
|
|
styleDef = gameLocal.FindEntityDefDict( styleDefName, false );
|
|
}
|
|
assert( styleDef );
|
|
|
|
viewAngles = styleDef->GetAngles( "viewangles" );
|
|
viewOffset = styleDef->GetVector( "viewoffset" );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Spawn
|
|
================
|
|
*/
|
|
void rvWeapon::Spawn ( void ) {
|
|
|
|
memset ( &wsfl, 0, sizeof(wsfl) );
|
|
memset ( &wfl, 0, sizeof(wfl) );
|
|
|
|
// RAVEN BEGIN
|
|
// nrausch:
|
|
#if defined(_XENON)
|
|
aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" );
|
|
#endif
|
|
// RAVEN END
|
|
|
|
// Initialize variables
|
|
projectileEnt = NULL;
|
|
kick_endtime = 0;
|
|
hideStart = 0.0f;
|
|
hideEnd = 0.0f;
|
|
hideOffset = 0.0f;
|
|
status = WP_HOLSTERED;
|
|
lastAttack = 0;
|
|
clipPredictTime = 0;
|
|
|
|
muzzleAxis.Identity();
|
|
muzzleOrigin.Zero();
|
|
pushVelocity.Zero();
|
|
playerViewAxis.Identity();
|
|
playerViewOrigin.Zero();
|
|
viewModelAxis.Identity();
|
|
viewModelOrigin.Zero();
|
|
|
|
// View
|
|
viewModelForeshorten = spawnArgs.GetFloat ( "foreshorten", "1" );
|
|
|
|
FindViewModelPositionStyle( viewModelOffset, viewModelAngles );
|
|
|
|
// Offsets
|
|
weaponAngleOffsetAverages = spawnArgs.GetInt( "weaponAngleOffsetAverages", "10" );
|
|
weaponAngleOffsetScale = spawnArgs.GetFloat( "weaponAngleOffsetScale", "0.25" );
|
|
weaponAngleOffsetMax = spawnArgs.GetFloat( "weaponAngleOffsetMax", "10" );
|
|
weaponOffsetTime = spawnArgs.GetFloat( "weaponOffsetTime", "400" );
|
|
weaponOffsetScale = spawnArgs.GetFloat( "weaponOffsetScale", "0.005" );
|
|
|
|
fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate" ) );
|
|
altFireRate = SEC2MS ( spawnArgs.GetFloat ( "altFireRate" ) );
|
|
if( altFireRate == 0 ) {
|
|
altFireRate = fireRate;
|
|
}
|
|
spread = (gameLocal.IsMultiplayer()&&spawnArgs.FindKey("spread_mp"))?spawnArgs.GetFloat ( "spread_mp" ):spawnArgs.GetFloat ( "spread" );
|
|
nextAttackTime = 0;
|
|
|
|
// Zoom
|
|
zoomFov = spawnArgs.GetInt( "zoomFov", "-1" );
|
|
zoomGui = uiManager->FindGui ( spawnArgs.GetString ( "gui_zoom", "" ), true );
|
|
zoomTime = spawnArgs.GetFloat ( "zoomTime", ".15" );
|
|
wfl.zoomHideCrosshair = spawnArgs.GetBool ( "zoomHideCrosshair", "1" );
|
|
|
|
// Attack related values
|
|
muzzle_kick_time = SEC2MS( spawnArgs.GetFloat( "muzzle_kick_time" ) );
|
|
muzzle_kick_maxtime = SEC2MS( spawnArgs.GetFloat( "muzzle_kick_maxtime" ) );
|
|
muzzle_kick_angles = spawnArgs.GetAngles( "muzzle_kick_angles" );
|
|
muzzle_kick_offset = spawnArgs.GetVector( "muzzle_kick_offset" );
|
|
|
|
// General weapon properties
|
|
wfl.silent_fire = spawnArgs.GetBool( "silent_fire" );
|
|
wfl.hasWindupAnim = spawnArgs.GetBool( "has_windup", "0" );
|
|
icon = spawnArgs.GetString( "mtr_icon" );
|
|
hideTime = SEC2MS( weaponDef->dict.GetFloat( "hide_time", "0.3" ) );
|
|
hideDistance = weaponDef->dict.GetFloat( "hide_distance", "-15" );
|
|
hideStartTime = gameLocal.time - hideTime;
|
|
muzzleOffset = weaponDef->dict.GetFloat ( "muzzleOffset", "14" );
|
|
|
|
// Ammo
|
|
clipSize = spawnArgs.GetInt( "clipSize" );
|
|
ammoRequired = spawnArgs.GetInt( "ammoRequired" );
|
|
lowAmmo = spawnArgs.GetInt( "lowAmmo" );
|
|
ammoType = GetAmmoIndexForName( spawnArgs.GetString( "ammoType" ) );
|
|
maxAmmo = owner->inventory.MaxAmmoForAmmoClass ( owner, GetAmmoNameForIndex ( ammoType ) );
|
|
|
|
if ( ( ammoType < 0 ) || ( ammoType >= MAX_AMMO ) ) {
|
|
gameLocal.Warning( "Unknown ammotype for class '%s'", this->GetClassname ( ) );
|
|
}
|
|
|
|
// If the weapon has a clip, then fill it up
|
|
ammoClip = owner->inventory.clip[weaponIndex];
|
|
if ( ( ammoClip < 0 ) || ( ammoClip > clipSize ) ) {
|
|
// first time using this weapon so have it fully loaded to start
|
|
ammoClip = clipSize;
|
|
int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired );
|
|
if ( ammoClip > ammoAvail ) {
|
|
ammoClip = ammoAvail;
|
|
}
|
|
}
|
|
|
|
// Complex initializations Initialize
|
|
InitDefs( );
|
|
InitWorldModel( );
|
|
InitViewModel( );
|
|
|
|
// Requires the view model so must be done after it
|
|
InitLights( );
|
|
|
|
viewModel->PostGUIEvent( "weapon_init" );
|
|
viewModel->PostGUIEvent( "weapon_ammo" );
|
|
if ( ammoClip == 0 && AmmoAvailable() == 0 ) {
|
|
viewModel->PostGUIEvent( "weapon_noammo" );
|
|
}
|
|
|
|
stateThread.SetName( va("%s_%s_%s", owner->GetName(), viewModel->GetName ( ), spawnArgs.GetString("classname") ) );
|
|
stateThread.SetOwner( this );
|
|
|
|
forceGUIReload = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::InitViewModel
|
|
================
|
|
*/
|
|
void rvWeapon::InitViewModel( void ) {
|
|
const char* guiName;
|
|
const char* temp;
|
|
int i;
|
|
const idKeyValue* kv;
|
|
|
|
// Reset view model to clean state
|
|
viewModel->Clear ( );
|
|
// Make sure the sound handle is initted
|
|
viewModel->refSound.referenceSoundHandle = -1;
|
|
|
|
// Intialize the weapon guis
|
|
if ( spawnArgs.GetString ( "gui", "", &guiName ) ) {
|
|
int g = 0;
|
|
do {
|
|
viewModel->GetRenderEntity()->gui[g++] = uiManager->FindGui ( guiName, true, false, true );
|
|
guiName = spawnArgs.GetString ( va("gui%d", g + 1 ) );
|
|
} while ( *guiName && viewModel->GetRenderEntity()->gui[g-1] );
|
|
}
|
|
|
|
// Set the view models spawn args
|
|
viewModel->spawnArgs = weaponDef->dict;
|
|
|
|
// Set the model for the view model
|
|
if ( isStrogg ) {
|
|
temp = spawnArgs.GetString ( "model_view_strogg", spawnArgs.GetString ( "model_view" ) );
|
|
} else {
|
|
temp = spawnArgs.GetString ( "model_view" );
|
|
}
|
|
viewModel->SetModel( temp, mods );
|
|
|
|
// Hide surfaces
|
|
for ( kv = spawnArgs.MatchPrefix ( "hidesurface", NULL );
|
|
kv;
|
|
kv = spawnArgs.MatchPrefix ( "hidesurface", kv ) ) {
|
|
viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() );
|
|
}
|
|
|
|
// Show and Hide the mods
|
|
for ( i = 0; i < MAX_WEAPONMODS; i ++ ) {
|
|
const idDict* modDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_mod%d", i+1) ), false );
|
|
if ( !modDict ) {
|
|
continue;
|
|
}
|
|
|
|
// Hide any show surfaces for mods that arent on
|
|
if ( !(mods & (1<<i)) ) {
|
|
for ( kv = modDict->MatchPrefix ( "mod_showsurface", NULL );
|
|
kv;
|
|
kv = modDict->MatchPrefix ( "mod_showsurface", kv ) ) {
|
|
viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); // NOTE: HIDING them because we don't have this mod yet
|
|
}
|
|
} else {
|
|
for ( kv = modDict->MatchPrefix ( "mod_hidesurface", NULL );
|
|
kv;
|
|
kv = modDict->MatchPrefix ( "mod_hidesurface", kv ) ) {
|
|
viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() );
|
|
}
|
|
}
|
|
}
|
|
|
|
// find some joints in the model for locating effects
|
|
viewAnimator = viewModel->GetAnimator ( );
|
|
barrelJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_barrel", "barrel" ) );
|
|
flashJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_flash", "flash" ) );
|
|
ejectJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) );
|
|
guiLightJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_guiLight", "guiLight" ) );
|
|
flashlightJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_flashlight", "flashlight" ) );
|
|
|
|
// Eject offset
|
|
spawnArgs.GetVector ( "ejectOffset", "0 0 0", ejectOffset );
|
|
|
|
// Setup a skin for the view model
|
|
if ( spawnArgs.GetString ( "skin", "", &temp ) ) {
|
|
viewModel->GetRenderEntity()->customSkin = declManager->FindSkin ( temp );
|
|
}
|
|
|
|
// make sure we have the correct skin
|
|
viewModel->UpdateSkin();
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::InitLights
|
|
================
|
|
*/
|
|
void rvWeapon::InitLights ( void ) {
|
|
const char* shader;
|
|
idVec4 color;
|
|
renderLight_t* light;
|
|
|
|
memset ( lights, 0, sizeof(lights) );
|
|
memset ( lightHandles, -1, sizeof(lightHandles) );
|
|
|
|
// setup gui light
|
|
light = &lights[WPLIGHT_GUI];
|
|
shader = spawnArgs.GetString( "mtr_guiLightShader", "" );
|
|
if ( shader && *shader && viewModel->GetRenderEntity()->gui[0] ) {
|
|
light->shader = declManager->FindMaterial( shader, false );
|
|
light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = spawnArgs.GetFloat("glightRadius", "3" );
|
|
color = viewModel->GetRenderEntity()->gui[0]->GetLightColor ( );
|
|
light->shaderParms[ SHADERPARM_RED ] = color[0] * color[3];
|
|
light->shaderParms[ SHADERPARM_GREEN ] = color[1] * color[3];
|
|
light->shaderParms[ SHADERPARM_BLUE ] = color[2] * color[3];
|
|
light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
|
|
light->pointLight = true;
|
|
// RAVEN BEGIN
|
|
// dluetscher: added detail levels to render lights
|
|
light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL;
|
|
// dluetscher: changed lights to no shadow for performance reasons
|
|
light->noShadows = true;
|
|
// RAVEN END
|
|
light->lightId = WPLIGHT_GUI * 100 + owner->entityNumber;
|
|
light->allowLightInViewID = owner->entityNumber+1;
|
|
spawnArgs.GetVector ( "glightOffset", "0 0 0", guiLightOffset );
|
|
}
|
|
|
|
// Muzzle flash
|
|
light = &lights[WPLIGHT_MUZZLEFLASH];
|
|
shader = spawnArgs.GetString( "mtr_flashShader", "muzzleflash" );
|
|
if ( shader && *shader ) {
|
|
light->shader = declManager->FindMaterial( shader, false );
|
|
spawnArgs.GetVec4( "flashColor", "0 0 0 0", color );
|
|
light->shaderParms[ SHADERPARM_RED ] = color[0];
|
|
light->shaderParms[ SHADERPARM_GREEN ] = color[1];
|
|
light->shaderParms[ SHADERPARM_BLUE ] = color[2];
|
|
light->shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f;
|
|
light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = (float)spawnArgs.GetInt( "flashRadius" );
|
|
// RAVEN BEGIN
|
|
// dluetscher: added detail levels to render lights
|
|
light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL;
|
|
// dluetscher: changed lights to no shadow for performance reasons
|
|
light->noShadows = true;
|
|
// RAVEN END
|
|
light->pointLight = spawnArgs.GetBool( "flashPointLight", "1" );
|
|
if ( !light->pointLight ) {
|
|
light->target = spawnArgs.GetVector ( "flashTarget" );
|
|
light->up = spawnArgs.GetVector ( "flashUp" );
|
|
light->right = spawnArgs.GetVector ( "flashRight" );
|
|
light->end = light->target;
|
|
}
|
|
light->lightId = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber;
|
|
light->allowLightInViewID = owner->entityNumber+1;
|
|
muzzleFlashTime = SEC2MS( spawnArgs.GetFloat( "flashTime", "0.25" ) );
|
|
muzzleFlashEnd = 0;
|
|
spawnArgs.GetVector ( "flashViewOffset", "0 0 0", muzzleFlashViewOffset );
|
|
}
|
|
|
|
// the world muzzle flash is the same, just positioned differently
|
|
lights[WPLIGHT_MUZZLEFLASH_WORLD] = lights[WPLIGHT_MUZZLEFLASH];
|
|
light = &lights[WPLIGHT_MUZZLEFLASH_WORLD];
|
|
light->suppressLightInViewID = owner->entityNumber+1;
|
|
light->allowLightInViewID = 0;
|
|
light->lightId = WPLIGHT_MUZZLEFLASH_WORLD * 100 + owner->entityNumber;
|
|
|
|
// flashlight
|
|
light = &lights[WPLIGHT_FLASHLIGHT];
|
|
shader = spawnArgs.GetString( "mtr_flashlightShader", "lights/muzzleflash" );
|
|
if ( shader && *shader ) {
|
|
light->shader = declManager->FindMaterial( shader, false );
|
|
spawnArgs.GetVec4( "flashlightColor", "0 0 0 0", color );
|
|
light->shaderParms[ SHADERPARM_RED ] = color[0];
|
|
light->shaderParms[ SHADERPARM_GREEN ] = color[1];
|
|
light->shaderParms[ SHADERPARM_BLUE ] = color[2];
|
|
light->shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f;
|
|
light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] =
|
|
(float)spawnArgs.GetInt( "flashlightRadius" );
|
|
// RAVEN BEGIN
|
|
// dluetscher: added detail levels to render lights
|
|
light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL;
|
|
// dluetscher: changed lights to no shadow for performance reasons
|
|
light->noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3;
|
|
// RAVEN END
|
|
light->pointLight = spawnArgs.GetBool( "flashlightPointLight", "1" );
|
|
if ( !light->pointLight ) {
|
|
light->target = spawnArgs.GetVector( "flashlightTarget" );
|
|
light->up = spawnArgs.GetVector( "flashlightUp" );
|
|
light->right = spawnArgs.GetVector( "flashlightRight" );
|
|
light->end = light->target;
|
|
}
|
|
|
|
light->allowLightInViewID = owner->entityNumber+1;
|
|
light->lightId = WPLIGHT_FLASHLIGHT * 100 + owner->entityNumber;
|
|
spawnArgs.GetVector ( "flashlightViewOffset", "0 0 0", flashlightViewOffset );
|
|
}
|
|
|
|
// the world muzzle flashlight is the same, just positioned differently
|
|
lights[WPLIGHT_FLASHLIGHT_WORLD] = lights[WPLIGHT_FLASHLIGHT];
|
|
light = &lights[WPLIGHT_FLASHLIGHT_WORLD];
|
|
light->suppressLightInViewID = owner->entityNumber+1;
|
|
light->allowLightInViewID = 0;
|
|
light->lightId = WPLIGHT_FLASHLIGHT_WORLD * 100 + owner->entityNumber;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::InitDefs
|
|
================
|
|
*/
|
|
void rvWeapon::InitDefs( void ) {
|
|
const char* name;
|
|
const idDeclEntityDef* def;
|
|
const char* spawnclass;
|
|
idTypeInfo* cls;
|
|
|
|
// get the projectile
|
|
attackDict.Clear();
|
|
|
|
// Projectile
|
|
if ( spawnArgs.GetString( "def_projectile", "", &name ) && *name ) {
|
|
def = gameLocal.FindEntityDef( name, false );
|
|
if ( !def ) {
|
|
gameLocal.Warning( "Unknown projectile '%s' for weapon '%s'", name, weaponDef->GetName() );
|
|
} else {
|
|
spawnclass = def->dict.GetString( "spawnclass" );
|
|
cls = idClass::GetClass( spawnclass );
|
|
if ( !cls || !cls->IsType( idProjectile::GetClassType() ) ) {
|
|
gameLocal.Warning( "Invalid spawnclass '%s' for projectile '%s' (used by weapon '%s')", spawnclass, name, weaponDef->GetName ( ) );
|
|
} else {
|
|
attackDict = def->dict;
|
|
}
|
|
}
|
|
} else if ( spawnArgs.GetString( "def_hitscan", "", &name ) && *name ) {
|
|
def = gameLocal.FindEntityDef( name, false );
|
|
if ( !def ) {
|
|
gameLocal.Warning( "Unknown hitscan '%s' for weapon '%s'", name, weaponDef->GetName ( ) );
|
|
} else {
|
|
attackDict = def->dict;
|
|
hitscanAttackDef = def->Index();
|
|
}
|
|
wfl.attackHitscan = true;
|
|
}
|
|
|
|
// Alternate projectile
|
|
attackAltDict.Clear ();
|
|
if ( spawnArgs.GetString( "def_altprojectile", "", &name ) && *name ) {
|
|
def = gameLocal.FindEntityDef( name, false );
|
|
if ( !def ) {
|
|
gameLocal.Warning( "Unknown alt projectile '%s' for weapon '%s'", name, weaponDef->GetName() );
|
|
} else {
|
|
spawnclass = def->dict.GetString( "spawnclass" );
|
|
cls = idClass::GetClass( spawnclass );
|
|
if ( !cls || !cls->IsType( idProjectile::GetClassType() ) ) {
|
|
gameLocal.Warning( "Invalid spawnclass '%s' for alt projectile '%s' (used by weapon '%s')", spawnclass, name, weaponDef->GetName ( ) );
|
|
} else {
|
|
attackAltDict = def->dict;
|
|
}
|
|
}
|
|
} else if ( spawnArgs.GetString( "def_althitscan", "", &name ) && *name ) {
|
|
def = gameLocal.FindEntityDef( name, false );
|
|
if ( !def ) {
|
|
gameLocal.Warning( "Unknown hitscan '%s' for weapon '%s'", name, weaponDef->GetName ( ) );
|
|
} else {
|
|
attackAltDict = def->dict;
|
|
}
|
|
wfl.attackAltHitscan = true;
|
|
}
|
|
|
|
// get the melee damage def
|
|
meleeDistance = spawnArgs.GetFloat( "melee_distance" );
|
|
if ( spawnArgs.GetString( "def_melee", "", &name ) && *name ) {
|
|
meleeDef = gameLocal.FindEntityDef( name, false );
|
|
if ( !meleeDef ) {
|
|
gameLocal.Error( "Unknown melee '%s' for weapon '%s'", name, weaponDef->GetName() );
|
|
}
|
|
} else {
|
|
meleeDef = NULL;
|
|
}
|
|
|
|
// get the brass def
|
|
brassDict.Clear();
|
|
if ( spawnArgs.GetString( "def_ejectBrass", "", &name ) && *name ) {
|
|
def = gameLocal.FindEntityDef( name, false );
|
|
if ( !def ) {
|
|
gameLocal.Warning( "Unknown brass def '%s' for weapon '%s'", name, weaponDef->GetName() );
|
|
} else {
|
|
brassDict = def->dict;
|
|
// force any brass to spawn as client moveable
|
|
brassDict.Set( "spawnclass", "rvClientMoveable" );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Think
|
|
================
|
|
*/
|
|
void rvWeapon::Think ( void ) {
|
|
|
|
// Cache the player origin and axis
|
|
playerViewOrigin = owner->firstPersonViewOrigin;
|
|
playerViewAxis = owner->firstPersonViewAxis;
|
|
|
|
// calculate weapon position based on player movement bobbing
|
|
owner->CalculateViewWeaponPos( viewModelOrigin, viewModelAxis );
|
|
|
|
// hide offset is for dropping the gun when approaching a GUI or NPC
|
|
// This is simpler to manage than doing the weapon put-away animation
|
|
if ( gameLocal.time - hideStartTime < hideTime ) {
|
|
float frac = ( float )( gameLocal.time - hideStartTime ) / ( float )hideTime;
|
|
if ( hideStart < hideEnd ) {
|
|
frac = 1.0f - frac;
|
|
frac = 1.0f - frac * frac;
|
|
} else {
|
|
frac = frac * frac;
|
|
}
|
|
hideOffset = hideStart + ( hideEnd - hideStart ) * frac;
|
|
} else {
|
|
hideOffset = hideEnd;
|
|
}
|
|
viewModelOrigin += hideOffset * viewModelAxis[ 2 ];
|
|
|
|
// kick up based on repeat firing
|
|
MuzzleRise( viewModelOrigin, viewModelAxis );
|
|
|
|
if ( viewModel ) {
|
|
// set the physics position and orientation
|
|
viewModel->GetPhysics()->SetOrigin( viewModelOrigin );
|
|
viewModel->GetPhysics()->SetAxis( viewModelAxis );
|
|
viewModel->UpdateVisuals();
|
|
} else {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
}
|
|
|
|
// Update the zoom variable before updating the script
|
|
wsfl.zoom = owner->IsZoomed( );
|
|
|
|
// Only update the state loop on new frames
|
|
if ( gameLocal.isNewFrame ) {
|
|
stateThread.Execute( );
|
|
}
|
|
|
|
if ( viewModel ) {
|
|
viewModel->UpdateAnimation( );
|
|
}
|
|
|
|
// Clear reload and flashlight flags
|
|
wsfl.reload = false;
|
|
wsfl.flashlight = false;
|
|
|
|
// deal with the third-person visible world model
|
|
// don't show shadows of the world model in first person
|
|
if ( worldModel && worldModel->GetRenderEntity() ) {
|
|
// always show your own weapon
|
|
if( owner->entityNumber == gameLocal.localClientNum ) {
|
|
worldModel->GetRenderEntity()->suppressLOD = 1;
|
|
} else {
|
|
worldModel->GetRenderEntity()->suppressLOD = 0;
|
|
}
|
|
|
|
if ( gameLocal.IsMultiplayer() && g_skipPlayerShadowsMP.GetBool() ) {
|
|
// Disable all weapon shadows for the local client
|
|
worldModel->GetRenderEntity()->suppressShadowInViewID = gameLocal.localClientNum+1;
|
|
worldModel->GetRenderEntity()->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber;
|
|
} else if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) {
|
|
// Show all weapon shadows
|
|
worldModel->GetRenderEntity()->suppressShadowInViewID = 0;
|
|
} else {
|
|
// Only show weapon shadows for other clients
|
|
worldModel->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber+1;
|
|
worldModel->GetRenderEntity()->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber;
|
|
}
|
|
}
|
|
|
|
UpdateGUI();
|
|
|
|
// Update lights
|
|
UpdateFlashlight ( );
|
|
UpdateMuzzleFlash ( );
|
|
|
|
// update the gui light
|
|
renderLight_t& light = lights[WPLIGHT_GUI];
|
|
if ( light.lightRadius[0] && guiLightJointView != INVALID_JOINT ) {
|
|
if ( viewModel ) {
|
|
idVec4 color = viewModel->GetRenderEntity()->gui[0]->GetLightColor ( );
|
|
light.shaderParms[ SHADERPARM_RED ] = color[0] * color[3];
|
|
light.shaderParms[ SHADERPARM_GREEN ] = color[1] * color[3];
|
|
light.shaderParms[ SHADERPARM_BLUE ] = color[2] * color[3];
|
|
GetGlobalJointTransform( true, guiLightJointView, light.origin, light.axis, guiLightOffset );
|
|
UpdateLight ( WPLIGHT_GUI );
|
|
} else {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
}
|
|
}
|
|
|
|
// Alert Monsters if the flashlight is one or a muzzle flash is active?
|
|
if ( !gameLocal.isMultiplayer ) {
|
|
if ( !owner->fl.notarget && (lightHandles[WPLIGHT_MUZZLEFLASH] != -1 || lightHandles[WPLIGHT_FLASHLIGHT] != -1 ) ) {
|
|
AlertMonsters ( );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::InitWorldModel
|
|
================
|
|
*/
|
|
void rvWeapon::InitWorldModel( void ) {
|
|
idEntity *ent;
|
|
|
|
ent = worldModel;
|
|
if ( !ent ) {
|
|
gameLocal.Warning ( "InitWorldModel failed due to missing entity" );
|
|
return;
|
|
}
|
|
|
|
const char *model = spawnArgs.GetString( "model_world" );
|
|
const char *attach = spawnArgs.GetString( "joint_attach" );
|
|
|
|
if ( model[0] && attach[0] ) {
|
|
ent->Show();
|
|
ent->SetModel( model );
|
|
ent->GetPhysics()->SetContents( 0 );
|
|
ent->GetPhysics()->SetClipModel( NULL, 1.0f );
|
|
ent->BindToJoint( owner, attach, true );
|
|
ent->GetPhysics()->SetOrigin( vec3_origin );
|
|
ent->GetPhysics()->SetAxis( mat3_identity );
|
|
|
|
// supress model in player views, but allow it in mirrors and remote views
|
|
renderEntity_t *worldModelRenderEntity = ent->GetRenderEntity();
|
|
if ( worldModelRenderEntity ) {
|
|
worldModelRenderEntity->suppressSurfaceInViewID = owner->entityNumber+1;
|
|
worldModelRenderEntity->suppressShadowInViewID = owner->entityNumber+1;
|
|
worldModelRenderEntity->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber;
|
|
}
|
|
} else {
|
|
ent->SetModel( "" );
|
|
ent->Hide();
|
|
}
|
|
|
|
// the renderEntity is reused, so the relevant fields (except this one) appear to be correctly reinitialized
|
|
worldModel->GetRenderEntity()->suppressSurfaceMask = 0;
|
|
|
|
// Cache the world joints
|
|
worldAnimator = ent->GetAnimator ( );
|
|
flashJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_flash", "flash" ) );
|
|
flashlightJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_flashlight", "flashlight" ) );
|
|
ejectJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_eject", "eject" ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::SetState
|
|
================
|
|
*/
|
|
void rvWeapon::SetState( const char *statename, int blendFrames ) {
|
|
stateThread.SetState( statename, blendFrames );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::PostState
|
|
================
|
|
*/
|
|
void rvWeapon::PostState( const char* statename, int blendFrames ) {
|
|
stateThread.PostState( statename, blendFrames );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvWeapon::ExecuteState
|
|
=====================
|
|
*/
|
|
void rvWeapon::ExecuteState ( const char* statename ) {
|
|
SetState ( statename, 0 );
|
|
stateThread.Execute ( );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::UpdateLight
|
|
================
|
|
*/
|
|
void rvWeapon::UpdateLight ( int lightID ) {
|
|
if ( lightHandles[lightID] == -1 ) {
|
|
lightHandles[lightID] = gameRenderWorld->AddLightDef ( &lights[lightID] );
|
|
} else {
|
|
gameRenderWorld->UpdateLightDef( lightHandles[lightID], &lights[lightID] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::FreeLight
|
|
================
|
|
*/
|
|
void rvWeapon::FreeLight ( int lightID ) {
|
|
if ( lightHandles[lightID] != -1 ) {
|
|
gameRenderWorld->FreeLightDef( lightHandles[lightID] );
|
|
lightHandles[lightID] = -1;
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
Networking
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
rvWeapon::WriteToSnapshot
|
|
================
|
|
*/
|
|
void rvWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const {
|
|
msg.WriteBits( ammoClip, ASYNC_PLAYER_INV_CLIP_BITS );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::ReadFromSnapshot
|
|
================
|
|
*/
|
|
void rvWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) {
|
|
ammoClip = msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::SkipFromSnapshot
|
|
================
|
|
*/
|
|
void rvWeapon::SkipFromSnapshot ( const idBitMsgDelta &msg ) {
|
|
msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::ClientStale
|
|
================
|
|
*/
|
|
void rvWeapon::ClientStale( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::ClientReceiveEvent
|
|
================
|
|
*/
|
|
bool rvWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
|
|
switch( event ) {
|
|
case EVENT_RELOAD: {
|
|
if ( gameLocal.time - time < 1000 ) {
|
|
wsfl.netReload = true;
|
|
wsfl.netEndReload = false;
|
|
}
|
|
return true;
|
|
}
|
|
case EVENT_ENDRELOAD: {
|
|
wsfl.netEndReload = true;
|
|
return true;
|
|
}
|
|
case EVENT_CHANGESKIN: {
|
|
/*
|
|
// FIXME: use idGameLocal::ReadDecl
|
|
int index = msg.ReadLong();
|
|
renderEntity.customSkin = ( index != -1 ) ? static_cast<const idDeclSkin *>( declManager->DeclByIndex( DECL_SKIN, index ) ) : NULL;
|
|
UpdateVisuals();
|
|
if ( worldModel.GetEntity() ) {
|
|
worldModel.GetEntity()->SetSkin( renderEntity.customSkin );
|
|
}
|
|
*/
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Save / Load
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Save
|
|
================
|
|
*/
|
|
void rvWeapon::Save ( idSaveGame *savefile ) const {
|
|
int i;
|
|
|
|
// Flags
|
|
savefile->Write ( &wsfl, sizeof( wsfl ) );
|
|
savefile->Write ( &wfl, sizeof( wfl ) );
|
|
|
|
// Write all cached joints
|
|
savefile->WriteJoint ( barrelJointView );
|
|
savefile->WriteJoint ( flashJointView );
|
|
savefile->WriteJoint ( ejectJointView );
|
|
savefile->WriteJoint ( guiLightJointView );
|
|
savefile->WriteJoint ( flashlightJointView );
|
|
|
|
savefile->WriteJoint ( flashJointWorld );
|
|
savefile->WriteJoint ( ejectJointWorld );
|
|
savefile->WriteJoint ( flashlightJointWorld );
|
|
|
|
savefile->WriteInt ( status );
|
|
savefile->WriteInt ( lastAttack );
|
|
|
|
// Hide / Show
|
|
savefile->WriteInt ( hideTime );
|
|
savefile->WriteFloat ( hideDistance );
|
|
savefile->WriteInt ( hideStartTime );
|
|
savefile->WriteFloat ( hideStart );
|
|
savefile->WriteFloat ( hideEnd );
|
|
savefile->WriteFloat ( hideOffset );
|
|
|
|
// Write attack related values
|
|
savefile->WriteVec3 ( pushVelocity );
|
|
savefile->WriteInt ( kick_endtime );
|
|
savefile->WriteInt ( muzzle_kick_time );
|
|
savefile->WriteInt ( muzzle_kick_maxtime );
|
|
savefile->WriteAngles ( muzzle_kick_angles );
|
|
savefile->WriteVec3 ( muzzle_kick_offset );
|
|
savefile->WriteVec3 ( muzzleOrigin );
|
|
savefile->WriteMat3 ( muzzleAxis );
|
|
savefile->WriteFloat ( muzzleOffset );
|
|
projectileEnt.Save ( savefile );
|
|
savefile->WriteVec3 ( ejectOffset ); // cnicholson: Added unsaved var
|
|
|
|
savefile->WriteInt ( fireRate );
|
|
savefile->WriteFloat ( spread );
|
|
// savefile->WriteInt ( nextAttackTime ); // cnicholson: This is set to 0 in restore, so don't save it
|
|
|
|
// cnicholson: These 3 idDicts are setup during restore, no need to save them.
|
|
// TOSAVE: idDict attackAltDict;
|
|
// TOSAVE: idDict attackDict;
|
|
// TOSAVE: idDict brassDict;
|
|
|
|
// Defs
|
|
// TOSAVE: const idDeclEntityDef * meleeDef; // cnicholson: This is setup in restore, so don't save it
|
|
savefile->WriteFloat ( meleeDistance );
|
|
|
|
// Zoom
|
|
savefile->WriteInt ( zoomFov );
|
|
savefile->WriteUserInterface ( zoomGui, true );
|
|
savefile->WriteFloat ( zoomTime );
|
|
|
|
// Lights
|
|
for ( i = 0; i < WPLIGHT_MAX; i ++ ) {
|
|
savefile->WriteInt( lightHandles[i] );
|
|
savefile->WriteRenderLight( lights[i] );
|
|
}
|
|
savefile->WriteVec3 ( guiLightOffset );
|
|
savefile->WriteInt ( muzzleFlashEnd );
|
|
savefile->WriteInt ( muzzleFlashTime );
|
|
savefile->WriteVec3 ( muzzleFlashViewOffset );
|
|
savefile->WriteVec3 ( flashlightViewOffset );
|
|
savefile->WriteBool ( flashlightOn ); // cnicholson: Added unsaved var
|
|
savefile->WriteVec3 ( flashlightViewOffset ); // cnicholson: Added unsaved var
|
|
|
|
// Write ammo values
|
|
savefile->WriteInt ( ammoType );
|
|
savefile->WriteInt ( ammoRequired );
|
|
savefile->WriteInt ( clipSize );
|
|
savefile->WriteInt ( ammoClip );
|
|
savefile->WriteInt ( lowAmmo );
|
|
savefile->WriteInt ( maxAmmo );
|
|
|
|
// multiplayer
|
|
savefile->WriteInt ( clipPredictTime ); // TOSAVE: Save MP value?
|
|
|
|
// View
|
|
savefile->WriteVec3 ( playerViewOrigin );
|
|
savefile->WriteMat3 ( playerViewAxis );
|
|
|
|
savefile->WriteVec3 ( viewModelOrigin );
|
|
savefile->WriteMat3 ( viewModelAxis );
|
|
savefile->WriteAngles ( viewModelAngles );
|
|
savefile->WriteVec3 ( viewModelOffset ); // cnicholson: Added unsaved var
|
|
|
|
// Offsets
|
|
savefile->WriteInt ( weaponAngleOffsetAverages );
|
|
savefile->WriteFloat ( weaponAngleOffsetScale );
|
|
savefile->WriteFloat ( weaponAngleOffsetMax );
|
|
savefile->WriteFloat ( weaponOffsetTime );
|
|
savefile->WriteFloat ( weaponOffsetScale );
|
|
|
|
savefile->WriteString ( icon );
|
|
savefile->WriteBool ( isStrogg );
|
|
|
|
// TOSAVE: idDict spawnArgs;
|
|
|
|
// TOSAVE: idEntityPtr<rvViewWeapon> viewModel; // cnicholson: Setup in restore, no need to save
|
|
// TOSAVE: idAnimator* viewAnimator;
|
|
// TOSAVE: idEntityPtr<idAnimatedEntity> worldModel; // cnicholson: Setup in restore, no need to save
|
|
// TOSAVE: idAnimator* worldAnimator;
|
|
// TOSAVE: const idDeclEntityDef* weaponDef;
|
|
// TOSAVE: idScriptObject* scriptObject;
|
|
savefile->WriteObject ( owner );
|
|
savefile->WriteInt ( weaponIndex ); // cnicholson: Added unsaved var
|
|
savefile->WriteInt ( mods ); // cnicholson: Added unsaved var
|
|
|
|
savefile->WriteFloat ( viewModelForeshorten );
|
|
|
|
stateThread.Save( savefile );
|
|
|
|
for ( i = 0; i < ANIM_NumAnimChannels; i++ ) {
|
|
savefile->WriteInt( animDoneTime[i] );
|
|
}
|
|
|
|
savefile->WriteInt ( methodOfDeath ); // cnicholson: Added unsaved var
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Restore
|
|
================
|
|
*/
|
|
void rvWeapon::Restore ( idRestoreGame *savefile ) {
|
|
int i;
|
|
const idDeclEntityDef* def;
|
|
|
|
// General
|
|
savefile->Read ( &wsfl, sizeof( wsfl ) );
|
|
savefile->Read ( &wfl, sizeof( wfl ) );
|
|
|
|
// Read cached joints
|
|
savefile->ReadJoint ( barrelJointView );
|
|
savefile->ReadJoint ( flashJointView );
|
|
savefile->ReadJoint ( ejectJointView );
|
|
savefile->ReadJoint ( guiLightJointView );
|
|
savefile->ReadJoint ( flashlightJointView );
|
|
|
|
savefile->ReadJoint ( flashJointWorld );
|
|
savefile->ReadJoint ( ejectJointWorld );
|
|
savefile->ReadJoint ( flashlightJointWorld );
|
|
|
|
savefile->ReadInt ( (int&)status );
|
|
savefile->ReadInt ( lastAttack );
|
|
|
|
// Hide / Show
|
|
savefile->ReadInt ( hideTime );
|
|
savefile->ReadFloat ( hideDistance );
|
|
savefile->ReadInt ( hideStartTime );
|
|
savefile->ReadFloat ( hideStart );
|
|
savefile->ReadFloat ( hideEnd );
|
|
savefile->ReadFloat ( hideOffset );
|
|
|
|
// Read attack related values
|
|
savefile->ReadVec3 ( pushVelocity );
|
|
savefile->ReadInt ( kick_endtime );
|
|
savefile->ReadInt ( muzzle_kick_time );
|
|
savefile->ReadInt ( muzzle_kick_maxtime );
|
|
savefile->ReadAngles ( muzzle_kick_angles );
|
|
savefile->ReadVec3 ( muzzle_kick_offset );
|
|
savefile->ReadVec3 ( muzzleOrigin );
|
|
savefile->ReadMat3 ( muzzleAxis );
|
|
savefile->ReadFloat ( muzzleOffset );
|
|
projectileEnt.Restore ( savefile );
|
|
savefile->ReadVec3 ( ejectOffset ); // cnicholson: Added unrestored var
|
|
|
|
savefile->ReadInt ( fireRate );
|
|
savefile->ReadFloat ( spread );
|
|
nextAttackTime = 0;
|
|
|
|
// Attack Alt Def
|
|
attackAltDict.Clear( );
|
|
wfl.attackAltHitscan = false;
|
|
def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_altprojectile" ), false );
|
|
if ( def ) {
|
|
attackAltDict = def->dict;
|
|
} else {
|
|
def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_althitscan" ), false );
|
|
if ( def ) {
|
|
attackAltDict = def->dict;
|
|
wfl.attackAltHitscan = true;
|
|
}
|
|
}
|
|
|
|
// Attack def
|
|
attackDict.Clear( );
|
|
def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_projectile" ), false );
|
|
wfl.attackHitscan = false;
|
|
if ( def ) {
|
|
attackDict = def->dict;
|
|
} else {
|
|
def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_hitscan" ), false );
|
|
if ( def ) {
|
|
attackDict = def->dict;
|
|
wfl.attackHitscan = true;
|
|
}
|
|
}
|
|
|
|
// Brass Def
|
|
def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_ejectBrass" ), false );
|
|
if ( def ) {
|
|
brassDict = def->dict;
|
|
} else {
|
|
brassDict.Clear();
|
|
}
|
|
|
|
// Melee Def
|
|
meleeDef = gameLocal.FindEntityDef( spawnArgs.GetString( "def_melee" ), false );
|
|
savefile->ReadFloat( meleeDistance );
|
|
|
|
// Zoom
|
|
savefile->ReadInt ( zoomFov );
|
|
savefile->ReadUserInterface ( zoomGui, &spawnArgs );
|
|
savefile->ReadFloat ( zoomTime );
|
|
|
|
// Lights
|
|
for ( i = 0; i < WPLIGHT_MAX; i ++ ) {
|
|
savefile->ReadInt ( lightHandles[i] );
|
|
savefile->ReadRenderLight( lights[i] );
|
|
if ( lightHandles[i] != -1 ) {
|
|
//get the handle again as it's out of date after a restore!
|
|
lightHandles[i] = gameRenderWorld->AddLightDef ( &lights[i] );
|
|
}
|
|
}
|
|
savefile->ReadVec3 ( guiLightOffset );
|
|
savefile->ReadInt ( muzzleFlashEnd );
|
|
savefile->ReadInt ( muzzleFlashTime );
|
|
savefile->ReadVec3 ( muzzleFlashViewOffset );
|
|
savefile->ReadVec3 ( flashlightViewOffset );
|
|
savefile->ReadBool ( flashlightOn ); // cnicholson: Added unrestored var
|
|
savefile->ReadVec3 ( flashlightViewOffset ); // cnicholson: Added unrestored var
|
|
|
|
// Read the ammo values
|
|
savefile->ReadInt ( (int&)ammoType );
|
|
savefile->ReadInt ( ammoRequired );
|
|
savefile->ReadInt ( clipSize );
|
|
savefile->ReadInt ( ammoClip );
|
|
savefile->ReadInt ( lowAmmo );
|
|
savefile->ReadInt ( maxAmmo );
|
|
|
|
// multiplayer
|
|
savefile->ReadInt ( clipPredictTime ); // TORESTORE: Restore MP value?
|
|
|
|
// View
|
|
savefile->ReadVec3 ( playerViewOrigin );
|
|
savefile->ReadMat3 ( playerViewAxis );
|
|
|
|
savefile->ReadVec3 ( viewModelOrigin );
|
|
savefile->ReadMat3 ( viewModelAxis );
|
|
savefile->ReadAngles ( viewModelAngles );
|
|
savefile->ReadVec3 ( viewModelOffset ); // cnicholson: Added unrestored var
|
|
|
|
// Offsets
|
|
savefile->ReadInt ( weaponAngleOffsetAverages );
|
|
savefile->ReadFloat ( weaponAngleOffsetScale );
|
|
savefile->ReadFloat ( weaponAngleOffsetMax );
|
|
savefile->ReadFloat ( weaponOffsetTime );
|
|
savefile->ReadFloat ( weaponOffsetScale );
|
|
|
|
savefile->ReadString ( icon );
|
|
savefile->ReadBool ( isStrogg );
|
|
|
|
// TORESTORE: idDict spawnArgs;
|
|
|
|
// TORESTORE: idAnimator* viewAnimator;
|
|
// TORESTORE: idAnimator* worldAnimator;
|
|
// TORESTORE: const idDeclEntityDef* weaponDef;
|
|
// TORESTORE: idScriptObject* scriptObject;
|
|
|
|
// Entities
|
|
savefile->ReadObject( reinterpret_cast<idClass *&>( owner ) );
|
|
viewModel = owner->GetWeaponViewModel ( );
|
|
worldModel = owner->GetWeaponWorldModel ( );
|
|
|
|
savefile->ReadInt ( weaponIndex ); // cnicholson: Added unrestored var
|
|
savefile->ReadInt ( mods ); // cnicholson: Added unrestored var
|
|
|
|
savefile->ReadFloat ( viewModelForeshorten );
|
|
|
|
stateThread.Restore( savefile, this );
|
|
|
|
for ( i = 0; i < ANIM_NumAnimChannels; i++ ) {
|
|
savefile->ReadInt( animDoneTime[i] );
|
|
}
|
|
|
|
savefile->ReadInt ( methodOfDeath ); // cnicholson: Added unrestored var
|
|
|
|
#ifdef _XENON
|
|
aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" );
|
|
#endif
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
State control/player interface
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Hide
|
|
================
|
|
*/
|
|
void rvWeapon::Hide( void ) {
|
|
muzzleFlashEnd = 0;
|
|
|
|
if ( viewModel ) {
|
|
viewModel->Hide();
|
|
}
|
|
if ( worldModel ) {
|
|
worldModel->Hide ( );
|
|
}
|
|
|
|
// Stop flashlight and gui lights
|
|
FreeLight ( WPLIGHT_GUI );
|
|
FreeLight ( WPLIGHT_FLASHLIGHT );
|
|
FreeLight ( WPLIGHT_FLASHLIGHT_WORLD );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Show
|
|
================
|
|
*/
|
|
void rvWeapon::Show ( void ) {
|
|
if ( viewModel ) {
|
|
viewModel->Show();
|
|
}
|
|
if ( worldModel ) {
|
|
worldModel->Show();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::IsHidden
|
|
================
|
|
*/
|
|
bool rvWeapon::IsHidden( void ) const {
|
|
return !viewModel || viewModel->IsHidden();
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::HideWorldModel
|
|
================
|
|
*/
|
|
void rvWeapon::HideWorldModel ( void ) {
|
|
if ( worldModel ) {
|
|
worldModel->Hide();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::ShowWorldModel
|
|
================
|
|
*/
|
|
void rvWeapon::ShowWorldModel ( void ) {
|
|
if ( worldModel ) {
|
|
worldModel->Show();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
rvWeapon::LowerWeapon
|
|
================
|
|
*/
|
|
void rvWeapon::LowerWeapon( void ) {
|
|
if ( !wfl.hide ) {
|
|
hideStart = 0.0f;
|
|
hideEnd = hideDistance;
|
|
if ( gameLocal.time - hideStartTime < hideTime ) {
|
|
hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) );
|
|
} else {
|
|
hideStartTime = gameLocal.time;
|
|
}
|
|
wfl.hide = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::RaiseWeapon
|
|
================
|
|
*/
|
|
void rvWeapon::RaiseWeapon( void ) {
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
|
|
viewModel->Show();
|
|
|
|
if ( forceGUIReload ) {
|
|
forceGUIReload = false;
|
|
int ammo = AmmoInClip();
|
|
for ( int g = 0; g < MAX_RENDERENTITY_GUI && viewModel->GetRenderEntity()->gui[g]; g ++ ) {
|
|
idUserInterface* gui = viewModel->GetRenderEntity()->gui[g];
|
|
if ( gui ) {
|
|
gui->SetStateInt ( "player_ammo", ammo );
|
|
|
|
if ( ClipSize ( ) ) {
|
|
gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)ClipSize() );
|
|
gui->SetStateInt ( "player_clip_size", ClipSize() );
|
|
} else {
|
|
gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)maxAmmo );
|
|
gui->SetStateInt ( "player_clip_size", maxAmmo );
|
|
}
|
|
gui->SetStateInt ( "player_cachedammo", ammo );
|
|
gui->HandleNamedEvent ( "weapon_ammo" );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( wfl.hide ) {
|
|
hideStart = hideDistance;
|
|
hideEnd = 0.0f;
|
|
if ( gameLocal.time - hideStartTime < hideTime ) {
|
|
hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) );
|
|
} else {
|
|
hideStartTime = gameLocal.time;
|
|
}
|
|
wfl.hide = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::PutAway
|
|
================
|
|
*/
|
|
void rvWeapon::PutAway( void ) {
|
|
wfl.hasBloodSplat = false;
|
|
wsfl.lowerWeapon = true;
|
|
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
viewModel->PostGUIEvent ( "weapon_lower" );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Raise
|
|
================
|
|
*/
|
|
void rvWeapon::Raise( void ) {
|
|
wsfl.raiseWeapon = true;
|
|
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
viewModel->PostGUIEvent ( "weapon_raise" );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Flashlight
|
|
================
|
|
*/
|
|
void rvWeapon::Flashlight ( void ) {
|
|
wsfl.flashlight = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::SetPushVelocity
|
|
================
|
|
*/
|
|
void rvWeapon::SetPushVelocity( const idVec3& _pushVelocity ) {
|
|
pushVelocity = _pushVelocity;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Reload
|
|
NOTE: this is only for impulse-triggered reload, auto reload is scripted
|
|
================
|
|
*/
|
|
void rvWeapon::Reload( void ) {
|
|
if ( clipSize ) {
|
|
wsfl.reload = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::CancelReload
|
|
================
|
|
*/
|
|
void rvWeapon::CancelReload( void ) {
|
|
wsfl.attack = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::AutoReload
|
|
================
|
|
*/
|
|
bool rvWeapon::AutoReload ( void ) {
|
|
assert( owner );
|
|
|
|
// on a network client, never predict reloads of other clients. wait for the server
|
|
if ( gameLocal.isClient ) {
|
|
return false;
|
|
}
|
|
return gameLocal.userInfo[ owner->entityNumber ].GetBool( "ui_autoReload" );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::NetReload
|
|
================
|
|
*/
|
|
void rvWeapon::NetReload ( void ) {
|
|
assert( owner );
|
|
if ( gameLocal.isServer ) {
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
viewModel->ServerSendEvent( EVENT_RELOAD, NULL, false, -1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
rvWeapon::NetEndReload
|
|
===============
|
|
*/
|
|
void rvWeapon::NetEndReload ( void ) {
|
|
assert( owner );
|
|
if ( gameLocal.isServer ) {
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
viewModel->ServerSendEvent( EVENT_ENDRELOAD, NULL, false, -1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::SetStatus
|
|
================
|
|
*/
|
|
void rvWeapon::SetStatus ( weaponStatus_t _status ) {
|
|
status = _status;
|
|
switch ( status ) {
|
|
case WP_READY:
|
|
wsfl.raiseWeapon = false;
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
break;
|
|
}
|
|
viewModel->PostGUIEvent ( "weapon_ready" );
|
|
break;
|
|
case WP_OUTOFAMMO:
|
|
wsfl.raiseWeapon = false;
|
|
break;
|
|
case WP_RELOAD:
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
break;
|
|
}
|
|
viewModel->PostGUIEvent ( "weapon_reload" );
|
|
break;
|
|
case WP_HOLSTERED:
|
|
case WP_RISING:
|
|
wsfl.lowerWeapon = false;
|
|
owner->WeaponRisingCallback();
|
|
break;
|
|
case WP_LOWERING:
|
|
wsfl.raiseWeapon = false;
|
|
owner->WeaponLoweringCallback();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::OwnerDied
|
|
================
|
|
*/
|
|
void rvWeapon::OwnerDied( void ) {
|
|
|
|
CleanupWeapon();
|
|
|
|
ExecuteState( "OwnerDied" );
|
|
|
|
if ( viewModel ) {
|
|
viewModel->StopSound( SCHANNEL_ANY, false );
|
|
viewModel->StopAllEffects( );
|
|
viewModel->Hide();
|
|
}
|
|
if ( worldModel ) {
|
|
worldModel->Hide();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::BeginAttack
|
|
================
|
|
*/
|
|
void rvWeapon::BeginAttack( void ) {
|
|
wsfl.attack = true;
|
|
|
|
if ( status != WP_OUTOFAMMO ) {
|
|
lastAttack = gameLocal.time;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::EndAttack
|
|
================
|
|
*/
|
|
void rvWeapon::EndAttack( void ) {
|
|
wsfl.attack = false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::isReady
|
|
================
|
|
*/
|
|
bool rvWeapon::IsReady( void ) const {
|
|
return !wfl.hide && !(gameLocal.time - hideStartTime < hideTime) && (viewModel && !viewModel->IsHidden()) && ( ( status == WP_READY ) || ( status == WP_OUTOFAMMO ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::IsReloading
|
|
================
|
|
*/
|
|
bool rvWeapon::IsReloading( void ) const {
|
|
return ( status == WP_RELOAD );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::IsHolstered
|
|
================
|
|
*/
|
|
bool rvWeapon::IsHolstered( void ) const {
|
|
return ( status == WP_HOLSTERED );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::ShowCrosshair
|
|
================
|
|
*/
|
|
bool rvWeapon::ShowCrosshair( void ) const {
|
|
if ( owner->IsZoomed ( ) && zoomGui && wfl.zoomHideCrosshair ) {
|
|
return false;
|
|
}
|
|
return !( status == WP_HOLSTERED );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvWeapon::CanDrop
|
|
=====================
|
|
*/
|
|
bool rvWeapon::CanDrop( void ) const {
|
|
const char *classname = spawnArgs.GetString( "def_dropItem" );
|
|
if ( !classname[ 0 ] ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvViewWeapon::CanZoom
|
|
=====================
|
|
*/
|
|
bool rvWeapon::CanZoom( void ) const {
|
|
#ifdef _XENON
|
|
// apparently a xenon specific bug in medlabs.
|
|
return zoomFov != -1 && !IsHidden();
|
|
#else
|
|
return zoomFov != -1;
|
|
#endif
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Visual presentation
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
rvWeapon::MuzzleRise
|
|
================
|
|
*/
|
|
void rvWeapon::MuzzleRise( idVec3 &origin, idMat3 &axis ) {
|
|
int time;
|
|
float amount;
|
|
idAngles ang;
|
|
idVec3 offset;
|
|
|
|
time = kick_endtime - gameLocal.time;
|
|
if ( time <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( muzzle_kick_maxtime <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( time > muzzle_kick_maxtime ) {
|
|
time = muzzle_kick_maxtime;
|
|
}
|
|
|
|
amount = ( float )time / ( float )muzzle_kick_maxtime;
|
|
ang = muzzle_kick_angles * amount;
|
|
offset = muzzle_kick_offset * amount;
|
|
|
|
origin = origin - axis * offset;
|
|
axis = ang.ToMat3() * axis;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::UpdateFlashPosition
|
|
================
|
|
*/
|
|
void rvWeapon::UpdateMuzzleFlash ( void ) {
|
|
// remove the muzzle flash light when it's done
|
|
if ( gameLocal.time >= muzzleFlashEnd || !gameLocal.GetLocalPlayer() || !owner || gameLocal.GetLocalPlayer()->GetInstance() != owner->GetInstance() ) {
|
|
FreeLight ( WPLIGHT_MUZZLEFLASH );
|
|
FreeLight ( WPLIGHT_MUZZLEFLASH_WORLD );
|
|
return;
|
|
}
|
|
|
|
renderLight_t& light = lights[WPLIGHT_MUZZLEFLASH];
|
|
renderLight_t& lightWorld = lights[WPLIGHT_MUZZLEFLASH_WORLD];
|
|
|
|
light.origin = playerViewOrigin + (playerViewAxis * muzzleFlashViewOffset);
|
|
light.axis = playerViewAxis;
|
|
|
|
// put the world muzzle flash on the end of the joint, no matter what
|
|
GetGlobalJointTransform( false, flashJointWorld, lightWorld.origin, lightWorld.axis );
|
|
|
|
UpdateLight ( WPLIGHT_MUZZLEFLASH );
|
|
UpdateLight ( WPLIGHT_MUZZLEFLASH_WORLD );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::UpdateFlashlight
|
|
================
|
|
*/
|
|
void rvWeapon::UpdateFlashlight ( void ) {
|
|
// Turn flashlight off?
|
|
if (! owner->IsFlashlightOn ( ) ) {
|
|
FreeLight ( WPLIGHT_FLASHLIGHT );
|
|
FreeLight ( WPLIGHT_FLASHLIGHT_WORLD );
|
|
return;
|
|
}
|
|
|
|
renderLight_t& light = lights[WPLIGHT_FLASHLIGHT];
|
|
renderLight_t& lightWorld = lights[WPLIGHT_FLASHLIGHT_WORLD];
|
|
trace_t tr;
|
|
|
|
// the flash has an explicit joint for locating it
|
|
GetGlobalJointTransform( true, flashlightJointView, light.origin, light.axis, flashlightViewOffset );
|
|
|
|
// if the desired point is inside or very close to a wall, back it up until it is clear
|
|
gameLocal.TracePoint( owner, tr, light.origin - playerViewAxis[0] * 8.0f, light.origin, MASK_SHOT_BOUNDINGBOX, owner );
|
|
|
|
// be at least 8 units away from a solid
|
|
light.origin = tr.endpos - (tr.fraction < 1.0f ? (playerViewAxis[0] * 8) : vec3_origin);
|
|
|
|
// put the world muzzle flash on the end of the joint, no matter what
|
|
if ( flashlightJointWorld != INVALID_JOINT ) {
|
|
GetGlobalJointTransform( false, flashlightJointWorld, lightWorld.origin, lightWorld.axis );
|
|
} else {
|
|
lightWorld.origin = playerViewOrigin + playerViewAxis[0] * 20.0f;
|
|
lightWorld.axis = playerViewAxis;
|
|
}
|
|
|
|
UpdateLight ( WPLIGHT_FLASHLIGHT );
|
|
UpdateLight ( WPLIGHT_FLASHLIGHT_WORLD );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::MuzzleFlash
|
|
================
|
|
*/
|
|
void rvWeapon::MuzzleFlash ( void ) {
|
|
renderLight_t& light = lights[WPLIGHT_MUZZLEFLASH];
|
|
renderLight_t& lightWorld = lights[WPLIGHT_MUZZLEFLASH_WORLD];
|
|
|
|
if ( !g_muzzleFlash.GetBool() || flashJointView == INVALID_JOINT || !light.lightRadius[0] ) {
|
|
return;
|
|
}
|
|
if ( g_perfTest_weaponNoFX.GetBool() ) {
|
|
return;
|
|
}
|
|
|
|
if ( viewModel ) {
|
|
// these will be different each fire
|
|
light.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
|
|
light.shaderParms[ SHADERPARM_DIVERSITY ] = viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ];
|
|
light.noShadows = true;
|
|
|
|
lightWorld.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
|
|
lightWorld.shaderParms[ SHADERPARM_DIVERSITY ] = viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ];
|
|
lightWorld.noShadows = true;
|
|
|
|
// the light will be removed at this time
|
|
muzzleFlashEnd = gameLocal.time + muzzleFlashTime;
|
|
} else {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
}
|
|
UpdateMuzzleFlash ( );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
rvWeapon::UpdateGUI
|
|
================
|
|
*/
|
|
void rvWeapon::UpdateGUI( void ) {
|
|
idUserInterface* gui;
|
|
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
|
|
gui = viewModel->GetRenderEntity()->gui[0];
|
|
if ( !gui || status == WP_HOLSTERED ) {
|
|
return;
|
|
}
|
|
int g;
|
|
for ( g = 0; g < MAX_RENDERENTITY_GUI && viewModel->GetRenderEntity()->gui[g]; g ++ ) {
|
|
gui = viewModel->GetRenderEntity()->gui[g];
|
|
|
|
if ( gameLocal.localClientNum != owner->entityNumber ) {
|
|
// if updating the hud for a followed client
|
|
idPlayer *p = gameLocal.GetLocalPlayer();
|
|
if ( !p ) {
|
|
return;
|
|
}
|
|
if ( !p->spectating || p->spectator != owner->entityNumber ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
int ammo = AmmoInClip();
|
|
if ( ammo >= 0 ) {
|
|
// show remaining ammo
|
|
if ( gui->State().GetInt ( "player_cachedammo", "-1") != ammo ) {
|
|
gui->SetStateInt ( "player_ammo", ammo );
|
|
|
|
if ( ClipSize ( ) ) {
|
|
gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)ClipSize() );
|
|
gui->SetStateInt ( "player_clip_size", ClipSize() );
|
|
} else {
|
|
gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)maxAmmo );
|
|
gui->SetStateInt ( "player_clip_size", maxAmmo );
|
|
}
|
|
gui->SetStateInt ( "player_cachedammo", ammo );
|
|
gui->HandleNamedEvent ( "weapon_ammo" );
|
|
}
|
|
}
|
|
|
|
// viewModel->GetRenderEntity()->gui[g]->SetStateInt ( "player_clip_size", ClipSize() );
|
|
}
|
|
for ( int i = 0; i < viewModel->pendingGUIEvents.Num(); i ++ ) {
|
|
gui->HandleNamedEvent( viewModel->pendingGUIEvents[i] );
|
|
}
|
|
viewModel->pendingGUIEvents.Clear();
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::UpdateCrosshairGUI
|
|
================
|
|
*/
|
|
void rvWeapon::UpdateCrosshairGUI( idUserInterface* gui ) const {
|
|
// RAVEN BEGIN
|
|
// cnicholson: Added support for universal crosshair
|
|
|
|
// COMMENTED OUT until Custom crosshair GUI is implemented.
|
|
if ( g_crosshairCustom.GetBool() ) { // If there's a custom crosshair, use it.
|
|
gui->SetStateString( "crossImage", g_crosshairCustomFile.GetString());
|
|
|
|
const idMaterial *material = declManager->FindMaterial( g_crosshairCustomFile.GetString() );
|
|
if ( material ) {
|
|
material->SetSort( SS_GUI );
|
|
}
|
|
} else {
|
|
gui->SetStateString( "crossImage", spawnArgs.GetString( "mtr_crosshair" ) );
|
|
|
|
const idMaterial *material = declManager->FindMaterial( spawnArgs.GetString( "mtr_crosshair" ) );
|
|
if ( material ) {
|
|
material->SetSort( SS_GUI );
|
|
}
|
|
}
|
|
|
|
// Original Block
|
|
//gui->SetStateString ( "crossImage", spawnArgs.GetString ( "mtr_crosshair" ) );
|
|
// RAVEN END
|
|
gui->SetStateString( "crossColor", g_crosshairColor.GetString() );
|
|
gui->SetStateInt( "crossOffsetX", spawnArgs.GetInt( "crosshairOffsetX", "0" ) );
|
|
gui->SetStateInt( "crossOffsetY", spawnArgs.GetInt( "crosshairOffsetY", "0" ) );
|
|
gui->StateChanged( gameLocal.time );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::ForeshortenAxis
|
|
================
|
|
*/
|
|
idMat3 rvWeapon::ForeshortenAxis( const idMat3& axis ) const {
|
|
return idMat3( axis[0] * viewModelForeshorten, axis[1], axis[2] );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::GetAngleOffsets
|
|
================
|
|
*/
|
|
void rvWeapon::GetAngleOffsets ( int *average, float *scale, float *max ) {
|
|
*average = weaponAngleOffsetAverages;
|
|
*scale = weaponAngleOffsetScale;
|
|
*max = weaponAngleOffsetMax;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::GetTimeOffsets
|
|
================
|
|
*/
|
|
void rvWeapon::GetTimeOffsets ( float *time, float *scale ) {
|
|
*time = weaponOffsetTime;
|
|
*scale = weaponOffsetScale;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::GetGlobalJointTransform
|
|
|
|
This returns the offset and axis of a weapon bone in world space, suitable for attaching models or lights
|
|
================
|
|
*/
|
|
bool rvWeapon::GetGlobalJointTransform ( bool view, const jointHandle_t jointHandle, idVec3 &origin, idMat3 &axis, const idVec3& offset ) {
|
|
if ( view) {
|
|
// view model
|
|
if ( viewModel && viewAnimator->GetJointTransform( jointHandle, gameLocal.time, origin, axis ) ) {
|
|
origin = offset * axis + origin;
|
|
origin = origin * ForeshortenAxis(viewModelAxis) + viewModelOrigin;
|
|
axis = axis * viewModelAxis;
|
|
return true;
|
|
}
|
|
} else {
|
|
// world model
|
|
if ( worldModel && worldAnimator->GetJointTransform( jointHandle, gameLocal.time, origin, axis ) ) {
|
|
origin = offset * axis + origin;
|
|
origin = worldModel->GetPhysics()->GetOrigin() + origin * worldModel->GetPhysics()->GetAxis();
|
|
axis = axis * worldModel->GetPhysics()->GetAxis();
|
|
return true;
|
|
}
|
|
}
|
|
origin = viewModelOrigin + offset * viewModelAxis;
|
|
axis = viewModelAxis;
|
|
return false;
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Ammo
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
rvWeapon::GetAmmoIndexForName
|
|
================
|
|
*/
|
|
int rvWeapon::GetAmmoIndexForName( const char *ammoname ) {
|
|
int num;
|
|
const idDict *ammoDict;
|
|
|
|
assert( ammoname );
|
|
|
|
ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false );
|
|
if ( !ammoDict ) {
|
|
gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" );
|
|
}
|
|
|
|
if ( !ammoname[ 0 ] ) {
|
|
return 0;
|
|
}
|
|
|
|
if ( !ammoDict->GetInt( ammoname, "-1", num ) ) {
|
|
gameLocal.Error( "Unknown ammo type '%s'", ammoname );
|
|
}
|
|
|
|
if ( ( num < 0 ) || ( num >= MAX_AMMOTYPES ) ) {
|
|
gameLocal.Error( "Ammo type '%s' value out of range. Maximum ammo types is %d.\n", ammoname, MAX_AMMOTYPES );
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::GetAmmoNameForNum
|
|
================
|
|
*/
|
|
const char* rvWeapon::GetAmmoNameForIndex( int index ) {
|
|
int i;
|
|
int num;
|
|
const idDict *ammoDict;
|
|
const idKeyValue *kv;
|
|
char text[ 32 ];
|
|
|
|
ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false );
|
|
if ( !ammoDict ) {
|
|
gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" );
|
|
}
|
|
|
|
sprintf( text, "%d", index );
|
|
|
|
num = ammoDict->GetNumKeyVals();
|
|
for( i = 0; i < num; i++ ) {
|
|
kv = ammoDict->GetKeyVal( i );
|
|
if ( kv->GetValue() == text ) {
|
|
return kv->GetKey();
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::TotalAmmoCount
|
|
================
|
|
*/
|
|
int rvWeapon::TotalAmmoCount ( void ) const {
|
|
return owner->inventory.HasAmmo( ammoType, 1 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::AmmoAvailable
|
|
================
|
|
*/
|
|
int rvWeapon::AmmoAvailable( void ) const {
|
|
if ( owner ) {
|
|
return owner->inventory.HasAmmo( ammoType, ammoRequired );
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::AmmoInClip
|
|
================
|
|
*/
|
|
int rvWeapon::AmmoInClip( void ) const {
|
|
if ( !clipSize ) {
|
|
return AmmoAvailable();
|
|
}
|
|
return ammoClip;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::ResetAmmoClip
|
|
================
|
|
*/
|
|
void rvWeapon::ResetAmmoClip( void ) {
|
|
ammoClip = -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::GetAmmoType
|
|
================
|
|
*/
|
|
int rvWeapon::GetAmmoType( void ) const {
|
|
return ammoType;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::ClipSize
|
|
================
|
|
*/
|
|
int rvWeapon::ClipSize( void ) const {
|
|
return clipSize;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::LowAmmo
|
|
================
|
|
*/
|
|
int rvWeapon::LowAmmo() const {
|
|
return lowAmmo;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::AmmoRequired
|
|
================
|
|
*/
|
|
int rvWeapon::AmmoRequired( void ) const {
|
|
return ammoRequired;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::SetClip
|
|
================
|
|
*/
|
|
void rvWeapon::SetClip ( int amount ) {
|
|
ammoClip = amount;
|
|
if ( amount < 0 ) {
|
|
ammoClip = 0;
|
|
} else if ( amount > clipSize ) {
|
|
ammoClip = clipSize;
|
|
}
|
|
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
|
|
viewModel->PostGUIEvent ( "weapon_ammo" );
|
|
if ( ammoClip == 0 && AmmoAvailable() == 0 ) {
|
|
viewModel->PostGUIEvent ( "weapon_noammo" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::UseAmmo
|
|
================
|
|
*/
|
|
void rvWeapon::UseAmmo ( int amount ) {
|
|
owner->inventory.UseAmmo( ammoType, amount * ammoRequired );
|
|
if ( clipSize && ammoRequired ) {
|
|
ammoClip -= ( amount * ammoRequired );
|
|
if ( ammoClip < 0 ) {
|
|
ammoClip = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::AddToClip
|
|
================
|
|
*/
|
|
void rvWeapon::AddToClip ( int amount ) {
|
|
int ammoAvail;
|
|
|
|
if ( gameLocal.isClient ) {
|
|
return;
|
|
}
|
|
|
|
ammoClip += amount;
|
|
if ( ammoClip > clipSize ) {
|
|
ammoClip = clipSize;
|
|
}
|
|
|
|
ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired );
|
|
if ( ammoAvail > 0 && ammoClip > ammoAvail ) {
|
|
ammoClip = ammoAvail;
|
|
}
|
|
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
|
|
viewModel->PostGUIEvent ( "weapon_ammo" );
|
|
if ( ammoClip == 0 && AmmoAvailable() == 0 ) {
|
|
viewModel->PostGUIEvent ( "weapon_noammo" );
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Attack
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Attack
|
|
================
|
|
*/
|
|
void rvWeapon::Attack( bool altAttack, int num_attacks, float spread, float fuseOffset, float power ) {
|
|
idVec3 muzzleOrigin;
|
|
idMat3 muzzleAxis;
|
|
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
|
|
if ( viewModel->IsHidden() ) {
|
|
return;
|
|
}
|
|
|
|
// avoid all ammo considerations on an MP client
|
|
if ( !gameLocal.isClient ) {
|
|
// check if we're out of ammo or the clip is empty
|
|
int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired );
|
|
if ( !ammoAvail || ( ( clipSize != 0 ) && ( ammoClip <= 0 ) ) ) {
|
|
return;
|
|
}
|
|
|
|
owner->inventory.UseAmmo( ammoType, ammoRequired );
|
|
if ( clipSize && ammoRequired ) {
|
|
clipPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots
|
|
ammoClip -= 1;
|
|
}
|
|
|
|
// wake up nearby monsters
|
|
if ( !wfl.silent_fire ) {
|
|
gameLocal.AlertAI( owner );
|
|
}
|
|
}
|
|
|
|
// set the shader parm to the time of last projectile firing,
|
|
// which the gun material shaders can reference for single shot barrel glows, etc
|
|
viewModel->SetShaderParm ( SHADERPARM_DIVERSITY, gameLocal.random.CRandomFloat() );
|
|
viewModel->SetShaderParm ( SHADERPARM_TIMEOFFSET, -MS2SEC( gameLocal.realClientTime ) );
|
|
|
|
if ( worldModel.GetEntity() ) {
|
|
worldModel->SetShaderParm( SHADERPARM_DIVERSITY, viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ] );
|
|
worldModel->SetShaderParm( SHADERPARM_TIMEOFFSET, viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] );
|
|
}
|
|
|
|
// calculate the muzzle position
|
|
if ( barrelJointView != INVALID_JOINT && spawnArgs.GetBool( "launchFromBarrel" ) ) {
|
|
// there is an explicit joint for the muzzle
|
|
GetGlobalJointTransform( true, barrelJointView, muzzleOrigin, muzzleAxis );
|
|
} else {
|
|
// go straight out of the view
|
|
muzzleOrigin = playerViewOrigin;
|
|
muzzleAxis = playerViewAxis;
|
|
muzzleOrigin += playerViewAxis[0] * muzzleOffset;
|
|
}
|
|
|
|
// add some to the kick time, incrementally moving repeat firing weapons back
|
|
if ( kick_endtime < gameLocal.realClientTime ) {
|
|
kick_endtime = gameLocal.realClientTime;
|
|
}
|
|
kick_endtime += muzzle_kick_time;
|
|
if ( kick_endtime > gameLocal.realClientTime + muzzle_kick_maxtime ) {
|
|
kick_endtime = gameLocal.realClientTime + muzzle_kick_maxtime;
|
|
}
|
|
|
|
// add the muzzleflash
|
|
MuzzleFlash();
|
|
|
|
// quad damage overlays a sound
|
|
if ( owner->PowerUpActive( POWERUP_QUADDAMAGE ) ) {
|
|
viewModel->StartSound( "snd_quaddamage", SND_CHANNEL_VOICE, 0, false, NULL );
|
|
}
|
|
|
|
// Muzzle flash effect
|
|
bool muzzleTint = spawnArgs.GetBool( "muzzleTint" );
|
|
viewModel->PlayEffect( "fx_muzzleflash", flashJointView, false, vec3_origin, false, EC_IGNORE, muzzleTint ? owner->GetHitscanTint() : vec4_one );
|
|
|
|
if ( worldModel && flashJointWorld != INVALID_JOINT ) {
|
|
worldModel->PlayEffect( gameLocal.GetEffect( weaponDef->dict, "fx_muzzleflash_world" ), flashJointWorld, vec3_origin, mat3_identity, false, vec3_origin, false, EC_IGNORE, muzzleTint ? owner->GetHitscanTint() : vec4_one );
|
|
}
|
|
|
|
owner->WeaponFireFeedback( &weaponDef->dict );
|
|
|
|
// Inform the gui of the ammo change
|
|
viewModel->PostGUIEvent ( "weapon_ammo" );
|
|
if ( ammoClip == 0 && AmmoAvailable() == 0 ) {
|
|
viewModel->PostGUIEvent ( "weapon_noammo" );
|
|
}
|
|
|
|
// The attack is either a hitscan or a launched projectile, do that now.
|
|
if ( !gameLocal.isClient ) {
|
|
idDict& dict = altAttack ? attackAltDict : attackDict;
|
|
power *= owner->PowerUpModifier( PMOD_PROJECTILE_DAMAGE );
|
|
if ( altAttack ? wfl.attackAltHitscan : wfl.attackHitscan ) {
|
|
Hitscan( dict, muzzleOrigin, muzzleAxis, num_attacks, spread, power );
|
|
} else {
|
|
LaunchProjectiles( dict, muzzleOrigin, muzzleAxis, num_attacks, spread, fuseOffset, power );
|
|
}
|
|
//asalmon: changed to keep stats even in single player
|
|
statManager->WeaponFired( owner, weaponIndex, num_attacks );
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::LaunchProjectiles
|
|
================
|
|
*/
|
|
void rvWeapon::LaunchProjectiles ( idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_projectiles, float spread, float fuseOffset, float power ) {
|
|
idProjectile* proj;
|
|
idEntity* ent;
|
|
int i;
|
|
float spreadRad;
|
|
idVec3 dir;
|
|
idBounds ownerBounds;
|
|
|
|
if ( gameLocal.isClient ) {
|
|
return;
|
|
}
|
|
|
|
// Let the AI know about the new attack
|
|
if ( !gameLocal.isMultiplayer ) {
|
|
aiManager.ReactToPlayerAttack ( owner, muzzleOrigin, muzzleAxis[0] );
|
|
}
|
|
|
|
ownerBounds = owner->GetPhysics()->GetAbsBounds();
|
|
spreadRad = DEG2RAD( spread );
|
|
|
|
idVec3 dirOffset;
|
|
idVec3 startOffset;
|
|
|
|
spawnArgs.GetVector( "dirOffset", "0 0 0", dirOffset );
|
|
spawnArgs.GetVector( "startOffset", "0 0 0", startOffset );
|
|
|
|
for( i = 0; i < num_projectiles; i++ ) {
|
|
float ang;
|
|
float spin;
|
|
idVec3 dir;
|
|
idBounds projBounds;
|
|
idVec3 muzzle_pos;
|
|
|
|
// Calculate a random launch direction based on the spread
|
|
ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
|
|
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
|
|
//RAVEN BEGIN
|
|
//asalmon: xbox must use muzzle Axis for aim assistance
|
|
#ifdef _XBOX
|
|
dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - muzzleAxis[ 1 ] * ( ang * idMath::Cos( spin ) );
|
|
dir += dirOffset;
|
|
#else
|
|
dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) );
|
|
dir += dirOffset;
|
|
#endif
|
|
//RAVEN END
|
|
dir.Normalize();
|
|
|
|
// If a projectile entity has already been created then use that one, otherwise
|
|
// spawn a new one based on the given dictionary
|
|
if ( projectileEnt ) {
|
|
ent = projectileEnt;
|
|
ent->Show();
|
|
ent->Unbind();
|
|
projectileEnt = NULL;
|
|
} else {
|
|
dict.SetInt( "instance", owner->GetInstance() );
|
|
gameLocal.SpawnEntityDef( dict, &ent, false );
|
|
}
|
|
|
|
// Make sure it spawned
|
|
if ( !ent ) {
|
|
gameLocal.Error( "failed to spawn projectile for weapon '%s'", weaponDef->GetName ( ) );
|
|
}
|
|
|
|
assert ( ent->IsType( idProjectile::GetClassType() ) );
|
|
|
|
// Create the projectile
|
|
proj = static_cast<idProjectile*>(ent);
|
|
proj->Create( owner, muzzleOrigin + startOffset, dir, NULL, owner->extraProjPassEntity );
|
|
|
|
projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() );
|
|
|
|
// make sure the projectile starts inside the bounding box of the owner
|
|
if ( i == 0 ) {
|
|
idVec3 start;
|
|
float distance;
|
|
trace_t tr;
|
|
//RAVEN BEGIN
|
|
//asalmon: xbox must use muzzle Axis for aim assistance
|
|
#ifdef _XBOX
|
|
muzzle_pos = muzzleOrigin + muzzleAxis[ 0 ] * 2.0f;
|
|
if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, muzzleAxis[0], distance ) ) {
|
|
start = muzzle_pos + distance * muzzleAxis[0];
|
|
}
|
|
#else
|
|
muzzle_pos = muzzleOrigin + playerViewAxis[ 0 ] * 2.0f;
|
|
if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) {
|
|
start = muzzle_pos + distance * playerViewAxis[0];
|
|
}
|
|
#endif
|
|
//RAVEN END
|
|
else {
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
// RAVEN BEGIN
|
|
// ddynerman: multiple clip worlds
|
|
gameLocal.Translation( owner, tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner );
|
|
// RAVEN END
|
|
muzzle_pos = tr.endpos;
|
|
}
|
|
|
|
// Launch the actual projectile
|
|
proj->Launch( muzzle_pos + startOffset, dir, pushVelocity, fuseOffset, power );
|
|
|
|
// Increment the projectile launch count and let the derived classes
|
|
// mess with it if they want.
|
|
OnLaunchProjectile ( proj );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::OnLaunchProjectile
|
|
================
|
|
*/
|
|
void rvWeapon::OnLaunchProjectile ( idProjectile* proj ) {
|
|
owner->AddProjectilesFired( 1 );
|
|
if ( proj ) {
|
|
proj->methodOfDeath = owner->GetCurrentWeapon();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::Hitscan
|
|
================
|
|
*/
|
|
void rvWeapon::Hitscan( const idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_hitscans, float spread, float power ) {
|
|
idVec3 fxOrigin;
|
|
idMat3 fxAxis;
|
|
int i;
|
|
float ang;
|
|
float spin;
|
|
idVec3 dir;
|
|
int areas[ 2 ];
|
|
|
|
idBitMsg msg;
|
|
byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
|
|
|
|
// Let the AI know about the new attack
|
|
if ( !gameLocal.isMultiplayer ) {
|
|
aiManager.ReactToPlayerAttack( owner, muzzleOrigin, muzzleAxis[0] );
|
|
}
|
|
|
|
GetGlobalJointTransform( true, flashJointView, fxOrigin, fxAxis, dict.GetVector( "fxOriginOffset" ) );
|
|
|
|
if ( gameLocal.isServer ) {
|
|
|
|
assert( hitscanAttackDef >= 0 );
|
|
assert( owner && owner->entityNumber < MAX_CLIENTS );
|
|
int ownerId = owner ? owner->entityNumber : 0;
|
|
|
|
msg.Init( msgBuf, sizeof( msgBuf ) );
|
|
msg.BeginWriting();
|
|
msg.WriteByte( GAME_UNRELIABLE_MESSAGE_HITSCAN );
|
|
msg.WriteLong( hitscanAttackDef );
|
|
msg.WriteBits( ownerId, idMath::BitsForInteger( MAX_CLIENTS ) );
|
|
msg.WriteFloat( muzzleOrigin[0] );
|
|
msg.WriteFloat( muzzleOrigin[1] );
|
|
msg.WriteFloat( muzzleOrigin[2] );
|
|
msg.WriteFloat( fxOrigin[0] );
|
|
msg.WriteFloat( fxOrigin[1] );
|
|
msg.WriteFloat( fxOrigin[2] );
|
|
}
|
|
|
|
float spreadRad = DEG2RAD( spread );
|
|
idVec3 end;
|
|
for( i = 0; i < num_hitscans; i++ ) {
|
|
if( weaponDef->dict.GetBool( "machinegunSpreadStyle" ) ) {
|
|
float r = gameLocal.random.RandomFloat() * idMath::PI * 2.0f;
|
|
float u = idMath::Sin( r ) * gameLocal.random.CRandomFloat() * spread * 16;
|
|
r = idMath::Cos( r ) * gameLocal.random.CRandomFloat() * spread * 16;
|
|
#ifdef _XBOX
|
|
end = muzzleOrigin + ( ( 8192 * 16 ) * muzzleAxis[ 0 ] );
|
|
end += ( r * muzzleAxis[ 1 ] );
|
|
end += ( u * muzzleAxis[ 2 ] );
|
|
#else
|
|
end = muzzleOrigin + ( ( 8192 * 16 ) * playerViewAxis[ 0 ] );
|
|
end += ( r * playerViewAxis[ 1 ] );
|
|
end += ( u * playerViewAxis[ 2 ] );
|
|
#endif
|
|
dir = end - muzzleOrigin;
|
|
} else if( weaponDef->dict.GetBool( "shotgunSpreadStyle" ) ) {
|
|
float r = gameLocal.random.CRandomFloat() * spread * 16;
|
|
float u = gameLocal.random.CRandomFloat() * spread * 16;
|
|
|
|
#ifdef _XBOX
|
|
end = muzzleOrigin + ( ( 8192 * 16 ) * muzzleAxis[ 0 ] );
|
|
end += ( r * muzzleAxis[ 1 ] );
|
|
end += ( u * muzzleAxis[ 2 ] );
|
|
#else
|
|
end = muzzleOrigin + ( ( 8192 * 16 ) * playerViewAxis[ 0 ] );
|
|
end += ( r * playerViewAxis[ 1 ] );
|
|
end += ( u * playerViewAxis[ 2 ] );
|
|
#endif
|
|
dir = end - muzzleOrigin;
|
|
} else {
|
|
ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
|
|
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
|
|
//RAVEN BEGIN
|
|
//asalmon: xbox must use the muzzleAxis so the aim can be adjusted for aim assistance
|
|
#ifdef _XBOX
|
|
dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - muzzleAxis[ 1 ] * ( ang * idMath::Cos( spin ) );
|
|
#else
|
|
dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) );
|
|
#endif
|
|
//RAVEN END
|
|
}
|
|
dir.Normalize();
|
|
|
|
gameLocal.HitScan( dict, muzzleOrigin, dir, fxOrigin, owner, false, 1.0f, NULL, areas );
|
|
|
|
if ( gameLocal.isServer ) {
|
|
msg.WriteDir( dir, 24 );
|
|
if ( i == num_hitscans - 1 ) {
|
|
// NOTE: we emit to the areas of the last hitscan
|
|
// there is a remote possibility that multiple hitscans for shotgun would cover more than 2 areas,
|
|
// so in some rare case a client might miss it
|
|
gameLocal.SendUnreliableMessagePVS( msg, owner, areas[0], areas[1] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::AlertMonsters
|
|
================
|
|
*/
|
|
void rvWeapon::AlertMonsters( void ) {
|
|
trace_t tr;
|
|
idEntity *ent;
|
|
idVec3 end;
|
|
renderLight_t& muzzleFlash = lights[WPLIGHT_MUZZLEFLASH];
|
|
|
|
end = muzzleFlash.origin + muzzleFlash.axis * muzzleFlash.target;
|
|
gameLocal.TracePoint( owner, tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner );
|
|
if ( g_debugWeapon.GetBool() ) {
|
|
gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 );
|
|
gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 );
|
|
}
|
|
|
|
if ( tr.fraction < 1.0f ) {
|
|
ent = gameLocal.GetTraceEntity( tr );
|
|
if ( ent->IsType( idAI::GetClassType() ) ) {
|
|
static_cast<idAI *>( ent )->TouchedByFlashlight( owner );
|
|
} else if ( ent->IsType( idTrigger::GetClassType() ) ) {
|
|
ent->Signal( SIG_TOUCH );
|
|
ent->ProcessEvent( &EV_Touch, owner, &tr );
|
|
}
|
|
}
|
|
|
|
// jitter the trace to try to catch cases where a trace down the center doesn't hit the monster
|
|
end += muzzleFlash.axis * muzzleFlash.right * idMath::Sin16( MS2SEC( gameLocal.time ) * 31.34f );
|
|
end += muzzleFlash.axis * muzzleFlash.up * idMath::Sin16( MS2SEC( gameLocal.time ) * 12.17f );
|
|
gameLocal.TracePoint( owner, tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner );
|
|
if ( g_debugWeapon.GetBool() ) {
|
|
gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 );
|
|
gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 );
|
|
}
|
|
|
|
if ( tr.fraction < 1.0f ) {
|
|
ent = gameLocal.GetTraceEntity( tr );
|
|
if ( ent->IsType( idAI::GetClassType() ) ) {
|
|
static_cast<idAI *>( ent )->TouchedByFlashlight( owner );
|
|
} else if ( ent->IsType( idTrigger::GetClassType() ) ) {
|
|
ent->Signal( SIG_TOUCH );
|
|
ent->ProcessEvent( &EV_Touch, owner, &tr );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::EjectBrass
|
|
================
|
|
*/
|
|
void rvWeapon::EjectBrass ( void ) {
|
|
if ( g_brassTime.GetFloat() <= 0.0f || !owner->CanShowWeaponViewmodel() ) {
|
|
return;
|
|
}
|
|
|
|
if ( g_perfTest_weaponNoFX.GetBool() ) {
|
|
return;
|
|
}
|
|
|
|
if ( gameLocal.isMultiplayer ) {
|
|
return;
|
|
}
|
|
|
|
if ( ejectJointView == INVALID_JOINT || !brassDict.GetNumKeyVals() ) {
|
|
return;
|
|
}
|
|
|
|
idMat3 axis;
|
|
idVec3 origin;
|
|
idVec3 linear_velocity;
|
|
idVec3 angular_velocity;
|
|
int brassTime;
|
|
|
|
if ( !GetGlobalJointTransform( true, ejectJointView, origin, axis ) ) {
|
|
return;
|
|
}
|
|
|
|
// Spawn the client side moveable for the brass
|
|
rvClientMoveable* cent = NULL;
|
|
|
|
gameLocal.SpawnClientEntityDef( brassDict, (rvClientEntity**)(¢), false );
|
|
|
|
if( !cent ) {
|
|
return;
|
|
}
|
|
|
|
cent->SetOwner( GetOwner() );
|
|
cent->SetOrigin ( origin + playerViewAxis * ejectOffset );
|
|
cent->SetAxis ( playerViewAxis );
|
|
|
|
// Depth hack the brass to make sure it clips in front of view weapon properly
|
|
cent->GetRenderEntity()->weaponDepthHackInViewID = GetOwner()->entityNumber + 1;
|
|
|
|
// Clear the depth hack soon after it clears the view
|
|
cent->PostEventMS ( &CL_ClearDepthHack, 200 );
|
|
|
|
// Fade the brass out so they dont accumulate
|
|
brassTime =(int)SEC2MS(g_brassTime.GetFloat() / 2.0f);
|
|
cent->PostEventMS ( &CL_FadeOut, brassTime, brassTime );
|
|
|
|
// Generate a good velocity for the brass
|
|
idVec3 linearVelocity = brassDict.GetVector("linear_velocity").Random( brassDict.GetVector("linear_velocity_range"), gameLocal.random );
|
|
cent->GetPhysics()->SetLinearVelocity( GetOwner()->GetPhysics()->GetLinearVelocity() + linearVelocity * cent->GetPhysics()->GetAxis() );
|
|
idAngles angularVelocity = brassDict.GetAngles("angular_velocity").Random( brassDict.GetVector("angular_velocity_range"), gameLocal.random );
|
|
cent->GetPhysics()->SetAngularVelocity( angularVelocity.ToAngularVelocity() * cent->GetPhysics()->GetAxis() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::BloodSplat
|
|
================
|
|
*/
|
|
bool rvWeapon::BloodSplat( float size ) {
|
|
float s, c;
|
|
idMat3 localAxis, axistemp;
|
|
idVec3 localOrigin, normal;
|
|
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return false;
|
|
}
|
|
|
|
if ( wfl.hasBloodSplat ) {
|
|
return true;
|
|
}
|
|
|
|
wfl.hasBloodSplat = true;
|
|
|
|
if ( viewModel->modelDefHandle < 0 ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !GetGlobalJointTransform( true, ejectJointView, localOrigin, localAxis ) ) {
|
|
return false;
|
|
}
|
|
|
|
localOrigin[0] += gameLocal.random.RandomFloat() * -10.0f;
|
|
localOrigin[1] += gameLocal.random.RandomFloat() * 1.0f;
|
|
localOrigin[2] += gameLocal.random.RandomFloat() * -2.0f;
|
|
|
|
normal = idVec3( gameLocal.random.CRandomFloat(), -gameLocal.random.RandomFloat(), -1 );
|
|
normal.Normalize();
|
|
|
|
idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c );
|
|
|
|
localAxis[2] = -normal;
|
|
localAxis[2].NormalVectors( axistemp[0], axistemp[1] );
|
|
localAxis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s;
|
|
localAxis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c;
|
|
|
|
localAxis[0] *= 1.0f / size;
|
|
localAxis[1] *= 1.0f / size;
|
|
|
|
idPlane localPlane[2];
|
|
|
|
localPlane[0] = localAxis[0];
|
|
localPlane[0][3] = -(localOrigin * localAxis[0]) + 0.5f;
|
|
|
|
localPlane[1] = localAxis[1];
|
|
localPlane[1][3] = -(localOrigin * localAxis[1]) + 0.5f;
|
|
|
|
const idMaterial *mtr = declManager->FindMaterial( "textures/decals/duffysplatgun" );
|
|
|
|
gameRenderWorld->ProjectOverlay( viewModel->modelDefHandle, localPlane, mtr );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::EnterCinematic
|
|
================
|
|
*/
|
|
void rvWeapon::EnterCinematic( void ) {
|
|
if( viewModel ) {
|
|
viewModel->StopSound( SND_CHANNEL_ANY, false );
|
|
}
|
|
ExecuteState( "EnterCinematic" );
|
|
|
|
memset( &wsfl, 0, sizeof(wsfl) );
|
|
|
|
wfl.disabled = true;
|
|
|
|
LowerWeapon();
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::ExitCinematic
|
|
================
|
|
*/
|
|
void rvWeapon::ExitCinematic( void ) {
|
|
wfl.disabled = false;
|
|
ExecuteState ( "ExitCinematic" );
|
|
RaiseWeapon();
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::NetCatchup
|
|
================
|
|
*/
|
|
void rvWeapon::NetCatchup( void ) {
|
|
ExecuteState ( "NetCatchup" );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
rvWeapon::PlayAnim
|
|
===============
|
|
*/
|
|
void rvWeapon::PlayAnim( int channel, const char *animname, int blendFrames ) {
|
|
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
|
|
int anim;
|
|
|
|
anim = viewAnimator->GetAnim( animname );
|
|
if ( !anim ) {
|
|
gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, viewModel->GetName(), viewModel->GetEntityDefName() );
|
|
viewAnimator->Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) );
|
|
animDoneTime[channel] = 0;
|
|
} else {
|
|
viewModel->Show();
|
|
viewAnimator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) );
|
|
animDoneTime[channel] = viewAnimator->CurrentAnim( channel )->GetEndTime();
|
|
|
|
// Play the animation on the world model as well
|
|
if ( worldAnimator ) {
|
|
worldAnimator->GetAnim( animname );
|
|
if ( anim ) {
|
|
worldAnimator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
rvWeapon::PlayCycle
|
|
===============
|
|
*/
|
|
void rvWeapon::PlayCycle( int channel, const char *animname, int blendFrames ) {
|
|
int anim;
|
|
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
|
|
anim = viewAnimator->GetAnim( animname );
|
|
if ( !anim ) {
|
|
gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, viewModel->GetName(), viewModel->GetEntityDefName() );
|
|
viewAnimator->Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) );
|
|
animDoneTime[channel] = 0;
|
|
} else {
|
|
viewModel->Show();
|
|
viewAnimator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) );
|
|
animDoneTime[channel] = viewAnimator->CurrentAnim( channel )->GetEndTime();
|
|
|
|
// Play the animation on the world model as well
|
|
if ( worldAnimator ) {
|
|
anim = worldAnimator->GetAnim( animname );
|
|
if ( anim ) {
|
|
worldAnimator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
rvWeapon::AnimDone
|
|
===============
|
|
*/
|
|
bool rvWeapon::AnimDone( int channel, int blendFrames ) {
|
|
if ( animDoneTime[channel] - FRAME2MS( blendFrames ) <= gameLocal.time ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
rvWeapon::StartSound
|
|
===============
|
|
*/
|
|
bool rvWeapon::StartSound ( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) {
|
|
if ( !viewModel ) {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return false;
|
|
}
|
|
return viewModel->StartSound( soundName, channel, soundShaderFlags, broadcast, length );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
rvWeapon::StopSound
|
|
===============
|
|
*/
|
|
void rvWeapon::StopSound( const s_channelType channel, bool broadcast ) {
|
|
if ( viewModel ) {
|
|
viewModel->StopSound( channel, broadcast );
|
|
} else {
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
rvWeapon::PlayEffect
|
|
===============
|
|
*/
|
|
rvClientEffect* rvWeapon::PlayEffect( const char* effectName, jointHandle_t joint, bool loop, const idVec3& endOrigin, bool broadcast ) {
|
|
if ( viewModel ) {
|
|
return viewModel->PlayEffect( effectName, joint, loop, endOrigin, broadcast );
|
|
}
|
|
|
|
common->Warning( "NULL viewmodel %s\n", __FUNCTION__ );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::CacheWeapon
|
|
================
|
|
*/
|
|
void rvWeapon::CacheWeapon( const char *weaponName ) {
|
|
const idDeclEntityDef *weaponDef;
|
|
const char *brassDefName;
|
|
const char *clipModelName;
|
|
idTraceModel trm;
|
|
|
|
weaponDef = gameLocal.FindEntityDef( weaponName, false );
|
|
if ( !weaponDef ) {
|
|
return;
|
|
}
|
|
|
|
// precache the brass collision model
|
|
brassDefName = weaponDef->dict.GetString( "def_ejectBrass" );
|
|
if ( brassDefName[0] ) {
|
|
const idDeclEntityDef *brassDef = gameLocal.FindEntityDef( brassDefName, false );
|
|
if ( brassDef ) {
|
|
brassDef->dict.GetString( "clipmodel", "", &clipModelName );
|
|
if ( idStr::Icmp( clipModelName, SIMPLE_TRI_NAME ) == 0 ) {
|
|
trm.SetupPolygon( simpleTri, 3 );
|
|
} else {
|
|
if ( !clipModelName[0] ) {
|
|
clipModelName = brassDef->dict.GetString( "model" ); // use the visual model
|
|
}
|
|
// load the trace model
|
|
collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm );
|
|
}
|
|
}
|
|
}
|
|
|
|
const idKeyValue* kv;
|
|
|
|
kv = weaponDef->dict.MatchPrefix( "gui", NULL );
|
|
while( kv ) {
|
|
if ( kv->GetValue().Length() ) {
|
|
uiManager->FindGui( kv->GetValue().c_str(), true, false, true );
|
|
}
|
|
kv = weaponDef->dict.MatchPrefix( "gui", kv );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
States
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
CLASS_STATES_DECLARATION ( rvWeapon )
|
|
STATE ( "Raise", rvWeapon::State_Raise )
|
|
STATE ( "Lower", rvWeapon::State_Lower )
|
|
STATE ( "ExitCinematic", rvWeapon::State_ExitCinematic )
|
|
STATE ( "NetCatchup", rvWeapon::State_NetCatchup )
|
|
STATE ( "EjectBrass", rvWeapon::Frame_EjectBrass )
|
|
END_CLASS_STATES
|
|
|
|
/*
|
|
================
|
|
rvWeapon::State_Raise
|
|
|
|
Raise the weapon
|
|
================
|
|
*/
|
|
stateResult_t rvWeapon::State_Raise ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_INIT,
|
|
STAGE_WAIT,
|
|
};
|
|
switch ( parms.stage ) {
|
|
// Start the weapon raising
|
|
case STAGE_INIT:
|
|
SetStatus ( WP_RISING );
|
|
PlayAnim( ANIMCHANNEL_ALL, "raise", 0 );
|
|
return SRESULT_STAGE ( STAGE_WAIT );
|
|
|
|
case STAGE_WAIT:
|
|
if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) {
|
|
SetState ( "Idle", 4 );
|
|
return SRESULT_DONE;
|
|
}
|
|
if ( wsfl.lowerWeapon ) {
|
|
SetState ( "Lower", 4 );
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeapon::State_Lower
|
|
|
|
Lower the weapon
|
|
================
|
|
*/
|
|
stateResult_t rvWeapon::State_Lower ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_INIT,
|
|
STAGE_WAIT,
|
|
STAGE_WAITRAISE
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_INIT:
|
|
SetStatus ( WP_LOWERING );
|
|
PlayAnim ( ANIMCHANNEL_ALL, "putaway", parms.blendFrames );
|
|
return SRESULT_STAGE(STAGE_WAIT);
|
|
|
|
case STAGE_WAIT:
|
|
if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) {
|
|
SetStatus ( WP_HOLSTERED );
|
|
return SRESULT_STAGE(STAGE_WAITRAISE);
|
|
}
|
|
return SRESULT_WAIT;
|
|
|
|
case STAGE_WAITRAISE:
|
|
if ( wsfl.raiseWeapon ) {
|
|
SetState ( "Raise", 0 );
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvWeapon::State_ExitCinematic
|
|
=====================
|
|
*/
|
|
stateResult_t rvWeapon::State_NetCatchup ( const stateParms_t& parms ) {
|
|
SetState ( "idle", parms.blendFrames );
|
|
return SRESULT_DONE;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvWeapon::State_ExitCinematic
|
|
=====================
|
|
*/
|
|
stateResult_t rvWeapon::State_ExitCinematic ( const stateParms_t& parms ) {
|
|
SetState ( "Idle", 0 );
|
|
return SRESULT_DONE;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvWeapon::Frame_EjectBrass
|
|
=====================
|
|
*/
|
|
stateResult_t rvWeapon::Frame_EjectBrass( const stateParms_t& parms ) {
|
|
EjectBrass();
|
|
return SRESULT_DONE;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvWeapon::GetDebugInfo
|
|
=====================
|
|
*/
|
|
void rvWeapon::GetDebugInfo ( debugInfoProc_t proc, void* userData ) {
|
|
idClass::GetDebugInfo ( proc, userData );
|
|
proc ( "rvWeapon", "state", stateThread.GetState()?stateThread.GetState()->state->name : "<none>", userData );
|
|
}
|