Files
HL2Overcharged/game/server/overcharged/weapon_custom.cpp
2025-05-21 21:20:08 +03:00

577 lines
18 KiB
C++

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "basehlcombatweapon.h"
#include "NPCevent.h"
#include "basecombatcharacter.h"
#include "AI_BaseNPC.h"
#include "player.h"
#include "game.h"
#include "in_buttons.h"
#include "grenade_ar2.h"
#include "weapon_rpg.h"
#include "triggers.h"
#include "AI_Memory.h"
#include "soundent.h"
#include "rumble_shared.h"
#include "gamestats.h"
#include "Filesystem.h"
#include "weapon_custom.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern ConVar sk_plr_dmg_smg1_grenade;
//LINK_ENTITY_TO_CLASS( weapon_custom1, CWeaponCustom ); //I give up.
//PRECACHE_WEAPON_REGISTER(weapon_custom1);
//LINK_ENTITY_TO_CLASS( weapon_custom2, CWeaponCustom );
//PRECACHE_WEAPON_REGISTER(weapon_custom2);
//LINK_ENTITY_TO_CLASS( weapon_custom3, CWeaponCustom );
//PRECACHE_WEAPON_REGISTER(weapon_custom3);
//LINK_ENTITY_TO_CLASS( weapon_custom4, CWeaponCustom );
//PRECACHE_WEAPON_REGISTER(weapon_custom4);
//LINK_ENTITY_TO_CLASS_CUSTOM("weapon_ak47",CWeaponCustom)
IMPLEMENT_SERVERCLASS_ST(CWeaponCustom, DT_WeaponCustom)
END_SEND_TABLE()
BEGIN_DATADESC(CWeaponCustom)
DEFINE_FIELD(m_hMissile, FIELD_EHANDLE),
END_DATADESC()
//=========================================================
CWeaponCustom::CWeaponCustom()
{
m_fMinRange1 = 0;// No minimum range.
m_fMaxRange1 = 1400;
m_bAltFiresUnderwater = false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponCustom::Precache(void)
{
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose: Give this weapon longer range when wielded by an ally NPC.
//-----------------------------------------------------------------------------
void CWeaponCustom::Equip(CBaseCombatCharacter *pOwner)
{
if (pOwner->Classify() == CLASS_PLAYER_ALLY)
{
m_fMaxRange1 = 3000;
}
else
{
m_fMaxRange1 = 1400;
}
BaseClass::Equip(pOwner);
}
void CWeaponCustom::ItemPostFrame(void)
{
CBasePlayer *pOwner = ToBasePlayer(GetOwner());
if (!pOwner)
return;
//Track the duration of the fire
//FIXME: Check for IN_ATTACK2 as well?
//FIXME: What if we're calling ItemBusyFrame?
m_fFireDuration = (pOwner->m_nButtons & IN_ATTACK) ? (m_fFireDuration + gpGlobals->frametime) : 0.0f;
if (UsesClipsForAmmo1())
{
CheckReload();
}
if ((pOwner->m_nButtons & IN_ATTACK2) && (m_flNextPrimaryAttack <= gpGlobals->curtime))
{
// Clip empty? Or out of ammo on a no-clip weapon?
if (!IsMeleeWeapon() &&
((UsesClipsForAmmo1() && m_iClip1 <= 0) || (!UsesClipsForAmmo1() && pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0)))
{
HandleFireOnEmpty();
}
else if (pOwner->GetWaterLevel() == 3 && m_bFiresUnderwater == false)
{
// This weapon doesn't fire underwater
WeaponSound(EMPTY);
m_flNextPrimaryAttack = gpGlobals->curtime + 0.2;
return;
}
else
{
//NOTENOTE: There is a bug with this code with regards to the way machine guns catch the leading edge trigger
// on the player hitting the attack key. It relies on the gun catching that case in the same frame.
// However, because the player can also be doing a secondary attack, the edge trigger may be missed.
// We really need to hold onto the edge trigger and only clear the condition when the gun has fired its
// first shot. Right now that's too much of an architecture change -- jdw
// If the firing button was just pressed, or the alt-fire just released, reset the firing time
if ((pOwner->m_afButtonPressed & IN_ATTACK) || (pOwner->m_afButtonReleased & IN_ATTACK2))
{
m_flNextPrimaryAttack = gpGlobals->curtime;
}
SecondaryAttack();
}
}
if ((pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime))
{
// Clip empty? Or out of ammo on a no-clip weapon?
if (!IsMeleeWeapon() &&
((UsesClipsForAmmo1() && m_iClip1 <= 0) || (!UsesClipsForAmmo1() && pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0)))
{
HandleFireOnEmpty();
}
else if (pOwner->GetWaterLevel() == 3 && m_bFiresUnderwater == false)
{
// This weapon doesn't fire underwater
WeaponSound(EMPTY);
m_flNextPrimaryAttack = gpGlobals->curtime + 0.2;
return;
}
else
{
//NOTENOTE: There is a bug with this code with regards to the way machine guns catch the leading edge trigger
// on the player hitting the attack key. It relies on the gun catching that case in the same frame.
// However, because the player can also be doing a secondary attack, the edge trigger may be missed.
// We really need to hold onto the edge trigger and only clear the condition when the gun has fired its
// first shot. Right now that's too much of an architecture change -- jdw
// If the firing button was just pressed, or the alt-fire just released, reset the firing time
if ((pOwner->m_afButtonPressed & IN_ATTACK) || (pOwner->m_afButtonReleased & IN_ATTACK2))
{
m_flNextPrimaryAttack = gpGlobals->curtime;
}
PrimaryAttack();
}
}
// -----------------------
// Reload pressed / Clip Empty
// -----------------------
if (pOwner->m_nButtons & IN_RELOAD && UsesClipsForAmmo1() && !m_bInReload)
{
// reload when reload is pressed, or if no buttons are down and weapon is empty.
Reload();
m_fFireDuration = 0.0f;
}
// -----------------------
// No buttons down
// -----------------------
if (!((pOwner->m_nButtons & IN_ATTACK) || (pOwner->m_nButtons & IN_ATTACK2) || (pOwner->m_nButtons & IN_RELOAD)))
{
// no fire buttons down or reloading
if (!ReloadOrSwitchWeapons() && (m_bInReload == false))
{
WeaponIdle();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponCustom::FireNPCPrimaryAttack(CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir)
{
// FIXME: use the returned number of bullets to account for >10hz firerate
WeaponSoundRealtime(SINGLE_NPC);
CSoundEnt::InsertSound(SOUND_COMBAT | SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy());
/*if (IsPrimaryBullet() == true)
{
pOperator->FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED,
MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), 0);
FireBulletsInfo_t info;
info.m_iShots = this->GetWpnData().m_sPrimaryShotCount;
info.m_flDamage = this->GetWpnData().m_sPrimaryDamage;
info.m_iPlayerDamage = this->GetWpnData().m_sPrimaryDamage;
info.m_vecSrc = vecShootOrigin;
info.m_vecDirShooting = vecShootDir;
info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
info.m_flDistance = MAX_TRACE_LENGTH;
info.m_iAmmoType = m_iPrimaryAmmoType;
info.m_iTracerFreq = 2;
info.m_iPenetrationCount = this->GetWpnData().m_iPrimaryPenetrateCount;
FireBullets(info);
pOperator->DoMuzzleFlash();
}*/
m_iClip1 = m_iClip1 - 1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponCustom::Operator_ForceNPCFire(CBaseCombatCharacter *pOperator, bool bSecondary)
{
// Ensure we have enough rounds in the clip
m_iClip1++;
Vector vecShootOrigin, vecShootDir;
QAngle angShootDir;
GetAttachment(LookupAttachment("muzzle"), vecShootOrigin, angShootDir);
AngleVectors(angShootDir, &vecShootDir);
FireNPCPrimaryAttack(pOperator, vecShootOrigin, vecShootDir);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponCustom::Operator_HandleAnimEvent(animevent_t *pEvent, CBaseCombatCharacter *pOperator)
{
switch (pEvent->event)
{
case EVENT_WEAPON_SMG1:
{
Vector vecShootOrigin, vecShootDir;
QAngle angDiscard;
// Support old style attachment point firing
if ((pEvent->options == NULL) || (pEvent->options[0] == '\0') || (!pOperator->GetAttachment(pEvent->options, vecShootOrigin, angDiscard)))
{
vecShootOrigin = pOperator->Weapon_ShootPosition();
}
CAI_BaseNPC *npc = pOperator->MyNPCPointer();
ASSERT(npc != NULL);
vecShootDir = npc->GetActualShootTrajectory(vecShootOrigin);
FireNPCPrimaryAttack(pOperator, vecShootOrigin, vecShootDir);
}
break;
/*//FIXME: Re-enable
case EVENT_WEAPON_AR2_GRENADE:
{
CAI_BaseNPC *npc = pOperator->MyNPCPointer();
Vector vecShootOrigin, vecShootDir;
vecShootOrigin = pOperator->Weapon_ShootPosition();
vecShootDir = npc->GetShootEnemyDir( vecShootOrigin );
Vector vecThrow = m_vecTossVelocity;
CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create( "grenade_ar2", vecShootOrigin, vec3_angle, npc );
pGrenade->SetAbsVelocity( vecThrow );
pGrenade->SetLocalAngularVelocity( QAngle( 0, 400, 0 ) );
pGrenade->SetMoveType( MOVETYPE_FLYGRAVITY );
pGrenade->m_hOwner = npc;
pGrenade->m_pMyWeaponAR2 = this;
pGrenade->SetDamage(sk_npc_dmg_ar2_grenade.GetFloat());
// FIXME: arrgg ,this is hard coded into the weapon???
m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
m_iClip2--;
}
break;
*/
default:
BaseClass::Operator_HandleAnimEvent(pEvent, pOperator);
break;
}
}
void CWeaponCustom::ShootBullets(bool isPrimary, bool usePrimaryAmmo)
{
// Only the player fires this way so we can cast
CBasePlayer *pPlayer = ToBasePlayer(GetOwner());
if (!pPlayer)
return;
if (isPrimary || usePrimaryAmmo)
{
// Abort here to handle burst and auto fire modes
if ((UsesClipsForAmmo1() && m_iClip1 == 0) || (!UsesClipsForAmmo1() && !pPlayer->GetAmmoCount(m_iPrimaryAmmoType)))
return;
}
else
{
// Abort here to handle burst and auto fire modes
if ((UsesClipsForAmmo2() && m_iClip2 == 0) || (!UsesClipsForAmmo2() && !pPlayer->GetAmmoCount(m_iSecondaryAmmoType)))
return;
}
m_nShotsFired++;
pPlayer->DoMuzzleFlash();
// To make the firing framerate independent, we may have to fire more than one bullet here on low-framerate systems,
// especially if the weapon we're firing has a really fast rate of fire.
int iBulletsToFire = 0;
//float fireRate = (isPrimary) ? GetFireRate() : this->GetWpnData().m_sSecondaryFireRate;
// MUST call sound before removing a round from the clip of a CHLMachineGun
while (m_flNextPrimaryAttack <= gpGlobals->curtime)
{
if (isPrimary)
WeaponSound(SINGLE, m_flNextPrimaryAttack);
else
WeaponSound(WPN_DOUBLE, m_flNextPrimaryAttack);
//m_flNextPrimaryAttack = m_flNextPrimaryAttack + fireRate;
iBulletsToFire++;
}
if (isPrimary || usePrimaryAmmo)
{
m_iPrimaryAttacks++;
// Make sure we don't fire more than the amount in the clip, if this weapon uses clips
if (UsesClipsForAmmo1())
{
if (iBulletsToFire > m_iClip1)
iBulletsToFire = m_iClip1;
m_iClip1 -= iBulletsToFire;
}
}
else
{
m_iSecondaryAttacks++;
// Make sure we don't fire more than the amount in the clip, if this weapon uses clips
if (UsesClipsForAmmo2())
{
if (iBulletsToFire > m_iClip2)
iBulletsToFire = m_iClip2;
m_iClip2 -= iBulletsToFire;
}
}
gamestats->Event_WeaponFired(pPlayer, isPrimary, GetClassname());
// Fire the bullets
FireBulletsInfo_t info;
/*if (isPrimary)
{
info.m_iShots = this->GetWpnData().m_sPrimaryShotCount;
info.m_flDamage = this->GetWpnData().m_sPrimaryDamage;
info.m_iPlayerDamage = this->GetWpnData().m_sPrimaryDamage;
info.m_vecSpread = GetBulletSpreadPrimary();
info.m_iPenetrationCount = this->GetWpnData().m_iPrimaryPenetrateCount;
info.m_flPenetrationForce = this->GetWpnData().m_flPrimaryPenetrateDepth;
}
else
{
info.m_iShots = this->GetWpnData().m_sSecondaryShotCount;
info.m_flDamage = this->GetWpnData().m_sSecondaryDamage;
info.m_iPlayerDamage = this->GetWpnData().m_sSecondaryDamage;
info.m_vecSpread = GetBulletSpreadSecondary();
info.m_iPenetrationCount = this->GetWpnData().m_iSecondaryPenetrateCount;
info.m_flPenetrationForce = this->GetWpnData().m_flSecondaryPenetrateDepth;
}*/
info.m_vecSrc = pPlayer->Weapon_ShootPosition();
info.m_vecDirShooting = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT);
info.m_flDistance = MAX_TRACE_LENGTH;
info.m_iAmmoType = m_iPrimaryAmmoType;
info.m_iTracerFreq = 2;
FireBullets(info);
//Factor in the view kick
AddViewKick();
CSoundEnt::InsertSound(SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pPlayer);
SendWeaponAnim(GetPrimaryAttackActivity());
pPlayer->SetAnimation(PLAYER_ATTACK1);
// Register a muzzleflash for the AI
pPlayer->SetMuzzleFlashTime(gpGlobals->curtime + 0.5);
}
#ifdef HL2_DLL
extern int g_interactionPlayerLaunchedRPG;
#endif
void CWeaponCustom::ShootProjectile(bool isPrimary, bool usePrimaryAmmo)
{
// Can't have an active missile out
if (m_hMissile != NULL)
return;
// Can't be reloading
if (GetActivity() == ACT_VM_RELOAD)
return;
Vector vecOrigin;
Vector vecForward;
m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f;
CBasePlayer *pOwner = ToBasePlayer(GetOwner());
if (pOwner == NULL)
return;
Vector vForward, vRight, vUp;
pOwner->EyeVectors(&vForward, &vRight, &vUp);
Vector muzzlePoint = pOwner->Weapon_ShootPosition() + vForward * 12.0f + vRight * 6.0f + vUp * -3.0f;
QAngle vecAngles;
VectorAngles(vForward, vecAngles);
m_hMissile = CMissile::Create(muzzlePoint, vecAngles, GetOwner()->edict());
// m_hMissile->m_hOwner = this;
// If the shot is clear to the player, give the missile a grace period
trace_t tr;
Vector vecEye = pOwner->EyePosition();
UTIL_TraceLine(vecEye, vecEye + vForward * 128, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
if (tr.fraction == 1.0)
{
m_hMissile->SetGracePeriod(0.3);
}
// Register a muzzleflash for the AI
pOwner->SetMuzzleFlashTime(gpGlobals->curtime + 0.5);
SendWeaponAnim(ACT_VM_PRIMARYATTACK);
WeaponSound(SINGLE);
pOwner->RumbleEffect(RUMBLE_SHOTGUN_SINGLE, 0, RUMBLE_FLAG_RESTART);
m_iPrimaryAttacks++;
gamestats->Event_WeaponFired(pOwner, true, GetClassname());
CSoundEnt::InsertSound(SOUND_COMBAT, GetAbsOrigin(), 1000, 0.2, GetOwner(), SOUNDENT_CHANNEL_WEAPON);
// Check to see if we should trigger any RPG firing triggers
//Yes, these now work.
int iCount = g_hWeaponFireTriggers.Count();
for (int i = 0; i < iCount; i++)
{
if (g_hWeaponFireTriggers[i]->IsTouching(pOwner))
{
if (FClassnameIs(g_hWeaponFireTriggers[i], "trigger_rpgfire"))
{
g_hWeaponFireTriggers[i]->ActivateMultiTrigger(pOwner);
}
}
}
if (hl2_episodic.GetBool())
{
CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
int nAIs = g_AI_Manager.NumAIs();
string_t iszStriderClassname = AllocPooledString("npc_strider");
for (int i = 0; i < nAIs; i++)
{
if (ppAIs[i]->m_iClassname == iszStriderClassname)
{
ppAIs[i]->DispatchInteraction(g_interactionPlayerLaunchedRPG, NULL, m_hMissile);
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Activity
//-----------------------------------------------------------------------------
Activity CWeaponCustom::GetPrimaryAttackActivity(void)
{
if (m_nShotsFired < 2)
return ACT_VM_PRIMARYATTACK;
if (m_nShotsFired < 3)
return ACT_VM_RECOIL1;
if (m_nShotsFired < 4)
return ACT_VM_RECOIL2;
return ACT_VM_RECOIL3;
}
void CWeaponCustom::PrimaryAttack(void)
{
/*if (this->GetWpnData().m_sPrimaryMissleEnabled)
this->ShootProjectile(true, true);
else if (IsPrimaryBullet() == true)
ShootBullets(true, true);*/
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CWeaponCustom::Reload(void)
{
bool fRet;
float fCacheTime = m_flNextSecondaryAttack;
fRet = DefaultReload(GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD);
if (fRet)
{
// Undo whatever the reload process has done to our secondary
// attack timer. We allow you to interrupt reloading to fire
// a grenade.
m_flNextSecondaryAttack = GetOwner()->m_flNextAttack = fCacheTime;
WeaponSound(RELOAD);
}
return fRet;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponCustom::AddViewKick(void)
{
#define EASY_DAMPEN 0.5f
#define MAX_VERTICAL_KICK 1.0f //Degrees
#define SLIDE_LIMIT 2.0f //Seconds
//Get the view kick
CBasePlayer *pPlayer = ToBasePlayer(GetOwner());
if (pPlayer == NULL)
return;
DoMachineGunKick(pPlayer, EASY_DAMPEN, MAX_VERTICAL_KICK, m_fFireDuration, SLIDE_LIMIT);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponCustom::SecondaryAttack(void)
{
/*if (this->GetWpnData().m_sSecondaryMissleEnabled)
this->ShootProjectile(false, this->GetWpnData().m_sUsePrimaryAmmo);
else if (IsSecondaryBullet())
ShootBullets(false, this->GetWpnData().m_sUsePrimaryAmmo);*/
}
#define COMBINE_MIN_GRENADE_CLEAR_DIST 256
//-----------------------------------------------------------------------------
// Purpose:
// Input : flDot -
// flDist -
// Output : int
//-----------------------------------------------------------------------------
int CWeaponCustom::WeaponRangeAttack2Condition(float flDot, float flDist)
{
return COND_NONE;
}
//-----------------------------------------------------------------------------
const WeaponProficiencyInfo_t *CWeaponCustom::GetProficiencyValues()
{
static WeaponProficiencyInfo_t proficiencyTable[] =
{
{ 7.0, 0.75 },
{ 5.00, 0.75 },
{ 10.0 / 3.0, 0.75 },
{ 5.0 / 3.0, 0.75 },
{ 1.00, 1.0 },
};
COMPILE_TIME_ASSERT(ARRAYSIZE(proficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1);
return proficiencyTable;
}