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


