1769 lines
59 KiB
Plaintext
1769 lines
59 KiB
Plaintext
#using scripts\shared\ai_shared;
|
|
#using scripts\shared\clientfield_shared;
|
|
#using scripts\shared\fx_shared;
|
|
#using scripts\shared\math_shared;
|
|
#using scripts\shared\scene_shared;
|
|
#using scripts\shared\spawner_shared;
|
|
#using scripts\shared\util_shared;
|
|
#using scripts\shared\gameobjects_shared;
|
|
#using scripts\shared\laststand_shared;
|
|
#using scripts\shared\system_shared;
|
|
|
|
#using scripts\shared\ai\systems\animation_state_machine_notetracks;
|
|
#using scripts\shared\ai\systems\animation_state_machine_utility;
|
|
#using scripts\shared\ai\archetype_utility;
|
|
#using scripts\shared\ai\archetype_locomotion_utility;
|
|
#using scripts\shared\ai\systems\behavior_tree_utility;
|
|
#using scripts\shared\ai\systems\blackboard;
|
|
#using scripts\shared\ai\systems\ai_blackboard;
|
|
#using scripts\shared\ai\systems\debug;
|
|
#using scripts\shared\ai\archetype_mocomps_utility;
|
|
#using scripts\shared\ai\systems\ai_interface;
|
|
#using scripts\shared\ai\archetype_warlord_interface;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/# #/
|
|
|
|
#precache( "fx", "vehicle/fx_quadtank_airburst" );
|
|
#precache( "fx", "vehicle/fx_quadtank_airburst_ground" );
|
|
|
|
///***********************************WARLORD BEHAVIOR SUMMERY*******************************************//
|
|
//
|
|
//Fire Behavior
|
|
//-Threat (2 or more attackers)
|
|
//-Angry (2 or more attackers)
|
|
//-Exposed pain cooldown and pain don't interrupt shooting
|
|
|
|
//Move Behavior
|
|
//-Move from dangerous place (2 or more attackers)
|
|
//-Hunt Enemy
|
|
//-Prefered Locations
|
|
//-Juke
|
|
//*********************************************************************************************************//
|
|
|
|
function autoexec __init__sytem__() { system::register("warlord",&__init__,undefined,undefined); }
|
|
|
|
function __init__()
|
|
{
|
|
// INIT BLACKBOARD
|
|
spawner::add_archetype_spawn_function( "warlord", &WarlordBehavior::ArchetypeWarlordBlackboardInit );
|
|
|
|
// INIT WARLORD ON SPAWN
|
|
spawner::add_archetype_spawn_function( "warlord", &WarlordServerUtils::warlordSpawnSetup );
|
|
|
|
// CLIENTFIELDS
|
|
if( ai::shouldRegisterClientFieldForArchetype( "warlord" ) )
|
|
{
|
|
clientfield::register("actor", "warlord_damage_state", 1, 2, "int");
|
|
clientfield::register("actor", "warlord_thruster_direction", 1, 3, "int");
|
|
clientfield::register("actor", "warlord_type", 1, 2, "int");
|
|
clientfield::register("actor", "warlord_lights_state", 1, 1, "int" );
|
|
}
|
|
|
|
//REGISTER INTERFACE ATTRIBUTES
|
|
WarlordInterface::RegisterWarlordInterfaceAttributes();
|
|
}
|
|
|
|
#namespace WarlordBehavior;
|
|
|
|
function autoexec RegisterBehaviorScriptFunctions()
|
|
{
|
|
// ------- WARLORD CONDITIONS -----------//
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("warlordCanJukeCondition",&canJukeCondition);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("warlordCanTacticalJukeCondition",&canTacticalJukeCondition);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("warlordShouldBeAngryCondition",&shouldBeAngryCondition);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("warlordShouldNormalMelee",&warlordShouldNormalMelee);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("warlordCanTakePainCondition",&canTakePainCondition);;
|
|
|
|
// ------- WARLORD FUNCTIONS -----------//
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("warlordExposedPainActionStart",&exposedPainActionStart);;
|
|
|
|
// ------- WARLORD ACTIONS -----------//
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeAction("warlordDeathAction",&deathAction,undefined,undefined);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeAction("warlordJukeAction",&jukeAction,undefined,&jukeActionTerminate);;
|
|
|
|
// ------- WARLORD SERVICES -----------//
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("chooseBetterPositionService",&chooseBetterPositionService);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("WarlordAngryAttack",&WarlordAngryAttack);;
|
|
}
|
|
|
|
|
|
function private ArchetypeWarlordBlackboardInit()
|
|
{
|
|
// CREATE BLACKBOARD
|
|
Blackboard::CreateBlackBoardForEntity( self );
|
|
|
|
// CREATE INTERFACE
|
|
ai::CreateInterfaceForEntity( self );
|
|
|
|
|
|
// USE UTILITY BLACKBOARD
|
|
self AiUtility::RegisterUtilityBlackboardAttributes();
|
|
|
|
// CREATE WARLORD BLACKBOARD - NOT NEEDED
|
|
|
|
|
|
// REGISTER ANIMSCRIPTED CALLBACK
|
|
self.___ArchetypeOnAnimscriptedCallback = &ArchetypeWarlordOnAnimscriptedCallback;
|
|
|
|
// ENABLE DEBUGGING IN ODYSSEY
|
|
/#self FinalizeTrackedBlackboardAttributes();#/;
|
|
}
|
|
|
|
function private ArchetypeWarlordOnAnimscriptedCallback( entity )
|
|
{
|
|
// UNREGISTER THE BLACKBOARD
|
|
entity.__blackboard = undefined;
|
|
|
|
// REREGISTER BLACKBOARD
|
|
entity ArchetypeWarlordBlackboardInit();
|
|
}
|
|
|
|
function private shouldHuntEnemyPlayer( entity )
|
|
{
|
|
if(isdefined(entity.enemy) && isdefined(entity.HuntEnemyTime) && (GetTime() < entity.HuntEnemyTime))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function private _warlordHuntEnemy( entity )
|
|
{
|
|
/#
|
|
WarlordDebugHelpers::TryState( entity, 3, true);
|
|
#/
|
|
|
|
if ( Distance2DSquared( entity.origin, self LastKnownPos(self.enemy) ) <= ( (250) * (250) ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( isdefined( entity.huntUpdateNextTime ) && GetTime() < entity.huntUpdateNextTime )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(entity.isAngryAttack)
|
|
{
|
|
/#
|
|
WarlordDebugHelpers::PrintState(3, (1, 0, 1), "Suspended by Angery Attack");
|
|
#/
|
|
|
|
return false;
|
|
}
|
|
|
|
positionOnNavMesh = GetClosestPointOnNavMesh( self LastKnownPos(self.enemy), 200 );
|
|
|
|
if ( !isdefined( positionOnNavMesh ) )
|
|
{
|
|
positionOnNavMesh = self LastKnownPos(self.enemy);
|
|
}
|
|
|
|
queryResult = PositionQuery_Source_Navigation(
|
|
positionOnNavMesh,
|
|
150,
|
|
250,
|
|
0.5 * 90,
|
|
36,
|
|
entity,
|
|
36 );
|
|
|
|
PositionQuery_Filter_InClaimedLocation( queryResult, entity );
|
|
PositionQuery_Filter_DistanceToGoal(queryResult, entity);
|
|
|
|
if ( queryResult.data.size > 0 )
|
|
{
|
|
closestPoint = undefined;
|
|
closestDistance = undefined;
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if ( !point.inclaimedlocation && point.distToGoal == 0)
|
|
{
|
|
newClosestDistance = Distance2DSquared( entity.origin, point.origin );
|
|
|
|
if ( !isdefined( closestPoint ) ||
|
|
newClosestDistance < closestDistance )
|
|
{
|
|
closestPoint = point.origin;
|
|
closestDistance = newClosestDistance;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( isdefined( closestPoint ) )
|
|
{
|
|
/#
|
|
WarlordDebugHelpers::SetCurrentState(entity, 3, true);
|
|
#/
|
|
|
|
entity UsePosition( closestPoint );
|
|
entity.huntUpdateNextTime = GetTime() + RandomIntRange( 500, 1500 );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/#
|
|
WarlordDebugHelpers::SetStateFailed(entity, 3);
|
|
#/
|
|
|
|
entity.HuntEnemyTime = undefined;
|
|
return false;
|
|
}
|
|
|
|
|
|
function chooseBetterPositionService( entity )
|
|
{
|
|
if ( entity ASMIsTransitionRunning() ||
|
|
( entity GetBehaviortreeStatus() != 5 ) ||
|
|
entity ASMIsSubStatePending() ||
|
|
entity AsmIsTransDecRunning() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
shouldRepath = false;
|
|
isTrackingEnemyLastPos = false;
|
|
searchOrigin = undefined;
|
|
bApproachingGoal = entity IsApproachingGoal();
|
|
|
|
if( !bApproachingGoal) //1- Go to goal if we are ouside it or changed
|
|
{
|
|
WarlordServerUtils::ClearPreferedPoint( entity );
|
|
|
|
/#
|
|
WarlordDebugHelpers::SetCurrentState( entity, 6);
|
|
#/
|
|
|
|
//if we are following an entity or goalradius is too small, just go to it directly
|
|
if( isdefined(entity.goalent) || (entity.goalradius < (36 * 2)) )
|
|
{
|
|
goalPosOnMesh = GetClosestPointOnNavMesh( self.GoalPos, 200 );
|
|
|
|
if(!isdefined(goalPosOnMesh))
|
|
{
|
|
goalPosOnMesh = self.GoalPos;
|
|
}
|
|
|
|
entity UsePosition( goalPosOnMesh );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(bApproachingGoal && shouldHuntEnemyPlayer(entity)) //2- Should hunt enemy
|
|
{
|
|
return _warlordHuntEnemy(entity);
|
|
}
|
|
else if(bApproachingGoal && warlordserverutils::UpdatePreferedPoint(entity)) //3- Go to prefered point location
|
|
{
|
|
return true;
|
|
}
|
|
else if( isdefined( entity.lastenemysightpos ) && !warlordserverutils::HaveTooLowToAttackEnemy(entity)) //4- Should investigate enemy last position if any
|
|
{
|
|
searchOrigin = entity.lastenemysightpos;
|
|
}
|
|
else
|
|
{
|
|
/#
|
|
entity WarlordDebugHelpers::PrintState(undefined, (1, 0, 0), "searchOrigin = self.GoalPos");
|
|
#/
|
|
|
|
searchOrigin = entity.GoalPos;
|
|
}
|
|
|
|
|
|
// Clamp enemy's position to the navmesh.
|
|
if ( isdefined( searchOrigin ) )
|
|
{
|
|
searchOrigin = GetClosestPointOnNavMesh( searchOrigin, 200 );
|
|
}
|
|
|
|
if ( !isdefined( searchOrigin ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!bApproachingGoal || !isdefined( entity.nextFindBetterPositionTime ) || GetTime() > entity.nextFindBetterPositionTime) //ShouldRepath if we are outside the goal or we are bored
|
|
{
|
|
shouldRepath = true;
|
|
}
|
|
|
|
if ( isdefined( entity.enemy ) && !entity SeeRecently( entity.enemy, 2 ) && isdefined( entity.lastenemysightpos )) //ShouldRepath if we lost the enemy
|
|
{
|
|
/#
|
|
entity WarlordDebugHelpers::PrintState(undefined, (1, 1, 1), "TrackingEnemyLastPos");
|
|
#/
|
|
|
|
isTrackingEnemyLastPos = true;
|
|
|
|
//if the warlord is moving near the lastKnowEnemyPos give it a chance to reach there
|
|
if(isdefined(entity.pathGoalPos))
|
|
{
|
|
distanceToGoalSqr = DistanceSquared(searchOrigin, entity.pathGoalPos);
|
|
|
|
if( distanceToGoalSqr < ( (200) * (200) ) )
|
|
{
|
|
shouldRepath = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
shouldRepath = true;
|
|
}
|
|
}
|
|
|
|
if(!shouldRepath)
|
|
{
|
|
if( isdefined(entity.hasCrouchingEnemy) )
|
|
{
|
|
entity.hasCrouchingEnemy = undefined;
|
|
shouldRepath = true;
|
|
}
|
|
}
|
|
|
|
if (shouldRepath)
|
|
{
|
|
/////////// Find points around the enemy.
|
|
queryResult = PositionQuery_Source_Navigation( searchOrigin, 0, entity.engagemaxdist, 0.5 * 90, 36 * 2, entity, 36 * 2 );
|
|
|
|
PositionQuery_Filter_InClaimedLocation( queryResult, entity );
|
|
PositionQuery_Filter_DistanceToGoal(queryResult, entity);
|
|
|
|
//ToDo - mali 3/12/2015: limit the number of Sight checks
|
|
|
|
//if there is an enemy try to pick the points that sees it, early out after 20 successful checks
|
|
if(isdefined( entity.enemy ) && isTrackingEnemyLastPos && ( isdefined( entity.WarlordAggressiveMode ) && entity.WarlordAggressiveMode ))
|
|
{
|
|
PositionQuery_Filter_Sight(queryResult, self LastKnownPos(self.enemy), self GetEye() - self.origin, self, 20);
|
|
}
|
|
|
|
// Throw away points close to the current position or outside the goalradius.
|
|
preferedPoints = [];
|
|
randomPoints = [];
|
|
bPointsAvailable = 0;
|
|
bPointsInGoal = 0;
|
|
bPointsVisible = 0;
|
|
|
|
closePointDistance = 36;
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if (point.inClaimedLocation)
|
|
continue;
|
|
|
|
bPointsAvailable++;
|
|
|
|
if (point.distToGoal > 0) //outside goal radius
|
|
continue;
|
|
|
|
bPointsInGoal++;
|
|
|
|
if (isdefined(point.visibility) && !point.visibility)
|
|
continue;
|
|
|
|
bPointsVisible++;
|
|
|
|
if(point.distToOrigin2D < closePointDistance)
|
|
continue;
|
|
|
|
randomPoints[randomPoints.size] = point.origin;
|
|
}
|
|
|
|
//Get Valid Preferred Points as long as we are not forced to track the player
|
|
if( !(isdefined( entity.enemy ) && isTrackingEnemyLastPos && ( isdefined( entity.WarlordAggressiveMode ) && entity.WarlordAggressiveMode )) )
|
|
{
|
|
preferedPoints = WarlordServerUtils::GetPreferedValidPoints(entity);
|
|
}
|
|
|
|
/////////no perfect points found...fallback to less than perfect ones
|
|
if ( randomPoints.size == 0 && preferedPoints.size == 0 )
|
|
{
|
|
if(bPointsAvailable == 0) //all available points are claimed by other actors!
|
|
{
|
|
return false;
|
|
}
|
|
else if(bPointsInGoal == 0) //if no points from the current searchOrigin, find one near the goal
|
|
{
|
|
// Find points on the edge of the goal radius
|
|
searchOriginOnGoalRadius = entity.GoalPos + VectorNormalize(searchOrigin - entity.GoalPos) * entity.GoalRadius;
|
|
|
|
queryResult = PositionQuery_Source_Navigation( searchOriginOnGoalRadius, 0, entity.engagemaxdist, 0.5 * 90, 36 * 2, entity, 36 * 3 );
|
|
|
|
PositionQuery_Filter_InClaimedLocation( queryResult, entity );
|
|
PositionQuery_Filter_DistanceToGoal(queryResult, entity);
|
|
|
|
//ToDo - mali 3/12/2015: limit the number of Sight checks
|
|
/*
|
|
if(isdefined( entity.enemy ) && isTrackingEnemyLastPos && entity.WarlordAggressiveMode === true)
|
|
{
|
|
PositionQuery_Filter_Sight(queryResult, self LastKnownPos(self.enemy), self GetEye() - self.origin, self, 20);
|
|
}
|
|
*/
|
|
|
|
//try to find prefect nodes in the new search
|
|
bPointsAvailable = 0;
|
|
bPointsInGoal = 0;
|
|
bPointsVisible = 0;
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if (point.inClaimedLocation)
|
|
continue;
|
|
|
|
bPointsAvailable++;
|
|
|
|
if (point.distToGoal > 0) //outside goal radius
|
|
continue;
|
|
|
|
bPointsInGoal++;
|
|
|
|
if (isdefined(point.visibility) && !point.visibility)
|
|
continue;
|
|
|
|
bPointsVisible++;
|
|
|
|
if(point.distToOrigin2D < closePointDistance)
|
|
continue;
|
|
|
|
randomPoints[randomPoints.size] = point.origin;
|
|
}
|
|
|
|
//ok let's settle for less perfect nodes if still nothing found
|
|
if ( randomPoints.size == 0 )
|
|
{
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if (point.inClaimedLocation)
|
|
continue;
|
|
|
|
if (point.distToGoal > 0) //still outside goal radius
|
|
continue;
|
|
|
|
if (bPointsVisible > 0 && isdefined(point.visibility) && !point.visibility)
|
|
continue;
|
|
|
|
randomPoints[randomPoints.size] = point.origin;
|
|
}
|
|
}
|
|
}
|
|
else //There are points inside the goal but not visible
|
|
{
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if (point.inClaimedLocation)
|
|
continue;
|
|
|
|
if (point.distToGoal > 0) //outside goal radius
|
|
continue;
|
|
|
|
if (bPointsVisible > 0 && isdefined(point.visibility) && !point.visibility)
|
|
continue;
|
|
|
|
randomPoints[randomPoints.size] = point.origin;
|
|
}
|
|
}
|
|
|
|
if ( randomPoints.size == 0 )
|
|
{
|
|
if(!bApproachingGoal)
|
|
{
|
|
if ( !isdefined( randomPoints ) ) randomPoints = []; else if ( !IsArray( randomPoints ) ) randomPoints = array( randomPoints ); randomPoints[randomPoints.size]=entity.GoalPos;;
|
|
}
|
|
else
|
|
{
|
|
/#
|
|
WarlordDebugHelpers::SetStateFailed(entity, 5);
|
|
#/
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/////////////Calculate final weights and goal
|
|
|
|
goalWeight = -10000;
|
|
|
|
engageMinFalloffDistSqrd = entity.engageMinFalloffDist * entity.engageMinFalloffDist;
|
|
engageMinDistSqrd = entity.engageMinDist * entity.engageMinDist;
|
|
engageMaxDistSqrd = entity.engageMaxDist * entity.engageMaxDist;
|
|
engageMaxFalloffDistSqrd = entity.engageMaxFalloffDist * entity.engageMaxFalloffDist;
|
|
|
|
if( isdefined( entity.enemy ) && IsSentient( entity.enemy ) )
|
|
{
|
|
enemyForward = VectorNormalize( AnglesToForward( entity.enemy.angles ) );
|
|
}
|
|
|
|
|
|
for ( index = 0; index < randomPoints.size; index++ )
|
|
{
|
|
// Rate the available positions.
|
|
distanceSqrdToEnemy = Distance2DSquared( randomPoints[index], searchOrigin );
|
|
|
|
//Warlord prefers far locations unless it is tracking the player or he is ina Aggressive mode
|
|
bWeightSign = 1;
|
|
if(isdefined(isTrackingEnemyLastPos) || ( isdefined( entity.WarlordAggressiveMode ) && entity.WarlordAggressiveMode ))
|
|
{
|
|
bWeightSign = -1;
|
|
}
|
|
|
|
pointWeight = 0;
|
|
|
|
if ( distanceSqrdToEnemy < engageMinFalloffDistSqrd )
|
|
{
|
|
pointWeight = -1.0 * bWeightSign;
|
|
}
|
|
else if ( distanceSqrdToEnemy < engageMinDistSqrd )
|
|
{
|
|
pointWeight = -0.5 * bWeightSign;
|
|
}
|
|
else if ( distanceSqrdToEnemy > engageMaxFalloffDistSqrd )
|
|
{
|
|
pointWeight = 1.0 * bWeightSign;
|
|
}
|
|
else if ( distanceSqrdToEnemy > engageMaxDistSqrd )
|
|
{
|
|
pointWeight = 1.0 * bWeightSign;
|
|
}
|
|
|
|
// Lower the pointWeights of points directly behind the enemy.
|
|
if(isdefined(enemyForward))
|
|
{
|
|
// TODO(David Young 3-25-14): Simplify the calculations after finding a good heuristic.
|
|
// TODO(David Young 4-16-14): Clamping is unnecessary but there is a weird SRE that occurred because of the dot product returning a value lower than -1.
|
|
angleFromForward = acos( math::clamp( VectorDot( VectorNormalize( pointWeight - entity.enemy.origin ), enemyForward ), -1, 1 ) );
|
|
|
|
if ( angleFromForward > 80.0 )
|
|
{
|
|
pointWeight += -0.5;
|
|
}
|
|
}
|
|
|
|
//Slightly random the points
|
|
pointWeight += RandomFloatRange(-0.25, 0.25);
|
|
|
|
// Set the best goal position.
|
|
if ( goalWeight < pointWeight )
|
|
{
|
|
goalWeight = pointWeight;
|
|
goalPosition = randomPoints[index];
|
|
}
|
|
|
|
|
|
// Draw debug info
|
|
/#
|
|
if ( GetDvarInt("ai_debugPositionService") > 0 && isdefined( GetEntByNum( GetDvarInt("ai_debugPositionService") ) ) && entity == GetEntByNum( GetDvarInt("ai_debugPositionService") ) )
|
|
{
|
|
as_debug::debugDrawWeightedPoint( entity, randomPoints[index], pointWeight, -1.25, 1.75 );
|
|
}
|
|
#/
|
|
// End of debug
|
|
}
|
|
|
|
//preferedPoints have higher weight
|
|
normalPointsMaxGoalWeight = goalWeight;
|
|
foreach(point in preferedPoints)
|
|
{
|
|
if(point === entity.previous_prefered_point)
|
|
continue;
|
|
|
|
pointWeight = RandomFloatRange(normalPointsMaxGoalWeight - 0.25, normalPointsMaxGoalWeight + 0.5);
|
|
|
|
// Set the best goal position.
|
|
if ( goalWeight < pointWeight )
|
|
{
|
|
goalWeight = pointWeight;
|
|
goalPosition = point.origin;
|
|
preferedPoint = point;
|
|
}
|
|
|
|
// Draw debug info
|
|
/#
|
|
if ( GetDvarInt("ai_debugPositionService") > 0 && isdefined( GetEntByNum( GetDvarInt("ai_debugPositionService") ) ) && entity == GetEntByNum( GetDvarInt("ai_debugPositionService") ) )
|
|
{
|
|
as_debug::debugDrawWeightedPoint( entity, point.origin, pointWeight, -1.25, 1.75 );
|
|
}
|
|
#/
|
|
// End of debug
|
|
}
|
|
|
|
// End of calculations
|
|
|
|
if ( isdefined( goalPosition ) )
|
|
{
|
|
if ( entity FindPath( entity.origin, goalPosition, true, false ) )
|
|
{
|
|
entity UsePosition( goalPosition );
|
|
entity.nextFindBetterPositionTime = GetTime() + entity.coversearchinterval;
|
|
|
|
if(isdefined(preferedPoint))
|
|
{
|
|
/#
|
|
WarlordDebugHelpers::SetCurrentState(entity, 4);
|
|
#/
|
|
|
|
WarlordServerUtils::SetPreferedPoint( entity, preferedPoint );
|
|
}
|
|
|
|
/#
|
|
if(!isdefined(preferedPoint))
|
|
{
|
|
WarlordDebugHelpers::SetCurrentState(entity, 5);
|
|
}
|
|
#/
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
// End of setting the best goal position.
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ------- BEHAVIOR CONDITIONS -----------//
|
|
function canJukeCondition( behaviorTreeEntity )
|
|
{
|
|
if ( isdefined( behaviorTreeEntity.nextJukeTime ) && GetTime() < behaviorTreeEntity.nextJukeTime )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return WarlordServerUtils::warlordCanJuke( behaviorTreeEntity );
|
|
}
|
|
|
|
function canTacticalJukeCondition( behaviorTreeEntity )
|
|
{
|
|
if ( isdefined( behaviorTreeEntity.nextJukeTime ) && GetTime() < behaviorTreeEntity.nextJukeTime )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return WarlordServerUtils::warlordCanTacticalJuke( behaviorTreeEntity );
|
|
}
|
|
|
|
function warlordShouldNormalMelee( behaviorTreeEntity)
|
|
{
|
|
if ( isdefined( behaviorTreeEntity.enemy ) && !( ( isdefined( behaviorTreeEntity.enemy.allowDeath ) && behaviorTreeEntity.enemy.allowDeath ) ) )
|
|
return false;
|
|
|
|
if ( AiUtility::hasEnemy( behaviorTreeEntity ) && !IsAlive( behaviorTreeEntity.enemy ) )
|
|
return false;
|
|
|
|
if( !IsSentient( behaviorTreeEntity.enemy ) )
|
|
return false;
|
|
|
|
if( IsVehicle( behaviorTreeEntity.enemy ) && !( isdefined( behaviorTreeEntity.enemy.good_melee_target ) && behaviorTreeEntity.enemy.good_melee_target ) )
|
|
return false;
|
|
|
|
if( !AiUtility::shouldMutexMelee( behaviorTreeEntity ) )
|
|
return false;
|
|
|
|
if( behaviorTreeEntity ai::has_behavior_attribute( "can_melee" ) && !behaviorTreeEntity ai::get_behavior_attribute( "can_melee" ) )
|
|
return false;
|
|
|
|
if( behaviorTreeEntity.enemy ai::has_behavior_attribute( "can_be_meleed" ) && !behaviorTreeEntity.enemy ai::get_behavior_attribute( "can_be_meleed" ) )
|
|
return false;
|
|
|
|
if( !IsPlayer(behaviorTreeEntity.enemy) && !( isdefined( behaviorTreeEntity.enemy.magic_bullet_shield ) && behaviorTreeEntity.enemy.magic_bullet_shield ) ) //we don't want the warlord to keep punching an enemy who can't die
|
|
return false;
|
|
|
|
if( AiUtility::hasCloseEnemyToMeleeWithRange( behaviorTreeEntity, ( (100) * (100) )) )
|
|
{
|
|
if( WarlordServerUtils::IsEnemyTooLowToAttack( behaviorTreeEntity.enemy) )
|
|
{
|
|
WarlordServerUtils::SetEnemyTooLowToAttack(behaviorTreeEntity);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function canTakePainCondition( behaviorTreeEntity )
|
|
{
|
|
return (GetTime() >= behaviorTreeEntity.nextExposedPain);
|
|
}
|
|
|
|
|
|
// ------- COMBAT LOCOMOTION ACTIONS -----------//
|
|
|
|
function jukeAction( behaviorTreeEntity, asmStateName )
|
|
{
|
|
if(warlordserverutils::HaveTooLowToAttackEnemy(behaviorTreeEntity))
|
|
{
|
|
nextJukeTime = 3000 / 3;
|
|
}
|
|
else
|
|
{
|
|
nextJukeTime = 3000;
|
|
}
|
|
|
|
behaviorTreeEntity.nextJukeTime = GetTime() + nextJukeTime;
|
|
|
|
AnimationStateNetworkUtility::RequestState( behaviorTreeEntity, asmStateName );
|
|
|
|
jukeDirection = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_juke_direction");
|
|
|
|
switch ( jukeDirection )
|
|
{
|
|
case "left":
|
|
clientfield::set( "warlord_thruster_direction", 4 );
|
|
break;
|
|
case "right":
|
|
clientfield::set( "warlord_thruster_direction", 3 );
|
|
break;
|
|
}
|
|
|
|
behaviorTreeEntity ClearPath();
|
|
|
|
//Notify other actors that the warlord just did a juke
|
|
jukeInfo = SpawnStruct();
|
|
jukeInfo.origin = behaviorTreeEntity.origin;
|
|
jukeInfo.entity = behaviorTreeEntity;
|
|
|
|
Blackboard::AddBlackboardEvent( "actor_juke", jukeInfo, 2000 );
|
|
|
|
jukeInfo.entity playsound("fly_jetpack_juke_warlord");
|
|
|
|
|
|
return 5;
|
|
}
|
|
|
|
function jukeActionTerminate( behaviorTreeEntity, asmStateName )
|
|
{
|
|
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_juke_direction", undefined );
|
|
|
|
clientfield::set( "warlord_thruster_direction", 0 );
|
|
|
|
//use the new Juke landing position
|
|
positionOnNavMesh = GetClosestPointOnNavMesh( behaviorTreeEntity.origin, 200 );
|
|
|
|
if ( !isdefined( positionOnNavMesh ) )
|
|
{
|
|
positionOnNavMesh = behaviorTreeEntity.origin;
|
|
}
|
|
|
|
behaviorTreeEntity UsePosition( positionOnNavMesh );
|
|
|
|
return 4;
|
|
}
|
|
|
|
// ------- RECHARGE ACTIONS -----------//
|
|
function deathAction( behaviorTreeEntity, asmStateName )
|
|
{
|
|
clientfield::set( "warlord_damage_state", 3 );
|
|
|
|
AnimationStateNetworkUtility::RequestState( behaviorTreeEntity, asmStateName );
|
|
|
|
return 5;
|
|
}
|
|
|
|
function exposedPainActionStart( behaviorTreeEntity )
|
|
{
|
|
behaviorTreeEntity.nextExposedPain = GetTime() + RandomIntRange(500, 2500);
|
|
|
|
AiUtility::keepClaimNode( behaviorTreeEntity );
|
|
}
|
|
|
|
// ------- Angry -----------//
|
|
function shouldBeAngryCondition( behaviorTreeEntity )
|
|
{
|
|
if( isdefined(behaviorTreeEntity.nextAngryTime) && (GetTime() < behaviorTreeEntity.nextAngryTime) )
|
|
return false;
|
|
|
|
if( !isDefined( behaviorTreeEntity.knownAttackers) || behaviorTreeEntity.knownAttackers.size == 0)
|
|
{
|
|
return false;
|
|
}
|
|
else if(behaviorTreeEntity.knownAttackers.size == 1 && isdefined(behaviorTreeEntity.enemy) && behaviorTreeEntity.knownAttackers[0].attacker == behaviorTreeEntity.enemy) //if attacker is enemy and we only have one attacker no need to do something extra
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
if(isdefined(behaviorTreeEntity.AccumilatedDamage) && behaviorTreeEntity.AccumilatedDamage > WarlordServerUtils::GetScaledForPlayers(200, 1.5, 2, 2.5) )
|
|
return true;
|
|
|
|
return behaviorTreeEntity.isAngryAttack;
|
|
}
|
|
|
|
|
|
function WarlordAngryAttack( entity )
|
|
{
|
|
/#
|
|
WarlordDebugHelpers::PrintState(1, (0, 1, 0), " STARTED");
|
|
#/
|
|
|
|
entity.isAngryAttack = true;
|
|
entity.forceFire = true;
|
|
entity.AccumilatedDamage = 0;
|
|
entity.nextAngryTime = GetTime() + 13000;
|
|
|
|
WarlordServerUtils::UpdateAttackersList( entity );
|
|
|
|
attackersArray = [];
|
|
|
|
//Sort known Attackers based on threat
|
|
for(i = 0;i < entity.knownAttackers.size; i++)
|
|
{
|
|
for( j = i + 1; j < entity.knownAttackers.size; j++)
|
|
{
|
|
if(entity.knownAttackers[i].threat < entity.knownAttackers[j].threat)
|
|
{
|
|
tmp = entity.knownAttackers[j].threat;
|
|
entity.knownAttackers[j].threat = entity.knownAttackers[i].threat;
|
|
entity.knownAttackers[i].threat = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach(data in entity.knownAttackers)
|
|
{
|
|
if ( !isdefined( attackersArray ) ) attackersArray = []; else if ( !IsArray( attackersArray ) ) attackersArray = array( attackersArray ); attackersArray[attackersArray.size]=data.attacker;;
|
|
}
|
|
|
|
thread WarlordAngryAttack_ShootThemAll(entity, attackersArray);
|
|
|
|
return true;
|
|
}
|
|
|
|
function WarlordAngryAttack_ShootThemAll( entity, attackersArray )
|
|
{
|
|
entity endon("disconnect");
|
|
entity endon("death");
|
|
|
|
entity notify("angry_attack");
|
|
|
|
shootTime = GetDVarFloat("warlordangryattack", 3);
|
|
|
|
foreach(attacker in attackersArray)
|
|
{
|
|
if(isdefined(attacker))
|
|
{
|
|
entity ai::shoot_at_target("normal", attacker, undefined, shootTime, undefined, true);
|
|
}
|
|
}
|
|
|
|
/#
|
|
WarlordDebugHelpers::PrintState(1, (0, 0, 1), " ENDED");
|
|
#/
|
|
|
|
entity.forceFire = false;
|
|
entity.isAngryAttack = false;;
|
|
}
|
|
|
|
// end of #namespace WarlordBehavior
|
|
|
|
#namespace WarlordServerUtils;
|
|
|
|
function GetAlivePlayersCount(entity)
|
|
{
|
|
if(entity.team == "allies")
|
|
{
|
|
return level.aliveCount["axis"];
|
|
}
|
|
else
|
|
{
|
|
return level.aliveCount["allies"];
|
|
}
|
|
}
|
|
|
|
function SetWarlordAggressiveMode( entity, b_aggressive_mode )
|
|
{
|
|
entity.WarlordAggressiveMode = b_aggressive_mode;
|
|
|
|
if(( isdefined( b_aggressive_mode ) && b_aggressive_mode ))
|
|
{
|
|
//Increase the threat bias towards the players relative to the allies
|
|
foreach(player in level.players)
|
|
{
|
|
entity SetPersonalThreatBias(player, 1000);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Reset the threat bias in non Aggressive mode
|
|
foreach(player in level.players)
|
|
{
|
|
entity SetPersonalThreatBias(player, 0, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
function AddPreferedPoint(entity, position, min_duration, max_duration, name)
|
|
{
|
|
positionOnNavMesh = GetClosestPointOnNavMesh( position, 200, 25 );
|
|
|
|
if(!isdefined(positionOnNavMesh))
|
|
{
|
|
/# println( "^3Warning: Passing Warlord prefered point that is not on the NavMesh or is in invalid location: " + position ); #/
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
position = positionOnNavMesh;
|
|
}
|
|
|
|
if(!(entity IsPosAtGoal(position)))
|
|
{
|
|
/# println( "^3Warning: Passing Warlord prefered point that it not inside the Goal: " + position ); #/
|
|
}
|
|
|
|
point = SpawnStruct();
|
|
point.origin = position;
|
|
point.min_duration = min_duration;
|
|
point.max_duration = max_duration;
|
|
point.name = name;
|
|
|
|
if ( !isdefined( entity.prefered_points ) ) entity.prefered_points = []; else if ( !IsArray( entity.prefered_points ) ) entity.prefered_points = array( entity.prefered_points ); entity.prefered_points[entity.prefered_points.size]=point;;
|
|
}
|
|
|
|
function DeletePreferedPoint( entity, name )
|
|
{
|
|
if(isdefined(entity.prefered_points))
|
|
{
|
|
points_to_remove = [];
|
|
|
|
foreach(point in entity.prefered_points)
|
|
{
|
|
if( point.name === name )
|
|
{
|
|
if ( !isdefined( points_to_remove ) ) points_to_remove = []; else if ( !IsArray( points_to_remove ) ) points_to_remove = array( points_to_remove ); points_to_remove[points_to_remove.size]=point;;
|
|
}
|
|
}
|
|
|
|
if(points_to_remove.size > 0)
|
|
{
|
|
foreach(point in points_to_remove)
|
|
{
|
|
ArrayRemoveValue( entity.prefered_points, point );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function ClearAllPreferedPoints(entity)
|
|
{
|
|
ClearPreferedPoint(entity);
|
|
|
|
entity.prefered_points = [];
|
|
}
|
|
|
|
function ClearPreferedPointsOutsideGoal(entity)
|
|
{
|
|
points_to_remove = [];
|
|
|
|
foreach(point in entity.prefered_points)
|
|
{
|
|
if(!(entity IsPosAtGoal(point.origin)))
|
|
{
|
|
if ( !isdefined( points_to_remove ) ) points_to_remove = []; else if ( !IsArray( points_to_remove ) ) points_to_remove = array( points_to_remove ); points_to_remove[points_to_remove.size]=point;;
|
|
}
|
|
}
|
|
|
|
foreach(point in points_to_remove)
|
|
{
|
|
ArrayRemoveValue( entity.prefered_points, point );
|
|
}
|
|
}
|
|
|
|
function private SetPreferedPoint( entity, point)
|
|
{
|
|
entity.previous_prefered_point = entity.current_prefered_point;
|
|
entity.current_prefered_point = point;
|
|
}
|
|
|
|
function private ClearPreferedPoint( entity)
|
|
{
|
|
/#
|
|
WarlordDebugHelpers::SetCurrentState(entity, undefined);
|
|
#/
|
|
|
|
entity.current_prefered_point_start_time = undefined;
|
|
entity.current_prefered_point_expiration = undefined;
|
|
entity.current_prefered_point = undefined;
|
|
}
|
|
|
|
function private AtPreferedPoint(entity)
|
|
{
|
|
if(isdefined(entity.current_prefered_point) && ( (DistanceSquared(entity.current_prefered_point.origin, entity.origin) < ( (36) * (36) )) && (abs(self.current_prefered_point.origin[2] - entity.origin[2]) < (90 / 2)) ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function private ReachingPreferedPoint(entity)
|
|
{
|
|
if(!isdefined(entity.current_prefered_point))
|
|
return false;
|
|
|
|
if(AtPreferedPoint(entity))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if(isdefined(entity.pathGoalPos) && entity.pathGoalPos == entity.current_prefered_point.origin)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function private UpdatePreferedPoint(entity)
|
|
{
|
|
if(isdefined(entity.current_prefered_point))
|
|
{
|
|
if(AtPreferedPoint(entity))
|
|
{
|
|
if(isdefined(entity.current_prefered_point_expiration))
|
|
{
|
|
if(GetTime() > entity.current_prefered_point_expiration)
|
|
{
|
|
ClearPreferedPoint(entity);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if(isdefined(entity.current_prefered_point.min_duration))
|
|
{
|
|
entity.current_prefered_point_start_time = GetTime();
|
|
|
|
if(!isdefined(entity.current_prefered_point.max_duration) || entity.current_prefered_point.max_duration == entity.current_prefered_point.min_duration)
|
|
{
|
|
entity.current_prefered_point_expiration = GetTime() + entity.current_prefered_point.min_duration;
|
|
}
|
|
else
|
|
{
|
|
duration = RandomIntRange(entity.current_prefered_point.min_duration, entity.current_prefered_point.max_duration);
|
|
entity.current_prefered_point_expiration = GetTime() + duration;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
ClearPreferedPoint(entity);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if(!ReachingPreferedPoint(entity))
|
|
{
|
|
entity UsePosition( entity.current_prefered_point.origin );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
function private GetPreferedValidPoints( entity )
|
|
{
|
|
validPoints = [];
|
|
|
|
if(isdefined(entity.prefered_points))
|
|
{
|
|
foreach(point in entity.prefered_points)
|
|
{
|
|
if(!(entity IsPosAtGoal(point.origin)))
|
|
{
|
|
distance = Distance2DSquared(entity.origin, point.origin);
|
|
distance = sqrt(distance);
|
|
continue;
|
|
}
|
|
|
|
if((entity IsPosInClaimedLocation(point.origin)))
|
|
continue;
|
|
|
|
if(isdefined( entity.enemy ) && ( isdefined( entity.WarlordAggressiveMode ) && entity.WarlordAggressiveMode ))
|
|
{
|
|
if( !BulletTracePassed( entity GetEye(), entity.enemy.origin + (0, 0, 50), false, entity, entity.enemy ) )
|
|
continue;
|
|
}
|
|
|
|
if ( !isdefined( validPoints ) ) validPoints = []; else if ( !IsArray( validPoints ) ) validPoints = array( validPoints ); validPoints[validPoints.size]=point;;
|
|
}
|
|
}
|
|
|
|
return validPoints;
|
|
}
|
|
|
|
function GetScaledForPlayers(val, scale2, scale3, scale4)
|
|
{
|
|
if(!isdefined( level.players))
|
|
{
|
|
return val;
|
|
}
|
|
|
|
if( level.players.size == 2 )
|
|
{
|
|
return val * scale2;
|
|
}
|
|
else if( level.players.size == 3 )
|
|
{
|
|
return val * scale3;
|
|
}
|
|
else if( level.players.size == 4 )
|
|
{
|
|
return val * scale4;
|
|
}
|
|
else
|
|
{
|
|
return val;
|
|
}
|
|
}
|
|
|
|
function warlordCanJuke( entity )
|
|
{
|
|
|
|
if (!isdefined( entity.enemy ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
distanceSqr = DistanceSquared(entity.enemy.origin, entity.origin);
|
|
|
|
if(distanceSqr < ( (300) * (300) ))
|
|
{
|
|
jukeDistance = 145 / 2;
|
|
}
|
|
else
|
|
{
|
|
jukeDistance = 145;
|
|
}
|
|
|
|
jukeDirection = AiUtility::calculateJukeDirection( entity, 18, jukeDistance );
|
|
|
|
if(jukeDirection != "forward")
|
|
{
|
|
Blackboard::SetBlackBoardAttribute( entity, "_juke_direction", jukeDirection );
|
|
|
|
if(jukeDistance == 145)
|
|
{
|
|
Blackboard::SetBlackBoardAttribute( entity, "_juke_distance", "long" );
|
|
}
|
|
else
|
|
{
|
|
Blackboard::SetBlackBoardAttribute( entity, "_juke_distance", "short" );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function warlordCanTacticalJuke( entity )
|
|
{
|
|
if ( entity HasPath () )
|
|
{
|
|
locomotionDirection = AiUtility::BB_GetLocomotionFaceEnemyQuadrant();
|
|
|
|
if( locomotionDirection == "locomotion_face_enemy_front" || locomotionDirection == "locomotion_face_enemy_back")
|
|
{
|
|
jukeDirection = AiUtility::calculateJukeDirection( entity, 50, 145 ) ;
|
|
|
|
if(jukeDirection != "forward" )
|
|
{
|
|
Blackboard::SetBlackBoardAttribute( entity, "_juke_direction", jukeDirection );
|
|
Blackboard::SetBlackBoardAttribute( entity, "_juke_distance", "long" );
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function IsEnemyTooLowToAttack( enemy )
|
|
{
|
|
if(IsPlayer(enemy))
|
|
{
|
|
if(( isdefined( enemy.laststand ) && enemy.laststand ))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
playerStance = enemy GetStance();
|
|
|
|
if(isdefined(playerStance) && (playerStance == "prone" || playerStance == "crouch"))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function HaveTooLowToAttackEnemy( entity )
|
|
{
|
|
if(!isdefined(entity.lastTimeToHaveCrouchingEnemy))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( (GetTime() - entity.lastTimeToHaveCrouchingEnemy) <= 4000)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
entity.lastTimeToHaveCrouchingEnemy = undefined;
|
|
return false;
|
|
}
|
|
|
|
function SetEnemyTooLowToAttack( entity )
|
|
{
|
|
if(HaveTooLowToAttackEnemy(entity))
|
|
{
|
|
return;
|
|
}
|
|
|
|
entity.lastTimeToHaveCrouchingEnemy = GetTime();
|
|
entity.hasCrouchingEnemy = true;
|
|
}
|
|
|
|
function ComputeAttackerThreat( entity, attackerInfo)
|
|
{
|
|
if(attackerInfo.damage < 250)
|
|
return 0;
|
|
|
|
threat = 1;
|
|
isAttackerPlayer = IsPlayer(attackerInfo.attacker);
|
|
|
|
if(isAttackerPlayer)
|
|
{
|
|
threat *= 10;
|
|
}
|
|
|
|
distanceFromAttackerSqr = Distance2DSquared( entity.origin, attackerInfo.attacker.origin );
|
|
normalizedDistanceFromAttacker = 0;
|
|
|
|
if(isAttackerPlayer)
|
|
{
|
|
if(distanceFromAttackerSqr <= ( (100) * (100) ))
|
|
{
|
|
threat *= 1000;
|
|
}
|
|
else
|
|
{
|
|
normalizedDistanceFromAttacker = distanceFromAttackerSqr / ( (entity.engageMaxFalloffDist) * (entity.engageMaxFalloffDist) );
|
|
|
|
if(normalizedDistanceFromAttacker > 1)
|
|
normalizedDistanceFromAttacker = 1;
|
|
|
|
normalizedDistanceFromAttacker = 1 - normalizedDistanceFromAttacker;
|
|
}
|
|
}
|
|
|
|
normalizedDamageFromAttacker = attackerInfo.damage / 1000;
|
|
|
|
if(normalizedDamageFromAttacker > 1)
|
|
normalizedDamageFromAttacker = 1;
|
|
|
|
threat *= (normalizedDistanceFromAttacker * 0.65 + normalizedDamageFromAttacker * ( 1 - 0.65)) * 100;
|
|
|
|
return threat;
|
|
}
|
|
|
|
function ShouldSwitchToNewThreat( entity, attacker, threat)
|
|
{
|
|
if(entity.enemy === attacker)
|
|
return false;
|
|
|
|
if(!isdefined(entity.currentDangerousAttacker))
|
|
return true;
|
|
|
|
if(entity.currentDangerousAttacker.Health <= 0)
|
|
return true;
|
|
|
|
if(entity.currentDangerousAttacker == attacker)
|
|
return false;
|
|
|
|
if(GetTime() - entity.lastDangerousAttackerTime < 1)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
function UpdateAttackersList( entity, newAttacker, damage)
|
|
{
|
|
if(!isdefined(entity.knownAttackers))
|
|
{
|
|
entity.knownAttackers = [];
|
|
}
|
|
|
|
maxThreat = 0;
|
|
threatCount = 0;
|
|
|
|
for(i = 0; i < entity.knownAttackers.size; i++)
|
|
{
|
|
attacker = entity.knownAttackers[i].attacker;
|
|
|
|
if(!isdefined(attacker) || !IsEntity(attacker) || attacker.health <= 0 || (GetTime() - entity.knownAttackers[i].lastAttackTime) > 5000)
|
|
{
|
|
ArrayRemoveIndex(entity.knownAttackers, i);
|
|
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
//Check to see if this is a high immediate threat
|
|
entity.knownAttackers[i].threat = ComputeAttackerThreat(entity, entity.knownAttackers[i]);
|
|
|
|
if(entity.knownAttackers[i].threat > maxThreat)
|
|
{
|
|
maxThreat = entity.knownAttackers[i].threat;
|
|
dangerousAttackerInfo = entity.knownAttackers[i];
|
|
}
|
|
}
|
|
|
|
if(isdefined(newAttacker))
|
|
{
|
|
for(i = 0; i < entity.knownAttackers.size; i++)
|
|
{
|
|
if(entity.knownAttackers[i].attacker == newAttacker)
|
|
{
|
|
attackData = entity.knownAttackers[i];
|
|
attackData.lastAttackTime = GetTime();
|
|
attackData.damage += damage;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!isdefined(attackData))
|
|
{
|
|
attackData = SpawnStruct();
|
|
attackData.attacker = newAttacker;
|
|
attackData.lastAttackTime = GetTime();
|
|
attackData.damage = damage;
|
|
attackData.threat = 0;
|
|
|
|
if ( !isdefined( entity.knownAttackers ) ) entity.knownAttackers = []; else if ( !IsArray( entity.knownAttackers ) ) entity.knownAttackers = array( entity.knownAttackers ); entity.knownAttackers[entity.knownAttackers.size]=attackData;;
|
|
}
|
|
|
|
//Check to see if this new attacker is a high immediate threat
|
|
attackData.threat = ComputeAttackerThreat(entity, attackData);
|
|
|
|
if(attackData.threat > maxThreat)
|
|
{
|
|
maxThreat = attackData.threat;
|
|
dangerousAttackerInfo = attackData;
|
|
}
|
|
}
|
|
|
|
if(isdefined(dangerousAttackerInfo) && maxThreat > 0)
|
|
{
|
|
if(ShouldSwitchToNewThreat( entity, dangerousAttackerInfo.attacker, maxThreat))
|
|
{
|
|
thread WarlordDangerousEnemyAttack( entity, dangerousAttackerInfo.attacker, maxThreat);
|
|
}
|
|
}
|
|
|
|
//if we are not moving then move if we are getting multiple damage
|
|
CheckifWeShouldMove(entity);
|
|
}
|
|
|
|
function CheckifWeShouldMove( entity )
|
|
{
|
|
if(!isdefined(entity.knownAttackers) || entity.knownAttackers.size <= 1)
|
|
return;
|
|
|
|
isStandStill = false;
|
|
if(AtPreferedPoint(entity))
|
|
{
|
|
//at least allow a second for the warlord to stand on the prefered point before moving him
|
|
if(!isdefined(entity.current_prefered_point_start_time) || GetTime() - entity.current_prefered_point_start_time < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
isStandStill = true;
|
|
}
|
|
|
|
if(!isStandStill)
|
|
{
|
|
if(isdefined(entity.pathGoalPos))
|
|
{
|
|
if( (Distance2DSquared(entity.pathGoalPos, entity.origin) < ( (36) * (36) )) && (abs(entity.pathGoalPos[2] - entity.origin[2]) < (90 / 2)) )
|
|
{
|
|
isStandStill = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(isStandStill)
|
|
{
|
|
if(HaveTooLowToAttackEnemy(entity))
|
|
{
|
|
dangerousAttackersCount = 1;
|
|
}
|
|
else
|
|
{
|
|
dangerousAttackersCount = 0;
|
|
|
|
foreach(attackerInfo in entity.knownAttackers)
|
|
{
|
|
if(attackerInfo.damage > 200 )
|
|
{
|
|
dangerousAttackersCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//if we are taking fire from many attackers start moving
|
|
if(dangerousAttackersCount > 1)
|
|
{
|
|
ClearPreferedPoint( entity );
|
|
entity.nextFindBetterPositionTime = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
function WarlordDangerousEnemyAttack( entity, attacker, threat)
|
|
{
|
|
entity endon("disconnect");
|
|
entity endon("death");
|
|
attacker endon("death");
|
|
|
|
entity endon("angry_attack");
|
|
|
|
entity notify("dangerous_attack");
|
|
entity endon("dangerous_attack");
|
|
|
|
entity.lastDangerousAttackerTime = GetTime();
|
|
entity.currentDangerousAttacker = attacker;
|
|
entity.currentMaxThreat = threat;
|
|
|
|
/#
|
|
WarlordDebugHelpers::PrintState(0, (0, 1, 0), " STARTED");
|
|
#/
|
|
|
|
shootTime = GetDVarFloat("warlordangryattack", 3);
|
|
entity ai::shoot_at_target("normal", attacker, undefined, shootTime, undefined, true);
|
|
|
|
entity.currentDangerousAttacker = undefined;
|
|
|
|
/#
|
|
WarlordDebugHelpers::PrintState(0, (0, 0, 1), " ENDED");
|
|
#/
|
|
}
|
|
|
|
function warlordDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, timeOffset, boneIndex, modelIndex, surfaceType, surfaceNormal )
|
|
{
|
|
entity = self;
|
|
|
|
//Non-player enemies should be very ineffective on the warlord
|
|
if(!IsPlayer(eAttacker))
|
|
{
|
|
iDamage = int(iDamage * 0.05);
|
|
}
|
|
|
|
if ( isdefined(sMeansOfDeath) && ( sMeansOfDeath == "MOD_PROJECTILE" || sMeansOfDeath == "MOD_PROJECTILE_SPLASH" || sMeansOfDeath == "MOD_EXPLOSIVE" || sMeansOfDeath == "MOD_GRENADE" ) )
|
|
{
|
|
iDamage = int(iDamage * 0.25);
|
|
}
|
|
|
|
//maintain attackers list
|
|
UpdateAttackersList( entity, eAttacker, iDamage);
|
|
|
|
if ( entity.health <= entity.damageHeavyStateHealth )
|
|
{
|
|
clientfield::set( "warlord_damage_state", 2 );
|
|
}
|
|
else if ( entity.health <= entity.damageStateHealth )
|
|
{
|
|
clientfield::set( "warlord_damage_state", 1 );
|
|
}
|
|
else
|
|
{
|
|
clientfield::set( "warlord_damage_state", 0 );
|
|
}
|
|
|
|
if(!isdefined(entity.LastDamageTime))
|
|
{
|
|
entity.LastDamageTime = 0;
|
|
}
|
|
|
|
if((GetTime() - entity.LastDamageTime) > 1500)
|
|
{
|
|
entity.AccumilatedDamage = iDamage;
|
|
}
|
|
else
|
|
{
|
|
entity.AccumilatedDamage += iDamage;
|
|
}
|
|
|
|
WARLORD_HUNT_MAX_ACCUMILATED_DAMAGE_VAR = GetDVarInt("warlordhuntdamage", 350);
|
|
|
|
if(entity.AccumilatedDamage > WarlordServerUtils::GetScaledForPlayers(WARLORD_HUNT_MAX_ACCUMILATED_DAMAGE_VAR, 1.5, 2, 2.5) )
|
|
{
|
|
self.HuntEnemyTime = GetTime() + 15000;
|
|
}
|
|
|
|
entity.LastDamageTime = GetTime();
|
|
|
|
return iDamage;
|
|
}
|
|
|
|
function warlordSpawnSetup()
|
|
{
|
|
entity = self;
|
|
|
|
entity.WarlordAggressiveMode = false;
|
|
entity.isAngryAttack = false;
|
|
entity.nextExposedPain = 0;
|
|
entity.lastDangerousAttackerTime = 0;
|
|
entity.currentMaxThreat = 0;
|
|
entity.ignorerunAndgundist = true;
|
|
|
|
entity.combatmode = "no_cover";
|
|
|
|
AiUtility::AddAIOverrideDamageCallback( entity, &WarlordServerUtils::warlordDamageOverride );
|
|
|
|
entity.health = int(GetScaledForPlayers(entity.health, 2, 2.5, 3));
|
|
|
|
entity.fullHealth = entity.health;
|
|
entity.damageStateHealth = Int( entity.fullHealth * 0.5 );
|
|
entity.damageHeavyStateHealth = Int( entity.fullHealth * 0.25 );
|
|
|
|
entity warlord_projectile_watcher();
|
|
|
|
clientfield::set( "warlord_damage_state", 0 );
|
|
clientfield::set( "warlord_lights_state", 1 );
|
|
|
|
switch (entity.classname)
|
|
{
|
|
case "actor_spawner_bo3_warlord_enemy_hvt":
|
|
clientfield::set( "warlord_type", 2 );
|
|
break;
|
|
default:
|
|
clientfield::set( "warlord_type", 1 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
function warlord_projectile_watcher()
|
|
{
|
|
if( !isdefined( self.missile_repulsor ) )
|
|
{
|
|
self.missile_repulsor = missile_createrepulsorent( self, 40000, 256, true );
|
|
}
|
|
self thread repulsor_fx();
|
|
}
|
|
|
|
|
|
function remove_repulsor()
|
|
{
|
|
self endon( "death" );
|
|
|
|
if( isdefined( self.missile_repulsor ) )
|
|
{
|
|
missile_deleteattractor( self.missile_repulsor );
|
|
self.missile_repulsor = undefined;
|
|
}
|
|
|
|
wait 0.5;
|
|
|
|
if(isdefined(self))
|
|
{
|
|
self warlord_projectile_watcher();
|
|
}
|
|
}
|
|
|
|
function repulsor_fx()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "killing_repulsor" );
|
|
|
|
while( 1 )
|
|
{
|
|
self util::waittill_any( "projectile_applyattractor", "play_meleefx" );
|
|
|
|
PlayFxOnTag( "vehicle/fx_quadtank_airburst", self, "tag_origin" );
|
|
PlayFxOnTag( "vehicle/fx_quadtank_airburst_ground", self, "tag_origin" );
|
|
|
|
self PlaySound( "wpn_trophy_alert" );
|
|
|
|
self thread remove_repulsor();
|
|
self notify( "killing_repulsor" );
|
|
|
|
}
|
|
}
|
|
|
|
function trigger_player_shock_fx()
|
|
{
|
|
if ( !isdefined( self._player_shock_fx_quadtank_melee ) )
|
|
{
|
|
self._player_shock_fx_quadtank_melee = 0;
|
|
}
|
|
|
|
self._player_shock_fx_quadtank_melee = !self._player_shock_fx_quadtank_melee;
|
|
self clientfield::set_to_player( "player_shock_fx", self._player_shock_fx_quadtank_melee );
|
|
}
|
|
|
|
// end #namespace WarlordServerUtils;
|
|
|
|
#namespace WarlordDebugHelpers;
|
|
|
|
function PrintState(state, color, string)
|
|
{
|
|
|
|
/#
|
|
if(GetDvarInt("ai_debugWarlord") > 0)
|
|
{
|
|
if(!isdefined(string))
|
|
{
|
|
string = "";
|
|
}
|
|
|
|
|
|
if(!isdefined(state))
|
|
{
|
|
if(!isdefined(self) || !isdefined(self.lastMessage) || self.lastMessage != string)
|
|
{
|
|
self.lastMessage = string;
|
|
PrintTopRightln(string + GetTime(), color, -1 );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(state == 0)
|
|
{
|
|
PrintTopRightln("WARLORD_STATE_THREAT " + string + GetTime(), color, -1 );
|
|
}
|
|
else if(state == 1)
|
|
{
|
|
PrintTopRightln("WARLORD_STATE_ANGRY " + string + GetTime(), color, -1 );
|
|
}
|
|
else if(state == 2)
|
|
{
|
|
PrintTopRightln("WARLORD_STATE_MOVE_FROM_DANGER " + string + GetTime(), color, -1 );
|
|
}
|
|
else if(state == 3)
|
|
{
|
|
PrintTopRightln("WARLORD_STATE_HUNT " + string + GetTime(), color, -1 );
|
|
}
|
|
else if(state == 4)
|
|
{
|
|
PrintTopRightln("WARLORD_STATE_PREFERED_POINT " + string + GetTime(), color, -1 );
|
|
}
|
|
else if(state == 5)
|
|
{
|
|
PrintTopRightln("WARLORD_STATE_NORMAL_POINT " + string + GetTime(), color, -1 );
|
|
}
|
|
else if(state == 6)
|
|
{
|
|
PrintTopRightln("WARLORD_STATE_GOAL " + string + GetTime(), color, -1 );
|
|
}
|
|
}
|
|
#/
|
|
|
|
}
|
|
|
|
function TryState(entity, state, bCheckNew)
|
|
{
|
|
/#
|
|
if(GetDvarInt("ai_debugWarlord") > 0)
|
|
{
|
|
if( !(isdefined(bCheckNew) && IsNewState( entity, state)) )
|
|
{
|
|
color = (1, 1, 1);
|
|
|
|
entity PrintState( state, color );
|
|
}
|
|
}
|
|
#/
|
|
}
|
|
|
|
function SetCurrentState( entity, state, bCanUpdate = false )
|
|
{
|
|
/#
|
|
if(GetDvarInt("ai_debugWarlord") > 0)
|
|
{
|
|
if(!isdefined(bCanUpdate) || IsNewState( entity, state))
|
|
{
|
|
color = (0, 1, 0);
|
|
}
|
|
else
|
|
{
|
|
color = (0, 1, 1);
|
|
}
|
|
|
|
if(!isdefined(state))
|
|
{
|
|
color = (0, 0, 1);
|
|
entity PrintState( entity.currentState, color, " ENDED" );
|
|
}
|
|
|
|
entity PrintState( state, color );
|
|
}
|
|
#/
|
|
|
|
entity.currentState = state;
|
|
}
|
|
|
|
function SetStateFailed( entity, state )
|
|
{
|
|
/#
|
|
if(GetDvarInt("ai_debugWarlord") > 0)
|
|
{
|
|
color = (1, 1, 0);
|
|
|
|
entity PrintState( state, color );
|
|
}
|
|
#/
|
|
}
|
|
|
|
function IsNewState( entity, state )
|
|
{
|
|
bNewState = false;
|
|
|
|
if(!isdefined(entity.currentState))
|
|
{
|
|
bNewState = true;
|
|
}
|
|
else if(!isdefined(state))
|
|
{
|
|
return false;
|
|
}
|
|
else if(entity.currentState != state)
|
|
{
|
|
bNewState = true;
|
|
}
|
|
|
|
return bNewState;
|
|
}
|
|
|
|
// end #namespace WarlordDebugHelpers; |