#using scripts\shared\ai_shared; #using scripts\shared\callbacks_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\array_shared; #using scripts\shared\ai\systems\animation_state_machine_utility; #using scripts\shared\ai\systems\animation_state_machine_notetracks; #using scripts\shared\ai\systems\animation_state_machine_mocomp; #using scripts\shared\ai\archetype_locomotion_utility; #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\debug; #using scripts\shared\ai\systems\gib; #using scripts\shared\ai\zombie_utility; #using scripts\shared\ai\zombie_death; #using scripts\shared\ai\zombie_shared; #using scripts\codescripts\struct; #using scripts\shared\ai\archetype_mocomps_utility; //INTERFACE #using scripts\shared\ai\systems\ai_interface; #using scripts\shared\ai\archetype_zombie_interface; #namespace ZombieBehavior; function autoexec init() { // INIT BEHAVIORS InitZombieBehaviorsAndASM(); // INIT BLACKBOARD spawner::add_archetype_spawn_function( "zombie", &ArchetypeZombieBlackboardInit ); spawner::add_archetype_spawn_function( "zombie", &ArchetypeZombieDeathOverrideInit ); spawner::add_archetype_spawn_function( "zombie", &ArchetypeZombieSpecialEffectsInit ); // INIT ZOMBIE ON SPAWN spawner::add_archetype_spawn_function( "zombie", &zombie_utility::zombieSpawnSetup ); clientfield::register( "actor", "zombie", 1, 1, "int"); clientfield::register( "actor", "zombie_special_day", 6001, 1, "counter" ); ZombieInterface::RegisterZombieInterfaceAttributes(); } function private InitZombieBehaviorsAndASM() { BehaviorTreeNetworkUtility::RegisterBehaviorTreeAction("zombieMoveAction",&zombieMoveAction,&zombieMoveActionUpdate,undefined);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieTargetService",&zombieTargetService);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieCrawlerCollisionService",&zombieCrawlerCollision);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieTraversalService",&zombieTraversalService);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieIsAtAttackObject",&zombieIsAtAttackObject);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieShouldAttackObject",&zombieShouldAttackObject);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieShouldMelee",&zombieShouldMeleeCondition);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieShouldJumpMelee",&zombieShouldJumpMeleeCondition);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieShouldJumpUnderwaterMelee",&zombieShouldJumpUnderwaterMelee);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieGibLegsCondition",&zombieGibLegsCondition);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieShouldDisplayPain",&zombieShouldDisplayPain);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isZombieWalking",&isZombieWalking);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieShouldMeleeSuicide",&zombieShouldMeleeSuicide);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieMeleeSuicideStart",&zombieMeleeSuicideStart);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieMeleeSuicideUpdate",&zombieMeleeSuicideUpdate);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieMeleeSuicideTerminate",&zombieMeleeSuicideTerminate);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieShouldJuke",&zombieShouldJukeCondition);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieJukeActionStart",&zombieJukeActionStart);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieJukeActionTerminate",&zombieJukeActionTerminate);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieDeathAction",&zombieDeathAction);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieJukeService",&zombieJuke);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieStumbleService",&zombieStumble);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieStumbleCondition",&zombieShouldStumbleCondition);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieStumbleActionStart",&zombieStumbleActionStart);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieAttackObjectStart",&zombieAttackObjectStart);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieAttackObjectTerminate",&zombieAttackObjectTerminate);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("wasKilledByInterdimensionalGun",&wasKilledByInterdimensionalGunCondition);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("wasCrushedByInterdimensionalGunBlackhole",&wasCrushedByInterdimensionalGunBlackholeCondition);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieIDGunDeathUpdate",&zombieIDGunDeathUpdate);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieVortexPullUpdate",&zombieIDGunDeathUpdate);; //for doa BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieHasLegs",&zombieHasLegs);; BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieShouldProceduralTraverse",&zombieShouldProceduralTraverse);; AnimationStateNetwork::RegisterNotetrackHandlerFunction("zombie_melee",&zombieNotetrackMeleeFire);; AnimationStateNetwork::RegisterNotetrackHandlerFunction("crushed",&zombieNotetrackCrushFire);; // ------- ZOMBIE DEATH -----------// AnimationStateNetwork::RegisterAnimationMocomp("mocomp_death_idgun@zombie",&zombieIDGunDeathMocompStart,undefined,undefined);; AnimationStateNetwork::RegisterAnimationMocomp("mocomp_vortex_pull@zombie",&zombieIDGunDeathMocompStart,undefined,undefined);; //for doa AnimationStateNetwork::RegisterAnimationMocomp("mocomp_death_idgun_hole@zombie",&zombieIDGunHoleDeathMocompStart,undefined,&zombieIDGunHoleDeathMocompTerminate);; AnimationStateNetwork::RegisterAnimationMocomp("mocomp_turn@zombie",&zombieTurnMocompStart,&zombieTurnMocompUpdate,&zombieTurnMocompTerminate);; AnimationStateNetwork::RegisterAnimationMocomp("mocomp_melee_jump@zombie",&zombieMeleeJumpMocompStart,&zombieMeleeJumpMocompUpdate,&zombieMeleeJumpMocompTerminate);; AnimationStateNetwork::RegisterAnimationMocomp("mocomp_zombie_idle@zombie",&zombieZombieIdleMocompStart,undefined,undefined);; AnimationStateNetwork::RegisterAnimationMocomp("mocomp_attack_object@zombie",&zombieAttackObjectMocompStart,&zombieAttackObjectMocompUpdate,undefined);; } function ArchetypeZombieBlackboardInit() { // CREATE BLACKBOARD Blackboard::CreateBlackBoardForEntity( self ); // USE UTILITY BLACKBOARD self AiUtility::RegisterUtilityBlackboardAttributes(); // CREATE INTERFACE ai::CreateInterfaceForEntity( self ); // CREATE ZOMBIE BLACKBOARD Blackboard::RegisterBlackBoardAttribute(self,"_arms_position","arms_up",&BB_GetArmsPosition); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_arms_position");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_locomotion_speed","locomotion_speed_walk",&BB_GetLocomotionSpeedType); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_locomotion_speed");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_has_legs","has_legs_yes",&BB_GetHasLegsStatus); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_has_legs");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_variant_type",0,&BB_GetVariantType); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_variant_type");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_which_board_pull",undefined,undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_which_board_pull");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_board_attack_spot",undefined,undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_board_attack_spot");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_grapple_direction",undefined,undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_grapple_direction");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_locomotion_should_turn","should_not_turn",&BB_GetShouldTurn); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_locomotion_should_turn");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_idgun_damage_direction","back",&BB_IDGunGetDamageDirection); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_idgun_damage_direction");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_low_gravity_variant",0,&BB_GetLowGravityVariant); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_low_gravity_variant");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_knockdown_direction",undefined,undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_knockdown_direction");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_knockdown_type",undefined,undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_knockdown_type");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_whirlwind_speed","whirlwind_normal",undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_whirlwind_speed");#/ }; Blackboard::RegisterBlackBoardAttribute(self,"_zombie_blackholebomb_pull_state",undefined,undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_zombie_blackholebomb_pull_state");#/ }; // REGISTER ANIMSCRIPTED CALLBACK self.___ArchetypeOnAnimscriptedCallback = &ArchetypeZombieOnAnimscriptedCallback; // ENABLE DEBUGGING IN ODYSSEY /#self FinalizeTrackedBlackboardAttributes();#/; } function private ArchetypeZombieOnAnimscriptedCallback( entity ) { // UNREGISTER THE BLACKBOARD entity.__blackboard = undefined; // REREGISTER BLACKBOARD entity ArchetypeZombieBlackboardInit(); } function ArchetypeZombieSpecialEffectsInit() { AiUtility::AddAIOverrideDamageCallback( self, &ArchetypeZombieSpecialEffectsCallback ); } function private ArchetypeZombieSpecialEffectsCallback( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, damageFromUnderneath, modelIndex, partName ) { specialDayEffectChance = GetDvarInt("tu6_ffotd_zombieSpecialDayEffectsChance", 0); if( specialDayEffectChance && RandomInt(100) < specialDayEffectChance ) { if( IsDefined( eAttacker ) && IsPlayer( eAttacker ) ) { self clientfield::increment("zombie_special_day"); } } return iDamage; } // ------- BLACKBOARD -----------// function BB_GetArmsPosition() { if( IsDefined( self.zombie_arms_position ) ) { if( self.zombie_arms_position == "up" ) return "arms_up"; return "arms_down"; } return "arms_up"; } function BB_GetLocomotionSpeedType() { if ( IsDefined( self.zombie_move_speed ) ) { if( self.zombie_move_speed == "walk" ) { return "locomotion_speed_walk"; } else if( self.zombie_move_speed == "run" ) { return "locomotion_speed_run"; } else if( self.zombie_move_speed == "sprint" ) { return "locomotion_speed_sprint"; } else if( self.zombie_move_speed == "super_sprint" ) { return "locomotion_speed_super_sprint"; } else if( self.zombie_move_speed == "jump_pad_super_sprint" ) { return "locomotion_speed_jump_pad_super_sprint"; } else if( self.zombie_move_speed == "burned" ) { return "locomotion_speed_burned"; } else if( self.zombie_move_speed == "slide" ) { return "locomotion_speed_slide"; } } return "locomotion_speed_walk"; } function BB_GetVariantType() { if( IsDefined( self.variant_type ) ) { return self.variant_type; } return 0; } function BB_GetHasLegsStatus() { if( self.missingLegs ) return "has_legs_no"; return "has_legs_yes"; } function BB_GetShouldTurn() { if( IsDefined( self.should_turn ) && self.should_turn ) { return "should_turn"; } return "should_not_turn"; } function BB_IDGunGetDamageDirection() { if( IsDefined( self.damage_direction ) ) { return self.damage_direction; } return self AiUtility::BB_GetDamageDirection(); } function BB_GetLowGravityVariant() { if ( isdefined( self.low_gravity_variant ) ) { return self.low_gravity_variant; } return 0; } // ------- BLACKBOARD -----------// function isZombieWalking( behaviorTreeEntity ) { return !( isdefined( behaviorTreeEntity.missingLegs ) && behaviorTreeEntity.missingLegs ); } function zombieShouldDisplayPain( behaviorTreeEntity ) { if( ( isdefined( behaviorTreeEntity.suicidalDeath ) && behaviorTreeEntity.suicidalDeath ) ) return false; return !( isdefined( behaviorTreeEntity.missingLegs ) && behaviorTreeEntity.missingLegs ); } function zombieShouldJukeCondition( behaviorTreeEntity ) { if ( IsDefined( behaviorTreeEntity.juke ) && ( behaviorTreeEntity.juke == "left" || behaviorTreeEntity.juke == "right" ) ) { return true; } return false; } function zombieShouldStumbleCondition( behaviorTreeEntity ) { if ( isDefined( behaviorTreeEntity.stumble ) ) { return true; } return false; } function private zombieJukeActionStart( behaviorTreeEntity ) { Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_juke_direction", behaviorTreeEntity.juke ); if ( IsDefined( behaviorTreeEntity.jukeDistance ) ) { Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_juke_distance", behaviorTreeEntity.jukeDistance ); } else { //default to short although this should never happen Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_juke_distance", "short" ); } behaviorTreeEntity.jukeDistance = undefined; behaviorTreeEntity.juke = undefined; } function private zombieJukeActionTerminate( behaviorTreeEntity ) { behaviorTreeEntity ClearPath(); } function private zombieStumbleActionStart( behaviorTreeEntity ) { behaviorTreeEntity.stumble = undefined; } function private zombieAttackObjectStart( behaviorTreeEntity ) { behaviorTreeEntity.is_inert = true; } function private zombieAttackObjectTerminate( behaviorTreeEntity ) { behaviorTreeEntity.is_inert = false; } function zombieGibLegsCondition( behaviorTreeEntity) { return GibServerUtils::IsGibbed( behaviorTreeEntity, 256) || GibServerUtils::IsGibbed( behaviorTreeEntity, 128); } function zombieNotetrackMeleeFire( entity ) { if ( ( isdefined( entity.aat_turned ) && entity.aat_turned ) ) { if ( IsDefined( entity.enemy ) && !isPlayer( entity.enemy ) ) { if ( entity.enemy.archetype == "zombie" && ( isdefined( entity.enemy.allowDeath ) && entity.enemy.allowDeath )) { GibServerUtils::GibHead( entity.enemy ); entity.enemy zombie_utility::gib_random_parts(); entity.enemy Kill(); entity.n_aat_turned_zombie_kills++; // Tracked in _zm_aat_turned.gsc } else if ( ( entity.enemy.archetype == "zombie_quad" || entity.enemy.archetype == "spider" ) && ( isdefined( entity.enemy.allowDeath ) && entity.enemy.allowDeath )) { entity.enemy Kill(); entity.n_aat_turned_zombie_kills++; // Tracked in _zm_aat_turned.gsc } else if( ( isdefined( entity.enemy.canBeTargetedByTurnedZombies ) && entity.enemy.canBeTargetedByTurnedZombies ) ) { entity Melee(); } } } else { if ( IsDefined( entity.enemy ) && ( ( isdefined( entity.enemy.bgb_in_plain_sight_active ) && entity.enemy.bgb_in_plain_sight_active ) || ( isdefined( entity.enemy.bgb_idle_eyes_active ) && entity.enemy.bgb_idle_eyes_active ) ) ) { return; } if ( IsDefined( entity.enemy ) && ( isdefined( entity.enemy.allow_zombie_to_target_ai ) && entity.enemy.allow_zombie_to_target_ai ) ) { if ( entity.enemy.health > 0 ) { entity.enemy DoDamage( entity.meleeWeapon.meleeDamage, entity.origin, entity, entity, "none", "MOD_MELEE" ); } return; } entity Melee(); /# Record3DText( "melee", self.origin, ( 1, 0, 0 ), "Script", entity ); #/ if ( zombieShouldAttackObject( entity ) ) { if ( IsDefined( level.attackableCallback ) ) { entity.attackable [[ level.attackableCallback ]]( entity ); } } } } function zombieNotetrackCrushFire( behaviorTreeEntity ) { behaviorTreeEntity delete(); } function zombieTargetService( behaviorTreeEntity) { if ( isdefined( behaviorTreeEntity.enablePushTime ) ) { if ( GetTime() >= behaviorTreeEntity.enablePushTime ) { behaviorTreeEntity PushActors( true ); behaviorTreeEntity.enablePushTime = undefined; } } if( ( isdefined( behaviorTreeEntity.disableTargetService ) && behaviorTreeEntity.disableTargetService ) ) { return false; } if ( ( isdefined( behaviorTreeEntity.ignoreall ) && behaviorTreeEntity.ignoreall ) ) { return false; } specificTarget = undefined; // Check if there is a point of interest if( IsDefined( level.zombieLevelSpecificTargetCallback ) ) { specificTarget = [[level.zombieLevelSpecificTargetCallback]](); } if( IsDefined( specificTarget ) ) { behaviorTreeEntity SetGoal( specificTarget.origin ); } else if( isdefined( behaviorTreeEntity.v_zombie_custom_goal_pos ) ) { goalPos = behaviorTreeEntity.v_zombie_custom_goal_pos; if ( isdefined( behaviorTreeEntity.n_zombie_custom_goal_radius ) ) { behaviorTreeEntity.goalradius = behaviorTreeEntity.n_zombie_custom_goal_radius; } behaviorTreeEntity SetGoal( goalPos ); } else { player = zombie_utility::get_closest_valid_player( self.origin, self.ignore_player ); if( !IsDefined( player ) ) { if( IsDefined( self.ignore_player ) ) { if(isDefined(level._should_skip_ignore_player_logic) && [[level._should_skip_ignore_player_logic]]() ) { return false; } self.ignore_player = []; } self SetGoal( self.origin ); return false; } else { if ( IsDefined( player.last_valid_position ) ) { if( !( isdefined( self.zombie_do_not_update_goal ) && self.zombie_do_not_update_goal ) ) { if( ( isdefined( level.zombie_use_zigzag_path ) && level.zombie_use_zigzag_path ) ) { behaviorTreeEntity zombieUpdateZigZagGoal(); } else { behaviorTreeEntity SetGoal( player.last_valid_position ); } } return true; } else { if( !( isdefined( self.zombie_do_not_update_goal ) && self.zombie_do_not_update_goal ) ) { behaviorTreeEntity SetGoal( behaviorTreeEntity.origin ); } return false; } } } } function zombieUpdateZigZagGoal() { AIProfile_BeginEntry( "zombieUpdateZigZagGoal" ); const ZM_ZOMBIE_HEIGHT = 72; const ZM_ZOMBIE_ZIGZAG_GOAL_TOLERENCE_DIST = 72; const ZM_ZOMBIE_ZIGZAZ_ACTIVATION_DIST = 250; shouldRepath = false; if ( !shouldRepath && IsDefined( self.favoriteenemy ) ) { if ( !IsDefined( self.nextGoalUpdate ) || self.nextGoalUpdate <= GetTime() ) { // It's been a while, repath! shouldRepath = true; } else if ( DistanceSquared( self.origin, self.favoriteenemy.origin ) <= ( (ZM_ZOMBIE_ZIGZAZ_ACTIVATION_DIST) * (ZM_ZOMBIE_ZIGZAZ_ACTIVATION_DIST) ) ) { // Repath if close to the enemy. shouldRepath = true; } else if ( IsDefined( self.pathGoalPos ) ) { // Repath if close to the current goal position. distanceToGoalSqr = DistanceSquared( self.origin, self.pathGoalPos ); shouldRepath = distanceToGoalSqr < ( (ZM_ZOMBIE_ZIGZAG_GOAL_TOLERENCE_DIST) * (ZM_ZOMBIE_ZIGZAG_GOAL_TOLERENCE_DIST) ); } } if ( ( isdefined( self.keep_moving ) && self.keep_moving ) ) { if ( GetTime() > self.keep_moving_time ) { self.keep_moving = false; } } if ( shouldRepath ) { goalPos = self.favoriteenemy.origin; if ( IsDefined( self.favoriteenemy.last_valid_position ) ) { goalPos = self.favoriteenemy.last_valid_position; } // Fist set the position directly to the current goal position self SetGoal( goalPos ); // Randomize zig-zag path following if 20+ feet away from the enemy. This will override the goal position set earlier if needed. if ( DistanceSquared( self.origin, goalPos ) > ( (ZM_ZOMBIE_ZIGZAZ_ACTIVATION_DIST) * (ZM_ZOMBIE_ZIGZAZ_ACTIVATION_DIST) ) ) { self.keep_moving = true; self.keep_moving_time = GetTime() + 250; path = self CalcApproximatePathToPosition( goalPos,false ); /# if ( GetDvarInt( "ai_debugZigZag" ) ) { for ( index = 1; index < path.size; index++ ) { RecordLine( path[index - 1], path[index], ( 1, .5, 0 ), "Animscript", self ); } } #/ if( IsDefined( level._zombieZigZagDistanceMin ) && IsDefined( level._zombieZigZagDistanceMax ) ) { min = level._zombieZigZagDistanceMin; max = level._zombieZigZagDistanceMax; } else { min = 240; max = 600; } deviationDistance = RandomIntRange( min, max ); // 20 to 50 feet segmentLength = 0; // Walks the current path to find the point where the AI should deviate from their normal path. for ( index = 1; index < path.size; index++ ) { currentSegLength = Distance( path[index - 1], path[index] ); if ( ( segmentLength + currentSegLength ) > deviationDistance ) { remainingLength = deviationDistance - segmentLength; seedPosition = path[index - 1] + ( VectorNormalize( path[index] - path[index - 1] ) * remainingLength ); /# RecordCircle( seedPosition, 2, ( 1, .5, 0 ), "Animscript", self ); #/ innerZigZagRadius = 0; outerZigZagRadius = 96; // Find a point offset from the deviation point along the path. queryResult = PositionQuery_Source_Navigation( seedPosition, innerZigZagRadius, outerZigZagRadius, 0.5 * ZM_ZOMBIE_HEIGHT, 16, self, 16 ); PositionQuery_Filter_InClaimedLocation( queryResult, self ); if ( queryResult.data.size > 0 ) { point = queryResult.data[ RandomInt( queryResult.data.size ) ]; // Use the deviated point as the path instead. self SetGoal( point.origin ); } break; } segmentLength += currentSegLength; } } if( IsDefined( level._zombieZigZagTimeMin ) && IsDefined( level._zombieZigZagTimeMax ) ) { minTime = level._zombieZigZagTimeMin; maxTime = level._zombieZigZagTimeMax; } else { minTime = 2500; maxTime = 3500; } // Force repathing after a certain amount of time to smooth out movement. self.nextGoalUpdate = GetTime() + RandomIntRange(minTime, maxTime); } AIProfile_EndEntry(); } // turn off actor pushing if a regular zombie is too close function zombieCrawlerCollision( behaviorTreeEntity ) { if ( !( isdefined( behaviorTreeEntity.missingLegs ) && behaviorTreeEntity.missingLegs ) && !( isdefined( behaviorTreeEntity.knockdown ) && behaviorTreeEntity.knockdown ) ) { return false; } if ( IsDefined( behaviorTreeEntity.dontPushTime ) ) { if ( GetTime() < behaviorTreeEntity.dontPushTime ) { return true; } } zombies = GetAITeamArray( level.zombie_team ); foreach( zombie in zombies ) { if ( zombie == behaviorTreeEntity ) { continue; } if ( ( isdefined( zombie.missingLegs ) && zombie.missingLegs ) || ( isdefined( zombie.knockdown ) && zombie.knockdown ) ) { continue; } dist_sq = DistanceSquared( behaviorTreeEntity.origin, zombie.origin ); if ( dist_sq < 120 * 120 ) { behaviorTreeEntity PushActors( false ); behaviorTreeEntity.dontPushTime = GetTime() + 2000; return true; } } behaviorTreeEntity PushActors( true ); return false; } function zombieTraversalService( entity ) { if ( isdefined( entity.traverseStartNode ) ) { entity PushActors( false ); return true; } return false; } function zombieIsAtAttackObject( entity ) { if ( ( isdefined( entity.missingLegs ) && entity.missingLegs ) ) { return false; } if ( IsDefined( entity.enemyoverride ) && IsDefined( entity.enemyoverride[1] ) ) { return false; } if ( IsDefined( entity.favoriteenemy ) && ( isdefined( entity.favoriteenemy.b_is_designated_target ) && entity.favoriteenemy.b_is_designated_target ) ) { return false; } if ( ( isdefined( entity.aat_turned ) && entity.aat_turned ) ) { return false; } if ( IsDefined( entity.attackable ) && ( isdefined( entity.attackable.is_active ) && entity.attackable.is_active ) ) { if ( !IsDefined( entity.attackable_slot ) ) { return false; } //if ( entity IsAtGoal() ) //{ // entity.is_at_attackable = true; // return true; //} dist = Distance2DSquared( entity.origin, entity.attackable_slot.origin ); if ( dist < 256 ) { height_offset = Abs( entity.origin[2] - entity.attackable_slot.origin[2] ); if ( height_offset < 32 ) { entity.is_at_attackable = true; return true; } } //yawToObject = AngleClamp180( entity.angles[ 1 ] - entity.attackable_slot.angles[ 1 ] ); //if( abs( yawToObject ) > ZM_MELEE_YAW ) //{ // return false; //} } return false; } function zombieShouldAttackObject( entity ) { if ( ( isdefined( entity.missingLegs ) && entity.missingLegs ) ) { return false; } if ( IsDefined( entity.enemyoverride ) && IsDefined( entity.enemyoverride[1] ) ) { return false; } if ( IsDefined( entity.favoriteenemy ) && ( isdefined( entity.favoriteenemy.b_is_designated_target ) && entity.favoriteenemy.b_is_designated_target ) ) { return false; } if ( ( isdefined( entity.aat_turned ) && entity.aat_turned ) ) { return false; } if ( IsDefined( entity.attackable ) && ( isdefined( entity.attackable.is_active ) && entity.attackable.is_active ) ) { if ( ( isdefined( entity.is_at_attackable ) && entity.is_at_attackable ) ) { return true; } } return false; } function zombieShouldMeleeCondition( behaviorTreeEntity ) { if( IsDefined( behaviorTreeEntity.enemyoverride ) && IsDefined( behaviorTreeEntity.enemyoverride[1] ) ) { return false; } if( !IsDefined( behaviortreeentity.enemy ) ) { return false; } if( IsDefined( behaviorTreeEntity.marked_for_death ) ) { return false; } if( ( isdefined( behaviorTreeEntity.ignoreMelee ) && behaviorTreeEntity.ignoreMelee ) ) { return false; } if( DistanceSquared( behaviorTreeEntity.origin, behaviorTreeEntity.enemy.origin ) > 64 * 64 ) { return false; } yawToEnemy = AngleClamp180( behaviorTreeEntity.angles[ 1 ] - (VectorToAngles(behaviorTreeEntity.enemy.origin-behaviorTreeEntity.origin)[1]) ); if( abs( yawToEnemy ) > 60 ) { return false; } return true; } function zombieShouldJumpMeleeCondition( behaviorTreeEntity ) { if ( !( isdefined( behaviorTreeEntity.low_gravity ) && behaviorTreeEntity.low_gravity ) ) { return false; } if( IsDefined( behaviorTreeEntity.enemyoverride ) && IsDefined( behaviorTreeEntity.enemyoverride[1] ) ) { return false; } if( !IsDefined( behaviortreeentity.enemy ) ) { return false; } if( IsDefined( behaviorTreeEntity.marked_for_death ) ) { return false; } if( ( isdefined( behaviorTreeEntity.ignoreMelee ) && behaviorTreeEntity.ignoreMelee ) ) { return false; } if ( behaviorTreeEntity.enemy IsOnGround() ) { return false; } jumpChance = GetDvarFloat( "zmMeleeJumpChance", 0.5 ); if ( ( ( behaviorTreeEntity GetEntityNumber() % 10 ) / 10 ) > jumpChance ) { return false; } predictedPosition = behaviorTreeEntity.enemy.origin + behaviorTreeEntity.enemy GetVelocity() * .05 * 2; jumpDistanceSq = pow( GetDvarInt( "zmMeleeJumpDistance", 180 ), 2 ); if( Distance2DSquared( behaviorTreeEntity.origin, predictedPosition ) > jumpDistanceSq ) { return false; } yawToEnemy = AngleClamp180( behaviorTreeEntity.angles[ 1 ] - (VectorToAngles(behaviorTreeEntity.enemy.origin-behaviorTreeEntity.origin)[1]) ); if( abs( yawToEnemy ) > 60 ) { return false; } heightToEnemy = behaviorTreeEntity.enemy.origin[2] - behaviorTreeEntity.origin[2]; if ( heightToEnemy <= GetDvarInt( "zmMeleeJumpHeightDifference", 60 ) ) { return false; } return true; } function zombieShouldJumpUnderwaterMelee( behaviorTreeEntity ) { if( IsDefined( behaviorTreeEntity.enemyoverride ) && IsDefined( behaviorTreeEntity.enemyoverride[1] ) ) { return false; } if( !IsDefined( behaviortreeentity.enemy ) ) { return false; } if( IsDefined( behaviorTreeEntity.marked_for_death ) ) { return false; } if( ( isdefined( behaviorTreeEntity.ignoreMelee ) && behaviorTreeEntity.ignoreMelee ) ) { return false; } if ( behaviorTreeEntity.enemy IsOnGround() ) { return false; } if ( behaviorTreeEntity DepthInWater() < 48 ) { return false; } jumpDistanceSq = pow( GetDvarInt( "zmMeleeWaterJumpDistance", 64 ), 2 ); if( Distance2DSquared( behaviorTreeEntity.origin, behaviorTreeEntity.enemy.origin ) > jumpDistanceSq ) { return false; } yawToEnemy = AngleClamp180( behaviorTreeEntity.angles[ 1 ] - (VectorToAngles(behaviorTreeEntity.enemy.origin-behaviorTreeEntity.origin)[1]) ); if( abs( yawToEnemy ) > 60 ) { return false; } heightToEnemy = behaviorTreeEntity.enemy.origin[2] - behaviorTreeEntity.origin[2]; if ( heightToEnemy <= GetDvarInt( "zmMeleeJumpUnderwaterHeightDifference", 48 ) ) { return false; } return true; } function zombieStumble( behaviorTreeEntity ) { if ( ( isdefined( behaviorTreeEntity.missingLegs ) && behaviorTreeEntity.missingLegs ) ) { return false; } if ( !( isdefined( behaviorTreeEntity.canStumble ) && behaviorTreeEntity.canStumble ) ) { return false; } if ( !IsDefined( behaviorTreeEntity.zombie_move_speed ) || behaviorTreeEntity.zombie_move_speed != "sprint" ) { return false; } if ( IsDefined( behaviorTreeEntity.stumble ) ) { return false; } if (!IsDefined( behaviorTreeEntity.next_stumble_time ) ) { behaviorTreeEntity.next_stumble_time = GetTime() + RandomIntRange( 9000, 12000 ); } if ( GetTime() > behaviorTreeEntity.next_stumble_time ) { if ( RandomInt( 100 ) < 5 ) { closestPlayer = ArrayGetClosest( behaviorTreeEntity.origin, level.players ); if( DistanceSquared( closestPlayer.origin, behaviorTreeEntity.origin ) > 50000 ) { if ( IsDefined( behaviorTreeEntity.next_juke_time ) ) { behaviorTreeEntity.next_juke_time = undefined; } behaviorTreeEntity.next_stumble_time = undefined; behaviorTreeEntity.stumble = true; return true; } } } return false; } function zombieJuke( behaviorTreeEntity ) { if ( !behaviorTreeEntity ai::has_behavior_attribute( "can_juke" ) ) { return false; } if( !behaviorTreeEntity ai::get_behavior_attribute( "can_juke" ) ) { return false; } if ( ( isdefined( behaviorTreeEntity.missingLegs ) && behaviorTreeEntity.missingLegs ) ) { return false; } if ( behaviorTreeEntity ZombieBehavior::bb_getlocomotionspeedtype() != "locomotion_speed_walk" ) { if ( behaviorTreeEntity ai::has_behavior_attribute( "spark_behavior" ) && !behaviorTreeEntity ai::get_behavior_attribute( "spark_behavior" ) ) { return false; } } if ( IsDefined( behaviorTreeEntity.juke ) ) { return false; } if ( !IsDefined( behaviorTreeEntity.next_juke_time ) ) { behaviorTreeEntity.next_juke_time = GetTime() + RandomIntRange( 7500, 9500 ); } if ( GetTime() > behaviorTreeEntity.next_juke_time ) { behaviorTreeEntity.next_juke_time = undefined; if ( RandomInt( 100 ) < 25 || ( behaviorTreeEntity ai::has_behavior_attribute( "spark_behavior" ) && behaviorTreeEntity ai::get_behavior_attribute( "spark_behavior" ) ) ) { if ( IsDefined( behaviorTreeEntity.next_stumble_time ) ) { behaviorTreeEntity.next_stumble_time = undefined; } forwardOffset = 15; behaviorTreeEntity.ignoreBackwardPosition = true; //TODO remove this temp var if( math::cointoss() ) //decide if going to be short or long juke { //try long juke jukeDistance = 101; behaviorTreeEntity.jukeDistance = "long"; switch( behaviorTreeEntity ZombieBehavior::bb_getlocomotionspeedtype() ) { case "locomotion_speed_walk": case "locomotion_speed_run": forwardOffset = 122; break; case "locomotion_speed_sprint": forwardOffset = 129; break; } behaviorTreeEntity.juke = AiUtility::calculateJukeDirection( behaviorTreeEntity, forwardOffset, jukeDistance ); //juke == forward -> can't juke left or right } if ( !IsDefined( behaviorTreeEntity.juke ) || behaviorTreeEntity.juke == "forward" ) // could not long juke { //long juke didn't work out, so try short juke jukeDistance = 69; behaviorTreeEntity.jukeDistance = "short"; switch( behaviorTreeEntity ZombieBehavior::bb_getlocomotionspeedtype() ) { case "locomotion_speed_walk": case "locomotion_speed_run": forwardOffset = 127; break; case "locomotion_speed_sprint": forwardOffset = 148; break; } behaviorTreeEntity.juke = AiUtility::calculateJukeDirection( behaviorTreeEntity, forwardOffset, jukeDistance ); if( behaviorTreeEntity.juke == "forward" ) { //both juke checks failed, so don't juke at all behaviorTreeEntity.juke = undefined; behaviorTreeEntity.jukeDistance = undefined; return false; } } } } } function zombieDeathAction( behaviorTreeEntity ) { //insert anything that needs to be done right before zombie death } function wasKilledByInterdimensionalGunCondition( behaviorTreeEntity ) { if( isdefined( behaviorTreeEntity.interdimensional_gun_kill ) && !isdefined( behaviorTreeEntity.killby_interdimensional_gun_hole ) && IsAlive( behaviorTreeEntity ) ) { return true; } return false; } function wasCrushedByInterdimensionalGunBlackholeCondition( behaviorTreeEntity ) { if(isdefined(behaviorTreeEntity.killby_interdimensional_gun_hole)) { return true; } return false; } function zombieIDGunDeathMocompStart( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { entity OrientMode( "face angle", entity.angles[1] ); entity AnimMode("noclip"); entity.pushable = false; entity.blockingPain = true; entity PathMode( "dont move" ); entity.hole_pull_speed = 0; } function zombieMeleeJumpMocompStart( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { entity OrientMode( "face enemy" ); entity AnimMode( "noclip", false ); entity.pushable = false; entity.blockingPain = true; entity.clampToNavMesh = false; entity PushActors( false ); entity.jumpStartPosition = entity.origin; } function zombieMeleeJumpMocompUpdate( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { normalizedTime = ( ( entity GetAnimTime( mocompAnim ) * GetAnimLength( mocompAnim ) ) + mocompAnimBlendOutTime ) / mocompDuration; if (normalizedTime > 0.5) { entity OrientMode( "face angle", entity.angles[1] ); } speed = 5; if ( IsDefined( entity.zombie_move_speed ) ) { switch ( entity.zombie_move_speed ) { case "walk": speed = 5; break; case "run": speed = 6; break; case "sprint": speed = 7; break; } } newPosition = entity.origin + AnglesToForward( entity.angles ) * speed; // Test that the new position only moves the zombie across valid navmesh. newTestPosition = ( newPosition[0], newPosition[1], entity.jumpStartPosition[2] ); newValidPosition = GetClosestPointOnNavMesh( newTestPosition, 12, 20 ); if ( IsDefined( newValidPosition ) ) { // New position appears to be valid. newValidPosition = ( newValidPosition[0], newValidPosition[1], entity.origin[2] ); } else { // New position is not above navmesh, prevent all lateral movement. newValidPosition = entity.origin; } // Prevent zombie from penetrating the ground. groundPoint = GetClosestPointOnNavMesh( newValidPosition, 12, 20 ); if ( IsDefined( groundPoint ) && groundPoint[2] > newValidPosition[2] ) { newValidPosition = ( newValidPosition[0], newValidPosition[1], groundPoint[2] ); } entity ForceTeleport( newValidPosition ); } function zombieMeleeJumpMocompTerminate( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { entity.pushable = true; entity.blockingPain = false; entity.clampToNavMesh = true; entity PushActors( true ); groundPoint = GetClosestPointOnNavMesh( entity.origin, 12 ); if ( IsDefined( groundPoint ) ) { entity ForceTeleport( groundPoint ); } } // Offset also defined in _zm_weap_idgun.gsc function zombieIDGunDeathUpdate( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { if(!isdefined(entity.killby_interdimensional_gun_hole)) { entity_eye = entity GetEye(); // if world is paused unpause entity to be pulled into vortex if( entity IsPaused() ) { entity SetIgnorePauseWorld( true ); entity SetEntityPaused( false ); } if ( entity.b_vortex_repositioned !== true ) { entity.b_vortex_repositioned = true; v_nearest_navmesh_point = GetClosestPointOnNavMesh( entity.damageOrigin, 36, 15 ); if ( isdefined(v_nearest_navmesh_point) ) { f_distance = Distance( entity.damageOrigin, v_nearest_navmesh_point); // Added 5 units to offset to capture a larger set of points if ( f_distance < 36 + 5 ) { entity.damageOrigin = entity.damageOrigin + ( 0, 0, 36); } } } entity_center = entity.origin + ( ( entity_eye - entity.origin ) / 2 ); flyingDir = entity.damageOrigin - entity_center; lengthFromHole = Length(flyingDir); if(lengthFromHole < entity.hole_pull_speed) { entity.killby_interdimensional_gun_hole = true; entity.allowdeath = true; entity.takedamage = true; entity.aiOverrideDamage = undefined; entity.magic_bullet_shield = false; level notify("interdimensional_kill",entity); if( IsDefined( entity.interdimensional_gun_weapon ) && IsDefined( entity.interdimensional_gun_attacker ) ) { entity kill(entity.origin, entity.interdimensional_gun_attacker, entity.interdimensional_gun_attacker, entity.interdimensional_gun_weapon); } else { entity kill( entity.origin ); } } else { if(entity.hole_pull_speed < 12) { entity.hole_pull_speed += 0.5; if(entity.hole_pull_speed > 12) entity.hole_pull_speed = 12; } flyingDir = VectorNormalize(flyingDir); entity ForceTeleport(entity.origin + flyingDir * entity.hole_pull_speed); } } } function zombieIDGunHoleDeathMocompStart( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { entity OrientMode( "face angle", entity.angles[1] ); entity AnimMode("noclip"); entity.pushable = false; } function zombieIDGunHoleDeathMocompTerminate( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { if( !( isdefined( entity.interdimensional_gun_kill_vortex_explosion ) && entity.interdimensional_gun_kill_vortex_explosion ) ) { entity hide(); } } function private zombieTurnMocompStart( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { entity OrientMode( "face angle", entity.angles[1] ); entity AnimMode( "angle deltas", false ); } function private zombieTurnMocompUpdate( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { normalizedTime = ( entity GetAnimTime( mocompAnim ) + mocompAnimBlendOutTime ) / mocompDuration; if ( normalizedTime > 0.25 ) { entity OrientMode( "face motion" ); entity AnimMode( "normal", false ); } } function private zombieTurnMocompTerminate( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { entity OrientMode( "face motion" ); entity AnimMode( "normal", false ); } function zombieHasLegs( behaviorTreeEntity ) { if( behaviorTreeEntity.missingLegs === true ) { return false; } return true; } function zombieShouldProceduralTraverse( entity ) { return IsDefined( entity.traverseStartNode ) && IsDefined( entity.traverseEndNode ) && entity.traverseStartNode.spawnflags & 1024 && entity.traverseEndNode.spawnflags & 1024; } function zombieShouldMeleeSuicide( behaviorTreeEntity ) { if( !behaviorTreeEntity ai::get_behavior_attribute( "suicidal_behavior" ) ) { return false; } if( ( isdefined( behaviorTreeEntity.magic_bullet_shield ) && behaviorTreeEntity.magic_bullet_shield ) ) { return false; } if( !IsDefined( behaviortreeentity.enemy ) ) { return false; } if( IsDefined( behaviorTreeEntity.marked_for_death ) ) { return false; } if( DistanceSquared( behaviorTreeEntity.origin, behaviorTreeEntity.enemy.origin ) > ( 200 * 200 ) ) { return false; } return true; } function zombieMeleeSuicideStart( behaviorTreeEntity ) { behaviorTreeEntity.blockingPain = true; if( IsDefined( level.zombieMeleeSuicideCallback ) ) { behaviorTreeEntity thread [[level.zombieMeleeSuicideCallback]](behaviorTreeEntity); } } function zombieMeleeSuicideUpdate( behaviorTreeEntity ) { } function zombieMeleeSuicideTerminate( behaviorTreeEntity ) { if( IsAlive( behaviorTreeEntity ) && zombieShouldMeleeSuicide( behaviorTreeEntity ) ) { behaviorTreeEntity.takedamage = true; behaviorTreeEntity.allowDeath = true; // SUMEET : I dont like how this is being done but have to do this, as killing an entity in // Terminate functon can lead to an interrupt that might get dropped by the behavior tree update if( IsDefined( level.zombieMeleeSuicideDoneCallback ) ) { behaviorTreeEntity thread [[level.zombieMeleeSuicideDoneCallback]](behaviorTreeEntity); } } } // ------- ZOMBIE LOCOMOTION -----------// function zombieMoveAction( behaviorTreeEntity, asmStateName ) { behaviorTreeEntity.moveTime = GetTime(); behaviorTreeEntity.moveOrigin = behaviorTreeEntity.origin; AnimationStateNetworkUtility::RequestState( behaviorTreeEntity, asmStateName ); //Stumble at the end of the current move animation if( IsDefined( behaviorTreeEntity.stumble ) && !IsDefined( behaviorTreeEntity.move_anim_end_time ) ) { stumbleActionResult = behaviorTreeEntity ASTSearch( IString( asmStateName ) ); stumbleActionAnimation = AnimationStateNetworkUtility::SearchAnimationMap( behaviorTreeEntity, stumbleActionResult[ "animation" ] ); behaviorTreeEntity.move_anim_end_time = behaviorTreeEntity.moveTime + GetAnimLength( stumbleActionAnimation ); } if( IsDefined( behaviorTreeEntity.zombieMoveActionCallback ) ) { behaviorTreeEntity [[behaviorTreeEntity.zombieMoveActionCallback]]( behaviorTreeEntity ); } return 5; } // Looping Action will always return BHTN_RUNNING and request the state again when the ASM_STATE_COMPLETE function zombieMoveActionUpdate( behaviorTreeEntity, asmStateName ) { if ( IsDefined( behaviorTreeEntity.move_anim_end_time ) && ( GetTime() >= behaviorTreeEntity.move_anim_end_time ) ) { behaviorTreeEntity.move_anim_end_time = undefined; return 4; } if ( !( isdefined( behaviorTreeEntity.missingLegs ) && behaviorTreeEntity.missingLegs ) && ( GetTime() - behaviorTreeEntity.moveTime > 1000 ) ) { distSq = Distance2DSquared( behaviorTreeEntity.origin, behaviorTreeEntity.moveOrigin ); if ( distSq < 12 * 12 ) { behaviorTreeEntity SetAvoidanceMask( "avoid all" ); behaviorTreeEntity.cant_move = true; if ( IsDefined( behaviorTreeEntity.cant_move_cb ) ) { behaviorTreeEntity [[ behaviorTreeEntity.cant_move_cb ]](); } } else { behaviorTreeEntity SetAvoidanceMask( "avoid none" ); behaviorTreeEntity.cant_move = false; } behaviorTreeEntity.moveTime = GetTime(); behaviorTreeEntity.moveOrigin = behaviorTreeEntity.origin; } if( behaviorTreeEntity ASMGetStatus() == "asm_status_complete" ) { if( behaviorTreeEntity IsCurrentBTActionLooping() ) zombieMoveAction( behaviorTreeEntity, asmStateName ); else return 4; } return 5; } function zombieMoveActionTerminate( behaviorTreeEntity, asmStateName ) { if ( !( isdefined( behaviorTreeEntity.missingLegs ) && behaviorTreeEntity.missingLegs ) ) { behaviorTreeEntity SetAvoidanceMask( "avoid none" ); } return 4; } // ------- ZOMBIE DEATH GIB OVERRIDE -----------// function ArchetypeZombieDeathOverrideInit() // Self = AI { AiUtility::AddAIOverrideKilledCallback( self, &ZombieGibKilledAnhilateOverride ); } function private ZombieGibKilledAnhilateOverride( inflictor, attacker, damage, meansOfDeath, weapon, dir, hitLoc, offsetTime ) // self = AI { // Level must opt-in to anhilation if( !( isdefined( level.zombieAnhilationEnabled ) && level.zombieAnhilationEnabled ) ) return damage; if( ( isdefined( self.forceAnhilateOnDeath ) && self.forceAnhilateOnDeath ) ) { self zombie_utility::gib_random_parts(); GibServerUtils::Annihilate( self ); return damage; } // Forced anhilation for players if( IsDefined( attacker ) && IsPlayer( attacker ) && ( ( isdefined( attacker.forceAnhilateOnDeath ) && attacker.forceAnhilateOnDeath ) || ( isdefined( level.forceAnhilateOnDeath ) && level.forceAnhilateOnDeath ) ) ) { self zombie_utility::gib_random_parts(); GibServerUtils::Annihilate( self ); return damage; } // Generic anhilation attackerDistance = 0; if ( IsDefined( attacker ) ) { attackerDistance = DistanceSquared( attacker.origin, self.origin ); } isExplosive = IsInArray( array( "MOD_CRUSH", "MOD_GRENADE", "MOD_GRENADE_SPLASH", "MOD_PROJECTILE", "MOD_PROJECTILE_SPLASH", "MOD_EXPLOSIVE" ), meansOfDeath ); if ( IsDefined( weapon.weapclass ) && weapon.weapclass == "turret" ) { // Annihilate AI's from turrent explosives that are inflicted at a close distance. if ( IsDefined( inflictor ) ) { isDirectExplosive = IsInArray( array( "MOD_GRENADE", "MOD_GRENADE_SPLASH", "MOD_PROJECTILE", "MOD_PROJECTILE_SPLASH", "MOD_EXPLOSIVE" ), meansOfDeath ); isCloseExplosive = DistanceSquared( inflictor.origin, self.origin ) <= ( (60) * (60) ); if ( isDirectExplosive && isCloseExplosive ) { self zombie_utility::gib_random_parts(); GibServerUtils::Annihilate( self ); } } } return damage; } function private zombieZombieIdleMocompStart( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { if( IsDefined( entity.enemyoverride ) && IsDefined( entity.enemyoverride[1] ) && entity != entity.enemyoverride[1] ) { entity OrientMode( "face direction", entity.enemyoverride[1].origin - entity.origin ); entity AnimMode( "zonly_physics", false ); } else { entity OrientMode( "face current" ); entity AnimMode( "zonly_physics", false ); } } function private zombieAttackObjectMocompStart( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { if( IsDefined( entity.attackable_slot ) ) { entity OrientMode( "face angle", entity.attackable_slot.angles[1] ); entity AnimMode( "zonly_physics", false ); } else { entity OrientMode( "face current" ); entity AnimMode( "zonly_physics", false ); } } function private zombieAttackObjectMocompUpdate( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration ) { if( IsDefined( entity.attackable_slot ) ) { entity ForceTeleport( entity.attackable_slot.origin ); } }