2699 lines
79 KiB
C++
2699 lines
79 KiB
C++
#include "cbase.h"
|
|
|
|
#ifdef CLIENT_DLL
|
|
#include "c_asw_marine.h"
|
|
#include "c_asw_weapon.h"
|
|
#include "c_asw_player.h"
|
|
#include "c_asw_marine_resource.h"
|
|
#include "iasw_client_vehicle.h"
|
|
#include "c_te_effect_dispatch.h"
|
|
#include "c_asw_simple_alien.h"
|
|
#include "c_asw_fx.h"
|
|
#include "c_asw_pickup_weapon.h"
|
|
#include "c_asw_shieldbug.h"
|
|
#include "c_asw_hack.h"
|
|
#define CASW_Simple_Alien C_ASW_Simple_Alien
|
|
#define CASW_Pickup_Weapon C_ASW_Pickup_Weapon
|
|
#define CAI_BaseNPC C_AI_BaseNPC
|
|
#else
|
|
#include "te_effect_dispatch.h"
|
|
#include "asw_marine.h"
|
|
#include "asw_weapon.h"
|
|
#include "asw_player.h"
|
|
#include "asw_marine_resource.h"
|
|
#include "iasw_vehicle.h"
|
|
#include "iservervehicle.h"
|
|
#include "asw_simple_alien.h"
|
|
#include "asw_pickup_weapon.h"
|
|
#include "asw_alien.h"
|
|
#include "asw_hack.h"
|
|
#endif
|
|
#include "game_timescale_shared.h"
|
|
#include "asw_marine_gamemovement.h"
|
|
#include "asw_trace_filter_melee.h"
|
|
#include "asw_gamerules.h"
|
|
#include "ai_debug_shared.h"
|
|
#include "takedamageinfo.h"
|
|
#include "decals.h"
|
|
#include "shot_manipulator.h"
|
|
#include "effect_dispatch_data.h"
|
|
#include "asw_marine_profile.h"
|
|
#include "asw_remote_turret_shared.h"
|
|
#include "obstacle_pushaway.h"
|
|
#include "bone_setup.h"
|
|
#include "ammodef.h"
|
|
#include "asw_equipment_list.h"
|
|
#include "asw_weapon_parse.h"
|
|
#include "fmtstr.h"
|
|
#include "collisionutils.h"
|
|
#include "coordsize.h"
|
|
#include "asw_melee_system.h"
|
|
#include "eventlist.h"
|
|
#include "particle_parse.h"
|
|
#include "asw_trace_filter_shot.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
// the amount of damage required to make an alien always gib
|
|
#define ASW_GIB_DAMAGE_LIMIT 200
|
|
|
|
extern ConVar ai_debug_shoot_positions;
|
|
extern ConVar asw_melee_debug;
|
|
extern ConVar asw_debug_marine_damage;
|
|
extern ConVar asw_stim_time_scale;
|
|
extern ConVar asw_marine_ff;
|
|
ConVar asw_leadership_radius("asw_leadership_radius", "600", FCVAR_REPLICATED, "Radius of the leadership field around NCOs with the leadership skill");
|
|
ConVar asw_marine_speed_scale_easy("asw_marine_speed_scale_easy", "0.96", FCVAR_REPLICATED);
|
|
ConVar asw_marine_speed_scale_normal("asw_marine_speed_scale_normal", "1.0", FCVAR_REPLICATED);
|
|
ConVar asw_marine_speed_scale_hard("asw_marine_speed_scale_hard", "1.0", FCVAR_REPLICATED);
|
|
ConVar asw_marine_speed_scale_insane("asw_marine_speed_scale_insane", "1.0", FCVAR_REPLICATED);
|
|
ConVar asw_marine_box_collision("asw_marine_box_collision", "1", FCVAR_REPLICATED);
|
|
ConVar asw_allow_hull_shots("asw_allow_hull_shots", "1", FCVAR_REPLICATED);
|
|
#ifdef GAME_DLL
|
|
extern ConVar ai_show_hull_attacks;
|
|
ConVar asw_melee_knockback_up_force( "asw_melee_knockback_up_force", "1.0", FCVAR_CHEAT );
|
|
#endif
|
|
|
|
static const float ASW_MARINE_MELEE_HULL_TRACE_Z = 32.0f;
|
|
|
|
bool CASW_Marine::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex /*=0*/ )
|
|
{
|
|
if ( GetHealth() <= 0 )
|
|
return false;
|
|
|
|
// check we're actually carrying this weapon in one of our 3 marine slots
|
|
if (GetASWWeapon(0)!= pWeapon && GetASWWeapon(1) != pWeapon && GetASWWeapon(2) != pWeapon)
|
|
return false;
|
|
|
|
if (BaseClass::Weapon_Switch( pWeapon, viewmodelindex ))
|
|
{
|
|
if (pWeapon != GetLastWeaponSwitchedTo() && ASWGameRules() && ASWGameRules()->GetGameState() >= ASW_GS_INGAME )
|
|
{
|
|
m_hLastWeaponSwitchedTo = pWeapon;
|
|
DoAnimationEvent( PLAYERANIMEVENT_WEAPON_SWITCH );
|
|
}
|
|
|
|
CASW_Weapon* pASWWeapon = dynamic_cast<CASW_Weapon*>(pWeapon);
|
|
if (pASWWeapon)
|
|
{
|
|
pASWWeapon->ApplyWeaponSwitchTime();
|
|
}
|
|
|
|
#ifndef CLIENT_DLL
|
|
CheckAndRequestAmmo();
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// allow switching to weapons with no ammo
|
|
bool CASW_Marine::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon )
|
|
{
|
|
//if ( !pWeapon->HasAnyAmmo() && !GetAmmoCount( pWeapon->m_iPrimaryAmmoType ) )
|
|
//return false;
|
|
CASW_Weapon* pASWWeapon = dynamic_cast<CASW_Weapon*>(pWeapon);
|
|
if (pASWWeapon && !pASWWeapon->ASWCanBeSelected())
|
|
return false;
|
|
|
|
// disallow selection of offhand item
|
|
if ( pASWWeapon->GetWeaponInfo() && pASWWeapon->GetWeaponInfo()->m_bExtra )
|
|
return false;
|
|
|
|
if ( !pWeapon->CanDeploy() )
|
|
return false;
|
|
|
|
if ( GetActiveWeapon() )
|
|
{
|
|
if ( !GetActiveWeapon()->CanHolster() )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const QAngle& CASW_Marine::ASWEyeAngles( void )
|
|
{
|
|
if ( GetCommander() && IsInhabited() )
|
|
return GetCommander()->EyeAngles();
|
|
|
|
#ifdef CLIENT_DLL
|
|
m_AIEyeAngles = EyeAngles();
|
|
m_AIEyeAngles[PITCH] = m_fAIPitch;
|
|
return m_AIEyeAngles;
|
|
#endif
|
|
|
|
return EyeAngles();
|
|
}
|
|
|
|
CASW_Weapon* CASW_Marine::GetASWWeapon(int index) const
|
|
{
|
|
CASW_Weapon* pWeapon = assert_cast<CASW_Weapon*>(GetWeapon(index));
|
|
return pWeapon;
|
|
}
|
|
|
|
bool CASW_Marine::IsInfested()
|
|
{
|
|
if (!GetMarineResource())
|
|
return false;
|
|
|
|
return GetMarineResource()->IsInfested();
|
|
}
|
|
|
|
// forces marine to look towards a certain point
|
|
void CASW_Marine::SetFacingPoint(const Vector &vec, float fDuration)
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
m_vecFacingPoint = vec;
|
|
#else
|
|
m_vecFacingPointFromServer = vec;
|
|
#endif
|
|
m_fStopFacingPointTime = gpGlobals->curtime + fDuration;
|
|
}
|
|
|
|
// tick emotes, if server sets any emote bool to true, it causes the emote timer
|
|
// to go to 2.0 on all machines and tick down to zero (whereupon it gets set to false again)
|
|
void CASW_Marine::TickEmotes(float d)
|
|
{
|
|
bEmoteMedic = TickEmote(d, bEmoteMedic, bClientEmoteMedic, fEmoteMedicTime);
|
|
bEmoteAmmo = TickEmote(d, bEmoteAmmo, bClientEmoteAmmo, fEmoteAmmoTime);
|
|
bEmoteSmile = TickEmote(d, bEmoteSmile, bClientEmoteSmile, fEmoteSmileTime);
|
|
bEmoteStop = TickEmote(d, bEmoteStop, bClientEmoteStop, fEmoteStopTime);
|
|
bEmoteGo = TickEmote(d, bEmoteGo, bClientEmoteGo, fEmoteGoTime);
|
|
bEmoteExclaim = TickEmote(d, bEmoteExclaim, bClientEmoteExclaim, fEmoteExclaimTime);
|
|
bEmoteAnimeSmile = TickEmote(d, bEmoteAnimeSmile, bClientEmoteAnimeSmile, fEmoteAnimeSmileTime);
|
|
bEmoteQuestion = TickEmote(d, bEmoteQuestion, bClientEmoteQuestion, fEmoteQuestionTime);
|
|
}
|
|
|
|
bool CASW_Marine::TickEmote(float d, bool bEmote, bool& bClientEmote, float& fEmoteTime)
|
|
{
|
|
if (bEmote != bClientEmote)
|
|
{
|
|
bClientEmote = bEmote;
|
|
if (bEmote) // started an emote
|
|
fEmoteTime = 2.0;
|
|
}
|
|
|
|
if (bEmote) // if we're doing an emote, tick down the emote timer
|
|
{
|
|
fEmoteTime -= d * 2;
|
|
if (fEmoteTime <= 0)
|
|
bEmote = false;
|
|
}
|
|
return bEmote;
|
|
}
|
|
|
|
// asw fixme to be + eye height (crouch/no)
|
|
Vector CASW_Marine::EyePosition( void )
|
|
{
|
|
// if we're driving, return the position of our vehicle
|
|
if (IsInVehicle())
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
if (GetClientsideVehicle() && GetClientsideVehicle()->GetEntity())
|
|
return GetClientsideVehicle()->GetEntity()->GetAbsOrigin();
|
|
#endif
|
|
if (GetASWVehicle() && GetASWVehicle()->GetEntity())
|
|
return GetASWVehicle()->GetEntity()->GetAbsOrigin();
|
|
}
|
|
//if (IsControllingTurret())
|
|
//{
|
|
//return GetRemoteTurret()->GetTurretCamPosition();
|
|
//}
|
|
#ifdef CLIENT_DLL
|
|
//if (m_bUseLastRenderedEyePosition)
|
|
//return m_vecLastRenderedPos + GetViewOffset();
|
|
return GetRenderOrigin() + GetViewOffset();
|
|
#else
|
|
return GetAbsOrigin() + GetViewOffset();
|
|
#endif
|
|
}
|
|
|
|
void CASW_Marine::AvoidPhysicsProps( CUserCmd *pCmd )
|
|
{
|
|
#ifndef _XBOX
|
|
// Don't avoid if noclipping or in movetype none
|
|
switch ( GetMoveType() )
|
|
{
|
|
case MOVETYPE_NOCLIP:
|
|
case MOVETYPE_NONE:
|
|
case MOVETYPE_OBSERVER:
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
AvoidPushawayProps( this, pCmd );
|
|
#endif
|
|
}
|
|
|
|
Vector CASW_Marine::Weapon_ShootPosition( )
|
|
{
|
|
Vector forward, right, up, v;
|
|
|
|
v = GetAbsOrigin();
|
|
|
|
if (IsInVehicle() && GetASWVehicle() && GetASWVehicle()->GetEntity())
|
|
{
|
|
v = GetASWVehicle()->GetEntity()->GetAbsOrigin();
|
|
#ifdef CLIENT_DLL
|
|
if (gpGlobals->maxClients>1 && GetClientsideVehicle() && GetClientsideVehicle()->GetEntity())
|
|
v = GetClientsideVehicle()->GetEntity()->GetAbsOrigin();
|
|
#endif
|
|
}
|
|
|
|
QAngle ang = ASWEyeAngles();
|
|
ang.x = 0; // clear out pitch, so we're matching the fixes point of our autoaim calcs
|
|
AngleVectors( ang, &forward, &right, &up );
|
|
v = v + up * ASW_MARINE_GUN_OFFSET_Z;
|
|
Vector vecSrc = v
|
|
+ forward * ASW_MARINE_GUN_OFFSET_X
|
|
+ right * ASW_MARINE_GUN_OFFSET_Y;
|
|
|
|
trace_t tr;
|
|
UTIL_TraceLine(v, vecSrc, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
|
|
if (tr.fraction < 1.0f)
|
|
return tr.endpos;
|
|
|
|
return vecSrc;
|
|
}
|
|
|
|
float CASW_Marine::MaxSpeed()
|
|
{
|
|
float speed = 300;
|
|
if (GetMarineResource())
|
|
{
|
|
speed = MarineSkills()->GetSkillBasedValueByMarine(this, ASW_MARINE_SKILL_AGILITY);
|
|
}
|
|
float speedscale = 1.0f;
|
|
// half speed if we're firing or reloading
|
|
if (GetActiveASWWeapon())
|
|
speedscale = GetActiveASWWeapon()->GetMovementScale();
|
|
|
|
// adjust the speed by difficulty level
|
|
switch (ASWGameRules()->GetSkillLevel())
|
|
{
|
|
case 4: speedscale *= asw_marine_speed_scale_insane.GetFloat(); break;
|
|
case 3: speedscale *= asw_marine_speed_scale_hard.GetFloat(); break;
|
|
case 2: speedscale *= asw_marine_speed_scale_normal.GetFloat(); break;
|
|
default: speedscale *= asw_marine_speed_scale_easy.GetFloat(); break;
|
|
}
|
|
|
|
// if we're an AI following someone, boost our speed as we get far away.
|
|
// it's a quadratic interpolation from 110% speed to 175% speed mapped
|
|
// to a distance of 20 units to 1800 units
|
|
CBaseEntity *pSquadLeader = GetSquadLeader();
|
|
if (!IsInhabited() && pSquadLeader )
|
|
{
|
|
float distSq = GetAbsOrigin().DistToSqr(pSquadLeader->GetAbsOrigin());
|
|
float t = (distSq - (20*20)) / (1800*1800);
|
|
float q = ( 1.750f - 1.100f ) * t + 1.100f;
|
|
speedscale *= clamp<float>( q, 1.000f, 1.750f );
|
|
}
|
|
|
|
// here's where we account for increased speed via powerup
|
|
if ( m_iPowerupType == POWERUP_TYPE_INCREASED_SPEED )
|
|
speedscale *= 1.75;
|
|
|
|
// speed up as time slows down
|
|
float flTimeDifference = 0.0f;
|
|
|
|
if ( gpGlobals->curtime > ASWGameRules()->GetStimEndTime() )
|
|
{
|
|
flTimeDifference = 1.0f - MAX( asw_stim_time_scale.GetFloat(), GameTimescale()->GetCurrentTimescale() );
|
|
}
|
|
|
|
if ( flTimeDifference > 0.0f )
|
|
{
|
|
speedscale *= 1.0f + flTimeDifference * 0.75f;
|
|
}
|
|
|
|
return speed * speedscale;
|
|
}
|
|
|
|
// ammo in weapons this marine is carrying
|
|
int CASW_Marine::GetWeaponAmmoCount( int iAmmoIndex )
|
|
{
|
|
// find how much is in guns we're carrying
|
|
int iWeapons = WeaponCount();
|
|
int iWeaponAmmo = 0;
|
|
for (int i=0;i<iWeapons;i++)
|
|
{
|
|
CASW_Weapon* pWeapon = GetASWWeapon(i);
|
|
if (pWeapon && pWeapon->GetPrimaryAmmoType() == iAmmoIndex)
|
|
iWeaponAmmo += pWeapon->Clip1();
|
|
if (pWeapon && pWeapon->GetSecondaryAmmoType() == iAmmoIndex)
|
|
iWeaponAmmo += pWeapon->Clip2();
|
|
}
|
|
|
|
return iWeaponAmmo;
|
|
}
|
|
|
|
// include ammo in weapons this marine is carrying
|
|
int CASW_Marine::GetWeaponAmmoCount( char *szName )
|
|
{
|
|
return GetWeaponAmmoCount( GetAmmoDef()->Index(szName) );
|
|
}
|
|
|
|
// include ammo in weapons this marine is carrying
|
|
int CASW_Marine::GetTotalAmmoCount( int iAmmoIndex )
|
|
{
|
|
// find how much is in held ammo
|
|
int iSpareAmmo = GetAmmoCount(iAmmoIndex);
|
|
|
|
// find how much is in guns we're carrying
|
|
int iWeaponAmmo = GetWeaponAmmoCount(iAmmoIndex);;
|
|
|
|
return iSpareAmmo + iWeaponAmmo;
|
|
}
|
|
|
|
int CASW_Marine::GetAllAmmoCount( void )
|
|
{
|
|
int nCount = 0;
|
|
|
|
for ( int i = 0; i < MAX_AMMO_TYPES; ++i )
|
|
{
|
|
nCount += GetTotalAmmoCount( i );
|
|
}
|
|
|
|
return nCount;
|
|
}
|
|
|
|
// include ammo in weapons this marine is carrying
|
|
int CASW_Marine::GetTotalAmmoCount( char *szName )
|
|
{
|
|
return GetTotalAmmoCount( GetAmmoDef()->Index(szName) );
|
|
}
|
|
|
|
// controlling a turret
|
|
bool CASW_Marine::IsControllingTurret()
|
|
{
|
|
return (m_hRemoteTurret.Get()!=NULL);
|
|
}
|
|
|
|
CASW_Remote_Turret* CASW_Marine::GetRemoteTurret()
|
|
{
|
|
return m_hRemoteTurret.Get();
|
|
}
|
|
|
|
const char *CASW_Marine::GetPlayerName() const
|
|
{
|
|
CASW_Player *pPlayer = GetCommander();
|
|
if ( !pPlayer )
|
|
{
|
|
return BaseClass::GetPlayerName();
|
|
}
|
|
|
|
return pPlayer->GetPlayerName();
|
|
}
|
|
|
|
CASW_Player* CASW_Marine::GetCommander() const
|
|
{
|
|
return dynamic_cast<CASW_Player*>(m_Commander.Get());
|
|
}
|
|
|
|
bool CASW_Marine::IsInhabited()
|
|
{
|
|
if (!GetMarineResource())
|
|
return false;
|
|
return GetMarineResource()->IsInhabited();
|
|
}
|
|
|
|
void CASW_Marine::DoDamagePowerupEffects( CBaseEntity *pTarget, CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
|
|
{
|
|
#ifdef GAME_DLL
|
|
m_iDamageAttributeEffects = 0;
|
|
|
|
if ( m_iPowerupType < 0 )
|
|
return;
|
|
|
|
switch ( m_iPowerupType )
|
|
{
|
|
case POWERUP_TYPE_FREEZE_BULLETS:
|
|
m_iDamageAttributeEffects |= BULLET_ATT_FREEZE;
|
|
//EmitSound( "ASW_Weapon_Crit.Freeze" );
|
|
break;
|
|
case POWERUP_TYPE_FIRE_BULLETS:
|
|
m_iDamageAttributeEffects |= BULLET_ATT_FIRE;
|
|
//EmitSound( "ASW_Weapon_Crit.Flame" );
|
|
break;
|
|
//case POWERUP_TYPE_CHEMICAL_BULLETS:
|
|
// m_iDamageAttributeEffects |= BULLET_ATT_CHEMICAL;
|
|
// EmitSound( "ASW_Weapon_Crit.Normal" );
|
|
// break;
|
|
//case POWERUP_TYPE_EXPLOSIVE_BULLETS:
|
|
// m_iDamageAttributeEffects |= BULLET_ATT_EXPLODE;
|
|
// EmitSound( "ASW_Weapon_Crit.Normal" );
|
|
// break;
|
|
case POWERUP_TYPE_ELECTRIC_BULLETS:
|
|
m_iDamageAttributeEffects |= BULLET_ATT_ELECTRIC;
|
|
//EmitSound( "ASW_Weapon_Crit.Electric" );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ( m_iDamageAttributeEffects <= 0 || !GetMarineProfile() )
|
|
return;
|
|
|
|
// if we have a bullet-based powerup,
|
|
// check if the weapon that has it is currently wielded
|
|
CASW_Weapon* pWeapon = GetActiveASWWeapon();
|
|
if ( !pWeapon || !pWeapon->m_bPoweredUp )
|
|
return;
|
|
|
|
info.ScaleDamage( 1.5f );
|
|
|
|
if ( m_iDamageAttributeEffects & BULLET_ATT_EXPLODE )
|
|
{
|
|
CBaseEntity *pHelpHelpImBeingSupressed = (CBaseEntity*) te->GetSuppressHost();
|
|
te->SetSuppressHost( NULL );
|
|
|
|
int iExplosionDamage = 16;
|
|
float flExplosionRadius = 40;
|
|
Vector vecExplosionPos = info.GetDamagePosition();
|
|
CPASFilter filter( vecExplosionPos );
|
|
int iFlags = TE_EXPLFLAG_SCALEPARTICLES;
|
|
iFlags |= TE_EXPLFLAG_NOSOUND;
|
|
|
|
if ( gpGlobals->curtime < m_flLastAttributeExplosionSound + 0.5f )
|
|
{
|
|
// no sound
|
|
}
|
|
else
|
|
{
|
|
m_flLastAttributeExplosionSound = gpGlobals->curtime;
|
|
CBaseEntity::EmitSound( filter, 0, "ASWGrenadeAmmo.Explode", &vecExplosionPos );
|
|
}
|
|
|
|
DispatchParticleEffect( "explosion_air_small", vecExplosionPos, vec3_angle );
|
|
|
|
RadiusDamage( CTakeDamageInfo( this, this, iExplosionDamage, DMG_BLAST ), vecExplosionPos, flExplosionRadius, CLASS_NONE, NULL );
|
|
|
|
te->SetSuppressHost( pHelpHelpImBeingSupressed );
|
|
}
|
|
|
|
|
|
if ( m_iDamageAttributeEffects & BULLET_ATT_FIRE )
|
|
{
|
|
IASW_Spawnable_NPC *pSpawnableNPC = dynamic_cast<IASW_Spawnable_NPC*>( pTarget );
|
|
if ( pSpawnableNPC )
|
|
{
|
|
//CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
//int iLevel = 1; //GetMarineProfile()->GetLevel();
|
|
//if ( pWeapon && pWeapon->GetAttributeContainer() && pWeapon->GetAttributeContainer()->GetItem() )
|
|
//{
|
|
// iLevel = pWeapon->GetAttributeContainer()->GetItem()->GetItemLevel();
|
|
//}
|
|
/*
|
|
static float flBaseFireDamage = 10.0f;
|
|
static float flDamageChangePerLevel = 0.15f;
|
|
static float flFireDuration = 3.0f;
|
|
float flDamagePerSecond = ( ( iLevel - 1 ) * flDamageChangePerLevel * flBaseFireDamage + flBaseFireDamage ) / flFireDuration;
|
|
|
|
|
|
// TODO: merge over the new ASW_Ignite to account for DPS?
|
|
pSpawnableNPC->ASW_Ignite( flFireDuration, flDamagePerSecond, info.GetAttacker() );
|
|
*/
|
|
pSpawnableNPC->ASW_Ignite( 2.0f, 0, info.GetAttacker(), info.GetWeapon() );
|
|
}
|
|
}
|
|
|
|
if ( m_iDamageAttributeEffects & BULLET_ATT_FREEZE )
|
|
{
|
|
CAI_BaseNPC *pNPC = dynamic_cast<CAI_BaseNPC*>( pTarget );
|
|
if ( pNPC )
|
|
{
|
|
pNPC->Freeze( 100.0f, this );
|
|
}
|
|
}
|
|
|
|
/*
|
|
if ( m_iDamageAttributeEffects & BULLET_ATT_CHEMICAL )
|
|
{
|
|
CASW_Weapon_EffectVolume *pRad = (CASW_Weapon_EffectVolume*) CreateEntityByName("asw_weapon_effect_volume");
|
|
if (pRad)
|
|
{
|
|
pRad->SetAbsOrigin( info.GetDamagePosition() );
|
|
pRad->SetOwnerEntity( this );
|
|
pRad->SetOwnerWeapon( GetActiveASWWeapon() );
|
|
pRad->SetEffectFlag( ASW_WEV_RADIATION );
|
|
pRad->SetDuration( 3.0f );
|
|
pRad->Spawn();
|
|
|
|
// also spawn our big cloud marking out the area of radiation
|
|
CASW_Emitter *pEmitter = (CASW_Emitter*) CreateEntityByName("asw_emitter");
|
|
if ( pEmitter )
|
|
{
|
|
pEmitter->UseTemplate("radcloud1");
|
|
pEmitter->SetAbsOrigin( info.GetDamagePosition() );
|
|
pEmitter->Spawn();
|
|
pEmitter->SetDieTime( gpGlobals->curtime + 3.0f );
|
|
}
|
|
|
|
// play the geiger sound for 10 seconds
|
|
const char *pszSound = "Misc.Geiger";
|
|
float fRadPitch = random->RandomInt( 95, 105 );
|
|
CPASAttenuationFilter filter( this );
|
|
CSoundPatch *pPatch = CSoundEnvelopeController::GetController().SoundCreate( filter, entindex(), CHAN_STATIC, pszSound, ATTN_NORM );
|
|
CSoundEnvelopeController::GetController().Play( pPatch, 1.0, fRadPitch );
|
|
CSoundEnvelopeController::GetController().CommandAdd( pPatch, 1.0f, SOUNDCTRL_CHANGE_VOLUME, 1.0f, 0.0f );
|
|
CSoundEnvelopeController::GetController().CommandAdd( pPatch, 2.0f, SOUNDCTRL_DESTROY, 0.0f, 0.0f );
|
|
}
|
|
}
|
|
*/
|
|
|
|
if ( m_iDamageAttributeEffects & BULLET_ATT_ELECTRIC )
|
|
{
|
|
int damageType = info.GetDamageType();
|
|
damageType |= DMG_SHOCK;
|
|
info.SetDamageType( damageType );
|
|
|
|
// search other enemies near this one
|
|
int iMaxArcs = 1; // TODO: max arc should be attribute of the weapon? if we can combine two attribs into one somehow
|
|
CUtlVector<CBaseEntity*> shocked;
|
|
int iNumShocked = 0; // TODO: Use size of shocked vector for this?
|
|
shocked.AddToTail( pTarget );
|
|
float flArcDistSqr = 300.0f * 300.0f;
|
|
float flShockDamage = info.GetDamage(); // TODO: base on attribute?
|
|
float flDamageReduction = flShockDamage / (float)iMaxArcs; // how much damage each arc loses
|
|
Vector vecShockSrc = info.GetDamagePosition();
|
|
|
|
CBaseEntity *pLastShocked = pTarget;
|
|
pLastShocked->EmitSound( "Electricity.Zap" );
|
|
|
|
CASW_Alien *pAlien = dynamic_cast<CASW_Alien*>( pTarget );
|
|
if ( pAlien )
|
|
pAlien->ForceFlinch( vecShockSrc );
|
|
|
|
for ( int k = 0; k < iMaxArcs; k++ )
|
|
{
|
|
CAI_BaseNPC ** ppAIs = g_AI_Manager.AccessAIs();
|
|
int nAIs = g_AI_Manager.NumAIs();
|
|
bool bShocked = false;
|
|
|
|
for ( int i = 0; i < nAIs; i++ )
|
|
{
|
|
if ( shocked.Find( ppAIs[i] ) != shocked.InvalidIndex() ) // already shocked this guy
|
|
continue;
|
|
|
|
if ( IRelationType( ppAIs[i] ) >= D_LI ) // friendly
|
|
continue;
|
|
|
|
if ( ppAIs[i]->GetAbsOrigin().DistToSqr( vecShockSrc ) > flArcDistSqr ) // too far away
|
|
continue;
|
|
|
|
// trace from the last shock position to this guy
|
|
trace_t shockTR;
|
|
Vector vecAIPos = ppAIs[i]->WorldSpaceCenter();
|
|
CASWTraceFilterShot traceFilter( this, pLastShocked, COLLISION_GROUP_NONE );
|
|
AI_TraceLine( vecShockSrc, vecAIPos, MASK_SHOT, &traceFilter, &shockTR );
|
|
|
|
if ( shockTR.fraction != 1.0 && shockTR.m_pEnt )
|
|
{
|
|
if ( IRelationType( shockTR.m_pEnt ) >= D_LI ) // friendly
|
|
continue;
|
|
|
|
// shock this thing
|
|
vecAIPos = shockTR.endpos;
|
|
CTakeDamageInfo shockDmgInfo( this, this, flShockDamage, DMG_SHOCK );
|
|
Vector vecDir = vecAIPos - vecShockSrc;
|
|
VectorNormalize( vecDir );
|
|
shockDmgInfo.SetDamagePosition( shockTR.endpos );
|
|
shockDmgInfo.SetDamageForce( vecDir );
|
|
shockDmgInfo.ScaleDamageForce( 1.0 );
|
|
shockDmgInfo.SetAmmoType( info.GetAmmoType() );
|
|
|
|
shockTR.m_pEnt->DispatchTraceAttack( shockDmgInfo, vecDir, &shockTR );
|
|
CASW_Alien *pAlien = dynamic_cast<CASW_Alien*>( shockTR.m_pEnt );
|
|
if ( pAlien )
|
|
pAlien->ForceFlinch( shockTR.endpos );
|
|
|
|
// spawn a shock effect
|
|
// spawn a shock effect
|
|
CRecipientFilter filter;
|
|
filter.AddAllPlayers();
|
|
UserMessageBegin( filter, "ASWEnemyTeslaGunArcShock" );
|
|
WRITE_SHORT( shockTR.m_pEnt->entindex() );
|
|
WRITE_SHORT( pLastShocked->entindex() );
|
|
MessageEnd();
|
|
|
|
vecShockSrc = vecAIPos;
|
|
pLastShocked = shockTR.m_pEnt;
|
|
bShocked = true;
|
|
iNumShocked++;
|
|
|
|
// reduce shock damage as energy arcs
|
|
flShockDamage = MAX( flShockDamage - flDamageReduction, 0.0f );
|
|
|
|
break;
|
|
}
|
|
}
|
|
if ( !bShocked )
|
|
break;
|
|
}
|
|
if ( iNumShocked > 0 )
|
|
{
|
|
// Return reduced shock damage if we arced
|
|
info.SetDamage( flShockDamage );
|
|
}
|
|
}
|
|
#endif //GAME_DLL
|
|
}
|
|
|
|
void CASW_Marine::FireBullets( const FireBulletsInfo_t &info )
|
|
{
|
|
float fPiercingChance = 0;
|
|
if (GetMarineResource() && GetMarineProfile() && GetMarineProfile()->GetMarineClass() == MARINE_CLASS_SPECIAL_WEAPONS)
|
|
fPiercingChance = MarineSkills()->GetSkillBasedValueByMarine(this, ASW_MARINE_SKILL_PIERCING);
|
|
|
|
//if ( GetActiveWeapon() )
|
|
//{
|
|
//CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetActiveWeapon(), fPiercingChance, mod_piercing );
|
|
//}
|
|
|
|
if (fPiercingChance > 0)
|
|
{
|
|
FirePenetratingBullets(info, 1, fPiercingChance, 0 );
|
|
}
|
|
else
|
|
{
|
|
FireRegularBullets(info);
|
|
}
|
|
}
|
|
|
|
void CASW_Marine::FireRegularBullets( const FireBulletsInfo_t &info )
|
|
{
|
|
static int tracerCount;
|
|
trace_t tr;
|
|
CAmmoDef* pAmmoDef = GetAmmoDef();
|
|
int nDamageType = pAmmoDef->DamageType(info.m_iAmmoType);
|
|
int nAmmoFlags = pAmmoDef->Flags(info.m_iAmmoType);
|
|
|
|
#ifdef GAME_DLL
|
|
if (ASWGameRules())
|
|
ASWGameRules()->m_fLastFireTime = gpGlobals->curtime;
|
|
#endif
|
|
|
|
bool bDoServerEffects = true;
|
|
|
|
#if defined ( _XBOX ) && defined( GAME_DLL )
|
|
if( IsInhabited() )
|
|
{
|
|
CBasePlayer *pPlayer = GetCommander();
|
|
if (pPlayer)
|
|
{
|
|
|
|
int rumbleEffect = pPlayer->GetActiveWeapon()->GetRumbleEffect();
|
|
|
|
if( rumbleEffect != RUMBLE_INVALID )
|
|
{
|
|
if( rumbleEffect == RUMBLE_SHOTGUN_SINGLE )
|
|
{
|
|
if( info.m_iShots == 12 )
|
|
{
|
|
// Upgrade to double barrel rumble effect
|
|
rumbleEffect = RUMBLE_SHOTGUN_DOUBLE;
|
|
}
|
|
}
|
|
|
|
pPlayer->RumbleEffect( rumbleEffect, 0, RUMBLE_FLAG_RESTART );
|
|
}
|
|
}
|
|
}
|
|
#endif//_XBOX
|
|
|
|
float flPlayerDamage = info.m_flPlayerDamage;
|
|
if ( flPlayerDamage == 0 )
|
|
{
|
|
if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER )
|
|
{
|
|
flPlayerDamage = pAmmoDef->PlrDamage( info.m_iAmmoType );
|
|
}
|
|
}
|
|
|
|
// the default attacker is ourselves
|
|
CBaseEntity *pAttacker = info.m_pAttacker ? info.m_pAttacker : this;
|
|
|
|
// Make sure we don't have a dangling damage target from a recursive call
|
|
if ( g_MultiDamage.GetTarget() != NULL )
|
|
{
|
|
ApplyMultiDamage();
|
|
}
|
|
|
|
ClearMultiDamage();
|
|
g_MultiDamage.SetDamageType( nDamageType | DMG_NEVERGIB );
|
|
|
|
Vector vecDir;
|
|
Vector vecEnd;
|
|
Vector vecFinalDir; // bullet's final direction can be changed by passing through a portal
|
|
|
|
CASWTraceFilterShot traceFilter( this, info.m_pAdditionalIgnoreEnt, COLLISION_GROUP_NONE );
|
|
traceFilter.SetSkipMarines( false );
|
|
traceFilter.SetSkipRollingMarines( true );
|
|
|
|
int iSeed = CBaseEntity::GetPredictionRandomSeed();// & 255;
|
|
|
|
CShotManipulator Manipulator( info.m_vecDirShooting );
|
|
|
|
bool bDoImpacts = false;
|
|
bool bDoTracers = false;
|
|
|
|
for (int iShot = 0; iShot < info.m_iShots; iShot++)
|
|
{
|
|
bool bHitWater = false;
|
|
bool bHitGlass = false;
|
|
|
|
// Prediction is only usable on players
|
|
RandomSeed( iSeed + iShot ); // init random system with this seed
|
|
|
|
if (info.m_vecSpread[0] < 0) // weapon wants simple random spread, not gaussian
|
|
{
|
|
QAngle angChange(info.m_vecSpread[0] * random->RandomFloat(-0.5f, 0.5f),
|
|
info.m_vecSpread[1] * random->RandomFloat(-0.5f, 0.5f),
|
|
info.m_vecSpread[2] * random->RandomFloat(-0.5f, 0.5f));
|
|
matrix3x4_t matrix;
|
|
AngleMatrix( angChange, matrix );
|
|
VectorTransform(info.m_vecDirShooting, matrix, vecDir);
|
|
}
|
|
else
|
|
{
|
|
// If we're firing multiple shots, and the first shot has to be bang on target, ignore spread
|
|
if ( iShot == 0 && info.m_iShots > 1 && (info.m_nFlags & FIRE_BULLETS_FIRST_SHOT_ACCURATE) )
|
|
{
|
|
vecDir = Manipulator.GetShotDirection();
|
|
}
|
|
else
|
|
{
|
|
if (info.m_nFlags & FIRE_BULLETS_ANGULAR_SPREAD)
|
|
vecDir = Manipulator.ApplyAngularSpread( info.m_vecSpread );
|
|
else
|
|
vecDir = Manipulator.ApplySpread( info.m_vecSpread );
|
|
}
|
|
}
|
|
|
|
vecEnd = info.m_vecSrc + vecDir * info.m_flDistance;
|
|
|
|
if( (info.m_nFlags & FIRE_BULLETS_HULL) != 0 && asw_allow_hull_shots.GetBool())
|
|
{
|
|
// hulls that make it easier to hit targets with the shotgun.
|
|
AI_TraceHull( info.m_vecSrc, vecEnd, Vector( -3, -3, -3 ), Vector( 3, 3, 3 ), MASK_SHOT, &traceFilter, &tr );
|
|
}
|
|
else
|
|
{
|
|
AI_TraceLine(info.m_vecSrc, vecEnd, MASK_SHOT, &traceFilter, &tr);
|
|
}
|
|
|
|
vecFinalDir = tr.endpos - tr.startpos;
|
|
if (vecFinalDir == vec3_origin)
|
|
{
|
|
vecFinalDir = info.m_vecDirShooting;
|
|
}
|
|
VectorNormalize( vecFinalDir );
|
|
|
|
#ifdef GAME_DLL
|
|
if ( ai_debug_shoot_positions.GetBool() )
|
|
NDebugOverlay::Line(info.m_vecSrc, vecEnd, 255, 255, 255, false, .1 );
|
|
#endif
|
|
|
|
// Now hit all triggers along the ray that respond to shots...
|
|
// Clip the ray to the first collided solid returned from traceline
|
|
CTakeDamageInfo triggerInfo( pAttacker, pAttacker, info.m_flDamage, nDamageType );
|
|
CalculateBulletDamageForce( &triggerInfo, info.m_iAmmoType, vecFinalDir, tr.endpos );
|
|
triggerInfo.ScaleDamageForce( info.m_flDamageForceScale );
|
|
triggerInfo.SetAmmoType( info.m_iAmmoType );
|
|
#ifdef GAME_DLL
|
|
TraceAttackToTriggers( triggerInfo, tr.startpos, tr.endpos, vecFinalDir );
|
|
#endif
|
|
|
|
// Make sure given a valid bullet type
|
|
if (info.m_iAmmoType == -1)
|
|
{
|
|
DevMsg("ERROR: Undefined ammo type!\n");
|
|
return;
|
|
}
|
|
|
|
Vector vecTracerDest = tr.endpos;
|
|
|
|
// do damage, paint decals
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
#ifdef GAME_DLL
|
|
// For shots that don't need persistance
|
|
int soundEntChannel = ( info.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED;
|
|
|
|
CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, tr.endpos, 200, 0.5, this, soundEntChannel );
|
|
#endif
|
|
|
|
// See if the bullet ended up underwater + started out of the water
|
|
if ( !bHitWater && ( enginetrace->GetPointContents( tr.endpos ) & (CONTENTS_WATER|CONTENTS_SLIME) ) )
|
|
{
|
|
bHitWater = HandleShotImpactingWater( info, vecEnd, &traceFilter, &vecTracerDest );
|
|
}
|
|
|
|
float flActualDamage = info.m_flDamage;
|
|
|
|
// If we hit a player, and we have player damage specified, use that instead
|
|
// Adrian: Make sure to use the currect value if we hit a vehicle the player is currently driving.
|
|
if ( flPlayerDamage != 0.0f )
|
|
{
|
|
if ( tr.m_pEnt->IsPlayer() )
|
|
{
|
|
flActualDamage = flPlayerDamage;
|
|
}
|
|
#ifdef GAME_DLL
|
|
else if ( tr.m_pEnt->GetServerVehicle() )
|
|
{
|
|
if ( tr.m_pEnt->GetServerVehicle()->GetPassenger() && tr.m_pEnt->GetServerVehicle()->GetPassenger()->IsPlayer() )
|
|
{
|
|
flActualDamage = flPlayerDamage;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int nActualDamageType = nDamageType;
|
|
if ( flActualDamage == 0.0 )
|
|
{
|
|
flActualDamage = g_pGameRules->GetAmmoDamage( pAttacker, tr.m_pEnt, info.m_iAmmoType );
|
|
}
|
|
else
|
|
{
|
|
nActualDamageType = nDamageType | ((flActualDamage > ASW_GIB_DAMAGE_LIMIT) ? DMG_ALWAYSGIB : DMG_NEVERGIB );
|
|
}
|
|
|
|
if ( !bHitWater || ((info.m_nFlags & FIRE_BULLETS_DONT_HIT_UNDERWATER) == 0) )
|
|
{
|
|
// Damage specified by function parameter
|
|
CTakeDamageInfo dmgInfo( this, pAttacker, flActualDamage, nActualDamageType );
|
|
CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, vecFinalDir, tr.endpos );
|
|
dmgInfo.ScaleDamageForce( info.m_flDamageForceScale );
|
|
dmgInfo.SetAmmoType( info.m_iAmmoType );
|
|
dmgInfo.SetWeapon( GetActiveASWWeapon() );
|
|
DoDamagePowerupEffects( tr.m_pEnt, dmgInfo, vecFinalDir, &tr );
|
|
tr.m_pEnt->DispatchTraceAttack( dmgInfo, vecFinalDir, &tr );
|
|
|
|
if ( !bHitWater || (info.m_nFlags & FIRE_BULLETS_ALLOW_WATER_SURFACE_IMPACTS) )
|
|
{
|
|
if ( bDoServerEffects == true )
|
|
{
|
|
DoImpactEffect( tr, nDamageType );
|
|
}
|
|
else
|
|
{
|
|
bDoImpacts = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We may not impact, but we DO need to affect ragdolls on the client
|
|
CEffectData data;
|
|
data.m_vStart = tr.startpos;
|
|
data.m_vOrigin = tr.endpos;
|
|
data.m_nDamageType = nDamageType;
|
|
|
|
DispatchEffect( "RagdollImpact", data );
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
if ( nAmmoFlags & AMMO_FORCE_DROP_IF_CARRIED )
|
|
{
|
|
// Make sure if the player is holding this, he drops it
|
|
Pickup_ForcePlayerToDropThisObject( tr.m_pEnt );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// See if we hit glass
|
|
if ( tr.m_pEnt != NULL )
|
|
{
|
|
#ifdef GAME_DLL
|
|
surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps );
|
|
if ( ( psurf != NULL ) && ( psurf->game.material == CHAR_TEX_GLASS ) && ( tr.m_pEnt->ClassMatches( "func_breakable" ) ) )
|
|
{
|
|
bHitGlass = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ( ( info.m_iTracerFreq != 0 ) && ( tracerCount++ % info.m_iTracerFreq ) == 0 && ( bHitGlass == false ) )
|
|
{
|
|
if ( bDoServerEffects == true )
|
|
{
|
|
Vector vecTracerSrc = vec3_origin;
|
|
ComputeTracerStartPosition( info.m_vecSrc, &vecTracerSrc );
|
|
|
|
trace_t Tracer;
|
|
Tracer = tr;
|
|
Tracer.endpos = vecTracerDest;
|
|
|
|
MakeTracer( vecTracerSrc, Tracer, pAmmoDef->TracerType(info.m_iAmmoType) );
|
|
}
|
|
else
|
|
{
|
|
bDoTracers = true;
|
|
}
|
|
}
|
|
// See if we should pass through glass
|
|
#ifdef GAME_DLL
|
|
if ( bHitGlass )
|
|
{
|
|
HandleShotImpactingGlass( info, tr, vecFinalDir, &traceFilter );
|
|
}
|
|
#endif
|
|
|
|
//iSeed++;
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
ApplyMultiDamage();
|
|
#endif
|
|
}
|
|
|
|
// fire bullets that will pass through NPCs, potentially hurting a whole bunch in a row
|
|
void CASW_Marine::FirePenetratingBullets( const FireBulletsInfo_t &info, int iMaxPenetrate, float fPenetrateChance, int iSeedPlus, bool bAllowChange/*=true*/, Vector *pPiercingTracerEnd/*=NULL*/, bool bSegmentTracer/*=true*/ )
|
|
{
|
|
if (iMaxPenetrate < 0)
|
|
return;
|
|
|
|
#ifdef GAME_DLL
|
|
if (ASWGameRules())
|
|
ASWGameRules()->m_fLastFireTime = gpGlobals->curtime;
|
|
#endif
|
|
|
|
static int tracerCount;
|
|
trace_t tr;
|
|
CAmmoDef* pAmmoDef = GetAmmoDef();
|
|
int nDamageType = pAmmoDef->DamageType(info.m_iAmmoType);
|
|
int nAmmoFlags = pAmmoDef->Flags(info.m_iAmmoType);
|
|
|
|
Vector vecPiercingTracerEnd(0,0,0);
|
|
|
|
bool bDoServerEffects = true;
|
|
|
|
#if defined( HL2MP ) && defined( GAME_DLL )
|
|
bDoServerEffects = false;
|
|
#endif
|
|
|
|
#if defined ( _XBOX ) && defined( GAME_DLL )
|
|
if( IsPlayer() )
|
|
{
|
|
CBasePlayer *pPlayer = dynamic_cast<CBasePlayer*>(this);
|
|
|
|
int rumbleEffect = pPlayer->GetActiveWeapon()->GetRumbleEffect();
|
|
|
|
if( rumbleEffect != RUMBLE_INVALID )
|
|
{
|
|
if( rumbleEffect == RUMBLE_SHOTGUN_SINGLE )
|
|
{
|
|
if( info.m_iShots == 12 )
|
|
{
|
|
// Upgrade to double barrel rumble effect
|
|
rumbleEffect = RUMBLE_SHOTGUN_DOUBLE;
|
|
}
|
|
}
|
|
|
|
pPlayer->RumbleEffect( rumbleEffect, 0, RUMBLE_FLAG_RESTART );
|
|
}
|
|
}
|
|
#endif//_XBOX
|
|
|
|
float flPlayerDamage = info.m_flPlayerDamage;
|
|
if ( flPlayerDamage == 0 )
|
|
{
|
|
if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER )
|
|
{
|
|
flPlayerDamage = pAmmoDef->PlrDamage( info.m_iAmmoType );
|
|
}
|
|
}
|
|
|
|
// the default attacker is ourselves
|
|
CBaseEntity *pAttacker = info.m_pAttacker ? info.m_pAttacker : this;
|
|
|
|
// Make sure we don't have a dangling damage target from a recursive call
|
|
if ( g_MultiDamage.GetTarget() != NULL )
|
|
{
|
|
ApplyMultiDamage();
|
|
}
|
|
|
|
ClearMultiDamage();
|
|
g_MultiDamage.SetDamageType( nDamageType | DMG_NEVERGIB );
|
|
|
|
Vector vecDir;
|
|
Vector vecEnd;
|
|
Vector vecFinalDir; // bullet's final direction can be changed by passing through a portal
|
|
|
|
CASWTraceFilterShot traceFilter( this, info.m_pAdditionalIgnoreEnt, COLLISION_GROUP_NONE );
|
|
traceFilter.SetSkipMarines( false );
|
|
traceFilter.SetSkipRollingMarines( true );
|
|
|
|
// Prediction is only usable on players
|
|
int iSeed = (CBaseEntity::GetPredictionRandomSeed() + iSeedPlus );// & 255;
|
|
|
|
#if defined( HL2MP ) && defined( GAME_DLL )
|
|
int iEffectSeed = iSeed;
|
|
#endif
|
|
CShotManipulator Manipulator( info.m_vecDirShooting );
|
|
|
|
bool bDoImpacts = false;
|
|
|
|
for (int iShot = 0; iShot < info.m_iShots; iShot++)
|
|
{
|
|
bool bHitWater = false;
|
|
bool bHitGlass = false;
|
|
|
|
// Prediction is only usable on players
|
|
RandomSeed( iSeed + iShot ); // init random system with this seed
|
|
|
|
if (pPiercingTracerEnd != NULL) // if this is a refire, don't alter trajectory randomly again
|
|
{
|
|
vecDir = Manipulator.GetShotDirection();
|
|
}
|
|
else
|
|
{
|
|
// If we're firing multiple shots, and the first shot has to be bang on target, ignore spread
|
|
if ( iShot == 0 && info.m_iShots > 1 && (info.m_nFlags & FIRE_BULLETS_FIRST_SHOT_ACCURATE) )
|
|
{
|
|
vecDir = Manipulator.GetShotDirection();
|
|
}
|
|
else
|
|
{
|
|
if (info.m_nFlags & FIRE_BULLETS_ANGULAR_SPREAD)
|
|
vecDir = Manipulator.ApplyAngularSpread( info.m_vecSpread );
|
|
else
|
|
vecDir = Manipulator.ApplySpread( info.m_vecSpread );
|
|
}
|
|
}
|
|
|
|
vecEnd = info.m_vecSrc + vecDir * info.m_flDistance;
|
|
|
|
if( (info.m_nFlags & FIRE_BULLETS_HULL) != 0 && asw_allow_hull_shots.GetBool())
|
|
{
|
|
if (pPiercingTracerEnd == NULL) // if this is the first trace from a shotgun, do a short wide hull search first, to catch those annoying cases where an alien is just sitting underneath our gun
|
|
{
|
|
AI_TraceHull( info.m_vecSrc, info.m_vecSrc + vecDir * 30, Vector( -10, -10, -10 ), Vector( 10, 10, 10 ), MASK_SHOT, &traceFilter, &tr );
|
|
#ifdef GAME_DLL
|
|
if ( ai_debug_shoot_positions.GetBool() )
|
|
{
|
|
NDebugOverlay::SweptBox(info.m_vecSrc, info.m_vecSrc + vecDir * 30, Vector( -10, -10, -10 ), Vector( 10, 10, 10 ), QAngle(0,0,0), 255, 255, 0, 255, 10);
|
|
}
|
|
#endif
|
|
|
|
// if we didn't hit anything, then do a normal thin trace
|
|
if (!tr.DidHit() || tr.DidHitWorld())
|
|
{
|
|
// hulls that make it easier to hit targets with the shotgun.
|
|
AI_TraceHull( info.m_vecSrc, vecEnd, Vector( -3, -3, -3 ), Vector( 3, 3, 3 ), MASK_SHOT, &traceFilter, &tr );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// hulls that make it easier to hit targets with the shotgun.
|
|
AI_TraceHull( info.m_vecSrc, vecEnd, Vector( -3, -3, -3 ), Vector( 3, 3, 3 ), MASK_SHOT, &traceFilter, &tr );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AI_TraceLine(info.m_vecSrc, vecEnd, MASK_SHOT, &traceFilter, &tr);
|
|
}
|
|
|
|
vecFinalDir = tr.endpos - tr.startpos;
|
|
if (vecFinalDir == vec3_origin)
|
|
{
|
|
vecFinalDir = info.m_vecDirShooting;
|
|
}
|
|
VectorNormalize( vecFinalDir );
|
|
|
|
#ifdef GAME_DLL
|
|
if ( ai_debug_shoot_positions.GetBool() )
|
|
{
|
|
if( (info.m_nFlags & FIRE_BULLETS_HULL) != 0 && asw_allow_hull_shots.GetBool())
|
|
{
|
|
NDebugOverlay::SweptBox(info.m_vecSrc, vecEnd, Vector( -3, -3, -3 ), Vector( 3, 3, 3 ), QAngle(0,0,0), 255, 255, 0, 255, 10);
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::Line(info.m_vecSrc, vecEnd, 255, 255, 255, false, .1 );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Now hit all triggers along the ray that respond to shots...
|
|
// Clip the ray to the first collided solid returned from traceline
|
|
CTakeDamageInfo triggerInfo( pAttacker, pAttacker, info.m_flDamage, nDamageType );
|
|
CalculateBulletDamageForce( &triggerInfo, info.m_iAmmoType, vecFinalDir, tr.endpos );
|
|
triggerInfo.ScaleDamageForce( info.m_flDamageForceScale );
|
|
triggerInfo.SetAmmoType( info.m_iAmmoType );
|
|
#ifdef GAME_DLL
|
|
TraceAttackToTriggers( triggerInfo, tr.startpos, tr.endpos, vecFinalDir );
|
|
#endif
|
|
|
|
// Make sure given a valid bullet type
|
|
if (info.m_iAmmoType == -1)
|
|
{
|
|
DevMsg("ERROR: Undefined ammo type!\n");
|
|
return;
|
|
}
|
|
|
|
Vector vecTracerDest = tr.endpos;
|
|
|
|
vecPiercingTracerEnd = vecTracerDest; // set to the end point of our trace. If we recurse, then this gets filled in by a child call
|
|
|
|
// do damage, paint decals
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
#ifdef GAME_DLL
|
|
|
|
// For shots that don't need persistance
|
|
int soundEntChannel = ( info.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED;
|
|
|
|
CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, tr.endpos, 200, 0.5, this, soundEntChannel );
|
|
#endif
|
|
|
|
// See if the bullet ended up underwater + started out of the water
|
|
if ( !bHitWater && ( enginetrace->GetPointContents( tr.endpos ) & (CONTENTS_WATER|CONTENTS_SLIME) ) )
|
|
{
|
|
bHitWater = HandleShotImpactingWater( info, vecEnd, &traceFilter, &vecTracerDest );
|
|
}
|
|
|
|
float flActualDamage = info.m_flDamage;
|
|
|
|
// If we hit a player, and we have player damage specified, use that instead
|
|
// Adrian: Make sure to use the currect value if we hit a vehicle the player is currently driving.
|
|
if ( flPlayerDamage != 0.0f )
|
|
{
|
|
if ( tr.m_pEnt->IsPlayer() )
|
|
{
|
|
flActualDamage = flPlayerDamage;
|
|
}
|
|
#ifdef GAME_DLL
|
|
else if ( tr.m_pEnt->GetServerVehicle() )
|
|
{
|
|
if ( tr.m_pEnt->GetServerVehicle()->GetPassenger() && tr.m_pEnt->GetServerVehicle()->GetPassenger()->IsPlayer() )
|
|
{
|
|
flActualDamage = flPlayerDamage;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int nActualDamageType = nDamageType;
|
|
if ( flActualDamage == 0.0 )
|
|
{
|
|
flActualDamage = g_pGameRules->GetAmmoDamage( pAttacker, tr.m_pEnt, info.m_iAmmoType );
|
|
}
|
|
else
|
|
{
|
|
// asw - upped the number on this so the railgun doesn't always gib things
|
|
nActualDamageType = nDamageType | ((flActualDamage > ASW_GIB_DAMAGE_LIMIT ) ? DMG_ALWAYSGIB : DMG_NEVERGIB );
|
|
}
|
|
|
|
if ( !bHitWater || ((info.m_nFlags & FIRE_BULLETS_DONT_HIT_UNDERWATER) == 0) )
|
|
{
|
|
// Damage specified by function parameter
|
|
CTakeDamageInfo dmgInfo( this, pAttacker, flActualDamage, nActualDamageType );
|
|
CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, vecFinalDir, tr.endpos );
|
|
dmgInfo.ScaleDamageForce( info.m_flDamageForceScale );
|
|
dmgInfo.SetAmmoType( info.m_iAmmoType );
|
|
dmgInfo.SetWeapon( GetActiveASWWeapon() );
|
|
tr.m_pEnt->DispatchTraceAttack( dmgInfo, vecFinalDir, &tr );
|
|
|
|
if ( !bHitWater || (info.m_nFlags & FIRE_BULLETS_ALLOW_WATER_SURFACE_IMPACTS) )
|
|
{
|
|
if ( bDoServerEffects == true )
|
|
{
|
|
DoImpactEffect( tr, nDamageType );
|
|
}
|
|
else
|
|
{
|
|
bDoImpacts = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We may not impact, but we DO need to affect ragdolls on the client
|
|
CEffectData data;
|
|
data.m_vStart = tr.startpos;
|
|
data.m_vOrigin = tr.endpos;
|
|
data.m_nDamageType = nDamageType;
|
|
|
|
DispatchEffect( "RagdollImpact", data );
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
if ( nAmmoFlags & AMMO_FORCE_DROP_IF_CARRIED )
|
|
{
|
|
// Make sure if the player is holding this, he drops it
|
|
Pickup_ForcePlayerToDropThisObject( tr.m_pEnt );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// See if we hit glass
|
|
if ( tr.m_pEnt != NULL )
|
|
{
|
|
#ifdef GAME_DLL
|
|
surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps );
|
|
if ( ( psurf != NULL ) && ( psurf->game.material == CHAR_TEX_GLASS ) && ( tr.m_pEnt->ClassMatches( "func_breakable" ) ) )
|
|
{
|
|
bHitGlass = true;
|
|
}
|
|
#endif
|
|
}
|
|
float fDiceRoll = 0;
|
|
if ( CBaseEntity::GetPredictionRandomSeed() != -1 )
|
|
{
|
|
// if we're predicting, use a shared float
|
|
fDiceRoll = SharedRandomFloat("Piercing", 0.0f, 1.0f);
|
|
}
|
|
else
|
|
{
|
|
fDiceRoll = RandomFloat( 0.0f, 1.0f );
|
|
}
|
|
|
|
bool bPierce = ( tr.m_pEnt != NULL ) && ( fDiceRoll < fPenetrateChance) && !bHitGlass && !tr.DidHitWorld();
|
|
if (bPierce)
|
|
{
|
|
CAI_BaseNPC *pNPC = dynamic_cast<CAI_BaseNPC*>(tr.m_pEnt);
|
|
CASW_Simple_Alien *pAlien = dynamic_cast<CASW_Simple_Alien*>(tr.m_pEnt);
|
|
if (pNPC || pAlien)
|
|
{
|
|
#ifdef GAME_DLL
|
|
if ( tr.m_pEnt->Classify() == CLASS_ASW_SHIELDBUG ) // don't let bullets pass through shieldbugs
|
|
#else
|
|
if ( dynamic_cast<C_ASW_Shieldbug*>( tr.m_pEnt ) )
|
|
#endif
|
|
{
|
|
bPierce = false;
|
|
}
|
|
else if ((info.m_nFlags & FIRE_BULLETS_NO_PIERCING_SPARK) == 0)
|
|
{
|
|
CEffectData data;
|
|
data.m_vOrigin = tr.endpos;
|
|
data.m_vNormal = vecDir;
|
|
CPASFilter filter( data.m_vOrigin );
|
|
//filter.SetIgnorePredictionCull(true);
|
|
DispatchEffect( filter, 0.0, "PierceSpark", data );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bPierce = false; // don't pierce non-aliens
|
|
}
|
|
}
|
|
|
|
if ( ( tr.m_pEnt != NULL ) && fPenetrateChance > 1.0f )
|
|
{
|
|
// We can only penetrate a few walls
|
|
fPenetrateChance -= 1.0f;
|
|
|
|
// If we didn't hit a wall, slow down the penetrating madness
|
|
if( fPenetrateChance > 1.0f && !tr.DidHitWorld() )
|
|
{
|
|
fPenetrateChance -= static_cast<int>( fPenetrateChance );
|
|
}
|
|
|
|
bPierce = true;
|
|
}
|
|
|
|
// See if we should pass through glass
|
|
#ifdef GAME_DLL
|
|
if ( bHitGlass )
|
|
{
|
|
HandleShotImpactingGlass( info, tr, vecFinalDir, &traceFilter );
|
|
}
|
|
|
|
// if we hit an entity and aren't at shot end, then see about firing another bullet
|
|
else
|
|
#endif
|
|
{
|
|
{
|
|
#ifdef GAME_DLL
|
|
ApplyMultiDamage(); // apply the previous damage, since it'll get cleared when we refire
|
|
#endif
|
|
if (bPierce)
|
|
{
|
|
// Refire the round, as if starting from behind the NPC
|
|
FireBulletsInfo_t behindNPCInfo(info);
|
|
behindNPCInfo.m_iShots = 1;
|
|
behindNPCInfo.m_vecSrc = tr.endpos;
|
|
behindNPCInfo.m_vecDirShooting = vecDir;
|
|
behindNPCInfo.m_vecSpread = vec3_origin;
|
|
behindNPCInfo.m_flDistance = info.m_flDistance*( 1.0f - tr.fraction );
|
|
|
|
// alter the angle so we're more likely to hit another enemy
|
|
if ( tr.m_pEnt )
|
|
{
|
|
float zdiff = tr.m_pEnt->GetAbsOrigin().z - GetAbsOrigin().z;
|
|
if (fabs(zdiff) < 70 && bAllowChange) // alien and marine are roughly on the same floor, let's flatten our penetrated shot, so it's more likely to hit another
|
|
{
|
|
behindNPCInfo.m_vecDirShooting.z = 0;
|
|
behindNPCInfo.m_vecDirShooting.NormalizeInPlace();
|
|
}
|
|
}
|
|
|
|
if ( !tr.DidHitWorld() )
|
|
{
|
|
behindNPCInfo.m_pAdditionalIgnoreEnt = tr.m_pEnt;
|
|
}
|
|
else
|
|
{
|
|
// Penetrate past the solid
|
|
behindNPCInfo.m_vecSrc = behindNPCInfo.m_vecSrc + behindNPCInfo.m_vecDirShooting * 28.0f;
|
|
iMaxPenetrate++;
|
|
behindNPCInfo.m_pAdditionalIgnoreEnt = NULL;
|
|
}
|
|
|
|
FirePenetratingBullets( behindNPCInfo, --iMaxPenetrate, fPenetrateChance, iSeedPlus, bAllowChange, &vecPiercingTracerEnd, bSegmentTracer );
|
|
// this function returns with vecPiercingTracerEnd set to the end of the tracer
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ( info.m_iTracerFreq != 0 ) && ( tracerCount++ % info.m_iTracerFreq ) == 0 && ( bHitGlass == false ) )
|
|
{
|
|
if ( bDoServerEffects == true )
|
|
{
|
|
if ( pPiercingTracerEnd == NULL && !bSegmentTracer ) // only the very first call in a firepene chain actually makes a tracer
|
|
{
|
|
Vector vecTracerSrc = vec3_origin;
|
|
ComputeTracerStartPosition( info.m_vecSrc, &vecTracerSrc );
|
|
|
|
trace_t Tracer;
|
|
Tracer = tr;
|
|
Tracer.endpos = vecPiercingTracerEnd;
|
|
|
|
// if we were a hull, move our endpoint nearer the wall so impact effects work
|
|
if( (info.m_nFlags & FIRE_BULLETS_HULL) != 0 && asw_allow_hull_shots.GetBool())
|
|
{
|
|
Tracer.endpos += vecFinalDir * 2.9f;
|
|
}
|
|
MakeTracer( vecTracerSrc, Tracer, pAmmoDef->TracerType(info.m_iAmmoType) );
|
|
}
|
|
else
|
|
{
|
|
Vector vecTracerSrc = vec3_origin;
|
|
if ( pPiercingTracerEnd != NULL ) // this is not the first tracer, move the start out a bit so there's a gap
|
|
{
|
|
float gap = 32.0f;
|
|
// come back to this later
|
|
/*
|
|
if ( info.m_pAdditionalIgnoreEnt )
|
|
{
|
|
Vector mins, maxs;
|
|
info.m_pAdditionalIgnoreEnt->ExtractBbox( info.m_pAdditionalIgnoreEnt->GetSequence(), mins, maxs );
|
|
|
|
float flRadius = MAX( MAX( FloatMakePositive( mins.x ), FloatMakePositive( maxs.x ) ),
|
|
MAX( FloatMakePositive( mins.y ), FloatMakePositive( maxs.y ) ) );
|
|
|
|
gap = flRadius;
|
|
}
|
|
*/
|
|
vecTracerSrc = info.m_vecSrc + (vecFinalDir * gap);
|
|
}
|
|
else
|
|
ComputeTracerStartPosition( info.m_vecSrc, &vecTracerSrc );
|
|
|
|
trace_t Tracer;
|
|
Tracer = tr;
|
|
Tracer.endpos = tr.endpos;
|
|
|
|
// if we were a hull, move our endpoint nearer the wall so impact effects work
|
|
if( (info.m_nFlags & FIRE_BULLETS_HULL) != 0 && asw_allow_hull_shots.GetBool())
|
|
{
|
|
Tracer.endpos += vecFinalDir * 2.9f;
|
|
}
|
|
|
|
if ( pPiercingTracerEnd == NULL )
|
|
{
|
|
if ( bSegmentTracer )
|
|
MakeTracer( vecTracerSrc, Tracer, pAmmoDef->TracerType(info.m_iAmmoType) );
|
|
else
|
|
{
|
|
trace_t Tracer2;
|
|
Tracer = tr;
|
|
Tracer.endpos = vecPiercingTracerEnd;
|
|
MakeTracer( vecTracerSrc, Tracer2, pAmmoDef->TracerType(info.m_iAmmoType) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( bSegmentTracer )
|
|
MakeUnattachedTracer( vecTracerSrc, Tracer, pAmmoDef->TracerType(info.m_iAmmoType) );
|
|
QAngle vecAngles;
|
|
VectorAngles( vecFinalDir, vecAngles );
|
|
DispatchParticleEffect( "drone_shot_exit", vecTracerSrc, vecAngles );
|
|
#ifdef CLIENT_DLL
|
|
//CLocalPlayerFilter filter;
|
|
//CBaseEntity::EmitSound( filter, entindex(), "ASW_Alien.Penetrate_Flesh" );
|
|
#endif //CLIENT_DLL
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
// if we're a child call, then let our parent know where our tracer ended
|
|
if (pPiercingTracerEnd != NULL)
|
|
{
|
|
*pPiercingTracerEnd = vecPiercingTracerEnd;
|
|
}
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
ApplyMultiDamage();
|
|
#endif
|
|
}
|
|
|
|
void CASW_Marine::FireBouncingBullets( const FireBulletsInfo_t &info, int iMaxBounce, int iSeedPlus/*=0 */ )
|
|
{
|
|
if (iMaxBounce < 0)
|
|
return;
|
|
|
|
#ifdef GAME_DLL
|
|
if (ASWGameRules())
|
|
ASWGameRules()->m_fLastFireTime = gpGlobals->curtime;
|
|
#endif
|
|
|
|
static int tracerCount;
|
|
trace_t tr;
|
|
CAmmoDef* pAmmoDef = GetAmmoDef();
|
|
int nDamageType = pAmmoDef->DamageType(info.m_iAmmoType);
|
|
int nAmmoFlags = pAmmoDef->Flags(info.m_iAmmoType);
|
|
|
|
bool bDoServerEffects = true;
|
|
|
|
#if defined ( _XBOX ) && defined( GAME_DLL )
|
|
if( IsInhabited() )
|
|
{
|
|
CBasePlayer *pPlayer = GetCommander();
|
|
if (pPlayer)
|
|
{
|
|
|
|
int rumbleEffect = pPlayer->GetActiveWeapon()->GetRumbleEffect();
|
|
|
|
if( rumbleEffect != RUMBLE_INVALID )
|
|
{
|
|
if( rumbleEffect == RUMBLE_SHOTGUN_SINGLE )
|
|
{
|
|
if( info.m_iShots == 12 )
|
|
{
|
|
// Upgrade to double barrel rumble effect
|
|
rumbleEffect = RUMBLE_SHOTGUN_DOUBLE;
|
|
}
|
|
}
|
|
|
|
pPlayer->RumbleEffect( rumbleEffect, 0, RUMBLE_FLAG_RESTART );
|
|
}
|
|
}
|
|
}
|
|
#endif//_XBOX
|
|
|
|
float flPlayerDamage = info.m_flPlayerDamage;
|
|
if ( flPlayerDamage == 0 )
|
|
{
|
|
if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER )
|
|
{
|
|
flPlayerDamage = pAmmoDef->PlrDamage( info.m_iAmmoType );
|
|
}
|
|
}
|
|
|
|
// the default attacker is ourselves
|
|
CBaseEntity *pAttacker = info.m_pAttacker ? info.m_pAttacker : this;
|
|
|
|
// Make sure we don't have a dangling damage target from a recursive call
|
|
if ( g_MultiDamage.GetTarget() != NULL )
|
|
{
|
|
ApplyMultiDamage();
|
|
}
|
|
|
|
ClearMultiDamage();
|
|
g_MultiDamage.SetDamageType( nDamageType | DMG_NEVERGIB );
|
|
|
|
Vector vecDir;
|
|
Vector vecEnd;
|
|
Vector vecFinalDir; // bullet's final direction can be changed by passing through a portal
|
|
|
|
CASWTraceFilterShot traceFilter( this, info.m_pAdditionalIgnoreEnt, COLLISION_GROUP_NONE );
|
|
traceFilter.SetSkipMarines( false );
|
|
traceFilter.SetSkipRollingMarines( true );
|
|
|
|
int iSeed = (CBaseEntity::GetPredictionRandomSeed() + iSeedPlus );// & 255;
|
|
|
|
CShotManipulator Manipulator( info.m_vecDirShooting );
|
|
|
|
bool bDoImpacts = false;
|
|
bool bDoTracers = false;
|
|
|
|
for (int iShot = 0; iShot < info.m_iShots; iShot++)
|
|
{
|
|
bool bHitWater = false;
|
|
bool bHitGlass = false;
|
|
|
|
// Prediction is only usable on players
|
|
RandomSeed( iSeed + iShot ); // init random system with this seed
|
|
|
|
if (info.m_vecSpread[0] < 0) // weapon wants simple random spread, not gaussian
|
|
{
|
|
QAngle angChange(info.m_vecSpread[0] * random->RandomFloat(-0.5f, 0.5f),
|
|
info.m_vecSpread[1] * random->RandomFloat(-0.5f, 0.5f),
|
|
info.m_vecSpread[2] * random->RandomFloat(-0.5f, 0.5f));
|
|
matrix3x4_t matrix;
|
|
AngleMatrix( angChange, matrix );
|
|
VectorTransform(info.m_vecDirShooting, matrix, vecDir);
|
|
}
|
|
else
|
|
{
|
|
// If we're firing multiple shots, and the first shot has to be bang on target, ignore spread
|
|
if ( iShot == 0 && info.m_iShots > 1 && (info.m_nFlags & FIRE_BULLETS_FIRST_SHOT_ACCURATE) )
|
|
{
|
|
vecDir = Manipulator.GetShotDirection();
|
|
}
|
|
else
|
|
{
|
|
if (info.m_nFlags & FIRE_BULLETS_ANGULAR_SPREAD)
|
|
vecDir = Manipulator.ApplyAngularSpread( info.m_vecSpread );
|
|
else
|
|
vecDir = Manipulator.ApplySpread( info.m_vecSpread );
|
|
}
|
|
}
|
|
|
|
vecEnd = info.m_vecSrc + vecDir * info.m_flDistance;
|
|
|
|
if( (info.m_nFlags & FIRE_BULLETS_HULL) != 0 && asw_allow_hull_shots.GetBool())
|
|
{
|
|
// hulls that make it easier to hit targets with the shotgun.
|
|
AI_TraceHull( info.m_vecSrc, vecEnd, Vector( -3, -3, -3 ), Vector( 3, 3, 3 ), MASK_SHOT, &traceFilter, &tr );
|
|
}
|
|
else
|
|
{
|
|
AI_TraceLine(info.m_vecSrc, vecEnd, MASK_SHOT, &traceFilter, &tr);
|
|
}
|
|
|
|
vecFinalDir = tr.endpos - tr.startpos;
|
|
if (vecFinalDir == vec3_origin)
|
|
{
|
|
vecFinalDir = info.m_vecDirShooting;
|
|
}
|
|
VectorNormalize( vecFinalDir );
|
|
|
|
#ifdef GAME_DLL
|
|
if ( ai_debug_shoot_positions.GetBool() )
|
|
NDebugOverlay::Line(info.m_vecSrc, vecEnd, 255, 255, 255, false, .1 );
|
|
#endif
|
|
|
|
// Now hit all triggers along the ray that respond to shots...
|
|
// Clip the ray to the first collided solid returned from traceline
|
|
CTakeDamageInfo triggerInfo( pAttacker, pAttacker, info.m_flDamage, nDamageType );
|
|
CalculateBulletDamageForce( &triggerInfo, info.m_iAmmoType, vecFinalDir, tr.endpos );
|
|
triggerInfo.ScaleDamageForce( info.m_flDamageForceScale );
|
|
triggerInfo.SetAmmoType( info.m_iAmmoType );
|
|
#ifdef GAME_DLL
|
|
TraceAttackToTriggers( triggerInfo, tr.startpos, tr.endpos, vecFinalDir );
|
|
#endif
|
|
|
|
// Make sure given a valid bullet type
|
|
if (info.m_iAmmoType == -1)
|
|
{
|
|
DevMsg("ERROR: Undefined ammo type!\n");
|
|
return;
|
|
}
|
|
|
|
Vector vecTracerDest = tr.endpos;
|
|
|
|
// do damage, paint decals
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
#ifdef GAME_DLL
|
|
// For shots that don't need persistance
|
|
int soundEntChannel = ( info.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED;
|
|
|
|
CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, tr.endpos, 200, 0.5, this, soundEntChannel );
|
|
#endif
|
|
|
|
// See if the bullet ended up underwater + started out of the water
|
|
if ( !bHitWater && ( enginetrace->GetPointContents( tr.endpos ) & (CONTENTS_WATER|CONTENTS_SLIME) ) )
|
|
{
|
|
bHitWater = HandleShotImpactingWater( info, vecEnd, &traceFilter, &vecTracerDest );
|
|
}
|
|
|
|
float flActualDamage = info.m_flDamage;
|
|
|
|
// If we hit a player, and we have player damage specified, use that instead
|
|
// Adrian: Make sure to use the currect value if we hit a vehicle the player is currently driving.
|
|
if ( flPlayerDamage != 0.0f )
|
|
{
|
|
if ( tr.m_pEnt->IsPlayer() )
|
|
{
|
|
flActualDamage = flPlayerDamage;
|
|
}
|
|
#ifdef GAME_DLL
|
|
else if ( tr.m_pEnt->GetServerVehicle() )
|
|
{
|
|
if ( tr.m_pEnt->GetServerVehicle()->GetPassenger() && tr.m_pEnt->GetServerVehicle()->GetPassenger()->IsPlayer() )
|
|
{
|
|
flActualDamage = flPlayerDamage;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int nActualDamageType = nDamageType;
|
|
if ( flActualDamage == 0.0 )
|
|
{
|
|
flActualDamage = g_pGameRules->GetAmmoDamage( pAttacker, tr.m_pEnt, info.m_iAmmoType );
|
|
}
|
|
else
|
|
{
|
|
nActualDamageType = nDamageType | ((flActualDamage > ASW_GIB_DAMAGE_LIMIT) ? DMG_ALWAYSGIB : DMG_NEVERGIB );
|
|
}
|
|
|
|
if ( !bHitWater || ((info.m_nFlags & FIRE_BULLETS_DONT_HIT_UNDERWATER) == 0) )
|
|
{
|
|
// Damage specified by function parameter
|
|
CTakeDamageInfo dmgInfo( this, pAttacker, flActualDamage, nActualDamageType );
|
|
CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, vecFinalDir, tr.endpos );
|
|
dmgInfo.ScaleDamageForce( info.m_flDamageForceScale );
|
|
dmgInfo.SetAmmoType( info.m_iAmmoType );
|
|
dmgInfo.SetWeapon( GetActiveASWWeapon() );
|
|
tr.m_pEnt->DispatchTraceAttack( dmgInfo, vecFinalDir, &tr );
|
|
|
|
if ( !bHitWater || (info.m_nFlags & FIRE_BULLETS_ALLOW_WATER_SURFACE_IMPACTS) )
|
|
{
|
|
if ( bDoServerEffects == true )
|
|
{
|
|
DoImpactEffect( tr, nDamageType );
|
|
}
|
|
else
|
|
{
|
|
bDoImpacts = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We may not impact, but we DO need to affect ragdolls on the client
|
|
CEffectData data;
|
|
data.m_vStart = tr.startpos;
|
|
data.m_vOrigin = tr.endpos;
|
|
data.m_nDamageType = nDamageType;
|
|
|
|
DispatchEffect( "RagdollImpact", data );
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
if ( nAmmoFlags & AMMO_FORCE_DROP_IF_CARRIED )
|
|
{
|
|
// Make sure if the player is holding this, he drops it
|
|
Pickup_ForcePlayerToDropThisObject( tr.m_pEnt );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// See if we hit glass
|
|
if ( tr.m_pEnt != NULL )
|
|
{
|
|
#ifdef GAME_DLL
|
|
surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps );
|
|
if ( ( psurf != NULL ) && ( psurf->game.material == CHAR_TEX_GLASS ) && ( tr.m_pEnt->ClassMatches( "func_breakable" ) ) )
|
|
{
|
|
bHitGlass = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ( ( info.m_iTracerFreq != 0 ) && ( tracerCount++ % info.m_iTracerFreq ) == 0 && ( bHitGlass == false ) )
|
|
{
|
|
if ( bDoServerEffects == true )
|
|
{
|
|
if (iMaxBounce == 5) // bad hardcoded number for ricochet gun
|
|
{
|
|
Vector vecTracerSrc = vec3_origin;
|
|
ComputeTracerStartPosition( info.m_vecSrc, &vecTracerSrc );
|
|
|
|
trace_t Tracer;
|
|
Tracer = tr;
|
|
Tracer.endpos = vecTracerDest;
|
|
|
|
MakeTracer( vecTracerSrc, Tracer, pAmmoDef->TracerType(info.m_iAmmoType) );
|
|
}
|
|
else
|
|
{
|
|
Vector vecTracerSrc = info.m_vecSrc;
|
|
trace_t Tracer;
|
|
Tracer = tr;
|
|
Tracer.endpos = vecTracerDest;
|
|
|
|
MakeUnattachedTracer( vecTracerSrc, Tracer, pAmmoDef->TracerType(info.m_iAmmoType) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bDoTracers = true;
|
|
}
|
|
}
|
|
// See if we should pass through glass
|
|
#ifdef GAME_DLL
|
|
if ( bHitGlass )
|
|
{
|
|
HandleShotImpactingGlass( info, tr, vecFinalDir, &traceFilter );
|
|
}
|
|
#endif
|
|
|
|
if ( tr.DidHitWorld() )
|
|
{
|
|
// Refire the round, bouncing off the surface
|
|
FireBulletsInfo_t BouncingShotInfo(info);
|
|
BouncingShotInfo.m_iShots = 1;
|
|
BouncingShotInfo.m_vecSrc = tr.endpos;
|
|
BouncingShotInfo.m_vecDirShooting = vecDir;
|
|
// reflect the X+Y off the surface (leave Z intact so the shot is more likely to stay flat and hit enemies)
|
|
float proj = (BouncingShotInfo.m_vecDirShooting).Dot( tr.plane.normal );
|
|
VectorMA( BouncingShotInfo.m_vecDirShooting, -proj*2, tr.plane.normal, BouncingShotInfo.m_vecDirShooting );
|
|
BouncingShotInfo.m_vecDirShooting.z = vecDir.z;
|
|
|
|
BouncingShotInfo.m_vecSpread = vec3_origin;
|
|
BouncingShotInfo.m_flDistance = info.m_flDistance*( 1.0f - tr.fraction );
|
|
BouncingShotInfo.m_pAdditionalIgnoreEnt = NULL;
|
|
|
|
#ifdef GAME_DLL
|
|
ApplyMultiDamage(); // apply the previous damage, since it'll get cleared when we refire
|
|
int iAliensKilledBeforeBounce = GetMarineResource() ? GetMarineResource()->m_iAliensKilled.Get() : 0;
|
|
#endif
|
|
FireBouncingBullets( BouncingShotInfo, --iMaxBounce );
|
|
|
|
#ifdef GAME_DLL
|
|
if (GetMarineResource())
|
|
{
|
|
int iAliensKilledByBounce = GetMarineResource()->m_iAliensKilled.Get() - iAliensKilledBeforeBounce;
|
|
GetMarineResource()->m_iAliensKilledByBouncingBullets += iAliensKilledByBounce;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//iSeed++;
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
ApplyMultiDamage();
|
|
#endif
|
|
}
|
|
|
|
CBaseCombatWeapon* CASW_Marine::GetLastWeaponSwitchedTo()
|
|
{
|
|
return dynamic_cast<CBaseCombatWeapon*>(m_hLastWeaponSwitchedTo.Get());
|
|
}
|
|
|
|
bool CASW_Marine::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
if (asw_marine_box_collision.GetBool()) // forces simple box collision on the marines (feels better for friendly fire)
|
|
{
|
|
if ( IntersectRayWithBox( ray, WorldAlignMins() + GetAbsOrigin(), WorldAlignMaxs() + GetAbsOrigin(), DIST_EPSILON, &tr ) )
|
|
{
|
|
tr.hitbox = 0;
|
|
tr.hitgroup = HITGROUP_HEAD;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return BaseClass::TestHitboxes(ray, fContentsMask, tr);
|
|
}
|
|
|
|
void CASW_Marine::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
|
|
{
|
|
const char* tracer = "ASWUTracer";
|
|
if (GetActiveASWWeapon())
|
|
tracer = GetActiveASWWeapon()->GetUTracerType();
|
|
#ifdef CLIENT_DLL
|
|
CEffectData data;
|
|
data.m_vOrigin = tr.endpos;
|
|
data.m_hEntity = this;
|
|
data.m_nMaterial = m_iDamageAttributeEffects;
|
|
|
|
DispatchEffect( tracer, data );
|
|
#else
|
|
CRecipientFilter filter;
|
|
filter.AddAllPlayers();
|
|
if (gpGlobals->maxClients > 1 && IsInhabited() && GetCommander())
|
|
{
|
|
filter.RemoveRecipient(GetCommander());
|
|
}
|
|
|
|
UserMessageBegin( filter, tracer );
|
|
WRITE_SHORT( entindex() );
|
|
WRITE_FLOAT( tr.endpos.x );
|
|
WRITE_FLOAT( tr.endpos.y );
|
|
WRITE_FLOAT( tr.endpos.z );
|
|
WRITE_SHORT( m_iDamageAttributeEffects );
|
|
MessageEnd();
|
|
#endif
|
|
}
|
|
|
|
void CASW_Marine::MakeUnattachedTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
|
|
{
|
|
const char* tracer = "ASWUTracerUnattached";
|
|
#ifdef CLIENT_DLL
|
|
|
|
CEffectData data;
|
|
data.m_vOrigin = tr.endpos;
|
|
data.m_hEntity = this;
|
|
data.m_vStart = vecTracerSrc;
|
|
|
|
DispatchEffect( tracer, data );
|
|
#else
|
|
CRecipientFilter filter;
|
|
filter.AddAllPlayers();
|
|
if (gpGlobals->maxClients > 1 && IsInhabited() && GetCommander())
|
|
{
|
|
filter.RemoveRecipient(GetCommander());
|
|
}
|
|
|
|
UserMessageBegin( filter, tracer );
|
|
WRITE_SHORT( entindex() );
|
|
WRITE_FLOAT( tr.endpos.x );
|
|
WRITE_FLOAT( tr.endpos.y );
|
|
WRITE_FLOAT( tr.endpos.z );
|
|
WRITE_FLOAT( vecTracerSrc.x );
|
|
WRITE_FLOAT( vecTracerSrc.y );
|
|
WRITE_FLOAT( vecTracerSrc.z );
|
|
WRITE_SHORT( m_iDamageAttributeEffects );
|
|
MessageEnd();
|
|
#endif
|
|
}
|
|
|
|
// returns which slot in the m_hWeapons array this pickup should go in
|
|
int CASW_Marine::GetWeaponPositionForPickup( const char* szWeaponClass )
|
|
{
|
|
if (!szWeaponClass || !ASWEquipmentList())
|
|
{
|
|
Assert(0);
|
|
return 0;
|
|
}
|
|
// if it's an extra type item, return the 3rd slot as that's the only place it'll fit
|
|
CASW_WeaponInfo* pWeaponData = ASWEquipmentList()->GetWeaponDataFor(szWeaponClass);
|
|
if (pWeaponData && pWeaponData->m_bExtra)
|
|
return 2;
|
|
|
|
// if item is unique, then check if we're already carrying one
|
|
if (pWeaponData->m_bUnique)
|
|
{
|
|
CBaseCombatWeapon *pWeapon = GetWeapon(0);
|
|
if (pWeapon && !Q_strcmp(szWeaponClass, pWeapon->GetClassname()))
|
|
return 0;
|
|
|
|
pWeapon = GetWeapon(1);
|
|
if (pWeapon && !Q_strcmp(szWeaponClass, pWeapon->GetClassname()))
|
|
return 1;
|
|
}
|
|
|
|
if (GetWeapon(0) == NULL) // primary slot is free
|
|
return 0;
|
|
else if (GetWeapon(1) == NULL) // secondary slot is free
|
|
return 1;
|
|
// all slots are full
|
|
else if (GetActiveWeapon() == GetWeapon(0)) // we have primary currently selected, so exchange with that
|
|
return 0;
|
|
else if (GetActiveWeapon() == GetWeapon(1)) // we have primary currently selected, so exchange with that
|
|
return 1;
|
|
|
|
return 0; // otherwise, swap with the primary slot
|
|
|
|
/*
|
|
Potential better system?
|
|
|
|
for a weapon: put in primary slot if free
|
|
if not, put in secondary slot, if free
|
|
if not, drop current weapon and put in that slot
|
|
|
|
for a pickup: put in secondary slot, if free
|
|
if not, put in primary slot, if free
|
|
if not, drop secondary and put in secondary
|
|
*/
|
|
}
|
|
|
|
int CASW_Marine::GetNumberOfWeaponsUsingAmmo(int iAmmoIndex)
|
|
{
|
|
int count = 0;
|
|
for (int i=0;i<3;i++)
|
|
{
|
|
CBaseCombatWeapon* pWeapon = GetWeapon(i);
|
|
if (!pWeapon)
|
|
continue;
|
|
if (pWeapon->GetPrimaryAmmoType() == iAmmoIndex
|
|
|| pWeapon->GetSecondaryAmmoType() == iAmmoIndex)
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool CASW_Marine::CanPickupPrimaryAmmo()
|
|
{
|
|
CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
|
|
if ( pWeapon && pWeapon->IsOffensiveWeapon() )
|
|
{
|
|
int iAmmoType = pWeapon->GetPrimaryAmmoType();
|
|
int iGuns = GetNumberOfWeaponsUsingAmmo( iAmmoType );
|
|
int iMaxAmmoCount = GetAmmoDef()->MaxCarry( iAmmoType, this ) * iGuns;
|
|
int iBullets = GetAmmoCount( iAmmoType );
|
|
|
|
if ( iBullets < iMaxAmmoCount )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CASW_Marine::ApplyMeleeDamage( CBaseEntity *pHitEntity, CTakeDamageInfo &dmgInfo, Vector &vecAttackDir, trace_t *tr )
|
|
{
|
|
if ( !pHitEntity )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Knockback
|
|
#ifdef GAME_DLL
|
|
CASW_Melee_Attack *pAttack = GetCurrentMeleeAttack();
|
|
|
|
if ( pAttack )
|
|
{
|
|
if ( pAttack->m_flKnockbackForce > 0.0f )
|
|
{
|
|
CASW_Alien *pAlien = dynamic_cast<CASW_Alien*>(pHitEntity);
|
|
float flFlatForce = pAttack->m_flKnockbackForce;
|
|
float flUpForce = flFlatForce * asw_melee_knockback_up_force.GetFloat();
|
|
|
|
// knockback aliens on broad strokes
|
|
if ( pAlien )
|
|
{
|
|
Vector vecToTarget = pAlien->WorldSpaceCenter() - WorldSpaceCenter();
|
|
vecToTarget.z = 0;
|
|
VectorNormalize( vecToTarget );
|
|
|
|
// undone: knock aliens to the side to better clear a path for the marine
|
|
//Vector vecDirUp( 0, 0, 1 );
|
|
//float flSideForce = flFlatForce * ( RandomInt( -1, 1 ) == -1 ? -1 : 1);
|
|
//Vector vecSide;
|
|
//CrossProduct( vecToTarget, vecDirUp, vecSide );
|
|
|
|
pAlien->Knockback( vecToTarget * flFlatForce + Vector( 0, 0, 1 ) * flUpForce );
|
|
//pAlien->ForceFlinch( vecAttackDir );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ApplyPassiveMeleeDamageEffects( dmgInfo );
|
|
|
|
// Set the weapon to the marine so the stats system knows this was melee damage done
|
|
dmgInfo.SetWeapon( this );
|
|
pHitEntity->DispatchTraceAttack( dmgInfo, vecAttackDir, tr );
|
|
if ( pHitEntity->IsNPC() && !m_bPlayedMeleeHitSound )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
if ( IsInhabited() )
|
|
{
|
|
if ( HasPowerFist() )
|
|
{
|
|
EmitSound( "ASW.MarinePowerFistAttackFP" );
|
|
}
|
|
else
|
|
{
|
|
EmitSound( "ASW.MarineMeleeAttackFP" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( HasPowerFist() )
|
|
{
|
|
EmitSound( "ASW.MarinePowerFistAttack" );
|
|
}
|
|
else
|
|
{
|
|
EmitSound( "ASW.MarineMeleeAttack" );
|
|
}
|
|
}
|
|
#else
|
|
if ( gpGlobals->maxClients <= 1 && IsInhabited() )
|
|
{
|
|
if ( HasPowerFist() )
|
|
{
|
|
EmitSound( "ASW.MarinePowerFistAttackFP" );
|
|
}
|
|
else
|
|
{
|
|
EmitSound( "ASW.MarineMeleeAttackFP" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( HasPowerFist() )
|
|
{
|
|
EmitSound( "ASW.MarinePowerFistAttack" );
|
|
}
|
|
else
|
|
{
|
|
EmitSound( "ASW.MarineMeleeAttack" );
|
|
}
|
|
}
|
|
#endif
|
|
m_bPlayedMeleeHitSound = true;
|
|
}
|
|
m_RecentMeleeHits.AddToTail( pHitEntity );
|
|
|
|
ASW_WPN_MSG_CONVAR( asw_melee_debug, "%s(): [%.3f] Doing %f melee damage damage to %d:%s\n", __FUNCTION__, gpGlobals->curtime, dmgInfo.GetDamage(), pHitEntity->entindex(), pHitEntity->GetClassname() );
|
|
|
|
ApplyMultiDamage();
|
|
}
|
|
|
|
void CASW_Marine::PlayMeleeImpactEffects( CBaseEntity *pEntity, trace_t *tr )
|
|
{
|
|
if ( !pEntity )
|
|
{
|
|
return;
|
|
}
|
|
|
|
UTIL_ImpactTrace( tr, DMG_CLUB );
|
|
|
|
CASW_Alien *pAlien = dynamic_cast<CASW_Alien*>(pEntity);
|
|
|
|
if ( pAlien )
|
|
{
|
|
#ifdef GAME_DLL
|
|
// play a melee hit sound
|
|
CPASAttenuationFilter otherfilter;
|
|
CBaseEntity::EmitSound( otherfilter, pAlien->entindex(), "ASW.MarineMeleeAttack" );
|
|
#else // #ifdef GAME_DLL
|
|
CLocalPlayerFilter filter;
|
|
CBaseEntity::EmitSound( filter, pAlien->entindex(), "ASW.MarineMeleeAttackFP" );
|
|
#endif // #ifdef GAME_DLL
|
|
}
|
|
|
|
#ifdef CLIENT_DLL
|
|
DispatchParticleEffect( "melee_contact", tr->endpos, QAngle( 0, 0, 0 ) );
|
|
#endif // #ifdef CLIENT_DLL
|
|
}
|
|
|
|
CASW_Melee_Attack* CASW_Marine::GetCurrentMeleeAttack()
|
|
{
|
|
return ASWMeleeSystem()->GetMeleeAttackByID( m_iMeleeAttackID.Get() );
|
|
}
|
|
|
|
void CASW_Marine::HandlePredictedAnimEvent( int event, const char* options )
|
|
{
|
|
if ( event == AE_EMPTY )
|
|
{
|
|
}
|
|
else if ( event == AE_MELEE_DAMAGE )
|
|
{
|
|
float flYawStart, flYawEnd;
|
|
|
|
flYawStart = flYawEnd = 0.0f;
|
|
|
|
const char *p = options;
|
|
char token[256];
|
|
if ( options[0] )
|
|
{
|
|
// Read in yaw start
|
|
p = nexttoken( token, p, ' ' );
|
|
|
|
if( token )
|
|
{
|
|
flYawStart = atof( token );
|
|
}
|
|
|
|
// Read in yaw end
|
|
p = nexttoken( token, p, ' ' );
|
|
|
|
if( token )
|
|
{
|
|
flYawEnd = atof( token );
|
|
}
|
|
}
|
|
|
|
DoMeleeDamageTrace( flYawStart, flYawEnd );
|
|
}
|
|
else if ( event == AE_MELEE_START_COLLISION_DAMAGE )
|
|
{
|
|
if ( asw_melee_debug.GetBool() )
|
|
{
|
|
Msg( "%s AE_MELEE_START_COLLISION_DAMAGE\n", IsServer() ? "s" : " c" );
|
|
}
|
|
m_bMeleeCollisionDamage = true;
|
|
}
|
|
else if ( event == AE_MELEE_STOP_COLLISION_DAMAGE )
|
|
{
|
|
if ( asw_melee_debug.GetBool() )
|
|
{
|
|
Msg( "%s AE_MELEE_STOP_COLLISION_DAMAGE\n", IsServer() ? "s" : " c" );
|
|
}
|
|
m_bMeleeCollisionDamage = false;
|
|
}
|
|
else if ( event == AE_START_DETECTING_COMBO )
|
|
{
|
|
if ( asw_melee_debug.GetBool() )
|
|
{
|
|
Msg( "%s AE_START_DETECTING_COMBO\n", IsServer() ? "s" : " c" );
|
|
}
|
|
m_bMeleeComboKeypressAllowed = true;
|
|
}
|
|
else if ( event == AE_STOP_DETECTING_COMBO )
|
|
{
|
|
if ( asw_melee_debug.GetBool() )
|
|
{
|
|
Msg( "%s AE_STOP_DETECTING_COMBO\n", IsServer() ? "s" : " c" );
|
|
}
|
|
m_bMeleeComboKeypressAllowed = false;
|
|
}
|
|
else if ( event == AE_COMBO_TRANSITION )
|
|
{
|
|
if ( asw_melee_debug.GetBool() )
|
|
{
|
|
Msg( "%s AE_COMBO_TRANSITION\n", IsServer() ? "s" : " c" );
|
|
}
|
|
m_bMeleeComboTransitionAllowed = true;
|
|
}
|
|
else if ( event == AE_ALLOW_MOVEMENT )
|
|
{
|
|
if ( asw_melee_debug.GetBool() )
|
|
{
|
|
Msg( "%s AE_ALLOW_MOVEMENT\n", IsServer() ? "s" : " c" );
|
|
}
|
|
m_iMeleeAllowMovement = MELEE_MOVEMENT_FULL;
|
|
}
|
|
else if ( event == AE_SKILL_EVENT )
|
|
{
|
|
if ( asw_melee_debug.GetBool() )
|
|
{
|
|
Msg( "%s AE_SKILL_EVENT %s\n", IsServer() ? "s" : " c", options );
|
|
}
|
|
char token[256];
|
|
const char *p = options;
|
|
p = nexttoken(token, p, ' ');
|
|
if ( token )
|
|
{
|
|
if ( !Q_stricmp( token, "JumpJet" ) && ASWGameMovement() )
|
|
{
|
|
//m_iMeleeAllowMovement = MELEE_MOVEMENT_FULL;
|
|
//ASWGameMovement()->DoJumpJet();
|
|
}
|
|
else if ( !Q_stricmp( token, "RelectProjectiles" ) )
|
|
{
|
|
m_bReflectingProjectiles = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef GAME_DLL
|
|
//HandleAnimEvent( (animevent_t) event );
|
|
#else
|
|
FireEvent( GetAbsOrigin(), GetAbsAngles(), event, options );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool CASW_Marine::IsHacking( void )
|
|
{
|
|
return ( m_hCurrentHack.Get() != NULL );
|
|
}
|
|
|
|
// returns weapon's position in our myweapons array
|
|
int CASW_Marine::GetWeaponIndex( CBaseCombatWeapon *pWeapon ) const
|
|
{
|
|
for ( int i = 0; i < WeaponCount() ; i++ )
|
|
{
|
|
if ( GetWeapon( i ) == pWeapon )
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// DoMeleeDamageTrace()
|
|
// performs a melee damage trace forward or in a yaw range (counterclock wise, relative to the marine's forward vector)
|
|
void CASW_Marine::DoMeleeDamageTrace( float flYawStart, float flYawEnd )
|
|
{
|
|
if ( asw_melee_debug.GetBool() )
|
|
{
|
|
Msg( "%s DoMeleeDamageTrace\n", IsServer() ? "[S]" : "[C]" );
|
|
}
|
|
|
|
float flDistance = 50.0f;
|
|
float flTraceSize = 16.0f;
|
|
CASW_Melee_Attack *pAttack = GetCurrentMeleeAttack();
|
|
|
|
bool bHitBehindMarine = false;
|
|
|
|
if ( pAttack )
|
|
{
|
|
if ( pAttack->m_flTraceDistance > 0 )
|
|
{
|
|
flDistance = pAttack->m_flTraceDistance;
|
|
}
|
|
if ( pAttack->m_flTraceHullSize > 0 )
|
|
{
|
|
flTraceSize = pAttack->m_flTraceHullSize;
|
|
}
|
|
|
|
bHitBehindMarine = pAttack->m_bAllowHitsBehindMarine;
|
|
}
|
|
|
|
Vector maxs, mins;
|
|
maxs.Init();
|
|
mins.Init();
|
|
|
|
Vector vecTraceForward, vecTraceRight;
|
|
AngleVectors( GetAbsAngles(), &vecTraceForward, &vecTraceRight, NULL );
|
|
|
|
Vector vecTraceStart = GetAbsOrigin();
|
|
|
|
// For directed attacks, this is the allowed angle tolerance for a hit
|
|
float flAttackConeAngle = 0.0f;
|
|
|
|
float flVerticalOffset = WorldAlignSize().z * 0.5;
|
|
|
|
if( flVerticalOffset < maxs.z )
|
|
{
|
|
// There isn't enough room to trace this hull, it's going to drag the ground.
|
|
// so make the vertical offset just enough to clear the ground.
|
|
flVerticalOffset = maxs.z + 1.0;
|
|
}
|
|
|
|
vecTraceStart.z += flVerticalOffset;
|
|
|
|
if ( fabs(flYawStart - flYawEnd) > 0.0f )
|
|
{
|
|
QAngle angleStart = vec3_angle;
|
|
QAngle angleEnd = vec3_angle;
|
|
|
|
// If a yaw range is specified, compute an AABB that bounds the yaw range
|
|
flYawStart = AngleNormalize( flYawStart );
|
|
flYawEnd = AngleNormalize( flYawEnd );
|
|
|
|
flAttackConeAngle = 0.5f * AngleDiff(flYawEnd, flYawStart);
|
|
|
|
angleStart[ YAW ] = flYawStart;
|
|
angleEnd[ YAW ] = flYawEnd;
|
|
|
|
Vector vecAimStart, vecAimEnd;
|
|
VectorRotate( vecTraceForward, angleStart, vecAimStart );
|
|
VectorRotate( vecTraceForward, angleEnd, vecAimEnd );
|
|
|
|
vecTraceForward = (0.5f) * (vecAimStart + vecAimEnd);
|
|
VectorNormalize( vecTraceForward );
|
|
|
|
// Compute trace hull size
|
|
// If we're given an angle to sweep out, set the Y hull size to bound the cone
|
|
Vector vecExtents = 0.5 * ((vecAimEnd - vecAimStart) * flDistance);
|
|
|
|
maxs.x = MAX( vecExtents.x, -vecExtents.x );
|
|
maxs.y = MAX( vecExtents.y, -vecExtents.y );
|
|
|
|
mins.x = MIN( vecExtents.x, -vecExtents.x );
|
|
mins.y = MIN( vecExtents.y, -vecExtents.y );
|
|
|
|
maxs.z = 32.0f;
|
|
mins.z = 4.0f;
|
|
|
|
// Yaw angles may behind the marine, so allow hits even if creature is behind the marine
|
|
bHitBehindMarine = true;
|
|
|
|
#ifdef GAME_DLL
|
|
if ( ai_show_hull_attacks.GetBool() )
|
|
{
|
|
NDebugOverlay::Line( vecTraceStart, vecTraceStart + vecAimStart * flDistance, 255, 255, 0, true, 1.0f );
|
|
NDebugOverlay::Line( vecTraceStart, vecTraceStart + vecAimEnd * flDistance, 0, 255, 0, true, 1.0f );
|
|
NDebugOverlay::Line( vecTraceStart, vecTraceStart + vecTraceForward * flDistance, 255, 0, 0, true, 1.0f );
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// otherwise, just use the standard hull
|
|
maxs.x = flTraceSize;
|
|
maxs.y = flTraceSize;
|
|
maxs.z = ASW_MARINE_MELEE_HULL_TRACE_Z;
|
|
|
|
mins.x = -flTraceSize;
|
|
mins.y = -flTraceSize;
|
|
mins.z = -ASW_MARINE_MELEE_HULL_TRACE_Z;
|
|
}
|
|
|
|
Vector vecTraceEnd = vecTraceStart + (vecTraceForward * flDistance);
|
|
|
|
// asw - make melee attacks trace below us too, so it's possible hard to hit things just below you on a slope
|
|
mins.z -= 30;
|
|
|
|
#ifdef GAME_DLL
|
|
// check for turning on lag compensation
|
|
CASW_Player *pPlayer = GetCommander();
|
|
bool bLagComp = false;
|
|
if ( pPlayer && IsInhabited() && !CASW_Lag_Compensation::IsInLagCompensation() )
|
|
{
|
|
bLagComp = true;
|
|
CASW_Lag_Compensation::AllowLagCompensation( pPlayer );
|
|
CASW_Lag_Compensation::RequestLagCompensation( pPlayer, pPlayer->GetCurrentUserCommand() );
|
|
}
|
|
#endif
|
|
const CBaseEntity * ent = NULL;
|
|
if ( gpGlobals->maxClients > 1 )
|
|
{
|
|
// temp remove suppress host
|
|
ent = te->GetSuppressHost();
|
|
te->SetSuppressHost( NULL );
|
|
}
|
|
|
|
MeleeTraceHullAttack( vecTraceStart, vecTraceEnd, mins, maxs, bHitBehindMarine, flAttackConeAngle );
|
|
|
|
if ( gpGlobals->maxClients > 1 )
|
|
{
|
|
te->SetSuppressHost( (CBaseEntity *) ent );
|
|
}
|
|
#ifdef GAME_DLL
|
|
if ( bLagComp )
|
|
{
|
|
CASW_Lag_Compensation::FinishLagCompensation(); // undo lag compensation if we need to
|
|
}
|
|
#endif
|
|
}
|
|
|
|
CBaseEntity *CASW_Marine::MeleeTraceHullAttack( const Vector &vecStart, const Vector &vecEnd, const Vector &vecMins, const Vector &vecMaxs, bool bHitBehindMarine, float flAttackCone )
|
|
{
|
|
VPROF( "CASW_Marine::MeleeTraceHullAttack() - Marine melee attacks" );
|
|
|
|
CTakeDamageInfo dmgInfo( this, this, 0.0f, DMG_CLUB );
|
|
dmgInfo.SetDamage( MarineSkills()->GetSkillBasedValueByMarine( this, ASW_MARINE_SKILL_MELEE, ASW_MARINE_SUBSKILL_MELEE_DMG ) );
|
|
|
|
Vector vecAttackDir = vecEnd - vecStart;
|
|
VectorNormalize( vecAttackDir );
|
|
|
|
CASW_Trace_Filter_Melee traceFilter( this, COLLISION_GROUP_NONE, this, false, bHitBehindMarine, &vecAttackDir, flAttackCone, NULL /*&m_RecentMeleeHits*/ );
|
|
|
|
Ray_t ray;
|
|
ray.Init( vecStart, vecEnd, vecMins, vecMaxs );
|
|
|
|
int nTrace = 0;
|
|
trace_t tr;
|
|
enginetrace->TraceRay( ray, MASK_SHOT_HULL, &traceFilter, &tr );
|
|
|
|
#ifdef GAME_DLL
|
|
bool bHitBlockingProp = false;
|
|
bool bHitEnemy = false;
|
|
#endif
|
|
|
|
while ( nTrace < ASW_MAX_HITS_PER_TRACE && dmgInfo.GetDamage() > 0.0f )
|
|
{
|
|
Vector vecAttackerCenter = WorldSpaceCenter();
|
|
|
|
// Perform damage on hit entities
|
|
trace_t* tr = &traceFilter.m_HitTraces[ nTrace ];
|
|
CBaseEntity *pHitEntity = tr->m_pEnt;
|
|
|
|
if ( !pHitEntity )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( asw_melee_debug.GetBool() )
|
|
{
|
|
Msg( "Melee trace %d hit %d:%s\n", nTrace, pHitEntity->entindex(), pHitEntity->GetDebugName() );
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
// temp test
|
|
if ( !IsInhabited() && GetPhysicsPropTarget() && GetPhysicsPropTarget() == pHitEntity )
|
|
break;
|
|
#endif
|
|
|
|
#ifdef DISTRIBUTE_MELEE_DAMAGE_OVER_TARGETS
|
|
int nHealth = pHitEntity->GetHealth();
|
|
#endif
|
|
|
|
// TODO: TraceAttack should come from the impact fist? Would need to somehow mark which bone is being used for which melee attack?
|
|
// allow melee attacks to specify a source offset for effect playback
|
|
Vector vecTraceAttackOffset = Vector( 0.0f, 0.0f, 0.0f );
|
|
ASWMeleeSystem()->ComputeTraceOffset( this, vecTraceAttackOffset );
|
|
|
|
Vector vecHitDir = tr->endpos - (vecAttackerCenter + vecTraceAttackOffset);
|
|
if ( vecHitDir.IsZero() )
|
|
{
|
|
vecHitDir = vecAttackDir;
|
|
}
|
|
else
|
|
{
|
|
VectorNormalize( vecHitDir );
|
|
}
|
|
|
|
// notify melee weapons
|
|
//if ( pMeleeWeapon )
|
|
//{
|
|
//pMeleeWeapon->OnMeleeDamageTraceHit( dmgInfo, vecHitDir, tr );
|
|
//}
|
|
|
|
// TODO
|
|
//ASWCalculateMarineMeleeDamageForce( &dmgInfo, this, vecHitDir, tr );
|
|
CalculateMeleeDamageForce( &dmgInfo, vecHitDir, vecStart );
|
|
|
|
|
|
// actually perform the damage
|
|
ApplyMeleeDamage( pHitEntity, dmgInfo, vecHitDir, tr );
|
|
|
|
#ifdef GAME_DLL
|
|
|
|
if ( !IsInhabited() )
|
|
{
|
|
if ( GetPhysicsPropTarget() && GetPhysicsPropTarget() == pHitEntity )
|
|
{
|
|
bHitBlockingProp = true;
|
|
CheckForDisablingAICollision( GetPhysicsPropTarget() );
|
|
}
|
|
else if ( GetEnemy() && GetEnemy() == pHitEntity )
|
|
{
|
|
bHitEnemy = true;
|
|
}
|
|
}
|
|
|
|
// Handy debuging tool to visualize HullAttack trace
|
|
if ( ai_show_hull_attacks.GetBool() )
|
|
{
|
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + dmgInfo.GetDamageForce(), 0, 128, 0, true, 1.0f );
|
|
NDebugOverlay::EntityBounds( pHitEntity, 128, 0, 0, 60, 3.0f );
|
|
}
|
|
#endif
|
|
|
|
#ifdef DISTRIBUTE_MELEE_DAMAGE_OVER_TARGETS // enabling this will cause melee damage to get used up with each entity it hits. It will prevent hitting multiple entities with one melee attack.
|
|
if ( nHealth <= 0 || pHitEntity->GetHealth() >= 0 )
|
|
{
|
|
dmgInfo.SetDamage( 0.0f );
|
|
}
|
|
else
|
|
{
|
|
float fHealthChange = nHealth - pHitEntity->GetHealth();
|
|
float fDamageScale = fHealthChange / dmgInfo.GetDamage();
|
|
|
|
dmgInfo.SetDamage( -pHitEntity->GetHealth() / fDamageScale );
|
|
}
|
|
#endif
|
|
|
|
nTrace++;
|
|
}
|
|
|
|
// do an impact effect for kicking some things
|
|
PlayMeleeImpactEffects( traceFilter.m_hBestHit, traceFilter.m_pBestTrace );
|
|
|
|
#ifdef GAME_DLL
|
|
// Handy debuging tool to visualize HullAttack trace
|
|
if ( ai_show_hull_attacks.GetBool() )
|
|
{
|
|
// Draw this using SweptBox since the engine is doing AABB traces
|
|
NDebugOverlay::SweptBox( vecStart, vecEnd, vecMins, vecMaxs, vec3_angle, 255, 0, 0, 255, 1.0f );
|
|
}
|
|
|
|
if ( !IsInhabited() )
|
|
{
|
|
if ( GetPhysicsPropTarget() && !bHitBlockingProp )
|
|
{
|
|
// AI marine is trying to punch a prop out of the way, but he's not hitting it, force it to take damage
|
|
Vector vecTraceAttackOffset = Vector( 0.0f, 0.0f, 0.0f );
|
|
ASWMeleeSystem()->ComputeTraceOffset( this, vecTraceAttackOffset );
|
|
Vector vecHitDir = GetPhysicsPropTarget()->WorldSpaceCenter() - ( WorldSpaceCenter() + vecTraceAttackOffset );
|
|
VectorNormalize( vecHitDir );
|
|
CalculateMeleeDamageForce( &dmgInfo, vecHitDir, vecStart );
|
|
AddMultiDamage( dmgInfo, GetPhysicsPropTarget() );
|
|
ApplyMultiDamage();
|
|
CheckForDisablingAICollision( GetPhysicsPropTarget() );
|
|
}
|
|
else if ( GetEnemy() && !bHitEnemy )
|
|
{
|
|
// make sure AI marine connects with his enemy
|
|
Vector vecTraceAttackOffset = Vector( 0.0f, 0.0f, 0.0f );
|
|
ASWMeleeSystem()->ComputeTraceOffset( this, vecTraceAttackOffset );
|
|
Vector vecHitDir = GetEnemy()->WorldSpaceCenter() - ( WorldSpaceCenter() + vecTraceAttackOffset );
|
|
VectorNormalize( vecHitDir );
|
|
CalculateMeleeDamageForce( &dmgInfo, vecHitDir, vecStart );
|
|
AddMultiDamage( dmgInfo, GetEnemy() );
|
|
ApplyMultiDamage();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return traceFilter.m_hBestHit;
|
|
}
|
|
|
|
bool CASW_Marine::CanDoForcedAction( int iForcedAction )
|
|
{
|
|
// check our current weapon allows us to do a forced action
|
|
CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
if ( pWeapon && !pWeapon->CanDoForcedAction( iForcedAction ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CASW_Marine::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
|
|
{
|
|
#ifdef GAME_DLL
|
|
m_fNoDamageDecal = false;
|
|
if ( m_takedamage == DAMAGE_NO )
|
|
return;
|
|
#endif
|
|
|
|
CTakeDamageInfo subInfo = info;
|
|
|
|
#ifdef GAME_DLL
|
|
SetLastHitGroup( ptr->hitgroup );
|
|
m_nForceBone = ptr->physicsbone; // save this bone for physics forces
|
|
#endif
|
|
|
|
Assert( m_nForceBone > -255 && m_nForceBone < 256 );
|
|
|
|
if ( subInfo.GetDamage() >= 1.0 && !(subInfo.GetDamageType() & DMG_SHOCK )
|
|
&& !( subInfo.GetDamageType() & DMG_BURN ) )
|
|
{
|
|
#ifdef GAME_DLL
|
|
Bleed( subInfo, ptr->endpos, vecDir, ptr );
|
|
if ( ptr->hitgroup == HITGROUP_HEAD && m_iHealth - subInfo.GetDamage() > 0 )
|
|
{
|
|
m_fNoDamageDecal = true;
|
|
}
|
|
#else
|
|
Bleed( subInfo, ptr->endpos, vecDir, ptr );
|
|
#endif
|
|
}
|
|
|
|
if( !info.GetInflictor() )
|
|
{
|
|
subInfo.SetInflictor( info.GetAttacker() );
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
AddMultiDamage( subInfo, this );
|
|
#endif
|
|
}
|
|
|
|
void CASW_Marine::Bleed( const CTakeDamageInfo &info, const Vector &vecPos, const Vector &vecDir, trace_t *ptr )
|
|
{
|
|
#ifdef GAME_DLL
|
|
Vector vecDamagePos = info.GetDamagePosition();
|
|
CRecipientFilter filter;
|
|
filter.AddAllPlayers();
|
|
|
|
// if we've been shot by another marine...
|
|
if ( info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE )
|
|
{
|
|
if ( asw_marine_ff.GetInt() == 0 )
|
|
return;
|
|
|
|
UserMessageBegin( filter, "ASWMarineHitByFF" );
|
|
WRITE_SHORT( entindex() );
|
|
WRITE_FLOAT( vecDamagePos.x );
|
|
WRITE_FLOAT( vecDamagePos.y );
|
|
WRITE_FLOAT( vecDamagePos.z );
|
|
|
|
Vector vecNewDir = -vecDir;
|
|
WRITE_FLOAT( vecNewDir.x );
|
|
WRITE_FLOAT( vecNewDir.y );
|
|
WRITE_FLOAT( vecNewDir.z );
|
|
MessageEnd();
|
|
}
|
|
else
|
|
{
|
|
Vector vecInflictorPos = vecDamagePos;
|
|
if ( info.GetInflictor() )
|
|
{
|
|
vecInflictorPos = info.GetInflictor()->GetAbsOrigin();
|
|
}
|
|
|
|
UserMessageBegin( filter, "ASWMarineHitByMelee" );
|
|
WRITE_SHORT( entindex() );
|
|
WRITE_FLOAT( vecInflictorPos.x );
|
|
WRITE_FLOAT( vecInflictorPos.y );
|
|
WRITE_FLOAT( vecInflictorPos.z );
|
|
|
|
MessageEnd();
|
|
}
|
|
#endif
|
|
//DoBloodDecal( subInfo.GetDamage(), vecPos, vecDir, ptr, subInfo.GetDamageType() );
|
|
}
|
|
|
|
Vector CASW_Marine::GetOffhandThrowSource( const Vector *vecStandingPos )
|
|
{
|
|
Vector vecOrigin = GetAbsOrigin();
|
|
if ( vecStandingPos )
|
|
{
|
|
vecOrigin = *vecStandingPos;
|
|
}
|
|
Vector vecSrc = Weapon_ShootPosition() - GetAbsOrigin() + vecOrigin;
|
|
|
|
if ( IsInhabited() )
|
|
{
|
|
// check it fits where we want to spawn it
|
|
Ray_t ray;
|
|
trace_t pm;
|
|
ray.Init( WorldSpaceCenter() - GetAbsOrigin() + vecOrigin, vecSrc, -Vector( 4,4,4 ), Vector( 4,4,4 ) );
|
|
UTIL_TraceRay( ray, MASK_SOLID, this, COLLISION_GROUP_PROJECTILE, &pm );
|
|
if ( pm.fraction < 1.0f )
|
|
vecSrc = pm.endpos;
|
|
}
|
|
else
|
|
{
|
|
// AI marines throw from their center, so facing doesn't affect the arc
|
|
vecSrc.x = WorldSpaceCenter().x - GetAbsOrigin().x + vecOrigin.x;
|
|
vecSrc.y = WorldSpaceCenter().y - GetAbsOrigin().y + vecOrigin.y;
|
|
}
|
|
return vecSrc;
|
|
}
|
|
|
|
bool CASW_Marine::IsFiring()
|
|
{
|
|
return GetActiveASWWeapon() && GetActiveASWWeapon()->IsFiring();
|
|
}
|
|
|
|
// you can assume there is an attacker when this function is called.
|
|
void CASW_Marine::ApplyPassiveMeleeDamageEffects( CTakeDamageInfo &dmgInfo )
|
|
{
|
|
for (int i=0; i<ASW_MAX_MARINE_WEAPONS; ++i)
|
|
{
|
|
CASW_Weapon *pWeapon = GetASWWeapon( i );
|
|
if ( pWeapon )
|
|
{
|
|
float flDamageScale = pWeapon->GetPassiveMeleeDamageScale();
|
|
if ( flDamageScale != 1.0f )
|
|
{
|
|
dmgInfo.ScaleDamage( flDamageScale );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CASW_Marine::HasPowerFist()
|
|
{
|
|
CBaseEntity *pWeapon = GetWeapon( ASW_INVENTORY_SLOT_EXTRA );
|
|
return( pWeapon && pWeapon->Classify() == CLASS_ASW_FIST );
|
|
} |