sqwarmed/sdk_src/game/server/swarm/asw_marine.cpp

4770 lines
147 KiB
C++

//========= Copyright © 1996-2003, Valve LLC, All rights reserved. ============
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "ai_default.h"
#include "ai_task.h"
#include "ai_schedule.h"
#include "ai_node.h"
#include "ai_hull.h"
#include "ai_hint.h"
#include "ai_squad.h"
#include "ai_senses.h"
#include "ai_navigator.h"
#include "ai_motor.h"
#include "ai_behavior.h"
#include "ai_baseactor.h"
#include "ai_behavior_lead.h"
#include "ai_behavior_follow.h"
#include "ai_behavior_standoff.h"
#include "ai_behavior_assault.h"
#include "soundent.h"
#include "game.h"
#include "npcevent.h"
#include "entitylist.h"
#include "activitylist.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "sceneentity.h"
#include "asw_marine.h"
#include "asw_player.h"
#include "asw_marine_resource.h"
#include "asw_marine_profile.h"
#include "asw_weapon.h"
#include "asw_marine_speech.h"
//#include "asw_drone.h"
#include "asw_pickup.h"
#include "asw_pickup_weapon.h"
#include "asw_gamerules.h"
#include "asw_gamestats.h"
#include "asw_mission_manager.h"
#include "asw_fail_advice.h"
#include "ammodef.h"
#include "asw_shareddefs.h"
#include "asw_sentry_base.h"
#include "asw_button_area.h"
#include "asw_equipment_list.h"
#include "asw_weapon_parse.h"
#include "asw_fx_shared.h"
#include "asw_parasite.h"
#include "shareddefs.h"
#include "iasw_vehicle.h"
#include "obstacle_pushaway.h"
#include "asw_computer_area.h"
#include "asw_remote_turret_shared.h"
#include "asw_util_shared.h"
#include "EntityFlame.h"
#include "physics_prop_ragdoll.h"
#include "asw_weapon_flashlight_shared.h"
#include "beam_shared.h"
#include "iasw_server_usable_entity.h"
#include "asw_weapon_ammo_bag_shared.h"
#include "datacache/imdlcache.h"
#include "asw_weapon_autogun_shared.h"
#include "asw_burning.h"
#include "asw_door.h"
#include "asw_hack.h"
#include "te_effect_dispatch.h"
#include "asw_trace_filter_melee.h"
#include "ai_moveprobe.h"
#include "asw_ai_senses.h"
#include "particle_parse.h"
#include "asw_director.h"
#include "asw_melee_system.h"
#include "ai_network.h"
#include "asw_weapon_normal_armor.h"
#include "asw_fire.h"
#include "asw_achievements.h"
#include "asw_objective_escape.h"
#include "sendprop_priorities.h"
#include "asw_marine_gamemovement.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define ASW_DEFAULT_MARINE_MODEL "models/swarm/marine/marine.mdl"
//#define ASW_MARINE_ALWAYS_VPHYSICS
//=========================================================
// Marine activities
//=========================================================
LINK_ENTITY_TO_CLASS( asw_marine, CASW_Marine );
//---------------------------------------------------------
//
//---------------------------------------------------------
void SendProxy_CropMarineFlagsToPlayerFlagBitsLength( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID)
{
int mask = (1<<PLAYER_FLAG_BITS) - 1;
int data = *(int *)pVarData;
pOut->m_Int = ( data & mask );
//CBaseEntity *pEntity = (CBaseEntity *)pProp;
//if (pEntity)
//{
//if (( data & mask ) & FL_ONGROUND)
//{
//Msg(" [S] Transmitting FL_ONGROUND (flags=%d)\n", pEntity->GetFlags());
//}
//else
//{
//Msg(" [S] Not Transmitting FL_ONGROUND (flags=%d)\n", pEntity->GetFlags());
//}
//}
//else
//{
//Msg(" WARNING updated flags without an ent\n");
//}
}
IMPLEMENT_SERVERCLASS_ST(CASW_Marine, DT_ASW_Marine)
SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ),
SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ),
SendPropExclude( "DT_BaseCombatCharacter", "bcc_localdata" ),
// asw_playeranimstate and clientside animation takes care of these on the client
SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
// We need to send a hi-res origin to avoid prediction errors sliding along walls
SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ),
// send a hi-res origin to the local player for use in prediction
//SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
SendPropVectorXY(SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE | SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_OriginXY ),
SendPropFloat (SENDINFO_VECTORELEM(m_vecOrigin, 2), -1, SPROP_NOSCALE | SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_OriginZ ), // , SENDPROP_LOCALPLAYER_ORIGINZ_PRIORITY
SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 0), 32, SPROP_NOSCALE|SPROP_CHANGES_OFTEN ),
SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 1), 32, SPROP_NOSCALE|SPROP_CHANGES_OFTEN ),
SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 2), 32, SPROP_NOSCALE|SPROP_CHANGES_OFTEN ),
#if PREDICTION_ERROR_CHECK_LEVEL > 1
SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 0), 13, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesX ),
SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 1), 13, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesY ),
SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 2), 13, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesZ ),
#else
SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 0), 13, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesX ),
SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 1), 13, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesY ),
SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 2), 13, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesZ ),
#endif
SendPropFloat ( SENDINFO(m_fAIPitch), 0, SPROP_NOSCALE),
SendPropInt (SENDINFO(m_fFlags), PLAYER_FLAG_BITS, SPROP_UNSIGNED|SPROP_CHANGES_OFTEN, SendProxy_CropMarineFlagsToPlayerFlagBitsLength ),
SendPropInt (SENDINFO(m_iHealth), 10 ),
SendPropInt( SENDINFO(m_iMaxHealth), 10 ),
SendPropFloat (SENDINFO(m_fInfestedTime), 6, SPROP_UNSIGNED, 0.0f, 64.0f ),
SendPropFloat (SENDINFO(m_fInfestedStartTime), 0, SPROP_NOSCALE ),
SendPropInt (SENDINFO(m_ASWOrders), 4),
SendPropEHandle( SENDINFO ( m_Commander ) ),
SendPropArray3 ( SENDINFO_ARRAY3(m_iAmmo), SendPropInt( SENDINFO_ARRAY(m_iAmmo), 10, SPROP_UNSIGNED ) ),
SendPropBool (SENDINFO(m_bSlowHeal)),
SendPropInt (SENDINFO(m_iSlowHealAmount), 10 ),
SendPropBool (SENDINFO(m_bPreventMovement)),
SendPropBool (SENDINFO(m_bWalking) ),
SendPropFloat (SENDINFO(m_fFFGuardTime), 0, SPROP_NOSCALE ),
SendPropEHandle( SENDINFO ( m_hUsingEntity ) ),
SendPropEHandle( SENDINFO ( m_hCurrentHack ) ),
SendPropVector( SENDINFO ( m_vecFacingPointFromServer ), 0, SPROP_NOSCALE ),
SendPropEHandle ( SENDINFO( m_hGroundEntity ), SPROP_CHANGES_OFTEN ),
SendPropEHandle( SENDINFO ( m_hMarineFollowTarget ) ),
SendPropTime( SENDINFO(m_fStopMarineTime) ),
SendPropTime( SENDINFO(m_fNextMeleeTime) ),
SendPropTime( SENDINFO( m_flNextAttack ) ),
SendPropInt ( SENDINFO( m_iMeleeAttackID ), 7, SPROP_UNSIGNED ),
SendPropBool (SENDINFO(m_bOnFire)),
// emotes
SendPropBool (SENDINFO(bEmoteMedic)),
SendPropBool (SENDINFO(bEmoteAmmo)),
SendPropBool (SENDINFO(bEmoteSmile)),
SendPropBool (SENDINFO(bEmoteStop)),
SendPropBool (SENDINFO(bEmoteGo)),
SendPropBool (SENDINFO(bEmoteExclaim)),
SendPropBool (SENDINFO(bEmoteAnimeSmile)),
SendPropBool (SENDINFO(bEmoteQuestion)),
// driving
SendPropEHandle( SENDINFO ( m_hASWVehicle ) ),
SendPropBool (SENDINFO(m_bDriving)),
SendPropBool (SENDINFO(m_bIsInVehicle)),
// knocked out
SendPropBool (SENDINFO(m_bKnockedOut)),
// turret
SendPropEHandle( SENDINFO ( m_hRemoteTurret ) ),
// We want to send all the marine's weapons to all the other marines
SendPropArray3( SENDINFO_ARRAY3(m_hMyWeapons), SendPropEHandle( SENDINFO_ARRAY(m_hMyWeapons) ) ),
SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 0), 0, SPROP_NOSCALE ),
SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 1), 0, SPROP_NOSCALE ),
SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 2), 0, SPROP_NOSCALE ),
#ifdef MELEE_CHARGE_ATTACKS
SendPropFloat ( SENDINFO(m_flMeleeHeavyKeyHoldStart), 0, SPROP_NOSCALE ),
#endif
SendPropInt ( SENDINFO( m_iForcedActionRequest ) ),
SendPropBool ( SENDINFO( m_bReflectingProjectiles ) ),
SendPropTime ( SENDINFO( m_flDamageBuffEndTime ) ),
SendPropTime ( SENDINFO( m_flElectrifiedArmorEndTime ) ),
SendPropInt ( SENDINFO( m_iPowerupType ) ),
SendPropTime ( SENDINFO( m_flPowerupExpireTime ) ),
SendPropBool ( SENDINFO( m_bPowerupExpires ) ),
SendPropFloat ( SENDINFO(m_flKnockdownYaw) ),
SendPropFloat ( SENDINFO( m_flMeleeYaw ) ),
SendPropBool ( SENDINFO( m_bFaceMeleeYaw ) ),
SendPropFloat ( SENDINFO( m_flPreventLaserSightTime ) ),
SendPropBool ( SENDINFO( m_bAICrouch ) ),
SendPropInt ( SENDINFO( m_iJumpJetting )),
END_SEND_TABLE()
//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CASW_Marine )
DEFINE_FIELD( m_bSlowHeal, FIELD_BOOLEAN ),
DEFINE_FIELD( m_iSlowHealAmount, FIELD_INTEGER ),
DEFINE_FIELD( m_vecFacingPointFromServer, FIELD_VECTOR ),
DEFINE_FIELD( m_hRemoteTurret, FIELD_EHANDLE ),
DEFINE_FIELD( m_hASWVehicle, FIELD_EHANDLE ),
DEFINE_FIELD( m_bDriving, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bIsInVehicle, FIELD_BOOLEAN ),
DEFINE_FIELD( bEmoteMedic, FIELD_BOOLEAN ),
DEFINE_FIELD( bEmoteAmmo, FIELD_BOOLEAN ),
DEFINE_FIELD( bEmoteStop, FIELD_BOOLEAN ),
DEFINE_FIELD( bEmoteGo, FIELD_BOOLEAN ),
DEFINE_FIELD( bEmoteExclaim, FIELD_BOOLEAN ),
DEFINE_FIELD( bEmoteAnimeSmile, FIELD_BOOLEAN ),
DEFINE_FIELD( bEmoteQuestion, FIELD_BOOLEAN ),
DEFINE_FIELD( m_Commander, FIELD_EHANDLE),
DEFINE_FIELD( m_hRemoteTurret, FIELD_EHANDLE ),
DEFINE_FIELD( m_fHoldingYaw, FIELD_FLOAT ),
DEFINE_FIELD( m_flFirstBurnTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flLastBurnTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flLastBurnSoundTime, FIELD_TIME ),
DEFINE_FIELD( m_fNextPainSoundTime, FIELD_TIME ),
DEFINE_FIELD( m_fStartedFiringTime, FIELD_TIME ),
DEFINE_FIELD( m_fNextSlowHealTick, FIELD_FLOAT ),
DEFINE_FIELD( m_fInfestedTime, FIELD_TIME ),
DEFINE_FIELD( m_fInfestedStartTime, FIELD_TIME ),
DEFINE_FIELD( m_fStopFacingPointTime, FIELD_FLOAT ),
DEFINE_FIELD( m_fLastASWThink, FIELD_TIME ),
DEFINE_FIELD( m_iSlowHealAmount, FIELD_INTEGER ),
DEFINE_FIELD( m_iInfestCycle, FIELD_INTEGER ),
DEFINE_FIELD( m_fLastASWThink, FIELD_INTEGER ),
DEFINE_FIELD( m_Commander, FIELD_EHANDLE),
DEFINE_FIELD( m_bHacking, FIELD_BOOLEAN ),
DEFINE_FIELD( m_hCurrentHack, FIELD_EHANDLE),
DEFINE_FIELD( m_hUsingEntity, FIELD_EHANDLE),
DEFINE_FIELD( m_bKnockedOut, FIELD_BOOLEAN),
DEFINE_FIELD( m_fKickTime, FIELD_TIME ),
DEFINE_FIELD( m_fNextMeleeTime, FIELD_TIME ),
DEFINE_FIELD( m_fStopMarineTime, FIELD_TIME ),
DEFINE_FIELD( m_hLastWeaponSwitchedTo, FIELD_EHANDLE ),
// m_PlayerAnimState - recreated
// m_MarineSpeech - recreated
DEFINE_FIELD( m_MarineResource, FIELD_EHANDLE ),
DEFINE_FIELD( m_nOldButtons, FIELD_INTEGER ),
DEFINE_FIELD( m_bWantsToFire, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bWantsToFire2, FIELD_BOOLEAN ),
DEFINE_FIELD( m_fMarineAimError, FIELD_FLOAT ),
DEFINE_FIELD( m_ASWOrders, FIELD_INTEGER ),
DEFINE_FIELD( m_fOverkillShootTime, FIELD_TIME ),
DEFINE_FIELD( m_vecOverkillPos, FIELD_VECTOR ),
DEFINE_FIELD( m_fUnfreezeTime, FIELD_TIME ),
DEFINE_FIELD( m_fFFGuardTime, FIELD_FLOAT ),
DEFINE_FIELD( m_fLastStillTime, FIELD_TIME ),
DEFINE_FIELD( m_bDoneWoundedRebuke, FIELD_BOOLEAN ),
DEFINE_FIELD( m_vecMoveToOrderPos, FIELD_VECTOR ),
DEFINE_FIELD( m_fFriendlyFireDamage, FIELD_FLOAT ),
DEFINE_ARRAY( m_pRecentAttackers, FIELD_INTEGER, ASW_MOB_VICTIM_SIZE ),
DEFINE_FIELD( m_fLastMobDamageTime, FIELD_TIME ),
DEFINE_FIELD( m_bHasBeenMobAttacked, FIELD_BOOLEAN ),
DEFINE_FIELD( m_hInfestationCurer, FIELD_EHANDLE ),
DEFINE_FIELD( m_bOnFire, FIELD_BOOLEAN ),
DEFINE_FIELD( m_fLastShotAlienTime, FIELD_TIME ),
DEFINE_FIELD( m_fLastShotJunkTime, FIELD_TIME ),
DEFINE_FIELD( m_fUsingEngineeringAura, FIELD_TIME ),
DEFINE_FIELD( m_fCachedIdealSpeed, FIELD_FLOAT ),
DEFINE_FIELD( m_fNextAlienWalkDamage, FIELD_FLOAT ),
DEFINE_FIELD( m_iLightLevel, FIELD_INTEGER ),
DEFINE_FIELD( m_fLastFriendlyFireTime, FIELD_FLOAT ),
DEFINE_FIELD( m_fLastAmmoCheckTime, FIELD_FLOAT ),
DEFINE_FIELD( m_fFriendlyFireAbsorptionTime, FIELD_FLOAT ),
DEFINE_FIELD( m_vecMeleeStartPos, FIELD_VECTOR ),
DEFINE_FIELD( m_bFaceMeleeYaw, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flMeleeStartTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flMeleeLastCycle, FIELD_FLOAT ),
DEFINE_FIELD( m_flMeleeYaw, FIELD_FLOAT ),
DEFINE_FIELD( m_bMeleeCollisionDamage, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bMeleeComboKeypressAllowed, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bMeleeComboKeyPressed, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bMeleeComboTransitionAllowed, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bMeleeMadeContact, FIELD_BOOLEAN ),
DEFINE_FIELD( m_iUsableItemsOnMeleePress, FIELD_INTEGER ),
DEFINE_FIELD( m_iMeleeAllowMovement, FIELD_INTEGER ),
DEFINE_FIELD( m_bMeleeKeyReleased, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bPlayedMeleeHitSound, FIELD_BOOLEAN ),
#ifdef MELEE_CHARGE_ATTACKS
DEFINE_FIELD( m_flMeleeHeavyKeyHoldStart, FIELD_FLOAT ),
DEFINE_FIELD( m_bMeleeHeavyKeyHeld, FIELD_BOOLEAN ),
#endif
DEFINE_FIELD( m_bMeleeChargeActivate, FIELD_BOOLEAN ),
DEFINE_AUTO_ARRAY( m_iPredictedEvent, FIELD_INTEGER ),
DEFINE_AUTO_ARRAY( m_flPredictedEventTime, FIELD_FLOAT ),
DEFINE_FIELD( m_iNumPredictedEvents, FIELD_INTEGER ),
DEFINE_FIELD( m_iOnLandMeleeAttackID, FIELD_INTEGER ),
DEFINE_FIELD( m_flNextStumbleTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flDamageBuffEndTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flElectrifiedArmorEndTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flLastSquadEnemyTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flLastSquadShotAlienTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flLastHurtAlienTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flLastAttributeExplosionSound, FIELD_TIME ),
DEFINE_FIELD( m_iPowerupType, FIELD_INTEGER ),
DEFINE_FIELD( m_flPowerupExpireTime, FIELD_FLOAT ),
DEFINE_FIELD( m_bPowerupExpires, FIELD_BOOLEAN ),
END_DATADESC()
extern ConVar weapon_showproficiency;
extern ConVar asw_leadership_radius;
extern ConVar asw_buzzer_poison_duration;
extern ConVar asw_debug_marine_chatter;
extern ConVar asw_debug_medals;
extern ConVar ai_show_hull_attacks;
extern ConVar asw_medal_melee_hits;
extern int AE_MARINE_KICK;
extern int AE_MARINE_UNFREEZE;
ConVar asw_marine_stumble_on_damage( "asw_marine_stumble_on_damage", "1", FCVAR_CHEAT, "Marine stumbles when he takes damage" );
ConVar asw_stumble_interval( "asw_stumble_interval", "2.0", FCVAR_CHEAT, "Min time between stumbles" );
ConVar asw_knockdown_interval( "asw_knockdown_interval", "3.0", FCVAR_CHEAT, "Min time between knockdowns" );
ConVar asw_marine_fall_damage( "asw_marine_fall_damage", "0", FCVAR_CHEAT, "Marines take falling damage" );
ConVar asw_screenflash("asw_screenflash", "0", FCVAR_CHEAT, "Alpha of damage screen flash");
ConVar asw_damage_indicator("asw_damage_indicator", "1", FCVAR_CHEAT, "If set, directional damage indicator is shown");
ConVar asw_marine_server_ragdoll("asw_marine_server_ragdoll", "0", FCVAR_CHEAT, "If set, marines will have server ragdolls instead of clientside ones.");
ConVar asw_marine_death_protection("asw_marine_death_protection", "1", FCVAR_CHEAT, "Prevents marines from dying in one hit, unless on 1 health");
ConVar asw_marine_melee_distance("asw_marine_melee_distance", "50", FCVAR_CHEAT, "How far the marine can kick");
ConVar asw_marine_melee_damage("asw_marine_melee_damage", "20", FCVAR_CHEAT, "How much damage the marine's kick does");
ConVar asw_marine_melee_force("asw_marine_melee_force", "200000", FCVAR_CHEAT, "Marine kick force = this / dist");
ConVar asw_marine_melee_max_force("asw_marine_melee_max_force", "10000", FCVAR_CHEAT, "Maximum force allowed");
ConVar asw_marine_melee_kick_lift("asw_marine_melee_kick_lift", "0.2", FCVAR_CHEAT, "Upwards Z-Force given to kicked objects");
ConVar asw_marine_ai_acceleration("asw_marine_ai_acceleration", "4.0f", FCVAR_CHEAT, "Acceleration boost for marine AI");
ConVar asw_marine_scan_beams("asw_marine_scan_beams", "0", 0, "Draw scan beams for marines holding position");
ConVar asw_debug_marine_damage("asw_debug_marine_damage", "0", 0, "Show damage marines are taking");
ConVar asw_marine_ff("asw_marine_ff", "1", FCVAR_CHEAT, "Marine friendly fire setting (0 = FFGuard, 1 = Normal (based on mission difficulty), 2 = Always max)", true, 0, true, 2);
ConVar asw_marine_ff_guard_time("asw_marine_ff_guard_time", "5.0", FCVAR_CHEAT, "Amount of time firing is disabled for when activating friendly fire guard");
ConVar asw_marine_ff_dmg_base("asw_marine_ff_dmg_base", "1.0", FCVAR_CHEAT, "Amount of friendly fire damage on mission difficulty 5");
ConVar asw_marine_ff_dmg_step("asw_marine_ff_dmg_step", "0.2", FCVAR_CHEAT, "Amount friendly fire damage is modified per mission difficuly level away from 5");
ConVar asw_marine_ff_absorption("asw_marine_ff_absorption", "1", FCVAR_NONE, "Friendly fire absorption style (0=none 1=ramp up 2=ramp down)");
ConVar asw_marine_ff_absorption_decay_rate("asw_marine_ff_absorption_decay_rate", "0.33f", FCVAR_CHEAT, "Rate of FF absorption decay");
ConVar asw_marine_ff_absorption_build_rate("asw_marine_ff_absorption_build_rate", "0.25f", FCVAR_CHEAT, "Rate of FF absorption decay build up when being shot by friendlies");
ConVar asw_marine_burn_time_easy("asw_marine_burn_time_easy", "6", FCVAR_CHEAT, "Amount of time marine burns for when ignited on easy difficulty");
ConVar asw_marine_burn_time_normal("asw_marine_burn_time_normal", "8", FCVAR_CHEAT, "Amount of time marine burns for when ignited on normal difficulty");
ConVar asw_marine_burn_time_hard("asw_marine_burn_time_hard", "12", FCVAR_CHEAT, "Amount of time marine burns for when ignited on hard difficulty");
ConVar asw_marine_burn_time_insane("asw_marine_burn_time_insane", "15", FCVAR_CHEAT, "Amount of time marine burns for when ignited on insane difficulty");
ConVar asw_marine_time_until_ignite("asw_marine_time_until_ignite", "0.7f", FCVAR_CHEAT, "Amount of time before a marine ignites from taking repeated burn damage");
ConVar asw_mad_firing_break("asw_mad_firing_break", "4", 0, "Point at which the mad firing counter triggers the mad firing speech");
ConVar asw_mad_firing_decay("asw_mad_firing_decay", "0.15", 0, "Tick down rate of the mad firing counter");
ConVar asw_marine_special_idle_chatter_chance("asw_marine_special_idle_chatter_chance", "0.25", 0, "Chance of marine doing a special idle chatter");
ConVar asw_force_ai_fire("asw_force_ai_fire", "0", FCVAR_CHEAT, "Forces all AI marines to fire constantly");
ConVar asw_realistic_death_chatter("asw_realistic_death_chatter", "0", FCVAR_NONE, "If true, only 1 nearby marine will shout about marine deaths");
ConVar asw_god( "asw_god", "0", FCVAR_CHEAT, "Set to 1 to make marines invulnerable" );
ConVar asw_movement_direction_tolerance( "asw_movement_direction_tolerance", "30.0", FCVAR_CHEAT );
ConVar asw_movement_direction_interval( "asw_movement_direction_interval", "0.5", FCVAR_CHEAT );
float CASW_Marine::s_fNextMadFiringChatter = 0;
float CASW_Marine::s_fNextIdleChatterTime = 0;
void CASW_Marine::DoAnimationEvent( PlayerAnimEvent_t event )
{
if (gpGlobals->maxClients > 1 &&
(event == PLAYERANIMEVENT_RELOAD || event == PLAYERANIMEVENT_JUMP || event == PLAYERANIMEVENT_WEAPON_SWITCH
|| event == PLAYERANIMEVENT_HEAL || event == PLAYERANIMEVENT_KICK || event == PLAYERANIMEVENT_THROW_GRENADE
|| event == PLAYERANIMEVENT_BAYONET || event == PLAYERANIMEVENT_PICKUP
|| ( event >= PLAYERANIMEVENT_MELEE && event <= PLAYERANIMEVENT_MELEE_LAST ) ) )
{
TE_MarineAnimEventExceptCommander( this, event ); // Send to any clients other than my commander who can see this guy.
}
else
{
TE_MarineAnimEvent( this, event ); // Send to all clients who can see this guy.
}
MDLCACHE_CRITICAL_SECTION();
m_PlayerAnimState->DoAnimationEvent(event);
}
void CASW_Marine::DoAnimationEventToAll( PlayerAnimEvent_t event )
{
TE_MarineAnimEvent( this, event ); // Send to all clients who can see this guy.
m_PlayerAnimState->DoAnimationEvent(event);
Msg("CASW_Marine::DoAnimationEventToAll %d\n", (int) event);
}
void CASW_Marine::HandleAnimEvent( animevent_t *pEvent )
{
int nEvent = pEvent->Event();
if( !IsInhabited() && nEvent == AE_MELEE_DAMAGE )
{
float flYawStart, flYawEnd;
flYawStart = flYawEnd = 0.0f;
const char* options = pEvent->options;
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 );
return;
}
if ( GetCurrentMeleeAttack() )
{
if ( !GetCurrentMeleeAttack()->AllowNormalAnimEvent( this, nEvent ) )
return;
}
if ( nEvent == AE_MARINE_UNFREEZE )
{
Msg("AE_MARINE_UNFREEZE\n");
RemoveFlag(FL_FROZEN);
return;
}
else if ( nEvent == AE_ASW_FOOTSTEP || nEvent == AE_MARINE_FOOTSTEP )
{
// footsteps are played clientside
return;
}
BaseClass::HandleAnimEvent( pEvent );
}
CASW_Marine::CASW_Marine() : m_RecentMeleeHits( 16, 16 )
{
m_flLastEnemyYaw = 0;
m_flLastEnemyYawTime = 0;;
m_flAIYawOffset = 0;
m_flNextYawOffsetTime = 0;
m_bAICrouch = false;
m_MarineResource = NULL;
m_fUnfreezeTime = 0;
m_PlayerAnimState = CreatePlayerAnimState(this, this, LEGANIM_9WAY, false);
UseClientSideAnimation();
m_HackedGunPos = Vector ( 0, 0, ASW_MARINE_GUN_OFFSET_Z );
m_nOldButtons = 0;
m_MarineSpeech = new CASW_MarineSpeech(this);
m_flHealRateScale = 1.0f;
m_fNextSlowHealTick = 0;
m_fLastASWThink = gpGlobals->curtime;
m_fInfestedTime = 0;
m_fInfestedStartTime = 0;
m_iInfestCycle = 0;
m_flFirstBurnTime = 0;
m_flLastBurnTime = 0;
m_flLastBurnSoundTime = 0;
m_fNextPainSoundTime = 0;
m_fStopFacingPointTime = 0;
m_fHoldingYaw = 0;
m_hKnockedOutRagdoll = NULL;
m_fKickTime = 0;
m_fNextMeleeTime = 0;
#ifdef MELEE_CHARGE_ATTACKS
m_flMeleeHeavyKeyHoldStart = 0;
#endif
m_fMadFiringCounter = 0;
m_fUsingEngineeringAura = 0;
m_bDoneOrderChatter = false;
m_szInitialCommanderNetworkID[0] = '\0';
m_bWaitingForWeld = false;
m_flBeginWeldTime = 0.0f;
// ai control of firing
m_bWantsToFire = false;
m_bWantsToFire2 = false;
m_fMarineAimError = 0;
m_fStopMarineTime = 0;
m_flTimeNextScanPing = 0;
m_flPreventLaserSightTime = 0;
m_fLastStuckTime = 0;
m_flFirstStuckTime = 0;
m_fIdleChatterDelay = random->RandomInt(20, 30);
m_fRandomFacing = 0;
m_fNewRandomFacingTime = 0;
m_fCachedIdealSpeed = 300.0f;
m_flNextBreadcrumbTime = 0;
m_flNextAmmoScanTime = 0;
m_flResetAmmoIgnoreListTime = 0;
m_iPowerupType = -1;
m_flPowerupExpireTime = -1;
m_bPowerupExpires = false;
m_flLastSquadEnemyTime = 0.0f;
m_flLastSquadShotAlienTime = 0.0f;
m_flLastHurtAlienTime = 0.0f;
m_flLastGooScanTime = 0.0f;
m_fLastAmmoCheckTime = 0.0f;
m_nFastReloadsInARow = 0;
for ( int i = 0; i < ASW_MARINE_HISTORY_POSITIONS; i++ )
{
m_PositionHistory[ i ].vecPosition = vec3_origin;
m_PositionHistory[ i ].flTime = 0.0f;
}
m_nPositionHistoryTail = -1;
// rappel
m_hLine = NULL;
m_bWaitingToRappel = false;
m_bOnGround = true;
m_vecRopeAnchor = vec3_origin;
}
CASW_Marine::~CASW_Marine()
{
if (GetMarineResource())
GetMarineResource()->SetMarineEntity(NULL);
m_PlayerAnimState->Release();
delete m_MarineSpeech;
}
// create our custom senses class
CAI_Senses *CASW_Marine::CreateSenses()
{
CAI_Senses *pSenses = new CASW_Marine_AI_Senses;
pSenses->SetOuter( this );
return pSenses;
}
void CASW_Marine::SetHeightLook( float flHeightLook )
{
CAI_Senses *pSenses = GetSenses();
Assert(pSenses);
if ( pSenses )
{
CASW_Marine_AI_Senses *pMarineSenses = dynamic_cast<CASW_Marine_AI_Senses*>( GetSenses() );
if ( pMarineSenses )
pMarineSenses->SetHeightLook( flHeightLook );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Marine::SelectModel()
{
SelectModelFromProfile();
}
void CASW_Marine::UpdateOnRemove( void )
{
BaseClass::UpdateOnRemove();
StopUsing();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Marine::Spawn( void )
{
Precache();
BaseClass::Spawn();
SelectModel();
SetModel( STRING( GetModelName() ) );
SetHullType(HULL_HUMAN);
SetHullSizeNormal();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetBloodColor( BLOOD_COLOR_RED );
m_flFieldOfView = 0.02;
m_NPCState = NPC_STATE_NONE;
CapabilitiesClear();
CapabilitiesAdd( bits_CAP_MOVE_GROUND );
SetMoveType( MOVETYPE_STEP );
m_HackedGunPos = Vector( 0, 0, ASW_MARINE_GUN_OFFSET_Z );
// Marines collide/moveprobe as players.
m_nAITraceMask = MASK_PLAYERSOLID;
CapabilitiesRemove( bits_CAP_FRIENDLY_DMG_IMMUNE | bits_CAP_NO_HIT_PLAYER );
SetCollisionGroup( COLLISION_GROUP_PLAYER );
AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION );
// join the marines team
ChangeFaction( FACTION_MARINES );
SetHealth( 100 );
NPCInit();
SetInhabited(false);
m_Commander = NULL;
m_ASWOrders = ASW_ORDER_FOLLOW;
m_bWasFollowing = true;
m_flFieldOfView = ASW_HOLD_POSITION_FOV_DOT;
SetDistLook( ASW_HOLD_POSITION_SIGHT_RANGE );
m_flDistTooFar = 1024.0f;
SetAIWalkable(false);
// bias the box south a bit - this feels better for FF collisions with the tilted cam
Vector vecSurroundingMins( -16, -18, 0 );
Vector vecSurroundingMaxs( 16, 14, 70 );
CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs );
// make sure his move_x/y pose parameters are at full moving forwards, so the AI follow movement will detect some sequence motion when calculating goal speed
SetPoseParameter( "move_x", 1.0f );
SetPoseParameter( "move_y", 0.0f );
}
void CASW_Marine::NPCInit()
{
BaseClass::NPCInit();
m_LagCompensation.Init(this);
}
unsigned int CASW_Marine::PhysicsSolidMaskForEntity( void ) const
{
return MASK_PLAYERSOLID;
}
void CASW_Marine::Precache()
{
SelectModel();
BaseClass::Precache();
PrecacheSpeech();
PrecacheModel("models/swarm/shouldercone/shouldercone.mdl");
PrecacheModel("models/swarm/shouldercone/lasersight.mdl");
PrecacheModel( "cable/cable.vmt" );
PrecacheScriptSound( "ASW.MarineMeleeAttack" );
PrecacheScriptSound( "ASW.MarineMeleeAttackFP" );
PrecacheScriptSound( "ASW.MarinePowerFistAttack" );
PrecacheScriptSound( "ASW.MarinePowerFistAttackFP" );
PrecacheScriptSound( "ASW_Weapon_Flamer.FlameLoop" );
PrecacheScriptSound( "ASW_Weapon_Flamer.FlameStop" );
PrecacheScriptSound( "ASWFlashlight.FlashlightToggle" );
PrecacheScriptSound( "ASW_Flare.IgniteFlare" );
PrecacheScriptSound( "ASWScanner.Idle1" );
PrecacheScriptSound( "ASWScanner.Idle2" );
PrecacheScriptSound( "ASWScanner.Idle3" );
PrecacheScriptSound( "ASWScanner.Idle4" );
PrecacheScriptSound( "ASWScanner.Idle5" );
PrecacheScriptSound( "ASWScanner.Idle6" );
PrecacheScriptSound( "ASWScanner.Idle7" );
PrecacheScriptSound( "ASWScanner.Idle8" );
PrecacheScriptSound( "ASWScanner.Idle9" );
PrecacheScriptSound( "ASWScanner.Idle10" );
PrecacheScriptSound( "ASWScanner.Idle11" );
PrecacheScriptSound( "ASWScanner.Idle12" );
PrecacheScriptSound( "ASWScanner.Idle13" );
PrecacheScriptSound( "ASWScanner.Warning1" );
PrecacheScriptSound( "ASWScanner.Warning2" );
PrecacheScriptSound( "ASWScanner.Warning3" );
PrecacheScriptSound( "ASWScanner.Warning4" );
PrecacheScriptSound( "ASWScanner.Warning5" );
PrecacheScriptSound( "ASWScanner.Warning6" );
PrecacheScriptSound( "ASWScanner.Warning7" );
PrecacheScriptSound( "ASWScanner.Warning8" );
PrecacheScriptSound( "ASWScanner.Warning9" );
PrecacheScriptSound( "ASWScanner.Drawing" );
PrecacheScriptSound( "ASW_Weapon.Reload3" );
PrecacheScriptSound( "ASWInterface.Button3" );
PrecacheScriptSound( "Marine.DeathBeep" );
PrecacheScriptSound( "ASW.MarineImpactFP" );
PrecacheScriptSound( "ASW.MarineImpact" );
PrecacheScriptSound( "ASW.MarineImpactHeavyFP" );
PrecacheScriptSound( "ASW.MarineImpactHeavy" );
PrecacheScriptSound( "ASW.MarineMeleeAttack" );
PrecacheScriptSound( "ASW_Weapon.LowAmmoClick" );
PrecacheScriptSound( "ASW_ElectrifiedSuit.TurnOn" );
PrecacheScriptSound( "ASW_ElectrifiedSuit.Loop" );
PrecacheScriptSound( "ASW_ElectrifiedSuit.LoopFP" );
PrecacheScriptSound( "ASW_ElectrifiedSuit.OffFP" );
PrecacheScriptSound( "ASW.MarineBurnPain_NoIgnite" );
PrecacheScriptSound( "ASW_Extinguisher.OnLoop" );
PrecacheScriptSound( "ASW_Extinguisher.Stop" );
PrecacheScriptSound( "ASW_JumpJet.Activate" );
PrecacheScriptSound( "ASW_JumpJet.Loop" );
PrecacheScriptSound( "ASW_JumpJet.Impact" );
PrecacheScriptSound( "ASW_Blink.Blink" );
PrecacheScriptSound( "ASW_Blink.Teleport" );
PrecacheScriptSound( "ASW_XP.LevelUp" );
PrecacheScriptSound( "ASW_Weapon.InvalidDestination" );
PrecacheParticleSystem( "smallsplat" ); // shot
PrecacheParticleSystem( "marine_bloodsplat_light" ); // small shot
PrecacheParticleSystem( "marine_bloodsplat_heavy" ); // heavy shot
PrecacheParticleSystem( "marine_hit_blood_ff" );
PrecacheParticleSystem( "marine_hit_blood" );
PrecacheParticleSystem( "thorns_marine_buff" );
PrecacheParticleSystem( "marine_gib" );
PrecacheParticleSystem( "marine_death_ragdoll" );
PrecacheParticleSystem( "piercing_spark" );
PrecacheParticleSystem( "jj_trail_small" );
PrecacheParticleSystem( "jj_ground_pound" );
PrecacheParticleSystem( "invalid_destination" );
PrecacheParticleSystem( "Blink" );
}
void CASW_Marine::PrecacheSpeech()
{
if (m_MarineSpeech && GetMarineResource())
m_MarineSpeech->Precache();
}
void CASW_Marine::PhysicsSimulate( void )
{
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CASW_Player *player = ToASW_Player( UTIL_PlayerByIndex( i ) );
if ( player && player->GetMarine() == this)
{
InhabitedPhysicsSimulate();
return;
}
}
BaseClass::PhysicsSimulate();
CASW_Weapon *pWeapon = GetActiveASWWeapon();
if (pWeapon)
pWeapon->ItemPostFrame();
// check if offhand weapon needs postframe
CASW_Weapon *pExtra = GetASWWeapon(2);
if (pExtra && pExtra != pWeapon && pExtra->m_bShotDelayed)
{
pExtra->ItemPostFrame();
}
}
#define ASW_BREADCRUMB_INTERVAL 4.0f
// think only occurs when uninhabited (in singleplayer at least, not sure about multi)
// this is because we're not calling BaseClass::PhysicsSimulate
void CASW_Marine::Think( void )
{
if (!IsInhabited())
{
BaseClass::Think();
ASWThinkEffects();
m_fCachedIdealSpeed = MaxSpeed();
}
}
CBaseCombatWeapon* CASW_Marine::ASWAnim_GetActiveWeapon()
{
return GetActiveWeapon();
}
CASW_Marine_Profile* CASW_Marine::GetMarineProfile()
{
CASW_Marine_Resource* pMR = GetMarineResource();
if ( !pMR )
{
return NULL;
}
return pMR->GetProfile();
}
bool CASW_Marine::IsCurTaskContinuousMove()
{
const Task_t* pTask = GetTask();
// This bit of logic strikes me funny, but the case does exist. (sjb)
if( !pTask )
return true;
switch( pTask->iTask )
{
case TASK_ASW_WAIT_FOR_FOLLOW_MOVEMENT:
case TASK_ASW_MOVE_TO_GIVE_AMMO:
return true;
break;
default:
return BaseClass::IsCurTaskContinuousMove();
break;
}
}
bool CASW_Marine::ASWAnim_CanMove()
{
return true;
}
// sets which player commands this marine
void CASW_Marine::SetCommander( CASW_Player *player )
{
if ( m_Commander.Get() == player )
{
return;
}
m_Commander = player;
if ( player )
{
player->OnMarineCommanded( this );
}
}
// store ASWNetworkID of first commander
void CASW_Marine::SetInitialCommander(CASW_Player *player)
{
Q_snprintf( m_szInitialCommanderNetworkID, sizeof(m_szInitialCommanderNetworkID), "%s", player ? player->GetASWNetworkID() : "None" );
Msg( " Marine %d:%s SetInitialCommander id to %s\n", entindex(), GetEntityName(), m_szInitialCommanderNetworkID );
}
// called when a player takes direct control of this marine
void CASW_Marine::InhabitedBy(CASW_Player *player)
{
m_vecSmoothedVelocity = vec3_origin;
if (!IsInhabited())
{
m_vecSmoothedVelocity.z = GetAbsVelocity().z;
}
SetInhabited(true);
// stop the AI firing
m_bWantsToFire = m_bWantsToFire2 = false;
m_fMarineAimError = 0;
ClearForcedActionRequest();
GetSquadFormation()->Remove( this, true );
// always interrupt our current task when inhabiting a marine
TaskFail(FAIL_NO_PLAYER);
// set his schedule to holding position
SetSchedule(SCHED_ASW_HOLD_POSITION);
MDLCACHE_CRITICAL_SECTION();
NPCThink();
GetNavigator()->StopMoving( false );
SetPoseParameter( "move_x", 0.0f );
SetPoseParameter( "move_y", 0.0f );
SetMoveType( MOVETYPE_WALK );
Teleport(&GetAbsOrigin(), NULL, &m_vecSmoothedVelocity);
m_iLightLevel = 255; // reset light level - will get set correctly by first usercmd after this marine is inhabited
}
// called when a player stops direct control of this marine
void CASW_Marine::UninhabitedBy(CASW_Player *player)
{
SetInhabited(false);
if (GetActiveWeapon())
OnUpdateShotRegulator();
// clear any forward momentum
Vector vel = GetLocalVelocity();
vel.x *= 0.8f;
vel.y *= 0.8f;
SetLocalVelocity(vel);
// stop the AI firing
m_bWantsToFire = m_bWantsToFire2 = false;
m_fMarineAimError = 0;
// tell the marine to hold position here
OrdersFromPlayer(player, ASW_ORDER_HOLD_POSITION, this, true, GetLocalAngles().y);
//SetCondition( COND_ASW_NEW_ORDERS );
// make sure his move_x/y pose parameters are at full moving fowards, so the AI follow movement will detect some sequence motion when calculating goal speed
MDLCACHE_CRITICAL_SECTION();
SetPoseParameter( "move_x", 1.0f );
SetPoseParameter( "move_y", 0.0f );
// make sure he thinks almost immediately
if (GetHealth() > 0)
{
SetThink ( &CAI_BaseNPC::CallNPCThink );
SetNextThink( gpGlobals->curtime );
}
// set his schedule to holding position
SetSchedule(SCHED_ASW_HOLD_POSITION);
//Teleport(&GetAbsOrigin(), NULL, &vec3_origin);
ClearForcedActionRequest();
SetMoveType( MOVETYPE_STEP );
m_iLightLevel = 255; // reset light level - will get set correctly by first usercmd after this marine is inhabited
m_nLastThinkTick = gpGlobals->tickcount - 1;
}
void CASW_Marine::SetInhabited(bool bInhabited)
{
if (!GetMarineResource())
return;
GetMarineResource()->SetInhabited(bInhabited);
}
void CASW_Marine::SetMarineResource(CASW_Marine_Resource *pMR)
{
if (pMR != m_MarineResource)
{
m_MarineResource = pMR;
PrecacheSpeech();
}
}
void CASW_Marine::DoImpactEffect( trace_t &tr, int nDamageType )
{
// don't do impact effects, they're simulated clientside by the tracer usermessage
return;
BaseClass::DoImpactEffect(tr, nDamageType);
}
void CASW_Marine::DoMuzzleFlash()
{
return; // asw - muzzle flashes are triggered by tracer usermessages instead to save bandwidth
// Our weapon takes our muzzle flash command
CBaseCombatWeapon *pWeapon = GetActiveWeapon();
if ( pWeapon )
{
pWeapon->DoMuzzleFlash();
//NOTENOTE: We do not chain to the base here
//m_nMuzzleFlashParity = (m_nMuzzleFlashParity+1) & ((1 << EF_MUZZLEFLASH_BITS) - 1);
BaseClass::BaseClass::DoMuzzleFlash();
}
else
{
BaseClass::DoMuzzleFlash();
}
}
extern ConVar asw_sentry_friendly_fire_scale;
int CASW_Marine::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
// make marines immune to crush damage
if ( info.GetDamageType() & DMG_CRUSH || asw_god.GetBool() )
{
return 0;
}
CTakeDamageInfo newInfo(info);
if ( asw_debug_marine_damage.GetBool() )
Msg( "Marine taking premodified damage of %f\n", newInfo.GetDamage() );
// scale sentry gun damage
if ( newInfo.GetAttacker() && IsSentryClass( newInfo.GetAttacker()->Classify() ) )
{
if ( asw_sentry_friendly_fire_scale.GetFloat() <= 0 )
return 0;
newInfo.ScaleDamage( asw_sentry_friendly_fire_scale.GetFloat() );
}
// AI marines take much less damage from explosive barrels since they're too dumb to not get near them
if ( !IsInhabited() && info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_EXPLOSIVE_BARREL )
{
newInfo.ScaleDamage( 0.1f );
if ( asw_debug_marine_damage.GetBool() )
Msg( " Scaled AI taking damage from explosive barrel down to %f\n", newInfo.GetDamage() );
}
// don't allow FF from melee attacks
bool bFriendlyFire = newInfo.GetAttacker() && newInfo.GetAttacker()->Classify() == CLASS_ASW_MARINE;
if ( bFriendlyFire )
{
CASW_Marine *pOtherMarine = dynamic_cast<CASW_Marine*>(newInfo.GetAttacker());
if ( pOtherMarine && pOtherMarine->GetMarineResource() )
{
pOtherMarine->GetMarineResource()->m_iAliensKilledSinceLastFriendlyFireIncident = 0;
}
if ( pOtherMarine && !pOtherMarine->IsInhabited() && !(newInfo.GetDamageType() & DMG_DIRECT) )
{
// don't allow any damage if it's an AI firing: NOTE: This isn't 100% accurate, since the AI could've fired the shot, then a player switched into the marine while the projectile was in the air
if (asw_debug_marine_damage.GetBool())
Msg(" but all ignored, since it's from an AI\n");
return 0;
}
else
{
if (newInfo.GetDamageType() & DMG_CLUB)
{
if (asw_debug_marine_damage.GetBool())
Msg(" but all ignored, since it's FF meleee dmg\n");
return 0;
}
// drop the damage down by our absorption buffer
bool bFlamerDot = !!(newInfo.GetDamageType() & ( DMG_BURN | DMG_DIRECT ) );
if ( newInfo.GetDamage() > 0 && newInfo.GetAttacker() != this && !bFlamerDot )
{
bool bHardcoreMode = ASWGameRules() && ASWGameRules()->IsHardcoreMode();
if ( !bHardcoreMode && asw_marine_ff_absorption.GetInt() != 0 )
{
float flNewDamage = info.GetDamage() * GetFFAbsorptionScale();
newInfo.SetDamage(flNewDamage);
if ( asw_debug_marine_damage.GetBool() )
{
Msg(" FF damage (%f) reduced to %f from FF absorption (%f)\n", newInfo.GetDamage(), flNewDamage, GetFFAbsorptionScale());
}
}
GetMarineSpeech()->QueueChatter( CHATTER_FRIENDLY_FIRE, gpGlobals->curtime + 0.4f, gpGlobals->curtime + 1.0f );
m_fLastFriendlyFireTime = gpGlobals->curtime;
}
}
}
// don't kill the marine in one hit unless he's on 1 health
bool bKillProtection = false;
if (asw_marine_death_protection.GetBool() && !(ASWGameRules() && ASWGameRules()->IsHardcoreMode())) // no 1 hit protection in hardcore mode
{
if (newInfo.GetDamageType() != DMG_CRUSH && newInfo.GetDamageType() != DMG_FALL
&& newInfo.GetDamageType() != DMG_INFEST && GetHealth() > 1)
{
if (newInfo.GetDamage() > GetHealth())
bKillProtection = true;
}
}
if ( newInfo.GetInflictor() && newInfo.GetInflictor()->Classify() == CLASS_ASW_DOOR && ASWGameRules()->GetSkillLevel() < 3 )
{
// Don't crush the player on easier difficulties
Vector vDir = newInfo.GetDamageForce();
VectorNormalize( vDir );
newInfo.SetDamageForce( vDir * 20.0f );
Vector vNewPos = newInfo.GetDamagePosition() + newInfo.GetDamageForce();
vNewPos.z = GetAbsOrigin().z + 15.0f;
Vector vNewVel = vDir * 5.0f;
Teleport( &vNewPos, NULL, &vNewVel );
newInfo.SetDamage( 5.0f );
}
// reduce damage thanks to leadership
// see if we pass the chance
float fChance = MarineSkills()->GetHighestSkillValueNearby(GetAbsOrigin(),
asw_leadership_radius.GetFloat(),
ASW_MARINE_SKILL_LEADERSHIP, ASW_MARINE_SUBSKILL_LEADERSHIP_DAMAGE_RESIST );
static int iLeadershipResCount = 0;
if (random->RandomFloat() < fChance)
{
float fNewDamage = newInfo.GetDamage() * 0.5f;
if (fNewDamage <= 0)
return 0;
newInfo.SetDamage(fNewDamage);
iLeadershipResCount++;
if (asw_debug_marine_damage.GetBool())
{
Msg(" Damage reduced by nearby leadership to %f (leadership resistance applied %d times so far)\n", fNewDamage, iLeadershipResCount);
}
}
if ( newInfo.GetAttacker() )
{
// store FF damage dealt based on adjusted damage
if (newInfo.GetAttacker()->Classify() == CLASS_ASW_MARINE)
{
CASW_Marine *pOtherMarine = dynamic_cast<CASW_Marine*>(newInfo.GetAttacker());
if ( pOtherMarine && pOtherMarine->GetMarineResource() )
{
CASW_Marine_Resource *pMR = pOtherMarine->GetMarineResource();
if ( pMR )
{
pMR->m_fFriendlyFireDamageDealt += newInfo.GetDamage();
pMR->m_TimelineFriendlyFire.RecordValue( newInfo.GetDamage() );
}
}
if ( newInfo.GetAttacker() != this )
{
ASWFailAdvice()->OnFriendlyFire( newInfo.GetDamage() );
}
}
ApplyPassiveArmorEffects( newInfo );
// reduce damage and shock alien if we have electrified armour on
if ( newInfo.GetDamageType() & DMG_SLASH )
{
if ( IsElectrifiedArmorActive() )
{
CASW_Alien *pAlien = dynamic_cast<CASW_Alien*>( newInfo.GetAttacker() );
if ( pAlien )
{
const float flDamageReturn = 20.0f;
Vector vecToTarget = pAlien->WorldSpaceCenter() - WorldSpaceCenter();
vecToTarget.z = 0;
VectorNormalize( vecToTarget );
Vector vecForce = vecToTarget * 20 + Vector( 0, 0, 1 ) * 10;
CTakeDamageInfo returninfo( this, this,
vecForce, newInfo.GetAttacker()->WorldSpaceCenter(), flDamageReturn, DMG_SHOCK | DMG_BLAST );
pAlien->TakeDamage( returninfo );
//pAlien->ElectroShockBig( vecForce, WorldSpaceCenter() );
CRecipientFilter filter;
filter.AddAllPlayers();
UserMessageBegin( filter, "ASWEnemyZappedByThorns" );
WRITE_SHORT( entindex() );
WRITE_SHORT( pAlien->entindex() );
MessageEnd();
newInfo.ScaleDamage( 0.25f );
if (asw_debug_marine_damage.GetBool())
{
Msg(" Damage reduced by electrified armor to %f\n", newInfo.GetDamage() );
}
}
}
else // if ( newInfo.GetAttacker() )
{
if ( !m_bHasBeenMobAttacked )
{
if ( m_fLastMobDamageTime > 0.0f && m_fLastMobDamageTime + 1.5f < gpGlobals->curtime )
{
// It's been a while, reset
m_pRecentAttackers[ 0 ] = newInfo.GetAttacker()->entindex();
for ( int nAttackers = 1; nAttackers < ASW_MOB_VICTIM_SIZE; ++nAttackers )
{
m_pRecentAttackers[ nAttackers ] = 0;
}
}
else
{
int nAttackers;
for ( nAttackers = 0; nAttackers < ASW_MOB_VICTIM_SIZE; ++nAttackers )
{
if ( m_pRecentAttackers[ nAttackers ] == newInfo.GetAttacker()->entindex() )
{
break;
}
if ( m_pRecentAttackers[ nAttackers ] == 0 )
{
m_pRecentAttackers[ nAttackers ] = newInfo.GetAttacker()->entindex();
break;
}
}
if ( nAttackers >= ASW_MOB_VICTIM_SIZE )
{
ASWFailAdvice()->OnMarineMobAttacked();
m_bHasBeenMobAttacked = true;
m_fLastMobDamageTime = 0.0f;
}
}
m_fLastMobDamageTime = gpGlobals->curtime;
}
}
}
}
int iPreDamageHealth = GetHealth();
CASW_GameStats.Event_MarineTookDamage( this, newInfo );
int result = BaseClass::OnTakeDamage_Alive(newInfo);
int iDamageTaken = iPreDamageHealth - GetHealth();
if (asw_debug_marine_damage.GetBool() && result > 0)
Msg(" Marine took final damage: %f of type %d\n", newInfo.GetDamage(), newInfo.GetDamageType());
// notify weapons of damage
if (GetASWWeapon(0))
GetASWWeapon(0)->OnMarineDamage(newInfo);
if (GetASWWeapon(1))
GetASWWeapon(1)->OnMarineDamage(newInfo);
if (GetASWWeapon(2))
GetASWWeapon(2)->OnMarineDamage(newInfo);
if ( ASWDirector() )
ASWDirector()->MarineTookDamage( this, newInfo, bFriendlyFire );
if (m_iHealth <= 0)
{
CASW_Door *pDoor = dynamic_cast<CASW_Door*>(newInfo.GetInflictor());
if (!pDoor) // can't survive damage from a falling door, even with kill protection or die hard
{
if (bKillProtection)
m_iHealth = 1;
}
}
if ( newInfo.GetDamage() > 0 )
{
if ( m_hCurrentHack.Get() )
{
ASWFailAdvice()->OnHackerHurt( newInfo.GetDamage() );
}
bool bShowFFIcon = bFriendlyFire;
if ( newInfo.GetAttacker() )
{
IGameEvent * event = gameeventmanager->CreateEvent( "marine_hurt" );
if ( event )
{
CBasePlayer *pPlayer = GetCommander();
event->SetInt( "userid", ( pPlayer ? pPlayer->GetUserID() : 0 ) );
event->SetInt( "entindex", entindex() );
event->SetFloat( "health", static_cast< float >( MAX( 0.0f, GetHealth() ) ) / GetMaxHealth() );
CBasePlayer *pAttackPlayer = ToBasePlayer( newInfo.GetAttacker() );
if ( !pAttackPlayer )
{
CASW_Marine *pAttackMarine = dynamic_cast< CASW_Marine* >( newInfo.GetAttacker() );
if ( pAttackMarine )
{
pAttackPlayer = pAttackMarine->GetCommander();
if ( pAttackMarine == this )
{
bShowFFIcon = false;
}
}
}
if ( pAttackPlayer )
{
event->SetInt( "attacker", pAttackPlayer->GetUserID() ); // hurt by other player
event->SetInt( "attackerindex", newInfo.GetAttacker()->entindex() ); // hurt by entity
}
else
{
event->SetInt( "attacker", 0 ); // hurt by entity
event->SetInt( "attackerindex", newInfo.GetAttacker()->entindex() ); // hurt by entity
}
gameeventmanager->FireEvent( event );
}
}
if ( IsInhabited() )
{
//Msg("took %f damage\n", info.GetDamage());
CASW_Player *player = GetCommander();
if (player)
{
if ( asw_screenflash.GetInt() > 0 )
{
color32 flash_col = {128,0,0,192};
flash_col.a = asw_screenflash.GetInt();
UTIL_ScreenFade( player, flash_col, 1.0f, 0.1f, FFADE_IN );
}
if ( asw_damage_indicator.GetBool() )
{
// Tell the player's client that he's been hurt.
CSingleUserRecipientFilter user( player );
UserMessageBegin( user, "Damage" );
WRITE_SHORT( clamp( (int)newInfo.GetDamage(), 0, 32000 ) );
WRITE_LONG( newInfo.GetDamageType() );
// Tell the client whether they should show it in the indicator
if ( !(newInfo.GetDamageType() & (DMG_DROWN | DMG_FALL | DMG_BURN | DMG_INFEST | DMG_RADIATION) ) )
{
WRITE_BOOL( true );
WRITE_VEC3COORD( newInfo.GetDamagePosition() );
WRITE_BOOL( bShowFFIcon );
}
else
{
WRITE_BOOL( false );
}
MessageEnd();
}
if (info.GetDamageType() & DMG_BLURPOISON)
{
float duration = asw_buzzer_poison_duration.GetFloat();
// affect duration by mission difficulty
if (ASWGameRules())
{
duration = duration + (duration * (ASWGameRules()->GetMissionDifficulty() / 10.0f));
}
if (duration > 0)
UTIL_ASW_PoisonBlur( player, duration );
}
bool bBurnDamage = ( info.GetDamageType() & ( DMG_BURN | DMG_DIRECT ) ) != 0;
bool bElectrifiedArmorAbsorbed = ( ( info.GetDamageType() & DMG_SLASH ) && IsElectrifiedArmorActive() );
// play a meaty hit sound when attacked by aliens or FF
if ( ( !bBurnDamage && !bElectrifiedArmorAbsorbed ) ) //|| bFriendlyFire )
{
//UTIL_ASW_ScreenShake( GetAbsOrigin(), 4.0f, 30.0f, 0.25f, 128.0f, SHAKE_START );
if ( !info.GetInflictor() )
{
ASW_TransmitShakeEvent( player, 2.0f, 40.0f, 0.2f, SHAKE_START );
}
else
{
ASW_TransmitShakeEvent( player, 20.0f, 1.0f, 0.3f, SHAKE_START, (info.GetInflictor()->GetAbsOrigin() - GetAbsOrigin()).Normalized() );
}
// this is the sound that's played for the local player only
CSingleUserRecipientFilter localfilter( player );
localfilter.MakeReliable();
// this is the sound that's played for all other players, but the local player
CPASAttenuationFilter othersfilter( this, "ASW.MarineImpact" );
othersfilter.RemoveRecipient( player );
// if they take more than 10% damage in one hit or their health is below 20%, play a bigger sound
if ( float(iDamageTaken) / float(GetMaxHealth()) > 0.1 || float(m_iHealth) / float(GetMaxHealth()) < 0.25 )
{
CBaseEntity::EmitSound( localfilter, entindex(), "ASW.MarineImpactHeavyFP" );
CBaseEntity::EmitSound( othersfilter, entindex(), "ASW.MarineImpactHeavy" );
}
else
{
CBaseEntity::EmitSound( localfilter, entindex(), "ASW.MarineImpactFP" );
CBaseEntity::EmitSound( othersfilter, entindex(), "ASW.MarineImpact" );
}
}
}
}
else
{
// AI's being hurt, check he has a weapon (and maybe switch if it's not selected)
CheckAutoWeaponSwitch();
// if they take more than 10% damage in one hit or their health is below 20%, play a bigger sound
if ( float(iDamageTaken) / float(GetMaxHealth()) > 0.1 || float(m_iHealth) / float(GetMaxHealth()) < 0.25 )
{
EmitSound( "ASW.MarineImpactHeavy" );
}
else
{
EmitSound( "ASW.MarineImpact" );
}
}
}
if (result > 0)
{
// update our stats
CASW_Marine_Resource *pMR = GetMarineResource();
if ( pMR )
{
if (newInfo.GetDamage() > 0)
{
pMR->m_bHurt = true;
if ( asw_debug_marine_damage.GetBool() )
{
Msg( "Total damage taken is %f and taking damage %f ", pMR->m_fDamageTaken, newInfo.GetDamage() );
}
pMR->m_fDamageTaken += iDamageTaken;
pMR->m_TimelineHealth.RecordValue( MAX( 0, GetHealth() ) );
if ( asw_debug_marine_damage.GetBool() )
{
Msg( "so new total is %f (%d/%d) %d lost\n", pMR->m_fDamageTaken, GetHealth(), GetMaxHealth(), GetMaxHealth() - GetHealth() );
}
}
if (info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE)
m_fFriendlyFireDamage += iDamageTaken;
// check for flagging as wounded
float fWoundDamageThreshold = 0.5f;
if (ASWGameRules())
{
if (ASWGameRules()->GetSkillLevel() == 1)
fWoundDamageThreshold = 0.4f;
if (ASWGameRules()->GetSkillLevel() >= 3)
fWoundDamageThreshold = 0.6f;
}
float fWoundPoint = GetMaxHealth() * fWoundDamageThreshold;
if (m_iHealth < fWoundPoint)
{
pMR->m_bTakenWoundDamage = true;
}
}
if (asw_debug_marine_damage.GetBool())
Msg("marine took damage %f (total taken %f, ff taken %f)\n",
newInfo.GetDamage(), pMR->m_fDamageTaken, m_fFriendlyFireDamage);
// if we take fire damage, catch on fire
float fPainInterval = 0.7f;
if ( info.GetDamageType() & DMG_BURN )
{
ASW_Ignite( 1.0f, 0, newInfo.GetAttacker(), info.GetWeapon() );
}
// short stumbles on damage
if ( !(newInfo.GetDamageType() & (DMG_BURN | DMG_DIRECT | DMG_RADIATION) ) && asw_marine_stumble_on_damage.GetBool() )
{
Stumble( newInfo.GetAttacker(), newInfo.GetDamageForce(), true );
}
// flinch
if (gpGlobals->curtime > m_fNextPainSoundTime)
{
DoAnimationEvent( PLAYERANIMEVENT_FLINCH );
if (info.GetDamage() > 35.0f)
GetMarineSpeech()->PersonalChatter(CHATTER_PAIN_LARGE);
else
GetMarineSpeech()->PersonalChatter(CHATTER_PAIN_SMALL);
m_fNextPainSoundTime = gpGlobals->curtime + fPainInterval;
}
}
return result;
}
// you can assume there is an attacker when this function is called.
void CASW_Marine::ApplyPassiveArmorEffects( CTakeDamageInfo &dmgInfo ) RESTRICT
{
Assert( dmgInfo.GetAttacker() );
if ( dmgInfo.GetDamageType() & (DMG_CRUSH|DMG_FALL|DMG_DROWN|DMG_PARALYZE|DMG_DROWNRECOVER|DMG_DISSOLVE) )
return;
// do I have ordinary (non zappy) armor?
CASW_Weapon_Normal_Armor *pArmor = NULL;
for (int i=0; i<ASW_MAX_MARINE_WEAPONS; ++i)
{
CBaseCombatWeapon *pWep = m_hMyWeapons[i].Get();
if ( pWep && pWep->Classify() == CLASS_ASW_NORMAL_ARMOR )
{
pArmor = assert_cast<CASW_Weapon_Normal_Armor *>(pWep);
break;
}
}
if ( pArmor )
{
dmgInfo.ScaleDamage( pArmor->GetDamageScaleFactor() );
}
}
CASW_Weapon* CASW_Marine::GetActiveASWWeapon( void ) const
{
return dynamic_cast<CASW_Weapon*>(GetActiveWeapon());
}
bool CASW_Marine::IsWounded() const
{
return ( ( float(GetHealth()) / float(GetMaxHealth()) ) < 0.6f );
}
// asw todo: fix this!
bool CASW_Marine::IsAlienNear()
{
/*
CBaseEntity* pEntity = NULL;
while ((pEntity = gEntList.FindEntityByClassname( pEntity, "asw_drone" )) != NULL)
{
CASW_Drone* drone = dynamic_cast<CASW_Drone*>(pEntity);
if ( drone && drone->GetAbsOrigin().DistTo(GetAbsOrigin()) < 1220.0f)
return true;
}*/
int count = AimTarget_ListCount();
if ( count )
{
CBaseEntity **pList = (CBaseEntity **)stackalloc( sizeof(CBaseEntity *) * count );
AimTarget_ListCopy( pList, count );
for ( int i = 0; i < count; i++ )
{
CBaseEntity *pEntity = pList[i];
// Don't shoot yourself
if ( pEntity == this )
continue;
if (!pEntity->IsAlive() || !pEntity->edict() )
continue;
// is it something we dislike?
Disposition_t rel = CBaseCombatCharacter::IRelationType( pEntity );
if ( rel != D_HT && rel != D_FR )
continue;
// if it's nearby then let's do a reload shout
if (pEntity->GetAbsOrigin().DistToSqr(GetAbsOrigin()) < 1440000)
return true;
}
}
return false;
}
void CASW_Marine::HurtJunkItem(CBaseEntity *pEnt, const CTakeDamageInfo &info)
{
// increase our shot timer, so shooting barrels/crates etc doesn't reduce our accuracy
if (!(info.GetDamageType() & DMG_DIRECT)) // ignore flame DoT
{
m_fLastShotJunkTime = gpGlobals->curtime;
}
}
void CASW_Marine::HurtAlien(CBaseEntity *pAlien, const CTakeDamageInfo &info)
{
bool bMeleeDamage = ( info.GetDamageType() & DMG_CLUB ) != 0;
if ( !bMeleeDamage )
{
if ( GetMarineResource() )
{
GetMarineResource()->m_bDealtNonMeleeDamage = true;
}
}
m_flLastHurtAlienTime = gpGlobals->curtime;
CASW_Weapon *pWeapon = GetActiveASWWeapon();
if ( pWeapon && pAlien )
{
IASW_Spawnable_NPC *pNPC = dynamic_cast<IASW_Spawnable_NPC*>(pAlien);
if ( pNPC && !(info.GetDamageType() & (DMG_BURN | DMG_BLAST | DMG_SHOCK | DMG_DIRECT) ) )
{
// TODO: Make sure flamer and stun grenades still work
/*
float flIgniteChance = 0;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flIgniteChance, mod_ignite );
if ( RandomFloat() < flIgniteChance )
{
pNPC->ASW_Ignite(5.0f, 0, info.GetAttacker());
}
float flElectroChance = 0;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flElectroChance, mod_electro_stun );
if ( RandomFloat() < flElectroChance )
{
pNPC->ElectroStun( 5.0f );
}
*/
}
}
// don't do any chatter effects if this alien is being hurt by a burn DoT
CASW_Burning* pBurning = dynamic_cast<CASW_Burning*>(info.GetInflictor());
if (pBurning)
return;
if (!(info.GetDamageType() & DMG_DIRECT)) // ignore flame DoT
{
m_fLastShotAlienTime = gpGlobals->curtime;
}
// don't do any hurt alien affects against the little grubs or eggs
if (pAlien &&
(pAlien->Classify() == CLASS_ASW_GRUB ||
pAlien->Classify()== CLASS_ASW_ALIEN_GOO ||
pAlien->Classify() == CLASS_ASW_EGG ))
return;
bool bMadFiring = false;
if (gpGlobals->curtime > s_fNextMadFiringChatter)
{
if (!(info.GetDamageType() & (DMG_BURN | DMG_BLAST | DMG_SHOCK)))
{
if (pWeapon)
{
//Msg("Alien hurt by a %s\n", pWeapon->GetClassname());
m_fMadFiringCounter += pWeapon->GetMadFiringBias() * (pWeapon->GetFireRate() / pWeapon->GetNumPellets()); // add more per shot for slower weapons
if (m_fMadFiringCounter >= asw_mad_firing_break.GetInt())
{
bMadFiring = true;
m_fMadFiringCounter = 0;
//s_fNextMadFiringChatter = gpGlobals->curtime + // need this?
}
}
}
}
if (bMadFiring)
{
// check for autogun kill convos
bool bSkipChatter = false;
bool bAutogun = false;
if (GetMarineProfile())
{
if ( GetMarineProfile()->m_VoiceType == ASW_VOICE_WILDCAT || GetMarineProfile()->m_VoiceType == ASW_VOICE_WOLFE ) // wildcat or wolfe
{
// check we're using an autogun
bAutogun = (dynamic_cast<CASW_Weapon_Autogun*>(GetActiveASWWeapon()) != NULL);
if (bAutogun)
{
if (CASW_MarineSpeech::StartConversation(CONV_AUTOGUN, this))
bSkipChatter = true;
if (GetMarineResource())
GetMarineResource()->m_iMadFiringAutogun++;
}
}
}
if (!bSkipChatter)
{
GetMarineSpeech()->Chatter(CHATTER_MAD_FIRING);
if (GetMarineResource() && !bAutogun)
GetMarineResource()->m_iMadFiring++;
}
}
else
GetMarineSpeech()->Chatter(CHATTER_FIRING_AT_ALIEN);
if (info.GetDamageType() & DMG_CLUB && GetMarineResource())
{
GetMarineResource()->m_iAliensKicked++;
if ( IsInhabited() && GetCommander() && GetMarineResource()->m_iAliensKicked > asw_medal_melee_hits.GetInt() )
{
GetCommander()->AwardAchievement( ACHIEVEMENT_ASW_MELEE_KILLS );
}
}
}
/*
// don't send angles when this marine is inhabited
void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
{
if (IsInhabited())
return NULL;
return BaseClass::SendProxy_Angles(pProp, pStruct, pData, pOut, iElement, objectID);
}
*/
void CASW_Marine::InhabitedPhysicsSimulate()
{
BaseClass::InhabitedPhysicsSimulate();
// check for deleting this
if ( m_pfnThink == (BASEPTR)&CBaseEntity::SUB_Remove )
{
if (GetNextThink() > 0 && GetNextThink() < gpGlobals->curtime)
{
SUB_Remove();
}
}
}
// post think only happens when inhabited
void CASW_Marine::PostThink()
{
//Msg("[S] Marine Postthink, sl=%d amount=%d tick=%f\n",
//m_bSlowHeal, m_iSlowHealAmount, m_fNextSlowHealTick);
BaseClass::PostThink();
DispatchAnimEvents( this );
// is this correct here? should happen during/before think, not after?
m_PlayerAnimState->Update( GetAbsAngles()[YAW], GetAbsAngles()[PITCH] );
if (IsInhabited())
{
StudioFrameAdvance();
}
ASWThinkEffects();
if ( NeedToUpdateSquad() )
{
GetSquadFormation()->UpdateFollowPositions();
}
// check for pushing out phys props jammed inside us
if ( VPhysicsGetObject() )
{
#ifdef ASW_MARINE_ALWAYS_VPHYSICS
VPhysicsGetObject()->UpdateShadow( GetAbsOrigin(), vec3_angle, false, gpGlobals->frametime );
#else
//if ( !VectorCompare( oldOrigin, GetAbsOrigin() ) )
if (gpGlobals->curtime <= m_fLastStuckTime + 1.0f) // if we were stuck in the last second, make sure our shadow is on
{
VPhysicsGetObject()->SetVelocity( &vec3_origin, NULL );
VPhysicsGetObject()->SetVelocityInstantaneous( &vec3_origin, &vec3_origin );
//IPhysicsShadowController *pController = VPhysicsGetObject()->GetShadowController();
//float teleport_dist = pController ? VPhysicsGetObject()->GetTeleportDistance() : 0;
//if (pController)
//VPhysicsGetObject()->SetTeleportDistance(0.1f); // make sure the shadow teleports to its new position
// move the marine's vphys shadow to our current position
VPhysicsGetObject()->SetPosition( GetAbsOrigin(), vec3_angle, true );
//VPhysicsGetObject()->UpdateShadow( GetAbsOrigin(), vec3_angle, false, gpGlobals->frametime );
VPhysicsGetObject()->EnableCollisions(true);
// clear its velocity, so the marine doesn't wake up phys objects - doesn't work :/
VPhysicsGetObject()->SetVelocityInstantaneous( &vec3_origin, &vec3_origin );
//if (pController)
//VPhysicsGetObject()->SetTeleportDistance(teleport_dist);
}
else // no vphysics shadow enabled if we're not stuck
{
VPhysicsGetObject()->EnableCollisions(false);
}
#endif
}
if ( m_bCheckContacts )
{
CheckPhysicsContacts();
}
}
void CASW_Marine::ASWThinkEffects()
{
float fDeltaTime = gpGlobals->curtime - m_fLastASWThink;
if ( gpGlobals->curtime > m_flNextPositionHistoryTime )
{
CASW_Marine_Resource *pMR = GetMarineResource();
if ( pMR )
{
pMR->m_TimelinePosX.RecordValue( GetAbsOrigin().x );
pMR->m_TimelinePosY.RecordValue( GetAbsOrigin().y );
}
AddPositionHistory();
m_flNextPositionHistoryTime = gpGlobals->curtime + asw_movement_direction_interval.GetFloat();
}
if ( gpGlobals->maxClients > 1 )
{
m_LagCompensation.StorePositionHistory();
}
UpdatePowerupDuration();
UpdateCombatStatus();
// general timer for healing/infestation
if ( (m_bSlowHeal || IsInfested()) && GetHealth() > 0 )
{
while (gpGlobals->curtime >= m_fNextSlowHealTick)
{
if ( m_fNextSlowHealTick == 0 )
{
m_fNextSlowHealTick = gpGlobals->curtime;
}
m_fNextSlowHealTick += ( ASW_MARINE_HEALTICK_RATE * ( 1.0f / m_flHealRateScale ) );
// check slow heal isn't over out cap
if (m_bSlowHeal)
{
//m_iSlowHealAmount = GetMaxHealth() - GetHealth();
if ( GetHealth() >= GetMaxHealth() && !IsInfested() ) // clear all slow healing once we're at max health
{
ASWFailAdvice()->OnMarineOverhealed( m_iSlowHealAmount );
m_iSlowHealAmount = 0; // (and not infested - infestation means we'll be constantly dropping health, so we can keep the heal around)
}
int amount = MIN(4, m_iSlowHealAmount);
if (GetHealth() + amount > GetMaxHealth())
amount = GetMaxHealth() - GetHealth();
if (asw_debug_marine_damage.GetBool())
Msg("SH %f: marine applied slow heal of %d\n", gpGlobals->curtime, amount);
// change the health
SetHealth(GetHealth() + amount);
if ( GetMarineResource() )
{
GetMarineResource()->m_TimelineHealth.RecordValue( GetHealth() );
}
m_iSlowHealAmount -= amount;
if (m_iSlowHealAmount <= 0)
{
m_bSlowHeal = false;
}
}
if ( IsInfested() )
{
m_iInfestCycle++;
if ( m_iInfestCycle >= 3 ) // only do the infest damage once per second
{
float DamagePerTick = ASWGameRules()->TotalInfestDamage() / 20.0f; // this is also damage per second (based on standard 20 second infest time, slow heal interval of 0.33f and us only applying this every 1 in 3)
if ( asw_debug_marine_damage.GetBool() )
Msg("SH %f: Infest DamagePerTick %f (infest time left = %f)\n", gpGlobals->curtime, DamagePerTick, m_fInfestedTime);
CTakeDamageInfo info(NULL, NULL, Vector(0,0,0), GetAbsOrigin(), DamagePerTick, DMG_INFEST);
TakeDamage( info );
if ( IsElectrifiedArmorActive() )
{
// Do some serious hurt to that bug each time he bites!
CureInfestation( NULL, 0.4f );
}
m_iInfestCycle = 0;
m_fInfestedTime -= 1.0f;
if ( m_fInfestedTime <= 0 )
{
m_fInfestedTime = 0;
if ( !IsInhabited() )
{
DoEmote( 2 );
}
GetMarineResource()->SetInfested(false);
if (!m_bPlayedCureScream) // play some effects of the parasite dying
{
m_bPlayedCureScream = true;
CSoundParameters params;
if ( CBaseEntity::GetParametersForSound( "ASW_Parasite.Death", params, NULL ) )
{
Vector vecOrigin = WorldSpaceCenter();
CPASAttenuationFilter filter( vecOrigin, params.soundlevel );
EmitSound_t ep;
ep.m_nChannel = CHAN_AUTO;
ep.m_pSoundName = params.soundname;
ep.m_flVolume = params.volume;
ep.m_SoundLevel = params.soundlevel;
ep.m_nPitch = params.pitch;
ep.m_pOrigin = &vecOrigin;
CBaseEntity::EmitSound( filter, 0 /*sound.entityIndex*/, ep );
}
UTIL_ASW_DroneBleed( WorldSpaceCenter(), Vector(0, 0, 1), 4 );
}
if (m_hInfestationCurer.Get() && m_hInfestationCurer->GetMarineResource())
{
if ( m_hInfestationCurer->GetCommander() && m_hInfestationCurer->IsInhabited() )
{
m_hInfestationCurer->GetCommander()->AwardAchievement( ACHIEVEMENT_ASW_INFESTATION_CURING );
}
m_hInfestationCurer->GetMarineResource()->m_iCuredInfestation++;
m_hInfestationCurer = NULL;
}
IGameEvent * event = gameeventmanager->CreateEvent( "marine_infested_cured" );
if ( event )
{
CASW_Player *pPlayer = NULL;
if ( m_hInfestationCurer.Get() )
{
pPlayer = m_hInfestationCurer->GetCommander();
}
event->SetInt( "entindex", entindex() );
event->SetInt( "userid", ( pPlayer ? pPlayer->GetUserID() : 0 ) );
gameeventmanager->FireEvent( event );
}
}
}
}
}
}
// check for FFGuard time running out
if (m_fFFGuardTime != 0 && gpGlobals->curtime > m_fFFGuardTime)
{
// power up weapons again (play a sound?)
}
if (m_hUsingEntity.Get())
{
IASW_Server_Usable_Entity* pUsable = dynamic_cast<IASW_Server_Usable_Entity*>(m_hUsingEntity.Get());
if (pUsable)
{
if (!pUsable->IsUsable(this))
{
StopUsing();
}
else
{
pUsable->MarineUsing(this, fDeltaTime);
}
}
}
if (m_vecFacingPointFromServer.Get()!=vec3_origin && gpGlobals->curtime > m_fStopFacingPointTime)
{
m_vecFacingPointFromServer = vec3_origin;
}
// update emotes
TickEmotes(fDeltaTime);
// update this marine's resource
if (GetMarineResource() && GetActiveASWWeapon())
{
//Msg("Updating firing for %s\n", GetMarineResource()->GetProfile()->m_ShortName);
if (GetActiveASWWeapon()->IsFiring())
{
if (GetActiveASWWeapon()->IsRapidFire())
GetMarineResource()->SetFiring(1);
else
GetMarineResource()->SetFiring(2);
}
else
{
GetMarineResource()->SetFiring(0);
}
}
else if (GetMarineResource())
{
GetMarineResource()->SetFiring(0);
}
// unfreeze the marine sometime after he's started getting up
if ((GetFlags() & FL_FROZEN) && m_fUnfreezeTime != 0)
{
if (gpGlobals->curtime >= m_fUnfreezeTime)
{
m_fUnfreezeTime = 0;
RemoveFlag(FL_FROZEN);
}
}
// check for kicking
if (m_fKickTime !=0 && gpGlobals->curtime >= m_fKickTime)
{
m_fKickTime = 0;
//DoKickEffect();
}
if ( gpGlobals->curtime > m_fLastFriendlyFireTime + 2.0f )
{
m_fFriendlyFireAbsorptionTime = MAX( 0, m_fFriendlyFireAbsorptionTime - fDeltaTime * asw_marine_ff_absorption_decay_rate.GetFloat() );
}
else
{
m_fFriendlyFireAbsorptionTime = MIN( 1.0f, m_fFriendlyFireAbsorptionTime + fDeltaTime * asw_marine_ff_absorption_build_rate.GetFloat() );
}
if ( gpGlobals->curtime > m_fLastAmmoCheckTime + 2.0f )
{
CheckAndRequestAmmo();
}
if (m_fMadFiringCounter > 0 && GetActiveASWWeapon() && !GetActiveASWWeapon()->IsReloading()) // don't tick down the mad firing counter while you're reloading
{
m_fMadFiringCounter -= fDeltaTime * asw_mad_firing_decay.GetFloat();
if (asw_debug_marine_chatter.GetBool())
{
float fFraction = m_fMadFiringCounter / asw_mad_firing_break.GetFloat();
//char buffer[256];
//Q_snprintf(buffer, sizeof(buffer), );
engine->Con_NPrintf( 1, "MadFiringCounter: (%f) %f/%f", fFraction,
m_fMadFiringCounter, asw_mad_firing_break.GetFloat() );
}
}
//if (GetAbsVelocity() != vec3_origin || (GetMarineResource() && GetMarineResource()->IsFiring())
bool bMoving = IsInhabited() ? GetAbsVelocity() != vec3_origin : GetSmoothedVelocity().Length()>1.0f;
if (bMoving || (GetMarineResource() && GetMarineResource()->IsFiring())
|| m_hUsingEntity.Get())
{
m_fLastStillTime = gpGlobals->curtime;
}
else
{
if (gpGlobals->curtime > m_fLastStillTime + m_fIdleChatterDelay && m_fLastStillTime != 0)
{
if (asw_debug_marine_chatter.GetBool())
Msg("%s trying to idle chatter (cur=%f snextidle=%f idlechatdelay=%f(%f)\n",
GetMarineProfile()->m_ShortName,
gpGlobals->curtime, s_fNextIdleChatterTime, m_fIdleChatterDelay, m_fLastStillTime + m_fIdleChatterDelay
);
if (gpGlobals->curtime > s_fNextIdleChatterTime && GetMarineSpeech()->AllowCalmConversations(-1)) // check no-one else idle chatted recently
{
if (asw_debug_marine_chatter.GetBool())
Msg(" and there's a free gap, woot!\n");
bool bDidIdle = false;
// do a special idle?
if (random->RandomFloat() < asw_marine_special_idle_chatter_chance.GetFloat())
{
int iTryConversation = CONV_NONE;
if (GetMarineProfile()->m_VoiceType == ASW_VOICE_SARGE)
{
if (random->RandomFloat() < 0.5f)
iTryConversation = CONV_SARGE_IDLE;
else
iTryConversation = CONV_SARGE_JAEGER_CONV1;
}
else if (GetMarineProfile()->m_VoiceType == ASW_VOICE_JAEGER)
{
if (random->RandomFloat() < 0.5f)
iTryConversation = CONV_STILL_BREATHING;
else
iTryConversation = CONV_SARGE_JAEGER_CONV2;
}
else if (GetMarineProfile()->m_VoiceType == ASW_VOICE_CRASH)
{
if (random->RandomFloat() < 0.5f)
iTryConversation = CONV_CRASH_COMPLAIN;
else
iTryConversation = CONV_CRASH_IDLE;
}
else if (GetMarineProfile()->m_VoiceType == ASW_VOICE_WOLFE)
{
iTryConversation = CONV_WOLFE_BEST;
}
else if (GetMarineProfile()->m_VoiceType == ASW_VOICE_VEGAS)
{
iTryConversation = CONV_TEQUILA;
}
if (iTryConversation != CONV_NONE && CASW_MarineSpeech::StartConversation(iTryConversation, this))
bDidIdle = true;
else if (GetMarineSpeech()->Chatter(CHATTER_IDLE)) // fall back to regular idle if the conversation failed to start
bDidIdle = true;
}
else if (GetMarineSpeech()->Chatter(CHATTER_IDLE))
bDidIdle = true;
if (bDidIdle)
s_fNextIdleChatterTime = gpGlobals->curtime + random->RandomInt(15, 25);
//s_fNextIdleChatterTime = gpGlobals->curtime + random->RandomInt(30, 60);
//m_fIdleChatterDelay = random->RandomInt(10, 20);
m_fIdleChatterDelay = random->RandomInt(25, 40);
}
else
{
if (asw_debug_marine_chatter.GetBool())
Msg(" but we can't cos someone else did an idle chat recently\n");
//m_fIdleChatterDelay = random->RandomInt(5, 10);
m_fIdleChatterDelay = random->RandomInt(20, 35);
}
m_fLastStillTime = gpGlobals->curtime;
}
}
GetMarineSpeech()->Update();
if (asw_debug_medals.GetBool() && IsInhabited() && gpGlobals->maxClients <= 1)
{
GetMarineResource()->DebugMedalStatsOnScreen();
}
// network down if we're applying our engineering aura
if (GetMarineResource())
GetMarineResource()->m_bUsingEngineeringAura = (m_fUsingEngineeringAura >= gpGlobals->curtime - 0.2f);
if (!IsInhabited())
{
// uninhabited ticking of the hacking puzzles
if (m_hCurrentHack.Get())
{
CUserCmd ucmd;
m_hCurrentHack->ASWPostThink(NULL, this, &ucmd, fDeltaTime); // todo send deltatime parameter
}
// uninhabited post frame for offhand equip
CASW_Weapon *pExtra = GetASWWeapon(2);
if (pExtra && pExtra != GetActiveWeapon() && pExtra->WantsOffhandPostFrame() )
{
float flSavedFrameTime = gpGlobals->frametime;
gpGlobals->frametime = fDeltaTime;
pExtra->ItemPostFrame();
gpGlobals->frametime = flSavedFrameTime;
}
}
if ( gpGlobals->curtime > m_flNextBreadcrumbTime )
{
CASW_GameStats.Event_MarineBreadcrumb( this );
m_flNextBreadcrumbTime = gpGlobals->curtime + ASW_BREADCRUMB_INTERVAL;
}
// we've been burned relatively recently
if ( m_flFirstBurnTime > 0 )
{
float flGraceTime = asw_marine_time_until_ignite.GetFloat();
// if we haven't been burned in the last chunk of the total time-to-ignite, reset the timer
// additionally, if our initial burn time is over the time-to-ignite time, reset
if ( (gpGlobals->curtime - m_flFirstBurnTime) > flGraceTime + 1.0f )
m_flFirstBurnTime = 0;
}
m_fLastASWThink = gpGlobals->curtime;
}
/*
void CASW_Marine::Activate( void )
{
BaseClass::Activate();
// Find all drones
CBaseEntity *pObject = NULL;
while ( ( pObject = gEntList.FindEntityByClassname( pObject, "asw_drone" ) ) != NULL )
{
// Tell the AI sensing list that we want to consider this
g_AI_SensedObjectsManager.AddEntity( pObject );
}
}
*/
bool CASW_Marine::AIWantsToFire()
{
return m_bWantsToFire || (asw_force_ai_fire.GetBool());
}
bool CASW_Marine::AIWantsToFire2()
{
return m_bWantsToFire2;
}
bool CASW_Marine::AIWantsToReload()
{
return false;
}
void CASW_Marine::FlashlightToggle()
{
if (IsEffectActive( EF_DIMLIGHT ))
FlashlightTurnOff();
else
FlashlightTurnOn();
}
void CASW_Marine::FlashlightTurnOn( void )
{
if (!IsEffectActive( EF_DIMLIGHT ))
{
AddEffects( EF_DIMLIGHT );
EmitSound( "ASWFlashlight.FlashlightToggle" );
}
}
void CASW_Marine::FlashlightTurnOff( void )
{
if (IsEffectActive( EF_DIMLIGHT ))
{
EmitSound( "ASWFlashlight.FlashlightToggle");
RemoveEffects( EF_DIMLIGHT );
}
}
bool CASW_Marine::HasFlashlight()
{
for (int i=0; i<MAX_WEAPONS; ++i)
{
CASW_Weapon_Flashlight *pFlashlight = dynamic_cast<CASW_Weapon_Flashlight*>(m_hMyWeapons[i].Get());
if (pFlashlight)
return true;
}
return false;
}
// player (and player controlled marines) always avoid marines
bool CASW_Marine::ShouldPlayerAvoid( void )
{
return true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// as basecombat char, but we don't allow picking up of ammo unless we are
// holding a gun that uses it
// also allow double the max if we're carrying two guns of that type
int CASW_Marine::GiveAmmo( int iCount, int iAmmoIndex, bool bSuppressSound)
{
int iGuns = GetNumberOfWeaponsUsingAmmo(iAmmoIndex);
if (iGuns <= 0)
return 0;
if (iCount <= 0)
return 0;
//if ( !g_pGameRules->CanHaveAmmo( this, iAmmoIndex ) )
//{
// game rules say I can't have any more of this ammo type.
//return 0;
//}
if ( iAmmoIndex < 0 || iAmmoIndex >= MAX_AMMO_SLOTS )
return 0;
int iMax = GetAmmoDef()->MaxCarry(iAmmoIndex, this) * iGuns;
int iAdd = MIN( iCount, iMax - m_iAmmo[iAmmoIndex] );
if ( iAdd < 1 )
return 0;
// Ammo pickup sound
if ( !bSuppressSound )
{
EmitSound( "BaseCombatCharacter.AmmoPickup" );
}
m_iAmmo.Set( iAmmoIndex, m_iAmmo[iAmmoIndex] + iAdd );
return iAdd;
}
int CASW_Marine::GiveAmmoToAmmoBag( int iCount, int iAmmoIndex, bool bSuppressSound)
{
if (iCount <= 0)
return 0;
//if ( !g_pGameRules->CanHaveAmmo( this, iAmmoIndex ) )
//{
// game rules say I can't have any more of this ammo type.
// return 0;
//}
if ( iAmmoIndex < 0 || iAmmoIndex >= MAX_AMMO_SLOTS )
return 0;
CASW_Weapon_Ammo_Bag *pBag = dynamic_cast<CASW_Weapon_Ammo_Bag*>(GetWeapon(0));
if (!pBag || !pBag->HasRoomForAmmo(iAmmoIndex))
{
pBag = dynamic_cast<CASW_Weapon_Ammo_Bag*>(GetWeapon(1));
if (!pBag || !pBag->HasRoomForAmmo(iAmmoIndex))
return 0;
}
// Ammo pickup sound
if ( !bSuppressSound )
{
EmitSound( "BaseCombatCharacter.AmmoPickup" );
}
int iAdd = pBag->AddAmmo(iCount, iAmmoIndex);
return iAdd;
}
bool CASW_Marine::CanGiveAmmoTo( CASW_Marine* pMarine )
{
// iterate over my weapons to find ammo bag(s)
for ( int iWeapon = 0; iWeapon < ASW_NUM_INVENTORY_SLOTS; iWeapon++ )
{
CASW_Weapon_Ammo_Bag *pAmmoBag = dynamic_cast<CASW_Weapon_Ammo_Bag*>( GetASWWeapon( iWeapon ) );
if (pAmmoBag)
{
// see if the ammo bag can give ammo for any weapons the recipient has
for ( int iRecipientWeapon = 0; iRecipientWeapon < ASW_NUM_INVENTORY_SLOTS; iRecipientWeapon++ )
{
CASW_Weapon *pRecipientWeapon = pMarine->GetASWWeapon( iRecipientWeapon );
if (pAmmoBag->CanGiveAmmoToWeapon(pRecipientWeapon))
return true;
}
}
}
return false;
}
bool CASW_Marine::CarryingAGunThatUsesAmmo( int iAmmoIndex)
{
int n = WeaponCount();
for (int i=0;i<n;i++)
{
CBaseCombatWeapon* pWeapon = GetWeapon(i);
if (!pWeapon)
continue;
if (pWeapon->GetPrimaryAmmoType() == iAmmoIndex
|| pWeapon->GetSecondaryAmmoType() == iAmmoIndex)
return true;
}
return false;
}
void CASW_Marine::Weapon_Equip( CBaseCombatWeapon *pWeapon )
{
// Add the weapon to my weapon inventory
for (int i=0;i<MAX_WEAPONS;i++)
{
if (!m_hMyWeapons[i])
{
m_hMyWeapons.Set( i, pWeapon );
break;
}
}
Weapon_Equip_Post(pWeapon);
}
void CASW_Marine::Weapon_Equip_In_Index( CBaseCombatWeapon *pWeapon, int index )
{
if (GetWeapon(index)==NULL)
{
m_hMyWeapons.Set(index, pWeapon);
Weapon_Equip_Post(pWeapon);
}
}
// some things to do on the weapon after it's been put into the array
void CASW_Marine::Weapon_Equip_Post( CBaseCombatWeapon *pWeapon)
{
// Weapon is now on my team
pWeapon->ChangeTeam( GetTeamNumber() );
// ----------------------
// Give Primary Ammo
// ----------------------
// If gun doesn't use clips, just give ammo
if (pWeapon->GetMaxClip1() == -1)
{
GiveAmmo(pWeapon->GetDefaultClip1(), pWeapon->m_iPrimaryAmmoType);
}
// If default ammo given is greater than clip
// size, fill clips and give extra ammo
else if (pWeapon->GetDefaultClip1() > pWeapon->GetMaxClip1() )
{
pWeapon->m_iClip1 = pWeapon->GetMaxClip1();
GiveAmmo( (pWeapon->GetDefaultClip1() - pWeapon->GetMaxClip1()), pWeapon->m_iPrimaryAmmoType);
}
// ----------------------
// Give Secondary Ammo
// ----------------------
// If gun doesn't use clips, just give ammo
if (pWeapon->GetMaxClip2() == -1)
{
GiveAmmo(pWeapon->GetDefaultClip2(), pWeapon->m_iSecondaryAmmoType);
}
// If default ammo given is greater than clip
// size, fill clips and give extra ammo
else if ( pWeapon->GetDefaultClip2() > pWeapon->GetMaxClip2() )
{
pWeapon->m_iClip2 = pWeapon->GetMaxClip2();
GiveAmmo( (pWeapon->GetDefaultClip2() - pWeapon->GetMaxClip2()), pWeapon->m_iSecondaryAmmoType);
}
pWeapon->Equip( this );
// Gotta do this *after* Equip because it may whack maxRange
if ( IsPlayer() == false )
{
// If SF_NPC_LONG_RANGE spawn flags is set let weapon work from any distance
if ( HasSpawnFlags(SF_NPC_LONG_RANGE) )
{
m_hActiveWeapon->m_fMaxRange1 = 999999999;
m_hActiveWeapon->m_fMaxRange2 = 999999999;
}
}
WeaponProficiency_t proficiency;
proficiency = CalcWeaponProficiency( pWeapon );
if( weapon_showproficiency.GetBool() != 0 )
{
Msg("%s equipped with %s, proficiency is %s\n", GetClassname(), pWeapon->GetClassname(), GetWeaponProficiencyName( proficiency ) );
}
SetCurrentWeaponProficiency( proficiency );
// Pass the lighting origin over to the weapon if we have one
pWeapon->SetLightingOrigin( GetLightingOrigin() );
}
// all marines will send all their weapons to everyone
// since it's only 3 weapons per marine and knowing the other players' stuff will be handy
void CASW_Marine::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
{
// Skip this work if we're already marked for transmission.
if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
return;
BaseClass::SetTransmit( pInfo, bAlways );
for ( int i=0; i < MAX_WEAPONS; i++ )
{
CBaseCombatWeapon *pWeapon = m_hMyWeapons[i];
if ( !pWeapon )
continue;
// The local player is sent all of his weapons.
pWeapon->SetTransmit( pInfo, bAlways );
}
}
void CASW_Marine::TookAmmoPickup( CBaseEntity* pAmmoPickup )
{
//DoAnimationEvent(PLAYERANIMEVENT_PICKUP);
CASW_GameStats.Event_MarineTookPickup( this, pAmmoPickup, NULL );
}
bool CASW_Marine::TakeWeaponPickup( CASW_Weapon *pWeapon )
{
if ( !pWeapon )
return false;
// find the index this weapon is meant to go in
int index = GetWeaponPositionForPickup(pWeapon->GetClassname());
// is there already a weapon in this slot?
CASW_Weapon* pOldWeapon = GetASWWeapon(index);
// check we're allowed to take this item
bool bAllowed = true;
if (pOldWeapon) // we're swapping with an existing weapon
bAllowed = ASWGameRules()->MarineCanPickup(GetMarineResource(), pWeapon->GetClassname(), pOldWeapon->GetClassname());
else // we're putting it into an empty slot
bAllowed = ASWGameRules()->MarineCanPickup(GetMarineResource(), pWeapon->GetClassname());
if (!bAllowed)
return false;
CASW_GameStats.Event_MarineTookPickup( this, pWeapon, pOldWeapon );
bool bReplace = ( pOldWeapon != NULL );
// if we're swapping with a current weapon, drop it
if ( bReplace )
{
if (!DropWeapon(index, true)) // todo: set the pickup denial error
return false;
}
// If I have a name, make my weapon match it with "_weapon" appended
if ( GetEntityName() != NULL_STRING )
{
const char* weapon_name = UTIL_VarArgs("%s_weapon", STRING(GetEntityName()));
pWeapon->SetName( AllocPooledString(weapon_name) );
}
IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" );
if ( event )
{
CASW_Player *pPlayer = GetCommander();
event->SetInt( "userid", ( pPlayer ? pPlayer->GetUserID() : 0 ) );
event->SetInt( "entindex", pWeapon->entindex() );
event->SetString( "classname", pWeapon->GetWeaponInfo()->szClassName );
event->SetInt( "slot", index );
event->SetBool( "replace", bReplace );
event->SetBool( "offhand", pWeapon->GetWeaponInfo()->m_bOffhandActivate );
gameeventmanager->FireEvent( event );
}
// equip the weapon
Weapon_Equip_In_Index( pWeapon, index );
// set the number of clips
if (pWeapon->GetPrimaryAmmoType()!=-1)
GiveAmmo(pWeapon->GetPrimaryAmmoCount(), pWeapon->GetPrimaryAmmoType());
//maybe switch to this weapon, if current is none
if (GetActiveWeapon()==NULL)
{
Weapon_Switch( pWeapon );
}
else
{
pWeapon->SetWeaponVisible(false);
}
CheckAndRequestAmmo();
GetMarineResource()->UpdateWeaponIndices();
return true;
}
bool CASW_Marine::TakeWeaponPickup(CASW_Pickup_Weapon* pPickup)
{
// find the index this weapon is meant to go in
int index = GetWeaponPositionForPickup(pPickup->GetWeaponClass());
// is there already a weapon in this slot?
CASW_Weapon* pWeapon = GetASWWeapon( index );
// check we're allowed to take this item
bool bAllowed = true;
if ( pWeapon ) // we're swapping with an existing weapon
{
bAllowed = ASWGameRules()->MarineCanPickup( GetMarineResource(), pPickup->GetWeaponClass(), pWeapon->GetClassname() );
}
else // we're putting it into an empty slot
{
bAllowed = ASWGameRules()->MarineCanPickup( GetMarineResource(), pPickup->GetWeaponClass() );
}
if ( !bAllowed )
{
return false;
}
CASW_GameStats.Event_MarineTookPickup( this, pPickup, pWeapon );
// if we're swapping with a current weapon, drop it
bool bReplace = ( pWeapon != NULL );
if ( bReplace )
{
if ( !DropWeapon( index, true ) ) // todo: set the pickup denial error
{
return false;
}
}
// give ourselves this weapon, in the right slot
//Msg("CASW_Marine::TakeWeaponPickup calling Weapon_Create %s\n", pPickup->GetWeaponClass());
pWeapon = dynamic_cast<CASW_Weapon*>(Weapon_Create(pPickup->GetWeaponClass()));
if ( pWeapon )
{
// If I have a name, make my weapon match it with "_weapon" appended
if ( GetEntityName() != NULL_STRING )
{
const char* weapon_name = UTIL_VarArgs("%s_weapon", STRING(GetEntityName()));
pWeapon->SetName( AllocPooledString(weapon_name) );
}
// set + take ammo accordingly
pPickup->InitWeapon(this, pWeapon);
//maybe switch to this weapon, if current is none
if ( GetActiveWeapon() == NULL )
{
Weapon_Switch( pWeapon );
}
else
{
pWeapon->SetWeaponVisible(false);
}
IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" );
if ( event )
{
CASW_Player *pPlayer = GetCommander();
event->SetInt( "userid", ( pPlayer ? pPlayer->GetUserID() : 0 ) );
event->SetInt( "entindex", pWeapon->entindex() );
event->SetString( "classname", pWeapon->GetWeaponInfo()->szClassName );
event->SetInt( "slot", index );
event->SetBool( "replace", bReplace );
event->SetBool( "offhand", pWeapon->GetWeaponInfo()->m_bOffhandActivate );
gameeventmanager->FireEvent( event );
}
// destroy the pickup
pPickup->Remove();
GetMarineResource()->UpdateWeaponIndices();
CheckAndRequestAmmo();
return true;
}
if ( !GetASWWeapon( index ) )
{
SwitchToNextBestWeapon( NULL );
}
return false; // todo: clear the pickup denial error
}
bool CASW_Marine::DropWeapon(int iWeaponIndex, bool bNoSwap)
{
CASW_Weapon* pWeapon = GetASWWeapon(iWeaponIndex);
if (!pWeapon)
return false;
RemoveWeaponPowerup( pWeapon );
return DropWeapon(pWeapon, bNoSwap);
}
bool CASW_Marine::DropWeapon(CASW_Weapon* pWeapon, bool bNoSwap, const Vector *pvecTarget /* = NULL */, const Vector *pVelocity /* = NULL */ )
{
RemoveWeaponPowerup( pWeapon );
// dropping the weapon entity itself
// set clips in the dropped weapon
int iAmmoIndex = pWeapon->GetPrimaryAmmoType();
int bullets_on_player = GetAmmoCount(iAmmoIndex);
int iClips = bullets_on_player / pWeapon->GetMaxClip1();
if (GetNumberOfWeaponsUsingAmmo(iAmmoIndex) > 1)
{
// need to leave at least X clips with the marine, since he has a gun using this ammo
int iMax = GetAmmoDef()->MaxCarry(iAmmoIndex, this);
int iKeep = MAX(0, (bullets_on_player - iMax));
iClips = iKeep / pWeapon->GetMaxClip1();
}
pWeapon->SetPrimaryAmmoCount( iClips * pWeapon->GetMaxClip1() );
// remove ammo from the marine correspondingly
if (iAmmoIndex != -1)
{
int current_bullets = GetAmmoCount(pWeapon->GetPrimaryAmmoType());
current_bullets -= pWeapon->GetPrimaryAmmoCount();
if (current_bullets < 0)
current_bullets = 0;
SetAmmoCount(current_bullets, pWeapon->GetPrimaryAmmoType());
}
// throw the weapon a bit
Vector vecForward = BodyDirection2D();
QAngle gunAngles;
VectorAngles( vecForward, gunAngles );
//=========================================
// Teleport the weapon to the player's hand
//=========================================
int iBIndex = -1;
int iWeaponBoneIndex = -1;
MDLCACHE_CRITICAL_SECTION();
CStudioHdr *hdr = pWeapon->GetModelPtr();
// If I have a hand, set the weapon position to my hand bone position.
if ( hdr && hdr->numbones() > 0 )
{
// Assume bone zero is the root
for ( iWeaponBoneIndex = 0; iWeaponBoneIndex < hdr->numbones(); ++iWeaponBoneIndex )
{
iBIndex = LookupBone( hdr->pBone( iWeaponBoneIndex )->pszName() );
// Found one!
if ( iBIndex != -1 )
{
break;
}
}
if ( iBIndex == -1 )
{
iBIndex = LookupBone( "ValveBiped.bip01_R_Hand" );
}
}
else
{
iBIndex = LookupBone( "ValveBiped.bip01_R_Hand" );
}
if ( iBIndex != -1)
{
Vector origin;
QAngle angles;
matrix3x4_t transform;
// Get the transform for the weapon bonetoworldspace in the NPC
GetBoneTransform( iBIndex, transform );
// find offset of root bone from origin in local space
// Make sure we're detached from hierarchy before doing this!!!
pWeapon->StopFollowingEntity();
pWeapon->SetAbsOrigin( Vector( 0, 0, 0 ) );
pWeapon->SetAbsAngles( QAngle( 0, 0, 0 ) );
pWeapon->InvalidateBoneCache();
matrix3x4_t rootLocal;
if ( iWeaponBoneIndex < hdr->numbones() )
{
pWeapon->GetBoneTransform( iWeaponBoneIndex, rootLocal );
}
else
{
SetIdentityMatrix( rootLocal );
}
// invert it
matrix3x4_t rootInvLocal;
MatrixInvert( rootLocal, rootInvLocal );
matrix3x4_t weaponMatrix;
ConcatTransforms( transform, rootInvLocal, weaponMatrix );
MatrixAngles( weaponMatrix, angles, origin );
// Ensure this position isn't through a wall.
Vector vMarineCenter = WorldSpaceCenter();
trace_t tr;
Ray_t ray;
ray.Init( vMarineCenter, origin, pWeapon->ScriptGetBoundingMins(), pWeapon->ScriptGetBoundingMaxs() );
UTIL_TraceRay( ray, MASK_SOLID, this, COLLISION_GROUP_WEAPON, &tr );
if ( tr.DidHit() )
{
// We hit something... shove it back toward the marine
Vector vTraceDir = origin - vMarineCenter;
float fLength = VectorNormalize( vTraceDir );
origin = GetAbsOrigin() + vTraceDir * fLength * tr.fraction - vTraceDir * 0.5f * pWeapon->ScriptGetBoundingMaxs().DistTo( pWeapon->ScriptGetBoundingMins() );
}
if ( origin.z < vMarineCenter.z )
{
// Prevent stuff falling out of the world when the marines hands are very low (they're doing a bending down anim)
origin.z = vMarineCenter.z;
}
pWeapon->Teleport( &origin, &angles, NULL );
//Have to teleport the physics object as well
IPhysicsObject *pWeaponPhys = pWeapon->VPhysicsGetObject();
if( pWeaponPhys )
{
Vector vPos;
QAngle vAngles;
pWeaponPhys->GetPosition( &vPos, &vAngles );
pWeaponPhys->SetPosition( vPos, angles, true );
AngularImpulse angImp(0,0,0);
Vector vecAdd = GetAbsVelocity();
pWeaponPhys->AddVelocity( &vecAdd, &angImp );
}
}
else
{
pWeapon->SetAbsOrigin( GetAbsOrigin() + vecForward * 80.0f );
}
Vector vecThrow;
ThrowDirForWeaponStrip( pWeapon, vecForward, &vecThrow );
// apply the desired velocity, if any
if ( pvecTarget )
{
// I've been told to throw it somewhere specific.
vecThrow = VecCheckToss( this, pWeapon->GetAbsOrigin(), *pvecTarget, 0.2, 1.0, false );
}
else
{
if ( pVelocity )
{
vecThrow = *pVelocity;
float flLen = vecThrow.Length();
if ( flLen > 400 )
{
VectorNormalize( vecThrow );
vecThrow *= 400;
}
}
else
{
// Nowhere in particular; just drop it.
float throwForce = ( IsPlayer() ) ? 400.0f : random->RandomInt( 64, 128 );
vecThrow = BodyDirection3D() * throwForce;
}
}
pWeapon->Drop( vecThrow );
pWeapon->MarineDropped( this );
Weapon_Detach( pWeapon );
// switch to the next weapon, if any
if ( !bNoSwap )
{
if ( SwitchToNextBestWeapon( NULL ) )
{
// explicitly tell this client to play the weapon switch anim, since he didn't predict this change
TE_MarineAnimEventJustCommander(this, PLAYERANIMEVENT_WEAPON_SWITCH);
}
}
GetMarineResource()->UpdateWeaponIndices();
return true;
}
void CASW_Marine::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget /* = NULL */, const Vector *pVelocity /* = NULL */ )
{
CASW_Weapon* pASWWeapon = dynamic_cast<CASW_Weapon*>(pWeapon);
if (!pASWWeapon)
return;
DropWeapon(pASWWeapon, false, pvecTarget, pVelocity);
}
// healing
void CASW_Marine::AddSlowHeal( int iHealAmount, float flHealRateScale, CASW_Marine *pMedic, CBaseEntity* pHealingWeapon /*= NULL */ )
{
if ( GetHealth() <= 0 )
return;
if (iHealAmount > 0)
{
m_flHealRateScale = flHealRateScale;
if (!m_bSlowHeal)
{
m_bSlowHeal = true;
m_fNextSlowHealTick = gpGlobals->curtime + ( ASW_MARINE_HEALTICK_RATE * ( 1.0f / m_flHealRateScale ) );
}
m_iSlowHealAmount += iHealAmount;
//if (!m_bSlowHeal)
//{
// m_bSlowHeal = true;
// m_fNextSlowHealTick = gpGlobals->curtime + 0.33f;
//}
//m_iSlowHealAmount += iHealAmount;
// subtract FF from the amount healed, before storing it in the medic's healing stats
int iMedicMedalHealed = iHealAmount;
// don't give credit for healing more than the marine's max health
if (GetHealth() + iHealAmount > GetMaxHealth())
{
iMedicMedalHealed = GetMaxHealth() - GetHealth();
//Msg(" healing more than we have health for, so cutting down to %d\n", iMedicMedalHealed);
}
//Msg("healing marine for %d, ff damage is %f\n", iMedicMedalHealed, m_fFriendlyFireDamage);
if (m_fFriendlyFireDamage > 0)
{
iMedicMedalHealed -= m_fFriendlyFireDamage;
m_fFriendlyFireDamage -= iHealAmount;
if (m_fFriendlyFireDamage < 0)
m_fFriendlyFireDamage = 0;
//Msg(" So new medic medal healed is %d and new m_fFriendlyFireDamage is %f\n", iMedicMedalHealed, m_fFriendlyFireDamage);
}
if (iMedicMedalHealed > 0 && pMedic != this && pMedic && pMedic->GetMarineResource())
{
pMedic->GetMarineResource()->m_iMedicHealing += iMedicMedalHealed;
}
// healing puts out fires
if (IsOnFire())
{
Extinguish();
/*
CEntityFlame *pFireChild = dynamic_cast<CEntityFlame *>( GetEffectEntity() );
if ( pFireChild )
{
SetEffectEntity( NULL );
UTIL_Remove( pFireChild );
Extinguish();
}
*/
}
// count heal for stats
if (GetMarineResource())
GetMarineResource()->m_iHealCount++;
// Fire heal event for stat tracking
CASW_GameStats.Event_MarineHealed( this , iHealAmount, pHealingWeapon );
}
}
// using entities over time
bool CASW_Marine::StartUsing( CBaseEntity* pEntity )
{
if ( GetHealth() <= 0 )
return false;
IASW_Server_Usable_Entity* pUsable = dynamic_cast< IASW_Server_Usable_Entity* >( pEntity );
if ( pUsable )
{
if ( !pUsable->IsUsable( this ) )
return false;
if ( !pUsable->RequirementsMet( this ) )
return false;
pUsable->MarineStartedUsing( this );
m_hUsingEntity = pEntity;
return true;
}
return false;
}
void CASW_Marine::StopUsing()
{
if (!m_hUsingEntity)
return;
IASW_Server_Usable_Entity* pUsable = dynamic_cast<IASW_Server_Usable_Entity*>(m_hUsingEntity.Get());
if (pUsable)
{
pUsable->MarineStoppedUsing(this);
}
m_hUsingEntity = NULL;
m_hAreaToUse = NULL; // FIXME: This might accidently clear new orders if the marine was just ordered to a new area!
}
// marine has been hit by a melee attack
void CASW_Marine::MeleeBleed(CTakeDamageInfo* info)
{
Vector vecDir = vec3_origin;
if (info->GetAttacker())
{
// don't bleed from melee coming from other marines, as they can't hurt us
if (info->GetAttacker()->Classify() == CLASS_ASW_MARINE)
return;
vecDir = info->GetAttacker()->GetAbsOrigin() - GetAbsOrigin();
VectorNormalize(vecDir);
}
else
{
vecDir = RandomVector(-1, 1);
}
//UTIL_ASW_BloodDrips( GetAbsOrigin()+Vector(0,0,60)+vecDir*3, vecDir, BloodColor(), 5 );
Vector vecInflictorPos = info->GetDamagePosition();
if ( info->GetInflictor() )
{
vecInflictorPos = info->GetInflictor()->GetAbsOrigin();
}
CRecipientFilter filter;
filter.AddAllPlayers();
UserMessageBegin( filter, "ASWMarineHitByMelee" );
WRITE_SHORT( entindex() );
WRITE_FLOAT( vecInflictorPos.x );
WRITE_FLOAT( vecInflictorPos.y );
WRITE_FLOAT( vecInflictorPos.z );
MessageEnd();
}
/// issue any special effects or sounds on resurrection
void CASW_Marine::PerformResurrectionEffect( void ) RESTRICT
{
DispatchParticleEffect( "marine_resurrection", PATTACH_ABSORIGIN_FOLLOW, this );
this->EmitSound( "Marine.Resurrect" );
}
void CASW_Marine::BecomeInfested(CASW_Alien* pAlien)
{
if ( !GetMarineResource() )
return;
m_fInfestedTime = 20.0f;
GetMarineSpeech()->ForceChatter( CHATTER_INFESTED, ASW_CHATTER_TIMER_TEAM );
if ( !IsInfested() )
{
// do some damage to us immediately (unless we were already infested)
float DamagePerTick = ASWGameRules()->TotalInfestDamage() / 20.0f;
if ( asw_debug_marine_damage.GetBool() )
{
Msg("%f: Infest DamagePerTick %f (infest time left = %f)\n", gpGlobals->curtime, DamagePerTick, m_fInfestedTime);
}
CTakeDamageInfo info( NULL, NULL, Vector(0,0,0), GetAbsOrigin(), DamagePerTick, DMG_INFEST );
TakeDamage( info );
m_fInfestedStartTime = gpGlobals->curtime;
// Give them 3 free cycles (.9 seconds) to panic before we do the first real bite!
m_iInfestCycle = -3;
GetMarineResource()->SetInfested( true );
ASWFailAdvice()->OnMarineInfested();
IGameEvent * event = gameeventmanager->CreateEvent( "marine_infested" );
if ( event )
{
event->SetInt( "entindex", entindex() );
gameeventmanager->FireEvent( event );
}
if ( !IsInhabited() )
{
DoEmote( 0 );
}
}
if ( m_fNextSlowHealTick < gpGlobals->curtime )
{
m_fNextSlowHealTick = gpGlobals->curtime + 0.33f;
}
m_bPlayedCureScream = false;
}
void CASW_Marine::CureInfestation(CASW_Marine *pHealer, float fCureFraction)
{
if ( !GetMarineResource() )
return;
if ( m_fInfestedTime != 0 )
{
m_fInfestedTime = m_fInfestedTime * fCureFraction;
if ( pHealer )
m_hInfestationCurer = pHealer;
if ( m_fInfestedTime < 0.0f )
{
m_fInfestedTime = 0.0f;
if ( !IsInhabited() )
{
DoEmote( 2 );
}
}
}
}
// if we died from infestation, then gib
bool CASW_Marine::ShouldGib( const CTakeDamageInfo &info )
{
if (info.GetDamageType() & DMG_INFEST)
return true;
return BaseClass::ShouldGib(info);
}
bool CASW_Marine::CorpseGib( const CTakeDamageInfo &info )
{
EmitSound( "BaseCombatCharacter.CorpseGib" );
QAngle vecAngles;
VectorAngles( -info.GetDamageForce(), vecAngles );
CBaseEntity *pHelpHelpImBeingSupressed = (CBaseEntity*) te->GetSuppressHost();
te->SetSuppressHost( NULL );
DispatchParticleEffect( "marine_gib", PATTACH_ABSORIGIN_FOLLOW, this );
te->SetSuppressHost( pHelpHelpImBeingSupressed );
return true;
}
// if we gibbed from infestation damage, spawn some parasites
bool CASW_Marine::Event_Gibbed( const CTakeDamageInfo &info )
{
if ( info.GetDamageType() & DMG_INFEST )
{
if ( asw_debug_marine_damage.GetBool() )
{
Msg("marine infest gibbed at loc %f, %f, %f\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z);
}
if ( asw_debug_marine_damage.GetBool())
{
NDebugOverlay::EntityBounds(this, 255,0,0, 255, 15.0f);
}
ASWFailAdvice()->OnMarineInfestedGibbed();
int iNumParasites = 3 + RandomInt(0,2);
QAngle angParasiteFacing[5];
float fJumpDistance[5];
// for some reason if we calculate these inside the loop, the random numbers all come out the same. Worrying.
angParasiteFacing[0] = GetAbsAngles(); angParasiteFacing[0].y = RandomFloat( -180.0f, 180.0f );
angParasiteFacing[1] = GetAbsAngles(); angParasiteFacing[1].y = RandomFloat( -180.0f, 180.0f );
angParasiteFacing[2] = GetAbsAngles(); angParasiteFacing[2].y = RandomFloat( -180.0f, 180.0f );
angParasiteFacing[3] = GetAbsAngles(); angParasiteFacing[3].y = RandomFloat( -180.0f, 180.0f );
angParasiteFacing[4] = GetAbsAngles(); angParasiteFacing[4].y = RandomFloat( -180.0f, 180.0f );
fJumpDistance[0] = RandomFloat( 30.0f, 70.0f );
fJumpDistance[1] = RandomFloat( 30.0f, 70.0f );
fJumpDistance[2] = RandomFloat( 30.0f, 70.0f );
fJumpDistance[3] = RandomFloat( 30.0f, 70.0f );
fJumpDistance[4] = RandomFloat( 30.0f, 70.0f );
for ( int i = 0; i < iNumParasites; i++ )
{
bool bBlocked = true;
int k = 0;
Vector vecSpawnPos = GetAbsOrigin();
float fCircleDegree = ( static_cast< float >( i ) / iNumParasites ) * 2.0f * M_PI;
vecSpawnPos.x += sinf( fCircleDegree ) * RandomFloat( 3.0f, 20.0f );
vecSpawnPos.y += cosf( fCircleDegree ) * RandomFloat( 3.0f, 20.0f );
vecSpawnPos.z += RandomFloat( 20.0f, 40.0f );
while ( bBlocked && k < 6 )
{
if ( k > 0 )
{
// Scooch it up
vecSpawnPos.z += NAI_Hull::Maxs( HULL_TINY ).z - NAI_Hull::Mins( HULL_TINY ).z;
}
// check if there's room at this position
trace_t tr;
UTIL_TraceHull( vecSpawnPos, vecSpawnPos + Vector( 0.0f, 0.0f, 1.0f ),
NAI_Hull::Mins(HULL_TINY) + Vector( -4.0f, -4.0f, -4.0f ),NAI_Hull::Maxs(HULL_TINY) + Vector( 4.0f, 4.0f, 4.0f ),
MASK_NPCSOLID, this, ASW_COLLISION_GROUP_PARASITE, &tr );
if ( asw_debug_marine_damage.GetBool() )
{
NDebugOverlay::Box(vecSpawnPos, NAI_Hull::Mins(HULL_TINY),NAI_Hull::Maxs(HULL_TINY), 255,255,0,255,15.0f);
}
if ( tr.fraction == 1.0 )
{
bBlocked = false;
}
k++;
}
if (bBlocked)
continue; // couldn't find room for parasites
if ( asw_debug_marine_damage.GetBool() )
{
Msg("Found an unblocked pos for this entity, trying to spawn it there %f, %f, %f\n", vecSpawnPos.x,
vecSpawnPos.y, vecSpawnPos.z);
}
CASW_Parasite *pParasite = dynamic_cast< CASW_Parasite* >( CreateNoSpawn( "asw_parasite",
vecSpawnPos, angParasiteFacing[i], this));
if ( pParasite )
{
PhysDisableEntityCollisions( pParasite, this );
DispatchSpawn( pParasite );
pParasite->SetSleepState(AISS_WAITING_FOR_INPUT);
pParasite->SetJumpFromEgg(true, fJumpDistance[i]);
pParasite->Wake();
}
}
}
AddEffects( EF_NODRAW ); // make the model invisible.
SetSolid( SOLID_NONE );
SetNextThink( gpGlobals->curtime + 2.0f );
SetThink( &CASW_Marine::SUB_Remove );
return CorpseGib( info );
}
float CASW_Marine::GetIdealSpeed() const
{
return m_fCachedIdealSpeed;
}
CRagdollProp* CASW_Marine::GetRagdollProp()
{
return dynamic_cast<CRagdollProp*>(m_hKnockedOutRagdoll.Get());
}
void CASW_Marine::Event_Killed( const CTakeDamageInfo &info )
{
bool bAllDead = false;
if ( ASWGameRules() && ASWGameRules()->GetMissionManager() )
{
bAllDead = ASWGameRules()->GetMissionManager()->AllMarinesDead();
}
CASW_GameStats.Event_MarineKilled( this, info );
ASWFailAdvice()->OnMarineKilled();
float flPosition = -1.0f;
UTIL_ASW_NearestMarine( this, flPosition );
if ( !bAllDead )
{
if ( flPosition != -1.0f && flPosition > 1000.0f )
{
ASWFailAdvice()->OnMarineKilledAlone();
}
if ( m_hCurrentHack.Get() )
{
ASWFailAdvice()->OnHackerDied();
}
CASW_Player *pPlayer = GetCommander();
if ( pPlayer && pPlayer->GetMarine() == this )
{
if ( UTIL_ASW_NumCommandedMarines( pPlayer ) >= 1 )
{
IGameEvent * event = gameeventmanager->CreateEvent( "player_should_switch" );
if ( event )
{
event->SetInt( "userid", pPlayer->GetUserID() );
gameeventmanager->FireEvent( event );
}
}
}
if ( IsInfested() )
{
IGameEvent * event = gameeventmanager->CreateEvent( "marine_infested_killed" );
if ( event )
{
event->SetInt( "userid", pPlayer ? pPlayer->GetUserID() : 0 );
event->SetInt( "marine", entindex() );
gameeventmanager->FireEvent( event );
}
}
}
if ( ASWGameRules() )
{
ASWGameRules()->MarineKilled( this, info );
// Start up the death cam
ASWGameRules()->m_fMarineDeathTime = gpGlobals->curtime;
ASWGameRules()->m_vMarineDeathPos = GetAbsOrigin();
ASWGameRules()->m_nMarineForDeathCam = ASWGameResource()->GetMarineResourceIndex( GetMarineResource() ); // TODO: Is this ok!?
// Check mission status
if ( ASWGameRules()->GetMissionManager() )
{
ASWGameRules()->GetMissionManager()->MarineKilled( this );
}
}
if ( m_hUsingEntity.Get() )
{
StopUsing();
}
// store off our death position
CASW_Marine_Resource *pMR = GetMarineResource();
if ( pMR )
{
pMR->m_vecDeathPosition = GetAbsOrigin();
pMR->m_fDeathTime = gpGlobals->curtime;
pMR->SetFiring( 0 );
pMR->m_TimelineAmmo.RecordValue( 0.0f );
pMR->m_TimelineHealth.RecordValue( 0.0f );
}
// drop all of our equipment
// Calculate death force
Vector forceVector = CalcDeathForceVector( info );
float flMagnitude = forceVector.Length();
if ( flMagnitude > 20000 )
{
forceVector *= 20000 / flMagnitude;
}
for ( int i = 0; i < ASW_MAX_MARINE_WEAPONS; i++ )
{
CBaseCombatWeapon *pDroppedWeapon = GetWeapon( i );
if ( pDroppedWeapon )
{
// Drop any weapon that I own
if ( VPhysicsGetObject() )
{
Vector weaponForce = forceVector * VPhysicsGetObject()->GetInvMass();
Weapon_Drop( pDroppedWeapon, NULL, &weaponForce );
}
else
{
Weapon_Drop( pDroppedWeapon, NULL, &forceVector );
}
}
}
// see if any other marines are nearby to shout out about us
if ( ASWGameResource() )
{
CASW_Game_Resource *pGameResource = ASWGameResource();
if (pGameResource)
{
if ( asw_realistic_death_chatter.GetBool() )
{
int iNumNearby = 0;
for (int i=0;i<pGameResource->GetMaxMarineResources();i++)
{
CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
CASW_Marine *pOtherMarine = pMR ? pMR->GetMarineEntity() : NULL;
if (pOtherMarine && pOtherMarine != this
&& pOtherMarine->GetHealth() > 0
&& GetAbsOrigin().DistTo(pOtherMarine->GetAbsOrigin()) < 800)
iNumNearby++;
}
if (iNumNearby > 0)
{
int iChosen = random->RandomInt(1, iNumNearby);
for (int i=0;i<pGameResource->GetMaxMarineResources() && iChosen > 0;i++)
{
CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
CASW_Marine *pOtherMarine = pMR ? pMR->GetMarineEntity() : NULL;
if (pOtherMarine && pOtherMarine != this
&& pOtherMarine->GetHealth() > 0
&& GetAbsOrigin().DistTo(pOtherMarine->GetAbsOrigin()) < 800)
{
iChosen--;
if (iChosen <= 0)
{
if (asw_debug_marine_chatter.GetBool())
Msg("making marine CHATTER_MARINE_DOWN %s\n", pOtherMarine->GetMarineProfile()->m_ShortName);
pOtherMarine->GetMarineSpeech()->QueueChatter(CHATTER_MARINE_DOWN, gpGlobals->curtime+0.5f, gpGlobals->curtime+1.50f);
}
}
}
}
}
else
{
// pick one marine for each player to shout about the marine death
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CASW_Player* pOtherPlayer = dynamic_cast<CASW_Player*>(UTIL_PlayerByIndex(i));
if ( !pOtherPlayer)
continue;
Vector vecPlayerPos = vec3_origin;
if ( pOtherPlayer->GetMarine() )
{
vecPlayerPos = pOtherPlayer->GetMarine()->GetAbsOrigin();
}
// tell all other marines to shout about this death
CASW_Marine *pChosenMarine = NULL;
// count how many marines this player has
int iNumMarines = 0;
for (int i=0;i<pGameResource->GetMaxMarineResources();i++)
{
CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
CASW_Marine *pOtherMarine = pMR ? pMR->GetMarineEntity() : NULL;
if (pOtherMarine && pOtherMarine != this
&& pOtherMarine->GetHealth() > 0
&& ( vecPlayerPos == vec3_origin || pOtherMarine->GetAbsOrigin().DistTo( vecPlayerPos ) < 800 ) )
{
iNumMarines++;
}
}
// now choose one to play the sound
int iChosen = random->RandomInt(1, iNumMarines);
for (int i=0;i<pGameResource->GetMaxMarineResources();i++)
{
CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
CASW_Marine *pOtherMarine = pMR ? pMR->GetMarineEntity() : NULL;
if (pOtherMarine && pOtherMarine != this
&& pOtherMarine->GetHealth() > 0
&& ( vecPlayerPos == vec3_origin || pOtherMarine->GetAbsOrigin().DistTo( vecPlayerPos ) < 800 ) )
{
iChosen--;
if ( iChosen <= 0 )
{
pChosenMarine = pOtherMarine;
break;
}
}
}
if ( pChosenMarine )
{
// do private full volume chatter
pChosenMarine->GetMarineSpeech()->QueueChatter(CHATTER_MARINE_DOWN, gpGlobals->curtime+0.5f, gpGlobals->curtime+1.50f, pOtherPlayer);
}
}
}
}
}
BaseClass::Event_Killed(info);
if (asw_debug_marine_chatter.GetBool())
Msg("making marine CHATTER_DIE %s\n", GetMarineProfile()->m_ShortName);
GetMarineSpeech()->ForceChatter(CHATTER_DIE, ASW_CHATTER_TIMER_NONE);
if ( IsInhabited() && GetCommander() )
{
// play death beep to the person controlling this marine
GetCommander()->EmitPrivateSound( "Marine.DeathBeep" );
}
// check if this mission has a tech req
if (ASWGameRules() && ASWGameRules()->m_bMissionRequiresTech)
{
CASW_Game_Resource *pGameResource = ASWGameResource();
if ( pGameResource )
{
// count number of live techs
bool bTech = false;
for (int i=0;i<pGameResource->GetMaxMarineResources();i++)
{
CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
if (pMR && pMR->GetHealthPercent() > 0 && pMR->GetProfile() && pMR->GetProfile()->CanHack())
{
bTech = true;
break;
}
}
if ( !bTech && pGameResource->CountAllAliveMarines() > 0 )
{
ASWGameRules()->ScheduleTechFailureRestart( gpGlobals->curtime + 1.5f );
}
}
}
// print a message if marine was killed by another marine
if (info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE )
{
CASW_Marine *pOtherMarine = dynamic_cast< CASW_Marine* >( info.GetAttacker() );
if ( pOtherMarine && GetMarineProfile() && pOtherMarine->GetMarineProfile() )
{
CASW_Marine_Resource *pMR = GetMarineResource();
if ( pMR )
{
char szName[ 256 ];
pMR->GetDisplayName( szName, sizeof( szName ) );
if ( pOtherMarine == this )
{
if ( GetMarineProfile()->m_bFemale )
UTIL_ClientPrintAll( ASW_HUD_PRINTTALKANDCONSOLE, "#asw_suicide_female", szName );
else
UTIL_ClientPrintAll( ASW_HUD_PRINTTALKANDCONSOLE, "#asw_suicide_male", GetMarineProfile()->m_ShortName );
}
else
{
CASW_Marine_Resource *pMROther = pOtherMarine->GetMarineResource();
if ( pMROther )
{
char szNameOther[ 256 ];
pMROther->GetDisplayName( szNameOther, sizeof( szNameOther ) );
UTIL_ClientPrintAll( ASW_HUD_PRINTTALKANDCONSOLE, "#asw_team_killed", szName, szNameOther );
}
}
}
}
}
m_bSlowHeal = false; // no healing if we're dead!
}
void CASW_Marine::AimGun()
{
BaseClass::AimGun();
if (!IsInhabited())
{
m_fAIPitch = GetPoseParameter( "aim_pitch" );
}
}
void CASW_Marine::DoEmote(int iEmote)
{
if (GetFlags() & FL_FROZEN || !GetMarineSpeech()) // don't allow this if the marine is frozen
return;
switch (iEmote)
{
case 0:
{
GetMarineSpeech()->Chatter(CHATTER_MEDIC);
bEmoteMedic = true;
break;
}
case 1:
{
GetMarineSpeech()->Chatter(CHATTER_NEED_AMMO);
bEmoteAmmo = true;
break;
}
case 2:
{
bEmoteSmile = true;
break;
}
case 3:
{
GetMarineSpeech()->Chatter(CHATTER_HOLD_POSITION);
DoAnimationEvent(PLAYERANIMEVENT_HALT);
bEmoteStop = true;
break;
}
case 4:
{
GetMarineSpeech()->Chatter(CHATTER_FOLLOW_ME);
DoAnimationEvent(PLAYERANIMEVENT_GO);
bEmoteGo = true;
break;
}
case 5:
{
GetMarineSpeech()->Chatter(CHATTER_WATCH_OUT);
bEmoteExclaim = true;
break;
}
case 7:
{
GetMarineSpeech()->Chatter(CHATTER_QUESTION);
bEmoteQuestion = true;
break;
}
default:
{
bEmoteAnimeSmile = true;
break;
}
}
}
bool CASW_Marine::IsPlayerAlly( CBasePlayer *pPlayer )
{
return true;
}
IASW_Vehicle* CASW_Marine::GetASWVehicle()
{
//IASW_Vehicle* pEnt = m_hASWVehicle.Get();
//return dynamic_cast<IASW_Vehicle*>(pEnt);
return dynamic_cast<IASW_Vehicle*>(m_hASWVehicle.Get());
}
// make the marine start driving a particular vehicle
void CASW_Marine::StartDriving(IASW_Vehicle* pVehicle)
{
if (!pVehicle || IsDriving() || IsInVehicle() || pVehicle->ASWGetDriver()!=NULL)
return;
CBaseEntity* pEnt = pVehicle->GetEntity();
if (!pEnt)
return;
//Must be able to stow our weapon
CBaseCombatWeapon *pWeapon = GetActiveWeapon();
if ( ( pWeapon != NULL ) && ( pWeapon->Holster( NULL ) == false ) )
return;
pVehicle->ASWSetDriver(this);
pVehicle->ASWStartEngine();
m_bDriving = true;
m_bIsInVehicle = true;
m_hASWVehicle = pEnt;
AddEffects( EF_NODRAW );
SetCollisionGroup( COLLISION_GROUP_IN_VEHICLE );
m_takedamage = DAMAGE_NO;
}
void CASW_Marine::StopDriving(IASW_Vehicle* pVehicle)
{
if (!pVehicle || !IsDriving())
return;
CBaseEntity* pEnt = pVehicle->GetEntity();
if (!pEnt)
return;
// try and place the marine outside the vehicle
Vector v = pEnt->GetAbsOrigin() - UTIL_YawToVector(pEnt->GetAbsAngles().y) * 50;
trace_t tr;
Ray_t ray;
ray.Init( v + Vector(0,0,1), v, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs() );
UTIL_TraceRay( ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
if ( tr.fraction < 1.0 )
return; // blocked
SetAbsOrigin(v);
// todo: get weapon out again?
pVehicle->ASWStopEngine();
pVehicle->ASWSetDriver(NULL);
m_bDriving = false;
m_bIsInVehicle = false;
m_hASWVehicle = NULL;
RemoveEffects( EF_NODRAW );
SetCollisionGroup( COLLISION_GROUP_PLAYER );
m_takedamage = DAMAGE_YES;
}
int CASW_Marine::UpdateTransmitState()
{
// always call ShouldTransmit() for maines
//return SetTransmitState( FL_EDICT_FULLCHECK );
return SetTransmitState( FL_EDICT_ALWAYS );
}
int CASW_Marine::ShouldTransmit( const CCheckTransmitInfo *pInfo )
{
// asw temp
return FL_EDICT_ALWAYS;
// always transmit if we're inhabited by the target client
if ( GetCommander() && IsInhabited() && pInfo->m_pClientEnt == GetCommander()->edict() )
{
return FL_EDICT_ALWAYS;
}
return BaseClass::ShouldTransmit( pInfo );
}
void CASW_Marine::PhysicsShove()
{
Vector forward, up, right;
AngleVectors( EyeAngles(), &forward, &right, &up );
trace_t tr;
// Search for objects in a sphere (tests for entities that are not solid, yet still useable)
Vector searchCenter = WorldSpaceCenter();
UTIL_TraceLine( searchCenter, searchCenter + forward * 96.0f, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
if (!tr.m_pEnt)
{
UTIL_TraceLine( GetAbsOrigin() + Vector(0,0,25), searchCenter + forward * 96.0f, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
}
//UTIL_AddDebugLine(searchCenter, searchCenter + forward * 96.0f, false, false);
// try the hit entity if there is one, or the ground entity if there isn't.
CBaseEntity *entity = tr.m_pEnt;
if (entity && entity->VPhysicsGetObject() )
{
IPhysicsObject *pObj = entity->VPhysicsGetObject();
Vector vPushAway = (entity->WorldSpaceCenter() - WorldSpaceCenter());
vPushAway.z = 0;
float flDist = VectorNormalize( vPushAway );
flDist = MAX( flDist, 1 );
float flForce = MarineSkills()->GetSkillBasedValueByMarine(this, ASW_MARINE_SKILL_MELEE, ASW_MARINE_SUBSKILL_MELEE_FORCE);
//flForce /= flDist;
flForce = MIN( flForce, asw_marine_melee_max_force.GetFloat() );
if (asw_debug_marine_damage.GetBool())
{
Msg(" Kicking with force %f\n", flForce);
}
pObj->ApplyForceOffset( vPushAway * flForce, WorldSpaceCenter() );
}
}
CASW_Marine_Resource* CASW_Marine::GetMarineResource() const
{
return dynamic_cast<CASW_Marine_Resource*>(m_MarineResource.Get());
}
void CASW_Marine::Suicide()
{
if (GetFlags() & FL_FROZEN) // don't allow this if the marine is frozen
return;
m_iHealth = 1;
if (IsInfested())
{
CTakeDamageInfo info(this, this, Vector(0,0,0), GetAbsOrigin(), 100,
DMG_INFEST);
TakeDamage(info);
}
else
{
CTakeDamageInfo info(this, this, Vector(0,0,0), GetAbsOrigin(), 100,
DMG_NEVERGIB);
TakeDamage(info);
}
SetThink(&CBaseEntity::SUB_Remove);
SetNextThink(gpGlobals->curtime + 2.0f);
}
bool CASW_Marine::BecomeRagdollOnClient( const Vector &force )
{
if ( !CanBecomeRagdoll() )
return false;
// Become server-side ragdoll if we're flagged to do it
//if ( m_spawnflags & SF_ANTLIONGUARD_SERVERSIDE_RAGDOLL )
if (asw_marine_server_ragdoll.GetBool())
{
CTakeDamageInfo info;
// Fake the info
info.SetDamageType( DMG_GENERIC );
info.SetDamageForce( force );
info.SetDamagePosition( WorldSpaceCenter() );
IPhysicsObject *pPhysics = VPhysicsGetObject();
if ( pPhysics )
{
VPhysicsDestroyObject();
}
CBaseEntity *pRagdoll = CreateServerRagdoll( this, m_nForceBone, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true );
FixupBurningServerRagdoll( pRagdoll );
PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS );
//CBaseEntity *pRagdoll = CreateServerRagdoll( this, 0, info, COLLISION_GROUP_NONE );
// Transfer our name to the new ragdoll
pRagdoll->SetName( GetEntityName() );
//pRagdoll->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
// Get rid of our old body
//UTIL_Remove(this);
RemoveDeferred();
return true;
}
return BaseClass::BecomeRagdollOnClient( force );
}
void CASW_Marine::SelectModelFromProfile()
{
CASW_Marine_Profile *pProfile = GetMarineProfile();
if (pProfile)
{
SetModelName(MAKE_STRING(pProfile->m_ModelName));
m_nSkin = pProfile->m_SkinNum;
//Msg("%s Setting skin number to %d\n", pProfile->m_ShortName, m_nSkin);
}
else
{
SetModelName( AllocPooledString( ASW_DEFAULT_MARINE_MODEL ) );
//Msg("Warning (SelectModelFromProfile) couldn't get model from profile as profile doesn't exist yet\n");
}
}
void CASW_Marine::SetModelFromProfile()
{
CASW_Marine_Profile *pProfile = GetMarineProfile();
if (pProfile)
{
SetModelName(MAKE_STRING(pProfile->m_ModelName));
SetModel(pProfile->m_ModelName);
m_nSkin = pProfile->m_SkinNum;
//set the backpack bodygroup
SetBodygroup ( 1, m_nSkin );
//Msg("%s Setting skin number to %d\n", pProfile->m_ShortName, m_nSkin);
}
else
{
SetModelName( AllocPooledString( ASW_DEFAULT_MARINE_MODEL ) );
SetModel(ASW_DEFAULT_MARINE_MODEL);
Msg("Warning (SetModelFromProfile) couldn't get model from profile as profile doesn't exist yet\n");
}
}
void CASW_Marine::SetKnockedOut(bool bKnockedOut)
{
if (m_bKnockedOut == bKnockedOut)
return;
m_bKnockedOut = bKnockedOut;
if (m_bKnockedOut) // make the marine fall over
{
FlashlightTurnOff();
InvalidateBoneCache();
AddSolidFlags( FSOLID_NOT_SOLID );
CTakeDamageInfo info;
info.SetDamageType( DMG_GENERIC );
info.SetDamageForce( vec3_origin );
info.SetDamagePosition( WorldSpaceCenter() );
m_hKnockedOutRagdoll = (CRagdollProp*) CreateServerRagdoll( this, 0, info, COLLISION_GROUP_NONE );
if ( GetRagdollProp() )
{
GetRagdollProp()->DisableAutoFade();
GetRagdollProp()->SetThink( NULL );
GetRagdollProp()->SetUnragdoll( this );
}
AddEffects( EF_NODRAW );
AddFlag( FL_FROZEN );
Msg("%s has been knocked unconcious!\n", GetMarineProfile() ? GetMarineProfile()->m_ShortName : "UnknownMarine");
}
else // marine is already knocked out, let's make him get up again
{
Assert(IsEffectActive(EF_NODRAW));
Assert(GetRagdollProp());
SetStopTime(gpGlobals->curtime + 2.0f); // make sure he can't move for a while
DoAnimationEvent(PLAYERANIMEVENT_GETUP); // animate him standing up
//Calcs the diff between ragdoll worldspace center and victim worldspace center, moves the victim by this diff.
//Sets the victim's angles to 0, ragdoll yaw, 0
QAngle newAngles( 0, GetRagdollProp()->GetAbsAngles()[YAW], 0 );
Vector centerDelta = GetRagdollProp()->WorldSpaceCenter() - WorldSpaceCenter();
centerDelta.z = 0; // don't put us in the floor
Vector newOrigin = GetAbsOrigin() + centerDelta;
SetAbsOrigin( newOrigin );
SetAbsAngles( newAngles );
//GetRagdollProp()->AddEffects( EF_NODRAW );
RemoveEffects( EF_NODRAW );
RemoveSolidFlags( FSOLID_NOT_SOLID );
if (HasFlashlight())
FlashlightTurnOn();
m_fUnfreezeTime = gpGlobals->curtime + 3.0f;
UTIL_Remove( GetRagdollProp() );
m_hKnockedOutRagdoll = NULL;
Msg("%s has got back up.\n", GetMarineProfile() ? GetMarineProfile()->m_ShortName : "UnknownMarine");
}
}
/*
void CASW_Marine::DoKickEffect()
{
//Msg("CASW_Marine::DoKickEffect\n");
bool bHasBayonet = false;
// bayonet disabled at this time
//GetActiveASWWeapon() && GetActiveASWWeapon()->SupportsBayonet() &&
//(MarineSkills()->GetSkillBasedValueByMarine(this, ASW_MARINE_SKILL_EDGED) > 0);
//CBaseEntity *pHurt =
float flForce = MarineSkills()->GetSkillBasedValueByMarine(this, ASW_MARINE_SKILL_MELEE, ASW_MARINE_SUBSKILL_MELEE_FORCE);
// add a bit of randomness
// flForce *= random->RandomFloat(0.8f, 1.2f);
int iDamage = 1;
if (bHasBayonet)
{
// bayonet disabled at this time
//int iDamage = MarineSkills()->GetSkillBasedValueByMarine(this, ASW_MARINE_SKILL_EDGED);
}
else
{
iDamage = MarineSkills()->GetSkillBasedValueByMarine(this, ASW_MARINE_SKILL_MELEE, ASW_MARINE_SUBSKILL_MELEE_DMG);
}
const CBaseEntity * ent = NULL;
if ( g_pGameRules->IsMultiplayer() )
{
// temp remove suppress host
ent = te->GetSuppressHost();
te->SetSuppressHost( NULL );
}
CheckTraceHullAttack( asw_marine_melee_distance.GetFloat(), -Vector(16,16,32), Vector(16,16,32), iDamage, DMG_CLUB, flForce, true );
if ( g_pGameRules->IsMultiplayer() )
{
te->SetSuppressHost( (CBaseEntity *) ent );
}
}
CBaseEntity *CASW_Marine::CheckTraceHullAttack( float flDist, const Vector &mins, const Vector &maxs, int iDamage, int iDmgType, float forceScale, bool bDamageAnyNPC )
{
// If only a length is given assume we want to trace in our facing direction
Vector forward;
AngleVectors( GetAbsAngles(), &forward );
Vector vStart = GetAbsOrigin();
// The ideal place to start the trace is in the center of the attacker's bounding box.
// however, we need to make sure there's enough clearance. Some of the smaller monsters aren't
// as big as the hull we try to trace with. (SJB)
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;
}
vStart.z += flVerticalOffset;
Vector vEnd = vStart + (forward * flDist );
// asw - make melee attacks trace below us too, so it's possible hard to hit things just below you on a slope
Vector low_mins = mins;
low_mins.z -= 30;
return CheckTraceHullAttack( vStart, vEnd, low_mins, maxs, iDamage, iDmgType, forceScale, bDamageAnyNPC );
}
// asw note: same as CBaseCombatCharacter version, but we use our custom melee trace filter so the victim can bleed and we can kick our own grenades
CBaseEntity *CASW_Marine::CheckTraceHullAttack( const Vector &vStart, const Vector &vEnd, const Vector &mins, const Vector &maxs, int iDamage, int iDmgType, float flForceScale, bool bDamageAnyNPC )
{
// Handy debuging tool to visualize HullAttack trace
if ( ai_show_hull_attacks.GetBool() )
{
float length = (vEnd - vStart ).Length();
Vector direction = (vEnd - vStart );
VectorNormalize( direction );
Vector hullMaxs = maxs;
hullMaxs.x = length + hullMaxs.x;
NDebugOverlay::BoxDirection(vStart, mins, hullMaxs, direction, 100,255,255,20,1.0);
NDebugOverlay::BoxDirection(vStart, mins, maxs, direction, 255,0,0,20,1.0);
}
CTakeDamageInfo dmgInfo( this, this, iDamage, iDmgType );
CASW_Trace_Filter_Melee traceFilter( this, COLLISION_GROUP_NONE, &dmgInfo, flForceScale, bDamageAnyNPC );
Ray_t ray;
ray.Init( vStart, vEnd, mins, maxs );
trace_t tr;
enginetrace->TraceRay( ray, MASK_SHOT_HULL, &traceFilter, &tr );
CBaseEntity *pEntity = traceFilter.m_pHit;
// do an impact effect for kicking some things
if (traceFilter.m_hBestHit.Get())
{
trace_t tr;
UTIL_TraceLine(WorldSpaceCenter(), traceFilter.m_hBestHit->WorldSpaceCenter(), // check center to center
MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
if (tr.DidHit())
{
CASW_Door *pDoor = dynamic_cast<CASW_Door*>(tr.m_pEnt); // doors make their own bash sounds, so skip an impact trace vs them
if (!pDoor && !traceFilter.m_hBestHit->IsNPC())
UTIL_ImpactTrace( &tr, iDmgType );
}
}
else // didn't hit anything, just do a general trace
{
Vector forward, right, up, v;
v = GetAbsOrigin();
QAngle ang = GetAbsAngles();
AngleVectors( ang, &forward, &right, &up );
v = v + up * 45;
Vector vecKickSrc = v
- forward * 1
+ right * 1;
trace_t tr;
UTIL_TraceLine(vecKickSrc, vecKickSrc + forward * 50,
MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
if (tr.DidHit())
{
CASW_Door *pDoor = dynamic_cast<CASW_Door*>(tr.m_pEnt); // doors make their own bash sounds, so skip an impact trace vs them
if (!pDoor && !tr.m_pEnt->IsNPC())
UTIL_ImpactTrace( &tr, iDmgType );
}
}
return pEntity;
}*/
// marines always move efficiently`
void CASW_Marine::UpdateEfficiency( bool bInPVS )
{
// Sleeping NPCs always dormant
if ( GetSleepState() != AISS_AWAKE )
{
SetEfficiency( AIE_DORMANT );
return;
}
SetEfficiency( AIE_NORMAL );
SetMoveEfficiency( AIME_NORMAL );
}
float CASW_Marine::GetIdealAccel( ) const
{
return GetIdealSpeed() * asw_marine_ai_acceleration.GetFloat();
}
float CASW_Marine::MaxYawSpeed( void )
{
if ( GetEnemy() )
return 45.0f;
if ( m_vecFacingPointFromServer.Get() != vec3_origin || m_hUsingEntity.Get() )
return 24.0f;
return 8.0f;
}
#define GROUNDTURRET_VIEWCONE 60.0f
#define GROUNDTURRET_BEAM_SPRITE "materials/effects/bluelaser2.vmt"
void CASW_Marine::Scan()
{
if (IsInhabited() || GetASWOrders() != ASW_ORDER_HOLD_POSITION || !asw_marine_scan_beams.GetBool())
return;
if( gpGlobals->curtime >= m_flTimeNextScanPing )
{
m_flTimeNextScanPing = gpGlobals->curtime + 1.0f;
}
QAngle scanAngle;
Vector forward;
Vector vecEye = GetAbsOrigin();// + m_vecLightOffset;
// Draw the outer extents
scanAngle = GetAbsAngles();
scanAngle.y += (GROUNDTURRET_VIEWCONE / 2.0f);
AngleVectors( scanAngle, &forward, NULL, NULL );
ProjectBeam( vecEye, forward, 1, 30, 0.1 );
scanAngle = GetAbsAngles();
scanAngle.y -= (GROUNDTURRET_VIEWCONE / 2.0f);
AngleVectors( scanAngle, &forward, NULL, NULL );
ProjectBeam( vecEye, forward, 1, 30, 0.1 );
// Draw a sweeping beam
scanAngle = GetAbsAngles();
scanAngle.y += (GROUNDTURRET_VIEWCONE / 2.0f) * sin( gpGlobals->curtime * 3.0f );
AngleVectors( scanAngle, &forward, NULL, NULL );
ProjectBeam( vecEye, forward, 1, 30, 0.3 );
}
void CASW_Marine::ProjectBeam( const Vector &vecStart, const Vector &vecDir, int width, int brightness, float duration )
{
CBeam *pBeam;
pBeam = CBeam::BeamCreate( GROUNDTURRET_BEAM_SPRITE, width );
if ( !pBeam )
return;
trace_t tr;
AI_TraceLine( vecStart, vecStart + vecDir * 768.0f, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
pBeam->SetStartPos( tr.endpos );
pBeam->SetEndPos( tr.startpos );
pBeam->SetWidth( width );
pBeam->SetEndWidth( 0.1 );
pBeam->SetFadeLength( 16 );
pBeam->SetBrightness( brightness );
pBeam->SetColor( 0, 145+random->RandomInt( -16, 16 ), 255 );
pBeam->RelinkBeam();
pBeam->LiveForTime( duration );
}
void CASW_Marine::ASW_Ignite( float flFlameLifetime, float flSize, CBaseEntity *pAttacker, CBaseEntity *pDamagingWeapon /*= NULL */ )
{
if (!ASWGameRules())
return;
// set flame life time by the game difficulty
int iDiff = ASWGameRules()->GetSkillLevel();
if (iDiff == 1)
flFlameLifetime *= asw_marine_burn_time_easy.GetFloat();
else if (iDiff == 2)
flFlameLifetime *= asw_marine_burn_time_normal.GetFloat();
else if (iDiff == 3)
flFlameLifetime *= asw_marine_burn_time_hard.GetFloat();
else if (iDiff == 4)
flFlameLifetime *= asw_marine_burn_time_insane.GetFloat();
if ( m_flFirstBurnTime == 0 )
m_flFirstBurnTime = gpGlobals->curtime;
// if this is an env_fire trying to burn us, ignore the grace period that the AllowedToIgnite function does
// we want env_fires to always ignite the marine immediately so they can be used as dangerous blockers in levels
CFire *pFire = dynamic_cast<CFire*>(pAttacker);
if ( AllowedToIgnite() || pFire )
{
if( IsOnFire() )
return;
// scream about being on fire
GetMarineSpeech()->PersonalChatter(CHATTER_ON_FIRE);
AddFlag( FL_ONFIRE );
m_bOnFire = true;
if ( ASWBurning() )
{
ASWBurning()->BurnEntity(this, pAttacker, flFlameLifetime, 0.4f, 10.0f * 0.4f, pDamagingWeapon ); // 10 dps, applied every 0.4 seconds
}
IGameEvent * event = gameeventmanager->CreateEvent( "marine_ignited" );
if ( event )
{
event->SetInt( "entindex", entindex() );
gameeventmanager->FireEvent( event );
}
m_OnIgnite.FireOutput( this, this );
}
m_flLastBurnTime = gpGlobals->curtime;
}
void CASW_Marine::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
{
return; // use ASW_Ignite instead;
}
void CASW_Marine::Extinguish()
{
if ( m_bOnFire )
{
IGameEvent * event = gameeventmanager->CreateEvent( "marine_extinguished" );
if ( event )
{
event->SetInt( "entindex", entindex() );
gameeventmanager->FireEvent( event );
}
}
m_bOnFire = false;
if ( ASWBurning() )
{
ASWBurning()->Extinguish(this);
}
RemoveFlag( FL_ONFIRE );
}
bool CASW_Marine::AllowedToIgnite( void )
{
if ( m_iJumpJetting.Get() != 0 )
return false;
if ( m_flFirstBurnTime > 0 && (gpGlobals->curtime - m_flFirstBurnTime) >= asw_marine_time_until_ignite.GetFloat() )
return true;
// don't ignite, but play a flesh burn sound if we aren't on fire already
if ( !m_bOnFire && (gpGlobals->curtime - m_flLastBurnSoundTime) > 1.0f )
{
CASW_Player *player = GetCommander();
if ( player )
{
CSingleUserRecipientFilter localfilter( player );
localfilter.MakeReliable();
CBaseEntity::EmitSound( localfilter, entindex(), "ASW.MarineBurnPain_NoIgnite" );
m_flLastBurnSoundTime = gpGlobals->curtime;
}
}
return false;
}
int CASW_Marine::DrawDebugTextOverlays()
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
char buffer[256];
Q_snprintf(buffer, sizeof(buffer), "Using: %d (%s)\n",
m_hUsingEntity.Get(), m_hUsingEntity.Get() ? m_hUsingEntity->GetClassname() : " ");
NDebugOverlay::EntityText(entindex(),text_offset,buffer,0);
text_offset++;
if (GetASWOrders() == ASW_ORDER_HOLD_POSITION)
Q_snprintf(buffer, sizeof(buffer), "ASWOrders: ASW_ORDER_HOLD_POSITION\n");
else if (GetASWOrders() == ASW_ORDER_FOLLOW)
Q_snprintf(buffer, sizeof(buffer), "ASWOrders: ASW_ORDER_FOLLOW\n");
else if (GetASWOrders() == ASW_ORDER_MOVE_TO)
Q_snprintf(buffer, sizeof(buffer), "ASWOrders: ASW_ORDER_MOVE_TO\n");
else if (GetASWOrders() == ASW_ORDER_USE_OFFHAND_ITEM)
Q_snprintf(buffer, sizeof(buffer), "ASWOrders: ASW_ORDER_USE_OFFHAND_ITEM\n");
else
Q_snprintf(buffer, sizeof(buffer), "ASWOrders: Unknown\n");
NDebugOverlay::EntityText(entindex(),text_offset,buffer,0);
text_offset++;
Q_snprintf(buffer, sizeof(buffer), "FF scale: %f\n",m_fFriendlyFireAbsorptionTime);
NDebugOverlay::EntityText(entindex(),text_offset,buffer,0);
text_offset++;
}
return text_offset;
}
float CASW_Marine::GetReceivedDamageScale( CBaseEntity *pAttacker )
{
float flScale = 1;
// if we've been shot by another marine...
if (pAttacker && pAttacker->Classify() == CLASS_ASW_MARINE)
{
CASW_Marine *pMarine = CASW_Marine::AsMarine( pAttacker );
if (pMarine)
{
if (ASWGameRules() && ASWGameRules()->IsHardcoreMode())
{
// full damage in hardcore mode
flScale = 1;
}
else if (asw_marine_ff.GetInt() == 0) // FF Guard
{
flScale = 0.01f;
pMarine->ActivateFriendlyFireGuard(this);
}
else if (asw_marine_ff.GetInt() == 1) // normal
{
// allow friendly fire through based on difficulty level
int diff = ASWGameRules()->GetMissionDifficulty() - 5;
flScale = (asw_marine_ff_dmg_base.GetFloat() + asw_marine_ff_dmg_step.GetFloat() * diff);
}
else // full
{
// allow max friendly fire damage through, as though on mission difficulty +5
flScale = (asw_marine_ff_dmg_base.GetFloat() + asw_marine_ff_dmg_step.GetFloat() * 5.0f);
}
}
}
return flScale * BaseClass::GetReceivedDamageScale(pAttacker);
}
void CASW_Marine::ActivateFriendlyFireGuard(CASW_Marine *pVictim)
{
// stops the marine from being able to fire
// todo: make wepaons check this time isn't 0, to prevent firing
m_fFFGuardTime = gpGlobals->curtime + asw_marine_ff_guard_time.GetFloat();
// todo: play a sound warning the player of FF
}
int CASW_Marine::GetAlienMeleeFlinch()
{
return MarineSkills()->GetSkillBasedValueByMarine(this, ASW_MARINE_SKILL_MELEE, ASW_MARINE_SUBSKILL_MELEE_FLINCH);
}
// POWERUPS!
void CASW_Marine::AddPowerup( int iType, float flExpireTime )
{
RemoveAllPowerups();
// if a powerup doesn't expire, we tell the current weapon that it has the powerup
// if we want powerups that don't expire and aren't tied to a weapon's clip, we'll need to rethink this
if ( flExpireTime > gpGlobals->curtime )
{
m_bPowerupExpires = true;
m_flPowerupExpireTime = flExpireTime;
}
else
{
CASW_Weapon* pWeapon = GetActiveASWWeapon();
if ( !pWeapon )
return;
pWeapon->MakePoweredUp( true );
}
m_iPowerupType = iType;
}
bool CASW_Marine::HasPowerup( int iType )
{
if ( m_iPowerupType == iType )
return true;
return false;
}
void CASW_Marine::RemoveWeaponPowerup( CASW_Weapon* pWeapon )
{
if ( !pWeapon )
return;
if ( pWeapon->m_bPoweredUp )
{
m_bPowerupExpires = false;
pWeapon->MakePoweredUp( false );
m_iPowerupType = -1;
}
}
void CASW_Marine::RemoveAllPowerups( void )
{
m_bPowerupExpires = false;
m_iPowerupType = -1;
m_flPowerupExpireTime = -1;
for ( int i = 0; i < 3; i++ )
{
CASW_Weapon* pWeapon = GetASWWeapon(i);
if ( !pWeapon )
continue;
pWeapon->MakePoweredUp( false );
}
}
void CASW_Marine::UpdatePowerupDuration( void )
{
if ( m_iPowerupType >= 0 )
{
if ( m_bPowerupExpires && m_flPowerupExpireTime <= gpGlobals->curtime )
{
RemoveAllPowerups();
}
}
}
// test: always avoid..
void CASW_Marine::SetPlayerAvoidState()
{
m_bPlayerAvoidState = ShouldPlayerAvoid();
m_bPerformAvoidance = true;
}
void CASW_Marine::CheckAndRequestAmmo()
{
m_fLastAmmoCheckTime = gpGlobals->curtime;
bool bAllWeaponsOutOfAmmo = true;
CASW_Marine *pClosestAmmoBagSquadmate = NULL;
CASW_Marine *pActiveWpnAmmoBagSquadmate = NULL; // send high-priority message to player that can resupply active
for ( int iWeapon = 0; iWeapon < ASW_NUM_INVENTORY_SLOTS; iWeapon++ )
{
CASW_Weapon *pWeapon = GetASWWeapon(iWeapon);
if ( !pWeapon || !pWeapon->IsOffensiveWeapon() )
continue;
bool bWeaponHasAmmo = ( pWeapon->Clip1() > 0 || GetAmmoCount ( pWeapon->GetPrimaryAmmoType() ) > 0 );
bool bWeaponLowOnAmmo = ( pWeapon->Clip1() == 0 && GetAmmoCount ( pWeapon->GetPrimaryAmmoType() ) <= 1) ||
( GetAmmoCount ( pWeapon->GetPrimaryAmmoType() ) == 0 );
bool bActiveWeapon = ( GetActiveASWWeapon() == pWeapon );
if ( bWeaponHasAmmo )
{
bAllWeaponsOutOfAmmo = false;
}
// if we have some ammo, only request it if marine is player controlled, the weapon is active, and we're low on ammo
if ( bWeaponHasAmmo && ( !IsInhabited() || !bActiveWeapon || !bWeaponLowOnAmmo ) )
{
continue;
}
float fClosestAmmoBagDistSqr = FLT_MAX;
// find the closest marine who can resupply this weapon
CASW_Game_Resource *pGameResource = ASWGameResource();
for ( int i=0; i<pGameResource->GetMaxMarineResources(); i++ )
{
CASW_Marine_Resource *pMarineResource = pGameResource->GetMarineResource(i);
if ( !pMarineResource )
continue;
CASW_Marine *pSquadmate = pMarineResource->GetMarineEntity();
if ( !pSquadmate || ( pSquadmate == this ) )
continue;
if ( pSquadmate->CanGiveAmmoTo( this ) )
{
float fAmmoBagDistSqr = pSquadmate->GetAbsOrigin().DistToSqr( GetAbsOrigin() );
if ( fAmmoBagDistSqr < fClosestAmmoBagDistSqr )
{
fClosestAmmoBagDistSqr = fAmmoBagDistSqr;
pClosestAmmoBagSquadmate = pSquadmate; // need to save for later use
}
}
}
if ( pClosestAmmoBagSquadmate )
{
// if player controlled, we NEED ammo rather than simply wanting it
pClosestAmmoBagSquadmate->SetCondition( IsInhabited() ? COND_SQUADMATE_NEEDS_AMMO : COND_SQUADMATE_WANTS_AMMO );
if ( bActiveWeapon )
pActiveWpnAmmoBagSquadmate = pClosestAmmoBagSquadmate;
}
}
if ( pActiveWpnAmmoBagSquadmate )
{
// if anyone can resupply our active weapon, they get the higher priority NEED condition
pActiveWpnAmmoBagSquadmate->SetCondition( COND_SQUADMATE_NEEDS_AMMO );
}
else if ( pClosestAmmoBagSquadmate && bAllWeaponsOutOfAmmo )
{
// otherwise, if we're all out of ammo, send a higher priority NEED request
pClosestAmmoBagSquadmate->SetCondition( COND_SQUADMATE_NEEDS_AMMO );
}
}
bool CASW_Marine::IsOutOfAmmo()
{
for ( int iWeapon = 0; iWeapon < ASW_NUM_INVENTORY_SLOTS; iWeapon++ )
{
CASW_Weapon *pWeapon = GetASWWeapon(iWeapon);
if (pWeapon && pWeapon->IsOffensiveWeapon() && (pWeapon->Clip1() > 0 || GetAmmoCount(pWeapon->GetPrimaryAmmoType()) > 0))
{
return false;
}
}
return true;
}
void CASW_Marine::OnWeaponOutOfAmmo(bool bChatter)
{
if (bChatter && GetMarineSpeech())
{
GetMarineSpeech()->Chatter(CHATTER_NO_AMMO);
bEmoteAmmo = true;
CASW_Marine_Resource *pMR = GetMarineResource();
if ( pMR )
{
char szName[ 256 ];
pMR->GetDisplayName( szName, sizeof( szName ) );
UTIL_ClientPrintAll( ASW_HUD_PRINTTALKANDCONSOLE, "#asw_out_of_ammo", szName );
}
}
CheckAndRequestAmmo();
// check to see if completely out of ammo on all weapons
if ( !IsOutOfAmmo() )
return;
IGameEvent * event = gameeventmanager->CreateEvent( "marine_no_ammo" );
if ( event )
{
CASW_Player *pPlayer = GetCommander();
event->SetInt( "userid", ( pPlayer ? pPlayer->GetUserID() : 0 ) );
event->SetInt( "entindex", entindex() );
event->SetInt( "count", ( pPlayer ? UTIL_ASW_NumCommandedMarines( pPlayer ) : 0 ) );
gameeventmanager->FireEvent( event );
}
ASWFailAdvice()->OnMarineOutOfAmmo();
// if marine has no ammo in any offensive weapon, log the position for stats
if ( GetMarineResource() )
{
GetMarineResource()->m_vecOutOfAmmoSpot = GetAbsOrigin();
}
}
void CASW_Marine::PhysicsLandedOnGround( float fFallSpeed )
{
float fFallVel = fabs(fFallSpeed) * 1.17f; // add 17% onto the fall speed - this makes the fall speeds of AI roughly match up with the ones done by player movement
if ( GetGroundEntity() != NULL && GetHealth() > 0 && fFallVel >= PLAYER_FALL_PUNCH_THRESHOLD )
{
bool bAlive = true;
float fvol = 0.5;
if ( GetWaterLevel() > 0 )
{
// They landed in water.
}
else
{
// Scale it down if we landed on something that's floating...
if ( GetGroundEntity()->IsFloating() )
{
fFallVel -= PLAYER_LAND_ON_FLOATING_OBJECT;
}
// They hit the ground.
if ( fFallVel > PLAYER_MAX_SAFE_FALL_SPEED )
{
// If they hit the ground going this fast they may take damage (and die).
//bAlive = MoveHelper( )->PlayerFallingDamage();
#ifndef CLIENT_DLL
float fFallVelMod = fFallVel;
fFallVelMod -= PLAYER_MAX_SAFE_FALL_SPEED;
float flFallDamage = fFallVelMod * DAMAGE_FOR_FALL_SPEED;
//Msg("Marine fell with speed %f modded to %f damage is %f\n", fFallVel, fFallVelMod, flFallDamage);
if ( flFallDamage > 0 )
{
TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), flFallDamage, DMG_FALL ) );
CRecipientFilter filter;
filter.AddRecipientsByPAS( GetAbsOrigin() );
CBaseEntity::EmitSound( filter, entindex(), "Player.FallDamage" );
}
bAlive = GetHealth() > 0;
#endif
fvol = 1.0;
}
else if ( fFallVel > PLAYER_MAX_SAFE_FALL_SPEED / 2 )
{
fvol = 0.85;
}
else if ( fFallVel < PLAYER_MIN_BOUNCE_SPEED )
{
fvol = 0;
}
}
if ( fvol > 0.0 )
{
// asw todo?
// Play landing sound right away.
//player->m_flStepSoundTime = 400;
// Play step sound for current texture.
//PlayStepSound( mv->m_vecAbsOrigin, m_pSurfaceData, fvol, true );
}
}
}
float CASW_Marine::GetFFAbsorptionScale()
{
float fScale = 1.0f;
if ( asw_marine_ff_absorption.GetInt() == 1 ) // ramp damage up over time
{
fScale = m_fFriendlyFireAbsorptionTime * m_fFriendlyFireAbsorptionTime;
}
else if ( asw_marine_ff_absorption.GetInt() == 2 ) // ramp damage down over time
{
fScale = 1.0f - ( m_fFriendlyFireAbsorptionTime * m_fFriendlyFireAbsorptionTime );
}
fScale = 0.05f + 0.95f * fScale; // always do a minimum % damage
return fScale;
}
void CASW_Marine::Stumble( CBaseEntity *pSource, const Vector &vecStumbleDir, bool bShort )
{
if ( !pSource || GetForcedActionRequest() != 0 )
return;
if ( pSource->Classify() == CLASS_ASW_SHIELDBUG ) // don't stumble from shieldbugs, they do knockdowns instead
return;
if ( pSource->Classify() == CLASS_ASW_MARINE ) // don't stumble from friendly fire
return;
if ( gpGlobals->curtime < m_flNextStumbleTime )
return;
//vecStumbleDir.z = 0;
//vecStumbleDir.NormalizeInPlace();
QAngle staggerAngles;
VectorAngles( vecStumbleDir, staggerAngles );
float yawDelta = AngleNormalize( GetAbsAngles()[YAW] - staggerAngles[YAW] );
if ( yawDelta <= 45 && yawDelta >= -45 )
m_iForcedActionRequest = bShort ? FORCED_ACTION_STUMBLE_SHORT_FORWARD : FORCED_ACTION_STUMBLE_FORWARD;
else if ( yawDelta > 45 && yawDelta < 135 )
m_iForcedActionRequest = bShort ? FORCED_ACTION_STUMBLE_SHORT_RIGHT : FORCED_ACTION_STUMBLE_RIGHT;
else if ( yawDelta < -45 && yawDelta > -135 )
m_iForcedActionRequest = bShort ? FORCED_ACTION_STUMBLE_SHORT_LEFT : FORCED_ACTION_STUMBLE_LEFT;
else
m_iForcedActionRequest = bShort ? FORCED_ACTION_STUMBLE_SHORT_BACKWARD : FORCED_ACTION_STUMBLE_BACKWARD;
SetNextStumbleTime( gpGlobals->curtime + asw_stumble_interval.GetFloat() );
}
void CASW_Marine::Knockdown( CBaseEntity *pSource, const Vector &vecImpulse, bool bForce )
{
if ( !pSource )
return;
// already knocked down
if ( GetForcedActionRequest() >= FORCED_ACTION_KNOCKDOWN_FORWARD && GetForcedActionRequest() <= FORCED_ACTION_KNOCKDOWN_BACKWARD )
return;
if ( gpGlobals->curtime < m_flNextStumbleTime && !bForce )
return;
Vector vecKnockdownDir = vecImpulse.Normalized();
QAngle staggerAngles;
VectorAngles( vecKnockdownDir, staggerAngles );
float yawDelta = AngleNormalize( GetAbsAngles()[YAW] - staggerAngles[YAW] );
//Msg( "yawDelta = %f marine angles = %f staggerangles = %f\n", yawDelta, GetAbsAngles()[YAW], staggerAngles[YAW] );
if ( yawDelta <= 90 && yawDelta >= -90 )
m_iForcedActionRequest = FORCED_ACTION_KNOCKDOWN_FORWARD;
else
m_iForcedActionRequest = FORCED_ACTION_KNOCKDOWN_BACKWARD;
ApplyAbsVelocityImpulse( vecImpulse );
m_flKnockdownYaw = UTIL_VecToYaw( vecKnockdownDir );
SetNextStumbleTime( gpGlobals->curtime + asw_knockdown_interval.GetFloat() );
}
void CASW_Marine::ModifyOrAppendCriteria( AI_CriteriaSet& set )
{
BaseClass::ModifyOrAppendCriteria(set);
set.AppendCriteria( "who", GetResponseRulesName() );
}
const char * CASW_Marine::GetResponseRulesName()
{
// a little roundabout for now because we amateurishly
// have to store criteria values as strings (argh)
return AI_CriteriaSet::SymbolToStr(GetMarineProfile()->m_nResponseRulesName);
}
CASW_Marine * CASW_Marine::GetSquadLeader()
{
CASW_SquadFormation * RESTRICT psquad = GetSquadFormation();
return ( psquad ? psquad->Leader() : NULL );
}
void CASW_Marine::OnWeaponFired( const CBaseEntity *pWeapon, int nShotsFired, bool bIsSecondary /*= false */ )
{
if( !pWeapon )
return;
// Fire weapon fired event for gamestats
CASW_GameStats.Event_MarineWeaponFired( pWeapon, this, nShotsFired, bIsSecondary );
}
ConVar asw_marine_debug_movement( "asw_marine_debug_movement", "0", FCVAR_CHEAT, "Debug overall marine movement direction" );
void CASW_Marine::AddPositionHistory()
{
const float flToleranceSqr = asw_movement_direction_tolerance.GetFloat() * asw_movement_direction_tolerance.GetFloat();
// check we don't have an entry for this spot already
for ( int i = 0; i < ASW_MARINE_HISTORY_POSITIONS; i++ )
{
if ( m_PositionHistory[ i ].flTime != 0.0f && m_PositionHistory[ i ].vecPosition.DistToSqr( GetAbsOrigin() ) < flToleranceSqr )
{
if ( asw_marine_debug_movement.GetBool() )
{
Msg( "too near pos history %d distsq %f\n", i, m_PositionHistory[ i ].vecPosition.DistToSqr( GetAbsOrigin() ) );
float flMoveYaw = GetOverallMovementDirection();
NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 10 ), flMoveYaw, 64, 16, 255, 255, 64, 0, true, asw_movement_direction_interval.GetFloat() );
Msg( "Moveyaw = %f\n", flMoveYaw );
}
return;
}
}
m_nPositionHistoryTail++;
if ( m_nPositionHistoryTail >= ASW_MARINE_HISTORY_POSITIONS )
{
m_nPositionHistoryTail = 0;
}
m_PositionHistory[ m_nPositionHistoryTail ].vecPosition = GetAbsOrigin();
m_PositionHistory[ m_nPositionHistoryTail ].flTime = gpGlobals->curtime;
if ( asw_marine_debug_movement.GetBool() )
{
Msg( "Stored positioned [%d] = %f %f %f\n", m_nPositionHistoryTail, VectorExpand( GetAbsOrigin() ) );
NDebugOverlay::Cross( GetAbsOrigin(), 10, 255, 255, 0, false, asw_movement_direction_interval.GetFloat() * 5.0f );
float flMoveYaw = GetOverallMovementDirection();
NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 10 ), flMoveYaw, 64, 16, 255, 255, 64, 0, true, asw_movement_direction_interval.GetFloat() );
Msg( "Moveyaw = %f\n", flMoveYaw );
}
}
float CASW_Marine::GetOverallMovementDirection()
{
// take average of position histories
int nCount = 0;
Vector vecPos = vec3_origin;
for ( int i = 0; i < ASW_MARINE_HISTORY_POSITIONS; i++ )
{
if ( m_PositionHistory[ i ].flTime != 0.0f )
{
vecPos += m_PositionHistory[ i ].vecPosition;
nCount++;
}
}
if ( nCount == 0 )
{
return 90.0f;
}
Vector vecSmoothedPosition = vecPos / (float) nCount;
if ( asw_marine_debug_movement.GetBool() )
{
Msg( "Found %d positions in history. total vec = %f %f %f\n", nCount, VectorExpand( vecPos ) );
NDebugOverlay::Line( WorldSpaceCenter(), vecSmoothedPosition, 255, 0, 0, false, 1.0f );
}
return UTIL_VecToYaw( ( GetAbsOrigin() - vecSmoothedPosition ).Normalized() );
}
bool CASW_Marine::TeleportStuckMarine()
{
// Aim in the direction of the last saved position
Vector vToPrev = m_PositionHistory[ m_nPositionHistoryTail ].vecPosition - GetAbsOrigin();
VectorNormalize( vToPrev );
// Add some upward push
vToPrev.z = 1.0f;
VectorNormalize( vToPrev );
Vector vOldPos = GetAbsOrigin();
bool bSuccess = UTIL_FindClosestPassableSpace( this, vToPrev, MASK_PLAYERSOLID, NULL, FL_AXIS_DIRECTION_NZ );
if ( bSuccess )
{
DevMsg( "Unstuck marine from (%.2f %.2f %.2f) to (%.2f %.2f %.2f).\n", vOldPos.x, vOldPos.y, vOldPos.z, GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
}
trace_t tr;
Ray_t ray;
ray.Init( GetAbsOrigin(), GetAbsOrigin(), CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs() );
UTIL_TraceRay( ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
if ( ( tr.contents & MASK_PLAYERSOLID ) && tr.m_pEnt )
{
// still stuck
return TeleportToFreeNode();
}
return bSuccess;
}
bool CASW_Marine::TeleportToFreeNode()
{
// now find the nearest clear info node
CAI_Node *pNode = NULL;
CAI_Node *pNearest = NULL;
float fNearestDist = -1;
for (int i=0;i<GetNavigator()->GetNetwork()->NumNodes();i++)
{
pNode = GetNavigator()->GetNetwork()->GetNode(i);
if (!pNode)
continue;
float dist = GetAbsOrigin().DistTo(pNode->GetOrigin());
if (dist < fNearestDist || fNearestDist == -1)
{
// check the spot is clear
Vector vecPos = pNode->GetOrigin();
trace_t tr;
UTIL_TraceHull( vecPos,
vecPos + Vector( 0, 0, 1 ),
CollisionProp()->OBBMins(),
CollisionProp()->OBBMaxs(),
MASK_PLAYERSOLID,
this,
COLLISION_GROUP_NONE,
&tr );
if( tr.fraction == 1.0 )
{
fNearestDist = dist;
pNearest = pNode;
}
}
}
// found a valid node, teleport there
if (pNearest)
{
Vector vecPos = pNearest->GetOrigin();
Teleport( &vecPos, NULL, &vec3_origin );
return true;
}
return false;
}
CBaseTrigger* CASW_Marine::IsInEscapeVolume()
{
for ( int i = 0; i < g_aEscapeObjectives.Count(); i++ )
{
CBaseTrigger *pTrigger = g_aEscapeObjectives[ i ]->GetTrigger();
if ( pTrigger && pTrigger->IsTouching( this ) )
{
return pTrigger;
}
}
return NULL;
}