boiii-scripts/shared/ai/warlord.gsc
2023-04-13 17:30:38 +02:00

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;