3527 lines
99 KiB
C++
3527 lines
99 KiB
C++
#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 "ai_playerally.h"
|
|
#include "ai_pathfinder.h"
|
|
#include "ai_link.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_mission_manager.h"
|
|
#include "weapon_flaregun.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_weapon_ammo_bag_shared.h"
|
|
#include "asw_fx_shared.h"
|
|
#include "asw_parasite.h"
|
|
#include "shareddefs.h"
|
|
#include "datacache/imdlcache.h"
|
|
#include "asw_bloodhound.h"
|
|
#include "beam_shared.h"
|
|
#include "rope.h"
|
|
#include "shot_manipulator.h"
|
|
#include "asw_use_area.h"
|
|
#include "asw_computer_area.h"
|
|
#include "ai_network.h"
|
|
#include "ai_networkmanager.h"
|
|
#include "asw_util_shared.h"
|
|
#include "asw_ammo.h"
|
|
#include "asw_ammo_drop.h"
|
|
#include "asw_door_area.h"
|
|
#include "asw_door.h"
|
|
#include "smartptr.h"
|
|
#include "ai_waypoint.h"
|
|
#include "particle_parse.h"
|
|
#include "asw_alien_goo_shared.h"
|
|
#include "asw_prop_physics.h"
|
|
#include "asw_weapon_heal_gun_shared.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
// anim events
|
|
int AE_MARINE_KICK;
|
|
int AE_MARINE_UNFREEZE;
|
|
|
|
// activities
|
|
int ACT_MARINE_GETTING_UP;
|
|
int ACT_MARINE_LAYING_ON_FLOOR;
|
|
|
|
ConVar asw_marine_aim_error_max("asw_marine_aim_error_max", "20.0f", FCVAR_CHEAT, "Maximum firing error angle for AI marines with base accuracy skill\n");
|
|
ConVar asw_marine_aim_error_min("asw_marine_aim_error_min", "5.0f", FCVAR_CHEAT, "Minimum firing error angle for AI marines with base accuracy skill\n");
|
|
// todo: have this value vary based on marine skill/level/distance
|
|
ConVar asw_marine_aim_error_decay_multiplier("asw_marine_aim_error_decay_multiplier", "0.9f", FCVAR_CHEAT, "Value multiplied per turn to reduce aim error over time\n");
|
|
ConVar asw_blind_follow( "asw_blind_follow", "0", FCVAR_NONE, "Set to 1 to give marines short sight range while following (old school alien swarm style)" );
|
|
ConVar asw_debug_marine_aim( "asw_debug_marine_aim", "0", FCVAR_CHEAT, "Shows debug info on marine aiming" );
|
|
ConVar asw_debug_throw( "asw_debug_throw", "0", FCVAR_CHEAT, "Show node debug info on throw visibility checks" );
|
|
ConVar asw_debug_order_weld( "asw_debug_order_weld", "0", FCVAR_DEVELOPMENTONLY, "Debug lines for ordering marines to offhand weld a door" );
|
|
|
|
extern ConVar ai_lead_time;
|
|
|
|
#define ASW_MARINE_GOO_SCAN_TIME 0.5f
|
|
|
|
// offsets and yaws for following formation
|
|
|
|
const static Vector g_MarineFollowOffset[]=
|
|
{
|
|
Vector( -60, -70, 0 ),
|
|
Vector( -120, 0, 0 ),
|
|
Vector( -60, 70, 0 ),
|
|
Vector( -40, -80, 0 ),
|
|
Vector( -40, -80, 0 ),
|
|
Vector( -40, -80, 0 ),
|
|
};
|
|
|
|
const static float g_MarineFollowDirection[]=
|
|
{
|
|
-70,
|
|
180,
|
|
70,
|
|
90,
|
|
90,
|
|
90
|
|
};
|
|
|
|
bool CASW_Marine::CreateBehaviors()
|
|
{
|
|
//AddBehavior( &m_ActBusyBehavior );
|
|
//AddBehavior( &m_AssaultBehavior );
|
|
//AddBehavior( &m_StandoffBehavior );
|
|
|
|
return true;
|
|
}
|
|
|
|
// make the view cone
|
|
bool CASW_Marine::FInViewCone( const Vector &vecSpot )
|
|
{
|
|
Vector los = ( vecSpot - EyePosition() );
|
|
float flDist = los.NormalizeInPlace();
|
|
if ( flDist <= GetCloseCombatSightRange() ) // see 360 up close
|
|
return true;
|
|
|
|
if ( GetASWOrders() == ASW_ORDER_HOLD_POSITION )
|
|
{
|
|
// otherwise check if this enemy is within our holding cone
|
|
Vector holdingDir = vec3_origin;
|
|
QAngle holdingAng(0, m_fHoldingYaw, 0);
|
|
AngleVectors(holdingAng, &holdingDir);
|
|
|
|
float flDot = DotProduct( los, holdingDir );
|
|
|
|
return ( flDot > m_flFieldOfView );
|
|
}
|
|
else if ( GetASWOrders() == ASW_ORDER_FOLLOW )
|
|
{
|
|
Vector vecHead = HeadDirection2D();
|
|
float flDot = DotProduct( los, vecHead );
|
|
return ( flDot > m_flFieldOfView );
|
|
}
|
|
|
|
// see 360 otherwise
|
|
return true;
|
|
}
|
|
|
|
float CASW_Marine::GetFollowSightRange()
|
|
{
|
|
return GetSenses()->GetDistLook();
|
|
}
|
|
|
|
float CASW_Marine::GetCloseCombatSightRange()
|
|
{
|
|
if (ASWGameRules())
|
|
{
|
|
switch (ASWGameRules()->GetSkillLevel())
|
|
{
|
|
case 1: return ASW_CLOSE_COMBAT_SIGHT_RANGE_EASY; break;
|
|
case 2: return ASW_CLOSE_COMBAT_SIGHT_RANGE_NORMAL; break;
|
|
case 3: return ASW_CLOSE_COMBAT_SIGHT_RANGE_HARD; break;
|
|
case 4:
|
|
case 5:
|
|
default: return ASW_CLOSE_COMBAT_SIGHT_RANGE_INSANE; break;
|
|
}
|
|
}
|
|
return ASW_CLOSE_COMBAT_SIGHT_RANGE_INSANE;
|
|
}
|
|
|
|
bool CASW_Marine::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
|
|
{
|
|
if (!BaseClass::WeaponLOSCondition(ownerPos, targetPos, bSetConditions))
|
|
return false;
|
|
|
|
if (!GetSenses())
|
|
{
|
|
if (bSetConditions)
|
|
SetCondition(COND_NOT_FACING_ATTACK);
|
|
return false;
|
|
}
|
|
|
|
float flDist = targetPos.DistTo(ownerPos);
|
|
|
|
// too far away?
|
|
if (flDist > GetSenses()->GetDistLook())
|
|
{
|
|
if (bSetConditions)
|
|
SetCondition(COND_TOO_FAR_TO_ATTACK);
|
|
return false;
|
|
}
|
|
|
|
if ( flDist < GetCloseCombatSightRange() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Vector vBodyDir = BodyDirection2D( );
|
|
float flDot = DotProduct( targetPos - ownerPos, vBodyDir );
|
|
|
|
// in hold mode, we can shoot if it's in our cone, or if it's very close
|
|
if (GetASWOrders() == ASW_ORDER_HOLD_POSITION)
|
|
{
|
|
if ( flDot > ASW_HOLD_POSITION_FOV_DOT )
|
|
{
|
|
//if (bSetConditions)
|
|
//SetCondition(COND_CAN_RANGE_ATTACK1);
|
|
return true;
|
|
}
|
|
|
|
if (bSetConditions)
|
|
SetCondition(COND_NOT_FACING_ATTACK);
|
|
return false;
|
|
}
|
|
if ( GetASWOrders() == ASW_ORDER_FOLLOW )
|
|
{
|
|
if ( flDot < ASW_FOLLOW_MODE_FOV_DOT )
|
|
return false;
|
|
}
|
|
|
|
if ( flDist < GetFollowSightRange() ) //&& flDot > 0.5f )
|
|
{
|
|
//if (bSetConditions)
|
|
//SetCondition(COND_CAN_RANGE_ATTACK1);
|
|
return true;
|
|
}
|
|
|
|
if (bSetConditions)
|
|
SetCondition(COND_TOO_FAR_TO_ATTACK);
|
|
return false;
|
|
}
|
|
/*
|
|
int CASW_Marine::RangeAttack1Conditions ( float flDot, float flDist )
|
|
{
|
|
if (!GetSenses())
|
|
return COND_NOT_FACING_ATTACK;
|
|
|
|
// too far away?
|
|
if (flDist > GetSenses()->GetDistLook())
|
|
{
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
}
|
|
|
|
// in hold mode, we can shoot if it's in our cone, or if it's very close
|
|
if (GetASWOrders() == ASW_ORDER_HOLD_POSITION)
|
|
{
|
|
if (flDist < GetCloseCombatSightRange())
|
|
return COND_CAN_RANGE_ATTACK1;
|
|
|
|
if (flDot > 0.5f)
|
|
return COND_CAN_RANGE_ATTACK1;
|
|
else
|
|
return COND_NOT_FACING_ATTACK;
|
|
}
|
|
|
|
if (flDist < GetFollowSightRange())
|
|
{
|
|
Msg("%f in follow mode, alien is within follow range (r=%f fw=%f\n", gpGlobals->curtime, flDist, GetFollowSightRange());
|
|
return COND_CAN_RANGE_ATTACK1;
|
|
}
|
|
Msg("%f in follow mode, alien IS NOT in follow range (r=%f fw=%f\n", gpGlobals->curtime, flDist, GetFollowSightRange());
|
|
|
|
// must mean we're in follow mode and it's too far away
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
}
|
|
*/
|
|
|
|
// ========== ASW Schedule Stuff =========
|
|
int CASW_Marine::SelectSchedule()
|
|
{
|
|
if ( (HasCondition(COND_ENEMY_DEAD) || HasCondition(COND_ENEMY_OCCLUDED) || HasCondition(COND_WEAPON_SIGHT_OCCLUDED))
|
|
&& m_fOverkillShootTime > gpGlobals->curtime)
|
|
{
|
|
return SCHED_ASW_OVERKILL_SHOOT;
|
|
}
|
|
|
|
if (m_bWaitingToRappel && !m_bOnGround)
|
|
{
|
|
return SCHED_ASW_RAPPEL;
|
|
}
|
|
|
|
if (m_bPreventMovement)
|
|
return SCHED_ASW_HOLD_POSITION;
|
|
|
|
// if we acquire a new enemy, create our aim error offset
|
|
if (HasCondition(COND_NEW_ENEMY))
|
|
SetNewAimError(GetEnemy());
|
|
|
|
ClearCondition( COND_ASW_NEW_ORDERS );
|
|
|
|
if ( HasCondition( COND_PATH_BLOCKED_BY_PHYSICS_PROP ) || HasCondition( COND_COMPLETELY_OUT_OF_AMMO ) )
|
|
{
|
|
int iMeleeSchedule = SelectMeleeSchedule();
|
|
if( iMeleeSchedule != -1 )
|
|
return iMeleeSchedule;
|
|
}
|
|
|
|
int iOffhandSchedule = SelectOffhandItemSchedule();
|
|
if ( iOffhandSchedule != -1 )
|
|
return iOffhandSchedule;
|
|
|
|
int iHackingSchedule = SelectHackingSchedule();
|
|
if ( iHackingSchedule != -1 )
|
|
return iHackingSchedule;
|
|
|
|
if ( m_hUsingEntity.Get() )
|
|
return SCHED_ASW_USING_OVER_TIME;
|
|
|
|
int iHealSchedule = SelectHealSchedule();
|
|
if( iHealSchedule != -1)
|
|
return iHealSchedule;
|
|
|
|
if ( HasCondition( COND_SQUADMATE_NEEDS_AMMO ) )
|
|
{
|
|
int iResupplySchedule = SelectGiveAmmoSchedule();
|
|
if ( iResupplySchedule != -1 )
|
|
return iResupplySchedule;
|
|
}
|
|
|
|
int iTakeAmmoSchedule = SelectTakeAmmoSchedule();
|
|
if ( iTakeAmmoSchedule != -1 )
|
|
return iTakeAmmoSchedule;
|
|
|
|
if ( HasCondition ( COND_SQUADMATE_WANTS_AMMO ) )
|
|
{
|
|
int iResupplySchedule = SelectGiveAmmoSchedule();
|
|
if ( iResupplySchedule != -1 )
|
|
return iResupplySchedule;
|
|
}
|
|
|
|
// === following ===
|
|
if ( GetASWOrders() == ASW_ORDER_FOLLOW )
|
|
{
|
|
int iFollowSchedule = SelectFollowSchedule();
|
|
if ( iFollowSchedule != -1 )
|
|
return iFollowSchedule;
|
|
}
|
|
|
|
if ( GetASWOrders() == ASW_ORDER_MOVE_TO )
|
|
{/*
|
|
if ( HasCondition(COND_CAN_RANGE_ATTACK1) )
|
|
{
|
|
//Msg("Marine's select schedule returning SCHED_RANGE_ATTACK1\n");
|
|
CBaseEntity* pEnemy = GetEnemy();
|
|
if (pEnemy) // don't shoot unless we actually have an enemy
|
|
return SCHED_RANGE_ATTACK1;
|
|
} */
|
|
|
|
// if we're already there, hold position
|
|
float dist = GetAbsOrigin().DistTo(m_vecMoveToOrderPos);
|
|
if ( dist > 30 )
|
|
{
|
|
return SCHED_ASW_MOVE_TO_ORDER_POS;
|
|
}
|
|
SetASWOrders(ASW_ORDER_HOLD_POSITION, m_fHoldingYaw);
|
|
}
|
|
|
|
// === holding position ===
|
|
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
|
|
{
|
|
//Msg("Marine's select schedule returning SCHED_RANGE_ATTACK1\n");
|
|
CBaseEntity* pEnemy = GetEnemy();
|
|
if (pEnemy) // don't shoot unless we actually have an enemy
|
|
return SCHED_RANGE_ATTACK1;
|
|
}
|
|
|
|
//Msg("Marine's select schedule returning SCHED_ASW_HOLD_POSITION\n");
|
|
return SCHED_ASW_HOLD_POSITION;
|
|
}
|
|
|
|
// translation of schedules
|
|
int CASW_Marine::TranslateSchedule( int scheduleType )
|
|
{
|
|
Assert(scheduleType != SCHED_STANDOFF);
|
|
Assert(scheduleType != SCHED_CHASE_ENEMY);
|
|
|
|
int result = CAI_PlayerAlly::TranslateSchedule(scheduleType);
|
|
if (result == SCHED_RANGE_ATTACK1)
|
|
result = SCHED_ASW_RANGE_ATTACK1;
|
|
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CASW_Marine::TaskFail( AI_TaskFailureCode_t code )
|
|
{
|
|
if ( IsCurSchedule( SCHED_ASW_MOVE_TO_ORDER_POS, false ) )
|
|
{
|
|
// if we failed to reach some ammo to pickup, then don't try to pick it up again for a while
|
|
if ( m_hTakeAmmo.Get() )
|
|
{
|
|
m_hIgnoreAmmo.AddToTail( m_hTakeAmmo );
|
|
m_hTakeAmmo = NULL;
|
|
m_flResetAmmoIgnoreListTime = gpGlobals->curtime + 15.0f;
|
|
}
|
|
|
|
if ( m_hTakeAmmoDrop.Get() )
|
|
{
|
|
m_hIgnoreAmmo.AddToTail( m_hTakeAmmoDrop );
|
|
m_hTakeAmmoDrop = NULL;
|
|
m_flResetAmmoIgnoreListTime = gpGlobals->curtime + 15.0f;
|
|
}
|
|
|
|
// change orders back to hold/follow
|
|
m_hAreaToUse = NULL;
|
|
if ( m_bWasFollowing )
|
|
{
|
|
SetASWOrders( ASW_ORDER_FOLLOW );
|
|
}
|
|
else
|
|
{
|
|
SetASWOrders( ASW_ORDER_HOLD_POSITION, GetHoldingYaw() );
|
|
}
|
|
}
|
|
else if ( IsCurSchedule( SCHED_MELEE_ATTACK_PROP1, false ) )
|
|
{
|
|
m_hPhysicsPropTarget = NULL;
|
|
}
|
|
|
|
BaseClass::TaskFail( code );
|
|
}
|
|
|
|
ConVar asw_marine_auto_hack( "asw_marine_auto_hack", "0", FCVAR_CHEAT, "If set to 1, marine will automatically hack nearby computers and button panels" );
|
|
|
|
#define AUTO_HACK_DIST 768.0f
|
|
|
|
int CASW_Marine::SelectHackingSchedule()
|
|
{
|
|
if ( !GetMarineProfile() || GetMarineProfile()->GetMarineClass() != MARINE_CLASS_TECH )
|
|
return -1;
|
|
|
|
if ( asw_marine_auto_hack.GetBool() )
|
|
{
|
|
CASW_Use_Area *pClosestArea = NULL;
|
|
float flClosestDist = FLT_MAX;
|
|
|
|
// check for a computer hack nearby
|
|
for ( int i = 0; i < IASW_Use_Area_List::AutoList().Count(); i++ )
|
|
{
|
|
CASW_Use_Area *pArea = static_cast< CASW_Use_Area* >( IASW_Use_Area_List::AutoList()[ i ] );
|
|
if ( pArea->Classify() == CLASS_ASW_BUTTON_PANEL )
|
|
{
|
|
CASW_Button_Area *pButton = assert_cast< CASW_Button_Area* >( pArea );
|
|
if ( !pButton->IsLocked() || !pButton->HasPower() )
|
|
continue;
|
|
|
|
float flDist = GetAbsOrigin().DistTo( pArea->WorldSpaceCenter() );
|
|
if ( flDist < flClosestDist && flDist < AUTO_HACK_DIST )
|
|
{
|
|
flClosestDist = flDist;
|
|
pClosestArea = pArea;
|
|
}
|
|
}
|
|
else if ( pArea->Classify() == CLASS_ASW_COMPUTER_AREA )
|
|
{
|
|
CASW_Computer_Area *pComputer = assert_cast< CASW_Computer_Area* >( pArea );
|
|
if ( pComputer->IsLocked() || ( pComputer->HasDownloadObjective() && pComputer->GetHackProgress() < 1.0f ) )
|
|
{
|
|
float flDist = GetAbsOrigin().DistTo( pArea->WorldSpaceCenter() );
|
|
if ( flDist < flClosestDist && flDist < AUTO_HACK_DIST )
|
|
{
|
|
flClosestDist = flDist;
|
|
pClosestArea = pArea;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pClosestArea )
|
|
{
|
|
m_hAreaToUse = pClosestArea;
|
|
}
|
|
}
|
|
|
|
if ( m_hAreaToUse.Get() )
|
|
{
|
|
if ( m_hAreaToUse->IsUsable( this ) )
|
|
{
|
|
// notify to kill the effect in a couple of seconds
|
|
CASW_Player *pPlayer = GetCommander();
|
|
if ( pPlayer && pPlayer->GetMarine() )
|
|
{
|
|
CSingleUserRecipientFilter user( pPlayer );
|
|
UserMessageBegin( user, "ASWOrderStopItemFX" );
|
|
WRITE_SHORT( entindex() );
|
|
WRITE_BOOL( false );
|
|
WRITE_BOOL( false );
|
|
MessageEnd();
|
|
}
|
|
|
|
if ( m_hUsingEntity.Get() == m_hAreaToUse.Get() )
|
|
{
|
|
return SCHED_ASW_USING_OVER_TIME;
|
|
}
|
|
else
|
|
{
|
|
return SCHED_ASW_USE_AREA;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_vecMoveToOrderPos = m_hAreaToUse->WorldSpaceCenter(); // TODO: hacking spot marked up in the level?
|
|
return SCHED_ASW_MOVE_TO_ORDER_POS;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void CASW_Marine::OrderHackArea( CASW_Use_Area *pArea )
|
|
{
|
|
if ( !pArea )
|
|
return;
|
|
|
|
bool bHackOrdered = false;
|
|
|
|
if ( pArea->Classify() == CLASS_ASW_BUTTON_PANEL )
|
|
{
|
|
CASW_Button_Area *pButton = assert_cast< CASW_Button_Area* >( pArea );
|
|
if ( !pButton->IsLocked() || !pButton->HasPower() )
|
|
return;
|
|
|
|
float flDist = GetAbsOrigin().DistTo( pArea->WorldSpaceCenter() );
|
|
if ( flDist < AUTO_HACK_DIST )
|
|
{
|
|
m_hAreaToUse = pArea;
|
|
bHackOrdered = true;
|
|
}
|
|
}
|
|
else if ( pArea->Classify() == CLASS_ASW_COMPUTER_AREA )
|
|
{
|
|
CASW_Computer_Area *pComputer = assert_cast< CASW_Computer_Area* >( pArea );
|
|
if ( pComputer->IsLocked() || ( pComputer->HasDownloadObjective() && pComputer->GetHackProgress() < 1.0f ) )
|
|
{
|
|
float flDist = GetAbsOrigin().DistTo( pArea->WorldSpaceCenter() );
|
|
if ( flDist < AUTO_HACK_DIST )
|
|
{
|
|
m_hAreaToUse = pArea;
|
|
bHackOrdered = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bHackOrdered )
|
|
{
|
|
CASW_Player *pPlayer = GetCommander();
|
|
if ( pPlayer && pPlayer->GetMarine() )
|
|
{
|
|
Vector vecOrigin = pArea->WorldSpaceCenter();
|
|
trace_t tr;
|
|
|
|
// Find where that leg hits the ground
|
|
UTIL_TraceLine(vecOrigin, vecOrigin + Vector(0, 0, -60),
|
|
MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
|
|
|
|
CSingleUserRecipientFilter user( pPlayer );
|
|
UserMessageBegin( user, "ASWOrderUseItemFX" );
|
|
WRITE_SHORT( entindex() );
|
|
WRITE_SHORT( ASW_USE_ORDER_HACK );
|
|
WRITE_SHORT( -1 );
|
|
WRITE_FLOAT( tr.endpos.x );
|
|
WRITE_FLOAT( tr.endpos.y );
|
|
WRITE_FLOAT( tr.endpos.z );
|
|
MessageEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
int CASW_Marine::SelectTakeAmmoSchedule()
|
|
{
|
|
if ( !m_hTakeAmmo.Get() && !m_hTakeAmmoDrop.Get() )
|
|
{
|
|
if ( gpGlobals->curtime < m_flNextAmmoScanTime )
|
|
return -1;
|
|
m_flNextAmmoScanTime = gpGlobals->curtime + 1.0f;
|
|
|
|
if ( m_flResetAmmoIgnoreListTime != 0.0f && gpGlobals->curtime > m_flResetAmmoIgnoreListTime )
|
|
{
|
|
m_hIgnoreAmmo.Purge();
|
|
m_flResetAmmoIgnoreListTime = 0.0f;
|
|
}
|
|
|
|
const float flAmmoScanRadiusSqr = 384.0f * 384.0f;
|
|
int nAmmoPickupCount = IAmmoPickupAutoList::AutoList().Count();
|
|
for ( int i = 0; i < nAmmoPickupCount; i++ )
|
|
{
|
|
CASW_Ammo *pAmmo = static_cast< CASW_Ammo* >( IAmmoPickupAutoList::AutoList()[ i ] );
|
|
EHANDLE hAmmo = pAmmo;
|
|
if ( m_hIgnoreAmmo.Find( hAmmo ) != m_hIgnoreAmmo.InvalidIndex() )
|
|
continue;
|
|
|
|
// check ammo is nearby
|
|
if ( GetAbsOrigin().DistToSqr( pAmmo->GetAbsOrigin() ) > flAmmoScanRadiusSqr )
|
|
continue;
|
|
|
|
// check we need this ammo
|
|
if ( !pAmmo->AllowedToPickup( this ) )
|
|
continue;
|
|
|
|
int nCurrentCount = GetAmmoCount( pAmmo->GetAmmoType() );
|
|
|
|
// check we don't have more ammo than a human player
|
|
bool bHumanNeedsAmmo = false;
|
|
for ( int m = 0; m < ASWGameResource()->GetMaxMarineResources(); m++ )
|
|
{
|
|
CASW_Marine_Resource *pMR = ASWGameResource()->GetMarineResource( m );
|
|
if ( !pMR || pMR == GetMarineResource() || !pMR->IsInhabited() || !pMR->GetMarineEntity() )
|
|
continue;
|
|
|
|
if ( !pAmmo->AllowedToPickup( pMR->GetMarineEntity() ) )
|
|
continue;
|
|
|
|
if ( pMR->GetMarineEntity()->GetAmmoCount( pAmmo->GetAmmoType() ) < nCurrentCount )
|
|
{
|
|
bHumanNeedsAmmo = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( bHumanNeedsAmmo )
|
|
continue;
|
|
|
|
m_hTakeAmmo = pAmmo;
|
|
m_hTakeAmmoDrop = NULL;
|
|
break;
|
|
}
|
|
|
|
// search for ammo satchel drops
|
|
if ( !m_hTakeAmmo )
|
|
{
|
|
int nAmmoDropCount = IAmmoDropAutoList::AutoList().Count();
|
|
for( int i = 0; i < nAmmoDropCount; i++ )
|
|
{
|
|
CASW_Ammo_Drop *pAmmoDrop = static_cast< CASW_Ammo_Drop* >( IAmmoDropAutoList::AutoList()[ i ] );
|
|
EHANDLE hAmmoDrop = pAmmoDrop;
|
|
EHANDLE hAmmo = pAmmoDrop;
|
|
if ( m_hIgnoreAmmo.Find( hAmmoDrop ) != m_hIgnoreAmmo.InvalidIndex() )
|
|
continue;
|
|
|
|
if( GetAbsOrigin().DistToSqr( pAmmoDrop->GetAbsOrigin() ) > flAmmoScanRadiusSqr )
|
|
continue;
|
|
|
|
if( !pAmmoDrop->AllowedToPickup( this ) )
|
|
continue;
|
|
|
|
bool bHumanNeedsAmmo = false;
|
|
for( int j = 0; j < ASWGameResource()->GetMaxMarineResources(); j++)
|
|
{
|
|
CASW_Marine_Resource *pMR = ASWGameResource()->GetMarineResource( j );
|
|
if( !pMR || pMR == GetMarineResource() || !pMR->IsInhabited() || !pMR->GetMarineEntity() )
|
|
continue;
|
|
|
|
if( !pAmmoDrop->AllowedToPickup( pMR->GetMarineEntity() ) )
|
|
continue;
|
|
|
|
if ( !pAmmoDrop->NeedsAmmoMoreThan( this, pMR->GetMarineEntity() ) )
|
|
{
|
|
bHumanNeedsAmmo = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( bHumanNeedsAmmo )
|
|
continue;
|
|
|
|
m_hTakeAmmoDrop = pAmmoDrop;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EHANDLE hAmmo = ( m_hTakeAmmo.Get() ? m_hTakeAmmo : ( m_hTakeAmmoDrop.Get() ? m_hTakeAmmoDrop : NULL ) );
|
|
|
|
if ( hAmmo != NULL )
|
|
{
|
|
SetPoseParameter( "move_x", 1.0f );
|
|
SetPoseParameter( "move_y", 0.0f );
|
|
|
|
// if we're in range of the deploy spot, then use the item
|
|
if ( hAmmo->GetAbsOrigin().DistTo( GetAbsOrigin() ) < ASW_MARINE_USE_RADIUS )
|
|
{
|
|
if( m_hTakeAmmo.Get() )
|
|
{
|
|
m_hTakeAmmo->ActivateUseIcon( this, ASW_USE_RELEASE_QUICK );
|
|
}
|
|
else
|
|
{
|
|
m_hTakeAmmoDrop->ActivateUseIcon( this, ASW_USE_RELEASE_QUICK );
|
|
}
|
|
|
|
m_hTakeAmmo = NULL;
|
|
m_hTakeAmmoDrop = NULL;
|
|
|
|
DoAnimationEvent( PLAYERANIMEVENT_PICKUP );
|
|
m_flNextAmmoScanTime = gpGlobals->curtime + 1.0f;
|
|
return SCHED_ASW_PICKUP_WAIT;
|
|
}
|
|
else
|
|
{
|
|
// move to the ammo
|
|
m_vecMoveToOrderPos = hAmmo->GetAbsOrigin();
|
|
return SCHED_ASW_MOVE_TO_ORDER_POS;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
bool CASW_Marine::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult )
|
|
{
|
|
SetPhysicsPropTarget( NULL );
|
|
if ( pMoveGoal->directTrace.pObstruction )
|
|
{
|
|
CASW_Prop_Physics *pPropPhysics = dynamic_cast< CASW_Prop_Physics *>( pMoveGoal->directTrace.pObstruction );
|
|
if ( pPropPhysics && pPropPhysics->m_takedamage == DAMAGE_YES && pPropPhysics->m_iHealth > 0 )
|
|
{
|
|
SetPhysicsPropTarget( pPropPhysics );
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CASW_Marine::GatherConditions()
|
|
{
|
|
BaseClass::GatherConditions();
|
|
|
|
ClearCondition( COND_PATH_BLOCKED_BY_PHYSICS_PROP );
|
|
ClearCondition( COND_PROP_DESTROYED );
|
|
ClearCondition( COND_COMPLETELY_OUT_OF_AMMO );
|
|
|
|
if( !GetCurSchedule() )
|
|
return;
|
|
|
|
bool bClearTargets = false;
|
|
|
|
if( GetCurSchedule()->GetId() == GetGlobalScheduleId( SCHED_MELEE_ATTACK_PROP1 ) )
|
|
{
|
|
if( !GetPhysicsPropTarget() || GetPhysicsPropTarget()->GetHealth() <= 0 )
|
|
{
|
|
SetCondition( COND_PROP_DESTROYED );
|
|
bClearTargets = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( GetPhysicsPropTarget() )
|
|
{
|
|
if( GetPhysicsPropTarget()->GetHealth() > 0 )
|
|
{
|
|
SetCondition( COND_PATH_BLOCKED_BY_PHYSICS_PROP );
|
|
}
|
|
else
|
|
{
|
|
bClearTargets = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bClearTargets )
|
|
{
|
|
if( GetEnemy() == GetPhysicsPropTarget() )
|
|
{
|
|
SetEnemy( NULL );
|
|
}
|
|
|
|
SetPhysicsPropTarget( NULL );
|
|
}
|
|
|
|
int iCurSchedule = GetCurSchedule()->GetId();
|
|
bool bRangedAttackSchedule = false;
|
|
switch( iCurSchedule )
|
|
{
|
|
case SCHED_RANGE_ATTACK1:
|
|
case SCHED_RANGE_ATTACK2:
|
|
case SCHED_SPECIAL_ATTACK1:
|
|
case SCHED_SPECIAL_ATTACK2:
|
|
{
|
|
bRangedAttackSchedule = true;
|
|
}
|
|
break;
|
|
};
|
|
|
|
if( IsOutOfAmmo() && bRangedAttackSchedule )
|
|
{
|
|
SetCondition( COND_COMPLETELY_OUT_OF_AMMO );
|
|
}
|
|
}
|
|
|
|
void CASW_Marine::BuildScheduleTestBits()
|
|
{
|
|
// ammo bag functionality needs to interrupt base class behaviors
|
|
SetCustomInterruptCondition(COND_SQUADMATE_WANTS_AMMO);
|
|
SetCustomInterruptCondition(COND_SQUADMATE_NEEDS_AMMO);
|
|
SetCustomInterruptCondition(COND_PATH_BLOCKED_BY_PHYSICS_PROP);
|
|
SetCustomInterruptCondition(COND_COMPLETELY_OUT_OF_AMMO);
|
|
}
|
|
|
|
int CASW_Marine::SelectGiveAmmoSchedule()
|
|
{
|
|
// iterate over all teammates, looking for most needy target for ammo
|
|
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* pMarine = pMarineResource->GetMarineEntity();
|
|
if ( !pMarine || ( pMarine == this ) )
|
|
continue;
|
|
|
|
// see if the current marine can use ammo I have
|
|
if ( CanGiveAmmoTo( pMarine ) )
|
|
{
|
|
// $TODO: find most appropriate give ammo target a la AmmoNeed_t
|
|
m_hGiveAmmoTarget = pMarine;
|
|
return SCHED_ASW_GIVE_AMMO;
|
|
}
|
|
}
|
|
|
|
m_hGiveAmmoTarget = NULL;
|
|
return -1;
|
|
}
|
|
|
|
bool CASW_Marine::CanHeal() const
|
|
{
|
|
for ( int iWeapon = 0; iWeapon < ASW_NUM_INVENTORY_SLOTS; iWeapon++ )
|
|
{
|
|
CASW_Weapon *pWeapon = GetASWWeapon( iWeapon );
|
|
if ( pWeapon && pWeapon->Classify() == CLASS_ASW_HEAL_GUN && pWeapon->HasAmmo() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#define MARINE_START_HEAL_THRESHOLD 0.65f
|
|
#define MARINE_STOP_HEAL_THRESHOLD 0.85f
|
|
|
|
int CASW_Marine::SelectHealSchedule()
|
|
{
|
|
// iterate over all teammates, looking for most needy target for health
|
|
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* pMarine = pMarineResource->GetMarineEntity();
|
|
if ( !pMarine )
|
|
continue;
|
|
|
|
// see if the current marine can use ammo I have
|
|
if ( CanHeal() && pMarine->GetHealth() < pMarine->GetMaxHealth() * MARINE_START_HEAL_THRESHOLD )
|
|
{
|
|
m_hHealTarget = pMarine;
|
|
return SCHED_ASW_HEAL_MARINE;
|
|
}
|
|
}
|
|
|
|
m_hHealTarget = NULL;
|
|
return -1;
|
|
}
|
|
|
|
void CASW_Marine::SetASWOrders(ASW_Orders NewOrders, float fHoldingYaw, const Vector *pOrderPos)
|
|
{
|
|
Vector vecPos = vec3_origin;
|
|
if (pOrderPos)
|
|
vecPos = *pOrderPos;
|
|
if (m_ASWOrders != NewOrders || m_vecMoveToOrderPos != vecPos
|
|
|| (fHoldingYaw != -1 && fHoldingYaw != m_fHoldingYaw))
|
|
{
|
|
SetCondition( COND_ASW_NEW_ORDERS );
|
|
if (NewOrders == ASW_ORDER_HOLD_POSITION)
|
|
{
|
|
SetDistLook( ASW_HOLD_POSITION_SIGHT_RANGE ); // make marines see further when holding position
|
|
m_flFieldOfView = ASW_HOLD_POSITION_FOV_DOT;
|
|
}
|
|
else
|
|
{
|
|
m_flFieldOfView = ASW_FOLLOW_MODE_FOV_DOT;
|
|
if ( !asw_blind_follow.GetBool() )
|
|
{
|
|
SetDistLook( ASW_FOLLOW_MODE_SIGHT_RANGE );
|
|
SetHeightLook( ASW_FOLLOW_MODE_SIGHT_HEIGHT );
|
|
}
|
|
else
|
|
{
|
|
SetDistLook( ASW_DUMB_FOLLOW_MODE_SIGHT_RANGE );
|
|
SetHeightLook( ASW_DUMB_FOLLOW_MODE_SIGHT_HEIGHT );
|
|
}
|
|
}
|
|
}
|
|
if ( NewOrders != ASW_ORDER_FOLLOW )
|
|
{
|
|
GetSquadFormation()->Remove(this, true);
|
|
m_hMarineFollowTarget = NULL;
|
|
}
|
|
m_bWasFollowing = ( NewOrders == ASW_ORDER_FOLLOW || ( NewOrders != ASW_ORDER_HOLD_POSITION && m_ASWOrders == ASW_ORDER_FOLLOW ) ); // keeps track of follow vs hold, so we can return to the right orders after completing the new ones
|
|
m_ASWOrders = NewOrders;
|
|
m_vecMoveToOrderPos = vecPos;
|
|
if (fHoldingYaw != -1)
|
|
m_fHoldingYaw = fHoldingYaw;
|
|
//Msg("Marine receives asw orders: %d\n", NewOrders);
|
|
}
|
|
|
|
void CASW_Marine::OrdersFromPlayer(CASW_Player *pPlayer, ASW_Orders NewOrders, CBaseEntity *pMarine, bool bChatter, float fHoldingYaw, Vector *pVecOrderPos)
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
// asw temp - AI ignore follow orders in SP
|
|
//if (gpGlobals->maxClients <= 1 && NewOrders == ASW_ORDER_FOLLOW)
|
|
//return;
|
|
|
|
if (fHoldingYaw == -1)
|
|
fHoldingYaw = GetAbsAngles()[YAW];
|
|
|
|
m_bDoneOrderChatter = false;
|
|
|
|
SetASWOrders(NewOrders, fHoldingYaw, pVecOrderPos);
|
|
if (NewOrders == ASW_ORDER_FOLLOW)
|
|
{
|
|
if ( pMarine && pMarine->Classify() == CLASS_ASW_MARINE )
|
|
{
|
|
// Assert( assert_cast<CASW_Marine*>( pMarine )->GetSquadFormation()->Leader() == pMarine );
|
|
CASW_Marine *pReallyMarine = assert_cast<CASW_Marine*>( pMarine );
|
|
if ( pReallyMarine != GetSquadLeader() )
|
|
GetSquadFormation()->ChangeLeader( pReallyMarine );
|
|
if ( !GetSquadFormation()->IsValid( GetSquadFormation()->Find(this) ) )
|
|
pReallyMarine->GetSquadFormation()->Add( this );
|
|
}
|
|
else
|
|
{
|
|
GetSquadFormation()->Remove( this, true );
|
|
}
|
|
//DoEmote(2); // make them smile on receiving orders
|
|
// 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 );
|
|
|
|
if (bChatter && !IsInfested())
|
|
GetMarineSpeech()->QueueChatter(CHATTER_USE, gpGlobals->curtime + 0.1f, gpGlobals->curtime + 5.0f);
|
|
}
|
|
else if (NewOrders == ASW_ORDER_HOLD_POSITION)
|
|
{
|
|
//m_fHoldingYaw = GetAbsAngles().y;
|
|
if (bChatter && !IsInfested() && ( GetFlags() & FL_ONGROUND ) && !m_hUsingEntity.Get())
|
|
GetMarineSpeech()->QueueChatter(CHATTER_HOLDING_POSITION, gpGlobals->curtime + 0.1f, gpGlobals->curtime + 5.0f);
|
|
}
|
|
else if (NewOrders == ASW_ORDER_MOVE_TO && pVecOrderPos != NULL)
|
|
{
|
|
//DoEmote(2); // make them smile on receiving 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
|
|
SetPoseParameter( "move_x", 1.0f );
|
|
SetPoseParameter( "move_y", 0.0f );
|
|
|
|
//Msg("Recieived orders to move to %f %f %f\n",
|
|
//pVecOrderPos[0], pVecOrderPos[1], pVecOrderPos[2]);
|
|
//m_vecMoveToOrderPos = *pVecOrderPos;
|
|
//Msg("m_vecMoveToOrderPos= %f %f %f\n",
|
|
// m_vecMoveToOrderPos[0], m_vecMoveToOrderPos[1], m_vecMoveToOrderPos[2]);
|
|
}
|
|
|
|
m_fCachedIdealSpeed = MaxSpeed();
|
|
}
|
|
|
|
static void DebugWaypoint( AI_Waypoint_t * pWay, int r, int g, int b, float flDuration = 3 )
|
|
{
|
|
if ( pWay && pWay->GetNext() )
|
|
{
|
|
NDebugOverlay::Line( pWay->GetPos(), pWay->GetNext()->GetPos(), r, g, b, true, 3 );
|
|
return DebugWaypoint( pWay->GetNext(), r, g, b, flDuration );
|
|
}
|
|
}
|
|
|
|
/// because AI_Waypoint_t::flPathDistGoal is broken
|
|
static float GetWaypointDistToEnd( const Vector &vStartPos, AI_Waypoint_t *way )
|
|
{
|
|
if ( !way ) return FLT_MAX;
|
|
float ret = way->GetPos().DistTo(vStartPos);
|
|
while ( way->GetNext() )
|
|
{
|
|
ret += way->GetNext()->GetPos().DistTo( way->GetPos() );
|
|
way = way->GetNext();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/// When a marine uses an offhand item, sometimes you need to pick a different
|
|
/// location for its use than exactly where the cursor was. And sometimes
|
|
/// you need to kibosh the use altogether. Returns true if the offhand use
|
|
/// should continue, false means it should be aborted
|
|
static bool AdjustOffhandItemDestination( CASW_Marine *pMarine, CASW_Weapon *pWeapon, const Vector &vecDestIn, Vector * RESTRICT vecDestOut )
|
|
{
|
|
// NDebugOverlay::Cross3D( vecDestIn, 16, 255, 16, 16, true, 3 );
|
|
if ( pWeapon->Classify() == CLASS_ASW_WELDER )
|
|
{
|
|
// find a door near the given position
|
|
CASW_Door_Area *pClosestArea = NULL;
|
|
CASW_Door * RESTRICT pDoor = NULL;
|
|
float flClosestDist = FLT_MAX;
|
|
|
|
for ( int i = 0; i < IASW_Use_Area_List::AutoList().Count(); i++ )
|
|
{
|
|
CASW_Use_Area *pArea = static_cast< CASW_Use_Area* >( IASW_Use_Area_List::AutoList()[ i ] );
|
|
if ( pArea->Classify() == CLASS_ASW_DOOR_AREA )
|
|
{
|
|
CASW_Door_Area *pDoorArea = assert_cast<CASW_Door_Area*>( pArea );
|
|
|
|
float flDist = (vecDestIn - pArea->WorldSpaceCenter()).Length2D( );
|
|
if ( flDist < flClosestDist && flDist < 240 )
|
|
{
|
|
flClosestDist = flDist;
|
|
pClosestArea = pDoorArea;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !pClosestArea )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pDoor = pClosestArea->GetASWDoor();
|
|
if ( !pDoor )
|
|
{
|
|
AssertMsg( false, "Door area is missing its door!\n" );
|
|
return false;
|
|
}
|
|
|
|
if ( asw_debug_order_weld.GetBool() ) NDebugOverlay::EntityBounds( pDoor, 0, 0, 255, 64, 3 );
|
|
|
|
// pick a point to either side of the door and build routes to them
|
|
Vector vStandPoints[2];
|
|
float fkickout = pMarine->BoundingRadius() * 1.1f;
|
|
float flTolerance = pMarine->BoundingRadius() * 1.1f;
|
|
|
|
// get the door's transform, and punch its saved "closed position" into the translate row
|
|
if ( true )
|
|
{
|
|
matrix3x4_t mat = pDoor->EntityToWorldTransform();
|
|
mat.SetOrigin( pDoor->GetClosedPosition() );
|
|
VectorTransform( Vector(fkickout,0,0), mat, vStandPoints[0] );
|
|
VectorTransform( Vector(-fkickout,0,0), mat, vStandPoints[1] );
|
|
}
|
|
else
|
|
{
|
|
pDoor->EntityToWorldSpace( Vector(fkickout,0,0), vStandPoints + 0 ); // forward
|
|
pDoor->EntityToWorldSpace( Vector(-fkickout,0,0), vStandPoints + 1 );
|
|
}
|
|
|
|
CAI_Pathfinder *pathfinder = pMarine->GetPathfinder();
|
|
AI_Waypoint_t *routeFwd =
|
|
pathfinder->BuildRoute( pMarine->GetAbsOrigin(), vStandPoints[0],
|
|
NULL, flTolerance, NAV_GROUND, bits_BUILD_GET_CLOSE );
|
|
AI_Waypoint_t *routeBack =
|
|
pathfinder->BuildRoute( pMarine->GetAbsOrigin(), vStandPoints[1],
|
|
NULL, flTolerance, NAV_GROUND, bits_BUILD_GET_CLOSE );
|
|
|
|
if ( asw_debug_order_weld.GetBool() )
|
|
{
|
|
if ( routeFwd )
|
|
{
|
|
NDebugOverlay::Circle( vStandPoints[0], QAngle(90, 0, 0), 24, 0, 128, 255, 255, true, 3 );
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::Circle( vStandPoints[0], QAngle(90, 0, 0), 24, 255, 0, 0, 255, true, 3 );
|
|
}
|
|
|
|
if ( routeBack )
|
|
{
|
|
NDebugOverlay::Circle( vStandPoints[1], QAngle(90, 0, 0), 24, 0, 255, 128, 255, true, 3 );
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::Circle( vStandPoints[1], QAngle(90, 0, 0), 24, 255, 0, 0, 255, true, 3 );
|
|
}
|
|
}
|
|
|
|
if ( !routeBack && !routeFwd )
|
|
{
|
|
delete routeFwd;
|
|
delete routeBack;
|
|
return false;
|
|
}
|
|
|
|
CASW_Marine *pLeader = static_cast< CASW_Marine* >( pMarine->GetSquadLeader() );
|
|
float flDists[2];
|
|
flDists[0] = routeFwd ? GetWaypointDistToEnd( pMarine->GetAbsOrigin(), routeFwd ) : FLT_MAX;
|
|
flDists[1] = routeBack ? GetWaypointDistToEnd( pMarine->GetAbsOrigin(), routeBack ) : FLT_MAX;
|
|
bool bUseForwardRoute = ( routeFwd && ( !routeBack || flDists[0] < flDists[1] ) );
|
|
|
|
if ( pLeader )
|
|
{
|
|
// move to whichever one is closer to our follow leader
|
|
CAI_Pathfinder *pathfinder = pLeader->GetPathfinder();
|
|
CPlainAutoPtr<AI_Waypoint_t> leaderRouteFwd(
|
|
pathfinder->BuildRoute( pLeader->GetAbsOrigin(), vStandPoints[0],
|
|
NULL, flTolerance, NAV_GROUND, bits_BUILD_GET_CLOSE ) );
|
|
CPlainAutoPtr<AI_Waypoint_t> leaderRouteBack(
|
|
pathfinder->BuildRoute( pLeader->GetAbsOrigin(), vStandPoints[1],
|
|
NULL, flTolerance, NAV_GROUND, bits_BUILD_GET_CLOSE ) );
|
|
|
|
float flLeaderDists[2];
|
|
flLeaderDists[0] = leaderRouteFwd.IsValid() ? GetWaypointDistToEnd( pLeader->GetAbsOrigin(), leaderRouteFwd.Get() ) : FLT_MAX;
|
|
flLeaderDists[1] = leaderRouteBack.IsValid() ? GetWaypointDistToEnd( pLeader->GetAbsOrigin(), leaderRouteBack.Get()) : FLT_MAX;
|
|
|
|
if ( leaderRouteFwd.IsValid() || leaderRouteBack.IsValid() )
|
|
{
|
|
bool bLeaderUseForwardRoute = ( leaderRouteFwd.IsValid() && ( !leaderRouteBack.IsValid() || flLeaderDists[0] < flLeaderDists[1] ) );
|
|
if ( bLeaderUseForwardRoute != bUseForwardRoute )
|
|
{
|
|
// leader is on an opposite side to the marine, push the point forward a bit more so marine clears the doorway he's running through
|
|
matrix3x4_t mat = pDoor->EntityToWorldTransform();
|
|
mat.SetOrigin( pDoor->GetClosedPosition() );
|
|
float fkickout = pMarine->BoundingRadius() * 2.2f;
|
|
VectorTransform( Vector(fkickout,0,0), mat, vStandPoints[0] );
|
|
VectorTransform( Vector(-fkickout,0,0), mat, vStandPoints[1] );
|
|
|
|
delete routeFwd;
|
|
delete routeBack;
|
|
|
|
routeFwd =
|
|
pathfinder->BuildRoute( pMarine->GetAbsOrigin(), vStandPoints[0],
|
|
NULL, flTolerance, NAV_GROUND, bits_BUILD_GET_CLOSE );
|
|
routeBack =
|
|
pathfinder->BuildRoute( pMarine->GetAbsOrigin(), vStandPoints[1],
|
|
NULL, flTolerance, NAV_GROUND, bits_BUILD_GET_CLOSE );
|
|
|
|
if ( !routeBack && !routeFwd )
|
|
{
|
|
delete routeFwd;
|
|
delete routeBack;
|
|
return false;
|
|
}
|
|
|
|
flDists[0] = routeFwd ? GetWaypointDistToEnd( pMarine->GetAbsOrigin(), routeFwd ) : FLT_MAX;
|
|
flDists[1] = routeBack ? GetWaypointDistToEnd( pMarine->GetAbsOrigin(), routeBack ) : FLT_MAX;
|
|
|
|
if ( ( bLeaderUseForwardRoute && routeFwd ) || ( !bLeaderUseForwardRoute && routeBack ) )
|
|
{
|
|
bUseForwardRoute = bLeaderUseForwardRoute;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// pick the shorter (or only existing) route
|
|
if ( bUseForwardRoute )
|
|
{
|
|
*vecDestOut = routeFwd->GetLast()->GetPos(); // vStandPoints[0];
|
|
if ( asw_debug_order_weld.GetBool() )
|
|
{
|
|
DebugWaypoint( routeFwd, 255, 255, 0 );
|
|
if ( routeBack )
|
|
{
|
|
Msg( "%.3f < %.3f\n", flDists[0], flDists[1] );
|
|
DebugWaypoint( routeBack, 0, 255, 0 );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*vecDestOut = routeBack->GetLast()->GetPos(); // vStandPoints[1];
|
|
if ( asw_debug_order_weld.GetBool() )
|
|
{
|
|
DebugWaypoint( routeBack, 255, 255, 0 );
|
|
if ( routeFwd )
|
|
{
|
|
Msg( "%.3f < %.3f\n", flDists[1], flDists[0] );
|
|
DebugWaypoint( routeFwd, 0, 255, 0 );
|
|
}
|
|
}
|
|
}
|
|
delete routeFwd;
|
|
delete routeBack;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// do nothing, just copy through
|
|
*vecDestOut = vecDestIn;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void CASW_Marine::OrderUseOffhandItem( int iInventorySlot, const Vector &vecDest )
|
|
{
|
|
// check we have an item in that slot
|
|
CASW_Weapon* pWeapon = GetASWWeapon( iInventorySlot );
|
|
if ( !pWeapon || !pWeapon->GetWeaponInfo() || !pWeapon->GetWeaponInfo()->m_bOffhandActivate )
|
|
return;
|
|
|
|
// m_vecOffhandItemSpot = vecDest;
|
|
if ( !AdjustOffhandItemDestination( this, pWeapon, vecDest, &m_vecOffhandItemSpot) )
|
|
return;
|
|
else if ( asw_debug_order_weld.GetBool() )
|
|
{
|
|
NDebugOverlay::Cross( m_vecOffhandItemSpot, 12.0f, 255, 0, 0, true, 3 );
|
|
}
|
|
|
|
if ( pWeapon->GetWeaponInfo()->m_nOffhandOrderType == ASW_OFFHAND_USE_IMMEDIATELY )
|
|
{
|
|
pWeapon->OffhandActivate();
|
|
return;
|
|
}
|
|
|
|
CASW_Player *pPlayer = GetCommander();
|
|
if ( pPlayer )
|
|
{
|
|
//DispatchParticleEffect( "marine_hit_blood_ff", vecDest, QAngle( 0, 0, 0 ) );
|
|
// Tell the player's client that he's been hurt.
|
|
CSingleUserRecipientFilter user( pPlayer );
|
|
UserMessageBegin( user, "ASWOrderUseItemFX" );
|
|
WRITE_SHORT( entindex() );
|
|
WRITE_SHORT( ASW_USE_ORDER_WITH_ITEM );
|
|
WRITE_SHORT( iInventorySlot );
|
|
WRITE_FLOAT( m_vecOffhandItemSpot.x );
|
|
WRITE_FLOAT( m_vecOffhandItemSpot.y );
|
|
WRITE_FLOAT( m_vecOffhandItemSpot.z );
|
|
MessageEnd();
|
|
}
|
|
|
|
m_ASWOrders = ASW_ORDER_USE_OFFHAND_ITEM;
|
|
m_hOffhandItemToUse = pWeapon;
|
|
|
|
SetCondition( COND_ASW_NEW_ORDERS );
|
|
}
|
|
|
|
#define ASW_DEPLOY_RANGE 75.0f
|
|
|
|
int CASW_Marine::SelectOffhandItemSchedule()
|
|
{
|
|
if ( m_bWaitingForWeld )
|
|
{
|
|
if( gpGlobals->curtime > m_flBeginWeldTime + 5.0f )
|
|
{
|
|
FinishedUsingOffhandItem( false );
|
|
return SCHED_ASW_FOLLOW_WAIT;
|
|
}
|
|
|
|
return SCHED_ASW_FOLLOW_WAIT;
|
|
}
|
|
|
|
if ( GetASWOrders() != ASW_ORDER_USE_OFFHAND_ITEM )
|
|
return -1;
|
|
|
|
if ( !m_hOffhandItemToUse.Get() )
|
|
return -1;
|
|
|
|
const CASW_WeaponInfo *pInfo = m_hOffhandItemToUse->GetWeaponInfo();
|
|
if ( !pInfo )
|
|
return -1;
|
|
|
|
SetPoseParameter( "move_x", 1.0f );
|
|
SetPoseParameter( "move_y", 0.0f );
|
|
|
|
if ( pInfo->m_nOffhandOrderType == ASW_OFFHAND_DEPLOY )
|
|
{
|
|
// if we're in range of the deploy spot, then use the item
|
|
if ( ( m_vecOffhandItemSpot - GetAbsOrigin() ).Length2D() < ASW_DEPLOY_RANGE )
|
|
{
|
|
m_hOffhandItemToUse->OffhandActivate();
|
|
FinishedUsingOffhandItem( false );
|
|
if ( m_bWaitingForWeld )
|
|
{
|
|
return SCHED_ASW_FOLLOW_WAIT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// otherwise, move to just in front of the deploy spot
|
|
Vector dir = GetAbsOrigin() - m_vecOffhandItemSpot;
|
|
dir.z = 0;
|
|
dir.NormalizeInPlace();
|
|
m_vecMoveToOrderPos = m_vecOffhandItemSpot + dir * 50.0f;
|
|
return SCHED_ASW_MOVE_TO_ORDER_POS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we have line of sight to the target spot
|
|
if ( CanThrowOffhand( m_hOffhandItemToUse, GetOffhandThrowSource(), m_vecOffhandItemSpot, asw_debug_throw.GetInt() == 3 ) )
|
|
{
|
|
m_hOffhandItemToUse->OffhandActivate();
|
|
FinishedUsingOffhandItem( true );
|
|
}
|
|
else
|
|
{
|
|
const float MIN_THROW_RANGE = 50.0f;
|
|
const float MAX_THROW_RANGE = 700.0f;
|
|
Vector pos;
|
|
int iNode = FindThrowNode( m_vecOffhandItemSpot, MIN_THROW_RANGE, MAX_THROW_RANGE, 1.0f );
|
|
if ( iNode != NO_NODE )
|
|
{
|
|
// move to the spot with line of sight
|
|
m_vecMoveToOrderPos = g_pBigAINet->GetNode(iNode)->GetPosition(GetHullType());
|
|
return SCHED_ASW_MOVE_TO_ORDER_POS;
|
|
}
|
|
else
|
|
{
|
|
// abort
|
|
Msg( "Failed to find throw node\n" );
|
|
FinishedUsingOffhandItem( true, true );
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void CASW_Marine::FinishedUsingOffhandItem( bool bItemThrown, bool bFailed )
|
|
{
|
|
// go back to following our commander's marine
|
|
CASW_Player *pPlayer = GetCommander();
|
|
if ( pPlayer && pPlayer->GetMarine() )
|
|
{
|
|
OrdersFromPlayer( pPlayer, ASW_ORDER_FOLLOW, pPlayer->GetMarine(), true );
|
|
|
|
CSingleUserRecipientFilter user( pPlayer );
|
|
UserMessageBegin( user, "ASWOrderStopItemFX" );
|
|
WRITE_SHORT( entindex() );
|
|
WRITE_BOOL( bItemThrown );
|
|
WRITE_BOOL( bFailed );
|
|
MessageEnd();
|
|
}
|
|
else
|
|
{
|
|
OrdersFromPlayer( NULL, ASW_ORDER_HOLD_POSITION, this, true, GetLocalAngles().y);
|
|
}
|
|
}
|
|
|
|
// checks if our thrown offhand item will reach the destination
|
|
bool CASW_Marine::CanThrowOffhand( CASW_Weapon *pWeapon, const Vector &vecSrc, const Vector &vecDest, bool bDrawArc )
|
|
{
|
|
if ( !pWeapon )
|
|
return false;
|
|
|
|
Vector vecVelocity = UTIL_LaunchVector( vecSrc, vecDest, pWeapon->GetThrowGravity() ) * 28.0f;
|
|
|
|
Vector vecResult = UTIL_Check_Throw( vecSrc, vecVelocity, pWeapon->GetThrowGravity(),
|
|
-Vector( 12,12,12 ), Vector( 12,12,12 ), MASK_NPCSOLID, COLLISION_GROUP_PROJECTILE, this, bDrawArc );
|
|
|
|
float flDist = vecResult.DistTo( vecDest );
|
|
return ( flDist < 50.0f );
|
|
}
|
|
|
|
int CASW_Marine::FindThrowNode( const Vector &vThreatPos, float flMinThreatDist, float flMaxThreatDist, float flBlockTime )
|
|
{
|
|
if ( !CAI_NetworkManager::NetworksLoaded() )
|
|
return NO_NODE;
|
|
|
|
AI_PROFILE_SCOPE( CASW_Marine::FindThrowNode );
|
|
|
|
Remember( bits_MEMORY_TASK_EXPENSIVE );
|
|
|
|
int iMyNode = GetPathfinder()->NearestNodeToNPC();
|
|
if ( iMyNode == NO_NODE )
|
|
{
|
|
Vector pos = GetAbsOrigin();
|
|
DevWarning( 2, "FindThrowNode() - %s has no nearest node! (Check near %f %f %f)\n", GetClassname(), pos.x, pos.y, pos.z);
|
|
return NO_NODE;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
// We're going to search for a shoot node by expanding to our current node's neighbors
|
|
// and then their neighbors, until a shooting position is found, or all nodes are beyond MaxDist
|
|
// ------------------------------------------------------------------------------------
|
|
AI_NearNode_t *pBuffer = (AI_NearNode_t *)stackalloc( sizeof(AI_NearNode_t) * g_pBigAINet->NumNodes() );
|
|
CNodeList list( pBuffer, g_pBigAINet->NumNodes() );
|
|
CVarBitVec wasVisited(g_pBigAINet->NumNodes()); // Nodes visited
|
|
|
|
// mark start as visited
|
|
wasVisited.Set( iMyNode );
|
|
list.Insert( AI_NearNode_t(iMyNode, 0) );
|
|
|
|
static int nSearchRandomizer = 0; // tries to ensure the links are searched in a different order each time;
|
|
|
|
while ( list.Count() )
|
|
{
|
|
int nodeIndex = list.ElementAtHead().nodeIndex;
|
|
// remove this item from the list
|
|
list.RemoveAtHead();
|
|
|
|
const Vector &nodeOrigin = g_pBigAINet->GetNode(nodeIndex)->GetPosition(GetHullType());
|
|
|
|
// HACKHACK: Can't we rework this loop and get rid of this?
|
|
// skip the starting node, or we probably wouldn't have called this function.
|
|
if ( nodeIndex != iMyNode )
|
|
{
|
|
bool skip = false;
|
|
|
|
// Don't accept climb nodes, and assume my nearest node isn't valid because
|
|
// we decided to make this check in the first place. Keep moving
|
|
if ( !skip && !g_pBigAINet->GetNode(nodeIndex)->IsLocked() &&
|
|
g_pBigAINet->GetNode(nodeIndex)->GetType() != NODE_CLIMB )
|
|
{
|
|
// Now check its distance and only accept if in range
|
|
float flThreatDist = ( nodeOrigin - vThreatPos ).Length();
|
|
|
|
if ( flThreatDist < flMaxThreatDist &&
|
|
flThreatDist > flMinThreatDist )
|
|
{
|
|
//CAI_Node *pNode = g_pBigAINet->GetNode(nodeIndex);
|
|
if ( CanThrowOffhand( m_hOffhandItemToUse.Get(), GetOffhandThrowSource( &nodeOrigin ), vThreatPos, asw_debug_throw.GetInt() == 2 ) )
|
|
{
|
|
// Note when this node was used, so we don't try
|
|
// to use it again right away.
|
|
g_pBigAINet->GetNode(nodeIndex)->Lock( flBlockTime );
|
|
|
|
if ( asw_debug_throw.GetBool() )
|
|
{
|
|
NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:los", nodeIndex), false, 1 );
|
|
|
|
// draw the arc
|
|
CanThrowOffhand( m_hOffhandItemToUse.Get(), GetOffhandThrowSource( &nodeOrigin ), vThreatPos, true );
|
|
}
|
|
|
|
// The next NPC who searches should use a slight different pattern
|
|
nSearchRandomizer = nodeIndex;
|
|
return nodeIndex;
|
|
}
|
|
else
|
|
{
|
|
if ( asw_debug_throw.GetBool() )
|
|
{
|
|
NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:!throw", nodeIndex), false, 1 );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( asw_debug_throw.GetBool() )
|
|
{
|
|
CFmtStr msg( "%d:%s", nodeIndex, ( flThreatDist < flMaxThreatDist ) ? "too close" : "too far" );
|
|
NDebugOverlay::Text( nodeOrigin, msg, false, 1 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Go through each link and add connected nodes to the list
|
|
for (int link=0; link < g_pBigAINet->GetNode(nodeIndex)->NumLinks();link++)
|
|
{
|
|
int index = (link + nSearchRandomizer) % g_pBigAINet->GetNode(nodeIndex)->NumLinks();
|
|
CAI_Link *nodeLink = g_pBigAINet->GetNode(nodeIndex)->GetLinkByIndex(index);
|
|
|
|
if ( !GetPathfinder()->IsLinkUsable( nodeLink, iMyNode ) )
|
|
continue;
|
|
|
|
int newID = nodeLink->DestNodeID(nodeIndex);
|
|
|
|
// If not already visited, add to the list
|
|
if (!wasVisited.IsBitSet(newID))
|
|
{
|
|
float dist = (GetLocalOrigin() - g_pBigAINet->GetNode(newID)->GetPosition(GetHullType())).LengthSqr();
|
|
list.Insert( AI_NearNode_t(newID, dist) );
|
|
wasVisited.Set( newID );
|
|
}
|
|
}
|
|
}
|
|
// We failed. No range attack node node was found
|
|
return NO_NODE;
|
|
}
|
|
|
|
|
|
// custom follow stuff
|
|
#define ASW_FOLLOW_DISTANCE 230
|
|
#define ASW_FOLLOW_DISTANCE_NO_LOS 100
|
|
#define ASW_FORMATION_FOLLOW_DISTANCE 40
|
|
|
|
|
|
bool CASW_Marine::NeedToUpdateSquad()
|
|
{
|
|
// basically, if I am a leader and I've moved, an update is needed.
|
|
return ( GetSquadLeader() == this && GetSquadFormation()->ShouldUpdateFollowPositions() );
|
|
}
|
|
|
|
bool CASW_Marine::NeedToFollowMove()
|
|
{
|
|
CASW_Marine * RESTRICT pLeader = GetSquadLeader();
|
|
if ( !pLeader || pLeader == this )
|
|
return false;
|
|
|
|
if( IsOutOfAmmo() && GetEnemy() )
|
|
return false;
|
|
|
|
// only move if we're not near our saved follow point
|
|
float dist = ( GetAbsOrigin() - GetFollowPos() ).Length2DSqr();
|
|
return dist > ( ASW_FORMATION_FOLLOW_DISTANCE * ASW_FORMATION_FOLLOW_DISTANCE );
|
|
}
|
|
|
|
static bool ValidMarineMeleeTarget( CBaseEntity *pEnt )
|
|
{
|
|
if ( !pEnt )
|
|
return false;
|
|
|
|
// don't punch big scary aliens
|
|
Class_T entClass = pEnt->Classify();
|
|
if ( entClass == CLASS_ASW_BOOMER || entClass == CLASS_ASW_SHIELDBUG ||
|
|
entClass == CLASS_ASW_HARVESTER || entClass == CLASS_ASW_MORTAR_BUG )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
int CASW_Marine::SelectMeleeSchedule()
|
|
{
|
|
if( GetPhysicsPropTarget() )
|
|
{
|
|
SetEnemy( GetPhysicsPropTarget() );
|
|
|
|
return SCHED_MELEE_ATTACK_PROP1;
|
|
}
|
|
else if( GetEnemy() )
|
|
{
|
|
bool bLastManStanding = true;
|
|
|
|
CASW_Game_Resource *pGameResource = ASWGameResource();
|
|
for ( int i = 0; i < pGameResource->GetMaxMarineResources(); i++ )
|
|
{
|
|
CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
|
|
CASW_Marine *pMarine = pMR ? pMR->GetMarineEntity() : NULL;
|
|
if( pMarine && pMarine != this && pMarine->GetHealth() > 0 )
|
|
{
|
|
bLastManStanding = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( GetHealth() > GetMaxHealth() * 0.5f || bLastManStanding || GetAbsOrigin().DistToSqr( GetEnemyLKP() ) < Square( 100.0f ) )
|
|
{
|
|
if ( ValidMarineMeleeTarget( GetEnemy() ) )
|
|
{
|
|
return SCHED_ENGAGE_AND_MELEE_ATTACK1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
ConVar asw_follow_slow_distance( "asw_follow_slow_distance", "1200.0f", FCVAR_CHEAT, "Marines will follow their leader slowly during combat if he's within this distance" );
|
|
|
|
int CASW_Marine::SelectFollowSchedule()
|
|
{
|
|
// if we don't have anyone to follow, revert to holding position orders
|
|
if ( !GetSquadLeader() && ASWGameRules() && gpGlobals->curtime > ASWGameRules()->m_fMissionStartedTime + 4.0f )
|
|
{
|
|
SetASWOrders(ASW_ORDER_HOLD_POSITION, GetAbsAngles()[YAW], &GetAbsOrigin());
|
|
return SCHED_ASW_HOLD_POSITION;
|
|
}
|
|
|
|
if (NeedToFollowMove())
|
|
{
|
|
return SCHED_ASW_FOLLOW_MOVE;
|
|
}
|
|
|
|
// shoot if we need to
|
|
if ( HasCondition(COND_CAN_RANGE_ATTACK1) )
|
|
{
|
|
//Msg("Marine's select schedule returning SCHED_RANGE_ATTACK1\n");
|
|
CBaseEntity* pEnemy = GetEnemy();
|
|
if (pEnemy) // don't shoot unless we actually have an enemy
|
|
return SCHED_RANGE_ATTACK1;
|
|
}
|
|
|
|
if( IsOutOfAmmo() && GetEnemy() )
|
|
{
|
|
SetCondition( COND_COMPLETELY_OUT_OF_AMMO );
|
|
|
|
int iMeleeSchedule = SelectMeleeSchedule();
|
|
if( iMeleeSchedule != -1 )
|
|
return iMeleeSchedule;
|
|
}
|
|
|
|
// check if we're too near another marine
|
|
//CASW_Marine *pCloseMarine = TooCloseToAnotherMarine();
|
|
//if (pCloseMarine)
|
|
//{
|
|
//Msg("marine is too close to %d\n", pCloseMarine->entindex());
|
|
//return SCHED_ASW_FOLLOW_BACK_OFF;
|
|
//}
|
|
|
|
return SCHED_ASW_FOLLOW_WAIT;
|
|
}
|
|
|
|
#define ASW_MARINE_TOO_CLOSE 40
|
|
CASW_Marine* CASW_Marine::TooCloseToAnotherMarine()
|
|
{
|
|
if ( !ASWGameResource() )
|
|
return NULL;
|
|
|
|
CASW_Game_Resource *pGameResource = ASWGameResource();
|
|
for (int i=0;i<pGameResource->GetMaxMarineResources();i++)
|
|
{
|
|
CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
|
|
if (!pMR)
|
|
continue;
|
|
|
|
CASW_Marine *pMarine = pMR->GetMarineEntity();
|
|
if (!pMarine || pMarine == this)
|
|
continue;
|
|
|
|
float dist = pMarine->GetAbsOrigin().DistTo(GetAbsOrigin());
|
|
if ( dist < ASW_MARINE_TOO_CLOSE)
|
|
{
|
|
Msg("marine %d is %f away\n", i, dist);
|
|
return pMarine;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void CASW_Marine::StartTask(const Task_t *pTask)
|
|
{
|
|
switch (pTask->iTask)
|
|
{
|
|
case TASK_ASW_ORDER_TO_DEPLOY_SPOT:
|
|
{
|
|
CASW_Bloodhound *pDropship = dynamic_cast<CASW_Bloodhound*>(gEntList.FindEntityByClassname( NULL, "asw_bloodhound" ));
|
|
if (pDropship)
|
|
pDropship->MarineLanded(this);
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_ASW_FACE_HOLDING_YAW:
|
|
case TASK_ASW_FACE_USING_ITEM:
|
|
{
|
|
SetWait( pTask->flTaskData );
|
|
SetIdealActivity( ACT_IDLE );
|
|
break;
|
|
}
|
|
case TASK_ASW_START_USING_AREA:
|
|
{
|
|
if ( m_hAreaToUse.Get() && m_hAreaToUse->IsUsable( this ) )
|
|
{
|
|
m_hAreaToUse->ActivateUseIcon( this, ASW_USE_RELEASE_QUICK );
|
|
}
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_ASW_CHATTER_CONFIRM:
|
|
{
|
|
if (!m_bDoneOrderChatter)
|
|
{
|
|
m_bDoneOrderChatter = true;
|
|
if (!IsInfested() && random->RandomFloat() <= pTask->flTaskData)
|
|
GetMarineSpeech()->QueueChatter(CHATTER_USE, gpGlobals->curtime + 0.1f, gpGlobals->curtime + 5.0f, GetCommander());
|
|
}
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_ASW_FACE_FOLLOW_WAIT:
|
|
{
|
|
SetWait( pTask->flTaskData );
|
|
break;
|
|
}
|
|
case TASK_ASW_FACE_ENEMY_WITH_ERROR:
|
|
{
|
|
break;
|
|
}
|
|
case TASK_ASW_OVERKILL_SHOOT:
|
|
{
|
|
m_fStartedFiringTime = gpGlobals->curtime;
|
|
break;
|
|
}
|
|
case TASK_ASW_GET_PATH_TO_ORDER_POS:
|
|
{
|
|
//NDebugOverlay::Line( GetAbsOrigin(), m_vecMoveToOrderPos, 255, 0, 0, true, 4.0f );
|
|
float flTolerance = AIN_HULL_TOLERANCE;
|
|
if ( m_vecMoveToOrderPos == m_vecOffhandItemSpot )
|
|
{
|
|
flTolerance = 100.0f;
|
|
}
|
|
AI_NavGoal_t goal(m_vecMoveToOrderPos, ACT_RUN, flTolerance);
|
|
GetNavigator()->SetGoal( goal );
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_ASW_GET_PATH_TO_FOLLOW_TARGET:
|
|
{
|
|
if ( !GetSquadLeader() )
|
|
TaskFail("No follow target");
|
|
else
|
|
{
|
|
AI_NavGoal_t goal( GetFollowPos(), ACT_RUN, 60 ); // AIN_HULL_TOLERANCE
|
|
GetNavigator()->SetGoal( goal );
|
|
TaskComplete();
|
|
}
|
|
}
|
|
break;
|
|
case TASK_ASW_GET_PATH_TO_PROP:
|
|
{
|
|
if ( !GetPhysicsPropTarget() )
|
|
TaskFail("No prop");
|
|
else
|
|
{
|
|
AI_NavGoal_t goal( GetPhysicsPropTarget()->WorldSpaceCenter(), ACT_RUN, 60 ); // AIN_HULL_TOLERANCE
|
|
GetNavigator()->SetGoal( goal );
|
|
TaskComplete();
|
|
}
|
|
}
|
|
break;
|
|
case TASK_ASW_WAIT_FOR_FOLLOW_MOVEMENT:
|
|
case TASK_ASW_WAIT_FOR_MOVEMENT:
|
|
{
|
|
if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
|
|
{
|
|
TaskComplete();
|
|
GetNavigator()->ClearGoal(); // Clear residual state
|
|
}
|
|
else if (!GetNavigator()->IsGoalActive())
|
|
{
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
}
|
|
else
|
|
{
|
|
// Check validity of goal type
|
|
ValidateNavGoal();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_ASW_GET_BACK_OFF_PATH:
|
|
{
|
|
CASW_Marine *pCloseMarine = TooCloseToAnotherMarine();
|
|
if (!pCloseMarine)
|
|
TaskFail("No marine too close");
|
|
else
|
|
{
|
|
Vector diff = GetAbsOrigin() - pCloseMarine->GetAbsOrigin();
|
|
Vector diffnorm = diff;
|
|
VectorNormalize(diffnorm);
|
|
Vector vecGoalPos = GetAbsOrigin() + diffnorm * 60; // move to x units away from this marine
|
|
|
|
AI_NavGoal_t goal(vecGoalPos, ACT_RUN, AIN_HULL_TOLERANCE);
|
|
GetNavigator()->SetGoal( goal );
|
|
TaskComplete();
|
|
}
|
|
}
|
|
break;
|
|
case TASK_MOVE_AWAY_PATH:
|
|
GetMotor()->SetIdealYaw( UTIL_AngleMod( GetLocalAngles().y - 180.0f ) );
|
|
BaseClass::StartTask( pTask );
|
|
break;
|
|
|
|
case TASK_ASW_RAPPEL:
|
|
{
|
|
CreateZipline();
|
|
SetDescentSpeed();
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_HIT_GROUND:
|
|
m_bOnGround = true;
|
|
TaskComplete();
|
|
break;
|
|
|
|
case TASK_ASW_GET_PATH_TO_GIVE_AMMO:
|
|
if (!m_hGiveAmmoTarget)
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_MOVE_TO_GIVE_AMMO:
|
|
if (!m_hGiveAmmoTarget)
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_SWAP_TO_AMMO_BAG:
|
|
{
|
|
CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
if ( !pWeapon || pWeapon->Classify() != CLASS_ASW_AMMO_BAG )
|
|
{
|
|
for ( int iWeapon = 0; iWeapon < ASW_NUM_INVENTORY_SLOTS; iWeapon++ )
|
|
{
|
|
CASW_Weapon *pWeapon = GetASWWeapon( iWeapon );
|
|
if ( pWeapon && pWeapon->Classify() == CLASS_ASW_AMMO_BAG )
|
|
{
|
|
Weapon_Switch( pWeapon );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_GIVE_AMMO_TO_MARINE:
|
|
if (m_hGiveAmmoTarget)
|
|
{
|
|
CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
if ( pWeapon && pWeapon->Classify() == CLASS_ASW_AMMO_BAG )
|
|
{
|
|
CASW_Weapon_Ammo_Bag *pAmmoBag = dynamic_cast<CASW_Weapon_Ammo_Bag*>(pWeapon);
|
|
pAmmoBag->ThrowAmmo();
|
|
}
|
|
}
|
|
TaskComplete();
|
|
break;
|
|
|
|
case TASK_MELEE_ATTACK1:
|
|
{
|
|
if( !GetEnemy() )
|
|
{
|
|
TaskFail("No enemy!");
|
|
}
|
|
else if ( GetAbsOrigin().DistToSqr( GetEnemy()->GetAbsOrigin() ) > Square( 100.0f ) )
|
|
{
|
|
if ( GetEnemy() == GetPhysicsPropTarget() )
|
|
{
|
|
SetPhysicsPropTarget( NULL );
|
|
}
|
|
TaskFail("Melee target too far!");
|
|
}
|
|
else
|
|
{
|
|
DoAnimationEvent( (PlayerAnimEvent_t) ( PLAYERANIMEVENT_MELEE + ( int ) pTask->flTaskData ) ); // send anim event to clients
|
|
BaseClass::StartTask(pTask);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_GET_PATH_TO_HEAL:
|
|
{
|
|
if( !m_hHealTarget )
|
|
{
|
|
TaskFail( FAIL_NO_GOAL );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_MOVE_TO_HEAL:
|
|
{
|
|
if( !m_hHealTarget )
|
|
{
|
|
TaskFail( FAIL_NO_GOAL );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_SWAP_TO_HEAL_GUN:
|
|
{
|
|
CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
if( !pWeapon || pWeapon->Classify() != CLASS_ASW_HEAL_GUN )
|
|
{
|
|
for ( int iWeapon = 0; iWeapon < ASW_NUM_INVENTORY_SLOTS; iWeapon++ )
|
|
{
|
|
CASW_Weapon *pWeapon = GetASWWeapon( iWeapon );
|
|
if ( pWeapon && pWeapon->Classify() == CLASS_ASW_HEAL_GUN && pWeapon->HasAmmo() )
|
|
{
|
|
Weapon_Switch( pWeapon );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_HEAL_MARINE:
|
|
{
|
|
CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
if( !pWeapon || pWeapon->Classify() != CLASS_ASW_HEAL_GUN || !pWeapon->HasAmmo() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
CASW_Weapon_Heal_Gun *pHealgun = static_cast<CASW_Weapon_Heal_Gun*>( pWeapon );
|
|
|
|
// if the gun to longer has a target...
|
|
if ( !pHealgun || !m_hHealTarget ||
|
|
m_hHealTarget->GetHealth() <= 0 ||
|
|
m_hHealTarget->GetHealth() >= m_hHealTarget->GetMaxHealth() * MARINE_STOP_HEAL_THRESHOLD ||
|
|
m_hHealTarget->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) >= Square( CASW_Weapon_Heal_Gun::GetWeaponRange()*0.5 ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
bool bHealFailed = false;
|
|
// facing another direction
|
|
Vector vecAttach = m_hHealTarget->GetAbsOrigin() - GetAbsOrigin();
|
|
Vector vecForward;
|
|
|
|
AngleVectors( ASWEyeAngles(), &vecForward );
|
|
if ( DotProduct( vecForward, vecAttach ) < 0.0f )
|
|
{
|
|
TaskComplete();
|
|
bHealFailed = true;
|
|
}
|
|
|
|
if ( !bHealFailed )
|
|
pHealgun->HealAttach( m_hHealTarget );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
return BaseClass::StartTask(pTask);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
CBaseEntity *CASW_Marine::BestAlienGooTarget()
|
|
{
|
|
CASW_Alien_Goo *pAlienGooTarget = NULL;
|
|
float flClosestDistSqr = GetFollowSightRange() * GetFollowSightRange();
|
|
|
|
for ( int iAlienGoo = 0; iAlienGoo < g_AlienGoo.Count(); iAlienGoo++ )
|
|
{
|
|
// Get the instance of alien goo - if it exists.
|
|
CASW_Alien_Goo *pAlienGoo = g_AlienGoo[iAlienGoo];
|
|
if ( !pAlienGoo )
|
|
continue;
|
|
|
|
float flDistSqr = GetAbsOrigin().DistToSqr( pAlienGoo->GetAbsOrigin() );
|
|
if( flDistSqr < flClosestDistSqr && FInViewCone( pAlienGoo->GetAbsOrigin() ) && !pAlienGoo->m_bHasGrubs ) // grubs = decorative, not a target
|
|
{
|
|
flClosestDistSqr = flDistSqr;
|
|
pAlienGooTarget = pAlienGoo;
|
|
}
|
|
}
|
|
|
|
return pAlienGooTarget;
|
|
}
|
|
|
|
bool CASW_Marine::EngageNewAlienGooTarget()
|
|
{
|
|
CASW_Weapon *pActiveWeapon = GetActiveASWWeapon();
|
|
CASW_Weapon *pFlamer = NULL;
|
|
|
|
if( pActiveWeapon && pActiveWeapon->Classify() == CLASS_ASW_FLAMER && pActiveWeapon->HasAmmo() )
|
|
{
|
|
pFlamer = pActiveWeapon;
|
|
}
|
|
else
|
|
{
|
|
for ( int iWeapon = 0; iWeapon < ASW_NUM_INVENTORY_SLOTS; iWeapon++ )
|
|
{
|
|
CASW_Weapon *pWeapon = GetASWWeapon( iWeapon );
|
|
if ( pWeapon && pWeapon->Classify() == CLASS_ASW_FLAMER && pWeapon->HasAmmo() )
|
|
{
|
|
pFlamer = pWeapon;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pFlamer )
|
|
{
|
|
EHANDLE hAlienGooTarget = BestAlienGooTarget();
|
|
if( hAlienGooTarget.Get() )
|
|
{
|
|
SetAlienGooTarget( hAlienGooTarget );
|
|
|
|
// only switch weapons if we have a valid target and the weapon is usable
|
|
if( pFlamer != pActiveWeapon )
|
|
{
|
|
Weapon_Switch( pFlamer );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const Vector &CASW_Marine::GetEnemyLKP() const
|
|
{
|
|
// Only override default behavior if we're tracking alien goo and have no other target
|
|
if( GetAlienGooTarget() && ( !GetEnemy() || GetAlienGooTarget() == GetEnemy() ) )
|
|
{
|
|
return GetAlienGooTarget()->GetAbsOrigin();
|
|
}
|
|
else
|
|
{
|
|
CASW_Prop_Physics *pPropPhysics = dynamic_cast< CASW_Prop_Physics *>( GetEnemy() );
|
|
if ( pPropPhysics && pPropPhysics->m_takedamage == DAMAGE_YES && pPropPhysics->m_iHealth > 0 )
|
|
{
|
|
return pPropPhysics->GetAbsOrigin();
|
|
}
|
|
|
|
return BaseClass::GetEnemyLKP();
|
|
}
|
|
}
|
|
|
|
void CASW_Marine::RunTask( const Task_t *pTask )
|
|
{
|
|
bool bOld = m_bWantsToFire;
|
|
m_bWantsToFire = false;
|
|
|
|
CheckForAIWeaponSwitch();
|
|
|
|
// check for firing on the move, if our enemy is in range, with LOS and we're facing him
|
|
if (GetASWOrders() == ASW_ORDER_MOVE_TO || GetASWOrders() == ASW_ORDER_FOLLOW)
|
|
{
|
|
bool bMelee = GetCurSchedule()->GetId() == GetGlobalScheduleId( SCHED_MELEE_ATTACK_PROP1 );
|
|
|
|
if( !GetEnemy() )
|
|
{
|
|
if ( m_flLastGooScanTime + ASW_MARINE_GOO_SCAN_TIME < gpGlobals->curtime )
|
|
{
|
|
EngageNewAlienGooTarget();
|
|
m_flLastGooScanTime = gpGlobals->curtime;
|
|
}
|
|
|
|
// base AI doesn't see biomass - Need to override every time if biomass targeted
|
|
if( GetAlienGooTarget() )
|
|
{
|
|
SetEnemy( GetAlienGooTarget() );
|
|
}
|
|
else if( bMelee && GetPhysicsPropTarget() )
|
|
{
|
|
SetEnemy( GetPhysicsPropTarget() );
|
|
}
|
|
}
|
|
else if( !bMelee && GetEnemy() == GetPhysicsPropTarget() )
|
|
{
|
|
SetEnemy( NULL );
|
|
SetPhysicsPropTarget( NULL );
|
|
}
|
|
|
|
CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
bool bOffensiveWeapon = (pWeapon && pWeapon->IsOffensiveWeapon());
|
|
|
|
if ( GetEnemy() )
|
|
{
|
|
Vector vecEnemyLKP = GetEnemyLKP();
|
|
|
|
if ( bOffensiveWeapon && !bMelee && FInAimCone( vecEnemyLKP ) &&
|
|
GetAbsOrigin().DistTo( GetEnemy()->GetAbsOrigin() ) <= GetFollowSightRange() )
|
|
{
|
|
// check it's a valid offensive weapon
|
|
bool bWeaponHasLOS = WeaponLOSCondition( GetAbsOrigin(), GetEnemy()->EyePosition(), true );
|
|
if ( !bWeaponHasLOS )
|
|
{
|
|
bWeaponHasLOS = WeaponLOSCondition( GetAbsOrigin(), GetEnemy()->BodyTarget( GetAbsOrigin() ), true );
|
|
}
|
|
|
|
if ( bWeaponHasLOS && !HasCondition(COND_ENEMY_TOO_FAR) )
|
|
{
|
|
m_bWantsToFire = true;
|
|
m_fMarineAimError *= asw_marine_aim_error_decay_multiplier.GetFloat();
|
|
if (pTask->iTask != TASK_ASW_OVERKILL_SHOOT)
|
|
m_vecOverkillPos = vecEnemyLKP;
|
|
|
|
m_fRandomFacing = UTIL_VecToYaw(vecEnemyLKP - GetAbsOrigin());
|
|
float fTwitch = float(GetHealth()) / float(GetMaxHealth());
|
|
if (fTwitch < 0.2)
|
|
fTwitch = 0.2f;
|
|
m_fNewRandomFacingTime = gpGlobals->curtime + random->RandomFloat(5 * fTwitch, 9.0f * fTwitch);
|
|
}
|
|
else if ( asw_debug_marine_aim.GetBool() )
|
|
{
|
|
Msg( "Following AI not shooting enemy as we don't have weapon LOS\n" );
|
|
}
|
|
}
|
|
else if ( asw_debug_marine_aim.GetBool() )
|
|
{
|
|
Msg("Following AI not shooting enemy: InAimcone=%d FacingIdeal=%d\n",
|
|
FInAimCone( vecEnemyLKP ), FacingIdeal());
|
|
|
|
Vector los = ( vecEnemyLKP - GetAbsOrigin() );
|
|
|
|
// do this in 2D
|
|
los.z = 0;
|
|
VectorNormalize( los );
|
|
|
|
Vector facingDir = BodyDirection2D( );
|
|
|
|
float flDot = DotProduct( los, facingDir );
|
|
Msg( " los = %f %f %f (%f)", VectorExpand( los ), UTIL_VecToYaw( los ) );
|
|
Msg( " fac = %f %f %f (%f)", VectorExpand( facingDir ), UTIL_VecToYaw( facingDir ) );
|
|
Msg( " flDot = %f eye = %f\n", flDot, UTIL_VecToYaw( EyeDirection2D() ) );
|
|
|
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + facingDir * 200, 0, 0, 255, true, 0.1f );
|
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + los * 200, 255, 0, 0, true, 0.1f );
|
|
}
|
|
}
|
|
}
|
|
|
|
switch(pTask->iTask)
|
|
{
|
|
case TASK_ASW_FACE_HOLDING_YAW:
|
|
{
|
|
GetMotor()->SetIdealYawAndUpdate( m_fHoldingYaw );
|
|
Scan();
|
|
|
|
if ( IsWaitFinished())
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_ASW_FACE_USING_ITEM:
|
|
{
|
|
if (!m_hUsingEntity.Get())
|
|
{
|
|
GetMotor()->SetIdealYawAndUpdate( m_fHoldingYaw );
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
CASW_Use_Area *pArea = dynamic_cast< CASW_Use_Area* >( m_hUsingEntity->GetBaseEntity() );
|
|
Vector vecUseTarget = m_hUsingEntity.Get()->GetAbsOrigin();
|
|
if ( pArea && pArea->GetProp() )
|
|
vecUseTarget = pArea->GetProp()->GetAbsOrigin();
|
|
|
|
Vector diff = vecUseTarget - GetAbsOrigin();
|
|
float fUsingYaw = UTIL_VecToYaw( diff );
|
|
GetMotor()->SetIdealYawAndUpdate( fUsingYaw );
|
|
}
|
|
|
|
break;
|
|
}
|
|
case TASK_ASW_FACE_FOLLOW_WAIT:
|
|
{
|
|
UpdateFacing();
|
|
if ( IsWaitFinished())
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_ASW_WAIT_FOR_FOLLOW_MOVEMENT:
|
|
{
|
|
UpdateFacing();
|
|
bool fTimeExpired = ( pTask->flTaskData != 0 && pTask->flTaskData < gpGlobals->curtime - GetTimeTaskStarted() );
|
|
if (fTimeExpired || GetNavigator()->GetGoalType() == GOALTYPE_NONE)
|
|
{
|
|
TaskComplete();
|
|
GetNavigator()->StopMoving(); // Stop moving
|
|
}
|
|
else if (!GetNavigator()->IsGoalActive())
|
|
{
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
}
|
|
else
|
|
{
|
|
// Check validity of goal type
|
|
ValidateNavGoal();
|
|
|
|
const Vector &vecFollowPos = GetFollowPos();
|
|
if ( ( GetNavigator()->GetGoalPos() - vecFollowPos ).LengthSqr() > Square( 60 ) )
|
|
{
|
|
if ( GetNavigator()->GetNavType() != NAV_JUMP )
|
|
{
|
|
if ( !GetNavigator()->UpdateGoalPos( vecFollowPos ) )
|
|
{
|
|
TaskFail(FAIL_NO_ROUTE);
|
|
}
|
|
}
|
|
}
|
|
else if (!NeedToFollowMove())
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// try to keep facing towards the last known position of the enemy
|
|
//if (GetEnemy())
|
|
//{
|
|
//Vector vecEnemyLKP = GetEnemyLKP();
|
|
//AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
|
|
//Msg("follow task adding facing target\n");
|
|
//}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case TASK_ASW_WAIT_FOR_MOVEMENT:
|
|
{
|
|
UpdateFacing();
|
|
|
|
if ( IsMovementFrozen() )
|
|
{
|
|
TaskFail(FAIL_FROZEN);
|
|
break;
|
|
}
|
|
|
|
bool fTimeExpired = ( pTask->flTaskData != 0 && pTask->flTaskData < gpGlobals->curtime - GetTimeTaskStarted() );
|
|
|
|
if (fTimeExpired || GetNavigator()->GetGoalType() == GOALTYPE_NONE)
|
|
{
|
|
TaskComplete();
|
|
GetNavigator()->StopMoving(); // Stop moving
|
|
}
|
|
else if (!GetNavigator()->IsGoalActive())
|
|
{
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
}
|
|
else
|
|
{
|
|
// Check validity of goal type
|
|
ValidateNavGoal();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_ASW_FACE_ENEMY_WITH_ERROR:
|
|
{
|
|
// If the yaw is locked, this function will not act correctly
|
|
Assert( GetMotor()->IsYawLocked() == false );
|
|
|
|
UpdateFacing();
|
|
|
|
if ( FacingIdeal() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_ASW_OVERKILL_SHOOT:
|
|
{
|
|
RunTaskRangeAttack1(pTask);
|
|
break;
|
|
}
|
|
case TASK_ASW_RAPPEL:
|
|
{
|
|
// If we don't do this, the beam won't show up sometimes. Ideally, all beams would update their
|
|
// bboxes correctly, but we're close to shipping and we can't change that now.
|
|
if ( m_hLine )
|
|
{
|
|
m_hLine->RelinkBeam();
|
|
}
|
|
|
|
//if( GetEnemy() )
|
|
//{
|
|
// Face the enemy if there's one.
|
|
//Vector vecEnemyLKP = GetEnemyLKP();
|
|
//GetMotor()->SetIdealYawToTargetAndUpdate( vecEnemyLKP );
|
|
//}
|
|
|
|
SetDescentSpeed();
|
|
if( GetFlags() & FL_ONGROUND )
|
|
{
|
|
//m_OnRappelTouchdown.FireOutput( GetOuter(), GetOuter(), 0 );
|
|
RemoveFlag( FL_FLY );
|
|
|
|
CutZipline();
|
|
|
|
TaskComplete();
|
|
}
|
|
}
|
|
break;
|
|
case TASK_ASW_GET_PATH_TO_GIVE_AMMO:
|
|
{
|
|
if(m_hGiveAmmoTarget)
|
|
{
|
|
AI_NavGoal_t goal( m_hGiveAmmoTarget->GetAbsOrigin(), ACT_RUN, CASW_Weapon_Ammo_Bag::GetAIMaxAmmoGiveDistance() );
|
|
GetNavigator()->SetGoal( goal );
|
|
}
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
case TASK_ASW_MOVE_TO_GIVE_AMMO:
|
|
// if target is still valid, move to target
|
|
if( m_hGiveAmmoTarget)
|
|
{
|
|
UpdateFacing();
|
|
bool bTimeExpired = ( pTask->flTaskData != 0 && pTask->flTaskData < gpGlobals->curtime - GetTimeTaskStarted() );
|
|
bool bArrived = ( ( GetNavigator()->GetGoalPos() - GetAbsOrigin() ).LengthSqr() < Square( CASW_Weapon_Ammo_Bag::GetAIMaxAmmoGiveDistance() ) );
|
|
if ( bTimeExpired || GetNavigator()->GetGoalType() == GOALTYPE_NONE || bArrived )
|
|
{
|
|
TaskComplete();
|
|
GetNavigator()->StopMoving();
|
|
}
|
|
else if (!GetNavigator()->IsGoalActive())
|
|
{
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
}
|
|
else
|
|
{
|
|
// Check validity of goal type
|
|
ValidateNavGoal();
|
|
|
|
// see if target marine has moved far enough that we need to adjust path
|
|
const Vector &vecCurrentPos = m_hGiveAmmoTarget->GetAbsOrigin();
|
|
if ( ( GetNavigator()->GetGoalPos() - vecCurrentPos ).LengthSqr() > Square( CASW_Weapon_Ammo_Bag::GetAIMaxAmmoGiveDistance() ) )
|
|
{
|
|
if ( GetNavigator()->GetNavType() != NAV_JUMP )
|
|
{
|
|
if ( !GetNavigator()->UpdateGoalPos( vecCurrentPos ) )
|
|
{
|
|
TaskFail( FAIL_NO_ROUTE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
// otherwise, the move task is complete (either we've arrived or the target is invalid)
|
|
{
|
|
m_hGiveAmmoTarget = NULL;
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_GET_PATH_TO_HEAL:
|
|
{
|
|
if( m_hHealTarget )
|
|
{
|
|
AI_NavGoal_t goal( m_hHealTarget->GetAbsOrigin(), ACT_RUN, CASW_Weapon_Heal_Gun::GetWeaponRange() * 0.5f );
|
|
GetNavigator()->SetGoal( goal );
|
|
}
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_MOVE_TO_HEAL:
|
|
{
|
|
// if target is still valid, move to target
|
|
if( m_hHealTarget )
|
|
{
|
|
UpdateFacing();
|
|
bool bTimeExpired = ( pTask->flTaskData != 0 && pTask->flTaskData < gpGlobals->curtime - GetTimeTaskStarted() );
|
|
bool bArrived = ( ( GetNavigator()->GetGoalPos() - GetAbsOrigin() ).LengthSqr() < Square( CASW_Weapon_Heal_Gun::GetWeaponRange()*0.5f ) );
|
|
if ( bTimeExpired || GetNavigator()->GetGoalType() == GOALTYPE_NONE || bArrived )
|
|
{
|
|
TaskComplete();
|
|
GetNavigator()->StopMoving();
|
|
}
|
|
else if (!GetNavigator()->IsGoalActive())
|
|
{
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
}
|
|
else
|
|
{
|
|
// Check validity of goal type
|
|
ValidateNavGoal();
|
|
|
|
// see if target marine has moved far enough that we need to adjust path
|
|
const Vector &vecCurrentPos = m_hHealTarget->GetAbsOrigin();
|
|
if ( ( GetNavigator()->GetGoalPos() - vecCurrentPos ).LengthSqr() > Square( CASW_Weapon_Heal_Gun::GetWeaponRange()*0.5f ) )
|
|
{
|
|
if ( GetNavigator()->GetNavType() != NAV_JUMP )
|
|
{
|
|
if ( !GetNavigator()->UpdateGoalPos( vecCurrentPos ) )
|
|
{
|
|
TaskFail( FAIL_NO_ROUTE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
// otherwise, the move task is complete (either we've arrived or the target is invalid)
|
|
{
|
|
m_hHealTarget = NULL;
|
|
TaskComplete();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_SWAP_TO_HEAL_GUN:
|
|
{
|
|
// this is done in StartTask
|
|
}
|
|
break;
|
|
|
|
case TASK_ASW_HEAL_MARINE:
|
|
{
|
|
CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
if( !m_hHealTarget || !pWeapon || pWeapon->Classify() != CLASS_ASW_HEAL_GUN || !pWeapon->HasAmmo() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
CASW_Weapon_Heal_Gun *pHealgun = static_cast<CASW_Weapon_Heal_Gun*>( pWeapon );
|
|
|
|
bool bHealSucceeded = true;
|
|
|
|
// if the gun to longer has a target...
|
|
if ( m_hHealTarget->GetHealth() <= 0 ||
|
|
m_hHealTarget->GetHealth() >= m_hHealTarget->GetMaxHealth() * MARINE_STOP_HEAL_THRESHOLD )
|
|
{
|
|
TaskComplete();
|
|
bHealSucceeded = false;
|
|
}
|
|
else
|
|
{
|
|
if ( m_hHealTarget->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) >= Square( CASW_Weapon_Heal_Gun::GetWeaponRange()*0.75 ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// facing another direction
|
|
Vector vecAttach = m_hHealTarget->GetAbsOrigin() - GetAbsOrigin();
|
|
Vector vecForward;
|
|
|
|
AngleVectors( ASWEyeAngles(), &vecForward );
|
|
if ( DotProduct( vecForward, vecAttach ) < 0.0f )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !pHealgun || !pHealgun->HasHealAttachTarget() )
|
|
{
|
|
bHealSucceeded = false;
|
|
TaskComplete();
|
|
}
|
|
|
|
if ( bHealSucceeded )
|
|
pHealgun->PrimaryAttack();
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
BaseClass::RunTask(pTask);
|
|
}
|
|
}
|
|
|
|
if (bOld && !m_bWantsToFire)
|
|
{
|
|
//Msg("Stopped firing\n");
|
|
}
|
|
}
|
|
|
|
void CASW_Marine::CheckForAIWeaponSwitch()
|
|
{
|
|
if ( !GetEnemy() )
|
|
return;
|
|
|
|
CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
if ( pWeapon && pWeapon->IsOffensiveWeapon() && pWeapon->HasPrimaryAmmo() )
|
|
return;
|
|
|
|
// see if any of our other inventory items are valid weapons
|
|
for ( int i = 0; i < ASW_NUM_INVENTORY_SLOTS; i++ )
|
|
{
|
|
CASW_Weapon *pOtherWeapon = GetASWWeapon( i );
|
|
if ( pOtherWeapon != pWeapon && pOtherWeapon && pOtherWeapon->IsOffensiveWeapon() && pOtherWeapon->HasPrimaryAmmo() )
|
|
{
|
|
Weapon_Switch( pOtherWeapon );
|
|
m_iHurtWithoutOffensiveWeapon = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if we have no guns with primary ammo, search for one with secondary ammo
|
|
for ( int i = 0; i < ASW_NUM_INVENTORY_SLOTS; i++ )
|
|
{
|
|
CASW_Weapon *pOtherWeapon = GetASWWeapon( i );
|
|
if ( pOtherWeapon != pWeapon && pOtherWeapon && pOtherWeapon->IsOffensiveWeapon() && pOtherWeapon->HasAmmo() )
|
|
{
|
|
Weapon_Switch( pOtherWeapon );
|
|
m_iHurtWithoutOffensiveWeapon = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define ASW_OVERKILL_TIME random->RandomFloat(0.5f,1.0f)
|
|
void CASW_Marine::RunTaskRangeAttack1( const Task_t *pTask )
|
|
{
|
|
m_bWantsToFire = true;
|
|
|
|
AutoMovement( );
|
|
|
|
Vector vecEnemyLKP = GetEnemyLKP();
|
|
|
|
float fAimYaw;
|
|
|
|
if (pTask->iTask != TASK_ASW_OVERKILL_SHOOT)
|
|
fAimYaw = CalcIdealYaw(vecEnemyLKP);
|
|
else
|
|
fAimYaw = CalcIdealYaw(m_vecOverkillPos);
|
|
|
|
//add in our aim error
|
|
fAimYaw += m_fMarineAimError;
|
|
// reduce the aim error
|
|
m_fMarineAimError *= asw_marine_aim_error_decay_multiplier.GetFloat();
|
|
|
|
// If our enemy was killed, but I'm not done animating, the last known position comes
|
|
// back as the origin and makes the me face the world origin if my attack schedule
|
|
// doesn't break when my enemy dies. (sjb)
|
|
if( vecEnemyLKP != vec3_origin )
|
|
{
|
|
if ( ( pTask->iTask == TASK_RANGE_ATTACK1 || pTask->iTask == TASK_RELOAD || pTask->iTask == TASK_ASW_OVERKILL_SHOOT) &&
|
|
( CapabilitiesGet() & bits_CAP_AIM_GUN ) &&
|
|
FInAimCone( vecEnemyLKP ) )
|
|
{
|
|
GetMotor()->SetIdealYawAndUpdate( fAimYaw, AI_KEEP_YAW_SPEED );
|
|
if (pTask->iTask != TASK_ASW_OVERKILL_SHOOT)
|
|
m_fOverkillShootTime = gpGlobals->curtime + ASW_OVERKILL_TIME;
|
|
}
|
|
else
|
|
{
|
|
GetMotor()->SetIdealYawAndUpdate( fAimYaw, AI_KEEP_YAW_SPEED );
|
|
}
|
|
if (pTask->iTask != TASK_ASW_OVERKILL_SHOOT)
|
|
{
|
|
m_vecOverkillPos = vecEnemyLKP;
|
|
}
|
|
}
|
|
|
|
if (pTask->iTask != TASK_ASW_OVERKILL_SHOOT)
|
|
{
|
|
if (gpGlobals->curtime > m_fStartedFiringTime + 0.3f)
|
|
{
|
|
TaskComplete();
|
|
return;
|
|
}
|
|
|
|
if (HasCondition(COND_ENEMY_DEAD)
|
|
|| HasCondition(COND_ENEMY_OCCLUDED)
|
|
|| HasCondition(COND_WEAPON_BLOCKED_BY_FRIEND)
|
|
|| HasCondition(COND_WEAPON_SIGHT_OCCLUDED)
|
|
|| HasCondition(COND_ASW_NEW_ORDERS))
|
|
{
|
|
m_bWantsToFire = false;
|
|
TaskComplete();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (gpGlobals->curtime > m_fOverkillShootTime)
|
|
{
|
|
TaskComplete();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Marine::StartTaskRangeAttack1( const Task_t *pTask )
|
|
{
|
|
if (GetEnemy() == NULL)
|
|
{
|
|
//Msg("Started ranged attack task with no enemy!");
|
|
}
|
|
m_fStartedFiringTime = gpGlobals->curtime;
|
|
BaseClass::StartTaskRangeAttack1(pTask);
|
|
}
|
|
|
|
bool CASW_Marine::SetNewAimError(CBaseEntity *pTarget)
|
|
{
|
|
if (!pTarget)
|
|
return false;
|
|
|
|
// find our current yaw to the enemy
|
|
float currentYaw = UTIL_AngleMod( GetLocalAngles().y );
|
|
Vector enemyDir = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
|
|
VectorNormalize( enemyDir );
|
|
float angleDiff = VecToYaw( enemyDir );
|
|
angleDiff = UTIL_AngleDiff( angleDiff, currentYaw );
|
|
|
|
// create some random error amount, within our angle
|
|
m_fMarineAimError = random->RandomFloat( asw_marine_aim_error_min.GetFloat(), asw_marine_aim_error_max.GetFloat() );
|
|
if ( m_fMarineAimError > fabs( angleDiff ) )
|
|
{
|
|
m_fMarineAimError = fabs( angleDiff );
|
|
}
|
|
|
|
if ( angleDiff > 0 )
|
|
{
|
|
m_fMarineAimError = -m_fMarineAimError;
|
|
}
|
|
|
|
if ( asw_debug_marine_aim.GetBool() )
|
|
{
|
|
Msg( "Set marine's aim error to %f\n", m_fMarineAimError );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// adjusts a target point by our aim error angle
|
|
void CASW_Marine::AddAimErrorToTarget(Vector &vecTarget)
|
|
{
|
|
Vector diff = vecTarget;
|
|
Vector diff_rotated;
|
|
|
|
matrix3x4_t fRotateMatrix;
|
|
AngleMatrix(QAngle(0, m_fMarineAimError, 0), fRotateMatrix);
|
|
VectorRotate( diff, fRotateMatrix, diff_rotated);
|
|
VectorRotate(diff, QAngle(0, m_fMarineAimError, 0), diff_rotated);
|
|
vecTarget = diff_rotated;
|
|
|
|
return;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Return the actual position the NPC wants to fire at when it's trying
|
|
// to hit it's current enemy.
|
|
//-----------------------------------------------------------------------------
|
|
Vector CASW_Marine::GetActualShootPosition( const Vector &shootOrigin )
|
|
{
|
|
// Project the target's location into the future.
|
|
Vector vecEnemyLKP = GetEnemyLKP();
|
|
Vector vecEnemyOffset = GetEnemy()->BodyTarget( shootOrigin ) - GetEnemy()->GetAbsOrigin();
|
|
Vector vecTargetPosition = vecEnemyOffset + vecEnemyLKP;
|
|
|
|
// lead for some fraction of a second.
|
|
return (vecTargetPosition + ( GetEnemy()->GetSmoothedVelocity() * ai_lead_time.GetFloat() ));
|
|
}
|
|
|
|
// potentially miss
|
|
// asw - this is mostly copied from ai_basenpc, but with our added aim error on top
|
|
Vector CASW_Marine::GetActualShootTrajectory( const Vector &shootOrigin )
|
|
{
|
|
const Task_t *pCurTask = GetTask();
|
|
if ( pCurTask && pCurTask->iTask == TASK_ASW_OVERKILL_SHOOT )
|
|
{
|
|
// if we're overkill shooting, fire at the overkill pos
|
|
Vector shotDir = m_vecOverkillPos+Vector(0,0,45) - shootOrigin;
|
|
VectorNormalize( shotDir );
|
|
|
|
CollectShotStats( shootOrigin, shotDir );
|
|
CShotManipulator manipulator( shotDir );
|
|
manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) );
|
|
shotDir = manipulator.GetResult();
|
|
return shotDir;
|
|
}
|
|
|
|
Vector vecShotDir;
|
|
|
|
if( !GetEnemy() )
|
|
{
|
|
vecShotDir = GetShootEnemyDir( shootOrigin );
|
|
}
|
|
else
|
|
{
|
|
|
|
Vector vecProjectedPosition = GetActualShootPosition( shootOrigin );
|
|
|
|
vecShotDir = vecProjectedPosition - shootOrigin;
|
|
VectorNormalize( vecShotDir );
|
|
|
|
// NOW we have a shoot direction. Where a 100% accurate bullet should go.
|
|
// Modify it by weapon proficiency.
|
|
// construct a manipulator
|
|
CShotManipulator manipulator( vecShotDir );
|
|
|
|
manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) );
|
|
vecShotDir = manipulator.GetResult();
|
|
}
|
|
|
|
AddAimErrorToTarget( vecShotDir ); // asw
|
|
|
|
return vecShotDir;
|
|
}
|
|
|
|
void CASW_Marine::DrawDebugGeometryOverlays()
|
|
{
|
|
if (GetASWOrders() == ASW_ORDER_MOVE_TO)
|
|
{
|
|
float flViewRange = acos(0.8);
|
|
Vector vEyeDir = EyeDirection2D( );
|
|
Vector vLeftDir, vRightDir;
|
|
float fSin, fCos;
|
|
SinCos( flViewRange, &fSin, &fCos );
|
|
|
|
vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
|
|
vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
|
|
vLeftDir.z = vEyeDir.z;
|
|
fSin = sin(-flViewRange);
|
|
fCos = cos(-flViewRange);
|
|
vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
|
|
vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
|
|
vRightDir.z = vEyeDir.z;
|
|
|
|
//NDebugOverlay::BoxDirection(m_vecMoveToOrderPos, Vector(0,0,-40), Vector(200,0,40), vLeftDir, 255, 0, 0, 50, 0 );
|
|
//NDebugOverlay::BoxDirection(m_vecMoveToOrderPos, Vector(0,0,-40), Vector(200,0,40), vRightDir, 255, 0, 0, 50, 0 );
|
|
//NDebugOverlay::BoxDirection(m_vecMoveToOrderPos, Vector(0,0,-40), Vector(200,0,40), vEyeDir, 0, 255, 0, 50, 0 );
|
|
NDebugOverlay::Box(m_vecMoveToOrderPos, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 );
|
|
}
|
|
|
|
BaseClass::DrawDebugGeometryOverlays();
|
|
|
|
if ( GetSquadFormation() )
|
|
{
|
|
GetSquadFormation()->DrawDebugGeometryOverlays();
|
|
}
|
|
}
|
|
|
|
ConVar asw_marine_face_last_enemy_time( "asw_marine_face_last_enemy_time", "5.0", FCVAR_CHEAT, "Amount of time that AI marines will face their last enemy after losing it while in follow mode" );
|
|
|
|
void CASW_Marine::UpdateFacing()
|
|
{
|
|
if ( gpGlobals->curtime > m_flNextYawOffsetTime )
|
|
{
|
|
RecalculateAIYawOffset();
|
|
}
|
|
|
|
if ( m_vecFacingPointFromServer.Get() != vec3_origin )
|
|
{
|
|
float flAimYaw = UTIL_VecToYaw( m_vecFacingPointFromServer.Get() - GetAbsOrigin() );
|
|
GetMotor()->SetIdealYawAndUpdate( flAimYaw );
|
|
}
|
|
else if ( GetEnemy() )
|
|
{
|
|
Vector vecEnemyLKP = GetEnemyLKP();
|
|
float flAimYaw = CalcIdealYaw( vecEnemyLKP ) + m_fMarineAimError;
|
|
GetMotor()->SetIdealYawAndUpdate( flAimYaw );
|
|
|
|
m_flLastEnemyYaw = flAimYaw;
|
|
m_flLastEnemyYawTime = gpGlobals->curtime;
|
|
m_flAIYawOffset = 0;
|
|
|
|
if ( asw_debug_marine_aim.GetBool() )
|
|
{
|
|
Vector vecAim;
|
|
QAngle angAim = QAngle( 0, flAimYaw, 0 );
|
|
AngleVectors( angAim, &vecAim );
|
|
|
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vecAim * 50, 255, 255, 0, false, 0.1f );
|
|
NDebugOverlay::Line( GetAbsOrigin(), vecEnemyLKP, 255, 255, 255, false, 0.1f );
|
|
Msg( "aim error = %f fAimYaw = %f\n", m_fMarineAimError, flAimYaw );
|
|
}
|
|
}
|
|
else if ( IsCurSchedule( SCHED_ASW_MOVE_TO_ORDER_POS ) ) // face our order destination
|
|
{
|
|
Vector vecEnemyLKP = GetEnemyLKP();
|
|
float flAimYaw = CalcIdealYaw( m_vecMoveToOrderPos );
|
|
GetMotor()->SetIdealYawAndUpdate( flAimYaw );
|
|
|
|
if ( asw_debug_marine_aim.GetBool() )
|
|
{
|
|
Vector vecAim;
|
|
QAngle angAim = QAngle( 0, flAimYaw, 0 );
|
|
AngleVectors( angAim, &vecAim );
|
|
|
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vecAim * 50, 255, 255, 0, false, 0.1f );
|
|
NDebugOverlay::Line( GetAbsOrigin(), vecEnemyLKP, 255, 255, 255, false, 0.1f );
|
|
Msg( "aim error = %f fAimYaw = %f\n", m_fMarineAimError, flAimYaw );
|
|
}
|
|
}
|
|
else if ( IsCurSchedule( SCHED_ASW_HEAL_MARINE ) ) // face the marine that we want to heal
|
|
{
|
|
if ( m_hHealTarget.Get() )
|
|
{
|
|
float flAimYaw = CalcIdealYaw( m_hHealTarget.Get()->GetAbsOrigin() );
|
|
GetMotor()->SetIdealYawAndUpdate( flAimYaw );
|
|
|
|
if ( asw_debug_marine_aim.GetBool() )
|
|
{
|
|
Vector vecAim;
|
|
QAngle angAim = QAngle( 0, flAimYaw, 0 );
|
|
AngleVectors( angAim, &vecAim );
|
|
|
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vecAim * 50, 0, 255, 0, false, 0.2f );
|
|
}
|
|
}
|
|
}
|
|
else if ( GetASWOrders() == ASW_ORDER_FOLLOW )
|
|
{
|
|
float flAimYaw = GetSquadFormation()->GetYaw( GetSquadFormation()->Find(this) );
|
|
if ( gpGlobals->curtime < m_flLastEnemyYawTime + asw_marine_face_last_enemy_time.GetFloat() )
|
|
{
|
|
flAimYaw = m_flLastEnemyYaw;
|
|
}
|
|
flAimYaw += m_flAIYawOffset;
|
|
GetMotor()->SetIdealYawAndUpdate( flAimYaw );
|
|
|
|
if ( asw_debug_marine_aim.GetBool() )
|
|
{
|
|
Vector vecAim;
|
|
QAngle angAim = QAngle( 0, flAimYaw, 0 );
|
|
AngleVectors( angAim, &vecAim );
|
|
|
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vecAim * 50, 255, 255, 0, false, 0.1f );
|
|
Msg( "aim error = %f fAimYaw = %f m_flAIYawOffset = %f\n", m_fMarineAimError, flAimYaw, m_flAIYawOffset );
|
|
}
|
|
}
|
|
// else
|
|
// {
|
|
// // face our destination
|
|
// Vector vecDest = GetNavigator()->GetGoalPos();
|
|
//
|
|
// float fAimYaw = CalcIdealYaw( vecDest );// + m_fMarineAimError; // TODO: put aim error back in
|
|
// GetMotor()->SetIdealYawAndUpdate( fAimYaw );
|
|
// }
|
|
}
|
|
|
|
bool CASW_Marine::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
|
|
{
|
|
// make him looking towards his holding direction as he moves
|
|
/*
|
|
if (GetASWOrders() == ASW_ORDER_MOVE_TO)
|
|
{
|
|
QAngle holding(0, m_fHoldingYaw,0);
|
|
Vector vecForward;
|
|
AngleVectors(holding, &vecForward);
|
|
|
|
Vector vDest = GetNavigator()->GetGoalPos();
|
|
Msg("Adding facing target..\n");
|
|
AddFacingTarget( vDest + vecForward * 100.0f, 10.0f, 1.0f );
|
|
}*/
|
|
|
|
// if we're moving to a specific spot, make sure we face our enemy so we can shoot while on the move
|
|
if (GetEnemy() && (GetASWOrders()==ASW_ORDER_MOVE_TO || GetASWOrders() == ASW_ORDER_FOLLOW))
|
|
{
|
|
// ASWTODO - is this move_yaw set needed? (or needs to be move_x/y?)
|
|
//float flMoveYaw = UTIL_VecToYaw( move.dir );
|
|
|
|
//float idealYaw = UTIL_AngleMod( flMoveYaw );
|
|
//float flEYaw = UTIL_VecToYaw( GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter() );
|
|
|
|
// UpdateFacing();
|
|
|
|
// find movement direction to compensate for not being turned far enough
|
|
// ASWTODO - is this move_yaw set needed? (or needs to be move_x/y?)
|
|
/*
|
|
float fSequenceMoveYaw = GetSequenceMoveYaw( GetSequence() );
|
|
float flDiff = UTIL_AngleDiff( flMoveYaw, GetLocalAngles().y + fSequenceMoveYaw );
|
|
SetPoseParameter( "move_yaw", GetPoseParameter( "move_yaw" ) + flDiff );
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
|
|
return BaseClass::OverrideMoveFacing( move, flInterval );
|
|
}
|
|
|
|
// check for AI changing weapon if he's getting hurt and has a non-offensive weapon equipped
|
|
bool CASW_Marine::CheckAutoWeaponSwitch()
|
|
{
|
|
CASW_Weapon *pWeapon = GetActiveASWWeapon();
|
|
if (pWeapon && pWeapon->IsOffensiveWeapon())
|
|
return true;
|
|
|
|
// marine doesn't auto switch weapons the first two times he's hurt
|
|
m_iHurtWithoutOffensiveWeapon++;
|
|
if (m_iHurtWithoutOffensiveWeapon <3)
|
|
return false;
|
|
|
|
// find best offensive weapon
|
|
CASW_Weapon *pBestWeapon = NULL;
|
|
for (int i=0;i<ASW_MAX_MARINE_WEAPONS;i++)
|
|
{
|
|
pWeapon = GetASWWeapon(i);
|
|
if (pWeapon && pWeapon->IsOffensiveWeapon())
|
|
{
|
|
pBestWeapon = pWeapon;
|
|
break;
|
|
}
|
|
}
|
|
if (pBestWeapon)
|
|
{
|
|
Weapon_Switch( pWeapon );
|
|
m_iHurtWithoutOffensiveWeapon = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
inline const Vector & CASW_Marine::GetFollowPos()
|
|
{
|
|
m_hMarineFollowTarget = GetSquadLeader();
|
|
|
|
// if I'm in a squad and it has a leader, then
|
|
// use the computed position. Otherwise fall
|
|
// back to current pos.
|
|
unsigned slot = GetSquadFormation()->Find(this);
|
|
return ( GetSquadFormation()->IsValid(slot) ?
|
|
GetSquadFormation()->GetIdealPosition(slot) :
|
|
GetAbsOrigin() );
|
|
}
|
|
|
|
// ===== Rappeling ===================================
|
|
|
|
void CASW_Marine::BeginRappel()
|
|
{
|
|
m_bWaitingToRappel = true;
|
|
m_bOnGround = false;
|
|
RemoveFlag(FL_ONGROUND);
|
|
|
|
// Send the message to begin rappeling!
|
|
SetCondition( COND_ASW_BEGIN_RAPPEL );
|
|
|
|
m_vecRopeAnchor = GetAbsOrigin();
|
|
}
|
|
|
|
void CASW_Marine::CutZipline()
|
|
{
|
|
if( m_hLine.Get() )
|
|
{
|
|
UTIL_Remove( m_hLine );
|
|
}
|
|
|
|
// create one just hanging down in its place
|
|
CBeam *pBeam;
|
|
pBeam = CBeam::BeamCreate( "cable/cable.vmt", 1 );
|
|
pBeam->SetColor( 150, 150, 150 );
|
|
pBeam->SetWidth( 1.0 ); // was 0.3
|
|
pBeam->SetEndWidth( 1.0 );
|
|
|
|
//pBeam->PointEntInit( GetAbsOrigin() + Vector( 0, 0, 80 ), this );
|
|
|
|
//pBeam->SetEndAttachment( attachment );
|
|
//pBeam->SetAbsEndPos(GetAbsOrigin());
|
|
pBeam->PointsInit(m_vecRopeAnchor, GetAbsOrigin());
|
|
|
|
if( m_hLine.Get() )
|
|
{
|
|
UTIL_Remove( m_hLine );
|
|
}
|
|
m_hLine.Set( pBeam );
|
|
|
|
//CBaseEntity *pAnchor = CreateEntityByName( "asw_rope_anchor" );
|
|
//pAnchor->SetOwnerEntity( this );
|
|
//pAnchor->SetAbsOrigin( m_vecRopeAnchor );
|
|
//pAnchor->Spawn();
|
|
}
|
|
|
|
|
|
void CASW_Marine::CreateZipline()
|
|
{
|
|
if( !m_hLine )
|
|
{
|
|
int attachment = LookupAttachment( "zipline" );
|
|
|
|
if( attachment != -1 )
|
|
{
|
|
CBeam *pBeam;
|
|
pBeam = CBeam::BeamCreate( "cable/cable.vmt", 1 );
|
|
pBeam->SetColor( 150, 150, 150 );
|
|
pBeam->SetWidth( 1.0 ); // was 0.3
|
|
pBeam->SetEndWidth( 1.0 );
|
|
|
|
pBeam->PointEntInit( GetAbsOrigin() + Vector( 0, 0, 80 ), this );
|
|
|
|
pBeam->SetEndAttachment( attachment );
|
|
|
|
m_hLine.Set( pBeam );
|
|
}
|
|
}
|
|
}
|
|
|
|
#define RAPPEL_MAX_SPEED 600 // Go this fast if you're really high.
|
|
#define RAPPEL_MIN_SPEED 60 // Go no slower than this.
|
|
#define RAPPEL_DECEL_DIST (20.0f * 12.0f) // Start slowing down when you're this close to the ground.
|
|
void CASW_Marine::SetDescentSpeed()
|
|
{
|
|
// Trace to the floor and see how close we're getting. Slow down if we're close.
|
|
// STOP if there's an NPC under us.
|
|
trace_t tr;
|
|
AI_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 8192 ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
|
|
|
|
float flDist = fabs( GetAbsOrigin().z - tr.endpos.z );
|
|
|
|
float speed = RAPPEL_MAX_SPEED;
|
|
|
|
if( flDist <= RAPPEL_DECEL_DIST )
|
|
{
|
|
float factor;
|
|
factor = flDist / RAPPEL_DECEL_DIST;
|
|
|
|
speed = MAX( RAPPEL_MIN_SPEED, speed * factor );
|
|
}
|
|
|
|
Vector vecNewVelocity = vec3_origin;
|
|
vecNewVelocity.z = -speed;
|
|
SetAbsVelocity( vecNewVelocity );
|
|
}
|
|
|
|
void CASW_Marine::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput )
|
|
{
|
|
BaseClass::CleanupOnDeath( pCulprit, bFireDeathOutput );
|
|
|
|
//This will remove the beam and create a rope if the NPC dies while rappeling down.
|
|
if ( m_hLine )
|
|
{
|
|
CutZipline();
|
|
}
|
|
}
|
|
|
|
|
|
// use head direction to determine if spot is shootable
|
|
bool CASW_Marine::FInAimCone( const Vector &vecSpot )
|
|
{
|
|
return BaseClass::FInAimCone( vecSpot );
|
|
|
|
Vector los = ( vecSpot - Weapon_ShootPosition() );
|
|
|
|
// do this in 2D
|
|
los.z = 0;
|
|
VectorNormalize( los );
|
|
|
|
Vector facingDir = HeadDirection2D( );
|
|
|
|
float flDot = DotProduct( los, facingDir );
|
|
|
|
if (CapabilitiesGet() & bits_CAP_AIM_GUN)
|
|
{
|
|
// FIXME: query current animation for ranges
|
|
return ( flDot > DOT_30DEGREE );
|
|
}
|
|
|
|
if ( flDot > 0.994 )//!!!BUGBUG - magic number same as FacingIdeal(), what is this?
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void CASW_Marine::OnWeldStarted()
|
|
{
|
|
m_bWaitingForWeld = true;
|
|
m_flBeginWeldTime = gpGlobals->curtime;
|
|
SetCondition( COND_ASW_NEW_ORDERS );
|
|
}
|
|
|
|
void CASW_Marine::OnWeldFinished()
|
|
{
|
|
m_bWaitingForWeld = false;
|
|
SetCondition( COND_ASW_NEW_ORDERS );
|
|
}
|
|
|
|
ConVar asw_debug_combat_status( "asw_debug_combat_status", "0", FCVAR_CHEAT, "Show marine's combat status on the screen" );
|
|
|
|
void CASW_Marine::UpdateCombatStatus()
|
|
{
|
|
const float flCombatEnemyDist = 512.0f;
|
|
const float flNearbyMarineDist = 768.0f;
|
|
CASW_Game_Resource *pGameResource = ASWGameResource();
|
|
|
|
if ( !IsInhabited() && GetEnemy() && GetEnemy()->GetAbsOrigin().DistTo( GetAbsOrigin() ) < flCombatEnemyDist )
|
|
{
|
|
m_flLastSquadEnemyTime = gpGlobals->curtime;
|
|
}
|
|
else
|
|
{
|
|
// check nearby squad mates
|
|
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->IsInhabited() && pOtherMarine->GetEnemy()
|
|
&& pOtherMarine->GetAbsOrigin().DistTo( GetAbsOrigin() ) < flNearbyMarineDist
|
|
&& pOtherMarine->GetEnemy()->GetAbsOrigin().DistTo( pOtherMarine->GetAbsOrigin() ) < flCombatEnemyDist )
|
|
{
|
|
m_flLastSquadEnemyTime = gpGlobals->curtime;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_flLastSquadShotAlienTime = 0.0f;
|
|
|
|
for ( int i = 0; i < pGameResource->GetMaxMarineResources(); i++ )
|
|
{
|
|
CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
|
|
CASW_Marine *pOtherMarine = pMR ? pMR->GetMarineEntity() : NULL;
|
|
if ( pOtherMarine != this && pOtherMarine && pOtherMarine->GetAbsOrigin().DistTo( GetAbsOrigin() ) < flNearbyMarineDist )
|
|
{
|
|
m_flLastSquadShotAlienTime = MAX( m_flLastSquadShotAlienTime, pOtherMarine->m_flLastHurtAlienTime );
|
|
}
|
|
}
|
|
|
|
if ( asw_debug_combat_status.GetBool() )
|
|
{
|
|
engine->Con_NPrintf( GetMarineResource()->m_MarineProfileIndex, "%s: %s", GetMarineProfile()->GetShortName(), IsInCombat() ? "COMBAT" : "not in combat" );
|
|
}
|
|
}
|
|
|
|
ConVar asw_marine_force_combat_status( "asw_marine_force_combat_status", "0", FCVAR_CHEAT );
|
|
|
|
bool CASW_Marine::IsInCombat()
|
|
{
|
|
if ( asw_marine_force_combat_status.GetBool() )
|
|
return true;
|
|
|
|
const float flCombatTime = 10.0f;
|
|
return ( ( m_flLastSquadEnemyTime != 0.0f && m_flLastSquadEnemyTime > gpGlobals->curtime - flCombatTime ) ||
|
|
( m_flLastSquadShotAlienTime != 0.0f && m_flLastSquadShotAlienTime > gpGlobals->curtime - flCombatTime ) );
|
|
}
|
|
|
|
bool CASW_Marine::FValidateHintType( CAI_Hint *pHint )
|
|
{
|
|
if ( pHint->HintType() == HINT_FOLLOW_WAIT_POINT )
|
|
return true;
|
|
|
|
return BaseClass::FValidateHintType( pHint );
|
|
}
|
|
|
|
ConVar asw_marine_random_yaw( "asw_marine_random_yaw", "40", FCVAR_NONE, "Min/max angle the marine will change his yaw when idling in follow mode" );
|
|
ConVar asw_marine_yaw_interval_min( "asw_marine_yaw_interval_min", "3", FCVAR_NONE, "Min time between AI marine shifting his yaw" );
|
|
ConVar asw_marine_yaw_interval_max( "asw_marine_yaw_interval_max", "8", FCVAR_NONE, "Max time between AI marine shifting his yaw" );
|
|
ConVar asw_marine_toggle_crouch_chance( "asw_marine_toggle_crouch_chance", "0.4", FCVAR_NONE, "Chance of AI changing between crouched and non-crouched while idling in follow mode" );
|
|
|
|
void CASW_Marine::RecalculateAIYawOffset()
|
|
{
|
|
m_flAIYawOffset = RandomFloat( -asw_marine_random_yaw.GetFloat(), asw_marine_random_yaw.GetFloat() );
|
|
if ( asw_blind_follow.GetBool() )
|
|
{
|
|
m_bAICrouch = ( GetASWOrders() == ASW_ORDER_HOLD_POSITION );
|
|
}
|
|
else if ( RandomFloat() < asw_marine_toggle_crouch_chance.GetFloat() )
|
|
{
|
|
m_bAICrouch = !m_bAICrouch;
|
|
}
|
|
m_flNextYawOffsetTime = gpGlobals->curtime + RandomFloat( asw_marine_yaw_interval_min.GetFloat(), asw_marine_yaw_interval_max.GetFloat() );
|
|
}
|
|
|
|
// disables collision between the marine and certain props that the marine cannot break reliably
|
|
void CASW_Marine::CheckForDisablingAICollision( CBaseEntity *pEntity )
|
|
{
|
|
if ( !pEntity )
|
|
return;
|
|
|
|
// don't disable collision between the marine and breakable crates
|
|
const char *szModelName = STRING( pEntity->GetModelName() );
|
|
if ( !szModelName )
|
|
return;
|
|
|
|
if ( !CanMarineGetStuckOnProp( szModelName ) )
|
|
return;
|
|
|
|
PhysDisableEntityCollisions( this, pEntity );
|
|
SetPhysicsPropTarget( NULL );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Schedules
|
|
//-----------------------------------------------------------------------------
|
|
AI_BEGIN_CUSTOM_NPC( asw_marine, CASW_Marine )
|
|
|
|
DECLARE_CONDITION( COND_ASW_NEW_ORDERS )
|
|
DECLARE_CONDITION( COND_ASW_BEGIN_RAPPEL )
|
|
DECLARE_CONDITION( COND_SQUADMATE_WANTS_AMMO )
|
|
DECLARE_CONDITION( COND_SQUADMATE_NEEDS_AMMO )
|
|
DECLARE_CONDITION( COND_PATH_BLOCKED_BY_PHYSICS_PROP )
|
|
DECLARE_CONDITION( COND_PROP_DESTROYED )
|
|
DECLARE_CONDITION( COND_COMPLETELY_OUT_OF_AMMO )
|
|
|
|
DECLARE_TASK( TASK_ASW_FACE_HOLDING_YAW )
|
|
DECLARE_TASK( TASK_ASW_FACE_USING_ITEM )
|
|
DECLARE_TASK( TASK_ASW_START_USING_AREA )
|
|
DECLARE_TASK( TASK_ASW_FACE_ENEMY_WITH_ERROR )
|
|
DECLARE_TASK( TASK_ASW_OVERKILL_SHOOT )
|
|
DECLARE_TASK( TASK_ASW_GET_PATH_TO_ORDER_POS )
|
|
DECLARE_TASK( TASK_ASW_GET_PATH_TO_FOLLOW_TARGET )
|
|
DECLARE_TASK( TASK_ASW_GET_PATH_TO_PROP )
|
|
DECLARE_TASK( TASK_ASW_FACE_FOLLOW_WAIT )
|
|
DECLARE_TASK( TASK_ASW_GET_BACK_OFF_PATH )
|
|
DECLARE_TASK( TASK_ASW_WAIT_FOR_FOLLOW_MOVEMENT )
|
|
DECLARE_TASK( TASK_ASW_WAIT_FOR_MOVEMENT )
|
|
DECLARE_TASK( TASK_ASW_CHATTER_CONFIRM )
|
|
DECLARE_TASK( TASK_ASW_RAPPEL )
|
|
DECLARE_TASK( TASK_ASW_HIT_GROUND )
|
|
DECLARE_TASK( TASK_ASW_ORDER_TO_DEPLOY_SPOT )
|
|
DECLARE_TASK( TASK_ASW_GET_PATH_TO_GIVE_AMMO )
|
|
DECLARE_TASK( TASK_ASW_MOVE_TO_GIVE_AMMO )
|
|
DECLARE_TASK( TASK_ASW_SWAP_TO_AMMO_BAG )
|
|
DECLARE_TASK( TASK_ASW_GIVE_AMMO_TO_MARINE )
|
|
DECLARE_TASK( TASK_ASW_GET_PATH_TO_HEAL )
|
|
DECLARE_TASK( TASK_ASW_MOVE_TO_HEAL )
|
|
DECLARE_TASK( TASK_ASW_SWAP_TO_HEAL_GUN )
|
|
DECLARE_TASK( TASK_ASW_HEAL_MARINE )
|
|
|
|
DECLARE_ANIMEVENT( AE_MARINE_KICK )
|
|
DECLARE_ANIMEVENT( AE_MARINE_UNFREEZE )
|
|
// Activities
|
|
DECLARE_ACTIVITY( ACT_MARINE_GETTING_UP )
|
|
DECLARE_ACTIVITY( ACT_MARINE_LAYING_ON_FLOOR )
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_HOLD_POSITION,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
//" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_ASW_FACE_HOLDING_YAW 2"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_SEE_ENEMY"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_CAN_RANGE_ATTACK2"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
" COND_CAN_MELEE_ATTACK2"
|
|
" COND_IDLE_INTERRUPT"
|
|
" COND_ASW_NEW_ORDERS"
|
|
" COND_ASW_BEGIN_RAPPEL"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_USE_AREA,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_ASW_START_USING_AREA 0"
|
|
" TASK_ASW_FACE_USING_ITEM 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_SEE_ENEMY"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_CAN_RANGE_ATTACK2"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
" COND_CAN_MELEE_ATTACK2"
|
|
" COND_IDLE_INTERRUPT"
|
|
" COND_ASW_NEW_ORDERS"
|
|
" COND_ASW_BEGIN_RAPPEL"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_USING_OVER_TIME,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_ASW_FACE_USING_ITEM 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_SEE_ENEMY"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_CAN_RANGE_ATTACK2"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
" COND_CAN_MELEE_ATTACK2"
|
|
" COND_IDLE_INTERRUPT"
|
|
" COND_ASW_NEW_ORDERS"
|
|
" COND_ASW_BEGIN_RAPPEL"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_OVERKILL_SHOOT,
|
|
|
|
" Tasks"
|
|
" TASK_ASW_OVERKILL_SHOOT 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_NO_PRIMARY_AMMO"
|
|
" COND_ASW_NEW_ORDERS"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_RANGE_ATTACK1,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_ASW_FACE_ENEMY_WITH_ERROR 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
" TASK_RANGE_ATTACK1 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_ENEMY_OCCLUDED"
|
|
" COND_NO_PRIMARY_AMMO"
|
|
" COND_HEAR_DANGER"
|
|
" COND_WEAPON_BLOCKED_BY_FRIEND"
|
|
" COND_WEAPON_SIGHT_OCCLUDED"
|
|
" COND_LOST_ENEMY"
|
|
" COND_ASW_NEW_ORDERS"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_MOVE_TO_ORDER_POS,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASW_FOLLOW_WAIT"
|
|
" TASK_ASW_GET_PATH_TO_ORDER_POS 0"
|
|
" TASK_ASW_CHATTER_CONFIRM 0.4"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_ASW_WAIT_FOR_MOVEMENT 0"
|
|
" TASK_STOP_MOVING 1"
|
|
""
|
|
" Interrupts"
|
|
" COND_ASW_NEW_ORDERS"
|
|
" COND_NEW_ENEMY"
|
|
" COND_SEE_ENEMY"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_CAN_RANGE_ATTACK2"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_FOLLOW_MOVE,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASW_FOLLOW_WAIT"
|
|
" TASK_ASW_GET_PATH_TO_FOLLOW_TARGET 0"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_ASW_WAIT_FOR_FOLLOW_MOVEMENT 0"
|
|
" TASK_STOP_MOVING 1"
|
|
""
|
|
" Interrupts"
|
|
" COND_ASW_NEW_ORDERS"
|
|
" COND_NEW_ENEMY"
|
|
" COND_SEE_ENEMY"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_CAN_RANGE_ATTACK2"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_FOLLOW_WAIT,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_ASW_FACE_FOLLOW_WAIT 0.3"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_SEE_ENEMY"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_CAN_RANGE_ATTACK2"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
" COND_CAN_MELEE_ATTACK2"
|
|
" COND_IDLE_INTERRUPT"
|
|
" COND_ASW_NEW_ORDERS"
|
|
" COND_GIVE_WAY"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_PICKUP_WAIT,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_WAIT 1.5"
|
|
""
|
|
" Interrupts"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_FOLLOW_BACK_OFF,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASW_FOLLOW_WAIT"
|
|
" TASK_ASW_GET_BACK_OFF_PATH 0"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
" TASK_STOP_MOVING 1"
|
|
""
|
|
" Interrupts"
|
|
" COND_ASW_NEW_ORDERS"
|
|
" COND_NEW_ENEMY"
|
|
" COND_SEE_ENEMY"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_CAN_RANGE_ATTACK2"
|
|
)
|
|
|
|
//===============================================
|
|
//===============================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_RAPPEL_WAIT,
|
|
|
|
" Tasks"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_RAPPEL_LOOP"
|
|
" TASK_WAIT_INDEFINITE 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_ASW_BEGIN_RAPPEL"
|
|
);
|
|
|
|
//===============================================
|
|
//===============================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_RAPPEL,
|
|
|
|
" Tasks"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_RAPPEL_LOOP"
|
|
" TASK_WAIT 0.2"
|
|
" TASK_ASW_RAPPEL 0"
|
|
" TASK_ASW_HIT_GROUND 0"
|
|
" TASK_ASW_ORDER_TO_DEPLOY_SPOT 0"
|
|
//" TASK_SET_SCHEDULE SCHEDULE:SCHED_ASW_CLEAR_RAPPEL_POINT"
|
|
""
|
|
" Interrupts"
|
|
""
|
|
" COND_NEW_ENEMY" // Only so the enemy selection code will pick an enemy!
|
|
);
|
|
|
|
//===============================================
|
|
//===============================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_CLEAR_RAPPEL_POINT,
|
|
|
|
" Tasks"
|
|
" TASK_ASW_HIT_GROUND 0"
|
|
" TASK_MOVE_AWAY_PATH 128" // Clear this spot for other rappellers
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
""
|
|
" Interrupts"
|
|
""
|
|
);
|
|
|
|
//===============================================
|
|
//===============================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_GIVE_AMMO,
|
|
|
|
" Tasks"
|
|
" TASK_ASW_GET_PATH_TO_GIVE_AMMO 0"
|
|
" TASK_ASW_MOVE_TO_GIVE_AMMO 0"
|
|
" TASK_ASW_SWAP_TO_AMMO_BAG 0"
|
|
" TASK_ASW_GIVE_AMMO_TO_MARINE 0"
|
|
""
|
|
" Interrupts"
|
|
""
|
|
);
|
|
|
|
//===============================================
|
|
//===============================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_HEAL_MARINE,
|
|
|
|
" Tasks"
|
|
" TASK_ASW_GET_PATH_TO_HEAL 0"
|
|
" TASK_ASW_MOVE_TO_HEAL 0"
|
|
" TASK_ASW_SWAP_TO_HEAL_GUN 0"
|
|
" TASK_ASW_HEAL_MARINE 0"
|
|
""
|
|
" Interrupts"
|
|
""
|
|
);
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_MELEE_ATTACK_PROP1,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_ASW_GET_PATH_TO_PROP 40" // 40 = attack distance
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
" TASK_MELEE_ATTACK1 1"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
" TASK_MELEE_ATTACK1 2"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
" TASK_MELEE_ATTACK1 3"
|
|
""
|
|
" Interrupts"
|
|
//" COND_NEW_ENEMY"
|
|
" COND_PROP_DESTROYED"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_ENEMY_OCCLUDED"
|
|
);
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ENGAGE_AND_MELEE_ATTACK1,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_GET_CHASE_PATH_TO_ENEMY 40" // 40 = attack distance
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
" TASK_MELEE_ATTACK1 1"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
" TASK_MELEE_ATTACK1 2"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
" TASK_MELEE_ATTACK1 3"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_ENEMY_OCCLUDED"
|
|
);
|
|
|
|
AI_END_CUSTOM_NPC()
|
|
|
|
// DECLARE_TASK( TASK_ASW_TRACK_AND_FIRE )
|
|
|