You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4769 lines
142 KiB
4769 lines
142 KiB
//========= 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_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" ); |
|
extern ConVar asw_sentry_friendly_fire_scale; |
|
extern ConVar asw_marine_ff_absorption; |
|
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(); |
|
} |
|
} |
|
|
|
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 || iDiff == 5) |
|
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; |
|
|
|
float flBurnTime = ( asw_marine_ff_absorption.GetInt() > 0 ) ? asw_marine_time_until_ignite.GetFloat() : 0.2f; |
|
if ( m_flFirstBurnTime > 0 && (gpGlobals->curtime - m_flFirstBurnTime) >= flBurnTime ) |
|
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; |
|
}
|
|
|