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