538 lines
26 KiB
Plaintext
538 lines
26 KiB
Plaintext
#using scripts\shared\ai\systems\animation_state_machine_utility;
|
|
#using scripts\shared\ai\systems\animation_state_machine_notetracks;
|
|
#using scripts\shared\ai\archetype_utility;
|
|
#using scripts\shared\ai\systems\behavior_tree_utility;
|
|
#using scripts\shared\ai\systems\blackboard;
|
|
#using scripts\shared\ai\systems\ai_interface;
|
|
#using scripts\shared\ai\zombie;
|
|
#using scripts\shared\spawner_shared;
|
|
#using scripts\shared\ai\archetype_mocomps_utility;
|
|
#using scripts\shared\ai\archetype_zombie_dog_interface;
|
|
#using scripts\shared\ai_shared;
|
|
#using scripts\shared\math_shared;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#namespace ZombieDogBehavior;
|
|
|
|
function autoexec RegisterBehaviorScriptFunctions()
|
|
{
|
|
// INIT BLACKBOARD
|
|
spawner::add_archetype_spawn_function( "zombie_dog", &ArchetypeZombieDogBlackboardInit );
|
|
|
|
// ------- ZOMBIE DOG - SERVICES ----------- //
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieDogTargetService",&zombieDogTargetService);;
|
|
|
|
// ------- ZOMBIE DOG - CONDITIONS --------- //
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieDogShouldMelee",&zombieDogShouldMelee);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieDogShouldWalk",&zombieDogShouldWalk);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieDogShouldRun",&zombieDogShouldRun);;
|
|
|
|
// ------- ZOMBIE DOG - ACTIONS ------------ //
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeAction("zombieDogMeleeAction",&zombieDogMeleeAction,undefined,&zombieDogMeleeActionTerminate);;
|
|
|
|
AnimationStateNetwork::RegisterNotetrackHandlerFunction("dog_melee",&ZombieBehavior::zombieNotetrackMeleeFire);;
|
|
|
|
ZombieDogInterface::RegisterZombieDogInterfaceAttributes();
|
|
}
|
|
|
|
function ArchetypeZombieDogBlackboardInit() // self = AI
|
|
{
|
|
// CREATE BLACKBOARD
|
|
Blackboard::CreateBlackBoardForEntity( self );
|
|
|
|
// CREATE INTERFACE
|
|
ai::CreateInterfaceForEntity( self );
|
|
|
|
// USE UTILITY BLACKBOARD
|
|
self AiUtility::RegisterUtilityBlackboardAttributes();
|
|
|
|
// CREATE ZOMBIE DOG BLACKBOARD
|
|
Blackboard::RegisterBlackBoardAttribute(self,"_low_gravity","normal",undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_low_gravity");#/ };
|
|
Blackboard::RegisterBlackBoardAttribute(self,"_should_run","walk",&BB_GetShouldRunStatus); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_should_run");#/ };
|
|
Blackboard::RegisterBlackBoardAttribute(self,"_should_howl","dont_howl",&BB_GetShouldHowlStatus); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_should_howl");#/ };
|
|
|
|
// REGISTER ANIMSCRIPTED CALLBACK
|
|
self.___ArchetypeOnAnimscriptedCallback = &ArchetypeZombieDogOnAnimscriptedCallback;
|
|
|
|
// ENABLE DEBUGGING IN ODYSSEY
|
|
/#self FinalizeTrackedBlackboardAttributes();#/;
|
|
|
|
// Custom attributes
|
|
self.kill_on_wine_coccon = true;
|
|
}
|
|
|
|
function private ArchetypeZombieDogOnAnimscriptedCallback( entity )
|
|
{
|
|
// UNREGISTER THE BLACKBOARD
|
|
entity.__blackboard = undefined;
|
|
|
|
// REREGISTER BLACKBOARD
|
|
entity ArchetypeZombieDogBlackboardInit();
|
|
}
|
|
|
|
function BB_GetShouldRunStatus()
|
|
{
|
|
/#
|
|
if ( ( isdefined( self.isPuppet ) && self.isPuppet ) )
|
|
{
|
|
return "run";
|
|
}
|
|
#/
|
|
|
|
if ( ( isdefined( self.hasSeenFavoriteEnemy ) && self.hasSeenFavoriteEnemy ) || ( ai::HasAiAttribute( self, "sprint" ) && ai::GetAiAttribute( self, "sprint" ) ) )
|
|
return "run";
|
|
return "walk";
|
|
}
|
|
|
|
function BB_GetShouldHowlStatus()
|
|
{
|
|
if ( self ai::has_behavior_attribute( "howl_chance" ) && ( isdefined( self.hasSeenFavoriteEnemy ) && self.hasSeenFavoriteEnemy ) )
|
|
{
|
|
if ( !isdefined( self.shouldHowl ) )
|
|
{
|
|
chance = self ai::get_behavior_attribute( "howl_chance" );
|
|
self.shouldHowl = RandomFloat( 1 ) <= chance;
|
|
}
|
|
if ( self.shouldHowl )
|
|
{
|
|
return "howl";
|
|
}
|
|
else
|
|
{
|
|
return "dont_howl";
|
|
}
|
|
}
|
|
|
|
return "dont_howl";
|
|
}
|
|
|
|
function GetYaw(org)
|
|
{
|
|
angles = VectorToAngles(org-self.origin);
|
|
return angles[1];
|
|
}
|
|
|
|
// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away.
|
|
function AbsYawToEnemy()
|
|
{
|
|
assert( IsDefined(self.enemy) );
|
|
|
|
yaw = self.angles[1] - GetYaw(self.enemy.origin);
|
|
yaw = AngleClamp180( yaw );
|
|
|
|
if (yaw < 0)
|
|
{
|
|
yaw = -1 * yaw;
|
|
}
|
|
|
|
return yaw;
|
|
}
|
|
|
|
function need_to_run()
|
|
{
|
|
run_dist_squared = ( (self ai::get_behavior_attribute( "min_run_dist" )) * (self ai::get_behavior_attribute( "min_run_dist" )) );
|
|
run_yaw = 20;
|
|
run_pitch = 30;
|
|
run_height = 64;
|
|
|
|
if ( self.health < self.maxhealth )
|
|
{
|
|
// dog took damage
|
|
return true;
|
|
}
|
|
|
|
if ( !IsDefined( self.enemy ) || !IsAlive( self.enemy ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( !self CanSee( self.enemy ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
dist = DistanceSquared( self.origin, self.enemy.origin );
|
|
if ( dist > run_dist_squared )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
height = self.origin[2] - self.enemy.origin[2];
|
|
if ( abs( height ) > run_height )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
yaw = self AbsYawToEnemy();
|
|
if ( yaw > run_yaw )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pitch = AngleClamp180( VectorToAngles( self.origin - self.enemy.origin )[0] );
|
|
if ( abs( pitch ) > run_pitch )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function private is_target_valid( dog, target )
|
|
{
|
|
if( !IsDefined( target ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !IsAlive( target ) )
|
|
{
|
|
return false;
|
|
}
|
|
if (!(dog.team == "allies"))
|
|
{
|
|
if( !IsPlayer( target ) && SessionModeIsZombiesGame())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( IsDefined(target.is_zombie) && target.is_zombie == true )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if( IsPlayer( target ) && target.sessionstate == "spectator" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( IsPlayer( target ) && target.sessionstate == "intermission" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( ( isdefined( self.intermission ) && self.intermission ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( ( isdefined( target.ignoreme ) && target.ignoreme ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( target IsNoTarget() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( dog.team == target.team )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//for additional level specific checks
|
|
if( IsPlayer( target ) && IsDefined( level.is_player_valid_override ) )
|
|
{
|
|
return [[ level.is_player_valid_override ]]( target );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function private get_favorite_enemy( dog )
|
|
{
|
|
dog_targets = [];
|
|
if ( SessionModeIsZombiesGame() )
|
|
{
|
|
if (self.team == "allies")
|
|
{
|
|
dog_targets = GetAITeamArray( level.zombie_team );
|
|
}
|
|
else
|
|
{
|
|
dog_targets = getplayers();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dog_targets = ArrayCombine( getplayers(), GetAIArray(), false, false );
|
|
}
|
|
least_hunted = dog_targets[0];
|
|
closest_target_dist_squared = undefined;
|
|
for( i = 0; i < dog_targets.size; i++ )
|
|
{
|
|
if ( !isdefined( dog_targets[i].hunted_by ) )
|
|
{
|
|
dog_targets[i].hunted_by = 0;
|
|
}
|
|
|
|
if( !is_target_valid( dog, dog_targets[i] ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( !is_target_valid( dog, least_hunted ) )
|
|
{
|
|
least_hunted = dog_targets[i];
|
|
}
|
|
|
|
dist_squared = DistanceSquared( dog.origin, dog_targets[i].origin );
|
|
if( dog_targets[i].hunted_by <= least_hunted.hunted_by && ( !IsDefined( closest_target_dist_squared ) || dist_squared < closest_target_dist_squared ) )
|
|
{
|
|
least_hunted = dog_targets[i];
|
|
closest_target_dist_squared = dist_squared;
|
|
}
|
|
}
|
|
// do not return the default first player if he is invalid
|
|
if( !is_target_valid( dog, least_hunted ) )
|
|
{
|
|
return undefined;
|
|
}
|
|
else
|
|
{
|
|
least_hunted.hunted_by += 1;
|
|
|
|
return least_hunted;
|
|
}
|
|
|
|
}
|
|
|
|
function get_last_valid_position()
|
|
{
|
|
if ( IsPlayer( self ) )
|
|
return self.last_valid_position;
|
|
return self.origin;
|
|
}
|
|
|
|
function get_locomotion_target( behaviorTreeEntity )
|
|
{
|
|
last_valid_position = behaviorTreeEntity.favoriteenemy get_last_valid_position();
|
|
if ( !IsDefined( last_valid_position ) )
|
|
return undefined;
|
|
|
|
locomotion_target = last_valid_position;
|
|
if ( ai::has_behavior_attribute( "spacing_value" ) )
|
|
{
|
|
// calculate the point based on the spacing values
|
|
spacing_near_dist = ai::get_behavior_attribute( "spacing_near_dist" );
|
|
spacing_far_dist = ai::get_behavior_attribute( "spacing_far_dist" );
|
|
spacing_horz_dist = ai::get_behavior_attribute( "spacing_horz_dist" );
|
|
spacing_value = ai::get_behavior_attribute( "spacing_value" );
|
|
|
|
// get the offset from our target and get a vector perpendicular to it
|
|
to_enemy = behaviorTreeEntity.favoriteenemy.origin - behaviorTreeEntity.origin;
|
|
perp = VectorNormalize( ( -to_enemy[1], to_enemy[0], 0 ) );
|
|
|
|
// calculate the offset from the target
|
|
offset = perp * spacing_horz_dist * spacing_value;
|
|
|
|
// lerp to actual target position based on distance
|
|
spacing_dist = math::clamp( Length( to_enemy ), spacing_near_dist, spacing_far_dist );
|
|
lerp_amount = math::clamp( ( spacing_dist - spacing_near_dist ) / ( spacing_far_dist - spacing_near_dist ), 0.0, 1.0 );
|
|
desired_point = last_valid_position + ( offset * lerp_amount );
|
|
|
|
// get the closest pathable point
|
|
desired_point = GetClosestPointOnNavMesh( desired_point, spacing_horz_dist * 1.2, 16 );
|
|
if ( IsDefined( desired_point ) )
|
|
{
|
|
///#
|
|
//line( behaviorTreeEntity.origin, desired_point, ( 1.0, 1.0, 0.0 ), 0.9, 1 );
|
|
//#/
|
|
locomotion_target = desired_point;
|
|
}
|
|
}
|
|
|
|
return locomotion_target;
|
|
}
|
|
|
|
// ------- ZOMBIE DOG - TARGET SERVICE -----------//
|
|
function zombieDogTargetService( behaviorTreeEntity )
|
|
{
|
|
// don't target if the round is over
|
|
if ( ( isdefined( level.intermission ) && level.intermission ) )
|
|
{
|
|
behaviorTreeEntity ClearPath();
|
|
return;
|
|
}
|
|
|
|
// don't target if we're in puppet mode
|
|
/#
|
|
if ( ( isdefined( behaviortreeentity.isPuppet ) && behaviortreeentity.isPuppet ) )
|
|
{
|
|
return;
|
|
}
|
|
#/
|
|
|
|
// don't move if we don't have a target
|
|
if ( behaviorTreeEntity.ignoreall || behaviorTreeEntity.pacifist || ( isdefined( behaviorTreeEntity.favoriteenemy ) && !is_target_valid( behaviorTreeEntity, behaviorTreeEntity.favoriteenemy ) ) )
|
|
{
|
|
if ( isdefined( behaviorTreeEntity.favoriteenemy ) && isdefined( behaviorTreeEntity.favoriteenemy.hunted_by ) && behaviorTreeEntity.favoriteenemy.hunted_by > 0 )
|
|
{
|
|
// if we had a target before, decrement its hunted count so it knows we're not after it anymore
|
|
behaviorTreeEntity.favoriteenemy.hunted_by--;
|
|
}
|
|
behaviorTreeEntity.favoriteenemy = undefined;
|
|
behaviorTreeEntity.hasSeenFavoriteEnemy = false;
|
|
|
|
//if enemies are not valid, stay at the spot
|
|
if ( !behaviorTreeEntity.ignoreall )
|
|
{
|
|
behaviorTreeEntity SetGoal( behaviorTreeEntity.origin );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// dog isn't in playable area yet
|
|
if ( ( isdefined( behaviorTreeEntity.ignoreme ) && behaviorTreeEntity.ignoreme ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// calculate a favorite enemy if we don't have one (zombie mode does this in another script)
|
|
if( (!SessionModeIsZombiesGame() || behaviorTreeEntity.team == "allies") && !is_target_valid(behaviorTreeEntity, behaviorTreeEntity.favoriteenemy) )
|
|
{
|
|
behaviorTreeEntity.favoriteenemy = get_favorite_enemy( behaviorTreeEntity );
|
|
}
|
|
|
|
// have we seen our target yet?
|
|
if ( !( isdefined( behaviorTreeEntity.hasSeenFavoriteEnemy ) && behaviorTreeEntity.hasSeenFavoriteEnemy ) )
|
|
{
|
|
if ( IsDefined( behaviorTreeEntity.favoriteenemy ) && behaviorTreeEntity need_to_run() )
|
|
{
|
|
behaviorTreeEntity.hasSeenFavoriteEnemy = true;
|
|
}
|
|
}
|
|
|
|
// always move towards enemy
|
|
if ( IsDefined( behaviorTreeEntity.favoriteenemy ) )
|
|
{
|
|
if ( IsDefined( level.enemy_location_override_func ) )
|
|
{
|
|
goalPos = [[ level.enemy_location_override_func ]]( behaviorTreeEntity, behaviorTreeEntity.favoriteenemy );
|
|
|
|
if ( IsDefined( goalPos ) )
|
|
{
|
|
behaviorTreeEntity SetGoal( goalPos );
|
|
return;
|
|
}
|
|
}
|
|
|
|
locomotion_target = get_locomotion_target( behaviorTreeEntity );
|
|
if ( IsDefined( locomotion_target ) )
|
|
{
|
|
repathDist = 16;
|
|
if ( !IsDefined( behaviorTreeEntity.lastTargetPosition ) || DistanceSquared( behaviorTreeEntity.lastTargetPosition, locomotion_target ) > ( (repathDist) * (repathDist) ) || !behaviorTreeEntity HasPath() )
|
|
{
|
|
behaviorTreeEntity UsePosition( locomotion_target );
|
|
behaviorTreeEntity.lastTargetPosition = locomotion_target;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------- ZOMBIE DOG - SHOULD MELEE CONDITION -----------//
|
|
function zombieDogShouldMelee( behaviorTreeEntity )
|
|
{
|
|
// don't melee if we don't have a target
|
|
if ( behaviorTreeEntity.ignoreall || !is_target_valid( behaviorTreeEntity, behaviorTreeEntity.favoriteenemy ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( !( isdefined( level.intermission ) && level.intermission ) )
|
|
{
|
|
meleeDist = 6 * 12; // 6 ft
|
|
if ( DistanceSquared( behaviorTreeEntity.origin, behaviorTreeEntity.favoriteenemy.origin ) < ( (meleeDist) * (meleeDist) ) && behaviorTreeEntity CanSee( behaviorTreeEntity.favoriteenemy ) )
|
|
{
|
|
dog_eye = behaviorTreeEntity.origin + ( 0, 0, 40 );
|
|
enemy_eye = behaviorTreeEntity.favoriteenemy GetEye();
|
|
|
|
clip_mask = (1 << 0) | (1 << 3);
|
|
trace = PhysicsTrace( dog_eye, enemy_eye, (0, 0, 0), (0, 0, 0), self, clip_mask );
|
|
can_melee = ( trace[ "fraction" ] == 1.0 || ( IsDefined( trace[ "entity" ] ) && trace[ "entity" ] == behaviorTreeEntity.favoriteenemy ) );
|
|
if ( ( isdefined( can_melee ) && can_melee ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ------- ZOMBIE DOG - SHOULD WALK CONDITION -----------//
|
|
function zombieDogShouldWalk( behaviorTreeEntity )
|
|
{
|
|
return BB_GetShouldRunStatus() == "walk";
|
|
}
|
|
|
|
// ------- ZOMBIE DOG - SHOULD RUN CONDITION -----------//
|
|
function zombieDogShouldRun( behaviorTreeEntity )
|
|
{
|
|
return BB_GetShouldRunStatus() == "run";
|
|
}
|
|
|
|
// ------- ZOMBIE DOG - MELEE ACTION -----------//
|
|
function use_low_attack()
|
|
{
|
|
if ( !IsDefined( self.enemy ) || !IsPlayer( self.enemy ) )
|
|
return false;
|
|
|
|
height_diff = self.enemy.origin[2] - self.origin[2];
|
|
|
|
low_enough = 30.0;
|
|
|
|
if ( height_diff < low_enough && self.enemy GetStance() == "prone" )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// check if the jumping melee attack origin would be in a solid
|
|
melee_origin = ( self.origin[0], self.origin[1], self.origin[2] + 65 );
|
|
enemy_origin = ( self.enemy.origin[0], self.enemy.origin[1], self.enemy.origin[2] + 32 );
|
|
|
|
if ( !BulletTracePassed( melee_origin, enemy_origin, false, self ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function zombieDogMeleeAction( behaviorTreeEntity, asmStateName )
|
|
{
|
|
behaviorTreeEntity ClearPath();
|
|
|
|
context = "high";
|
|
if ( behaviorTreeEntity use_low_attack() )
|
|
context = "low";
|
|
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_context", context );
|
|
|
|
AnimationStateNetworkUtility::RequestState( behaviorTreeEntity, asmStateName );
|
|
return 5;
|
|
}
|
|
|
|
function zombieDogMeleeActionTerminate( behaviorTreeEntity, asmStateName )
|
|
{
|
|
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_context", undefined );
|
|
|
|
return 4;
|
|
}
|
|
|
|
function zombieDogGravity( entity, attribute, oldValue, value )
|
|
{
|
|
Blackboard::SetBlackBoardAttribute( entity, "_low_gravity", value );
|
|
}
|