635 lines
29 KiB
Plaintext
635 lines
29 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\laststand_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\system_shared;
|
|
|
|
#using scripts\shared\ai\systems\animation_state_machine_utility;
|
|
#using scripts\shared\ai\systems\animation_state_machine_notetracks;
|
|
#using scripts\shared\ai\archetype_utility;
|
|
#using scripts\shared\ai\systems\behavior_tree_utility;
|
|
#using scripts\shared\ai\systems\blackboard;
|
|
#using scripts\shared\ai\systems\debug;
|
|
#using scripts\shared\ai\systems\gib;
|
|
#using scripts\codescripts\struct;
|
|
#using scripts\shared\ai\archetype_mocomps_utility;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#namespace SkeletonBehavior;
|
|
|
|
function autoexec __init__sytem__() { system::register("skeleton",&__init__,undefined,undefined); }
|
|
|
|
function __init__()
|
|
{
|
|
// INIT BEHAVIORS
|
|
InitSkeletonBehaviorsAndASM();
|
|
|
|
// INIT BLACKBOARD
|
|
spawner::add_archetype_spawn_function( "skeleton", &ArchetypeSkeletonBlackboardInit );
|
|
|
|
// INIT SKELETON ON SPAWN
|
|
spawner::add_archetype_spawn_function( "skeleton", &skeletonSpawnSetup );
|
|
|
|
if( ai::shouldRegisterClientFieldForArchetype( "skeleton" ) )
|
|
{
|
|
clientfield::register(
|
|
"actor",
|
|
"skeleton",
|
|
1,
|
|
1,
|
|
"int");
|
|
}
|
|
}
|
|
|
|
function skeletonSpawnSetup()
|
|
{
|
|
self.zombie_move_speed = "walk";
|
|
if(randomint( 2 ) == 0)
|
|
self.zombie_arms_position = "up";
|
|
else
|
|
self.zombie_arms_position = "down";
|
|
self.missingLegs = false;
|
|
self setAvoidanceMask( "avoid none" );
|
|
self PushActors( true );
|
|
|
|
clientfield::set( "skeleton", true );
|
|
}
|
|
|
|
|
|
function private InitSkeletonBehaviorsAndASM()
|
|
{
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("skeletonTargetService",&skeletonTargetService);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("skeletonShouldMelee",&skeletonShouldMeleeCondition);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("skeletonGibLegsCondition",&skeletonGibLegsCondition);;
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isSkeletonWalking",&isSkeletonWalking);;
|
|
|
|
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("skeletonDeathAction",&skeletonDeathAction);;
|
|
|
|
AnimationStateNetwork::RegisterNotetrackHandlerFunction("contact",&skeletonNotetrackMeleeFire);;
|
|
}
|
|
|
|
function private ArchetypeSkeletonBlackboardInit()
|
|
{
|
|
// CREATE BLACKBOARD
|
|
Blackboard::CreateBlackBoardForEntity( self );
|
|
|
|
// USE UTILITY BLACKBOARD
|
|
self AiUtility::RegisterUtilityBlackboardAttributes();
|
|
|
|
// CREATE SKELETON 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,"_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");#/ };
|
|
|
|
// REGISTER ANIMSCRIPTED CALLBACK
|
|
self.___ArchetypeOnAnimscriptedCallback = &ArchetypeSkeletonOnAnimscriptedCallback;
|
|
|
|
// ENABLE DEBUGGING IN ODYSSEY
|
|
/#self FinalizeTrackedBlackboardAttributes();#/;
|
|
}
|
|
|
|
function private ArchetypeSkeletonOnAnimscriptedCallback( entity )
|
|
{
|
|
// UNREGISTER THE BLACKBOARD
|
|
entity.__blackboard = undefined;
|
|
|
|
// REREGISTER BLACKBOARD
|
|
entity ArchetypeSkeletonBlackboardInit();
|
|
}
|
|
|
|
// ------- BLACKBOARD -----------//
|
|
|
|
function BB_GetArmsPosition()
|
|
{
|
|
if( IsDefined( self.skeleton_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";
|
|
}
|
|
}
|
|
return "locomotion_speed_walk";
|
|
}
|
|
|
|
function BB_GetHasLegsStatus()
|
|
{
|
|
if( self.missingLegs )
|
|
return "has_legs_no";
|
|
return "has_legs_yes";
|
|
}
|
|
|
|
// ------- BLACKBOARD -----------//
|
|
|
|
|
|
|
|
function isSkeletonWalking( behaviorTreeEntity )
|
|
{
|
|
if( !isdefined(behaviorTreeEntity.zombie_move_speed))
|
|
return true;
|
|
return behaviorTreeEntity.zombie_move_speed == "walk" && !( isdefined( behaviorTreeEntity.missingLegs ) && behaviorTreeEntity.missingLegs ) && behaviorTreeEntity.zombie_arms_position == "up";
|
|
}
|
|
|
|
function skeletonGibLegsCondition( behaviorTreeEntity)
|
|
{
|
|
return GibServerUtils::IsGibbed( behaviorTreeEntity, 256) || GibServerUtils::IsGibbed( behaviorTreeEntity, 128);
|
|
}
|
|
|
|
function skeletonNotetrackMeleeFire( animationEntity )
|
|
{
|
|
hitEnt = animationentity Melee();
|
|
if(isDefined(hitEnt) && isDefined(animationentity.aux_melee_damage) && self.team != hitEnt.team )
|
|
{
|
|
animationentity [[animationentity.aux_melee_damage]](hitEnt);
|
|
}
|
|
}
|
|
|
|
function is_within_fov( start_origin, start_angles, end_origin, fov )
|
|
{
|
|
normal = VectorNormalize( end_origin - start_origin );
|
|
forward = AnglesToForward( start_angles );
|
|
dot = VectorDot( forward, normal );
|
|
|
|
return dot >= fov;
|
|
}
|
|
|
|
|
|
|
|
function skeletonCanSeePlayer( player )
|
|
{
|
|
self endon( "death" );
|
|
|
|
if(!isDefined(self.players_visCache))
|
|
{
|
|
self.players_visCache = [];
|
|
}
|
|
entNum = player GetEntityNumber();
|
|
if(!isDefined(self.players_visCache[entNum]))
|
|
{
|
|
self.players_visCache[entNum] = 0;
|
|
}
|
|
if (self.players_visCache[entNum] > GetTime() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//zombie_eye = self GetTagOrigin( "tag_eye" );
|
|
zombie_eye = self.origin + ( 0, 0, 40);
|
|
player_pos = player.origin + ( 0, 0, 40);
|
|
|
|
//as_debug::drawDebugCross( zombie_eye, 5.0, ( 0.0, 0.0, 1.0 ), 1 );
|
|
//as_debug::drawDebugCross( player_pos, 5.0, ( 0.0, 1.0, 1.0 ), 1 );
|
|
|
|
distanceSq = DistanceSquared( zombie_eye, player_pos );
|
|
|
|
//Check to see if the skeleton is super close OR super far
|
|
if( distanceSq < 64 * 64 )
|
|
{
|
|
self.players_visCache[entNum] = GetTime() + 3000;
|
|
return true;
|
|
}
|
|
else if( distanceSq > 1024*1024 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( is_within_fov( zombie_eye, self.angles, player_pos, cos(60.0) ) )
|
|
{
|
|
trace = GroundTrace( zombie_eye, player_pos, false, undefined );
|
|
|
|
if( trace["fraction"] < 1.0 )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
//as_debug::debugLine( zombie_eye, player_pos, ( 0.0, 1.0, 1.0 ), 1 );
|
|
self.players_visCache[entNum] = GetTime() + 3000;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function is_player_valid( player, checkIgnoreMeFlag, ignore_laststand_players )
|
|
{
|
|
if( !IsDefined( player ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !IsAlive( player ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !IsPlayer( player ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( IsDefined(player.is_zombie) && player.is_zombie == true )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( player.sessionstate == "spectator" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( player.sessionstate == "intermission" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( ( isdefined( self.intermission ) && self.intermission ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!( isdefined( ignore_laststand_players ) && ignore_laststand_players ))
|
|
{
|
|
if( player laststand::player_is_in_laststand() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//T6.5todo if ( player isnotarget() )
|
|
//T6.5todo {
|
|
//T6.5todo return false;
|
|
//T6.5todo }
|
|
|
|
//We only want to check this from the zombie attack script
|
|
if( ( isdefined( checkIgnoreMeFlag ) && checkIgnoreMeFlag ) && player.ignoreme )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//for additional level specific checks
|
|
if( IsDefined( level.is_player_valid_override ) )
|
|
{
|
|
return [[ level.is_player_valid_override ]]( player );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function get_closest_valid_player( origin, ignore_player )
|
|
{
|
|
valid_player_found = false;
|
|
|
|
players = GetPlayers();
|
|
|
|
if( IsDefined( ignore_player ) )
|
|
{
|
|
//PI_CHANGE_BEGIN - 7/2/2009 JV Reenabling change 274916 (from DLC3)
|
|
for(i = 0; i < ignore_player.size; i++ )
|
|
{
|
|
ArrayRemoveValue( players, ignore_player[i] );
|
|
}
|
|
//PI_CHANGE_END
|
|
}
|
|
|
|
// pre-cull any players that are in last stand
|
|
done = false;
|
|
while ( players.size && !done )
|
|
{
|
|
done = true;
|
|
for(i = 0; i < players.size; i++ )
|
|
{
|
|
player = players[i];
|
|
if( !is_player_valid( player, true ) )
|
|
{
|
|
ArrayRemoveValue( players, player );
|
|
done = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( players.size == 0 )
|
|
{
|
|
return undefined;
|
|
}
|
|
|
|
while( !valid_player_found )
|
|
{
|
|
// find the closest player
|
|
if( IsDefined( self.closest_player_override ) )
|
|
{
|
|
player = [[ self.closest_player_override ]]( origin, players );
|
|
}
|
|
else if( isdefined( level.closest_player_override ) )
|
|
{
|
|
player = [[ level.closest_player_override ]]( origin, players );
|
|
}
|
|
else
|
|
{
|
|
player = ArrayGetClosest( origin, players );
|
|
}
|
|
|
|
if( !isdefined( player ) || players.size == 0 )
|
|
{
|
|
return undefined;
|
|
}
|
|
|
|
// make sure they're not a zombie or in last stand
|
|
if( !is_player_valid( player, true ) )
|
|
{
|
|
// unlikely to get here unless there is a wait in one of the closest player overrides
|
|
ArrayRemoveValue( players, player );
|
|
if( players.size == 0 )
|
|
{
|
|
return undefined;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
return player;
|
|
}
|
|
}
|
|
|
|
|
|
function skeletonSetGoal(goal)
|
|
{
|
|
if(isDefined(self.setGoalOverrideCB))
|
|
{
|
|
return [[self.setGoalOverrideCB]](goal);
|
|
}
|
|
else
|
|
{
|
|
self SetGoal(goal);
|
|
}
|
|
}
|
|
function skeletonTargetService( behaviorTreeEntity)
|
|
{
|
|
|
|
self endon( "death" );
|
|
|
|
if ( ( isdefined( behaviorTreeEntity.ignoreall ) && behaviorTreeEntity.ignoreall ) )
|
|
{
|
|
return false;
|
|
}
|
|
if(isDefined(behaviorTreeEntity.enemy) && behaviorTreeEntity.enemy.team == behaviorTreeEntity.team )
|
|
behaviorTreeEntity ClearEntityTarget();
|
|
|
|
if(behaviorTreeEntity.team == "allies")
|
|
{
|
|
if(isDefined(behaviorTreeEntity.favoriteenemy))
|
|
{
|
|
behaviorTreeEntity skeletonSetGoal( behaviorTreeEntity.favoriteenemy.origin );
|
|
return true;
|
|
}
|
|
if(isDefined(behaviorTreeEntity.enemy))
|
|
{
|
|
behaviorTreeEntity skeletonSetGoal( behaviorTreeEntity.enemy.origin );
|
|
return true;
|
|
}
|
|
target = getClosestToMe(GetAITeamArray( "axis" ));
|
|
if(isDefined(target))
|
|
{
|
|
behaviorTreeEntity skeletonSetGoal( target.origin );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
behaviorTreeEntity skeletonSetGoal( behaviorTreeEntity.origin );
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
player = get_closest_valid_player( behaviorTreeEntity.origin, behaviorTreeEntity.ignore_player );
|
|
|
|
if( !isDefined( player ) )
|
|
{
|
|
if( IsDefined( behaviorTreeEntity.ignore_player ) )
|
|
{
|
|
if(isDefined(level._should_skip_ignore_player_logic) && [[level._should_skip_ignore_player_logic]]() )
|
|
{
|
|
return;
|
|
}
|
|
behaviorTreeEntity.ignore_player = [];
|
|
}
|
|
|
|
behaviorTreeEntity skeletonSetGoal( behaviorTreeEntity.origin );
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if ( IsDefined( player.last_valid_position ) )
|
|
{
|
|
canSEE = self skeletonCanSeePlayer( player );
|
|
|
|
//Fill in influence map
|
|
if( canSEE )
|
|
{
|
|
//self SetInfluenceAt( 5, player.last_valid_position, 1.0 );
|
|
behaviorTreeEntity skeletonSetGoal( player.last_valid_position );
|
|
//as_debug::drawDebugCross( player.last_valid_position, 5.0, ( 0.0, 1.0, 0.0 ), 5 );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
influencePos = undefined;
|
|
//try to find player - get hottest point on influence map and go there
|
|
//influencePos = self GetBestInfluencepos( 5, 0.01, 1.0 );
|
|
|
|
if( isdefined(influencePos) )
|
|
{
|
|
//as_debug::drawDebugCross( influencePos, 5.0, ( 1.0, 0.0, 0.0 ), 5 );
|
|
|
|
//Are we close to our goal?
|
|
if( DistanceSquared( influencePos, behaviorTreeEntity.origin ) > 32*32 )
|
|
{
|
|
behaviorTreeEntity skeletonSetGoal( influencePos );
|
|
return true;
|
|
}
|
|
//self SetGoal( self.origin );
|
|
behaviorTreeEntity ClearPath();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
//self SetGoal( self.origin );
|
|
behaviorTreeEntity ClearPath();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*attackPlayer = false;
|
|
if( DistanceSquared( player.last_valid_position, self.origin ) < (512*512) )
|
|
{
|
|
if( canSEE )
|
|
{
|
|
behaviorTreeEntity SetGoal( player.last_valid_position );
|
|
attackPlayer = true;
|
|
}
|
|
}
|
|
|
|
if( !attackPlayer )
|
|
{
|
|
//Get hottest point on influence map and go there
|
|
influencePos = self GetBestInfluencepos( 5, 0.01, 1.0 );
|
|
if( isdefined(influencePos) )
|
|
{
|
|
as_debug::drawDebugCross( influencePos, 5.0, ( 1.0, 0.0, 0.0 ), 5 );
|
|
self SetGoal( influencePos );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
self SetGoal( self.origin );
|
|
return false;
|
|
}
|
|
}*/
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
behaviorTreeEntity skeletonSetGoal( behaviorTreeEntity.origin );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function isValidEnemy( enemy )
|
|
{
|
|
if ( !isdefined( enemy ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function GetYaw(org)
|
|
{
|
|
angles = VectorToAngles(org-self.origin);
|
|
return angles[1];
|
|
}
|
|
|
|
// warning! returns (my yaw - yaw to enemy) instead of (yaw to enemy - my yaw)
|
|
function GetYawToEnemy()
|
|
{
|
|
pos = undefined;
|
|
if ( isValidEnemy( self.enemy ) )
|
|
{
|
|
pos = self.enemy.origin;
|
|
}
|
|
else
|
|
{
|
|
forward = AnglesToForward(self.angles);
|
|
forward = VectorScale (forward, 150);
|
|
pos = self.origin + forward;
|
|
}
|
|
|
|
yaw = self.angles[1] - GetYaw(pos);
|
|
yaw = AngleClamp180( yaw );
|
|
return yaw;
|
|
}
|
|
|
|
function skeletonShouldMeleeCondition( behaviorTreeEntity )
|
|
{
|
|
if( !IsDefined( behaviortreeentity.enemy ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( IsDefined( behaviorTreeEntity.marked_for_death ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( ( isdefined( behaviorTreeEntity.stunned ) && behaviorTreeEntity.stunned ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
yaw = abs( getYawToEnemy() );
|
|
if( ( yaw > 45 ) )
|
|
{
|
|
return false;
|
|
}
|
|
if( DistanceSquared( behaviorTreeEntity.origin, behaviorTreeEntity.enemy.origin ) < 64 * 64 )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function skeletonDeathAction( behaviorTreeEntity )
|
|
{
|
|
if ( IsDefined( behaviorTreeEntity.deathFunction ) )
|
|
{
|
|
behaviorTreeEntity [[ behaviorTreeEntity.deathFunction ]]();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
function getClosestTo(origin,entArray)
|
|
{
|
|
if (!isDefined(entArray) )
|
|
return;
|
|
|
|
if (entArray.size == 0 )
|
|
return;
|
|
|
|
return ArrayGetClosest( origin, entArray );
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
function getClosestToMe(entArray)
|
|
{
|
|
return getClosestTo(self.origin,entArray);
|
|
} |