1604 lines
61 KiB
Plaintext
1604 lines
61 KiB
Plaintext
#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 );
|
|
}
|
|
}
|
|
|
|
|