#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(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(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(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 5: speedscale *= asw_marine_speed_scale_insane.GetFloat(); break; 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( 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;iGetPrimaryAmmoType() == 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(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( 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( 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 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( 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( 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(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(tr.m_pEnt); CASW_Simple_Alien *pAlien = dynamic_cast(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( 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( 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(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(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(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; iGetPassiveMeleeDamageScale(); 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 ); }