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

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 );
}