sqwarmed/sdk_src/game/shared/swarm/asw_marine_shared.cpp

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