This commit is contained in:
Future committed 2023-04-13 17:30:38 +02:00
commit 013c6f3b4d
921 files changed
+337975

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
@@ -0,0 +1,236 @@
#using scripts\shared\ai\systems\animation_selector_table;
#using scripts\shared\array_shared;
Line truncated
function autoexec RegisterASTScriptFunctions()
{
AnimationSelectorTable::RegisterAnimationSelectorTableEvaluator("testFunction",&testFunction);;
// ------- EVALUATOR BLOCKED BY GEO ANIMATIONS -----------//
AnimationSelectorTable::RegisterAnimationSelectorTableEvaluator("evaluateBlockedAnimations",&evaluateBlockedAnimations);;
// ------- EVALUATOR HUMAN LOCOMOTION TURNS -----------//
AnimationSelectorTable::RegisterAnimationSelectorTableEvaluator("evaluateHumanTurnAnimations",&evaluateHumanTurnAnimations);;
// ------- EVALUATOR HUMAN EXPOSED ARRIVALS -----------//
AnimationSelectorTable::RegisterAnimationSelectorTableEvaluator("evaluateHumanExposedArrivalAnimations",&evaluateHumanExposedArrivalAnimations);;
}
function testFunction( entity, animations )
{
if ( IsArray( animations ) && animations.size > 0 )
{
return animations[0];
}
}
function private Evaluator_CheckAnimationAgainstGeo( entity, animation )
{
PixBeginEvent( "Evaluator_CheckAnimationAgainstGeo" );
assert( IsActor( entity ) );
// Since this check is mostly used for turn animations, a better approximation
// of movement is to use the midpoint position of the animation and the end
// point of the animation.
localDeltaHalfVector = GetMoveDelta( animation, 0, 0.5, entity );
midPoint = entity LocalToWorldCoords( localDeltaHalfVector );
// ignore any Z translation.
midPoint = ( midPoint[0], midPoint[1], entity.origin[2] );
/#
RecordLine( entity.origin, midPoint, ( 1, .5, 0 ), "Animscript", entity );
#/
if( entity MayMoveToPoint( midPoint, true, true ) )
{
localDeltaVector = GetMoveDelta( animation, 0, 1, entity );
endPoint = entity LocalToWorldCoords( localDeltaVector );
endPoint = ( endPoint[0], endPoint[1], entity.origin[2] );
/#
RecordLine( midPoint, endPoint, ( 1, .5, 0 ), "Animscript", entity );
#/
if ( entity MayMoveFromPointToPoint( midPoint, endPoint, true, true ) )
{
PixEndEvent();
return true;
}
}
PixEndEvent();
return false;
}
function private Evaluator_CheckAnimationEndPointAgainstGeo( entity, animation )
{
PixBeginEvent( "Evaluator_CheckAnimationEndPointAgainstGeo" );
assert( IsActor( entity ) );
localDeltaVector = GetMoveDelta( animation, 0, 1, entity );
endPoint = entity LocalToWorldCoords( localDeltaVector );
endPoint = ( endPoint[0], endPoint[1], entity.origin[2] );
if( entity MayMoveToPoint( endPoint, false, false ) )
{
PixEndEvent();
return true;
}
PixEndEvent();
return false;
}
function private Evaluator_CheckAnimationForOverShootingGoal( entity, animation )
{
PixBeginEvent( "Evaluator_CheckAnimationForOverShootingGoal" );
assert( IsActor( entity ) );
localDeltaVector = GetMoveDelta( animation, 0, 1, entity );
endPoint = entity LocalToWorldCoords( localDeltaVector );
animDistSq = LengthSquared( localDeltaVector );
if( entity HasPath() )
{
startPos = entity.origin;
goalPos = entity.pathGoalPos;
assert( IsDefined( goalPos ) );
distToGoalSq = DistanceSquared( startPos, goalPos );
// goal is straight in front of the AI, just make sure that the endpoint is not beyond the goal position
if( animDistSq < distToGoalSq )
{
PixEndEvent();
return true;
}
}
PixEndEvent();
return false;
}
function private Evaluator_CheckAnimationAgainstNavmesh( entity, animation )
{
assert( IsActor( entity ) );
localDeltaVector = GetMoveDelta( animation, 0, 1, entity );
endPoint = entity LocalToWorldCoords( localDeltaVector );
// make sure that the point is on the navmesh and away from boundary
if( IsPointOnNavMesh( endPoint, entity ) )
return true;
return false;
}
function private Evaluator_CheckAnimationArrivalPosition( entity, animation )
{
localDeltaVector = GetMoveDelta( animation, 0, 1, entity );
endPoint = entity LocalToWorldCoords( localDeltaVector );
animDistSq = LengthSquared( localDeltaVector );
startPos = entity.origin;
goalPos = entity.pathGoalPos;
distToGoalSq = DistanceSquared( startPos, goalPos );
return distToGoalSq < animDistSq && entity IsPosAtGoal( endPoint );
}
function private Evaluator_FindFirstValidAnimation( entity, animations, tests )
{
assert( IsArray( animations ), "An array of animations must be passed in to validate against." );
assert( IsArray( tests ), "An array of test functions must be passed in to validate an animation." );
// Only check the first animation within the group of animations, since each animation is synonymous with each other.
foreach ( aliasAnimations in animations )
{
if ( aliasAnimations.size > 0 )
{
valid = true;
animation = aliasAnimations[0];
foreach ( test in tests )
{
if ( ![[test]]( entity, animation ) )
{
valid = false;
break;
}
}
if ( valid )
{
return animation;
}
}
}
}
// ------- EVALUATOR BLOCKED BY GEO ANIMATIONS -----------//
function private evaluateBlockedAnimations( entity, animations )
{
if( animations.size > 0 )
{
return Evaluator_FindFirstValidAnimation(
entity,
animations,
array(
&Evaluator_CheckAnimationAgainstGeo,
&Evaluator_CheckAnimationForOverShootingGoal ) );
}
return undefined;
}
function private evaluateHumanTurnAnimations( entity, animations )
{
/#
// SUMEET - Added this check just for testing.
if( ( isdefined( level.ai_dontTurn ) && level.ai_dontTurn ) )
return undefined;
#/
/#
Record3DText( "" + GetTime() + ": Turn Evaluator", entity.origin, ( 1, .5, 0 ), "Animscript", entity );
#/
if( animations.size > 0 )
{
return Evaluator_FindFirstValidAnimation(
entity,
animations,
array(
&Evaluator_CheckAnimationForOverShootingGoal,
&Evaluator_CheckAnimationAgainstGeo,
&Evaluator_CheckAnimationAgainstNavmesh ) );
}
return undefined;
}
function private evaluateHumanExposedArrivalAnimations( entity, animations )
{
if( !IsDefined( entity.pathGoalPos ) )
return undefined;
if( animations.size > 0 )
{
return Evaluator_FindFirstValidAnimation(
entity,
animations,
array( &Evaluator_CheckAnimationArrivalPosition ) );
}
return undefined;
}
+872
View File
@@ -0,0 +1,872 @@
/*
* AI VS AI MELEE SYSTEM
* The AI vs AI Melee system is initiated from the behavior tree.
* A set of conditions are used to check if an AI can initiate a scripted melee attack against its enemy.
* If these pass, the AI designates itself as the initiator of the setup. The initiator then picks the two animations, and sets AnimScripted on both itself and its enemy from the AIvsAIMeleeAction
* Threads are spawned on the entities to handle death during the sequence since it is outside the tree. The loser is killed and the survivor kicks back into the behavior tree.
TODO:
- Rethink the initiator system to see if melee can be triggered without it
- Add support for few more animations, including close range melee without runup
- Add more archetypes
*/
#using scripts\codescripts\struct;
// COMMON AI SYSTEMS INCLUDES
#using scripts\shared\ai_shared;
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\archetype_utility;
Line truncated
function autoexec main()
{
meleeBundles = struct::get_script_bundles( "aiassassination" );
level._aivsai_meleeBundles = [];
foreach( meleeBundle in meleeBundles )
{
attacker_archetype = meleeBundle.attackerArchetype;
defender_archetype = meleeBundle.defenderArchetype;
attacker_variant = meleeBundle.attackerVariant;
defender_variant = meleeBundle.defenderVariant;
if( !IsDefined( level._aivsai_meleeBundles[ attacker_archetype ] ) )
{
level._aivsai_meleeBundles[ attacker_archetype ] = [];
level._aivsai_meleeBundles[ attacker_archetype ][ defender_archetype ] = [];
level._aivsai_meleeBundles[ attacker_archetype ][ defender_archetype ][ attacker_variant ] = [];
}
else if( !IsDefined( level._aivsai_meleeBundles[ attacker_archetype ][ defender_archetype ] ) )
{
level._aivsai_meleeBundles[ attacker_archetype ][ defender_archetype ] = [];
level._aivsai_meleeBundles[ attacker_archetype ][ defender_archetype ][ attacker_variant ] = [];
}
else if( !IsDefined( level._aivsai_meleeBundles[ attacker_archetype ][ defender_archetype ][ attacker_variant ] ) )
{
level._aivsai_meleeBundles[ attacker_archetype ][ defender_archetype ][ attacker_variant ] = [];
}
level._aivsai_meleeBundles[ attacker_archetype ][ defender_archetype ][ attacker_variant ][ defender_variant ] = meleeBundle;
}
}
function RegisterAIvsAIMeleeBehaviorFunctions()
{
// ------- AI vs AI BEHAVIOR TREE FUNCTIONS -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("hasAIvsAIEnemy",&hasAIvsAIEnemy);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("decideInitiator",&decideInitiator);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isInitiator",&isInitiator);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("hasCloseAIvsAIEnemy",&hasCloseAIvsAIEnemy);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("chooseAIvsAIMeleeAnimations",&chooseAIvsAIMeleeAnimations);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isCloseEnoughForAIvsAIMelee",&isCloseEnoughForAIvsAIMelee);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("hasPotentalAIvsAIMeleeEnemy",&hasPotentalAIvsAIMeleeEnemy);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeAction("AIvsAIMeleeAction",&AIvsAIMeleeInitialize,undefined,undefined);;
}
// ------- AI vs AI BEHAVIOR TREE FUNCTIONS -----------//
function hasPotentalAIvsAIMeleeEnemy( behaviorTreeEntity )
{
if( !hasAIvsAIEnemy( behaviorTreeEntity ) )
return false;
if( !chooseAIvsAIMeleeAnimations( behaviorTreeEntity ) )
return false;
if( !hasCloseAIvsAIEnemy( behaviorTreeEntity ) )
return true;
return false;
}
function isCloseEnoughForAIvsAIMelee( behaviorTreeEntity )
{
if( !hasAIvsAIEnemy( behaviorTreeEntity ) )
return false;
if( !chooseAIvsAIMeleeAnimations( behaviorTreeEntity ) )
return false;
if( !hasCloseAIvsAIEnemy( behaviorTreeEntity ) )
return false;
return true;
}
function private shouldAquireMutexOnEnemyForAIvsAIMelee( behaviorTreeEntity )
{
if( IsPlayer ( behaviorTreeEntity.enemy ) )
return false;
if( !IsDefined( behaviorTreeEntity.enemy ) )
return false;
// if already meleeing, then see if the enemy we are meleeing is the same as
// the one we want to perform AI vs AI melee
if( IsDefined( behaviorTreeEntity.melee ) )
{
if( IsDefined( behaviorTreeEntity.melee.enemy ) && behaviorTreeEntity.melee.enemy == behaviorTreeEntity.enemy )
return true;
}
if( IsDefined( behaviorTreeEntity.enemy.melee ) )
{
if( IsDefined( behaviorTreeEntity.enemy.melee.enemy ) && behaviorTreeEntity.enemy.melee.enemy != behaviorTreeEntity )
return false;
}
return true;
}
function private hasAIvsAIEnemy( behaviorTreeEntity )
{
enemy = behaviorTreeEntity.enemy;
if( GetDvarInt( "disable_aivsai_melee", 0 ) )
{
/#
Record3DText( "AI vs AI Melee Failure: Disable dvar set", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
if( !IsDefined( enemy ) )
{
/#
Record3DText( "AI vs AI Melee Failure: No enemy", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
if( !( IsAlive( behaviorTreeEntity ) && IsAlive( enemy ) ) )
{
/#
Record3DText( "AI vs AI Melee Failure: A participant may not be alive", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
// Only support AI Actors.
if( !IsAI( enemy ) || !IsActor( enemy ) )
{
/#
Record3DText( "AI vs AI Melee Failure: Not an actor nor AI", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//for now only human vs human and riotshield and robot
//NOTE: dog cannot be on the losing side
if( IsDefined( enemy.archetype ) )
{
if(( SessionModeIsCampaignZombiesGame() ))
{
if( enemy.archetype != "human" && enemy.archetype != "human_riotshield" && enemy.archetype != "robot" && enemy.archetype!= "zombie" )
{
/#
Record3DText( "AI vs AI Melee Failure: Non human/riotshield/robot/zombie enemy", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
}
else
{
if( enemy.archetype != "human" && enemy.archetype != "human_riotshield" && enemy.archetype != "robot" )
{
/#
Record3DText( "AI vs AI Melee Failure: Non human/riotshield/robot enemy", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
}
}
if( enemy.team == behaviorTreeEntity.team )
{
/#
Record3DText( "AI vs AI Melee Failure: Enemy is on same team", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
if( enemy IsRagdoll() )
{
/#
Record3DText( "AI vs AI Melee Failure: Enemy is in ragdoll", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//enemy should be ignored
if( ( isdefined( enemy.ignoreMe ) && enemy.ignoreMe ) )
{
/#
Record3DText( "AI vs AI Melee Failure: Enemy should be ignored", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//already a part of an aivsai melee, _ai_melee_markedDead is used to designate that a decision has been made on the conflict
if( ( isdefined( enemy._ai_melee_markedDead ) && enemy._ai_melee_markedDead ) )
{
/#
Record3DText( "AI vs AI Melee Failure: Enemy engaged in another AI vs AI melee", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//AI should not initiate aivsai melee
if ( behaviorTreeEntity ai::has_behavior_attribute( "can_initiateaivsaimelee" ) &&
!behaviorTreeEntity ai::get_behavior_attribute( "can_initiateaivsaimelee" ) )
{
/#
Record3DText( "AI vs AI Melee Failure: can_initiateaivsaimelee disabled for attacker", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
// Check if the AI can melee.
if ( behaviorTreeEntity ai::has_behavior_attribute( "can_melee" ) &&
!behaviorTreeEntity ai::get_behavior_attribute( "can_melee" ) )
{
/#
Record3DText( "AI vs AI Melee Failure: can_melee disabled for attacker", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
// Check if the enemy can be meleed.
if ( enemy ai::has_behavior_attribute( "can_be_meleed" ) &&
!enemy ai::get_behavior_attribute( "can_be_meleed" ) )
{
/#
Record3DText( "AI vs AI Melee Failure: can_melee disabled for enemy", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//to check if enemy is close by
if( Distance2DSquared( behaviorTreeEntity.origin, enemy.origin ) > ( 150 * 150 ) )
{
/#
Record3DText( "AI vs AI Melee Failure: Enemy too far", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
behaviorTreeEntity._ai_melee_initiator = undefined;
return false;
}
//making sure our enemy is in front of us
forwardVec = VectorNormalize( AnglesToForward( behaviorTreeEntity.angles ) );
rightVec = VectorNormalize( AnglesToRight( behaviorTreeEntity.angles ) );
toEnemyVec = VectorNormalize( enemy.origin - behaviorTreeEntity.origin );
fDot = VectorDot( toEnemyVec, forwardVec );
if ( fDot < 0 )
{
/#
Record3DText( "AI vs AI Melee Failure: Enemy behind us", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//do not melee an enemy that is already playing scripted anim
if( enemy isInScriptedState() )
{
/#
Record3DText( "AI vs AI Melee Failure: Enemy in scripted state", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//only allow standing ai vs ai melee
currentStance = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_stance" );
enemyStance = Blackboard::GetBlackBoardAttribute( enemy, "_stance" );
if( currentStance != "stand" || enemyStance != "stand" )
{
/#
Record3DText( "AI vs AI Melee Failure: attacker or enemy not standing", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//return if we should not acquire melee mutex
if( !shouldAquireMutexOnEnemyForAIvsAIMelee( behaviorTreeEntity ) )
{
/#
Record3DText( "AI vs AI Melee Failure: should not acquire melee mutex", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//slope test
if( Abs( behaviorTreeEntity.origin[2] - behaviorTreeEntity.enemy.origin[2] ) > 16.0 )
{
/#
Record3DText( "AI vs AI Melee Failure: Slope test failed", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//return if there is not enough open space
raisedEnemyEntOrigin = ( behaviorTreeEntity.enemy.origin[0], behaviorTreeEntity.enemy.origin[1], behaviorTreeEntity.enemy.origin[2] + 8.0 );
if( !behaviorTreeEntity MayMoveToPoint( raisedEnemyEntOrigin, false, true, behaviorTreeEntity.enemy ) )
{
/#
Record3DText( "AI vs AI Melee Failure: Trace failed", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
//cannot kill an AI that is not supposed to die
if( IsDefined( enemy.allowDeath ) && !enemy.allowDeath )
{
if( IsDefined( behaviorTreeEntity.allowDeath ) && !behaviorTreeEntity.allowDeath )
{
/#
Record3DText( "AI vs AI Melee Failure: Enemy and attacker cannot die", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
self notify( "failed_melee_mbs", enemy );
return false;
}
behaviorTreeEntity._ai_melee_attacker_loser = true;
return true;
}
return true;
}
function private decideInitiator( behaviorTreeEntity )
{
if( !IsDefined( behaviorTreeEntity._ai_melee_initiator ) )
{
if( !IsDefined( behaviorTreeEntity.enemy._ai_melee_initiator ) )
{
behaviorTreeEntity._ai_melee_initiator = true;
return true;
}
}
return false;
}
function private isInitiator( behaviorTreeEntity )
{
if( !( isdefined( behaviorTreeEntity._ai_melee_initiator ) && behaviorTreeEntity._ai_melee_initiator ) )
{
return false;
}
return true;
}
function private hasCloseAIvsAIEnemy( behaviorTreeEntity )
{
if( !( IsDefined( behaviorTreeEntity._ai_melee_animname ) && IsDefined( behaviorTreeEntity.enemy._ai_melee_animname ) ) )
{
/#
Record3DText( "AI vs AI Melee Failure: anim pair not found", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
animationStartOrigin = GetStartOrigin( behaviorTreeEntity.enemy GetTagOrigin( "tag_sync" ), behaviorTreeEntity.enemy GetTagAngles( "tag_sync" ), behaviorTreeEntity._ai_melee_animname );
/#
Record3DText("Threshold Initiator distance from AnimStart = " + Sqrt( (30 * 30) ), behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
Record3DText("Initiatior distance from AnimStart = " + Distance( animationStartOrigin, behaviorTreeEntity.origin ), behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
RecordCircle( behaviorTreeEntity.enemy GetTagOrigin( "tag_sync" ), 8, ( 1, 0, 0 ), "Animscript", behaviorTreeEntity );
RecordCircle( animationStartOrigin, 8, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity );
RecordLine( animationStartOrigin, behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity );
#/
if( Distance2DSquared( behaviorTreeEntity.origin, animationStartOrigin ) <= (30 * 30) )
{
return true;
}
//check predicted position of entity, and extrapolate position for back anims
if( behaviorTreeEntity HasPath() )
{
selfPredictedPos = behaviorTreeEntity.origin;
moveAngle = behaviorTreeEntity.angles[1] + behaviorTreeEntity getMotionAngle();
selfPredictedPos += (cos( moveAngle ), sin( moveAngle ), 0) * 200.0 * 0.2; //0.2 is the lookahead time
/#
Record3DText("Initiator predicted distance from AnimStart= " + Distance( selfPredictedPos, animationStartOrigin ), behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
if( Distance2DSquared( selfPredictedPos, animationStartOrigin ) <= (30 * 30) )
{
return true;
}
}
return false;
}
function private chooseAIvsAIMeleeAnimations( behaviorTreeEntity )
{
anglesToEnemy = VectorToAngles( behaviorTreeEntity.enemy.origin - behaviorTreeEntity.origin );
yawToEnemy = AngleClamp180( behaviorTreeEntity.enemy.angles[ 1 ] - anglesToEnemy[ 1 ] );
/#
//record yaw to enemy for debugging
Record3DText("Yaw to enemy = " + abs( yawToEnemy ), behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
//clear out old anims
behaviorTreeEntity._ai_melee_animname = undefined;
behaviorTreeEntity.enemy._ai_melee_animname = undefined;
//get the scriptbundle for these archetypes
attacker_variant = chooseArchetypeVariant( behaviorTreeEntity );
defender_variant = chooseArchetypeVariant( behaviorTreeEntity.enemy );
//TODO: Change this into an assert and ensure a bundle is always available if we get here
if( !AIvsAIMeleeBundleExists( behaviorTreeEntity, attacker_variant, defender_variant ) )
{
/#
Record3DText( "AI vs AI melee Failure: Bundle does not exist for " + behaviorTreeEntity.archetype + " " + behaviorTreeEntity.enemy.archetype + " " + attacker_variant + " " + defender_variant, behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
return false;
}
animBundle = level._aivsai_meleeBundles[ behaviorTreeEntity.archetype ][ behaviorTreeEntity.enemy.archetype ][ attacker_variant ][ defender_variant ];
/#
if( ( isdefined( behaviorTreeEntity._ai_melee_attacker_loser ) && behaviorTreeEntity._ai_melee_attacker_loser ) )
{
Record3DText( "AI vs AI Melee: Attacker selected as loser", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
}
#/
foundAnims = false;
possibleMelees = [];
if( abs( yawToEnemy ) > 120 )
{
if( IsDefined( behaviorTreeEntity.__forceAIFlipMelee ) )
{
possibleMelees[possibleMelees.size] = &chooseAIVsAIMeleeFrontFlipAnimations;
}
else if( IsDefined( behaviorTreeEntity.__forceAIWrestleMelee ) )
{
possibleMelees[possibleMelees.size] = &chooseAIVsAIMeleeFrontWrestleAnimations;
}
else
{
possibleMelees[possibleMelees.size] = &chooseAIVsAIMeleeFrontFlipAnimations;
possibleMelees[possibleMelees.size] = &chooseAIVsAIMeleeFrontWrestleAnimations;
}
}
else if( abs( yawToEnemy ) < 60 )
{
possibleMelees[possibleMelees.size] = &chooseAIVsAIMeleeBackAnimations;
}
else
{
//possibly left/right directional
rightVec = VectorNormalize( AnglesToRight( behaviorTreeEntity.enemy.angles ) );
toAttackerVec = VectorNormalize( behaviorTreeEntity.origin - behaviorTreeEntity.enemy.origin );
rDot = VectorDot( toAttackerVec, rightVec );
if( rDot > 0 )
{
//coming in from the right
possibleMelees[possibleMelees.size] = &chooseAIVsAIMeleeRightAnimations;
}
else
{
//coming in from the left
possibleMelees[possibleMelees.size] = &chooseAIVsAIMeleeLeftAnimations;
}
}
if( possibleMelees.size > 0 )
{
[[possibleMelees[GetArrayKeys(possibleMelees)[RandomInt(GetArrayKeys(possibleMelees).size)]]]]( behaviorTreeEntity, animBundle );
}
if( IsDefined( behaviorTreeEntity._ai_melee_animname ) )
{
Debug_ChosenMeleeAnimations( behaviorTreeEntity );
return true;
}
return false;
}
function private chooseArchetypeVariant( entity )
{
if( entity.archetype == "robot" )
{
robot_state = entity ai::get_behavior_attribute( "rogue_control" );
if( IsInArray( array( "forced_level_1", "level_1", "level_0" ), robot_state ) )
{
return "regular";
}
if( IsInArray( array( "forced_level_2", "level_2", "level_3", "forced_level_3" ), robot_state ) )
{
return "melee";
}
}
return "regular";
}
function private AIvsAIMeleeBundleExists( behaviorTreeEntity, attacker_variant, defender_variant )
{
if( !IsDefined( level._aivsai_meleeBundles[ behaviorTreeEntity.archetype ] ) )
{
return false;
}
else if( !IsDefined( level._aivsai_meleeBundles[ behaviorTreeEntity.archetype ][ behaviorTreeEntity.enemy.archetype ] ) )
{
return false;
}
else if( !IsDefined( level._aivsai_meleeBundles[ behaviorTreeEntity.archetype ][ behaviorTreeEntity.enemy.archetype ][ attacker_variant ] ) )
{
return false;
}
else if( !IsDefined( level._aivsai_meleeBundles[ behaviorTreeEntity.archetype ][ behaviorTreeEntity.enemy.archetype ][ attacker_variant ][ defender_variant ] ) )
{
return false;
}
return true;
}
function AIvsAIMeleeInitialize( behaviorTreeEntity, asmStateName )
{
behaviorTreeEntity.blockingPain = true;
behaviorTreeEntity.enemy.blockingPain = true;
//acquire the mutex since we are about to melee
AiUtility::meleeAcquireMutex( behaviorTreeEntity );
//keep a reference to the other person incase one of the participants does not have the other selected as enemy (especially in case of back melees)
behaviorTreeEntity._ai_melee_opponent = behaviorTreeEntity.enemy;
behaviorTreeEntity.enemy._ai_melee_opponent = behaviorTreeEntity;
//the winner will call playScriptedMeleeAnimations, since this function waits on winner's victory before resetting some flags
if( ( isdefined( behaviorTreeEntity._ai_melee_attacker_loser ) && behaviorTreeEntity._ai_melee_attacker_loser ) )
{
behaviorTreeEntity._ai_melee_markedDead = true;
behaviorTreeEntity.enemy thread playScriptedMeleeAnimations();
}
else
{
behaviorTreeEntity.enemy._ai_melee_markedDead = true;
behaviorTreeEntity thread playScriptedMeleeAnimations();
}
return 5;
}
// ------- AI vs AI INTERNAL FUNCTIONS -----------//
//Threaded call to animscripted so that anim is not blended out too early
function playScriptedMeleeAnimations()
{
self endon("death");
Assert( IsDefined( self._ai_melee_opponent ) );
opponent = self._ai_melee_opponent;
// In rare cases the enemy or AI could have died.
if ( !( IsAlive( self ) && IsAlive( opponent ) ) )
{
/#
Record3DText( "AI vs AI Melee Failsafe Failure: A participant may not be alive", self.origin, ( 1, .5, 0 ), "Animscript", self, 0.4 );
#/
return false;
}
if( ( isdefined( opponent._ai_melee_attacker_loser ) && opponent._ai_melee_attacker_loser ) )
{
//link to the defender
//call animscripted on both
opponent AnimScripted( "aivsaimeleeloser", self GetTagOrigin("tag_sync"), self GetTagAngles("tag_sync"), opponent._ai_melee_animname, "normal", undefined, 1.0, 0.2, 0.3 );
self AnimScripted( "aivsaimeleewinner", self GetTagOrigin("tag_sync"), self GetTagAngles("tag_sync"), self._ai_melee_animname, "normal", undefined, 1.0, 0.2, 0.3 );
/#
RecordCircle( self GetTagOrigin("tag_sync"), 2, ( 1, .5, 0 ), "Animscript" );
RecordLine( self GetTagOrigin("tag_sync"), opponent.origin, ( 1, .5, 0 ), "Animscript" );
#/
}
else
{
//link to the defender
//call animscripted on both
self AnimScripted( "aivsaimeleewinner", opponent GetTagOrigin("tag_sync"), opponent GetTagAngles("tag_sync"), self._ai_melee_animname, "normal", undefined, 1.0, 0.2, 0.3 );
opponent AnimScripted( "aivsaimeleeloser", opponent GetTagOrigin("tag_sync"), opponent GetTagAngles("tag_sync"), opponent._ai_melee_animname, "normal", undefined, 1.0, 0.2, 0.3 );
/#
RecordCircle( opponent GetTagOrigin("tag_sync"), 2, ( 1, .5, 0 ), "Animscript" );
RecordLine( opponent GetTagOrigin("tag_sync"), self.origin, ( 1, .5, 0 ), "Animscript" );
#/
}
//spawn a thread that kills off the loser at the end
opponent thread handleDeath( opponent._ai_melee_animname, self );
if ( GetDvarInt( "tu1_aivsaiMeleeDisableGib", 1 ) )
{
// TU1 fix to prevent vertex stretching when robots are killed in AI vs AI melee.
// Don't gib robots upon an AI vs AI melee death, since robots don't have
// enough time post death to correctly hide/show tags and swap models.
if ( opponent ai::has_behavior_attribute( "can_gib" ) )
{
opponent ai::set_behavior_attribute( "can_gib", false );
}
}
//process interrupted deaths
self thread processInterruptedDeath();
opponent thread processInterruptedDeath();
//wait for winner to finish
self waittillmatch( "aivsaimeleewinner", "end" );
self.fixedLinkYawOnly = false;
//flag cleanup
//release the melee mutex
AiUtility:: cleanupChargeMeleeAttack( self );
//remove knife model if necessary
if( ( isdefined( self._ai_melee_attachedKnife ) && self._ai_melee_attachedKnife ) )
{
self Detach( "t6_wpn_knife_melee", "TAG_WEAPON_LEFT" );
self._ai_melee_attachedKnife = false;
}
self.blockingPain = false;
self._ai_melee_initiator = undefined;
self notify("meleeCompleted");
self PathMode( "move delayed", true, 3 );
}
function private chooseAIVsAIMeleeFrontFlipAnimations( behaviorTreeEntity, animBundle )
{
/#
Record3DText( "AI vs AI Melee: Direction is front", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
assert( IsDefined( animBundle ) );
if( ( isdefined( behaviorTreeEntity._ai_melee_attacker_loser ) && behaviorTreeEntity._ai_melee_attacker_loser ) )
{
behaviorTreeEntity._ai_melee_animname = animBundle.attackerLoserFrontAnim;
behaviorTreeEntity.enemy._ai_melee_animname = animBundle.defenderWinnerFrontAnim;
}
else
{
behaviorTreeEntity._ai_melee_animname = animBundle.attackerFrontAnim;
behaviorTreeEntity.enemy._ai_melee_animname = animBundle.victimFrontAnim;
}
behaviorTreeEntity._ai_melee_animtype = 1;
behaviorTreeEntity.enemy._ai_melee_animtype = 1;
}
function private chooseAIVsAIMeleeFrontWrestleAnimations( behaviorTreeEntity, animBundle )
{
/#
Record3DText( "AI vs AI Melee: Direction is front", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
assert( IsDefined( animBundle ) );
if( ( isdefined( behaviorTreeEntity._ai_melee_attacker_loser ) && behaviorTreeEntity._ai_melee_attacker_loser ) )
{
behaviorTreeEntity._ai_melee_animname = animBundle.attackerLoserAlternateFrontAnim;
behaviorTreeEntity.enemy._ai_melee_animname = animBundle.defenderWinnerAlternateFrontAnim;
}
else
{
behaviorTreeEntity._ai_melee_animname = animBundle.attackerAlternateFrontAnim;
behaviorTreeEntity.enemy._ai_melee_animname = animBundle.victimAlternateFrontAnim;
}
behaviorTreeEntity._ai_melee_animtype = 0;
behaviorTreeEntity.enemy._ai_melee_animtype = 0;
}
function private chooseAIVsAIMeleeBackAnimations( behaviorTreeEntity, animBundle )
{
/#
Record3DText( "AI vs AI Melee: Direction is back", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
assert( IsDefined( animBundle ) );
if( ( isdefined( behaviorTreeEntity._ai_melee_attacker_loser ) && behaviorTreeEntity._ai_melee_attacker_loser ) )
{
behaviorTreeEntity._ai_melee_animname = animBundle.attackerLoserBackAnim;
behaviorTreeEntity.enemy._ai_melee_animname = animBundle.defenderWinnerBackAnim;
}
else
{
behaviorTreeEntity._ai_melee_animname = animBundle.attackerBackAnim;
behaviorTreeEntity.enemy._ai_melee_animname = animBundle.victimBackAnim;
}
behaviorTreeEntity._ai_melee_animtype = 2;
behaviorTreeEntity.enemy._ai_melee_animtype = 2;
}
function private chooseAIVsAIMeleeRightAnimations( behaviorTreeEntity, animBundle )
{
/#
Record3DText( "AI vs AI Melee: Direction is right", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
assert( IsDefined( animBundle ) );
if( ( isdefined( behaviorTreeEntity._ai_melee_attacker_loser ) && behaviorTreeEntity._ai_melee_attacker_loser ) )
{
behaviorTreeEntity._ai_melee_animname = animBundle.attackerLoserRightAnim;
behaviorTreeEntity.enemy._ai_melee_animname = animBundle.defenderWinnerRightAnim;
}
else
{
behaviorTreeEntity._ai_melee_animname = animBundle.attackerRightAnim;
behaviorTreeEntity.enemy._ai_melee_animname = animBundle.victimRightAnim;
}
behaviorTreeEntity._ai_melee_animtype = 3;
behaviorTreeEntity.enemy._ai_melee_animtype = 3;
}
function private chooseAIVsAIMeleeLeftAnimations( behaviorTreeEntity, animBundle )
{
/#
Record3DText( "AI vs AI Melee: Direction is left", behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
#/
assert( IsDefined( animBundle ) );
if( ( isdefined( behaviorTreeEntity._ai_melee_attacker_loser ) && behaviorTreeEntity._ai_melee_attacker_loser ) )
{
behaviorTreeEntity._ai_melee_animname = animBundle.attackerLoserLeftAnim;
behaviorTreeEntity.enemy._ai_melee_animname = animBundle.defenderWinnerLeftAnim;
}
else
{
behaviorTreeEntity._ai_melee_animname = animBundle.attackerLeftAnim;
behaviorTreeEntity.enemy._ai_melee_animname = animBundle.victimLeftAnim;
}
behaviorTreeEntity._ai_melee_animtype = 4;
behaviorTreeEntity.enemy._ai_melee_animtype = 4;
}
function private Debug_ChosenMeleeAnimations( behaviorTreeEntity )
{
/#
if( IsDefined( behaviorTreeEntity._ai_melee_animname ) && IsDefined( behaviorTreeEntity.enemy._ai_melee_animname ) )
{
Record3DText("AI vs AI chosen anim = " + behaviorTreeEntity._ai_melee_animname, behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
Record3DText("AI vs AI enemy chosen anim = " + behaviorTreeEntity.enemy._ai_melee_animname, behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity, 0.4 );
}
#/
}
//Need this to handle deaths for the loser
function handleDeath( animationName, attacker )
{
self endon("death");
self endon("interruptedDeath");
self.skipDeath = true;
self.diedInScriptedAnim = true;
totalTime = GetAnimLength( animationName );
wait( totalTime - 0.2 );
self killWrapper( attacker );
}
//To deal with deaths during animscripted
function processInterruptedDeath()
{
self endon("meleeCompleted");
Assert( IsDefined( self._ai_melee_opponent ) );
opponent = self._ai_melee_opponent;
//do not interrupt and kill someone that is not supposed to die
if( !( isdefined( self.allowDeath ) && self.allowDeath ) )
{
return;
}
self waittill("death");
//cleanup knife if required
if( IsDefined( self ) && ( isdefined( self._ai_melee_attachedKnife ) && self._ai_melee_attachedKnife ) )
{
self Detach( "t6_wpn_knife_melee", "TAG_WEAPON_LEFT" );
}
if( IsAlive( opponent ) )
{
//loser always dies
if( ( isdefined( opponent._ai_melee_markedDead ) && opponent._ai_melee_markedDead ) )
{
opponent.diedInScriptedAnim = true;
opponent.skipDeath = true;
opponent notify ( "interruptedDeath" );
opponent notify ( "meleeCompleted" );
opponent StopAnimScripted();
opponent killWrapper();
opponent StartRagDoll();
}
else
{
opponent._ai_melee_initiator = undefined;
opponent.blockingPain = false;
opponent._ai_melee_markedDead = undefined;
opponent.skipDeath = false;
opponent.diedInScriptedAnim = false;
//release the melee mutex for opponent
AiUtility:: cleanupChargeMeleeAttack( opponent );
opponent notify( "interruptedDeath" );
opponent notify( "meleeCompleted" );
opponent StopAnimScripted();
}
}
if( IsDefined( self ) )
{
self.diedInScriptedAnim = true;
self.skipDeath = true;
self notify ( "interruptedDeath" );
self StopAnimScripted();
self killWrapper();
self StartRagDoll();
}
}
function killWrapper( attacker )
{
if( IsDefined( self.overrideActorDamage ) )
{
self.overrideActorDamage = undefined;
}
self.TokubetsuKogekita = undefined; //this var reduces damage for certain cybercom affected robots
//guarding against team changes
if( IsDefined( attacker ) && self.team != attacker.team )
{
self kill(self.origin,attacker);
}
else
{
self kill();
}
}
+145
View File
@@ -0,0 +1,145 @@
#using scripts\shared\ai_shared;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\behavior_state_machine;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\spawner_shared;
Line truncated
function autoexec main()
{
ArchetypeCivilian::RegisterBehaviorScriptFunctions();
}
#namespace ArchetypeCivilian;
function RegisterBehaviorScriptFunctions()
{
// ------- SPAWN FUNCTIONS ------------//
spawner::add_archetype_spawn_function( "civilian", &civilianBlackboardInit );
spawner::add_archetype_spawn_function( "civilian", &archetypeCivilianInit );
// register some required ai interface attributes
ai::RegisterMatchedInterface( "civilian", "sprint", false, array( true, false ) );
ai::RegisterMatchedInterface( "civilian", "panic", false, array( true, false ) );
// ------- CIVILIAN ACTIONS -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeAction("civilianMoveAction",&civilianMoveActionInitialize,undefined,&civilianMoveActionFinalize);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeAction("civilianCowerAction",&civilianCowerActionInitialize,undefined,undefined);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("civilianIsPanicked",&civilianIsPanicked);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("civilianPanic",&civilianPanic);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("civilianPanic",&civilianPanic);;
}
function private civilianBlackboardInit() // self = AI
{
// CREATE BLACKBOARD
Blackboard::CreateBlackBoardForEntity( self );
// CREATE INTERFACE
ai::CreateInterfaceForEntity( self );
// USE UTILITY BLACKBOARD
self AiUtility::RegisterUtilityBlackboardAttributes();
// CREATE CIVILIAN BLACKBOARD
Blackboard::RegisterBlackBoardAttribute(self,"_panic","calm",&BB_GetPanic); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_panic");#/ };
Blackboard::RegisterBlackBoardAttribute(self,"_human_locomotion_variation",undefined,undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_human_locomotion_variation");#/ };
// REGISTER ANIMSCRIPTED CALLBACK
self.___ArchetypeOnAnimscriptedCallback = &civilianOnAnimscriptedCallback;
// ENABLE DEBUGGING IN ODYSSEY
/#self FinalizeTrackedBlackboardAttributes();#/;
}
function private archetypeCivilianInit()
{
entity = self;
locomotionTypes = array( "alt1", "alt2", "alt3", "alt4" );
altIndex = entity GetEntityNumber() % locomotionTypes.size;
Blackboard::SetBlackBoardAttribute( entity, "_human_locomotion_variation", locomotionTypes[ altIndex ] );
entity SetAvoidanceMask( "avoid ai" );
}
function private BB_GetPanic()
{
if ( ai::GetAiAttribute( self, "panic" ) )
return "panic";
return "calm";
}
function private civilianOnAnimscriptedCallback( entity )
{
// UNREGISTER THE BLACKBOARD
entity.__blackboard = undefined;
// REREGISTER BLACKBOARD
entity civilianBlackboardInit();
}
function private civilianMoveActionInitialize( entity, asmStateName )
{
Blackboard::SetBlackBoardAttribute( entity, "_desired_stance", "stand" );
AnimationStateNetworkUtility::RequestState( entity, asmStateName );
return 5;
}
function private civilianMoveActionFinalize( entity, asmStateName )
{
if ( Blackboard::GetBlackBoardAttribute( entity, "_stance" ) != "stand" )
{
Blackboard::SetBlackBoardAttribute( entity, "_desired_stance", "stand" );
}
return 4;
}
function private civilianCowerActionInitialize( entity, asmStateName )
{
if ( isdefined( entity.node ) )
{
highestStance = AiUtility::getHighestNodeStance( entity.node );
if ( highestStance == "crouch" )
{
Blackboard::SetBlackBoardAttribute( entity, "_stance", "crouch" );
}
else
{
Blackboard::SetBlackBoardAttribute( entity, "_stance", "stand" );
}
}
AnimationStateNetworkUtility::RequestState( entity, asmStateName );
return 5;
}
function private civilianIsPanicked( entity )
{
return Blackboard::GetBlackBoardAttribute( entity, "_panic" ) == "panic";
}
function private civilianPanic( entity )
{
entity ai::set_behavior_attribute( "panic", true );
return true;
}
// end #namespace ArchetypeCivilian;
+580
View File
@@ -0,0 +1,580 @@
#using scripts\shared\ai_shared;
#using scripts\shared\math_shared;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\blackboard;
Line truncated
#namespace AiUtility;
// Use this utility if AI takes cover, behavior here should not be archetype dependent at all
// 10 degree allowable threshold for aims against the aimtables
function autoexec RegisterBehaviorScriptFunctions()
{
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isAtCrouchNode",&isAtCrouchNode);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isAtCoverCondition",&isAtCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isAtCoverStrictCondition",&isAtCoverStrictCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isAtCoverModeOver",&isAtCoverModeOver);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isAtCoverModeNone",&isAtCoverModeNone);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isExposedAtCoverCondition",&isExposedAtCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("keepClaimedNodeAndChooseCoverDirection",&keepClaimedNodeAndChooseCoverDirection);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("resetCoverParameters",&resetCoverParameters);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("cleanupCoverMode",&cleanupCoverMode);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("canBeFlankedService",&canBeFlankedService);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldCoverIdleOnly",&shouldCoverIdleOnly);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isSuppressedAtCoverCondition",&isSuppressedAtCoverCondition);;
// ------- COVER - IDLE BEHAVIOR -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverIdleInitialize",&coverIdleInitialize);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverIdleUpdate",&coverIdleUpdate);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverIdleTerminate",&coverIdleTerminate);;
// ------- COVER - FLANKED BEHAVIOR -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("isFlankedByEnemyAtCover",&isFlankedByEnemyAtCover);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverFlankedActionStart",&coverFlankedInitialize);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverFlankedActionTerminate",&coverFlankedActionTerminate);;
// ------- COVER - OVER SHOOT BEHAVIOR -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("supportsOverCoverCondition",&supportsOverCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldOverAtCoverCondition",&shouldOverAtCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverOverInitialize",&coverOverInitialize);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverOverTerminate",&coverOverTerminate);;
// ------- COVER - LEAN SHOOT BEHAVIOR -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("supportsLeanCoverCondition",&supportsLeanCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldLeanAtCoverCondition",&shouldLeanAtCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("continueLeaningAtCoverCondition",&continueLeaningAtCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverLeanInitialize",&coverLeanInitialize);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverLeanTerminate",&coverLeanTerminate);;
// ------- COVER - PEEK SHOOT BEHAVIOR -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("supportsPeekCoverCondition",&supportsPeekCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverPeekInitialize",&coverPeekInitialize);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverPeekTerminate",&coverPeekTerminate);;
// ------- COVER - RELOAD BEHAVIOR -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverReloadInitialize",&coverReloadInitialize);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("refillAmmoAndCleanupCoverMode",&refillAmmoAndCleanupCoverMode);;
}
// ------- COVER - RELOAD BEHAVIOR -----------//
function private coverReloadInitialize( behaviorTreeEntity )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode", "cover_alert" );
AiUtility::keepClaimNode( behaviorTreeEntity );
}
function refillAmmoAndCleanupCoverMode( behaviorTreeEntity )
{
if( IsAlive( behaviorTreeEntity ) )
{
AiUtility::refillAmmo( behaviorTreeEntity );
}
AiUtility::cleanupCoverMode( behaviorTreeEntity );
}
// ------- COVER - PEEK BEHAVIOR -----------//
function private supportsPeekCoverCondition( behaviorTreeEntity )
{
return IsDefined( behaviorTreeEntity.node );
}
function private coverPeekInitialize( behaviorTreeEntity )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode", "cover_alert" );
AiUtility::keepClaimNode( behaviorTreeEntity );
chooseCoverDirection( behaviorTreeEntity );
}
function private coverPeekTerminate( behaviorTreeEntity )
{
AiUtility::chooseFrontCoverDirection( behaviorTreeEntity );
AiUtility::cleanupCoverMode( behaviorTreeEntity );
}
// ------- COVER - PEEK BEHAVIOR -----------//
// ------- COVER - LEAN SHOOT BEHAVIOR -----------//
function private supportsLeanCoverCondition( behaviorTreeEntity )
{
if ( IsDefined( behaviorTreeEntity.node ) )
{
if( (behaviorTreeEntity.node.type == "Cover Left") || (behaviorTreeEntity.node.type == "Cover Right") )
{
return true;
}
else if ( (behaviorTreeEntity.node.type == "Cover Pillar") )
{
if ( !( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 1024) == 1024))) || !( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 2048) == 2048))) )
{
return true;
}
}
}
return false;
}
function private shouldLeanAtCoverCondition( behaviorTreeEntity )
{
if( !IsDefined( behaviorTreeEntity.node ) ||
!IsDefined( behaviorTreeEntity.node.type ) ||
!IsDefined( behaviorTreeEntity.enemy) ||
!IsDefined( behaviorTreeEntity.enemy.origin ) )
{
return false;
}
yawToEnemyPosition = AiUtility::GetAimYawToEnemyFromNode( behaviorTreeEntity, behaviorTreeEntity.node, behaviorTreeEntity.enemy );
legalAimYaw = false;
if( (behaviorTreeEntity.node.type == "Cover Left") )
{
aimLimitsForCover = behaviortreeentity GetAimLimitsFromEntry("cover_left_lean");
legalAimYaw = yawToEnemyPosition <= ( aimLimitsForCover["aim_left"] + 10 ) && yawToEnemyPosition >= -10;
}
else if( (behaviorTreeEntity.node.type == "Cover Right") )
{
aimLimitsForCover = behaviortreeentity GetAimLimitsFromEntry("cover_right_lean");
legalAimYaw = yawToEnemyPosition >= ( aimLimitsForCover["aim_right"] - 10 ) && yawToEnemyPosition <= 10;
}
else if( (behaviorTreeEntity.node.type == "Cover Pillar") )
{
aimLimitsForCover = behaviortreeentity GetAimLimitsFromEntry("cover");
supportsLeft = !( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 1024) == 1024)));
supportsRight = !( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 2048) == 2048)));
// If both left and right are supported, then split the 180 coverage along the center, otherwise pad the leeway to force the AI to lean more often.
angleLeeway = 10;
if ( supportsRight && supportsLeft )
{
angleLeeway = 0;
}
if ( supportsLeft )
{
legalAimYaw = yawToEnemyPosition <= ( aimLimitsForCover["aim_left"] + 10 ) && yawToEnemyPosition >= -angleLeeway;
}
if ( !legalAimYaw && supportsRight )
{
legalAimYaw = yawToEnemyPosition >= ( aimLimitsForCover["aim_right"] - 10 ) && yawToEnemyPosition <= angleLeeway;
}
}
return legalAimYaw;
}
function private continueLeaningAtCoverCondition( behaviorTreeEntity )
{
if ( behaviorTreeEntity ASMIsTransitionRunning() )
{
return true;
}
return AiUtility::shouldLeanAtCoverCondition( behaviorTreeEntity );
}
function private coverLeanInitialize( behaviorTreeEntity )
{
AiUtility::setCoverShootStartTime( behaviorTreeEntity );
AiUtility::keepClaimNode( behaviorTreeEntity );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode", "cover_lean" );
AiUtility::chooseCoverDirection( behaviorTreeEntity );
}
function private coverLeanTerminate( behaviorTreeEntity )
{
AiUtility::chooseFrontCoverDirection( behaviorTreeEntity );
AiUtility::cleanupCoverMode( behaviorTreeEntity );
AiUtility::clearCoverShootStartTime( behaviorTreeEntity );
}
// ------- COVER - LEAN SHOOT BEHAVIOR -----------//
// ------- COVER - OVER SHOOT BEHAVIOR -----------//
function private supportsOverCoverCondition( behaviorTreeEntity )
{
stance = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_stance" );
if ( IsDefined( behaviorTreeEntity.node ) )
{
if ( !IsInArray( GetValidCoverPeekOuts( behaviorTreeEntity.node ), "over" ) )
{
return false;
}
if( (behaviorTreeEntity.node.type == "Cover Left")
|| (behaviorTreeEntity.node.type == "Cover Right")
|| (behaviorTreeEntity.node.type == "Cover Crouch" || behaviorTreeEntity.node.type == "Cover Crouch Window" || behaviorTreeEntity.node.type == "Conceal Crouch" ) )
{
if( stance == "crouch" )
{
return true;
}
}
else if( (behaviorTreeEntity.node.type == "Cover Stand" || behaviorTreeEntity.node.type == "Conceal Stand") )
{
if( stance == "stand" )
return true;
}
}
return false;
}
function private shouldOverAtCoverCondition( entity )
{
if ( !IsDefined( entity.node ) ||
!IsDefined( entity.node.type ) ||
!IsDefined( entity.enemy) ||
!IsDefined( entity.enemy.origin ) )
{
return false;
}
aimTable = ( AiUtility::isCoverConcealed( entity.node ) ?
"cover_concealed_over" : "cover_over" );
aimLimitsForCover = entity GetAimLimitsFromEntry( aimTable );
yawToEnemyPosition = AiUtility::GetAimYawToEnemyFromNode( entity, entity.node, entity.enemy );
legalAimYaw = ( yawToEnemyPosition >= ( aimLimitsForCover["aim_right"] - 10 ) ) &&
( yawToEnemyPosition <= ( aimLimitsForCover["aim_left"] + 10 ) );
if ( !legalAimYaw )
{
return false;
}
pitchToEnemyPosition = AiUtility::GetAimPitchToEnemyFromNode( entity, entity.node, entity.enemy );
legalAimPitch = ( pitchToEnemyPosition >= ( aimLimitsForCover["aim_up"] + 10 ) ) &&
( pitchToEnemyPosition <= ( aimLimitsForCover["aim_down"] + 10 ) );
if ( !legalAimPitch )
{
return false;
}
return true;
}
function private coverOverInitialize( behaviorTreeEntity )
{
AiUtility::setCoverShootStartTime( behaviorTreeEntity );
AiUtility::keepClaimNode( behaviorTreeEntity );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode", "cover_over" );
}
function private coverOverTerminate( behaviorTreeEntity )
{
AiUtility::cleanupCoverMode( behaviorTreeEntity );
AiUtility::clearCoverShootStartTime( behaviorTreeEntity );
}
// ------- COVER - OVER SHOOT BEHAVIOR -----------//
// ------- COVER - IDLE BEHAVIOR -----------//
function private coverIdleInitialize( behaviorTreeEntity )
{
AiUtility::keepClaimNode( behaviorTreeEntity );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode", "cover_alert" );
}
function private coverIdleUpdate( behaviorTreeEntity )
{
if( !behaviorTreeEntity ASMIsTransitionRunning() )
{
AiUtility::releaseClaimNode( behaviorTreeEntity );
}
}
function private coverIdleTerminate( behaviorTreeEntity )
{
AiUtility::releaseClaimNode( behaviorTreeEntity );
AiUtility::cleanupCoverMode( behaviorTreeEntity );
}
// ------- COVER - IDLE BEHAVIOR -----------//
// ------- COVER - FLANKED BEHAVIOR ---------//
function private isFlankedByEnemyAtCover( behaviorTreeEntity )
{
return canBeFlanked( behaviorTreeEntity ) &&
behaviorTreeEntity IsAtCoverNodeStrict() &&
behaviorTreeEntity IsFlankedAtCoverNode() &&
!behaviorTreeEntity HasPath();
}
function private canBeFlankedService( behaviorTreeEntity )
{
AiUtility::setCanBeFlanked( behaviorTreeEntity, true );
}
function private coverFlankedInitialize( behaviorTreeEntity )
{
if( IsDefined( behaviorTreeEntity.enemy ) )
{
// cheat and give perfect info about the close enemy, makes it feel like AI is smart :)
behaviorTreeEntity GetPerfectInfo( behaviorTreeEntity.enemy );
// Dont move for a few sec, this may trigger AI to charge melee attack
behaviorTreeEntity PathMode( "move delayed", false, 2 );
}
setCanBeFlanked( behaviorTreeEntity, false );
AiUtility::cleanupCoverMode( behaviorTreeEntity );
AiUtility::keepClaimNode( behaviorTreeEntity );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_desired_stance", "stand" );
}
function private coverFlankedActionTerminate( behaviorTreeEntity )
{
behaviorTreeEntity.newEnemyReaction = false;
AiUtility::releaseClaimNode( behaviorTreeEntity );
}
// ------- COVER - FLANKED BEHAVIOR ---------//
// ------- COVER - COMMON CONDITIONS ---------//
function isAtCrouchNode( behaviorTreeEntity )
{
if ( IsDefined( behaviorTreeEntity.node ) &&
( (behaviorTreeEntity.node.type == "Exposed") || (behaviorTreeEntity.node.type == "Guard") || (behaviorTreeEntity.node.type == "Path") ) )
{
if ( DistanceSquared( behaviorTreeEntity.origin, behaviorTreeEntity.node.origin ) <= ( (24) * (24) ) )
{
return !AiUtility::isStanceAllowedAtNode( "stand", behaviorTreeEntity.node ) &&
AiUtility::isStanceAllowedAtNode( "crouch", behaviorTreeEntity.node );
}
}
return false;
}
function isAtCoverCondition( behaviorTreeEntity )
{
return behaviorTreeEntity IsAtCoverNodeStrict() &&
behaviorTreeEntity ShouldUseCoverNode() &&
!behaviorTreeEntity HasPath();
}
function isAtCoverStrictCondition( behaviorTreeEntity )
{
return behaviorTreeEntity IsAtCoverNodeStrict() &&
!behaviorTreeEntity HasPath();
}
function isAtCoverModeOver( behaviorTreeEntity )
{
coverMode = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode" );
return coverMode == "cover_over";
}
function isAtCoverModeNone( behaviorTreeEntity )
{
coverMode = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode" );
return coverMode == "cover_mode_none";
}
function isExposedAtCoverCondition( behaviorTreeEntity )
{
return behaviorTreeEntity IsAtCoverNodeStrict() && !behaviorTreeEntity ShouldUseCoverNode();
}
function shouldCoverIdleOnly( behaviorTreeEntity )
{
if( behaviorTreeEntity ai::get_behavior_attribute( "coverIdleOnly" ) )
{
return true;
}
if( ( isdefined( behaviorTreeEntity.node.script_onlyidle ) && behaviorTreeEntity.node.script_onlyidle ) )
{
return true;
}
return false;
}
function isSuppressedAtCoverCondition( behaviorTreeEntity )
{
// TODO(David Young 9-6-13): Move this to code.
return behaviorTreeEntity.suppressionMeter > behaviorTreeEntity.suppressionThreshold;
}
function keepClaimedNodeAndChooseCoverDirection( behaviorTreeEntity )
{
AiUtility::keepClaimNode( behaviorTreeEntity );
chooseCoverDirection( behaviorTreeEntity );
}
function resetCoverParameters( behaviorTreeEntity )
{
AiUtility::chooseFrontCoverDirection( behaviorTreeEntity );
AiUtility::cleanupCoverMode( behaviorTreeEntity );
AiUtility::clearCoverShootStartTime( behaviorTreeEntity );
}
function chooseCoverDirection( behaviorTreeEntity, stepOut )
{
if ( !IsDefined( behaviorTreeEntity.node ) )
{
return;
}
coverDirection = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_cover_direction" );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_previous_cover_direction", coverDirection );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_direction", calculateCoverDirection( behaviorTreeEntity, stepOut ) );
}
function calculateCoverDirection( behaviorTreeEntity, stepOut )
{
if( IsDefined( behaviorTreeEntity.treatAllCoversAsGeneric ) )
{
if ( !IsDefined( stepOut ) )
{
stepOut = false;
}
coverDirection = "cover_front_direction";
if ( (behaviorTreeEntity.node.type == "Cover Left") )
{
if ( ( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 4) == 4))) || math::cointoss() || stepOut )
{
coverDirection = "cover_left_direction";
}
}
else if ( (behaviorTreeEntity.node.type == "Cover Right") )
{
if ( ( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 4) == 4))) || math::cointoss() || stepOut )
{
coverDirection = "cover_right_direction";
}
}
else if ( (behaviorTreeEntity.node.type == "Cover Pillar") )
{
// must choose either left or right
if( ( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 1024) == 1024))) )
{
return "cover_right_direction";
}
if( ( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 2048) == 2048))) )
{
return "cover_left_direction";
}
coverDirection = "cover_left_direction";
if ( IsDefined( behaviorTreeEntity.enemy ) )
{
yawToEnemyPosition = AiUtility::GetAimYawToEnemyFromNode( behaviorTreeEntity, behaviorTreeEntity.node, behaviorTreeEntity.enemy );
aimLimitsForDirectionRight = behaviorTreeEntity GetAimLimitsFromEntry("pillar_right_lean");
legalRightDirectionYaw = yawToEnemyPosition >= ( aimLimitsForDirectionRight["aim_right"] - 10 ) && yawToEnemyPosition <= 0;
if ( legalRightDirectionYaw )
{
coverDirection = "cover_right_direction";
}
}
}
return coverDirection;
}
else
{
coverDirection = "cover_front_direction";
if ( (behaviorTreeEntity.node.type == "Cover Pillar") )
{
// must choose either left or right
if( ( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 1024) == 1024))) )
{
return "cover_right_direction";
}
if( ( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 2048) == 2048))) )
{
return "cover_left_direction";
}
coverDirection = "cover_left_direction";
if ( IsDefined( behaviorTreeEntity.enemy ) )
{
yawToEnemyPosition = AiUtility::GetAimYawToEnemyFromNode( behaviorTreeEntity, behaviorTreeEntity.node, behaviorTreeEntity.enemy );
aimLimitsForDirectionRight = behaviorTreeEntity GetAimLimitsFromEntry("pillar_right_lean");
legalRightDirectionYaw = yawToEnemyPosition >= ( aimLimitsForDirectionRight["aim_right"] - 10 ) && yawToEnemyPosition <= 0;
if ( legalRightDirectionYaw )
{
coverDirection = "cover_right_direction";
}
}
}
}
return coverDirection;
}
function clearCoverShootStartTime( behaviorTreeEntity )
{
behaviorTreeEntity.coverShootStartTime = undefined;
}
function setCoverShootStartTime( behaviorTreeEntity )
{
behaviorTreeEntity.coverShootStartTime = GetTime();
}
function canBeFlanked( behaviorTreeEntity )
{
return IsDefined( behaviorTreeEntity.canBeFlanked ) && behaviorTreeEntity.canBeFlanked;
}
function setCanBeFlanked( behaviorTreeEntity, canBeFlanked )
{
behaviorTreeEntity.canBeFlanked = canBeFlanked;
}
function cleanupCoverMode( behaviorTreeEntity )
{
if( isAtCoverCondition( behaviorTreeEntity ) )
{
coverMode = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode" );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_previous_cover_mode", coverMode );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode", "cover_mode_none" );
}
else
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_previous_cover_mode", "cover_mode_none" );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode", "cover_mode_none" );
}
}
// ------- COVER - COMMON CONDITIONS ---------//
+487
View File
@@ -0,0 +1,487 @@
Line truncated
#using scripts\shared\clientfield_shared;
#using scripts\shared\array_shared;
#using scripts\shared\audio_shared;
#using scripts\shared\util_shared;
#using scripts\shared\math_shared;
//firebug human torching...
#precache( "client_fx", "fire/fx_fire_ai_human_arm_left_loop" );
#precache( "client_fx", "fire/fx_fire_ai_human_arm_left_os" );
#precache( "client_fx", "fire/fx_fire_ai_human_arm_right_loop" );
#precache( "client_fx", "fire/fx_fire_ai_human_arm_right_os" );
#precache( "client_fx", "fire/fx_fire_ai_human_hip_left_loop" );
#precache( "client_fx", "fire/fx_fire_ai_human_hip_left_os" );
#precache( "client_fx", "fire/fx_fire_ai_human_hip_right_loop" );
#precache( "client_fx", "fire/fx_fire_ai_human_hip_right_os" );
#precache( "client_fx", "fire/fx_fire_ai_human_leg_left_loop" );
#precache( "client_fx", "fire/fx_fire_ai_human_leg_left_os" );
#precache( "client_fx", "fire/fx_fire_ai_human_leg_right_loop" );
#precache( "client_fx", "fire/fx_fire_ai_human_leg_right_os" );
#precache( "client_fx", "fire/fx_fire_ai_human_torso_loop" );
#precache( "client_fx", "fire/fx_fire_ai_human_torso_os" );
#precache( "client_fx", "fire/fx_fire_ai_human_head_loop" );
#precache( "client_fx", "fire/fx_fire_ai_human_head_os" );
#precache( "client_fx", "fire/fx_fire_ai_robot_arm_left_loop" );
#precache( "client_fx", "fire/fx_fire_ai_robot_arm_left_os" );
#precache( "client_fx", "fire/fx_fire_ai_robot_arm_right_loop" );
#precache( "client_fx", "fire/fx_fire_ai_robot_arm_right_os" );
#precache( "client_fx", "fire/fx_fire_ai_robot_head_loop" );
#precache( "client_fx", "fire/fx_fire_ai_robot_head_os" );
#precache( "client_fx", "fire/fx_fire_ai_robot_leg_left_loop" );
#precache( "client_fx", "fire/fx_fire_ai_robot_leg_left_os" );
#precache( "client_fx", "fire/fx_fire_ai_robot_leg_right_loop" );
#precache( "client_fx", "fire/fx_fire_ai_robot_leg_right_os" );
#precache( "client_fx", "fire/fx_fire_ai_robot_torso_loop" );
#precache( "client_fx", "fire/fx_fire_ai_robot_torso_os" );
#precache( "client_fx", "smoke/fx_smk_ai_human_arm_left_os" );
#precache( "client_fx", "smoke/fx_smk_ai_human_arm_right_os" );
#precache( "client_fx", "smoke/fx_smk_ai_human_head_os" );
#precache( "client_fx", "smoke/fx_smk_ai_human_hip_left_os" );
#precache( "client_fx", "smoke/fx_smk_ai_human_hip_right_os" );
#precache( "client_fx", "smoke/fx_smk_ai_human_leg_left_os" );
#precache( "client_fx", "smoke/fx_smk_ai_human_leg_right_os" );
#precache( "client_fx", "smoke/fx_smk_ai_human_torso_os" );
#precache( "client_fx", "smoke/fx_smk_ai_human_waist_os" );
function autoexec main()
{
// clientfield setup
RegisterClientFields();
//effects caching
LoadEffects();
}
function RegisterClientFields()
{
clientfield::register( "actor", "arch_actor_fire_fx", 1, 2, "int", &actor_fire_fx_state, !true, !true );
clientfield::register( "actor", "arch_actor_char", 1, 2, "int", &actor_char, !true, !true );
}
function LoadEffects()
{
//fire fx
level._effect["fire_human_j_elbow_le_loop"] = "fire/fx_fire_ai_human_arm_left_loop"; // hand and forearm fires
level._effect["fire_human_j_elbow_ri_loop"] = "fire/fx_fire_ai_human_arm_right_loop";
level._effect["fire_human_j_shoulder_le_loop"] = "fire/fx_fire_ai_human_arm_left_loop"; // upper arm fires
level._effect["fire_human_j_shoulder_ri_loop"] = "fire/fx_fire_ai_human_arm_right_loop";
level._effect["fire_human_j_spine4_loop"] = "fire/fx_fire_ai_human_torso_loop"; // upper torso fires
level._effect["fire_human_j_hip_le_loop"] = "fire/fx_fire_ai_human_hip_left_loop"; // thigh fires
level._effect["fire_human_j_hip_ri_loop"] = "fire/fx_fire_ai_human_hip_right_loop";
level._effect["fire_human_j_knee_le_loop"] = "fire/fx_fire_ai_human_leg_left_loop"; // shin fires
level._effect["fire_human_j_knee_ri_loop"] = "fire/fx_fire_ai_human_leg_right_loop";
level._effect["fire_human_j_head_loop"] = "fire/fx_fire_ai_human_head_loop"; // head fire
level._effect["fire_human_j_elbow_le_os"] = "fire/fx_fire_ai_human_arm_left_os"; // hand and forearm fires
level._effect["fire_human_j_elbow_ri_os"] = "fire/fx_fire_ai_human_arm_right_os";
level._effect["fire_human_j_shoulder_le_os"] = "fire/fx_fire_ai_human_arm_left_os"; // upper arm fires
level._effect["fire_human_j_shoulder_ri_os"] = "fire/fx_fire_ai_human_arm_right_os";
level._effect["fire_human_j_spine4_os"] = "fire/fx_fire_ai_human_torso_os"; // upper torso fires
level._effect["fire_human_j_hip_le_os"] = "fire/fx_fire_ai_human_hip_left_os"; // thigh fire
level._effect["fire_human_j_hip_ri_os"] = "fire/fx_fire_ai_human_hip_right_os";
level._effect["fire_human_j_knee_le_os"] = "fire/fx_fire_ai_human_leg_left_os"; // shin fires
level._effect["fire_human_j_knee_ri_os"] = "fire/fx_fire_ai_human_leg_right_os";
level._effect["fire_human_j_head_os"] = "fire/fx_fire_ai_human_head_os"; // head fire
level._effect["fire_human_riotshield_j_elbow_le_loop"] = "fire/fx_fire_ai_human_arm_left_loop"; // hand and forearm fires
level._effect["fire_human_riotshield_j_elbow_ri_loop"] = "fire/fx_fire_ai_human_arm_right_loop";
level._effect["fire_human_riotshield_j_shoulder_le_loop"] = "fire/fx_fire_ai_human_arm_left_loop"; // upper arm fires
level._effect["fire_human_riotshield_j_shoulder_ri_loop"] = "fire/fx_fire_ai_human_arm_right_loop";
level._effect["fire_human_riotshield_j_spine4_loop"] = "fire/fx_fire_ai_human_torso_loop"; // upper torso fires
level._effect["fire_human_riotshield_j_hip_le_loop"] = "fire/fx_fire_ai_human_hip_left_loop"; // thigh fires
level._effect["fire_human_riotshield_j_hip_ri_loop"] = "fire/fx_fire_ai_human_hip_right_loop";
level._effect["fire_human_riotshield_j_knee_le_loop"] = "fire/fx_fire_ai_human_leg_left_loop"; // shin fires
level._effect["fire_human_riotshield_j_knee_ri_loop"] = "fire/fx_fire_ai_human_leg_right_loop";
level._effect["fire_human_riotshield_j_head_loop"] = "fire/fx_fire_ai_human_head_loop"; // head fire
level._effect["fire_human_riotshield_j_elbow_le_os"] = "fire/fx_fire_ai_human_arm_left_os"; // hand and forearm fires
level._effect["fire_human_riotshield_j_elbow_ri_os"] = "fire/fx_fire_ai_human_arm_right_os";
level._effect["fire_human_riotshield_j_shoulder_le_os"] = "fire/fx_fire_ai_human_arm_left_os"; // upper arm fires
level._effect["fire_human_riotshield_j_shoulder_ri_os"] = "fire/fx_fire_ai_human_arm_right_os";
level._effect["fire_human_riotshield_j_spine4_os"] = "fire/fx_fire_ai_human_torso_os"; // upper torso fires
level._effect["fire_human_riotshield_j_hip_le_os"] = "fire/fx_fire_ai_human_hip_left_os"; // thigh fire
level._effect["fire_human_riotshield_j_hip_ri_os"] = "fire/fx_fire_ai_human_hip_right_os";
level._effect["fire_human_riotshield_j_knee_le_os"] = "fire/fx_fire_ai_human_leg_left_os"; // shin fires
level._effect["fire_human_riotshield_j_knee_ri_os"] = "fire/fx_fire_ai_human_leg_right_os";
level._effect["fire_human_riotshield_j_head_os"] = "fire/fx_fire_ai_human_head_os"; // head fire
//fire fx
level._effect["fire_warlord_j_elbow_le_loop"] = "fire/fx_fire_ai_human_arm_left_loop"; // hand and forearm fires
level._effect["fire_warlord_j_elbow_ri_loop"] = "fire/fx_fire_ai_human_arm_right_loop";
level._effect["fire_warlord_j_shoulder_le_loop"] = "fire/fx_fire_ai_human_arm_left_loop"; // upper arm fires
level._effect["fire_warlord_j_shoulder_ri_loop"] = "fire/fx_fire_ai_human_arm_right_loop";
level._effect["fire_warlord_j_spine4_loop"] = "fire/fx_fire_ai_human_torso_loop"; // upper torso fires
level._effect["fire_warlord_j_hip_le_loop"] = "fire/fx_fire_ai_human_hip_left_loop"; // thigh fires
level._effect["fire_warlord_j_hip_ri_loop"] = "fire/fx_fire_ai_human_hip_right_loop";
level._effect["fire_warlord_j_knee_le_loop"] = "fire/fx_fire_ai_human_leg_left_loop"; // shin fires
level._effect["fire_warlord_j_knee_ri_loop"] = "fire/fx_fire_ai_human_leg_right_loop";
level._effect["fire_warlord_j_head_loop"] = "fire/fx_fire_ai_human_head_loop"; // head fire
level._effect["fire_warlord_j_elbow_le_os"] = "fire/fx_fire_ai_human_arm_left_os"; // hand and forearm fires
level._effect["fire_warlord_j_elbow_ri_os"] = "fire/fx_fire_ai_human_arm_right_os";
level._effect["fire_warlord_j_shoulder_le_os"] = "fire/fx_fire_ai_human_arm_left_os"; // upper arm fires
level._effect["fire_warlord_j_shoulder_ri_os"] = "fire/fx_fire_ai_human_arm_right_os";
level._effect["fire_warlord_j_spine4_os"] = "fire/fx_fire_ai_human_torso_os"; // upper torso fires
level._effect["fire_warlord_j_hip_le_os"] = "fire/fx_fire_ai_human_hip_left_os"; // thigh fire
level._effect["fire_warlord_j_hip_ri_os"] = "fire/fx_fire_ai_human_hip_right_os";
level._effect["fire_warlord_j_knee_le_os"] = "fire/fx_fire_ai_human_leg_left_os"; // shin fires
level._effect["fire_warlord_j_knee_ri_os"] = "fire/fx_fire_ai_human_leg_right_os";
level._effect["fire_warlord_j_head_os"] = "fire/fx_fire_ai_human_head_os"; // head fire
//fire fx
level._effect["fire_zombie_j_elbow_le_os"] = "fire/fx_fire_ai_human_arm_left_os"; // hand and forearm fires
level._effect["fire_zombie_j_elbow_ri_os"] = "fire/fx_fire_ai_human_arm_right_os";
level._effect["fire_zombie_j_shoulder_le_os"] = "fire/fx_fire_ai_human_arm_left_os"; // upper arm fires
level._effect["fire_zombie_j_shoulder_ri_os"] = "fire/fx_fire_ai_human_arm_right_os";
level._effect["fire_zombie_j_spine4_os"] = "fire/fx_fire_ai_human_torso_os"; // upper torso fires
level._effect["fire_zombie_j_hip_le_os"] = "fire/fx_fire_ai_human_hip_left_os"; // thigh fire
level._effect["fire_zombie_j_hip_ri_os"] = "fire/fx_fire_ai_human_hip_right_os";
level._effect["fire_zombie_j_knee_le_os"] = "fire/fx_fire_ai_human_leg_left_os"; // shin fires
level._effect["fire_zombie_j_knee_ri_os"] = "fire/fx_fire_ai_human_leg_right_os";
level._effect["fire_zombie_j_head_os"] = "fire/fx_fire_ai_human_head_os"; // head fire
//smoke fx
level._effect["smolder_human_j_elbow_le_os"] = "smoke/fx_smk_ai_human_arm_left_os";
level._effect["smolder_human_j_elbow_ri_os"] = "smoke/fx_smk_ai_human_arm_right_os";
level._effect["smolder_human_j_shoulder_le_os"] = "smoke/fx_smk_ai_human_arm_left_os";
level._effect["smolder_human_j_shoulder_ri_os"] = "smoke/fx_smk_ai_human_arm_right_os";
level._effect["smolder_human_j_spine4_os"] = "smoke/fx_smk_ai_human_torso_os";
level._effect["smolder_human_j_hip_le_os"] = "smoke/fx_smk_ai_human_hip_left_os";
level._effect["smolder_human_j_hip_ri_os"] = "smoke/fx_smk_ai_human_hip_right_os";
level._effect["smolder_human_j_knee_le_os"] = "smoke/fx_smk_ai_human_leg_left_os";
level._effect["smolder_human_j_knee_ri_os"] = "smoke/fx_smk_ai_human_leg_right_os";
level._effect["smolder_human_j_head_os"] = "smoke/fx_smk_ai_human_head_os";
// robot-specific fires
level._effect["fire_robot_j_elbow_le_rot_loop"] = "fire/fx_fire_ai_robot_arm_left_loop"; // hand and forearm fires
level._effect["fire_robot_j_elbow_ri_rot_loop"] = "fire/fx_fire_ai_robot_arm_right_loop";
level._effect["fire_robot_j_shoulder_le_rot_loop"] = "fire/fx_fire_ai_robot_arm_left_loop"; // upper arm fires
level._effect["fire_robot_j_shoulder_ri_rot_loop"] = "fire/fx_fire_ai_robot_arm_right_loop";
level._effect["fire_robot_j_spine4_loop"] = "fire/fx_fire_ai_robot_torso_loop"; // upper torso fires
level._effect["fire_robot_j_knee_le_loop"] = "fire/fx_fire_ai_robot_leg_left_loop"; // shin fires
level._effect["fire_robot_j_knee_ri_loop"] = "fire/fx_fire_ai_robot_leg_right_loop";
level._effect["fire_robot_j_head_loop"] = "fire/fx_fire_ai_robot_head_loop"; // head fire
level._effect["fire_robot_j_elbow_le_rot_os"] = "fire/fx_fire_ai_robot_arm_left_os"; // hand and forearm fires
level._effect["fire_robot_j_elbow_ri_rot_os"] = "fire/fx_fire_ai_robot_arm_right_os";
level._effect["fire_robot_j_shoulder_le_rot_os"] = "fire/fx_fire_ai_robot_arm_left_os"; // upper arm fires
level._effect["fire_robot_j_shoulder_ri_rot_os"] = "fire/fx_fire_ai_robot_arm_right_os";
level._effect["fire_robot_j_spine4_os"] = "fire/fx_fire_ai_robot_torso_os"; // upper torso fires
level._effect["fire_robot_j_knee_le_os"] = "fire/fx_fire_ai_robot_leg_left_os"; // shin fires
level._effect["fire_robot_j_knee_ri_os"] = "fire/fx_fire_ai_robot_leg_right_os";
level._effect["fire_robot_j_head_os"] = "fire/fx_fire_ai_robot_head_os"; // head fire
}
//------------------------------FIRE FX------------------------------
function private _burnTag(localClientNum, tag, postfix)
{
if(isDefined(self) && self HasDObj(localClientNum) )
{
fx_to_play = undefined;
fxname = "fire_"+self.archetype+"_"+tag+postfix;
if( isdefined(level._effect[fxname]))
{
fx_to_play = level._effect[fxname];
}
if( isdefined( self._effect ) && isdefined( self._effect[fxname]))
{
fx_to_play = self._effect[fxname];
}
if( isDefined( fx_to_play ))
{
fx = PlayFXOnTag(localClientNum, fx_to_play, self, tag);
if( ( SessionModeIsCampaignZombiesGame() ) && IsDefined(fx) )
{
SetFXIgnorePause( localClientNum, fx, true );
}
return fx;
}
}
}
function private _burnStage(localClientNum, tagArray, shouldWait )
{
if(!isDefined(self))
return;
self endon("entityshutdown");
tags = array::randomize(tagArray);
for(i=1;i<tags.size;i++)//drop random one for variance.
{
if ( tags[i] == "null" )
{
continue;
}
self.activeFX[self.activeFX.size] = self _burnTag(localClientNum, tags[i],(shouldWait?"_loop":"_os") );
if(shouldWait)
{
wait RandomFloatRange(0.1,.3);
}
}
if(shouldWait)
{
wait RandomFloatRange(0,1);
}
if ( isDefined(self) )
self notify("burn_stage_finished");
}
function private _burnBody(localClientNum)
{
self endon("entityshutdown");
self.burn_loop_sound_handle = self playloopsound( "chr_burn_npc_loop1", .2);
timer = 10;
boneModifier = "";
if ( self.archetype == "robot" )
{
boneModifier = "_rot";
timer = 6;
}
if( ( SessionModeIsCampaignZombiesGame() ) ) // special case for a suicidal zombie in campaign zombies
{
if( self.archetype !== "zombie" )
{
self thread sndStopBurnLoop(timer);
}
}
else
{
self thread sndStopBurnLoop(timer);
}
stage1BurnTags = array("j_elbow_le"+boneModifier, "j_elbow_ri"+boneModifier, "null");
stage2BurnTags = array("j_shoulder_le"+boneModifier, "j_shoulder_ri"+boneModifier, "null");
stage3BurnTags = array("j_spine4", "null");
stage4BurnTags = array("j_hip_le", "j_hip_ri", "j_head", "null");
stage5BurnTags = array("j_knee_le", "j_knee_ri", "null");
matureMask = 0.0;
if ( util::is_mature() )
matureMask = 1.0;
self.activeFX = [];
self.activeFX[self.activeFX.size] = self thread _burnStage(localClientNum, stage1BurnTags,true);
self MapShaderConstant( localClientNum, 0, "scriptVector0", matureMask * 0.20 );
self waittill("burn_stage_finished");
self.activeFX[self.activeFX.size] = self thread _burnStage(localClientNum, stage2BurnTags,true);
self MapShaderConstant( localClientNum, 0, "scriptVector0", matureMask * 0.40 );
self waittill("burn_stage_finished");
self.activeFX[self.activeFX.size] = self thread _burnStage(localClientNum, stage3BurnTags,true);
self MapShaderConstant( localClientNum, 0, "scriptVector0", matureMask * 0.60 );
self waittill("burn_stage_finished");
self.activeFX[self.activeFX.size] = self thread _burnStage(localClientNum, stage4BurnTags,true);
self MapShaderConstant( localClientNum, 0, "scriptVector0", matureMask * 0.80 );
self waittill("burn_stage_finished");
self.activeFX[self.activeFX.size] = self thread _burnStage(localClientNum, stage5BurnTags,true);
self MapShaderConstant( localClientNum, 0, "scriptVector0", matureMask * 1.00 );
}
function sndStopBurnLoop(timer)
{
self util::waittill_any_timeout(timer, "entityshutdown", "stopBurningSounds" );
if(isDefined(self))
{
if( isdefined( self.burn_loop_sound_handle ) )
{
self stoploopsound( self.burn_loop_sound_handle );
}
}
}
function private _burnCorpse(localClientNum, burningDuration)
{
self endon("entityshutdown");
timer = 10;
boneModifier = "";
if ( self.archetype == "robot" )
{
boneModifier = "_rot";
timer = 3;
}
stage1BurnTags = array("j_elbow_le"+boneModifier, "j_elbow_ri"+boneModifier);
stage2BurnTags = array("j_shoulder_le"+boneModifier, "j_shoulder_ri"+boneModifier);
stage3BurnTags = array("j_spine4", "j_spinelower", "null");
stage4BurnTags = array("j_hip_le", "j_hip_ri", "j_head");
stage5BurnTags = array("j_knee_le", "j_knee_ri");
self.burn_loop_sound_handle = self playloopsound( "chr_burn_npc_loop1", .2);
self thread sndStopBurnLoop(timer);
// TODO: If self is a robot, check which parts have gibbed and don't play effects on those bones.
// TODO: If there's a way to find out which effects were played on this archetype before it became a corpse, play only the oneshot versions of those effects.
self.activeFX = [];
self.activeFX[self.activeFX.size] = self thread _burnStage(localClientNum, stage1BurnTags, false);
self.activeFX[self.activeFX.size] = self thread _burnStage(localClientNum, stage2BurnTags, false);
self.activeFX[self.activeFX.size] = self thread _burnStage(localClientNum, stage3BurnTags, false);
self.activeFX[self.activeFX.size] = self thread _burnStage(localClientNum, stage4BurnTags, false);
self.activeFX[self.activeFX.size] = self thread _burnStage(localClientNum, stage5BurnTags, false);
matureMask = 0.0;
if ( util::is_mature() )
matureMask = 1.0;
self MapShaderConstant( localClientNum, 0, "scriptVector0", matureMask * 1.0 );
wait 20;
if(isDefined(self))
{
foreach(fx in self.activeFX)
{
StopFx(localClientNum,fx);
//wait RandomFloatRange(0.25,2); // effect timeouts are baked in, so they should all have stopped by now
self notify( "stopBurningSounds" );
}
if(isDefined(self))
self.activeFX = [];
}
}
function private _smolderCorpse(localClientNum)
{
self endon("entityshutdown");
boneModifier = "";
if ( self.archetype == "robot" )
{
boneModifier = "_rot";
}
activeFX = [];
fxToPlay = [];
tags = array("j_elbow_le"+boneModifier,"j_elbow_ri"+boneModifier,"j_shoulder_le","j_shoulder_ri","j_spine4","j_hip_le","j_hip_ri","j_knee_le","j_knee_ri","j_head");
num = RandomIntRange(6,10);
while(num)
{
fxToPlay[fxToPlay.size] = tags[RandomInt(tags.size)]; //dont care if there are dups
num--;
}
foreach(tag in fxToPlay)
{
fx = "smolder_"+self.archetype+tag+"_os";
if(isDefined(level._effect[fx]))
{
activeFX[activeFX.size] = playfxontag(localClientNum,level._effect[fx],self,tag);
wait RandomFloatRange(0.1,1);
}
}
wait 20;
if(isDefined(self))
{
foreach(fx in activeFX)
{
StopFx(localClientNum,fx);
}
}
}
function actor_fire_fx(localClientNum, value, burningDuration)
{
switch(value)
{
case 0: //turn off burning
if (isDefined(self.activeFX))
{
self StopAllLoopSounds(1);
foreach(fx in self.activeFX)
{
StopFx(localClientNum,fx);
}
}
self.activeFX = [];
break;
case 1: //burning
self thread _burnBody(localClientNum);
break;
case 2:
self thread _burnCorpse(localClientNum, burningDuration);
break;
case 3:
self thread _smolderCorpse(localClientNum);
break;
}
}
function actor_fire_fx_state(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump)
{
self actor_fire_fx(localClientNum, newVal, 14);
}
function actor_char(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump)
{
matureMask = 0.0;
if ( util::is_mature() )
matureMask = 1.0;
switch(newVal)
{
case 1:
self thread actorCharRampTo(localClientNum,1);
break;
case 0:
self MapShaderConstant( localClientNum, 0, "scriptVector0", 0);
break;
case 2:
self MapShaderConstant( localClientNum, 0, "scriptVector0", matureMask * 1.0 );
break;
}
}
function actorCharRampTo(localClientNum,charDesired)
{
self endon("entityshutdown");
if(!isDefined(self.curCharLevel))
self.curCharLevel = 0;
matureMask = 0.0;
if ( util::is_mature() )
matureMask = 1.0;
if(!isDefined(self.charsteps))
{
assert(isDefined(charDesired));
self.charsteps = int(2 / 0.01);
delta = charDesired - self.curCharLevel;
self.charinc = delta/self.charsteps;
}
while(self.charsteps)
{
self.curCharLevel = math::clamp(self.curCharLevel+self.charinc,0,1);
self MapShaderConstant( localClientNum, 0, "scriptVector0", matureMask * self.curCharLevel );
self.charsteps--;
wait 0.01;
}
}
+116
View File
@@ -0,0 +1,116 @@
Line truncated
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\math_shared;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\ai\systems\debug;
#using scripts\shared\ai\systems\gib;
#using scripts\shared\ai\systems\shared;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\callbacks_shared;
function autoexec main()
{
// clientfield setup
clientfield::register( "actor", "arch_actor_fire_fx", 1, 2, "int" );
clientfield::register( "actor", "arch_actor_char", 1, 2, "int" );
callback::on_actor_damage(&OnActorDamageCallback);
callback::on_vehicle_damage(&OnVehicleDamageCallback);
callback::on_actor_killed(&OnActorKilledCallback);
callback::on_vehicle_killed(&OnVehicleKilledCallback);
}
//------------------------------------Callbacks
function OnActorDamageCallback( params )
{
OnActorDamage( params );
}
function OnVehicleDamageCallback( params )
{
OnVehicleDamage( params );
}
function OnActorKilledCallback( params )
{
OnActorKilled();
//follow the example if you need to add more archetypes
switch(self.archetype)
{
case "human":
OnHumanKilled();
break;
case "robot":
OnRobotKilled();
break;
}
}
function OnVehicleKilledCallback( params )
{
OnVehicleKilled( params );
}
//------------------------------------Actor Damage FX
function OnActorDamage( params )
{}
//------------------------------------Vehicle Damage FX
function OnVehicleDamage( params )
{
OnVehicleKilled( params );
}
//------------------------------------Actor Killed FX
function OnActorKilled()
{
if (isDefined(self.damageMod))
{
if(self.damageMod == "MOD_BURNED")
{
//special weapons will handle the initiation of the burn fx
if(isDefined(self.damageWeapon) && isDefined(self.damageWeapon.specialpain) && self.damageWeapon.specialpain == false)
{
self clientfield::set("arch_actor_fire_fx", 2 );
}
}
}
}
function OnHumanKilled()
{
}
function OnRobotKilled()
{
}
//------------------------------------Vehicle Killed FX
function OnVehicleKilled( params )
{
}
+9
View File
@@ -0,0 +1,9 @@
+56
View File
@@ -0,0 +1,56 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\system_shared;
Line truncated
#precache( "client_fx", "animals/fx_bio_direwolf_eyes" );
#namespace ArchetypeDirewolf;
function autoexec __init__sytem__() { system::register("direwolf",&__init__,undefined,undefined); }
function autoexec precache()
{
level._effect[ "fx_bio_direwolf_eyes" ] = "animals/fx_bio_direwolf_eyes";
}
function __init__()
{
if( ai::shouldRegisterClientFieldForArchetype( "direwolf" ) )
{
clientfield::register(
"actor",
"direwolf_eye_glow_fx",
1,
1,
"int",
&direwolfEyeGlowFxHandler,
!true,
true );
}
}
function private direwolfEyeGlowFxHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( IsDefined( entity.archetype ) && entity.archetype != "direwolf" )
{
return;
}
if ( IsDefined( entity.eyeGlowFx ) )
{
StopFx( localClientNum, entity.eyeGlowFx );
entity.eyeGlowFx = undefined;
}
if ( newValue )
{
entity.eyeGlowFx = PlayFxOnTag( localClientNum, level._effect[ "fx_bio_direwolf_eyes" ], entity, "tag_eye" );
}
}
+58
View File
@@ -0,0 +1,58 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\spawner_shared;
#using scripts\shared\ai\behavior_zombie_dog;
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\util_shared;
#using scripts\shared\system_shared;
Line truncated
#namespace ArchetypeDirewolf;
function autoexec __init__sytem__() { system::register("direwolf",&__init__,undefined,undefined); }
function __init__()
{
// INIT BLACKBOARD
spawner::add_archetype_spawn_function( "direwolf", &ZombieDogBehavior::ArchetypeZombieDogBlackboardInit );
spawner::add_archetype_spawn_function( "direwolf", &direwolfSpawnSetup );
// REGISTER AI INTERFACE ATTRIBUTES
ai::RegisterMatchedInterface( "direwolf", "sprint", false, array( true, false ) );
ai::RegisterMatchedInterface( "direwolf", "howl_chance", 0.3 ); // 30% chance to howl when we spot our target
ai::RegisterMatchedInterface( "direwolf", "can_initiateaivsaimelee", true, array( true, false ) );
ai::RegisterMatchedInterface( "direwolf", "spacing_near_dist", 120 );
ai::RegisterMatchedInterface( "direwolf", "spacing_far_dist", 480 );
ai::RegisterMatchedInterface( "direwolf", "spacing_horz_dist", 144 );
ai::RegisterMatchedInterface( "direwolf", "spacing_value", 0 ); // between -1 and 1
if( ai::shouldRegisterClientFieldForArchetype( "direwolf" ) )
{
clientfield::register(
"actor",
"direwolf_eye_glow_fx",
1,
1,
"int" );
}
}
function private direwolfSpawnSetup()
{
// init the entity
self SetTeam( "team3" );
self AllowPitchAngle( 1 );
self setPitchOrient();
self setAvoidanceMask( "avoid all" );
self PushActors( true );
self ai::set_behavior_attribute( "spacing_value", RandomFloatRange( -1.0, 1.0 ) );
// enable eye glow
self clientfield::set( "direwolf_eye_glow_fx", 1 );
}
// end #namespace ArchetypeDirewolf;
+19
View File
@@ -0,0 +1,19 @@
///////////////////////////////////////////////////////////////////////////////
//
// Clientfields
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// FX
//
///////////////////////////////////////////////////////////////////////////////
+47
View File
@@ -0,0 +1,47 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\duplicaterender_mgr;
#using scripts\shared\util_shared;
#using scripts\shared\ai\systems\fx_character;
#using scripts\shared\ai\systems\gib;
Line truncated
#using_animtree("generic");
function autoexec precache()
{
}
function autoexec main()
{
clientfield::register(
"actor",
"facial_dial",
1,
1,
"int",
&HumanClientUtils::facialDialogueHandler,
!true,
true );
}
#namespace HumanClientUtils;
function facialDialogueHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if( newvalue )
{
self.facialDialogueActive = true;
}
else
{
if( ( isdefined( self.facialDialogueActive ) && self.facialDialogueActive ) )
{
self ClearAnim( %faces, 0 );
}
}
}
+426
View File
@@ -0,0 +1,426 @@
#using scripts\shared\ai_shared;
#using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\math_shared;
#using scripts\shared\spawner_shared;
#using scripts\shared\gameskill_shared;
#using scripts\shared\util_shared;
// COMMON AI SYSTEMS INCLUDES
#using scripts\shared\ai\systems\ai_blackboard;
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\systems\destructible_character;
#using scripts\shared\ai\systems\gib;
#using scripts\shared\ai\archetype_cover_utility;
#using scripts\shared\ai\archetype_mocomps_utility;
// BLACKBOARD
#using scripts\shared\ai\archetype_human_blackboard;
// NOTETRACKS
#using scripts\shared\ai\archetype_notetracks;
// BEHAVIORS
#using scripts\shared\ai\archetype_human_exposed;
#using scripts\shared\ai\archetype_human_cover;
#using scripts\shared\ai\archetype_human_locomotion;
// INTERFACE
#using scripts\shared\ai\archetype_human_interface;
#using scripts\shared\ai\archetype_utility;
Line truncated
#precache( "fx", "weapon/fx_hero_annhilatr_death_blood" );
function autoexec init()
{
// INIT BLACKBOARD
spawner::add_archetype_spawn_function( "human", &ArchetypeHumanBlackboardInit );
spawner::add_archetype_spawn_function( "human", &ArchetypeHumanInit );
HumanInterface::RegisterHumanInterfaceAttributes();
clientfield::register(
"actor",
"facial_dial",
1,
1,
"int");
/#
level.__ai_forceGibs = GetDvarInt( "ai_forceGibs" );
#/
}
function private ArchetypeHumanInit()
{
entity = self;
AiUtility::AddAIOverrideDamageCallback( entity, &DamageOverride );
AiUtility::AddAIOverrideKilledCallback( entity, &humanGibKilledOverride );
locomotionTypes = array( "alt1", "alt2", "alt3", "alt4" );
altIndex = entity GetEntityNumber() % locomotionTypes.size;
Blackboard::SetBlackBoardAttribute( entity, "_human_locomotion_variation", locomotionTypes[ altIndex ] );
if ( ( isdefined( entity.hero ) && entity.hero ) )
{
Blackboard::SetBlackBoardAttribute( entity, "_human_locomotion_variation", "alt1" );
}
}
function private ArchetypeHumanBlackboardInit() // self = AI
{
// CREATE BLACKBOARD
Blackboard::CreateBlackBoardForEntity( self );
// CREATE INTERFACE
ai::CreateInterfaceForEntity( self );
// USE UTILITY BLACKBOARD
self AiUtility::RegisterUtilityBlackboardAttributes();
// CREATE HUMAN BLACKBOARD
self Blackboard::RegisterActorBlackBoardAttributes();
// REGISTER ANIMSCRIPTED CALLBACK
self.___ArchetypeOnAnimscriptedCallback = &ArchetypeHumanOnAnimscriptedCallback;
self.___ArchetypeOnBehaveCallback = &ArchetypeHumanOnBehaveCallback;
// ENABLE DEBUGGING IN ODYSSEY
/#self FinalizeTrackedBlackboardAttributes();#/;
// THREAD PRE BULLET FIRE CALLBACK
self thread gameskill::accuracy_buildup_before_fire( self );
// RUN SNIPER GLINT AND LASER IF ACCURATE FIRE IS ON
if( self.accurateFire )
{
self thread AiUtility::preShootLaserAndGlintOn( self );
self thread AiUtility::postShootLaserAndGlintOff( self );
}
DestructServerUtils::ToggleSpawnGibs( self, true );
GibServerUtils::ToggleSpawnGibs( self, true );
}
function private ArchetypeHumanOnBehaveCallback( entity )
{
// Animscripted left the AI at cover.
if ( AiUtility::isAtCoverCondition( entity ) )
{
Blackboard::SetBlackBoardAttribute( entity, "_previous_cover_mode", "cover_alert" );
Blackboard::SetBlackBoardAttribute( entity, "_cover_mode", "cover_mode_none" );
}
// Prevent an AI that just left anim script from throwing a grenade.
grenadeThrowInfo = SpawnStruct();
grenadeThrowInfo.grenadeThrower = entity;
Blackboard::AddBlackboardEvent(
"human_grenade_throw",
grenadeThrowInfo,
RandomIntRange( 3000, 4000 ) );
}
function private ArchetypeHumanOnAnimscriptedCallback( entity ) // self/entity = AI
{
// UNREGISTER THE BLACKBOARD
entity.__blackboard = undefined;
// REREGISTER BLACKBOARD
entity ArchetypeHumanBlackboardInit();
// Reapply the current vignette_mode
vignetteMode = ai::GetAiAttribute( entity, "vignette_mode" );
HumanSoldierServerUtils::VignetteModeCallback( entity, "vignette_mode", vignetteMode, vignetteMode );
}
function private humanGibKilledOverride(
inflictor, attacker, damage, meansOfDeath, weapon, dir, hitLoc, offsetTime )
{
entity = self;
// Only allow human gibbing at a 50% rate.
if ( math::cointoss() )
{
return damage;
}
attackerDistance = 0;
if ( IsDefined( attacker ) )
{
attackerDistance = DistanceSquared( attacker.origin, entity.origin );
}
isExplosive = IsInArray(
array(
"MOD_CRUSH",
"MOD_GRENADE",
"MOD_GRENADE_SPLASH",
"MOD_PROJECTILE",
"MOD_PROJECTILE_SPLASH",
"MOD_EXPLOSIVE" ),
meansOfDeath );
forceGibbing = false;
if ( IsDefined( weapon.weapclass ) && weapon.weapclass == "turret" )
{
forceGibbing = true;
// 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, entity.origin ) <= ( (60) * (60) );
if ( isDirectExplosive && isCloseExplosive )
{
GibServerUtils::Annihilate( entity );
}
}
}
if ( forceGibbing || isExplosive ||
( IsDefined( level.__ai_forceGibs ) && level.__ai_forceGibs ) ||
( weapon.doGibbing && ( attackerDistance <= ( (weapon.maxGibDistance) * (weapon.maxGibDistance) ) ) ) )
{
// Enable spawning gib pieces.
GibServerUtils::ToggleSpawnGibs( entity, true );
DestructServerUtils::ToggleSpawnGibs( entity, true );
// TODO(David Young 2-15-15): Currently humans don't support head gibbing.
// TryGibbingHead( entity, damage, hitLoc, isExplosive );
TryGibbingLimb( entity, damage, hitLoc, isExplosive || forceGibbing );
TryGibbingLegs( entity, damage, hitLoc, isExplosive );
}
return damage;
}
function private TryGibbingHead( entity, damage, hitLoc, isExplosive )
{
if ( isExplosive )
{
GibServerUtils::GibHead( entity );
}
else if ( IsInArray( array( "head", "neck", "helmet" ), hitLoc ) )
{
GibServerUtils::GibHead( entity );
}
}
function private TryGibbingLimb( entity, damage, hitLoc, isExplosive )
{
if ( isExplosive )
{
randomChance = RandomFloatRange( 0, 1 );
if ( randomChance < 0.5 )
{
// 50% chance
GibServerUtils::GibRightArm( entity );
}
else
{
// 50% chance
GibServerUtils::GibLeftArm( entity );
}
}
else if ( IsInArray( array( "left_hand", "left_arm_lower", "left_arm_upper" ), hitLoc ) )
{
GibServerUtils::GibLeftArm( entity );
}
else if ( IsInArray( array( "right_hand", "right_arm_lower", "right_arm_upper" ), hitLoc ) )
{
GibServerUtils::GibRightArm( entity );
}
else if ( IsInArray( array( "torso_upper" ), hitLoc ) && math::cointoss() )
{
// Award a random gib.
if ( math::cointoss() )
{
GibServerUtils::GibLeftArm( entity );
}
else
{
GibServerUtils::GibRightArm( entity );
}
}
}
function private TryGibbingLegs( entity, damage, hitLoc, isExplosive, attacker )
{
if ( isExplosive )
{
randomChance = RandomFloatRange( 0, 1 );
if ( randomChance < 0.33 )
{
// 33% chance
GibServerUtils::GibRightLeg( entity );
}
else if ( randomChance < 0.66 )
{
// 33% chance
GibServerUtils::GibLeftLeg( entity );
}
else
{
// 34% chance
GibServerUtils::GibLegs( entity );
}
}
else if ( IsInArray( array( "left_leg_upper", "left_leg_lower", "left_foot" ), hitLoc ) )
{
GibServerUtils::GibLeftLeg( entity );
}
else if ( IsInArray( array( "right_leg_upper", "right_leg_lower", "right_foot" ), hitLoc ) )
{
GibServerUtils::GibRightLeg( entity );
}
else if ( IsInArray( array( "torso_lower" ), hitLoc ) && math::cointoss() )
{
// Award a random gib.
if ( math::cointoss() )
{
GibServerUtils::GibLeftLeg( entity );
}
else
{
GibServerUtils::GibRightLeg( entity );
}
}
}
function DamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex, modelIndex )
{
entity = self;
entity DestructServerUtils::HandleDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex, modelIndex );
if( isDefined( eAttacker) && !isPlayer( eAttacker ) && !isVehicle( eAttacker ) )
{
dist = DistanceSquared( entity.origin, eAttacker.origin );
if( dist < 256*256 )
{
iDamage = Int( iDamage * 10 );
}
else
{
iDamage = Int( iDamage * 1.5 );
}
}
if ( sWeapon.name == "incendiary_grenade" )
{
iDamage = entity.health;
}
return iDamage;
}
#namespace HumanSoldierServerUtils;
function cqbAttributeCallback( entity, attribute, oldValue, value )
{
if ( value )
{
entity AsmChangeAnimMappingTable( 2 );
}
else
{
if( entity ai::get_behavior_attribute( "useAnimationOverride" ) )
entity AsmChangeAnimMappingTable( 1 );
else
entity AsmChangeAnimMappingTable( 0 );
}
}
function forceTacticalWalkCallback( entity, attribute, oldValue, value )
{
entity.ignorerunAndgundist = value;
}
function moveModeAttributeCallback( entity, attribute, oldValue, value )
{
entity.ignorepathenemyfightdist = false;
switch ( value )
{
case "normal":
break;
case "rambo":
entity.ignorepathenemyfightdist = true;
break;
}
}
function UseAnimationOverrideCallback( entity, attribute, oldValue, value )
{
if ( value )
{
entity AsmChangeAnimMappingTable( 1 );
}
else
{
entity AsmChangeAnimMappingTable( 0 );
}
}
function VignetteModeCallback( entity, attribute, oldValue, value )
{
switch ( value )
{
case "off":
entity.pushable = true;
entity PushActors( false );
entity PushPlayer( false );
entity SetAvoidanceMask( "avoid all" );
entity SetSteeringMode( "normal steering" );
break;
case "slow":
entity.pushable = false;
entity PushActors( false );
entity PushPlayer( true );
entity SetAvoidanceMask( "avoid ai" );
entity SetSteeringMode( "vignette steering" );
break;
case "fast":
entity.pushable = false;
entity PushActors( true );
entity PushPlayer( true );
entity SetAvoidanceMask( "avoid none" );
entity SetSteeringMode( "vignette steering" );
break;
default:
break;
}
}
// end #namespace HumanSoldierServerUtils;
+141
View File
@@ -0,0 +1,141 @@
#using scripts\shared\array_shared;
#using scripts\shared\ai_shared;
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\systems\shared;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\archetype_cover_utility;
#using scripts\shared\ai\archetype_locomotion_utility;
Line truncated
#namespace Blackboard;
//*****************************************************************************
// NOTE! When adding a new blackboard variable you must also declare the
// blackboard variable within the ast_definitions file found at:
//
// //t7/main/game/share/raw/animtables/ast_definitions.json
//
// This allows the Animation Selector Table system to determine how to create
// queries based on the blackboard variable.
//
// Also, all blackboard values must be lowercased! No convert to lower is
// perform when assigning values to the blackboard.
//
//*****************************************************************************
function RegisterActorBlackBoardAttributes()
{
Blackboard::RegisterBlackBoardAttribute(self,"_tactical_arrival_facing_yaw",undefined,&BB_GetTacticalArrivalFacingYaw); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_tactical_arrival_facing_yaw");#/ };
Blackboard::RegisterBlackBoardAttribute(self,"_human_locomotion_movement_type",undefined,&BB_GetLocomotionMovementType); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_human_locomotion_movement_type");#/ };
Blackboard::RegisterBlackBoardAttribute(self,"_human_cover_flankability",undefined,&BB_GetCoverFlankability); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_human_cover_flankability");#/ };
Blackboard::RegisterBlackBoardAttribute(self,"_arrival_type",undefined,&BB_GetArrivalType); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_arrival_type");#/ };
Blackboard::RegisterBlackBoardAttribute(self,"_human_locomotion_variation",undefined,undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_human_locomotion_variation");#/ };
}
function private BB_GetArrivalType()
{
if( self ai::get_behavior_attribute( "disablearrivals" ) )
return "dont_arrive_at_goal";
return "arrive_at_goal";
}
function private BB_GetTacticalArrivalFacingYaw()
{
return AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] );
}
function private BB_GetLocomotionMovementType()
{
if ( !ai::GetAiAttribute( self, "disablesprint" ) )
{
// if the script interface needs sprinting
if ( ai::GetAiAttribute( self, "sprint" ) )
{
return "human_locomotion_movement_sprint";
}
if ( !isDefined( self.nearbyFriendlyCheck ) )
self.nearbyFriendlyCheck = 0;
now = GetTime();
if ( now >= self.nearbyFriendlyCheck )
{
self.nearbyFriendlyCount = GetActorTeamCountRadius( self.origin, ( 120 ), self.team, "neutral" );
self.nearbyFriendlyCheck = now + 500;
}
if ( self.nearbyFriendlyCount >= 3 )
{
// Too many nearby friendlies to sprint, run instead.
return "human_locomotion_movement_default";
}
// should sprint if too far away from enemy
if ( IsDefined( self.enemy ) && IsDefined( self.runAndGunDist ) )
{
if ( DistanceSquared( self.origin, self LastKnownPos( self.enemy ) )
> ( self.runAndGunDist * self.runAndGunDist ) )
{
return "human_locomotion_movement_sprint";
}
}
else if ( IsDefined( self.goalpos ) && IsDefined( self.runAndGunDist ) )
{
if ( DistanceSquared( self.origin, self.goalpos )
> ( self.runAndGunDist * self.runAndGunDist ) )
{
return "human_locomotion_movement_sprint";
}
}
}
return "human_locomotion_movement_default";
}
function private BB_GetCoverFlankability()
{
if( self ASMIsTransitionRunning() )
{
return "unflankable";
}
if( !IsDefined( self.node ) )
{
return "unflankable";
}
coverMode = Blackboard::GetBlackBoardAttribute( self, "_cover_mode" );
if( IsDefined( coverMode ) )
{
coverNode = self.node;
if( coverMode == "cover_alert" || coverMode == "cover_mode_none" )
{
return "flankable";
}
if( (coverNode.type == "Cover Pillar") )
{
return ( coverMode == "cover_blind" );
}
else if( (coverNode.type == "Cover Left") || (coverNode.type == "Cover Right") )
{
return ( coverMode == "cover_blind" || coverMode == "cover_over" );
}
else if( (coverNode.type == "Cover Stand" || coverNode.type == "Conceal Stand") || (coverNode.type == "Cover Crouch" || coverNode.type == "Cover Crouch Window" || coverNode.type == "Conceal Crouch" ) )
{
return "flankable";
}
}
return "unflankable";
}
+591
View File
@@ -0,0 +1,591 @@
// COMMON AI SYSTEMS INCLUDES
#using scripts\shared\ai_shared;
#using scripts\shared\ai_shared;
#using scripts\shared\ai\systems\ai_blackboard;
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\blackboard;
// ADDITIONAL INCLUDES
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\archetype_cover_utility;
#using scripts\shared\array_shared;
#using scripts\shared\laststand_shared;
Line truncated
function autoexec RegisterBehaviorScriptfunctions()
{
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldReturnToCoverCondition",&shouldReturnToCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldReturnToSuppressedCover",&shouldReturnToSuppressedCover);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldAdjustToCover",&shouldAdjustToCover);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("prepareForAdjustToCover",&prepareForAdjustToCover);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverBlindfireShootStart",&coverBlindfireShootActionStart);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("canChangeStanceAtCoverCondition",&canChangeStanceAtCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverChangeStanceActionStart",&coverChangeStanceActionStart);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("prepareToChangeStanceToStand",&prepareToChangeStanceToStand);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("cleanUpChangeStanceToStand",&cleanUpChangeStanceToStand);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("prepareToChangeStanceToCrouch",&prepareToChangeStanceToCrouch);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("cleanUpChangeStanceToCrouch",&cleanUpChangeStanceToCrouch);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldVantageAtCoverCondition",&shouldVantageAtCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("supportsVantageCoverCondition",&supportsVantageCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverVantageInitialize",&coverVantageInitialize);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldThrowGrenadeAtCoverCondition",&shouldThrowGrenadeAtCoverCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverPrepareToThrowGrenade",&coverPrepareToThrowGrenade);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("coverCleanUpToThrowGrenade",&coverCleanUpToThrowGrenade);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("senseNearbyPlayers",&senseNearbyPlayers);;
}
function shouldThrowGrenadeAtCoverCondition( behaviorTreeEntity, throwIfPossible = false )
{
// sjakatdar - Please do not use level.AIDisableGrenadeThrows, not a preferred method to disable grenades by script. Only used by campaign zombies.
if( ( isdefined( level.AIDisableGrenadeThrows ) && level.AIDisableGrenadeThrows ) )
{
return false;
}
if( !IsDefined( behaviorTreeEntity.enemy ) )
{
return false;
}
if ( !IsSentient( behaviorTreeEntity.enemy ) )
{
return false;
}
if ( IsVehicle( behaviorTreeEntity.enemy ) && behaviorTreeEntity.enemy.vehicleclass === "helicopter" )
{
return false;
}
if ( ai::HasAiAttribute( behaviorTreeEntity, "useGrenades" ) && !ai::GetAiAttribute( behaviorTreeEntity, "useGrenades" ) )
{
return false;
}
// Only throw a grenade if the enemy is within 60 degrees to the left or right of where the actor or the actor's node is facing.
entityAngles = behaviorTreeEntity.angles;
if ( IsDefined( behaviorTreeEntity.node ) &&
( (behaviorTreeEntity.node.type == "Cover Left") || (behaviorTreeEntity.node.type == "Cover Right") || (behaviorTreeEntity.node.type == "Cover Pillar") || (behaviorTreeEntity.node.type == "Cover Stand" || behaviorTreeEntity.node.type == "Conceal Stand") || (behaviorTreeEntity.node.type == "Cover Crouch" || behaviorTreeEntity.node.type == "Cover Crouch Window" || behaviorTreeEntity.node.type == "Conceal Crouch" ) ) &&
behaviorTreeEntity IsAtCoverNodeStrict() )
{
entityAngles = behaviorTreeEntity.node.angles;
}
toEnemy = behaviorTreeEntity.enemy.origin - behaviorTreeEntity.origin;
toEnemy = VectorNormalize( ( toEnemy[0], toEnemy[1], 0 ) );
entityForward = AnglesToForward( entityAngles );
entityForward = VectorNormalize( ( entityForward[0], entityForward[1], 0 ) );
// cos( 60 degrees ) = 0.5
if ( VectorDot( toEnemy, entityForward ) < 0.5 )
{
return false;
}
if ( !throwIfPossible )
{
// Don't throw grenades at enemys that are next to players.
if ( behaviorTreeEntity.team === "allies" )
{
foreach( player in level.players )
{
if( DistanceSquared( behaviorTreeEntity.enemy.origin, player.origin ) <= (500 * 500 ) )
{
return false;
}
}
}
// Don't throw grenades at enemies that are close to laststand players.
foreach( player in level.players )
{
if ( player laststand::player_is_in_laststand() &&
DistanceSquared( behaviortreeentity.enemy.origin, player.origin ) <= (500 * 500 ) )
{
return false;
}
}
// if there was a grenade thrown by the same team recently, don't throw another one
grenadeThrowInfos = Blackboard::GetBlackboardEvents( "team_grenade_throw" );
foreach ( grenadeThrowInfo in grenadeThrowInfos )
{
if( grenadeThrowInfo.data.grenadeThrowerTeam === behaviorTreeEntity.team )
{
return false;
}
}
// if there was a grenade that was thrown at the enemy recently, then dont throw it again
grenadeThrowInfos = Blackboard::GetBlackboardEvents( "human_grenade_throw" );
foreach ( grenadeThrowInfo in grenadeThrowInfos )
{
if( IsDefined( grenadeThrowInfo.data.grenadeThrownAt ) && IsAlive( grenadeThrowInfo.data.grenadeThrownAt ) )
{
if( grenadeThrowInfo.data.grenadeThrower == behaviorTreeEntity )
{
return false;
}
if( IsDefined( grenadeThrowInfo.data.grenadeThrownAt ) &&
grenadeThrowInfo.data.grenadeThrownAt == behaviorTreeEntity.enemy )
{
return false;
}
if ( IsDefined( grenadeThrowInfo.data.grenadeThrownPosition ) &&
IsDefined( behaviorTreeEntity.grenadeThrowPosition ) &&
DistanceSquared( grenadeThrowInfo.data.grenadeThrownPosition, behaviorTreeEntity.grenadeThrowPosition ) <= (600 * 600) )
{
// Prevent too many grenades in the same area.
return false;
}
}
}
}
throw_dist = Distance2DSquared( behaviorTreeEntity.origin, behaviorTreeEntity LastKnownPos( behaviorTreeEntity.enemy ) );
if ( throw_dist < ( (500) * (500) ) || throw_dist > ( (1250) * (1250) ) )
{
return false;
}
arm_offset = TEMP_get_arm_offset( behaviorTreeEntity, behaviorTreeEntity LastKnownPos( behaviorTreeEntity.enemy ) );
throw_vel = behaviorTreeEntity CanThrowGrenadePos( arm_offset, behaviorTreeEntity LastKnownPos( behaviorTreeEntity.enemy ) );
if ( !IsDefined( throw_vel ) )
{
return false;
}
return true;
}
function private senseNearbyPlayers( entity )
{
players = GetPlayers();
foreach ( player in players )
{
distanceSq = DistanceSquared( player.origin, entity.origin );
if ( distanceSq <= ( (360) * (360) ) )
{
distanceToPlayer = Sqrt( distanceSq );
chanceToDetect = RandomFloat( 1.0 );
if ( chanceToDetect < ( distanceToPlayer / 360 ) )
{
entity GetPerfectInfo( player );
}
}
}
}
function private coverPrepareToThrowGrenade( behaviorTreeEntity )
{
AiUtility::keepClaimedNodeAndChooseCoverDirection( behaviorTreeEntity );
if ( IsDefined( behaviorTreeEntity.enemy ) )
{
behaviorTreeEntity.grenadeThrowPosition = behaviorTreeEntity LastKnownPos( behaviorTreeEntity.enemy );
}
grenadeThrowInfo = SpawnStruct();
grenadeThrowInfo.grenadeThrower = behaviorTreeEntity;
grenadeThrowInfo.grenadeThrownAt = behaviorTreeEntity.enemy;
grenadeThrowInfo.grenadeThrownPosition = behaviorTreeEntity.grenadeThrowPosition;
Blackboard::AddBlackboardEvent( "human_grenade_throw", grenadeThrowInfo, RandomIntRange( 15000, 20000 ) );
grenadeThrowInfo = SpawnStruct();
grenadeThrowInfo.grenadeThrowerTeam = behaviorTreeEntity.team;
Blackboard::AddBlackboardEvent( "team_grenade_throw", grenadeThrowInfo, RandomIntRange( 1000, 2000 ) );
behaviorTreeEntity.prepareGrenadeAmmo = behaviorTreeEntity.grenadeammo;
}
function private coverCleanUpToThrowGrenade( behaviorTreeEntity )
{
AiUtility::resetCoverParameters( behaviorTreeEntity );
if ( behaviorTreeEntity.prepareGrenadeAmmo == behaviorTreeEntity.grenadeammo )
{
// Actor was killed before being able to drop a grenade, drop one instead.
if ( behaviorTreeEntity.health <= 0 )
{
grenade = undefined;
// Need someone living to spawn the dropped grenade, otherwise it cannot be picked up.
if ( IsActor( behaviorTreeEntity.enemy ) && IsDefined( behaviorTreeEntity.grenadeweapon ) )
{
grenade = behaviorTreeEntity.enemy MagicGrenadeType(
behaviorTreeEntity.grenadeweapon,
behaviorTreeEntity GetTagOrigin( "j_wrist_ri" ),
(0, 0, 0),
behaviorTreeEntity.grenadeweapon.aifusetime / 1000 );
}
else if ( IsPlayer( behaviorTreeEntity.enemy ) && IsDefined( behaviorTreeEntity.grenadeweapon ) )
{
grenade = behaviorTreeEntity.enemy MagicGrenadePlayer(
behaviorTreeEntity.grenadeweapon,
behaviorTreeEntity GetTagOrigin( "j_wrist_ri" ),
(0, 0, 0) );
}
if ( IsDefined( grenade ) )
{
grenade.owner = behaviorTreeEntity;
grenade.team = behaviorTreeEntity.team;
// Since the grenade is spawned so closely to the actor, disable collision against the actor.
grenade SetContents( grenade SetContents( 0 ) & ~( (1 << 15) | (1 << 26) | (1 << 23) | (1 << 25) ) );
}
}
}
}
function private canChangeStanceAtCoverCondition( behaviorTreeEntity )
{
switch ( Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_stance" ) )
{
case "stand":
return AiUtility::isStanceAllowedAtNode( "crouch", behaviorTreeEntity.node );
case "crouch":
return AiUtility::isStanceAllowedAtNode( "stand", behaviorTreeEntity.node );
}
return false;
}
function private shouldReturnToSuppressedCover( entity )
{
if ( !entity IsAtGoal() )
{
return true;
}
return false;
}
function private shouldReturnToCoverCondition( behaviorTreeEntity )
{
if ( behaviorTreeEntity ASMIsTransitionRunning() )
{
return false;
}
if ( IsDefined( behaviorTreeEntity.coverShootStartTime ) )
{
// take a few shots before returning
if ( GetTime() < behaviorTreeEntity.coverShootStartTime + 800 )
{
return false;
}
// try to finish off enemy
if ( IsDefined( behaviorTreeEntity.enemy ) &&
IsPlayer( behaviorTreeEntity.enemy ) &&
behaviorTreeEntity.enemy.health < behaviorTreeEntity.enemy.maxHealth * 0.5 )
{
if ( GetTime() < behaviorTreeEntity.coverShootStartTime + 3000 )
{
return false;
}
}
}
if ( AiUtility::isSuppressedAtCoverCondition( behaviorTreeEntity ) )
{
return true;
}
if ( !behaviorTreeEntity IsAtGoal() )
{
// AI's that lean out of cover can fall outside of a very small goalradius, use the node's position instead.
if ( IsDefined( behaviorTreeEntity.node ) )
{
offsetOrigin = behaviorTreeEntity GetNodeOffsetPosition( behaviorTreeEntity.node );
return !behaviorTreeEntity IsPosAtGoal( offsetOrigin );
}
return true;
}
if ( !behaviorTreeEntity IsSafeFromGrenade() )
{
return true;
}
return false;
}
function private shouldAdjustToCover( behaviorTreeEntity )
{
if( !IsDefined( behaviorTreeEntity.node ) )
{
return false;
}
// if the current stance is crouch, and highest supported stance for the node is crouch too then,
// there are no animations for that. Just do a pure animation blend.
highestSupportedStance = AiUtility::getHighestNodeStance( behaviorTreeEntity.node );
currentStance = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_stance" );
if( currentStance == "crouch" && highestSupportedStance == "crouch" )
{
return false;
}
// if AI has just arrived at this cover node ( meaning previousCoverMode != COVER_ALERT_MODE )
// That means he needs to adjust to the cover for the first time
coverMode = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode" );
previousCoverMode = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_previous_cover_mode" );
if ( coverMode != "cover_alert" && previousCoverMode != "cover_alert" && !behaviorTreeEntity.keepClaimedNode )
{
return true;
}
// if somehow the AI is at the covernode with appropriate COVER_MODE, but unsupported stance, then
// let him adjust to the cover node.
if( !AiUtility::isStanceAllowedAtNode( currentStance, behaviorTreeEntity.node ) )
{
return true;
}
return false;
}
// ------- COVER - VANTAGE SHOOT BEHAVIOR -----------//
// TODO - Fix and enable vantage behavior.
function private shouldVantageAtCoverCondition( behaviorTreeEntity )
{
if( !IsDefined( behaviorTreeEntity.node ) ||
!IsDefined( behaviorTreeEntity.node.type ) ||
!IsDefined( behaviorTreeEntity.enemy) ||
!IsDefined( behaviorTreeEntity.enemy.origin ) )
{
return false;
}
yawToEnemyPosition = AiUtility::GetAimYawToEnemyFromNode( behaviorTreeEntity, behaviorTreeEntity.node, behaviorTreeEntity.enemy );
pitchToEnemyPosition = AiUtility::GetAimPitchToEnemyFromNode( behaviorTreeEntity, behaviorTreeEntity.node, behaviorTreeEntity.enemy );
aimLimitsForCover = behaviortreeentity GetAimLimitsFromEntry("cover_vantage");
legalAim = false;
// allow vantage aiming if our target is within the 25 to 85 degree arc in front of us and 3 or more feet below us
if ( yawToEnemyPosition < aimLimitsForCover["aim_left"] &&
yawToEnemyPosition > aimLimitsForCover["aim_right"] &&
pitchToEnemyPosition < 85.0 &&
pitchToEnemyPosition > 25.0 &&
( behaviorTreeEntity.node.origin[2] - behaviorTreeEntity.enemy.origin[2] ) >= ( 3 * 12 ) )
{
legalAim = true;
}
return legalAim;
}
function private supportsVantageCoverCondition( behaviorTreeEntity )
{
return false;
/*
coverMode = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, COVER_MODE );
if ( IsDefined( behaviorTreeEntity.node ) && IsDefined( coverMode ) && coverMode == COVER_ALERT_MODE )
{
if( NODE_COVER_CROUCH( behaviorTreeEntity.node ) || NODE_COVER_STAND( behaviorTreeEntity.node ) )
{
return true;
}
}
return false;
*/
}
function private coverVantageInitialize( behaviorTreeEntity, asmStateName )
{
AiUtility::keepClaimNode( behaviorTreeEntity );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode", "cover_vantage" );
}
// ------- COVER - BLINDFIRE SHOOT BEHAVIOR -----------//
function private coverBlindfireShootActionStart( behaviorTreeEntity, asmStateName )
{
AiUtility::keepClaimNode( behaviorTreeEntity );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode", "cover_blind" );
AiUtility::chooseCoverDirection( behaviorTreeEntity );
}
// ------- COVER - CHANGE STANCE BEHAVIOR -----------//
function private prepareToChangeStanceToStand( behaviorTreeEntity, asmStateName )
{
AiUtility::cleanupCoverMode( behaviorTreeEntity );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_desired_stance", "stand" );
}
function private cleanUpChangeStanceToStand( behaviorTreeEntity, asmStateName )
{
AiUtility::releaseClaimNode( behaviorTreeEntity );
behaviorTreeEntity.newEnemyReaction = false;
}
function private prepareToChangeStanceToCrouch( behaviorTreeEntity, asmStateName )
{
AiUtility::cleanupCoverMode( behaviorTreeEntity );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_desired_stance", "crouch" );
}
function private cleanUpChangeStanceToCrouch( behaviorTreeEntity, asmStateName )
{
AiUtility::releaseClaimNode( behaviorTreeEntity );
behaviorTreeEntity.newEnemyReaction = false;
}
// ------- COVER - ADJUST STANCE BEHAVIOR -----------//
function private prepareForAdjustToCover( behaviorTreeEntity, asmStateName )
{
AiUtility::keepClaimNode( behaviorTreeEntity );
highestSupportedStance = AiUtility::getHighestNodeStance( behaviorTreeEntity.node );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_desired_stance", highestSupportedStance );
}
// ------- COVER - CHANGE STANCE BEHAVIOR -----------
function private coverChangeStanceActionStart( behaviorTreeEntity, asmStateName )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_cover_mode", "cover_alert" );
AiUtility::keepClaimNode( behaviorTreeEntity );
switch ( Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_stance" ) )
{
case "stand":
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_desired_stance", "crouch" );
break;
case "crouch":
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_desired_stance", "stand" );
break;
}
}
function TEMP_get_arm_offset( behaviorTreeEntity, throwPosition )
{
stance = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_stance" );
arm_offset = undefined;
// ( forward/back, left/right, up/down )
if ( stance == "crouch" )
{
arm_offset = ( 13, -1, 56 );
}
else
{
arm_offset = ( 14, -3, 80 );
}
if( IsDefined( behaviorTreeEntity.node ) && behaviorTreeEntity IsAtCoverNodeStrict() )
{
if( (behaviorTreeEntity.node.type == "Cover Left") )
{
if ( stance == "crouch" )
{
arm_offset = ( -38, 15, 23 );
}
else
{
arm_offset = ( -45, 0, 40 );
}
}
else if( (behaviorTreeEntity.node.type == "Cover Right") )
{
if ( stance == "crouch" )
{
arm_offset = ( 46, 12, 26 );
}
else
{
arm_offset = ( 34, -21, 50 );
}
}
else if( (behaviorTreeEntity.node.type == "Cover Stand" || behaviorTreeEntity.node.type == "Conceal Stand") )
{
arm_offset = ( 10, 7, 77 );
}
else if( (behaviorTreeEntity.node.type == "Cover Crouch" || behaviorTreeEntity.node.type == "Cover Crouch Window" || behaviorTreeEntity.node.type == "Conceal Crouch" ) )
{
arm_offset = ( 19, 5, 60 );
}
else if( (behaviorTreeEntity.node.type == "Cover Pillar") )
{
leftOffset = undefined;
rightOffset = undefined;
if ( stance == "crouch" )
{
leftOffset = ( -20, 0, 35 );
rightOffset = ( 34, 6, 50 );
}
else
{
leftOffset = ( -24, 0, 76 );
rightOffset = ( 24, 0, 76 );
}
if( ( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 1024) == 1024))) )
{
arm_offset = rightOffset;
}
else if( ( (isdefined(behaviorTreeEntity.node.spawnflags)&&((behaviorTreeEntity.node.spawnflags & 2048) == 2048))) )
{
arm_offset = leftOffset;
}
else
{
yawToEnemyPosition = AngleClamp180( VectorToAngles( throwPosition - behaviorTreeEntity.node.origin )[1] - behaviorTreeEntity.node.angles[1] );
aimLimitsForDirectionRight = behaviortreeentity GetAimLimitsFromEntry( "pillar_right_lean" );
legalRightDirectionYaw = yawToEnemyPosition >= ( aimLimitsForDirectionRight["aim_right"] - 10 ) && yawToEnemyPosition <= 0;
if( legalRightDirectionYaw )
{
arm_offset = rightOffset;
}
else
{
arm_offset = leftOffset;
}
}
}
}
return arm_offset;
}
+185
View File
@@ -0,0 +1,185 @@
// COMMON AI SYSTEMS INCLUDES
#using scripts\shared\ai_shared;
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\archetype_utility;
// ADDITIONAL INCLUDES
Line truncated
function autoexec RegisterBehaviorScriptfunctions()
{
// ------- EXPOSED - CONDITIONS -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("hasCloseEnemy",&hasCloseEnemy);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("noCloseEnemyService",&noCloseEnemyService);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("tryReacquireService",&tryReacquireService);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("prepareToReactToEnemy",&prepareToReactToEnemy);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("resetReactionToEnemy",&resetReactionToEnemy);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("exposedSetDesiredStanceToStand",&exposedSetDesiredStanceToStand);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("setPathMoveDelayedRandom",&setPathMoveDelayedRandom);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("vengeanceService",&vengeanceService);;
}
// ------- EXPOSED REACT TO ENEMY -----------//
function private prepareToReactToEnemy( behaviorTreeEntity )
{
behaviorTreeEntity.newEnemyReaction = false;
behaviorTreeEntity.malFunctionReaction = false;
// Delay movement when surprised.
behaviorTreeEntity PathMode( "move delayed", true, 3 );
}
function private resetReactionToEnemy( behaviorTreeEntity )
{
behaviorTreeEntity.newEnemyReaction = false;
behaviorTreeEntity.malFunctionReaction = false;
}
// ------- EXPOSED CHARGE MELEE -----------//
function private noCloseEnemyService( behaviorTreeEntity )
{
if ( IsDefined( behaviorTreeEntity.enemy ) &&
AiUtility::hasCloseEnemyToMelee( behaviorTreeEntity ) )
{
behaviorTreeEntity ClearPath();
return true;
}
return false;
}
function private hasCloseEnemy( behaviorTreeEntity )
{
if( !IsDefined( behaviorTreeEntity.enemy ) )
return false;
if ( DistanceSquared( behaviorTreeEntity.origin, behaviorTreeEntity.enemy.origin ) < ( 150 * 150 ) )
return true;
return false;
}
function private _IsValidNeighbor( entity, neighbor )
{
return IsDefined( neighbor ) &&
entity.team === neighbor.team;
}
function private vengeanceService( entity )
{
actors = GetAiArray();
if ( !IsDefined( entity.attacker ) )
{
return;
}
foreach( index, ai in actors )
{
if ( _IsValidNeighbor( entity, ai ) &&
DistanceSquared( entity.origin, ai.origin ) <= ( (360) * (360) ) &&
RandomFloat( 1 ) >= 0.5 )
{
ai GetPerfectInfo( entity.attacker, true );
}
}
}
function private setPathMoveDelayedRandom( behaviorTreeEntity, asmStateName )
{
// Delay movement to prevent jittering.
behaviorTreeEntity PathMode( "move delayed", false, RandomFloatRange( 1, 3 ) );
}
function private exposedSetDesiredStanceToStand( behaviorTreeEntity, asmStateName )
{
AiUtility::keepClaimNode( behaviorTreeEntity );
currentStance = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_stance" );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_desired_stance", "stand" );
}
// ------- EXPOSED - REACQUIRE -----------//
function private tryReacquireService( behaviorTreeEntity )
{
if ( !IsDefined( behaviorTreeEntity.reacquire_state ) )
{
behaviorTreeEntity.reacquire_state = 0;
}
if ( !IsDefined( behaviorTreeEntity.enemy ) )
{
behaviorTreeEntity.reacquire_state = 0;
return false;
}
if ( behaviorTreeEntity HasPath() )
{
behaviorTreeEntity.reacquire_state = 0;
return false;
}
if ( behaviorTreeEntity SeeRecently( behaviorTreeEntity.enemy, 4 ) )
{
behaviorTreeEntity.reacquire_state = 0;
return false;
}
// don't do reacquire unless facing enemy
dirToEnemy = VectorNormalize( behaviorTreeEntity.enemy.origin - behaviorTreeEntity.origin );
forward = AnglesToForward( behaviorTreeEntity.angles );
if ( VectorDot( dirToEnemy, forward ) < 0.5 )
{
behaviorTreeEntity.reacquire_state = 0;
return false;
}
switch ( behaviorTreeEntity.reacquire_state )
{
case 0:
case 1:
case 2:
step_size = 32 + behaviorTreeEntity.reacquire_state * 32;
reacquirePos = behaviorTreeEntity ReacquireStep( step_size );
break;
case 4:
if ( !( behaviorTreeEntity CanSee( behaviorTreeEntity.enemy ) ) || !( behaviorTreeEntity CanShootEnemy() ) )
{
behaviorTreeEntity FlagEnemyUnattackable();
}
break;
default:
if ( behaviorTreeEntity.reacquire_state > 15 )
{
behaviorTreeEntity.reacquire_state = 0;
return false;
}
break;
}
if ( IsVec( reacquirePos ) )
{
behaviorTreeEntity UsePosition( reacquirePos );
return true;
}
behaviorTreeEntity.reacquire_state++;
return false;
}
+243
View File
@@ -0,0 +1,243 @@
#using scripts\shared\ai\archetype_human;
#using scripts\shared\ai\systems\ai_interface;
#namespace HumanInterface;
function RegisterHumanInterfaceAttributes()
{
/*
* Name: can_be_meleed
* Summary: Controls whether other AI will choose to melee the human.
* Initial Value: true
* Attribute true: Normal behavior, humans will be meleed when close to their enemy.
* Attribute false: Forces other AI to shoot the AI instead of melee them at
* close distances.
* Example: entity ai::set_behavior_attribute( "can_be_meleed", true );
*/
ai::RegisterMatchedInterface(
"human",
"can_be_meleed",
true,
array( true, false ) );
/*
* Name: can_melee
* Summary: Controls whether the human AI is able to melee their enemies.
* Initial Value: true
* Attribute true: Normal behavior, will melee when close to their enemy.
* Attribute false: Forced to shoot even when they are within melee distance.
* Example: entity ai::set_behavior_attribute( "can_melee", true );
*/
ai::RegisterMatchedInterface(
"human",
"can_melee",
true,
array( true, false ) );
/*
* Name: can_initiateaivsaimelee
* Summary: Controls whether the human AI are able to initiate synced melee with their enemies.
* Initial Value: true
* Attribute true: Normal behavior, will do synced melee when appropriate conditions are met.
* Attribute false: Will never initiate synced melee even if it is possible.
* Example: entity ai::set_behavior_attribute( "can_initiateaivsaimelee", true );
*/
ai::RegisterMatchedInterface(
"human",
"can_initiateaivsaimelee",
true,
array( true, false ) );
/*
* Name: coverIdleOnly
* Summary: Forces humans to only choose the idle behavior when at cover.
* Initial Value: false
* Attribute true : Forces a human to only idle at cover.
* Attribute false : Disables forcing idle.
* Example: entity ai::set_behavior_attribute( "coverIdleOnly", true );
*/
ai::RegisterMatchedInterface(
"human",
"coverIdleOnly",
false,
array( true, false ) );
/*
* Name: cqb
* Summary: Controls whether humans will use their close quarter battle
* animation set. CQB animations are tailored for indoor combat distances.
* NOTE: This is only supported for male human characters.
* Initial Value: false
* Attribute true : Forces a human to use cqb animations.
* Attribute false : Uses the normal animation set.
* Example: entity ai::set_behavior_attribute( "cqb", true );
*/
ai::RegisterMatchedInterface(
"human",
"cqb",
false,
array( true, false ),
&HumanSoldierServerUtils::cqbAttributeCallback );
/*
* Name: forceTacticalWalk
* Summary: Controls whether humans will forcefully tactical walk while moving.
* Initial Value: false
* Attribute true : Forces a human to tactical walk when moving.
* Attribute false : Disables forcing tactical walk.
* Example: entity ai::set_behavior_attribute( "forceTacticalWalk", true );
*/
ai::RegisterMatchedInterface(
"human",
"forceTacticalWalk",
false,
array( true, false ),
&HumanSoldierServerUtils::forceTacticalWalkCallback );
/*
* Name: move_mode
* Summary: Controls how humans choose to move to their goal position.
* Initial Value normal
* Attribute normal: Adheres to normal movement and combat.
* Attribute rambo: Runs to goal position, allowed to shoot at enemies, but doesn't stop and
* fight enemies.
* Example: entity ai::set_behavior_attribute( "move_mode", "rambo" );
*/
ai::RegisterMatchedInterface(
"human",
"move_mode",
"normal",
array( "normal", "rambo" ),
&HumanSoldierServerUtils::moveModeAttributeCallback );
/*
* Name: useAnimationOverride
* Summary: Controls whether humans will use their specific animation set
* This set is meant to be used for level specific animation override
* NOTE: This is only supported for male human characters.
* NOTE: Talk to Sumeet before using this attribute.
* Initial Value: false
* Attribute true : Forces a human to use specific animation set.
* Attribute false : Uses the normal animation set.
* Example: entity ai::set_behavior_attribute( "useAnimationOverride", true );
*/
ai::RegisterMatchedInterface(
"human",
"useAnimationOverride",
false,
array( true, false ),
&HumanSoldierServerUtils::UseAnimationOverrideCallback );
/*
* Name: sprint
* Summary: Controls whether humans will forcefully sprint while moving,
* this prevents shooting while moving.
* Initial Value: false
* Attribute true : Forces a human to sprint when moving.
* Attribute false : Disables forcing sprint.
* Example: entity ai::set_behavior_attribute( "sprint", true );
*/
ai::RegisterMatchedInterface(
"human",
"sprint",
false,
array( true, false ) );
/*
* Name: patrol
* Summary: Controls whether humans will use their patrol walking
* animation set. Patrol walks are meant for non-combat situations.
* NOTE: This is only supported for male human characters.
* Initial Value: false
* Attribute true : Non combat movement uses patrol walk animations.
* Attribute false : Non combat movement uses default animations.
* Example: entity ai::set_behavior_attribute( "patrol", true );
*/
ai::RegisterMatchedInterface(
"human",
"patrol",
false,
array( true, false ) );
/*
* Name: disablearrivals
* Summary: Controls whether humans will use their arrival transitions while moving
* NOTE: This is only supported for male human characters.
* Initial Value: false
* Attribute true : when set to true, AI's will not use arrival transitions.
* Attribute false : AI will use arrival transitions.
* Example: entity ai::set_behavior_attribute( "disablearrivals", true );
*/
ai::RegisterMatchedInterface(
"human",
"disablearrivals",
false,
array( true, false ) );
/*
* Name: disablesprint
* Summary: Controls whether humans will use their sprint locomotion.
* Initial Value: false
* Attribute true : when set to true, AI's will not use sprint locomotion.
* Attribute false : AI will use sprint locomotion when applicable.
* Example: entity ai::set_behavior_attribute( "disablesprint", true );
*/
ai::RegisterMatchedInterface(
"human",
"disablesprint",
false,
array( true, false ) );
/*
* Name: stealth
* Summary: Controls whether humans will use stealth behavior or not.
* Initial Value: false
* Attribute true : Using stealth behavior.
* Attribute false : Not using stealth behavior (default).
* Example: entity ai::set_behavior_attribute( "stealth", true );
*/
ai::RegisterMatchedInterface(
"human",
"stealth",
false,
array( true, false ) );
/*
* Name: vignette_mode
* Summary: Overrides the actor's default settings to handle controlled scripted
* situations. Normal AI in combat should not be placed in a vignette mode.
* Vignette modes change low level attributes on an AI, such as being able
* to push players, ignore avoidance with obstacles, and change steering behaviors.
* Initial Value: off
* Attribute off : Returns the AI to normal settings.
* Attribute slow : Configures the actor to handle slow paced vignettes prioritizing
* smooth movement while attempting to avoid other obstacles.
* Attribute fast : Configures the actor to handle fast paced vignettes prioritizing
* movement, with expectations that other AI will attempt to avoid them.
* Example: entity ai::set_behavior_attribute( "vignette", "slow" );
*/
ai::RegisterMatchedInterface(
"human",
"vignette_mode",
"off",
array( "off", "slow", "fast" ),
&HumanSoldierServerUtils::VignetteModeCallback );
/*
* Name: useGrenades
* Summary: Controls whether humans will use their a grenades.
* NOTE: This is only supported for human archetypes.
* Initial Value: true
* Attribute true : when set to true, AI's will throw grenades at enemies.
* Attribute false : AI will not throw grendades at enemies.
* Example: entity ai::set_behavior_attribute( "useGrenades", false );
*/
ai::RegisterMatchedInterface(
"human",
"useGrenades",
true,
array( true, false ) );
}
+865
View File
@@ -0,0 +1,865 @@
// COMMON AI SYSTEMS INCLUDES
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\behavior_state_machine;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\blackboard;
// ADDITIONAL INCLUDES
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai_shared;
#using scripts\shared\array_shared;
Line truncated
function autoexec RegisterBehaviorScriptfunctions()
{
// ------- PREPARE FOR MOVEMENT -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("prepareForMovement",&prepareForMovement);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("prepareForMovement",&prepareForMovement);;
// ------- TACTICAL WALK ARRIVE -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldTacticalArrive",&shouldTacticalArriveCondition);;
// ------- SPRINT -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("humanShouldSprint",&humanShouldSprint);;
// ------- ARRIVAL -----------//
BehaviorStateMachine::RegisterBSMScriptAPIInternal("planHumanArrivalAtCover",&planHumanArrivalAtCover);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("shouldPlanArrivalIntoCover",&shouldPlanArrivalIntoCover);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("shouldArriveExposed",&shouldArriveExposed);;
// ------- LOCOMOTION UPDATE -----------//
BehaviorStateMachine::RegisterBSMScriptAPIInternal("nonCombatLocomotionUpdate",&nonCombatLocomotionUpdate);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("combatLocomotionStart",&combatLocomotionStart);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("combatLocomotionUpdate",&combatLocomotionUpdate);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("humanNonCombatLocomotionCondition",&humanNonCombatLocomotionCondition);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("humanCombatLocomotionCondition",&humanCombatLocomotionCondition);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("shouldSwitchToTacticalWalkFromRun",&shouldSwitchToTacticalWalkFromRun);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("prepareToStopNearEnemy",&prepareToStopNearEnemy);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("prepareToStopNearEnemy",&prepareToStopNearEnemy);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("prepareToMoveAwayFromNearByEnemy",&prepareToMoveAwayFromNearByEnemy);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("shouldTacticalWalkPain",&shouldTacticalWalkPain);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("beginTacticalWalkPain",&beginTacticalWalkPain);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("shouldContinueTacticalWalkPain",&shouldContinueTacticalWalkPain);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("shouldTacticalWalkScan",&shouldTacticalWalkScan);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("continueTacticalWalkScan",&continueTacticalWalkScan);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("tacticalWalkScanTerminate",&tacticalWalkScanTerminate);;
// ------- PAIN -----------//
BehaviorStateMachine::RegisterBSMScriptAPIInternal("BSMLocomotionHasValidPainInterrupt",&BSMLocomotionHasValidPainInterrupt);; // only to be used by the BSM
}
// ------- PAIN -----------//
function private tacticalWalkScanTerminate( entity )
{
entity.lastTacticalScanTime = GetTime();
return true;
}
function private shouldTacticalWalkScan( entity )
{
if ( IsDefined( entity.lastTacticalScanTime ) &&
( entity.lastTacticalScanTime + 2000 ) > GetTime() )
{
return false;
}
if( !entity HasPath() )
{
return false;
}
if ( IsDefined( entity.enemy ) )
{
return false;
}
if ( entity ShouldFaceMotion() )
{
if ( ai::HasAiAttribute( entity, "forceTacticalWalk" ) &&
!ai::GetAiAttribute( entity, "forceTacticalWalk" ) )
{
return false;
}
}
animation = entity AsmGetCurrentDeltaAnimation();
// Scan animations can only be played at tactical walk animation boundaries.
if ( IsDefined( animation ) )
{
animTime = entity GetAnimTime( animation );
return animTime <= 0.05;
}
return false;
}
function private continueTacticalWalkScan( entity )
{
if( !entity HasPath() )
{
return false;
}
if ( IsDefined( entity.enemy ) )
{
return false;
}
if ( entity ShouldFaceMotion() )
{
if ( ai::HasAiAttribute( entity, "forceTacticalWalk" ) &&
!ai::GetAiAttribute( entity, "forceTacticalWalk" ) )
{
return false;
}
}
animation = entity AsmGetCurrentDeltaAnimation();
if ( IsDefined( animation ) )
{
animLength = GetAnimLength( animation );
animTime = entity GetAnimTime( animation ) * animLength;
normalizedTime = ( animTime + 0.2 ) / animLength;
return normalizedTime < 1.0;
}
return false;
}
function private shouldTacticalWalkPain( entity )
{
if ( ( !IsDefined( entity.startPainTime ) || ( entity.startPainTime + 3000 ) < GetTime() ) &&
RandomFloat( 1.0 ) > 0.25 )
{
return BSMLocomotionHasValidPainInterrupt( entity );
}
return false;
}
function private beginTacticalWalkPain( entity )
{
entity.startPainTime = GetTime();
return true;
}
function private shouldContinueTacticalWalkPain( entity )
{
return ( entity.startPainTime + 100 ) >= GetTime();
}
function private BSMLocomotionHasValidPainInterrupt( entity )
{
return ( entity HasValidInterrupt("pain") );
}
function private shouldArriveExposed( behaviorTreeEntity )
{
if( behaviorTreeEntity ai::get_behavior_attribute( "disablearrivals" ) )
return false;
if( behaviorTreeEntity HasPath() )
{
if( IsDefined( behaviorTreeEntity.node )
&& IsCoverNode( behaviorTreeEntity.node )
&& IsDefined( behaviorTreeEntity.pathGoalPos )
&& DistanceSquared( behaviorTreeEntity.pathGoalPos, behaviorTreeEntity GetNodeOffsetPosition( behaviorTreeEntity.node ) ) < 8
)
{
return false;
}
}
return true;
}
function private prepareToStopNearEnemy( behaviorTreeEntity )
{
behaviorTreeEntity ClearPath();
behaviorTreeEntity.keepClaimedNode = true;
}
function private prepareToMoveAwayFromNearByEnemy( behaviorTreeEntity )
{
behaviorTreeEntity ClearPath();
behaviorTreeEntity.keepClaimedNode = true;
}
function private shouldPlanArrivalIntoCover( behaviorTreeEntity )
{
goingToCoverNode = IsDefined( behaviorTreeEntity.node ) && IsCoverNode( behaviorTreeEntity.node );
if( !goingToCoverNode )
{
return false;
}
if( IsDefined( behaviorTreeEntity.pathGoalPos ) )
{
if( IsDefined( behaviorTreeEntity.arrivalfinalpos ) )
{
if( behaviorTreeEntity.arrivalfinalpos != behaviorTreeEntity.pathGoalPos )
{
return true;
}
else if ( behaviorTreeEntity.replannedCoverArrival === false &&
IsDefined( behaviorTreeEntity.exitPos ) &&
IsDefined( behaviorTreeEntity.predictedExitPos ) )
{
behaviorTreeEntity.replannedCoverArrival = true;
exitDir = VectorNormalize( behaviorTreeEntity.predictedExitPos - behaviorTreeEntity.exitPos );
currentDir = VectorNormalize( behaviorTreeEntity.origin - behaviorTreeEntity.exitPos );
// If the AI deviated more than 30 degrees from the exit direction we used
// for planning an arrival, force a replan. This can typically happen when
// an exit animation moves the AI out of cover in a different direction
// than we planned for.
// Without replanning the AI's path can change significantly enough that
// the original planned arrival will be aborted.
if ( VectorDot( exitDir, currentDir ) < Cos( 30 ) )
{
// Force the predicted arrival direction to be recalculated upon a replan.
behaviorTreeEntity.predictedArrivalDirectionValid = false;
return true;
}
}
}
}
return false;
}
function private shouldSwitchToTacticalWalkFromRun( behaviorTreeEntity )
{
if( !behaviorTreeEntity HasPath() )
{
return false;
}
if ( ai::HasAiAttribute( behaviorTreeEntity, "forceTacticalWalk" ) &&
ai::GetAiAttribute( behaviorTreeEntity, "forceTacticalWalk" ) )
{
return true;
}
goalPos = undefined;
if( IsDefined( behaviorTreeEntity.arrivalFinalPos ) )
{
goalPos = behaviorTreeEntity.arrivalFinalPos;
}
else
{
goalPos = behaviorTreeEntity.pathGoalPos;
}
// for moving short distances
if( Isdefined( behaviorTreeEntity.pathStartPos ) && Isdefined( goalPos ) )
{
pathDist = DistanceSquared( behaviorTreeEntity.pathStartPos, goalPos );
if( pathDist < ( (250) * (250) ) )
{
return true;
}
}
if( !behaviorTreeEntity ShouldFaceMotion() )
{
return true;
}
return false;
}
function private humanNonCombatLocomotionCondition( behaviorTreeEntity )
{
if( !behaviorTreeEntity HasPath() )
return false;
if( ( isdefined( behaviorTreeEntity.accurateFire ) && behaviorTreeEntity.accurateFire ) )
return true;
if( behaviorTreeEntity humanShouldSprint() )
return true;
if( IsDefined( behaviorTreeEntity.enemy ) )
return false;
return true;
}
function private humanCombatLocomotionCondition( behaviorTreeEntity )
{
if( !behaviorTreeEntity HasPath() )
return false;
// AI's like snipers will not fire when on the move.
if( ( isdefined( behaviorTreeEntity.accurateFire ) && behaviorTreeEntity.accurateFire ) )
return false;
if( behaviorTreeEntity humanShouldSprint() )
return false;
if( IsDefined( behaviorTreeEntity.enemy ) )
return true;
return false;
}
function private combatLocomotionStart( behaviorTreeEntity )
{
// Choose a runNGun variation.
randomChance = RandomInt( 100 );
if( randomChance > 50 )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_run_n_gun_variation", "variation_forward" );
return true;
}
if( randomChance > 25 )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_run_n_gun_variation", "variation_strafe_1" );
return true;
}
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_run_n_gun_variation", "variation_strafe_2" );
return true;
}
function private nonCombatLocomotionUpdate( behaviorTreeEntity )
{
if( !behaviorTreeEntity HasPath() )
return false;
// AI will go into CombatLocomotion, unless they are set to accurate fire or sprinting
if( IsDefined( behaviorTreeEntity.enemy ) && ( !( isdefined( behaviorTreeEntity.accurateFire ) && behaviorTreeEntity.accurateFire ) && !behaviorTreeEntity humanShouldSprint() ) )
return false;
if( !behaviorTreeEntity ASMIsTransitionRunning() )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_stance", "stand" );
if ( !IsDefined( behaviorTreeEntity.replannedCoverArrival ) )
{
// Force an arrival replan once the transition animation completes.
behaviorTreeEntity.replannedCoverArrival = false;
}
}
else
{
behaviorTreeEntity.replannedCoverArrival = undefined;
}
return true;
}
function private combatLocomotionUpdate( behaviorTreeEntity )
{
if( !behaviorTreeEntity HasPath() )
return false;
if( behaviorTreeEntity humanShouldSprint() )
return false;
if( !behaviorTreeEntity ASMIsTransitionRunning() )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_stance", "stand" );
if ( !IsDefined( behaviorTreeEntity.replannedCoverArrival ) )
{
// Force an arrival replan once the transition animation completes.
behaviorTreeEntity.replannedCoverArrival = false;
}
}
else
{
behaviorTreeEntity.replannedCoverArrival = undefined;
}
if( IsDefined( behaviorTreeEntity.enemy ) )
return true;
return false;
}
// ------- PREPARE FOR MOVEMENT -----------//
function private prepareForMovement( behaviorTreeEntity )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_stance", "stand" );
return true;
}
// ------- TACTICAL WALK ARRIVE -----------//
// Here's how the arrival angles look relative to the cover node x, with the direction
// numbers on the outside
// 7 8 9
//
// 45 0 315
//
// 4 90 x 270 6
//
// 135 180 225
//
// 1 2 3
function private isArrivingFour( arrivalAngle )
{
if ( arrivalAngle >= 45 && arrivalAngle <= 120 )
{
return true;
}
return false;
}
function private isArrivingOne( arrivalAngle )
{
if ( arrivalAngle >= 120 && arrivalAngle <= 165 )
{
return true;
}
return false;
}
function private isArrivingTwo( arrivalAngle )
{
if ( arrivalAngle >= 165 && arrivalAngle <= 195 )
{
return true;
}
return false;
}
function private isArrivingThree( arrivalAngle )
{
if ( arrivalAngle >= 195 && arrivalAngle <= 240 )
{
return true;
}
return false;
}
function private isArrivingSix( arrivalAngle )
{
if ( arrivalAngle >= 240 && arrivalAngle <= 315 )
{
return true;
}
return false;
}
// Here's how the facing angles look relative to the player x, with the direction
// numbers on the outside
// 7 8 9
//
// 45 0 -45
//
// 4 90 x -90 6
//
// 135 180/-180 -135
//
// 1 2 3
function private isFacingFour( facingAngle )
{
if ( facingAngle >= 45 && facingAngle <= 135 )
{
return true;
}
return false;
}
function private isFacingEight( facingAngle )
{
if ( facingAngle >= -45 && facingAngle <= 45 )
{
return true;
}
return false;
}
function private isFacingSeven( facingAngle )
{
if ( facingAngle >= 0 && facingAngle <= 90 )
{
return true;
}
return false;
}
function private isFacingSix( facingAngle )
{
if ( facingAngle >= -135 && facingAngle <= -45 )
{
return true;
}
return false;
}
function private isFacingNine( facingAngle )
{
if ( facingAngle >= -90 && facingAngle <= 0 )
{
return true;
}
return false;
}
function private shouldTacticalArriveCondition( behaviorTreeEntity )
{
// TEMP: Alden Higgins 7-30-2014 don't tactical arrive unless dvar is set
if ( getDvarInt("enableTacticalArrival") != 1 )
{
return false;
}
// Check if behaviorTreeEntity has a cover node as a goal
if ( !IsDefined( behaviorTreeEntity.node ) )
{
return false;
}
// TEMP: Alden Higgins 7-31-2014 Temporary check cover node type as new tactical arrival animations are added
if ( !(behaviorTreeEntity.node.type == "Cover Left")
// && !NODE_COVER_RIGHT( behaviorTreeEntity.node )
// && !NODE_COVER_STAND( behaviorTreeEntity.node )
// && !NODE_COVER_CROUCH( behaviorTreeEntity.node )
)
{
return false;
}
// TEMP: Alden Higgins 8-5-2014 Temporary check cover node stance as well
stance = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_arrival_stance" );
if ( stance != "stand" )
{
return false;
}
arrivalDistance = 35;
/#
// Can be used for testing purposes to see if an animation works better from a different distance
arrivalDvar = getDvarInt( "tacArrivalDistance" );
if ( arrivalDvar != 0 )
{
arrivalDistance = arrivalDvar;
}
#/
nodeOffsetPosition = behaviorTreeEntity GetNodeOffsetPosition( behaviorTreeEntity.node );
if ( Distance( nodeOffsetPosition, behaviorTreeEntity.origin ) > arrivalDistance ||
Distance( nodeOffsetPosition, behaviorTreeEntity.origin ) < 25 )
{
return false;
}
// If the player is entering from an angle less than minimumTacArrivalAngle degrees away from the cover node's forward angles
// then don't tactically arrive...here's a diagram where x is the cover node, the dashes are the cover's geo, and the angles
// are the differences from the node's forward that we are checking:
//
// 45 25 0 25 45
// 60 60
// -----
// 90 x 90
//
// 135 180 135
//
entityAngles = VectorToAngles( behaviorTreeEntity.origin - nodeOffsetPosition );
if ( abs( behaviorTreeEntity.node.angles[ 1 ] - entityAngles[ 1 ] ) < 60 )
{
return false;
}
tacticalFaceAngle = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_tactical_arrival_facing_yaw" );
arrivalAngle = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_locomotion_arrival_yaw" );
// Check and make sure the player is facing the right way depending on the the angle they arrive at in order to play a tactical arrival
if ( isArrivingFour( arrivalAngle ) )
{
if ( !isFacingSix( tacticalFaceAngle ) && !isFacingEight( tacticalFaceAngle ) && !isFacingFour( tacticalFaceAngle ) )
{
return false;
}
}
else if ( isArrivingOne( arrivalAngle ) )
{
if ( !isFacingNine( tacticalFaceAngle ) && !isFacingSeven( tacticalFaceAngle ) )
{
return false;
}
}
else if ( isArrivingTwo( arrivalAngle ) )
{
if ( !isFacingEight( tacticalFaceAngle ) )
{
return false;
}
}
else if ( isArrivingThree( arrivalAngle ) )
{
if ( !isFacingSeven( tacticalFaceAngle ) && !isFacingNine( tacticalFaceAngle ) )
{
return false;
}
}
else if ( isArrivingSix( arrivalAngle ) )
{
if ( !isFacingFour( tacticalFaceAngle ) && !isFacingEight( tacticalFaceAngle ) && !isFacingSix( tacticalFaceAngle ) )
{
return false;
}
}
else
{
return false;
}
return true;
}
// ------- SPRINT -----------//
function private humanShouldSprint()
{
currentLocoMovementType = Blackboard::GetBlackBoardAttribute( self, "_human_locomotion_movement_type" );
return ( currentLocoMovementType == "human_locomotion_movement_sprint" );
}
// ------- ARRIVAL PLANNER -----------//
function private planHumanArrivalAtCover( behaviorTreeEntity, arrivalAnim )
{
if( behaviorTreeEntity ai::get_behavior_attribute( "disablearrivals" ) )
return false;
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_desired_stance", "stand" );
if( !IsDefined( arrivalAnim ) )
return false;
if( IsDefined( behaviorTreeEntity.node ) && IsDefined( behaviorTreeEntity.pathGoalPos ) )
{
if( !IsCoverNode( behaviorTreeEntity.node ) )
{
return false;
}
nodeOffsetPosition = behaviorTreeEntity GetNodeOffsetPosition( behaviorTreeEntity.node );
if( nodeOffsetPosition != behaviorTreeEntity.pathGoalPos )
{
return false;
}
if( IsDefined( arrivalAnim ) )
{
isRight = (behaviorTreeEntity.node.type == "Cover Right");
splitTime = GetArrivalSplitTime( arrivalAnim, isRight );
isSplitArrival = ( splitTime < 1 );
nodeApproachYaw = behaviorTreeEntity GetNodeOffsetAngles( behaviorTreeEntity.node )[1];
angle = (0, nodeApproachYaw - GetAngleDelta( arrivalAnim ), 0);
if( isSplitArrival )
{
forwardDir = AnglesToForward( angle );
rightDir = AnglesToRight( angle );
// TODO(David Young 2-25-15): This assumes a blend out of 0.2 seconds.
animLength = GetAnimLength( arrivalAnim );
moveDelta = GetMoveDelta( arrivalAnim, 0, (animLength - 0.2) / animLength );
preMoveDelta = GetMoveDelta( arrivalAnim, 0, splitTime );
postMoveDelta = moveDelta - preMoveDelta;
// post delta
forward = VectorScale( forwardDir, postMoveDelta[0] );
right = VectorScale( rightDir, postMoveDelta[1] );
coverEnterPos = nodeOffsetPosition - forward + right;
postEnterPos = coverEnterPos;
// pre delta
forward = VectorScale( forwardDir, preMoveDelta[0] );
right = VectorScale( rightDir, preMoveDelta[1] );
coverEnterPos = coverEnterPos - forward + right;
/#
RecordLine( postEnterPos, nodeOffsetPosition, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity );
RecordLine( coverEnterPos, postEnterPos, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity );
#/
if( !behaviorTreeEntity MayMoveFromPointToPoint( postEnterPos, nodeOffsetPosition, true, false ) )
{
return false;
}
if( !behaviorTreeEntity MayMoveFromPointToPoint( coverEnterPos, postEnterPos, true, false ) )
{
return false;
}
}
else
{
forwardDir = AnglesToForward( angle );
rightDir = AnglesToRight( angle );
moveDeltaArray = GetMoveDelta( arrivalAnim );
forward = VectorScale( forwardDir, moveDeltaArray[0] );
right = VectorScale( rightDir, moveDeltaArray[1] );
coverEnterPos = nodeOffsetPosition - forward + right;
if( !behaviorTreeEntity MayMoveFromPointToPoint( coverEnterPos, nodeOffsetPosition, true, true ) )
{
return false;
}
}
if( !checkCoverArrivalConditions( coverEnterPos, nodeOffsetPosition ) )
{
return false;
}
// Make sure the arrival pos is clear
if( IsPointOnNavMesh( coverEnterPos, behaviorTreeEntity ) )
{
/# RecordCircle( coverEnterPos, 2, (1,0,0), "Script", behaviorTreeEntity ); #/
behaviorTreeEntity UsePosition( coverEnterPos, behaviorTreeEntity.pathGoalPos );
return true;
}
}
}
return false;
}
function private checkCoverArrivalConditions( coverEnterPos, coverPos )
{
distSqToNode = DistanceSquared( self.origin, coverPos );
distSqFromNodeToEnterPos = DistanceSquared( coverPos, coverEnterPos );
// check if the AI is enough distance away from the coverEnterPos
awayFromEnterPos = distSqToNode >= ( distSqFromNodeToEnterPos + 150 );
if( !awayFromEnterPos )
return false;
trace = GroundTrace( coverEnterPos + ( 0, 0, 72 ), coverEnterPos + ( 0, 0, -72 ), false, false, false );
// Make sure the arrival position is within a height tolerance to the cover position.
if ( IsDefined( trace[ "position" ] ) && Abs( trace[ "position" ][2] - coverPos[2] ) > 30 )
{
/#
if ( GetDvarInt( "ai_debugArrivals" ) )
{
RecordCircle( coverEnterPos, 1, (1,0,0), "Animscript" );
Record3DText( "Arrival Start Position", coverEnterPos, ( 1, 0, 0 ), "Animscript", undefined, 0.4 );
RecordCircle( trace[ "position" ], 1, (1,0,0), "Animscript" );
Record3DText( "Distance To Ground: " + Int( Abs( trace[ "position" ][2] - coverPos[2] ) ), trace[ "position" ] + ( 0, 0, 5 ), ( 1, 0, 0 ), "Animscript", undefined, 0.4 );
Record3DText( "Ground Position Below Height Tolerance of " + 30, trace[ "position" ], ( 1, 0, 0 ), "Animscript", undefined, 0.4 );
RecordLine( coverEnterPos, trace[ "position" ], (1,0,0), "Animscript" );
}
#/
return false;
}
return true;
}
// TODO - This function is very expensive,
// we need to have cover_split notetrack in all the arrival/exit animations wherever there is a split.
function private GetArrivalSplitTime( arrivalAnim, isright )
{
if ( !isdefined( level.animArrivalSplitTimes ) )
{
level.animArrivalSplitTimes = [];
}
if ( isdefined( level.animArrivalSplitTimes[arrivalAnim] ) )
{
return level.animArrivalSplitTimes[arrivalAnim];
}
bestsplit = -1;
if( AnimHasNotetrack( arrivalAnim, "cover_split" ) )
{
times = GetNotetrackTimes( arrivalAnim, "cover_split" );
Assert( times.size > 0 );
bestsplit = times[0];
}
else
{
// TODO(David Young 2-25-15): Hardcoded assumption of 0.2 blendout.
animLength = GetAnimLength( arrivalAnim );
normalizedLength = (animLength - 0.2) / animLength;
angleDelta = getAngleDelta( arrivalAnim, 0, normalizedLength );
fullDelta = getMoveDelta( arrivalAnim, 0, normalizedLength );
const numiter = 100;
bestvalue = -100000000;
for ( i = 0; i < numiter; i++ )
{
splitTime = 1.0 * i / (numiter - 1);
delta = getMoveDelta( arrivalAnim, 0, splitTime );
delta = DeltaRotate( fullDelta - delta, 180 - angleDelta );
if ( isright )
delta = ( delta[0], 0 - delta[1], delta[2] );
val = min( delta[0] - 32, delta[1] );
if ( val > bestvalue || bestsplit < 0 )
{
bestvalue = val;
bestsplit = splitTime;
}
}
}
level.animArrivalSplitTimes[arrivalAnim] = bestsplit;
return bestsplit;
}
function private DeltaRotate( delta, yaw )
{
cosine = cos( yaw );
sine = sin( yaw );
return ( delta[0] * cosine - delta[1] * sine, delta[1] * cosine + delta[0] * sine, 0);
}
+411
View File
@@ -0,0 +1,411 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\math_shared;
#using scripts\shared\spawner_shared;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\zombie_utility;
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\ai\systems\animation_state_machine_mocomp;
#using scripts\shared\ai\systems\animation_state_machine_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\systems\shared;
// INTERFACE
#using scripts\shared\ai\archetype_human_riotshield_interface;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\archetype_cover_utility;
#using scripts\shared\ai\archetype_locomotion_utility;
#using scripts\shared\ai\archetype_mocomps_utility;
Line truncated
function autoexec main()
{
// INIT BLACKBOARD
spawner::add_archetype_spawn_function( "human_riotshield", &HumanRiotshieldBehavior::ArchetypeHumanRiotshieldBlackboardInit );
// INIT RIOTSHIELD ON SPAWN
spawner::add_archetype_spawn_function( "human_riotshield", &HumanRiotshieldServerUtils::humanRiotshieldSpawnSetup );
HumanRiotshieldBehavior::RegisterBehaviorScriptFunctions();
HumanRiotshieldInterface::RegisterHumanRiotshieldInterfaceAttributes();
}
#namespace HumanRiotshieldBehavior;
function RegisterBehaviorScriptFunctions()
{
// ------- TACTICAL WALK -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("riotshieldShouldTacticalWalk",&riotshieldShouldTacticalWalk);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("riotshieldNonCombatLocomotionCondition",&riotshieldNonCombatLocomotionCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("unarmedWalkAction",&unarmedWalkActionStart);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("riotshieldTacticalWalkStart",&riotshieldTacticalWalkStart);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("riotshieldAdvanceOnEnemyService",&riotshieldAdvanceOnEnemyService);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("riotshieldShouldFlinch",&riotshieldShouldFlinch);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("riotshieldIncrementFlinchCount",&riotshieldIncrementFlinchCount);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("riotshieldClearFlinchCount",&riotshieldClearFlinchCount);;
// ------- UNARMED -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("riotshieldUnarmedTargetService",&riotshieldUnarmedTargetService);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("riotshieldUnarmedAdvanceOnEnemyService",&riotshieldUnarmedAdvanceOnEnemyService);;
}
function private ArchetypeHumanRiotshieldBlackboardInit()
{
entity = self;
// CREATE BLACKBOARD
Blackboard::CreateBlackBoardForEntity( entity );
// CREATE INTERFACE
ai::CreateInterfaceForEntity( entity );
// USE UTILITY BLACKBOARD
entity AiUtility::RegisterUtilityBlackboardAttributes();
// REGISTER ANIMSCRIPTED CALLBACK
self.___ArchetypeOnAnimscriptedCallback = &ArchetypeHumanRiotshieldOnAnimscriptedCallback;
// ENABLE DEBUGGING IN ODYSSEY
/#entity FinalizeTrackedBlackboardAttributes();#/;
Blackboard::RegisterBlackBoardAttribute(self,"_move_mode","normal",&riotshieldMoveMode); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_move_mode");#/ };
}
function private ArchetypeHumanRiotshieldOnAnimscriptedCallback( entity )
{
// UNREGISTER THE BLACKBOARD
entity.__blackboard = undefined;
// REREGISTER BLACKBOARD
entity ArchetypeHumanRiotshieldBlackboardInit();
}
function private riotshieldMoveMode()
{
entity = self;
if ( entity ai::get_behavior_attribute( "phalanx" ) )
{
return "marching";
}
return "normal";
}
function private riotshieldShouldFlinch( entity )
{
if ( entity HasPath() && entity ai::get_behavior_attribute( "phalanx" ))
{
// Always flinch when moving in a phalanx.
return true;
}
if ( entity.damagelocation != "riotshield" )
{
return false;
}
if ( entity.damagelocation == "riotshield" &&
entity.flinchCount >= 5 &&
( entity.lastFlinchTime + 1500 ) >= GetTime() )
{
return false;
}
return true;
}
function private riotshieldIncrementFlinchCount( entity )
{
entity.flinchCount++;
entity.lastFlinchTime = GetTime();
}
function private riotshieldClearFlinchCount( entity )
{
entity.lastFlinchTime = GetTime();
entity.flinchCount = 0;
}
function private riotshieldShouldTacticalWalk( behaviorTreeEntity )
{
// always tactical walk
return true;
}
function private riotshieldNonCombatLocomotionCondition( behaviorTreeEntity )
{
if ( IsDefined( behaviorTreeEntity.enemy ) )
{
if ( DistanceSquared( behaviorTreeEntity.origin, behaviorTreeEntity LastKnownPos( behaviorTreeEntity.enemy ) ) > 700 * 700 )
{
return true;
}
}
return false;
}
function private riotshieldAdvanceOnEnemyService( behaviorTreeEntity )
{
itsBeenAWhile = ( GetTime() > behaviorTreeEntity.nextFindBestCoverTime );
isAtScriptGoal = behaviorTreeEntity IsAtGoal();
tooLongAtNode = false;
if ( behaviorTreeEntity ai::get_behavior_attribute( "phalanx" ) )
{
return false;
}
if ( IsDefined( behaviorTreeEntity.chosenNode ) )
{
dist_sq = DistanceSquared( behaviorTreeEntity.origin, behaviorTreeEntity.chosenNode.origin );
if ( dist_sq < 16 * 16 )
{
if ( !IsDefined( behaviorTreeEntity.timeAtChosenNode ) )
{
behaviorTreeEntity.timeAtChosenNode = GetTime();
}
}
}
if ( IsDefined( behaviorTreeEntity.timeAtChosenNode ) )
{
if ( GetTime() - behaviorTreeEntity.timeAtChosenNode > behaviorTreeEntity.timeAtNodeMax )
{
tooLongAtNode = true;
behaviorTreeEntity.timeAtChosenNode = undefined;
}
}
shouldLookForBetterCover = itsBeenAWhile || !isAtScriptGoal || tooLongAtNode;
if ( shouldLookForBetterCover && IsDefined( behaviorTreeEntity.enemy ) )
{
closestRandomNode = undefined;
closestRandomNodes = behaviorTreeEntity FindBestCoverNodes( behaviorTreeEntity.goalradius, behaviorTreeEntity.goalpos );
foreach( node in closestRandomNodes )
{
if ( IsDefined( behaviorTreeEntity.chosenNode ) && behaviorTreeEntity.chosenNode == node )
{
continue;
}
if ( AiUtility::getCoverType( node ) == "cover_exposed" )
{
closestRandomNode = node;
break;
}
}
if ( !IsDefined( closestRandomNode ) )
{
closestRandomNode = closestRandomNodes[0];
}
if( IsDefined( closestRandomNode )
&& behaviorTreeEntity FindPath( behaviorTreeEntity.origin, closestRandomNode.origin, true, false )
)
{
AiUtility::ReleaseClaimNode( behaviorTreeEntity );
AiUtility::useCoverNodeWrapper( behaviorTreeEntity, closestRandomNode );
behaviorTreeEntity.chosenNode = closestRandomNode;
behaviorTreeEntity.timeAtNodeMax = RandomIntRange( behaviorTreeEntity.moveDelayMin, behaviorTreeEntity.moveDelayMax );
behaviorTreeEntity.timeAtChosenNode = undefined;
return true;
}
}
return false;
}
function private riotshieldTacticalWalkStart( behaviorTreeEntity )
{
AiUtility::resetCoverParameters( behaviorTreeEntity );
AiUtility::setCanBeFlanked( behaviorTreeEntity, false );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_stance", "stand" );
behaviorTreeEntity OrientMode( "face enemy" );
}
function private riotshieldUnarmedTargetService( behaviorTreeEntity )
{
// Don't change targets during melee
if ( !AiUtility::shouldMutexMelee( behaviorTreeEntity ) )
{
return false;
}
enemies = [];
ai = GetAiArray();
// Add AI's that are on different teams.
foreach( index, value in ai )
{
if ( value.team != behaviorTreeEntity.team && IsActor( value ) )
{
enemies[enemies.size] = value;
}
}
// Find the closest enemy based on distance.
if ( enemies.size > 0 )
{
closestEnemy = undefined;
closestEnemyDistance = 0;
for ( index = 0; index < enemies.size; index++ )
{
enemy = enemies[index];
enemyDistance = DistanceSquared( behaviorTreeEntity.origin, enemy.origin );
checkEnemy = false;
if ( enemyDistance > behaviorTreeEntity.goalradius * behaviorTreeEntity.goalradius )
{
continue;
}
if ( !IsDefined( enemy.targeted_by ) || enemy.targeted_by == behaviorTreeEntity )
{
checkEnemy = true;
}
else
{
targetDistance = DistanceSquared( enemy.targeted_by.origin, enemy.origin );
if ( enemyDistance < targetDistance )
{
checkEnemy = true;
}
}
if ( checkEnemy )
{
if ( !IsDefined( closestEnemy ) || enemyDistance < closestEnemyDistance )
{
closestEnemyDistance = enemyDistance;
closestEnemy = enemy;
}
}
}
if ( IsDefined( behaviorTreeEntity.favoriteenemy ) )
{
behaviorTreeEntity.favoriteenemy.targeted_by = undefined;
}
behaviorTreeEntity.favoriteenemy = closestEnemy;
if ( IsDefined( behaviorTreeEntity.favoriteenemy ) )
{
behaviorTreeEntity.favoriteenemy.targeted_by = behaviorTreeEntity;
}
return true;
}
return false;
}
function private riotshieldUnarmedAdvanceOnEnemyService( behaviorTreeEntity )
{
if ( GetTime() < behaviorTreeEntity.nextFindBestCoverTime )
{
return false;
}
if( IsDefined( behaviorTreeEntity.favoriteenemy ) )
{
/#
RecordLine( behaviorTreeEntity.favoriteenemy.origin, behaviorTreeEntity.origin, ( 1, .5, 0 ), "Animscript", behaviorTreeEntity );
#/
enemyDistance = DistanceSquared( behaviorTreeEntity.favoriteenemy.origin, behaviorTreeEntity.origin );
if ( enemyDistance < behaviorTreeEntity.goalradius * behaviorTreeEntity.goalradius )
{
behaviorTreeEntity UsePosition( behaviorTreeEntity.favoriteenemy.origin );
return true;
}
}
behaviorTreeEntity ClearUsePosition();
return false;
}
function private unarmedWalkActionStart( behaviorTreeEntity )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_stance", "stand" );
behaviorTreeEntity OrientMode( "face enemy" );
}
function private riotshieldKilledOverride(
inflictor, attacker, damage, meansOfDeath, weapon, dir, hitLoc, offsetTime )
{
entity = self;
aiutility::dropRiotshield( entity );
return damage;
}
function private riotshieldDamageOverride(
eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex, modelIndex )
{
entity = self;
if ( sHitLoc == "riotshield" )
{
riotshieldIncrementFlinchCount( entity );
entity.health += 1;
return 1;
}
if ( sWeapon.name == "incendiary_grenade" )
{
iDamage = entity.health;
}
return iDamage;
}
#namespace HumanRiotshieldServerUtils;
function humanRiotshieldSpawnSetup()
{
entity = self;
aiutility::attachRiotshield( entity, GetWeapon( "riotshield" ), "wpn_t7_shield_riot_world_lh", "tag_weapon_left" );
entity.moveDelayMin = 2500;
entity.moveDelayMax = 5000;
entity.ignoreRunAndGunDist = true;
AiUtility::AddAIOverrideDamageCallback( entity, &HumanRiotshieldBehavior::riotshieldDamageOverride );
AiUtility::AddAIOverrideKilledCallback( entity, &HumanRiotshieldBehavior::riotshieldKilledOverride );
HumanRiotshieldBehavior::riotshieldClearFlinchCount( entity );
}
// end #namespace HumanRiotshieldServerUtils;
+21
View File
@@ -0,0 +1,21 @@
// Number of bullets it will take to make the AI stagger when their riotshield is shot
// Amount of time that must pass before the flinch count is reset
@@ -0,0 +1,124 @@
#using scripts\shared\ai\archetype_human_riotshield;
#using scripts\shared\ai\systems\ai_interface;
#namespace HumanRiotshieldInterface;
function RegisterHumanRiotshieldInterfaceAttributes()
{
/*
* Name: can_be_meleed
* Summary: Controls whether other AI will choose to melee the human.
* Initial Value: true
* Attribute true: Normal behavior, humans will be meleed when close to their enemy.
* Attribute false: Forces other AI to shoot the AI instead of melee them at
* close distances.
* Example: entity ai::set_behavior_attribute( "can_be_meleed", true );"
*/
ai::RegisterMatchedInterface(
"human_riotshield",
"can_be_meleed",
true,
array( true, false ) );
/*
* Name: can_melee
* Summary: Controls whether the human AI is able to melee their enemies.
* Initial Value: true
* Attribute true: Normal behavior, will melee when close to their enemy.
* Attribute false: Forced to shoot even when they are within melee distance.
* Example: entity ai::set_behavior_attribute( "can_melee", true );"
*/
ai::RegisterMatchedInterface(
"human_riotshield",
"can_melee",
true,
array( true, false ) );
/*
* Name: can_initiateaivsaimelee
* Summary: Controls whether the human AI are able to initiate synced melee with their enemies.
* Initial Value: true
* Attribute true: Normal behavior, will do synced melee when appropriate conditions are met.
* Attribute false: Will never initiate synced melee even if it is possible.
* Example: entity ai::set_behavior_attribute( "can_initiateaivsaimelee", true );"
*/
ai::RegisterMatchedInterface(
"human_riotshield",
"can_initiateaivsaimelee",
true,
array( true, false ) );
/*
* Name: coverIdleOnly
* Summary: Forces humans to only choose the idle behavior when at cover.
* Initial Value: false
* Attribute true : Forces a human to only idle at cover.
* Attribute false : Disables forcing idle.
* Example: entity ai::set_behavior_attribute( "coverIdleOnly", true );"
*/
ai::RegisterMatchedInterface(
"human_riotshield",
"coverIdleOnly",
false,
array( true, false ) );
/*
* Name: phalanx
* Summary: Controls whether the riotshield is in a phalanx formation or not.
* This value should be left to the phalanx system to manipulate.
* Initial Value: false
* Attribute true: Riotshield is in a phalanx formation.
* Attribute false: Riotshield is not in a phalanx formation.
* Example: entity ai::set_behavior_attribute( "phalanx", true );"
*/
ai::RegisterMatchedInterface(
"human_riotshield",
"phalanx",
false,
array( true, false ) );
/*
* Name: phalanx_force_stance
* Summary: Forces a riotshield in a phalanx formation to a specific stance when stationary.
* This value should be left to the phalanx system to manipulate.
* Initial Value: normal
* Attribute normal: Uses the default stance.
* Attribute stand: Forces the riotshield to stand.
* Attribute crouch: Forces the riotshield to crouch.
* Example: entity ai::set_behavior_attribute( "phalanx_force_stance", "stand" );"
*/
ai::RegisterMatchedInterface(
"human_riotshield",
"phalanx_force_stance",
"normal",
array( "normal", "stand", "crouch" ) );
/*
* Name: sprint
* Summary: Controls whether humans will forcefully sprint while moving,
* this prevents shooting while moving.
* Initial Value: false
* Attribute true : Forces a human to sprint when moving.
* Attribute false : Disables forcing sprint.
* Example: entity ai::set_behavior_attribute( "sprint", true );"
*/
ai::RegisterMatchedInterface(
"human_riotshield",
"sprint",
false,
array( true, false ) );
/*
* Name: attack_mode
* Summary: Controls how human riot shields attack their enemies.
* Initial Value: normal
* Attribute normal: Adheres to normal combat.
* Attribute unarmed: Can't shoot at enemies, try to melee enemies within goal.
* Example: entity ai::set_behavior_attribute( "attack_mode", "unarmed" );"
*/
ai::RegisterMatchedInterface(
"human_riotshield",
"attack_mode",
"normal",
array( "normal", "unarmed" ) );
}
+81
View File
@@ -0,0 +1,81 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\math_shared;
#using scripts\shared\spawner_shared;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\zombie_utility;
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\ai\systems\animation_state_machine_mocomp;
#using scripts\shared\ai\systems\animation_state_machine_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\systems\shared;
// INTERFACE
#using scripts\shared\ai\archetype_human_rpg_interface;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\archetype_cover_utility;
#using scripts\shared\ai\archetype_locomotion_utility;
#using scripts\shared\ai\archetype_mocomps_utility;
Line truncated
function autoexec main()
{
// INIT BLACKBOARD
spawner::add_archetype_spawn_function( "human_rpg", &HumanRpgBehavior::ArchetypeHumanRpgBlackboardInit );
HumanRpgBehavior::RegisterBehaviorScriptFunctions();
HumanRpgInterface::RegisterHumanRpgInterfaceAttributes();
}
#namespace HumanRpgBehavior;
function RegisterBehaviorScriptFunctions()
{
}
function private ArchetypeHumanRpgBlackboardInit()
{
entity = self;
// CREATE BLACKBOARD
Blackboard::CreateBlackBoardForEntity( entity );
// CREATE INTERFACE
ai::CreateInterfaceForEntity( entity );
// USE UTILITY BLACKBOARD
entity AiUtility::RegisterUtilityBlackboardAttributes();
// REGISTER ANIMSCRIPTED CALLBACK
self.___ArchetypeOnAnimscriptedCallback = &ArchetypeHumanRpgOnAnimscriptedCallback;
// ENABLE DEBUGGING IN ODYSSEY
/#entity FinalizeTrackedBlackboardAttributes();#/;
// USE OVERRIDE ANIMATIONS TO START WITH FOR RPG AI
entity AsmChangeAnimMappingTable( 1 );
}
function private ArchetypeHumanRpgOnAnimscriptedCallback( entity )
{
// UNREGISTER THE BLACKBOARD
entity.__blackboard = undefined;
// REREGISTER BLACKBOARD
entity ArchetypeHumanRpgBlackboardInit();
}
@@ -0,0 +1,81 @@
#using scripts\shared\ai\archetype_human_rpg;
#using scripts\shared\ai\systems\ai_interface;
#namespace HumanRpgInterface;
function RegisterHumanRpgInterfaceAttributes()
{
/*
* Name: can_be_meleed
* Summary: Controls whether other AI will choose to melee the human.
* Initial Value: true
* Attribute true: Normal behavior, humans will be meleed when close to their enemy.
* Attribute false: Forces other AI to shoot the AI instead of melee them at
* close distances.
* Example: entity ai::set_behavior_attribute( "can_be_meleed", true );"
*/
ai::RegisterMatchedInterface(
"human_rpg",
"can_be_meleed",
true,
array( true, false ) );
/*
* Name: can_melee
* Summary: Controls whether the human AI is able to melee their enemies.
* Initial Value: true
* Attribute true: Normal behavior, will melee when close to their enemy.
* Attribute false: Forced to shoot even when they are within melee distance.
* Example: entity ai::set_behavior_attribute( "can_melee", true );"
*/
ai::RegisterMatchedInterface(
"human_rpg",
"can_melee",
true,
array( true, false ) );
/*
* Name: coverIdleOnly
* Summary: Forces humans to only choose the idle behavior when at cover.
* Initial Value: false
* Attribute true : Forces a human to only idle at cover.
* Attribute false : Disables forcing idle.
* Example: entity ai::set_behavior_attribute( "coverIdleOnly", true );"
*/
ai::RegisterMatchedInterface(
"human_rpg",
"coverIdleOnly",
false,
array( true, false ) );
/*
* Name: sprint
* Summary: Controls whether humans will forcefully sprint while moving,
* this prevents shooting while moving.
* Initial Value: false
* Attribute true : Forces a human to sprint when moving.
* Attribute false : Disables forcing sprint.
* Example: entity ai::set_behavior_attribute( "sprint", true );"
*/
ai::RegisterMatchedInterface(
"human_rpg",
"sprint",
false,
array( true, false ) );
/*
* Name: patrol
* Summary: Controls whether humans will use their patrol walking
* animation set. Patrol walks are meant for non-combat situations.
* NOTE: This is only supported for male human characters.
* Initial Value: false
* Attribute true : Non combat movement uses patrol walk animations.
* Attribute false : Non combat movement uses default animations.
* Example: entity ai::set_behavior_attribute( "patrol", true );"
*/
ai::RegisterMatchedInterface(
"human_rpg",
"patrol",
false,
array( true, false ) );
}
+561
View File
@@ -0,0 +1,561 @@
#using scripts\shared\ai_shared;
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\behavior_state_machine;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\archetype_cover_utility;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\math_shared;
Line truncated
#namespace AiUtility;
// Use this utility if AI moves around using pathfinding should not be archetype dependent at all
// SUMEET TODO - add cover specific blackboard variables here.
function autoexec RegisterBehaviorScriptFunctions()
{
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("locomotionBehaviorCondition",&locomotionBehaviorCondition);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("locomotionBehaviorCondition",&locomotionBehaviorCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("nonCombatLocomotionCondition",&nonCombatLocomotionCondition);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("setDesiredStanceForMovement",&setDesiredStanceForMovement);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("clearPathFromScript",&clearPathFromScript);;
// ------- PATROL -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("locomotionShouldPatrol",&locomotionShouldPatrol);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("locomotionShouldPatrol",&locomotionShouldPatrol);;
// ------- TACTICAL WALK -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldTacticalWalk",&AiUtility::shouldTacticalWalk);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("shouldTacticalWalk",&AiUtility::shouldTacticalWalk);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldAdjustStanceAtTacticalWalk",&shouldAdjustStanceAtTacticalWalk);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("adjustStanceToFaceEnemyInitialize",&adjustStanceToFaceEnemyInitialize);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("adjustStanceToFaceEnemyTerminate",&adjustStanceToFaceEnemyTerminate);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("tacticalWalkActionStart",&tacticalWalkActionStart);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("tacticalWalkActionStart",&tacticalWalkActionStart);;
// ------- ARRIVAL -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("clearArrivalPos",&clearArrivalPos);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("clearArrivalPos",&clearArrivalPos);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("shouldStartArrival",&shouldStartArrivalCondition);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("shouldStartArrival",&shouldStartArrivalCondition);;
// ------- TRAVERSAL -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("locomotionShouldTraverse",&locomotionShouldTraverse);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("locomotionShouldTraverse",&locomotionShouldTraverse);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeAction("traverseActionStart",&traverseActionStart,undefined,undefined);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("traverseSetup",&traverseSetup);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("disableRepath",&disableRepath);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("enableRepath",&enableRepath);;
// ------- JUKE -----------//
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("canJuke",&canJuke);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("chooseJukeDirection",&chooseJukeDirection);;
// ------- PAIN -----------//
BehaviorStateMachine::RegisterBSMScriptAPIInternal("locomotionPainBehaviorCondition",&locomotionPainBehaviorCondition);;
// ------- STAIRS -----------//
BehaviorStateMachine::RegisterBSMScriptAPIInternal("locomotionIsOnStairs",&locomotionIsOnStairs);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("locomotionShouldLoopOnStairs",&locomotionShouldLoopOnStairs);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("locomotionShouldSkipStairs",&locomotionShouldSkipStairs);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("locomotionStairsStart",&locomotionStairsStart);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("locomotionStairsEnd",&locomotionStairsEnd);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("delayMovement",&delayMovement);;
BehaviorStateMachine::RegisterBSMScriptAPIInternal("delayMovement",&delayMovement);;
}
// ------- STAIRS -----------//
function private locomotionIsOnStairs( behaviorTreeEntity )
{
startNode = behaviorTreeEntity.traverseStartNode;
if ( IsDefined( startNode ) && behaviorTreeEntity ShouldStartTraversal() )
{
if( IsDefined( startNode.animscript ) && IsSubStr( ToLower( startNode.animscript ), "stairs" ) )
return true;
}
return false;
}
function private locomotionShouldSkipStairs( behaviorTreeEntity )
{
assert( IsDefined( behaviorTreeEntity._stairsStartNode ) && IsDefined( behaviorTreeEntity._stairsEndNode ) );
numTotalSteps = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_staircase_num_total_steps" );
stepsSoFar = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_staircase_num_steps" );
direction = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_staircase_direction" );
if( direction != "staircase_up" )
{
return false;
}
numOutSteps = 2;
totalStepsWithoutOut = numTotalSteps - numOutSteps;
if( stepsSoFar >= ( totalStepsWithoutOut ) )
{
return false;
}
remainingSteps = totalStepsWithoutOut - stepsSoFar;
if( remainingSteps >=3 || remainingSteps >= 6 || remainingSteps >= 8 )
{
return true;
}
return false;
}
function private locomotionShouldLoopOnStairs( behaviorTreeEntity )
{
assert( IsDefined( behaviorTreeEntity._stairsStartNode ) && IsDefined( behaviorTreeEntity._stairsEndNode ) );
numTotalSteps = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_staircase_num_total_steps" );
stepsSoFar = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_staircase_num_steps" );
exitType = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_staircase_exit_type" );
direction = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_staircase_direction" );
numOutSteps = 2; // 2 steps out unless we are going up
if( direction == "staircase_up" )
{
switch( exitType )
{
case "staircase_up_exit_l_3_stairs":
case "staircase_up_exit_r_3_stairs":
numOutSteps = 3;
break;
case "staircase_up_exit_l_4_stairs":
case "staircase_up_exit_r_4_stairs":
numOutSteps = 4;
break;
}
}
if( stepsSoFar >= ( numTotalSteps - numOutSteps ) )
{
behaviorTreeEntity SetStairsExitTransform();
return false;
}
return true;
}
function private locomotionStairsStart( behaviorTreeEntity )
{
startNode = behaviorTreeEntity.traverseStartNode;
endNode = behaviorTreeEntity.traverseEndNode;
assert( IsDefined( startNode ) && IsDefined( endNode ) );
behaviorTreeEntity._stairsStartNode = startNode;
behaviorTreeEntity._stairsEndNode = endNode;
if( startNode.type == "Begin" )
direction = "staircase_down";
else
direction = "staircase_up";
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_staircase_type", behaviorTreeEntity._stairsStartNode.animscript );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_staircase_state", "staircase_start" );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_staircase_direction", direction );
numTotalSteps = undefined;
if( IsDefined( startNode.script_int ) )
{
numTotalSteps = int( endNode.script_int );
}
else if( IsDefined( endNode.script_int ) )
{
numTotalSteps = int( endNode.script_int );
}
// Set total number of steps
assert( IsDefined( numTotalSteps ) && IsInt( numTotalSteps ) && numTotalSteps > 0 );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_staircase_num_total_steps", numTotalSteps );
// so far, we have not taken any steps
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_staircase_num_steps", 0 );
exitType = undefined;
if ( direction == "staircase_up" )
{
switch( int( behaviorTreeEntity._stairsStartNode.script_int ) % 4 )
{
case 0: exitType = "staircase_up_exit_r_3_stairs"; break;
case 1: exitType = "staircase_up_exit_r_4_stairs"; break;
case 2: exitType = "staircase_up_exit_l_3_stairs"; break;
case 3: exitType = "staircase_up_exit_l_4_stairs"; break;
}
}
else
{
switch( int( behaviorTreeEntity._stairsStartNode.script_int ) % 2 )
{
case 0: exitType = "staircase_down_exit_l_2_stairs"; break;
case 1: exitType = "staircase_down_exit_r_2_stairs"; break;
}
}
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_staircase_exit_type", exitType );
return true;
}
function private locomotionStairLoopStart( behaviorTreeEntity )
{
assert( IsDefined( behaviorTreeEntity._stairsStartNode ) && IsDefined( behaviorTreeEntity._stairsEndNode ) );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_staircase_state", "staircase_loop" );
}
function private locomotionStairsEnd( behaviorTreeEntity )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_staircase_state", undefined );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_staircase_direction", undefined );
}
// ------- PAIN -----------//
function private locomotionPainBehaviorCondition( entity )
{
return ( entity HasPath() && entity HasValidInterrupt("pain") );
}
function clearPathFromScript( behaviorTreeEntity )
{
behaviorTreeEntity ClearPath();
}
function private nonCombatLocomotionCondition( behaviorTreeEntity )
{
if( !behaviorTreeEntity HasPath() )
return false;
if( ( isdefined( behaviorTreeEntity.accurateFire ) && behaviorTreeEntity.accurateFire ) )
return true;
if( IsDefined( behaviorTreeEntity.enemy ) )
return false;
return true;
}
function private combatLocomotionCondition( behaviorTreeEntity )
{
if( !behaviorTreeEntity HasPath() )
return false;
// AI's like snipers will not fire when on the move.
if( ( isdefined( behaviorTreeEntity.accurateFire ) && behaviorTreeEntity.accurateFire ) )
return false;
if( IsDefined( behaviorTreeEntity.enemy ) )
return true;
return false;
}
function locomotionBehaviorCondition( behaviorTreeEntity )
{
return behaviorTreeEntity HasPath();
}
function private setDesiredStanceForMovement( behaviorTreeEntity )
{
if( Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_stance" ) != "stand" )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_desired_stance", "stand" );
}
}
// ------- TRAVERSAL -----------//
function private locomotionShouldTraverse( behaviorTreeEntity )
{
startNode = behaviorTreeEntity.traverseStartNode;
if ( IsDefined( startNode ) && behaviorTreeEntity ShouldStartTraversal() )
{
return true;
}
return false;
}
function traverseSetup( behaviorTreeEntity )
{
// TODO(David Young 7-25-14): This is really weird that we have to set the stance before taking a traversal.
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_stance", "stand" );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_traversal_type", behaviorTreeEntity.traverseStartNode.animscript );
return true;
}
function traverseActionStart( behaviorTreeEntity, asmStateName )
{
traverseSetup( behaviorTreeEntity );
/#
// Assert if no animation is found for the given traversal.
animationResults = behaviorTreeEntity ASTSearch( IString( asmStateName ) );
assert( IsDefined( animationResults[ "animation" ] ),
behaviorTreeEntity.archetype
+ " does not support traversal of type "
+ behaviorTreeEntity.traverseStartNode.animscript
+ " \n@"
+ behaviorTreeEntity.traverseStartNode.origin
+ "\n"
);
#/
AnimationStateNetworkUtility::RequestState( behaviorTreeEntity, asmStateName );
return 5;
}
function private disableRepath( entity )
{
entity.disableRepath = true;
}
function private enableRepath( entity )
{
entity.disableRepath = false;
}
// ------- ARRIVAL BEHAVIOR -----------//
function shouldStartArrivalCondition( behaviorTreeEntity )
{
if( behaviorTreeEntity ShouldStartArrival() )
return true;
return false;
}
/*
///BehaviorUtilityDocBegin
"Name: ClearArrivalPos \n"
"Summary: Clear arrival planning to a cover.\n"
"MandatoryArg: \n"
"OptionalArg: \n"
"Module: Behavior \n"
///BehaviorUtilityDocEnd
*/
function clearArrivalPos( behaviorTreeEntity )
{
// TODO(David Young 7-13-15): Default to clearing the path, until new executables post and "isarrivalpending" is available to script.
if ( !IsDefined( behaviorTreeEntity.isarrivalpending ) || ( isdefined( behaviorTreeEntity.isarrivalpending ) && behaviorTreeEntity.isarrivalpending ) )
{
self ClearUsePosition();
}
return true;
}
function delayMovement( entity )
{
entity PathMode( "move delayed", false, RandomFloatRange( 1, 2 ) );
return true;
}
// ------- LOCOMOTION - TACTICAL WALK -----------//
function private shouldAdjustStanceAtTacticalWalk( behaviorTreeEntity )
{
stance = Blackboard::GetBlackBoardAttribute( behaviorTreeEntity, "_stance" );
if( stance != "stand" )
{
return true;
}
return false;
}
function private adjustStanceToFaceEnemyInitialize( behaviorTreeEntity )
{
// AI's standing up out of cover should not play a reaction.
behaviorTreeEntity.newEnemyReaction = false;
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_desired_stance", "stand" );
behaviorTreeEntity OrientMode( "face enemy" );
return true;
}
function private adjustStanceToFaceEnemyTerminate( behaviorTreeEntity )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_stance", "stand" );
}
function private tacticalWalkActionStart( behaviorTreeEntity )
{
AiUtility::clearArrivalPos( behaviorTreeEntity );
AiUtility::resetCoverParameters( behaviorTreeEntity );
AiUtility::setCanBeFlanked( behaviorTreeEntity, false );
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_stance", "stand" );
behaviorTreeEntity OrientMode( "face enemy" );
return true;
}
// ------- LOCOMOTION - JUKE -----------//
function private validJukeDirection( entity, entityNavMeshPosition, forwardOffset, lateralOffset )
{
jukeNavmeshThreshold = 6;
forwardPosition = entity.origin + lateralOffset + forwardOffset;
backwardPosition = entity.origin + lateralOffset - forwardOffset;
forwardPositionValid = IsPointOnNavMesh( forwardPosition, entity ) && TracePassedOnNavMesh( entity.origin, forwardPosition );
backwardPositionValid = IsPointOnNavMesh( backwardPosition, entity ) && TracePassedOnNavMesh( entity.origin, backwardPosition );
if ( !IsDefined( entity.ignoreBackwardPosition ) )
{
return forwardPositionValid && backwardPositionValid;
}
else
{
return forwardPositionValid;
}
return false;
}
function calculateJukeDirection( entity, entityRadius, jukeDistance )
{
jukeNavmeshThreshold = 6;
defaultDirection = "forward";
if ( IsDefined( entity.defaultJukeDirection ) )
{
defaultDirection = entity.defaultJukeDirection;
}
if ( IsDefined( entity.enemy ) )
{
navmeshPosition = GetClosestPointOnNavMesh( entity.origin, jukeNavmeshThreshold );
if ( !IsVec( navmeshPosition ) )
{
return defaultDirection;
}
vectorToEnemy = entity.enemy.origin - entity.origin;
vectorToEnemyAngles = VectorToAngles( vectorToEnemy );
forwardDistance = AnglesToForward( vectorToEnemyAngles ) * entityRadius;
rightJukeDistance = AnglesToRight( vectorToEnemyAngles ) * jukeDistance;
preferLeft = undefined;
if ( entity HasPath() )
{
// Juke closer to the path goal position.
rightPosition = entity.origin + rightJukeDistance;
leftPosition = entity.origin - rightJukeDistance;
preferLeft = DistanceSquared( leftPosition, entity.pathgoalpos ) <=
DistanceSquared( rightPosition, entity.pathgoalpos );
}
else
{
preferLeft = math::cointoss();
}
if ( preferLeft )
{
if ( validJukeDirection( entity, navmeshPosition, forwardDistance, -rightJukeDistance ) )
{
return "left";
}
else if ( validJukeDirection( entity, navmeshPosition, forwardDistance, rightJukeDistance ) )
{
return "right";
}
}
else
{
if ( validJukeDirection( entity, navmeshPosition, forwardDistance, rightJukeDistance ) )
{
return "right";
}
else if ( validJukeDirection( entity, navmeshPosition, forwardDistance, -rightJukeDistance ) )
{
return "left";
}
}
}
return defaultDirection;
}
function private calculateDefaultJukeDirection( entity )
{
jukeDistance = 30;
entityRadius = 15;
if ( IsDefined( entity.jukeDistance ) )
{
jukeDistance = entity.jukeDistance;
}
if ( IsDefined( entity.entityRadius ) )
{
entityRadius = entity.entityRadius;
}
return AiUtility::calculateJukeDirection( entity, entityRadius, jukeDistance );
}
function canJuke( entity )
{
// Don't juke if the enemy is too far away.
if (( isdefined( self.is_disabled ) && self.is_disabled ))
return false;
if ( IsDefined( entity.jukeMaxDistance ) && IsDefined( entity.enemy ) )
{
maxDistSquared = entity.jukeMaxDistance * entity.jukeMaxDistance;
if ( Distance2DSquared( entity.origin, entity.enemy.origin ) > maxDistSquared )
{
return false;
}
}
jukeDirection =
AiUtility::calculateDefaultJukeDirection( entity );
return jukeDirection != "forward";
}
function chooseJukeDirection( entity )
{
jukeDirection =
AiUtility::calculateDefaultJukeDirection( entity );
Blackboard::SetBlackBoardAttribute( entity, "_juke_direction", jukeDirection );
}
View File
Whitespace-only changes.
+98
View File
@@ -0,0 +1,98 @@
#using scripts\shared\spawner_shared;
#using scripts\shared\ai\zombie;
#using scripts\shared\ai\zombie_utility;
#using scripts\shared\ai\archetype_mannequin_interface;
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\ai\systems\behavior_tree_utility;
Line truncated
#namespace MannequinBehavior;
function autoexec init()
{
level.zm_variant_type_max = [];
level.zm_variant_type_max[ "walk" ] = [];
level.zm_variant_type_max[ "run" ] = [];
level.zm_variant_type_max[ "sprint" ] = [];
level.zm_variant_type_max[ "walk" ][ "down" ] = 14;
level.zm_variant_type_max[ "walk" ][ "up" ] = 16;
level.zm_variant_type_max[ "run" ][ "down" ] = 13;
level.zm_variant_type_max[ "run" ][ "up" ] = 12;
level.zm_variant_type_max[ "sprint" ][ "down" ] = 7;
level.zm_variant_type_max[ "sprint" ][ "up" ] = 6;
// INIT BLACKBOARD
spawner::add_archetype_spawn_function( "mannequin", &ZombieBehavior::ArchetypeZombieBlackboardInit );
spawner::add_archetype_spawn_function( "mannequin", &ZombieBehavior::ArchetypeZombieDeathOverrideInit );
// INIT ZOMBIE ON SPAWN
spawner::add_archetype_spawn_function( "mannequin", &zombie_utility::zombieSpawnSetup );
spawner::add_archetype_spawn_function( "mannequin", &mannequinSpawnSetup );
MannequinInterface::RegisterMannequinInterfaceAttributes();
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("mannequinCollisionService",&mannequinCollisionService);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("mannequinShouldMelee",&mannequinShouldMelee);;
}
function mannequinCollisionService( entity )
{
if ( IsDefined( entity.enemy ) &&
DistanceSquared( entity.origin, entity.enemy.origin ) > ( (300) * (300) ) )
{
// Allow clipping with other AI's at a distance, this helps when the AI's move diagonally into each other.
entity PushActors( false );
}
else
{
// Force AI's to push each other close to their enemy.
entity PushActors( true );
}
}
function mannequinSpawnSetup( entity )
{
}
function private mannequinShouldMelee( entity )
{
if( !IsDefined( entity.enemy ) )
{
return false;
}
if( IsDefined( entity.marked_for_death ) )
{
return false;
}
if( ( isdefined( entity.ignoreMelee ) && entity.ignoreMelee ) )
{
return false;
}
if( Distance2DSquared( entity.origin, entity.enemy.origin ) > ( (64) * (64) ) )
{
return false;
}
if( abs( entity.origin[2] - entity.enemy.origin[2] ) > 72 )
{
return false;
}
yawToEnemy = AngleClamp180( entity.angles[ 1 ] - (VectorToAngles(entity.enemy.origin-entity.origin)[1]) );
if( abs( yawToEnemy ) > 45 )
{
return false;
}
return true;
}
@@ -0,0 +1,49 @@
#using scripts\shared\ai\zombie;
#using scripts\shared\ai\systems\ai_interface;
#namespace MannequinInterface;
function RegisterMannequinInterfaceAttributes()
{
/*
* Name: can_juke
* Summary: Controls whether the zombie can juke.
* Initial Value: true
* Attribute true: Normal behavior, zombie will occasionally juke left or right.
* Attribute false: Disables zombie's ability to juke.
* Example: entity ai::set_behavior_attribute( "can_juke", true );"
*/
ai::RegisterMatchedInterface(
"mannequin",
"can_juke",
false,
array( true, false ) );
/*
* Name: suicidal_behavior
* Summary: Controls whether the zombie is going to act as suicidal.
* Initial Value: false
* Attribute true: Will enable the suicidal behavior.
* Attribute false: Disables suicidal behavior.
* Example: entity ai::set_behavior_attribute( "suicidal_behavior", true );"
*/
ai::RegisterMatchedInterface(
"mannequin",
"suicidal_behavior",
false,
array( true, false ) );
/*
* Name: spark_behavior
* Summary: Controls whether the zombie is going to act as spark zombie.
* Initial Value: false
* Attribute true: Will enable the spark behavior.
* Attribute false: Disables spark behavior.
* Example: entity ai::set_behavior_attribute( "spark_behavior", true );"
*/
ai::RegisterMatchedInterface(
"mannequin",
"spark_behavior",
false,
array( true, false ) );
}
+334
View File
@@ -0,0 +1,334 @@
#using scripts\shared\ai\systems\animation_state_machine_mocomp;
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\archetype_utility;
Line truncated
//*****************************************************************************
// NOTE! When adding a new motion compensator you must also declare the
// mocomp within the ast_definitions file found at:
//
// //t7/main/game/share/raw/animtables/ast_definitions.json
//
// You should add the new mocomp to the list of "possibleValues" for the
// "_animation_mocomp" animation selector table column.
//
// This allows the AI Editor to know about all possible mocomps that can be
// used.
//
//*****************************************************************************
function autoexec RegisterDefaultAnimationMocomps()
{
// mocomps have been moved into code now, although you can still add new ones here
AnimationStateNetwork::RegisterAnimationMocomp("adjust_to_cover",&mocompAdjustToCoverInit,&mocompAdjustToCoverUpdate,&mocompAdjustToCoverTerminate);;
AnimationStateNetwork::RegisterAnimationMocomp("locomotion_explosion_death",&mocompLocoExplosionInit,undefined,undefined);;
AnimationStateNetwork::RegisterAnimationMocomp("mocomp_flank_stand",&mocompFlankStandInit,undefined,undefined);;
}
function autoexec InitAdjustToCoverParams()
{
// These values represent the normalized time the motion compensator should start procedurally orienting toward the node's forward offset.
// Since only four adjust animations exist, a lot of care in choosing the correct normalized time has to be taken to cover the full 360 degree range.
// Adjust animations should end promptly as soon as the cover pose is reached. If additional "settle" frames exist at the end of the animation
// this will cause the adjust to cover mocomp to overshoot the desired angle.
// If a normalized time is set too low, this can cause animations to rotate in the wrong direction.
// 3 32 2 21 1
// 63 14
// 6 X 4
// 96 47
// 9 89 8 78 7
// directions 2 32 3 63 6 96 9 89 8 78 7 47 4 14 1 21
_AddAdjustToCover( "human", "cover_any", "stance_any", 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.9, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8 );
_AddAdjustToCover( "human", "cover_stand", "stance_any", 0.4, 0.8, 0.6, 0.4, 0.6, 0.3, 0.3, 0.6, 0.9, 0.6, 0.3, 0.4, 0.7, 0.6, 0.6, 0.6 );
_AddAdjustToCover( "human", "cover_crouch", "stance_any", 0.4, 0.4, 0.4, 0.4, 0.8, 0.5, 0.2, 0.7, 0.9, 0.4, 0.2, 0.4, 0.5, 0.5, 0.5, 0.5 );
_AddAdjustToCover( "human", "cover_left", "stand", 0.8, 0.4, 0.4, 0.4, 0.4, 0.7, 0.3, 0.5, 0.8, 0.8, 0.8, 0.9, 0.6, 0.6, 0.4, 0.4 );
_AddAdjustToCover( "human", "cover_left", "crouch", 0.8, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.8, 0.8, 0.7, 0.6, 0.6, 0.4, 0.4 );
_AddAdjustToCover( "human", "cover_right", "stand", 0.8, 0.4, 0.3, 0.4, 0.6, 0.8, 0.4, 0.4, 0.4, 0.4, 0.3, 0.4, 0.6, 0.6, 0.5, 0.4 );
_AddAdjustToCover( "human", "cover_right", "crouch", 0.8, 0.4, 0.2, 0.4, 0.4, 0.7, 0.2, 0.3, 0.3, 0.5, 0.5, 0.7, 0.6, 0.6, 0.5, 0.4 );
_AddAdjustToCover( "human", "cover_pillar", "stance_any", 0.8, 0.7, 0.6, 0.7, 0.6, 0.5, 0.4, 0.4, 0.4, 0.6, 0.4, 0.3, 0.7, 0.5, 0.1, 0.7 );
_AddAdjustToCover( "robot", "cover_any", "stance_any", 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.6, 0.7, 0.5, 0.5, 0.5, 0.5, 0.4, 0.4, 0.4 );
_AddAdjustToCover( "robot", "cover_exposed", "stance_any", 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.9, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8 );
}
function private _AddAdjustToCover(
archetype, node, stance, rot2, rot32, rot3, rot36, rot6, rot69, rot9, rot98, rot8, rot87, rot7, rot47, rot4, rot14, rot1, rot21 )
{
if ( !IsDefined( level.adjustToCover ) )
{
level.adjustToCover = [];
}
if ( !IsDefined( level.adjustToCover[ archetype ] ) )
{
level.adjustToCover[ archetype ] = [];
}
if ( !IsDefined( level.adjustToCover[ archetype ][ node ] ) )
{
level.adjustToCover[ archetype ][ node ] = [];
}
directions = [];
directions[ 2 ] = rot2;
directions[ 32 ] = rot32;
directions[ 3 ] = rot3;
directions[ 63 ] = rot36;
directions[ 6 ] = rot6;
directions[ 96 ] = rot69;
directions[ 9 ] = rot9;
directions[ 89 ] = rot98;
directions[ 8 ] = rot8;
directions[ 78 ] = rot87;
directions[ 7 ] = rot7;
directions[ 47 ] = rot47;
directions[ 4 ] = rot4;
directions[ 14 ] = rot14;
directions[ 1 ] = rot1;
directions[ 21 ] = rot21;
level.adjustToCover[ archetype ][ node ][ stance ] = directions;
}
// Angle to node within this angle [0, 360).
function private _GetAdjustToCoverRotation( archetype, node, stance, angleToNode )
{
assert( IsArray( level.adjustToCover[ archetype ] ) );
if ( !IsDefined( level.adjustToCover[ archetype ][ node ] ) )
{
node = "cover_any";
}
assert( IsArray( level.adjustToCover[ archetype ][ node ] ) );
if ( !IsDefined( level.adjustToCover[ archetype ][ node ][ stance ] ) )
{
stance = "stance_any";
}
assert( IsArray( level.adjustToCover[ archetype ][ node ][ stance ] ) );
assert( angleToNode >= 0 && angleToNode < 360 );
direction = undefined;
// Convert angles to a direction on the keypad.
// Direction 2 is forward, and 0 degrees.
// Angles are counter clockwise.
//
// 3 32 2 21 1
// 63 14
// 6 X 4
// 96 47
// 9 89 8 78 7
if ( angletonode < 11.25 )
direction = 2;
else if ( angletonode < 33.75 )
direction = 32;
else if ( angletonode < 56.25 )
direction = 3;
else if ( angletonode < 78.75 )
direction = 63;
else if ( angletonode < 101.25 )
direction = 6;
else if ( angletonode < 123.75 )
direction = 96;
else if ( angletonode < 146.25 )
direction = 9;
else if ( angletonode < 168.75 )
direction = 89;
else if ( angletonode < 191.25 )
direction = 8;
else if ( angletonode < 213.75 )
direction = 78;
else if ( angletonode < 236.25 )
direction = 7;
else if ( angletonode < 258.75 )
direction = 47;
else if ( angletonode < 281.25 )
direction = 4;
else if ( angletonode < 303.75 )
direction = 14;
else if ( angletonode < 326.25 )
direction = 1;
else if ( angletonode < 348.75 )
direction = 21;
else
direction = 2;
assert( IsDefined( level.adjustToCover[archetype][ node ][ stance ][ direction ] ) );
adjustTime = level.adjustToCover[archetype][ node ][ stance ][ direction ];
if ( IsDefined( adjustTime ) )
{
return adjustTime;
}
// Fallback in ship builds.
return 0.8;
}
function private debugLocoExplosion( entity )
{
entity endon( "death" );
/#
startOrigin = entity.origin;
startYawForward = AnglesToForward( ( 0, entity.angles[1], 0 ) );
damageYawForward = AnglesToForward( ( 0, entity.damageyaw - entity.angles[1], 0 ) );
startTime = GetTime();
while ( GetTime() - startTime < 10000 )
{
RecordSphere( startOrigin, 5, ( 1, 0, 0 ), "Animscript", entity );
RecordLine( startOrigin, startOrigin + startYawForward * 100, ( 0, 0, 1 ), "Animscript", entity );
RecordLine( startOrigin, startOrigin + damageYawForward * 100, ( 1, 0, 0 ), "Animscript", entity );
{wait(.05);};
}
#/
}
function private mocompFlankStandInit( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration )
{
entity AnimMode( "nogravity", false );
entity OrientMode( "face angle", entity.angles[1] );
entity PathMode( "move delayed", false, RandomFloatRange( 0.5, 1.0 ) );
if ( IsDefined( entity.enemy ) )
{
entity GetPerfectInfo( entity.enemy );
entity.newEnemyReaction = false;
}
}
function private mocompLocoExplosionInit( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration )
{
entity AnimMode( "nogravity", false );
entity OrientMode( "face angle", entity.angles[1] );
/#
if ( GetDvarInt( "ai_debugLocoExplosionMocomp" ) )
{
entity thread debugLocoExplosion( entity );
}
#/
}
function private mocompAdjustToCoverInit( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration )
{
entity OrientMode( "face angle", entity.angles[1] );
entity AnimMode( "angle deltas", false );
entity.blockingPain = true;
if ( IsDefined( entity.node ) )
{
entity.adjustNode = entity.node;
entity.nodeOffsetOrigin = entity GetNodeOffsetPosition( entity.node );
entity.nodeOffsetAngles = entity GetNodeOffsetAngles( entity.node );
entity.nodeOffsetForward = AnglesToForward( entity.nodeOffsetAngles );
entity.nodeForward = AnglesToForward( entity.node.angles );
entity.nodeFinalStance = Blackboard::GetBlackBoardAttribute( entity, "_desired_stance" );
coverType = Blackboard::GetBlackBoardAttribute( entity, "_cover_type" );
if ( !IsDefined( entity.nodeFinalStance ) )
{
entity.nodeFinalStance = AiUtility::getHighestNodeStance( entity.adjustNode );
}
// [0, 360) relative to the node.
angleDifference = Floor( AbsAngleClamp360( entity.angles[1] - entity.node.angles[1] ) );
entity.mocompAngleStartTime = _GetAdjustToCoverRotation( entity.archetype, coverType, entity.nodeFinalStance, angleDifference );
}
}
function private mocompAdjustToCoverUpdate( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration )
{
if( !IsDefined( entity.adjustNode ) )
{
return;
}
moveVector = entity.nodeOffsetOrigin - entity.origin;
if ( LengthSquared( moveVector ) > ( 1 * 1 ) )
{
// Scale the moveVector by the max move distance.
moveVector = VectorNormalize( moveVector ) * 1;
}
entity ForceTeleport( entity.origin + moveVector, entity.angles, false );
normalizedTime = ( ( entity GetAnimTime( mocompAnim ) * GetAnimLength( mocompAnim ) ) + mocompAnimBlendOutTime ) / mocompDuration;
if ( normalizedTime > entity.mocompAngleStartTime )
{
entity OrientMode( "face angle", entity.nodeOffsetAngles );
entity AnimMode( "normal", false );
}
/#
if ( GetDvarInt( "ai_debugAdjustMocomp" ) )
{
record3DText( entity.mocompAngleStartTime, entity.origin + (0, 0, 5), ( 0, 1, 0 ), "Animscript" );
hipTagOrigin = entity GetTagOrigin( "j_mainroot" );
recordLine( entity.nodeOffsetOrigin, entity.nodeOffsetOrigin + entity.nodeOffsetForward * 30, ( 1, .5, 0 ), "Animscript", entity );
recordLine( entity.adjustNode.origin, entity.adjustNode.origin + entity.nodeForward * 20, ( 0, 1, 0 ), "Animscript", entity );
recordLine( entity.origin, entity.origin + AnglesToForward( entity.angles ) * 10, ( 1, 0, 0 ), "Animscript", entity );
recordLine( hipTagOrigin, ( hipTagOrigin[0], hipTagOrigin[1], entity.origin[2] ), ( 0, 0, 1 ), "Animscript", entity );
}
#/
}
function private mocompAdjustToCoverTerminate( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration )
{
entity.blockingPain = false;
entity.mocompAngleStartTime = undefined;
entity.nodeOffsetAngle = undefined;
entity.nodeOffsetForward = undefined;
entity.nodeForward = undefined;
entity.nodeFinalStance = undefined;
if( entity.adjustNode !== entity.node )
{
entity.nodeOffsetOrigin = undefined;
entity.nodeOffsetAngles = undefined;
entity.adjustNode = undefined;
return;
}
entity ForceTeleport( entity.nodeOffsetOrigin, entity.nodeOffsetAngles, false );
entity.nodeOffsetOrigin = undefined;
entity.nodeOffsetAngles = undefined;
entity.adjustNode = undefined;
}
+51
View File
@@ -0,0 +1,51 @@
#using scripts\shared\ai_shared;
#using scripts\shared\util_shared;
Line truncated
#namespace Notetracks;
function autoexec main()
{
if ( SessionModeIsZombiesGame() && GetDvarInt( "splitscreen_playerCount" ) > 2 )
return;
if ( SessionModeIsCampaignDeadOpsGame() && GetDvarInt( "splitscreen_playerCount" ) > 2 )
return;
ai::add_ai_spawn_function( &InitializeNotetrackHandlers );
}
function private InitializeNotetrackHandlers( localClientNum )
{
// AI Footsteps defined by the surfacefxtable_footstep table are handled in code.
AddSurfaceNotetrackFXHandler( localClientNum, "jumping", "surfacefxtable_jumping" );
AddSurfaceNotetrackFXHandler( localClientNum, "landing", "surfacefxtable_landing" );
AddSurfaceNotetrackFXHandler( localClientNum, "vtol_landing", "surfacefxtable_vtollanding" );
}
function private AddSurfaceNotetrackFXHandler( localClientNum, notetrack, surfaceTable )
{
entity = self;
entity thread HandleSurfaceNotetrackFX( localClientNum, notetrack, surfaceTable );
}
function private HandleSurfaceNotetrackFX( localClientNum, notetrack, surfaceTable )
{
entity = self;
entity endon( "entityshutdown" );
while ( true )
{
entity waittill( notetrack );
fxName = entity GetAIFxName( localClientNum, surfaceTable );
if ( IsDefined( fxName ) )
{
PlayFx( localClientNum, fxName, entity.origin );
}
}
}
+232
View File
@@ -0,0 +1,232 @@
#using scripts\shared\ai\archetype_human_cover;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\systems\ai_blackboard;
#using scripts\shared\ai\systems\animation_state_machine_notetracks;
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\systems\gib;
#using scripts\shared\ai\systems\shared;
#using scripts\shared\ai_shared;
Line truncated
#namespace AnimationStateNetwork;
function autoexec RegisterDefaultNotetrackHandlerFunctions()
{
AnimationStateNetwork::RegisterNotetrackHandlerFunction("fire",&notetrackFireBullet);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("gib_disable",&notetrackGibDisable);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("gib = \"head\"",&GibServerUtils::GibHead);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("gib = \"arm_left\"",&GibServerUtils::GibLeftArm);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("gib = \"arm_right\"",&GibServerUtils::GibRightArm);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("gib = \"leg_left\"",&GibServerUtils::GibLeftLeg);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("gib = \"leg_right\"",&GibServerUtils::GibRightLeg);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("dropgun",&notetrackDropGun);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("gun drop",&notetrackDropGun);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("drop_shield",&notetrackDropShield);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("hide_weapon",&notetrackHideWeapon);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("show_weapon",&notetrackShowWeapon);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("hide_ai",&notetrackHideAI);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("show_ai",&notetrackShowAI);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("attach_knife",&notetrackAttachKnife);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("detach_knife",&notetrackDetachKnife);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("grenade_throw",&notetrackGrenadeThrow);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("start_ragdoll",&notetrackStartRagdoll);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("ragdoll_nodeath",&notetrackStartRagdollNoDeath);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("unsync",&notetrackMeleeUnsync);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("step1",&notetrackStaircaseStep1);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("step2",&notetrackStaircaseStep2);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("anim_movement = \"stop\"",&notetrackAnimMovementStop);;
AnimationStateNetwork::RegisterBlackboardNotetrackHandler("anim_pose = \"stand\"","_stance","stand");;
AnimationStateNetwork::RegisterBlackboardNotetrackHandler("anim_pose = \"crouch\"","_stance","crouch");;
AnimationStateNetwork::RegisterBlackboardNotetrackHandler("anim_pose = \"prone_front\"","_stance","prone_front");;
AnimationStateNetwork::RegisterBlackboardNotetrackHandler("anim_pose = \"prone_back\"","_stance","prone_back");;
}
function private notetrackAnimMovementStop( entity )
{
if( entity HasPath() )
{
entity PathMode( "move delayed", true, RandomFloatRange( 2, 4 ) );
}
}
function private notetrackStaircaseStep1( entity )
{
numSteps = Blackboard::GetBlackBoardAttribute( entity, "_staircase_num_steps" );
numSteps++;
Blackboard::SetBlackBoardAttribute( entity, "_staircase_num_steps", numSteps );
}
function private notetrackStaircaseStep2( entity )
{
numSteps = Blackboard::GetBlackBoardAttribute( entity, "_staircase_num_steps" );
numSteps += 2;
Blackboard::SetBlackBoardAttribute( entity, "_staircase_num_steps", numSteps );
}
function private notetrackDropGunInternal( entity )
{
if( entity.weapon == level.weaponNone )
return;
entity.lastWeapon = entity.weapon;
primaryweapon = entity.primaryweapon;
secondaryweapon = entity.secondaryweapon;
entity thread shared::DropAIWeapon();
}
// necessary for AI vs AI melees where soldiers need to stab each other
function private notetrackAttachKnife( entity )
{
if( !( isdefined( entity._ai_melee_attachedKnife ) && entity._ai_melee_attachedKnife ) )
{
entity Attach( "t6_wpn_knife_melee", "TAG_WEAPON_LEFT" );
entity._ai_melee_attachedKnife = true;
}
}
function private notetrackDetachKnife( entity )
{
if( ( isdefined( entity._ai_melee_attachedKnife ) && entity._ai_melee_attachedKnife ) )
{
entity Detach( "t6_wpn_knife_melee", "TAG_WEAPON_LEFT" );
entity._ai_melee_attachedKnife = false;
}
}
function private notetrackHideWeapon( entity )
{
entity ai::gun_remove();
}
function private notetrackShowWeapon( entity )
{
entity ai::gun_recall();
}
function private notetrackHideAI( entity )
{
entity Hide();
}
function private notetrackShowAI( entity )
{
entity Show();
}
function private notetrackStartRagdoll( entity )
{
if( IsActor( entity ) && entity IsInScriptedState() )
{
entity.overrideActorDamage = undefined;
entity.allowdeath = true;//This ignores/overrides the scene setting if set
entity.skipdeath = true;
entity Kill();
}
// SUMEET HACK - drop gun, if its not dropped already
notetrackDropGunInternal( entity );
entity StartRagdoll();
}
function _DelayedRagdoll( entity )
{
wait 0.25;
if ( IsDefined( entity ) && !entity IsRagdoll() )
{
entity StartRagdoll();
}
}
function notetrackStartRagdollNoDeath( entity )
{
if( IsDefined( entity._ai_melee_opponent ) )
{
entity._ai_melee_opponent Unlink();
}
// Delay ragdoll to let more of the animscripted to play out.
entity thread _DelayedRagdoll( entity );
}
function private notetrackFireBullet( animationEntity )
{
// Fire a MagicBullet in scripted animations
if ( IsActor( animationEntity ) && animationEntity IsInScriptedState() )
{
if( animationEntity.weapon != level.weaponNone )
{
animationEntity notify("about_to_shoot");
startPos = animationEntity GetTagOrigin( "tag_flash" );
endPos = startPos + VectorScale( animationEntity GetWeaponForwardDir(), 100 );
MagicBullet( animationEntity.weapon, startPos, endPos, animationEntity );
animationEntity notify("shoot");
animationEntity.bulletsInClip--;
}
}
}
function private notetrackDropGun( animationEntity )
{
notetrackDropGunInternal( animationEntity );
}
function private notetrackDropShield( animationEntity )
{
AiUtility::dropRiotshield( animationEntity );
}
function private notetrackGrenadeThrow( animationEntity )
{
if ( archetype_human_cover::shouldThrowGrenadeAtCoverCondition( animationEntity, true ) )
{
animationEntity GrenadeThrow();
}
else if ( IsDefined( animationEntity.grenadeThrowPosition ) )
{
// Fallback to throwing the grenade at the last valid position.
arm_offset = archetype_human_cover::TEMP_get_arm_offset( animationEntity, animationEntity.grenadeThrowPosition );
throw_vel = animationEntity CanThrowGrenadePos( arm_offset, animationEntity.grenadeThrowPosition );
if ( IsDefined( throw_vel ) )
{
animationEntity GrenadeThrow();
}
}
}
function private notetrackMeleeUnsync( animationEntity )
{
if( IsDefined( animationEntity ) && IsDefined( animationEntity.enemy ) )
{
if( ( isdefined( animationEntity.enemy._ai_melee_markedDead ) && animationEntity.enemy._ai_melee_markedDead ) )
{
animationEntity unlink();
}
}
}
function private notetrackGibDisable( animationEntity )
{
if ( animationEntity ai::has_behavior_attribute( "can_gib" ) )
{
animationEntity ai::set_behavior_attribute( "can_gib", false );
}
}
+253
View File
@@ -0,0 +1,253 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\duplicaterender_mgr;
#using scripts\shared\util_shared;
#using scripts\shared\ai\systems\fx_character;
#using scripts\shared\ai\systems\gib;
#using scripts\shared\system_shared;
Line truncated
#precache( "client_fx", "electric/fx_ability_elec_surge_short_robot" );
#precache( "client_fx", "explosions/fx_exp_robot_stage3_evb" );
function autoexec __init__sytem__() { system::register("robot",&__init__,undefined,undefined); }
function autoexec precache()
{
level._effect[ "fx_ability_elec_surge_short_robot" ] = "electric/fx_ability_elec_surge_short_robot";
level._effect[ "fx_exp_robot_stage3_evb" ] = "explosions/fx_exp_robot_stage3_evb";
}
function __init__()
{
if( ai::shouldRegisterClientFieldForArchetype( "robot" ) )
{
clientfield::register(
"actor",
"robot_mind_control",
1,
2,
"int",
&RobotClientUtils::robotMindControlHandler,
!true,
true);
clientfield::register(
"actor",
"robot_mind_control_explosion",
1,
1,
"int",
&RobotClientUtils::robotMindControlExplosionHandler,
!true,
!true);
clientfield::register(
"actor",
"robot_lights",
1,
3,
"int",
&RobotClientUtils::robotLightsHandler,
!true,
!true);
clientfield::register(
"actor",
"robot_EMP",
1,
1,
"int",
&RobotClientUtils::robotEmpHandler,
!true,
!true);
}
ai::add_archetype_spawn_function( "robot", &RobotClientUtils::robotSoldierSpawnSetup );
}
#namespace RobotClientUtils;
function private robotSoldierSpawnSetup( localClientNum )
{
entity = self;
}
function private robotLighting( localClientNum, entity, flicker, mindControlState )
{
switch ( mindControlState )
{
case 0:
entity TmodeClearFlag( 0 );
if ( flicker )
{
FxClientUtils::PlayFxBundle( localClientNum, entity, entity.altfxdef3 );
}
else
{
FxClientUtils::PlayFxBundle( localClientNum, entity, entity.fxdef );
}
break;
case 1:
entity TmodeClearFlag( 0 );
FxClientUtils::StopAllFXBundles( localClientNum, entity );
if ( flicker )
{
FxClientUtils::PlayFxBundle( localClientNum, entity, entity.altfxdef4 );
}
else
{
FxClientUtils::PlayFxBundle( localClientNum, entity, entity.altfxdef1 );
}
if ( !GibClientUtils::IsGibbed( localClientNum, entity, 8 ) )
{
entity PlaySound(localClientNum, "fly_bot_ctrl_lvl_01_start", entity.origin);
}
break;
case 2:
entity TmodeSetFlag( 0 );
FxClientUtils::StopAllFXBundles( localClientNum, entity );
if ( flicker )
{
FxClientUtils::PlayFxBundle( localClientNum, entity, entity.altfxdef4 );
}
else
{
FxClientUtils::PlayFxBundle( localClientNum, entity, entity.altfxdef1 );
}
if ( !GibClientUtils::IsGibbed( localClientNum, entity, 8 ) )
{
entity PlaySound(localClientNum, "fly_bot_ctrl_lvl_02_start", entity.origin);
}
break;
case 3:
entity TmodeSetFlag( 0 );
FxClientUtils::StopAllFXBundles( localClientNum, entity );
if ( flicker )
{
FxClientUtils::PlayFxBundle( localClientNum, entity, entity.altfxdef5 );
}
else
{
FxClientUtils::PlayFxBundle( localClientNum, entity, entity.altfxdef2 );
}
entity PlaySound(localClientNum, "fly_bot_ctrl_lvl_03_start", entity.origin);
break;
}
}
function private robotLightsHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( !IsDefined( entity ) ||
!entity IsAI() ||
( IsDefined( entity.archetype ) && entity.archetype != "robot" ) )
{
return;
}
FxClientUtils::StopAllFXBundles( localClientNum, entity );
flicker = newValue == 1;
if ( newValue == 0 || newValue == 3 || flicker )
{
robotLighting( localClientNum, entity, flicker, clientfield::get( "robot_mind_control" ) );
}
else if ( newValue == 4 )
{
FxClientUtils::PlayFxBundle( localClientNum, entity, entity.deathfxdef );
}
}
function private robotEmpHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( !IsDefined( entity ) ||
!entity IsAI() ||
( IsDefined( entity.archetype ) && entity.archetype != "robot" ) )
{
return;
}
if ( IsDefined( entity.empFX ) )
{
StopFx( localClientNum, entity.empFX );
}
switch ( newValue )
{
case 0:
break;
case 1:
entity.empFX = PlayFxOnTag(
localClientNum,
level._effect[ "fx_ability_elec_surge_short_robot" ],
entity,
"j_spine4" );
break;
}
}
function private robotMindControlHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( !IsDefined( entity ) ||
!entity IsAI() ||
( IsDefined( entity.archetype ) && entity.archetype != "robot" ) )
{
return;
}
lights = clientfield::get( "robot_lights" );
flicker = lights == 1;
if ( lights == 0 || flicker )
{
robotLighting( localClientNum, entity, flicker, newValue );
}
}
function robotMindControlExplosionHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( !IsDefined( entity ) ||
!entity IsAI() ||
( IsDefined( entity.archetype ) && entity.archetype != "robot" ) )
{
return;
}
switch ( newValue )
{
case 1:
entity.explosionFx =
PlayFxOnTag(
localClientNum,
level._effect[ "fx_exp_robot_stage3_evb" ],
entity,
"j_spineupper" );
break;
}
}
File diff suppressed because it is too large. Load diff
+214
View File
@@ -0,0 +1,214 @@
///////////////////////////////////////////////////////////////////////////////
//
// Tweakables
//
///////////////////////////////////////////////////////////////////////////////
// Chance that a destructible piece will gib off when the robot is
// killed, regardless of hitlocation, this applies to all pieces. (0, 1)
// Maximum number of destructible pieces that will randomly gib off
// when the robot is killed.
// Percentage of health before robot arms and head can be gibbed off.
// Valid float range (0, 1)
// Chance the robot's head will gib off when killed with a headshot.
// Valid float range (0, 1)
// Chance the robot's head will gib off when damaged with an explosion.
// Valid float range (0, 1)
// Chance the robot's head will gib off when dealth a fatal blow.
// Percentage of health before the robots leg can be gibbed off. (0, 1)
// Chance the robot's legs will gib off when killed with a leg shot.
// Valid float range (0, 1)
// Chance the robot's legs will gib off randomly when killed (0, 1)
// Chance the robot's legs will gib off when killed with an explosion.
// Valid float range (0, 1)
// Chance the robot's limb will gib off when killed with an explosion. (0, 1)
// Chance the robot's limb will gib off when killed with a limb shot. (0, 10
// Chance the robot will choose a cover position over an exposed position.
// Minimum amount of time in seconds robots will step out of cover for.
// Maximum amount of time in seconds robots will step out of cover for.
// Damage modifier when robots are shot in the head.
// Damage modifier when robots are shot in the back.
// Time in milliseconds robots will remain exposed after being shot when popping out of cover.
// Robot must be within this distance to their enemy to cause them to juke.
// Robot must be within this distance to their enemy to preemptive juke
// Robot must be within this distance to their attacker to become a crawler.
// Robots must be at least this distance away from their enemy to become a rusher.
// Robots within this distance may turn into rushers, if their fellow robot dies.
// Robots' whose enemy is further than this distance will not be considered for rushing.
// Robots will walk to positions even within sprint range if the path is shorter than this.
// Robots will start to super sprint outside this distance to their target.
// Amount of time robots will stay inactive after being damaged by an emp.
///////////////////////////////////////////////////////////////////////////////
//
// Robot Grunt Defines
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Clientfields
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// FX
//
///////////////////////////////////////////////////////////////////////////////
+397
View File
@@ -0,0 +1,397 @@
#using scripts\shared\ai\archetype_robot;
#using scripts\shared\ai\systems\ai_interface;
#namespace RobotInterface;
function RegisterRobotInterfaceAttributes()
{
/*
* Name: can_be_meleed
* Summary: Controls whether other AI will choose to melee the robot.
* Initial Value: true
* Attribute true: Normal behavior, robots will be meleed when close to their enemy.
* Attribute false: Forces other AI to shoot the robot instead of melee them at
* close distances.
* Example: entity ai::set_behavior_attribute( "can_be_meleed", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"can_be_meleed",
true,
array( true, false ) );
/*
* Name: can_become_crawler
* Summary: Controls whether the robot can become a crawler robot. This does not prevent a
* robot from having their legs gibbed upon death.
* Initial Value: true
* Attribute true: Normal behavior, robot can become a crawler.
* Attribute false: Robots will not become a crawler.
* Example: entity ai::set_behavior_attribute( "can_become_crawler", false );"
*/
ai::RegisterMatchedInterface(
"robot",
"can_become_crawler",
true,
array( true, false ) );
/*
* Name: can_become_rusher
* Summary: Controls whether the robot can become a rusher, even when told to become a rusher.
* Initial Value: true
* Attribute true: Normal behavior, robot can become a rusher.
* Attribute false: Robots will not become a rusher, even when told to become a rusher.
* Example: entity ai::set_behavior_attribute( "can_become_rusher", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"can_become_rusher",
true,
array( true, false ) );
/*
* Name: can_gib
* Summary: Controls whether the robot can gib at all. This should not be turned off in normal gameplay and is needed for cybercom.
* Initial Value: true
* Attribute true: Normal behavior, robot can gib.
* Attribute false: Robots will not gib, unless gibbing is explicitly called on them.
* Example: entity ai::set_behavior_attribute( "can_gib", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"can_gib",
true,
array( true, false ) );
/*
* Name: can_melee
* Summary: Controls whether the robot is able to melee their enemies. This only pertains
* to normal shooting robots.
* Initial Value: true
* Attribute true: Normal behavior, robot will melee when close to their enemy.
* Attribute false: Forces a robot to shoot even when they are within melee distance.
* Example: entity ai::set_behavior_attribute( "can_melee", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"can_melee",
true,
array( true, false ) );
/*
* Name: can_initiateaivsaimelee
* Summary: Controls whether the robot AI are able to initiate synced melee with their enemies.
* Initial Value: true
* Attribute true: Normal behavior, will do synced melee when appropriate conditions are met.
* Attribute false: Will never initiate synced melee even if it is possible.
* Example: entity ai::set_behavior_attribute( "can_initiateaivsaimelee", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"can_initiateaivsaimelee",
true,
array( true, false ) );
/*
* Name: disablesprint
* Summary: Controls whether humans will use their sprint locomotion.
* Initial Value: false
* Attribute true : when set to true, AI's will not use sprint locomotion.
* Attribute false : AI will use sprint locomotion when applicable.
* Example: entity ai::set_behavior_attribute( "disablesprint", true );
*/
ai::RegisterMatchedInterface(
"robot",
"disablesprint",
false,
array( true, false ) );
/*
* Name: escort_position
* Summary: If the robot is in the escort move_mode, this position is used to determine where
* the robot should stay close to.
* Initial Value: undefined
* Attribute: Vector position.
* Example: entity ai::set_behavior_attribute( "escort_position", player.origin );"
*/
ai::RegisterVectorInterface(
"robot",
"escort_position" );
/*
* Name: force_cover
* Summary: Controls whether robots will forcefully go to cover positions instead
* of a mixture of cover and exposed positions.
* Initial Value: false
* Attribute true: Forces a robot to move to cover positions.
* Attribute false: Disables forcing cover positions.
* Example: entity ai::set_behavior_attribute( "force_cover", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"force_cover",
false,
array( true, false ) );
/*
* Name: force_crawler
* Summary: Converts a robot into a crawler, either by gibbing their legs immediately
* or making their legs disappear.
* Initial Value: normal
* Attribute normal: A normal walking robot, crawlers cannot be turned back into normal
* robots.
* Attribute gib_legs: Immediately gibs the legs of a robot creating a crawler.
* Attribute remove_legs: Makes the legs of the robot disappear, useful for spawning.
* Example: entity ai::set_behavior_attribute( "force_crawler", "remove_legs" );"
*/
ai::RegisterMatchedInterface(
"robot",
"force_crawler",
"normal",
array( "normal", "gib_legs", "remove_legs" ),
&RobotSoldierServerUtils::robotForceCrawler );
/*
* Name: move_mode
* Summary: Controls how robots choose to move to their goal position.
* Initial Value normal
* Attribute escort: Forces a robot to move toward their designated escort_position.
* Attribute guard: Randomly chooses positions to move to within the current goal radius.
* Attribute normal: Adheres to normal movement and combat.
* Attribute marching: Walks using a slow forward moving march. Marching robots can only
* shoot targets that are facing in front of them.
* Attribute rambo: Runs to goal position, allowed to shoot at enemies, but doesn't stop and
* fight enemies.
* Attribute rusher: Moves toward the player instead of staying at cover or in the open.
* Attribute squadmember: Indicates that the robot belongs to a squad.
* Example: entity ai::set_behavior_attribute( "move_mode", "rambo" );"
*/
ai::RegisterMatchedInterface(
"robot",
"move_mode",
"normal",
array( "escort", "guard", "normal", "marching", "rambo", "rusher", "squadmember" ),
&RobotSoldierServerUtils::robotMoveModeAttributeCallback );
/*
* Name: phalanx
* Summary: Controls whether the robot is in a phalanx formation or not.
* This value should be left to the robot_phalanx system to manipulate.
* Initial Value: false
* Attribute true: Robot is in a phalanx formation.
* Attribute false: Robot is not in a phalanx formation.
* Example: entity ai::set_behavior_attribute( "phalanx", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"phalanx",
false,
array( true, false ) );
/*
* Name: phalanx_force_stance
* Summary: Forces a robot in a phalanx formation to a specific stance when stationary.
* This value should be left to the robot_phalanx system to manipulate.
* Initial Value: normal
* Attribute normal: Uses the default stance.
* Attribute stand: Forces the robot to stand.
* Attribute crouch: Forces the robot to crouch.
* Example: entity ai::set_behavior_attribute( "phalanx_force_stance", "stand" );"
*/
ai::RegisterMatchedInterface(
"robot",
"phalanx_force_stance",
"normal",
array( "normal", "stand", "crouch" ) );
/*
* Name: robot_lights
* Summary: Enables or disable the lights attached to the robot.
* Initial Value: ROBOT_LIGHTS_ON
* Attribute ROBOT_LIGHTS_ON: Enable lights.
* Attribute ROBOT_LIGHTS_FLICKER: Enable flickering lights.
* Attribute ROBOT_LIGHTS_OFF: Disable lights.
* Attribute ROBOT_LIGHTS_DEATH: Play death fx.
* * Attribute ROBOT_LIGHTS_OFF: Enable hacked lights.
* Example: entity ai::set_behavior_attribute( "robot_lights", ROBOT_LIGHTS_ON );"
*/
ai::RegisterMatchedInterface(
"robot",
"robot_lights",
0,
array( 0, 1, 2, 3, 4 ),
&RobotSoldierServerUtils::robotLights );
/*
* Name: robot_mini_raps
* Summary: Equips a robot with a mini raps they can deploy like a grenade.
* Initial Value: false
* Attribute true: Gives a mini raps to the robot.
* Attribute false: Takes away the mini raps from the robot.
* Example: entity ai::set_behavior_attribute( "robot_mini_raps", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"robot_mini_raps",
false,
array( true, false ),
&RobotSoldierServerUtils::robotEquipMiniRaps );
/*
* Name: rogue_allow_predestruct
* Summary: If true, allows robots forced into level_2 or level_3 to spawn with destructible pieces missing.
* Initial Value: true
* Attribute true: Spawns rogue controlled robot predestructed.
* Attribute false: Spawn rogue controlled robot without predestructing.
* Example: entity ai::set_behavior_attribute( "rogue_allow_predestruct", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"rogue_allow_predestruct",
true,
array( true, false ) );
/*
* Name: rogue_allow_pregib
* Summary: If true, allows robots forced into level_2 or level_3 to spawn with limbs missing.
* Initial Value: true
* Attribute true: Spawns rogue controlled robot pregibbed.
* Attribute false: Spawn rogue controlled robot without pregibbing.
* Example: entity ai::set_behavior_attribute( "rogue_allow_pregib", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"rogue_allow_pregib",
true,
array( true, false ) );
/*
* Name: rogue_control
* Summary: Controls whether or not a robot is rogue controlled. Note that
* setting a level lower than the current level has no effect and will be disregarded.
* Initial Value: level_0
* Attribute level_0: A normal robot, not rogue controlled.
* Attribute level_1: Swaps the robot to team three but still lets them shoot like a normal robot.
* Attribute forced_level_1: Immediately moves to level_1 without playing an animation.
* Attribute level_2: Swaps the robot to team three and makes them melee only.
* Attribute forced_level_2: Immediately moves to level_2 without playing an animation.
* Attribute level_3: Swaps the robot to team three, makes them melee only, and they explode when
* within reach of their enemy.
* Attribute forced_level_3: Immediately moves to level_3 without playing an animation.
* Example: entity ai::set_behavior_attribute( "rogue_control", "forced_level_1" );
*/
ai::RegisterMatchedInterface(
"robot",
"rogue_control",
"level_0",
array( "level_0", "level_1", "forced_level_1", "level_2", "forced_level_2", "level_3", "forced_level_3" ),
&RobotSoldierServerUtils::rogueControlAttributeCallback );
/*
* Name: rogue_control_force_goal
* Summary: Forces a rogue controlled robot that is in level_2 or level_3 to move to a position
* if the robot is currently unable to path to the player. Once the robot can path to the
* player, this value will be ignored and reset to undefined.
* Initial Value: undefined
* Example: entity ai::set_behavior_attribute( "rogue_control_force_goal", (10, 100, 0) );
*/
ai::RegisterVectorInterface(
"robot",
"rogue_control_force_goal",
undefined,
&RobotSoldierServerUtils::rogueControlForceGoalAttributeCallback );
/*
* Name: rogue_control_speed
* Summary: Controls how fast a rogue controlled robot moves at.
* Initial Value: sprint
* Attribute walk: Slow speed walk.
* Attribute run: Medium speed walk.
* Attribute sprint: Fast sprint.
* Example: entity ai::set_behavior_attribute( "rogue_control_speed", "walk" );"
*/
ai::RegisterMatchedInterface(
"robot",
"rogue_control_speed",
"sprint",
array( "walk", "run", "sprint" ),
&RobotSoldierServerUtils::rogueControlSpeedAttributeCallback );
/*
* Name: rogue_force_explosion
* Summary: Causes a level 3 robot to forcefully explode regardless of range to
* their enemy.
* Initial Value: false
* Attribute true: Forces a level 3robot to explode.
* Attribute false: Level 3 robot will only explode within range of their enemy.
* Example: entity ai::set_behavior_attribute( "rogue_force_explosion", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"rogue_force_explosion",
false,
array( true, false ) );
/*
* Name: shutdown
* Summary: When set to true the robot will shutdown, as if they were hit
* by an emp.
* Initial Value: false
* Attribute true: Forces a robot to shutdown.
* Attribute false: Returns the robot to normal.
* Example: entity ai::set_behavior_attribute( "shutdown", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"shutdown",
false,
array( true, false ) );
/*
* Name: sprint
* Summary: Controls whether robots will forcefully sprint while moving,
* this prevents shooting while moving.
* Initial Value: false
* Attribute true: Forces a robot to sprint when moving.
* Attribute false: Disables forcing sprint.
* Example: entity ai::set_behavior_attribute( "sprint", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"sprint",
false,
array( true, false ) );
/*
* Name: supports_super_sprint
* Summary: Allows the robot to use super sprint.
* NOTE: This is an MP only feature of robots and should not be used in other modes.
* Initial Value: false
* Attribute true: Allows the robot to super sprint.
* Attribute false: Disables the use of super sprint.
* Example: entity ai::set_behavior_attribute( "supports_super_sprint", true );"
*/
ai::RegisterMatchedInterface(
"robot",
"supports_super_sprint",
false,
array( true, false ) );
/*
* Name: traversals
* Summary: Controls how robots handle traversals, either through animations
* or procedurally calculating a trajectory.
* Initial Value: normal
* Attribute normal: Animated traverals, only normal AI traverals and custom traverals.
* Attribute procedural: Procedurally created trajectory, supports nearly any possible traveral.
* Example: entity ai::set_behavior_attribute( "traversals", "normal" );"
*/
ai::RegisterMatchedInterface(
"robot",
"traversals",
"normal",
array( "normal", "procedural" ),
&RobotSoldierServerUtils::robotTraversalAttributeCallback );
}
@@ -0,0 +1,349 @@
#using scripts\shared\ai_shared;
#using scripts\shared\array_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\callbacks_shared;
Line truncated
// SUMEET TODO
// A very temp secondary (facial only currently) system for AI's.
// Need to make it data driven and support many things after DPS
// We can possibly use this system for any archetype to animate some bones which are
// purely for cosmetic reasons.
#using_animtree( "generic" );
function autoexec main()
{
if ( SessionModeIsZombiesGame() && GetDvarInt( "splitscreen_playerCount" ) > 2 )
return;
ai::add_archetype_spawn_function( "human", &SecondaryAnimationsInit );
ai::add_archetype_spawn_function( "zombie", &SecondaryAnimationsInit );
ai::add_ai_spawn_function( &on_entity_spawn );
}
function private SecondaryAnimationsInit( localClientNum )
{
if( !IsDefined( level.__facialAnimationsList ) )
{
BuildAndValidateFacialAnimationList( localClientNum );
}
self callback::on_shutdown( &on_entity_shutdown );
// Handle facial animations
self thread SecondaryFacialAnimationThink( localClientNum );
}
function private on_entity_spawn( localClientNum )
{
if ( self HasDObj( localClientNum ) )
{
self ClearAnim( %faces, 0 ); // stale facial anims from the previous entity may still be running
}
self._currentFaceState = "inactive";
}
function private on_entity_shutdown( localClientNum )
{
if( isdefined( self ) )
{
self notify("stopFacialThread");
if ( ( isdefined( self.facialDeathAnimStarted ) && self.facialDeathAnimStarted ) )
return;
self ApplyDeathAnim( localClientNum );
self.facialDeathAnimStarted = true;
}
}
function BuildAndValidateFacialAnimationList( localClientNum )
{
assert( !IsDefined( level.__facialAnimationsList ) );
level.__facialAnimationsList = [];
// HUMANS
level.__facialAnimationsList["human"] = [];
level.__facialAnimationsList["human"]["combat"] = array( "ai_face_male_generic_idle_1","ai_face_male_generic_idle_2","ai_face_male_generic_idle_3" );
level.__facialAnimationsList["human"]["combat_aim"] = array( "ai_face_male_aim_idle_1","ai_face_male_aim_idle_2","ai_face_male_aim_idle_3" );
level.__facialAnimationsList["human"]["combat_shoot"] = array( "ai_face_male_aim_fire_1","ai_face_male_aim_fire_2","ai_face_male_aim_fire_3" );
level.__facialAnimationsList["human"]["death"] = array( "ai_face_male_death_1","ai_face_male_death_2","ai_face_male_death_3" );
level.__facialAnimationsList["human"]["melee"] = array( "ai_face_male_melee_1" );
level.__facialAnimationsList["human"]["pain"] = array( "ai_face_male_pain_1" );
level.__facialAnimationsList["human"]["animscripted"] = array( "ai_face_male_generic_idle_1" );
// ZOMBIES
level.__facialAnimationsList["zombie"] = [];
level.__facialAnimationsList["zombie"]["combat"] = array( "ai_face_zombie_generic_idle_1" );
level.__facialAnimationsList["zombie"]["combat_aim"] = array( "ai_face_zombie_generic_idle_1" );
level.__facialAnimationsList["zombie"]["combat_shoot"] = array( "ai_face_zombie_generic_idle_1" );
level.__facialAnimationsList["zombie"]["death"] = array( "ai_face_zombie_generic_death_1" );
level.__facialAnimationsList["zombie"]["melee"] = array( "ai_face_zombie_generic_attack_1" );
level.__facialAnimationsList["zombie"]["pain"] = array( "ai_face_zombie_generic_pain_1" );
level.__facialAnimationsList["zombie"]["animscripted"] = array( "ai_face_zombie_generic_idle_1" );
// validate death animations against looping flag
deathAnims = [];
foreach( animation in level.__facialAnimationsList["human"]["death"] )
{
array::add( deathAnims, animation );
}
foreach( animation in level.__facialAnimationsList["zombie"]["death"] )
{
array::add( deathAnims, animation );
}
foreach( deathAnim in deathAnims )
{
assert( !IsAnimLooping( localClientNum, deathAnim ), "FACIAL ANIM - Death facial animation " + deathAnim + " is set to looping in the GDT. It needs to be non-looping." );
}
}
function private GetFacialAnimOverride( localClientNum )
{
if ( SessionModeIsCampaignGame() )
{
// get the primary delta anim and see if it has any notetracks
primaryDeltaAnim = self GetPrimaryDeltaAnim();
if ( isdefined( primaryDeltaAnim ) )
{
primaryDeltaAnimLength = GetAnimLength( primaryDeltaAnim );
notetracks = GetNotetracksInDelta( primaryDeltaAnim, 0, 1 );
foreach( notetrack in notetracks )
{
if ( notetrack[1] == "facial_anim" )
{
facialAnim = notetrack[2];
facialAnimLength = GetAnimLength( facialAnim );
/#
if ( facialAnimLength < primaryDeltaAnimLength && !IsAnimLooping( localClientNum, facialAnim ) )
{
//println( "Found facial anim '" + facialAnim + "' that is shorter than the fullbody anim '" + primaryDeltaAnim + "' it will be played on." );
}
#/
return facialAnim;
}
}
}
}
return undefined;
}
function private SecondaryFacialAnimationThink( localClientNum )
{
assert( IsDefined( self.archetype ) && ( self.archetype == "human" || self.archetype == "zombie" ) );
self endon ("entityshutdown");
self endon ("stopFacialThread");
self._currentFaceState = "inactive";
while(1)
{
if( self.archetype == "human" && self clientfield::get( "facial_dial" ) )
{
self._currentFaceState = "inactive";
self ClearCurrentFacialAnim(localClientNum);
wait 0.5;
continue;
}
animOverride = self GetFacialAnimOverride( localClientNum );
asmStatus = self ASMGetStatus( localClientNum );
forceNewAnim = false;
switch(asmStatus)
{
case "asm_status_terminated":
//ClearCurrentFacialAnim(localClientNum);
return;
case "asm_status_inactive":
// we're in the animscripted state -- allow facial anims
if ( isdefined( animOverride ) )
{
// did we just start playing a new primary delta anim?
scriptedAnim = self GetPrimaryDeltaAnim();
if ( isdefined( scriptedAnim ) && ( !isdefined( self._scriptedAnim ) || self._scriptedAnim != scriptedAnim ) )
{
self._scriptedAnim = scriptedAnim;
forceNewAnim = true;
}
if ( isdefined( animOverride ) && animOverride !== self._currentFaceAnim )
{
forceNewAnim = true;
}
}
else
{
if ( self._currentFaceState !== "death" )
{
self._currentFaceState = "inactive";
self ClearCurrentFacialAnim(localClientNum);
}
wait 0.5;
continue;
}
}
closestPlayer = ArrayGetClosest( self.origin, level.localPlayers, GetDvarInt( "ai_clientFacialCullDist", 2000 ) );
if( !IsDefined( closestPlayer ) )
{
wait 0.5;
continue;
}
if( !self HasDObj(localClientNum) || !self HasAnimTree() )
{
wait 0.5;
continue;
}
currFaceState = self._currentFaceState;
currentASMState = self ASMGetCurrentState( localClientNum );
if( isdefined( currentASMState ) )
{
currentASMState = ToLower( currentASMState );
}
if( self ASMIsTerminating( localClientNum ) )
{
nextFaceState = "death";
}
else if( asmStatus == "asm_status_inactive" )
{
nextFaceState = "animscripted";
}
else if( IsDefined( currentASMState ) && IsSubStr( currentASMState, "pain" ) )
{
nextFaceState = "pain";
}
else if( IsDefined( currentASMState ) && IsSubStr( currentASMState, "melee" ) )
{
nextFaceState = "melee";
}
else if( self ASMIsShootLayerActive( localClientNum ) )
{
nextFaceState = "combat_shoot";
}
else if( self ASMIsAimLayerActive( localClientNum ) )
{
nextFaceState = "combat_aim";
}
else
{
nextFaceState = "combat";
}
if( currFaceState == "inactive" || currFaceState != nextFaceState || forceNewAnim )
{
Assert( IsDefined( level.__facialAnimationsList[self.archetype][nextFaceState] ) );
clearOnCompletion = false;
if( nextFaceState == "death" )
{
//clearOnCompletion = true;
}
animToPlay = array::random( level.__facialAnimationsList[self.archetype][nextFaceState] );
if ( isdefined( animOverride ) )
{
animToPlay = animOverride;
assert( nextFaceState != "death" || !IsAnimLooping( localClientNum, animToPlay ), "FACIAL ANIM - Death facial animation " + animToPlay + " is set to looping in the GDT. It needs to be non-looping." );
}
ApplyNewFaceAnim( localClientNum, animToPlay, clearOnCompletion );
self._currentFaceState = nextFaceState;
}
if( self._currentFaceState == "death" )
break;
wait 0.25;
}
}
function private ApplyNewFaceAnim( localClientNum, animation, clearOnCompletion = false )
{
ClearCurrentFacialAnim(localClientNum);
if( IsDefined( animation ) )
{
self._currentFaceAnim = animation;
if( self HasDObj(localClientNum) && self HasAnimTree() )
{
self SetFlaggedAnimKnob( "ai_secondary_facial_anim", animation, 1.0, 0.1, 1.0 );
if( clearOnCompletion )
{
wait( GetAnimLength( animation ) );
ClearCurrentFacialAnim(localClientNum);
}
}
}
}
function private ApplyDeathAnim( localClientNum )
{
if( IsDefined( self._currentFaceState ) && self._currentFaceState == "death" )
return;
if ( GetMigrationStatus(localClientNum) )
return;
if( IsDefined( self ) &&
IsDefined( level.__facialAnimationsList ) &&
IsDefined( level.__facialAnimationsList[self.archetype] ) &&
IsDefined( level.__facialAnimationsList[self.archetype]["death"] ) )
{
animToPlay = array::random( level.__facialAnimationsList[self.archetype]["death"] );
animOverride = self GetFacialAnimOverride( localClientNum );
if ( isdefined( animOverride ) )
{
animToPlay = animOverride;
}
self._currentFaceState = "death";
ApplyNewFaceAnim( localClientNum, animToPlay );
}
}
function private ClearCurrentFacialAnim(localClientNum)
{
if( IsDefined( self._currentFaceAnim ) && self HasDObj(localClientNum) && self HasAnimTree() )
{
self ClearAnim( self._currentFaceAnim, 0.2 );
}
self._currentFaceAnim = undefined;
}
@@ -0,0 +1,7 @@
#using_animtree( "generic" );
function autoexec init()
{
// make sure these animations get registered on the client side
dummyVar = %faces;
}
+402
View File
@@ -0,0 +1,402 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\postfx_shared;
#using scripts\shared\ai\systems\gib;
#using scripts\shared\system_shared;
#using scripts\shared\visionset_mgr_shared;
Line truncated
#precache( "client_fx", "dlc1/castle/fx_mech_foot_step" );
#precache( "client_fx", "dlc2/island/fx_thrash_pustule_burst" );
#precache( "client_fx", "dlc2/island/fx_thrash_pustule_spore_exp" );
#precache( "client_fx", "dlc2/island/fx_thrash_pustule_impact" );
#precache( "client_fx", "dlc2/island/fx_thrash_eye_glow" );
#precache( "client_fx", "dlc2/island/fx_thrash_eye_glow_rage" );
#precache( "client_fx", "dlc2/island/fx_thrash_rage_gas_torso" );
#precache( "client_fx", "dlc2/island/fx_thrash_rage_gas_leg_lft" );
#precache( "client_fx", "dlc2/island/fx_thrash_rage_gas_leg_rgt" );
#precache( "client_fx", "dlc2/island/fx_thrash_pustule_reinflate" );
#precache( "client_fx", "dlc2/island/fx_spores_cloud_ambient_sm" );
#precache( "client_fx", "dlc2/island/fx_spores_cloud_ambient_md" );
#precache( "client_fx", "dlc2/island/fx_spores_cloud_ambient_lrg" );
#precache( "client_fx", "dlc2/island/fx_thrash_chest_mouth_drool_1p" );
#using_animtree( "generic" );
function autoexec __init__sytem__() { system::register("thrasher",&__init__,undefined,undefined); }
function __init__()
{
visionset_mgr::register_visionset_info( "zm_isl_thrasher_stomach_visionset", 9000, 16, undefined, "zm_isl_thrasher_stomach" );
if( ai::shouldRegisterClientFieldForArchetype( "thrasher" ) )
{
clientfield::register(
"actor",
"thrasher_spore_state",
5000, // Leave at VERSION_TU5
3,
"int",
&ThrasherClientUtils::thrasherSporeExplode,
!true,
!true );
clientfield::register(
"actor",
"thrasher_berserk_state",
5000, // Leave at VERSION_TU5
1,
"int",
&ThrasherClientUtils::thrasherBerserkMode,
!true,
true );
clientfield::register(
"actor",
"thrasher_player_hide",
8000, // Leave at VERSION_TU8
4,
"int",
&ThrasherClientUtils::thrasherHideFromPlayer,
!true,
!true );
clientfield::register(
"toplayer",
"sndPlayerConsumed",
10000, // Leave at VERSION_TU10
1,
"int",
&ThrasherClientUtils::sndPlayerConsumed,
!true,
true );
foreach ( spore in array( 1, 2, 4 ) )
{
clientfield::register(
"actor",
"thrasher_spore_impact" + spore,
8000, // Leave at VERSION_TU8
1,
"counter",
&ThrasherClientUtils::thrasherSporeImpact,
!true,
!true );
}
}
ai::add_archetype_spawn_function( "thrasher", &ThrasherClientUtils::thrasherSpawn );
level.thrasherPustules = [];
level thread ThrasherClientUtils::thrasherFxCleanup();
}
function autoexec precache()
{
level._effect[ "fx_mech_foot_step" ] = "dlc1/castle/fx_mech_foot_step";
level._effect[ "fx_thrash_pustule_burst" ] = "dlc2/island/fx_thrash_pustule_burst";
level._effect[ "fx_thrash_pustule_spore_exp" ] = "dlc2/island/fx_thrash_pustule_spore_exp";
level._effect[ "fx_thrash_pustule_impact" ] = "dlc2/island/fx_thrash_pustule_impact";
level._effect[ "fx_thrash_eye_glow" ] = "dlc2/island/fx_thrash_eye_glow";
level._effect[ "fx_thrash_eye_glow_rage" ] = "dlc2/island/fx_thrash_eye_glow_rage";
level._effect[ "fx_thrash_rage_gas_torso" ] = "dlc2/island/fx_thrash_rage_gas_torso";
level._effect[ "fx_thrash_rage_gas_leg_lft" ] = "dlc2/island/fx_thrash_rage_gas_leg_lft";
level._effect[ "fx_thrash_rage_gas_leg_rgt" ] = "dlc2/island/fx_thrash_rage_gas_leg_rgt";
level._effect[ "fx_thrash_pustule_reinflate" ] = "dlc2/island/fx_thrash_pustule_reinflate";
level._effect[ "fx_spores_cloud_ambient_sm" ] = "dlc2/island/fx_spores_cloud_ambient_sm";
level._effect[ "fx_spores_cloud_ambient_md" ] = "dlc2/island/fx_spores_cloud_ambient_md";
level._effect[ "fx_spores_cloud_ambient_lrg" ] = "dlc2/island/fx_thrash_pustule_reinflate";
level._effect[ "fx_thrash_chest_mouth_drool" ] = "dlc2/island/fx_thrash_chest_mouth_drool_1p";
}
#namespace ThrasherClientUtils;
function private thrasherSpawn( localClientNum )
{
entity = self;
entity.ignoreRagdoll = true;
level._footstepCBFuncs[entity.archetype] = &thrasherProcessFootstep;
GibClientUtils::AddGibCallback( localClientNum, entity, 4, &thrasherDisableEyeGlow );
}
function private thrasherFxCleanup()
{
while ( true )
{
pustules = level.thrasherPustules;
level.thrasherPustules = [];
time = GetTime();
foreach ( pustule in pustules )
{
if ( pustule.endTime <= time )
{
if( isdefined( pustule.fx ) )
{
StopFX( pustule.localClientNum, pustule.fx );
}
}
else
{
level.thrasherPustules[ level.thrasherPustules.size ] = pustule;
}
}
wait 0.5;
}
}
function thrasherProcessFootstep( localClientNum, pos, surface, notetrack, bone )
{
e_player = GetLocalPlayer( localClientNum );
n_dist = DistanceSquared( pos, e_player.origin );
n_thrasher_dist = ( 1000 * 1000 );
if (n_thrasher_dist <= 0)
{
return;
}
n_scale = ( n_thrasher_dist - n_dist ) / n_thrasher_dist;
if ( n_scale > 1 || n_scale < 0 || n_scale <= 0.01 )
{
return;
}
fx = PlayFXOnTag( localClientNum, level._effect[ "fx_mech_foot_step" ], self, bone );
if ( IsDefined( e_player.thrasherLastFootstep ) &&
( e_player.thrasherLastFootstep + 400 ) > GetTime() )
{
return;
}
earthquake_scale = n_scale * 0.1;
if( earthquake_scale > 0.01)
{
e_player Earthquake( earthquake_scale, 0.1, pos, n_dist );
}
if( n_scale <= 1 && n_scale > 0.8 )
{
e_player PlayRumbleOnEntity( localClientNum, "damage_heavy" );
}
else if( n_scale <= 0.8 && n_scale > 0.4 )
{
e_player PlayRumbleOnEntity( localClientNum, "reload_small" );
}
e_player.thrasherLastFootstep = GetTime();
}
function private _StopFx( localClientNum, effect )
{
if ( IsDefined( effect ) )
{
StopFX( localClientNum, effect );
}
}
function private thrasherHideFromPlayer( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( !IsDefined( entity ) || entity.archetype !== "thrasher" || !entity HasDObj( localClientNum ) )
{
return;
}
localPlayer = GetLocalPlayer( localClientNum );
localPlayerNum = localPlayer GetEntityNumber();
localPlayerBit = 1 << localPlayerNum;
if ( localPlayerBit & newValue )
{
entity Hide();
}
else
{
entity Show();
}
}
function private thrasherBerserkMode( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( !IsDefined( entity ) || entity.archetype !== "thrasher" || !entity HasDObj( localClientNum ) )
{
return;
}
_StopFx( localClientNum, entity.thrasherEyeGlow );
entity.thrasherEyeGlow = undefined;
_StopFx( localClientNum, entity.thrasherAmbientFX1 );
entity.thrasherAmbientFX1 = undefined;
_StopFx( localClientNum, entity.thrasherAmbientFX2 );
entity.thrasherAmbientFX2 = undefined;
_StopFx( localClientNum, entity.thrasherAmbientFX3 );
entity.thrasherAmbientFX3 = undefined;
hasHead = !GibClientUtils::IsGibbed( localClientNum, entity, 4 );
switch ( newValue )
{
case 0:
if ( hasHead )
{
entity.thrasherEyeGlow = PlayFXOnTag( localClientNum, level._effect[ "fx_thrash_eye_glow" ], entity, "j_eyeball_le" );
}
break;
case 1:
if ( hasHead )
{
entity.thrasherEyeGlow = PlayFXOnTag( localClientNum, level._effect[ "fx_thrash_eye_glow_rage" ], entity, "j_eyeball_le" );
}
entity.thrasherAmbientFX1 = PlayFXOnTag( localClientNum, level._effect[ "fx_thrash_rage_gas_torso" ], entity, "j_spinelower" );
entity.thrasherAmbientFX2 = PlayFXOnTag( localClientNum, level._effect[ "fx_thrash_rage_gas_leg_lft" ], entity, "j_hip_le" );
entity.thrasherAmbientFX3 = PlayFXOnTag( localClientNum, level._effect[ "fx_thrash_rage_gas_leg_rgt" ], entity, "j_hip_ri" );
break;
}
}
function private thrasherSporeExplode( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
sporeClientfields = array( 1, 2, 4 );
sporeTags = array( "tag_spore_chest", "tag_spore_back", "tag_spore_leg" );
// Flags bits that are 1 in the newValue and 0 in the oldValue.
newSporesExploded = (oldValue ^ newValue) & ~oldValue;
// Flags bits that are 0 in the newValue and 1 in the oldValue.
oldSporesInflated = (oldValue ^ newValue) & ~newValue;
currentSpore = sporeClientfields[0];
for ( index = 0; index < array( "tag_spore_chest", "tag_spore_back", "tag_spore_leg" ).size; index++ )
{
sporeTag = sporeTags[index];
pustuleInfo = undefined;
if ( newSporesExploded & currentSpore )
{
PlayFXOnTag( localClientNum, level._effect[ "fx_thrash_pustule_burst" ], entity, sporeTag );
PlayFXOnTag( localClientNum, level._effect[ "fx_thrash_pustule_spore_exp" ], entity, sporeTag );
// Ambient spore cloud.
pustuleInfo = SpawnStruct();
pustuleInfo.length = 5000;
if( !( isdefined( level.b_thrasher_custom_spore_fx ) && level.b_thrasher_custom_spore_fx ) )
{
pustuleInfo.fx = PlayFX( localClientNum, level._effect[ "fx_spores_cloud_ambient_md" ], entity GetTagOrigin( sporeTag ) );
}
}
else if ( oldSporesInflated & currentSpore )
{
pustuleInfo = SpawnStruct();
pustuleInfo.length = 2000;
pustuleInfo.fx = PlayFXOnTag( localClientNum, level._effect[ "fx_thrash_pustule_reinflate" ], entity, sporeTag );
}
if ( IsDefined( pustuleInfo ) )
{
pustuleInfo.localClientNum = localClientNum;
pustuleInfo.startTime = GetTime();
pustuleInfo.endTime = pustuleInfo.startTime + pustuleInfo.length;
level.thrasherPustules[ level.thrasherPustules.size ] = pustuleInfo;
}
currentSpore <<= 1;
}
}
function private thrasherSporeImpact( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
sporeTag = undefined;
sporeClientfields = array( 1, 2, 4 );
assert( sporeClientfields.size == array( "tag_spore_chest", "tag_spore_back", "tag_spore_leg" ).size );
for ( index = 0; index < sporeClientfields.size; index++ )
{
if ( fieldName == ( "thrasher_spore_impact" + sporeClientfields[ index ] ) )
{
sporeTag = array( "tag_spore_chest", "tag_spore_back", "tag_spore_leg" )[ index ];
break;
}
}
if ( IsDefined( sporeTag ) )
{
PlayFXOnTag( localClientNum, level._effect[ "fx_thrash_pustule_impact" ], entity, sporeTag );
}
}
function private thrasherDisableEyeGlow( localClientNum, entity, gibFlag )
{
if ( !IsDefined( entity ) || entity.archetype !== "thrasher" || !entity HasDObj( localClientNum ) )
{
return;
}
_StopFx( localClientNum, entity.thrasherEyeGlow );
entity.thrasherEyeGlow = undefined;
}
function private sndPlayerConsumed( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if( newValue )
{
if( !isdefined( self.sndPlayerConsumedID ) )
{
self.sndPlayerConsumedID = self playloopsound( "zmb_thrasher_consumed_lp", 5 );
}
if( !isdefined( self.n_fx_id_player_consumed ) )
{
self.n_fx_id_player_consumed = PlayFXOnCamera( localClientNum, level._effect[ "fx_thrash_chest_mouth_drool" ] );
}
self thread postfx::playpostfxbundle( "pstfx_thrasher_stomach" );
EnableSpeedBlur( localClientNum, 0.07, 0.55, 0.9, false, 100, 100 );
}
else
{
if( isdefined( self.sndPlayerConsumedID ) )
{
self stoploopsound( self.sndPlayerConsumedID, .5 );
self.sndPlayerConsumedID = undefined;
}
if( isdefined( self.n_fx_id_player_consumed ) )
{
StopFX( localClientNum, self.n_fx_id_player_consumed );
self.n_fx_id_player_consumed = undefined;
}
self StopAllLoopSounds(1);
if( isdefined( self.playingPostfxBundle ) )
{
self thread postfx::stopplayingpostfxbundle();
}
DisableSpeedBlur( localClientNum );
}
}
File diff suppressed because it is too large. Load diff
+227
View File
@@ -0,0 +1,227 @@
///////////////////////////////////////////////////////////////////////////////
//
// THRASHER TUNABLES
//
///////////////////////////////////////////////////////////////////////////////
// Initial thrasher health, NOT USED IN ZOMBIES
// Total health is based on level.zombie_health multiplied by this multiplier, increases per round
// Initial pustule health, NOT USED IN ZOMBIES
// Total pustule health is based on level.zombie_health multiplied by this multiplier, increases per round
// Initial head health
// Additional health added when enraged
// Amount of rage added when hit in a particular location
// Amount of rage added per second
// Additional rage added on top "THRASHER_RAGE_AUTO" per second multiplied by current level
// Amount of rage added when performing behavior
// Amount of rage before pushing the thrasher into berserk mode
// How often a thrasher can consume a zombie
// How many times a thrasher can consume a zombie
// How often a thrasher can teleport to close the distance in milliseconds
// How close a thrasher can teleport to another thrasher's position
// How far a thrasher will consider a downed player to consume
// How long stuns lasts in milliseconds
// How much normal damage before the thrasher is stunned
// How long the thrasher stays invulnerable after becoming enraged
// Distance from enemy before the thrasher will teleport
// How long the thrasher's spore inflate FX will play for.
// How long the spore cloud released from a popped pustule lasts
// How far away from the center of a spore cloud zombies can be affected
// Maximum number of zombies a single spore cloud can convert to thrashers
// Maximum number of zombies that can be converted per round into thrashers
// Chance a single zombie will be converted into a thrasher (0, 1)
// How often all zombies within a spore cloud will be tested to turn into a thrasher
// How long a player must be in laststand before being able to be eatten
// How long of a time must pass before a new thrasher can consume a player that was just freed from a thrasher.
///////////////////////////////////////////////////////////////////////////////
//
// THRASHER DEFINES
//
///////////////////////////////////////////////////////////////////////////////
// Melee
// Consume
// Consume Zombie
// Navmesh
// Spores
// Thrasher First Person Mouth
// 2 bits per player
// Berserk Mode
// Spikes
// FX
// Head Damage
// Teleport
@@ -0,0 +1,42 @@
#using scripts\shared\ai\archetype_thrasher;
#using scripts\shared\ai\systems\ai_interface;
#namespace ThrasherInterface;
function RegisterThrasherInterfaceAttributes()
{
/*
* Name: stunned
* Summary: Controls whether the thrasher AI is stunned.
* Initial Value: false
* Attribute true: Thrasher is stunned.
* Attribute false: Normal thrasher behaviors.
* Example: entity ai::set_behavior_attribute( "thrasher", true );"
*/
ai::RegisterMatchedInterface(
"thrasher",
"stunned",
false,
array( true, false ) );
ai::RegisterMatchedInterface(
"thrasher",
"move_mode",
"normal",
array( "normal", "friendly" ),
&ThrasherServerUtils::thrasherMoveModeAttributeCallback );
/*
* Name: use_attackable
* Summary: Controls whether the thrasher destroys attackable objects.
* Initial Value: false
* Attribute true: Will enable the attackable behavior.
* Attribute false: Disables attackable behavior.
* Example: entity ai::set_behavior_attribute( "use_attackable", true );"
*/
ai::RegisterMatchedInterface(
"thrasher",
"use_attackable",
false,
array( true, false ) );
}
File diff suppressed because it is too large. Load diff
+84
View File
@@ -0,0 +1,84 @@
#using scripts\shared\ai\warlord;
#using scripts\shared\ai\systems\ai_interface;
#namespace WarlordInterface;
function RegisterWarlordInterfaceAttributes()
{
/*
* Name: can_be_meleed
* Summary: Controls whether other AI will choose to melee the warlord.
* Initial Value: true
* Attribute true: Normal behavior, warlord will be meleed when close to their enemy.
* Attribute false: Forces other AI to shoot the warlord instead of melee it at close distances.
* Example: entity ai::set_behavior_attribute( "can_be_meleed", true );"
*/
ai::RegisterMatchedInterface(
"warlord",
"can_be_meleed",
false,
array( true, false ) );
}
/*
* Name: AddPreferedPoint
* Summary: Adds a prefered go to location that the warlord will be inclined to go to every now and then.
* The warlord will choose the location only if it is inside the goal bounadries.
* Multiple points can be added.
* Mandatory Arg - self : warlord entity
* Mandatory Arg - position : prefered position.
* Optional Arg - min_duration/max_duration Range in milliseconds where the warlord should hold that position if he chooses it
* Optional Arg - position name
* Example: entity AddPreferedPoint(interesting_node.origin, 10000, 15000);
*/
function AddPreferedPoint(position, min_duration, max_duration, name)
{
WarlordServerUtils::AddPreferedPoint(self, position, min_duration, max_duration, name);
}
/*
* Name: DeletePreferedPoint
* Summary: Deletes a specific preferred point.
* Mandatory Arg - self : warlord entity.
* Mandatory Arg - name : Name of the prefered point passed by AddPreferedPoint function.
* Return Arg : True is found a point and deleted it. False if it didn't find any points.
* Example: entity DeletePreferedPoint( "TruckPreferedPoint1" );
*/
function DeletePreferedPoint( name )
{
WarlordServerUtils::DeletePreferedPoint(self, name);
}
/*
* Name: ClearAllPreferedPoints
* Summary: Clears all Warlord prefered nodes.
* Mandatory Arg - self : warlord entity
* Example: entity ClearAllPreferedPoints();
*/
function ClearAllPreferedPoints()
{
WarlordServerUtils::ClearAllPreferedPoints(self);
}
/*
* Name: ClearPreferedPointsOutsideGoal
* Summary: Clears all Warlord prefered nodes which are outside the current Goal
* Mandatory Arg - self : warlord entity
* Example: entity ClearAllPreferedPoints();
*/
function ClearPreferedPointsOutsideGoal()
{
WarlordServerUtils::ClearPreferedPointsOutsideGoal(self);
}
/*
* Name: SetWarlordAggressiveMode
* Summary: Instruct the Warlord to be more careful about choosing position that can target the LastSeenEnemy and to make him prefer close encounters. Recommended for indoor areas.
* Recommended to not be set if the fighting are is outdoor.
* Mandatory Arg - self : warlord entity
* Example: entity SetWarlordAggressiveMode(true);
*/
function SetWarlordAggressiveMode( b_aggressive_mode )
{
WarlordServerUtils::SetWarlordAggressiveMode(self);
}
@@ -0,0 +1,47 @@
#using scripts\shared\ai\behavior_zombie_dog;
#using scripts\shared\ai\systems\ai_interface;
#namespace ZombieDogInterface;
function RegisterZombieDogInterfaceAttributes()
{
/*
* Name: gravity
* Summary: Enables or disables low gravity animations for the zombie dog.
* Initial Value: normal
* Attribute normal: Normal animations.
* Attribute low: Low gravity animations.
* Example: entity ai::set_behavior_attribute( "gravity", "low" );
*/
ai::RegisterMatchedInterface(
"zombie_dog",
"gravity",
"normal",
array( "low", "normal" ),
&ZombieDogBehavior::zombieDogGravity );
/*
* Name: min_run_dist
* Summary: The minimum distance at which a zombie dog will decide to start running towards its target.
* Initial Value: 500
* Example: entity ai::set_behavior_attribute( "min_run_dist", 500 );
*/
ai::RegisterMatchedInterface(
"zombie_dog",
"min_run_dist",
500 );
/*
* Name: sprint
* Summary: If this is set to true, the zombie dog will sprint towards its target even without LOS.
* Initial Value: false
* Example: entity ai::set_behavior_attribute( "sprint", true );
*/
ai::RegisterMatchedInterface(
"zombie_dog",
"sprint",
false,
array( true, false ) );
}
+63
View File
@@ -0,0 +1,63 @@
#using scripts\shared\ai\zombie;
#using scripts\shared\ai\systems\ai_interface;
#namespace ZombieInterface;
function RegisterZombieInterfaceAttributes()
{
/*
* Name: can_juke
* Summary: Controls whether the zombie can juke.
* Initial Value: true
* Attribute true: Normal behavior, zombie will occasionally juke left or right.
* Attribute false: Disables zombie's ability to juke.
* Example: entity ai::set_behavior_attribute( "can_juke", true );"
*/
ai::RegisterMatchedInterface(
"zombie",
"can_juke",
false,
array( true, false ) );
/*
* Name: suicidal_behavior
* Summary: Controls whether the zombie is going to act as suicidal.
* Initial Value: false
* Attribute true: Will enable the suicidal behavior.
* Attribute false: Disables suicidal behavior.
* Example: entity ai::set_behavior_attribute( "suicidal_behavior", true );"
*/
ai::RegisterMatchedInterface(
"zombie",
"suicidal_behavior",
false,
array( true, false ) );
/*
* Name: spark_behavior
* Summary: Controls whether the zombie is going to act as spark zombie.
* Initial Value: false
* Attribute true: Will enable the spark behavior.
* Attribute false: Disables spark behavior.
* Example: entity ai::set_behavior_attribute( "spark_behavior", true );"
*/
ai::RegisterMatchedInterface(
"zombie",
"spark_behavior",
false,
array( true, false ) );
/*
* Name: use_attackable
* Summary: Controls whether the zombie destroys attackable objects.
* Initial Value: false
* Attribute true: Will enable the attackable behavior.
* Attribute false: Disables attackable behavior.
* Example: entity ai::set_behavior_attribute( "use_attackable", true );"
*/
ai::RegisterMatchedInterface(
"zombie",
"use_attackable",
false,
array( true, false ) );
}
@@ -0,0 +1,18 @@
#using scripts\shared\ai_shared;
#using scripts\shared\ai\systems\behavior_state_machine;
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\math_shared;
Line truncated
function autoexec RegisterBehaviorScriptFunctions()
{
}
+537
View File
@@ -0,0 +1,537 @@
#using scripts\shared\ai\systems\animation_state_machine_utility;
#using scripts\shared\ai\systems\animation_state_machine_notetracks;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\systems\ai_interface;
#using scripts\shared\ai\zombie;
#using scripts\shared\spawner_shared;
#using scripts\shared\ai\archetype_mocomps_utility;
#using scripts\shared\ai\archetype_zombie_dog_interface;
#using scripts\shared\ai_shared;
#using scripts\shared\math_shared;
Line truncated
#namespace ZombieDogBehavior;
function autoexec RegisterBehaviorScriptFunctions()
{
// INIT BLACKBOARD
spawner::add_archetype_spawn_function( "zombie_dog", &ArchetypeZombieDogBlackboardInit );
// ------- ZOMBIE DOG - SERVICES ----------- //
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieDogTargetService",&zombieDogTargetService);;
// ------- ZOMBIE DOG - CONDITIONS --------- //
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieDogShouldMelee",&zombieDogShouldMelee);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieDogShouldWalk",&zombieDogShouldWalk);;
BehaviorTreeNetworkUtility::RegisterBehaviorTreeScriptAPI("zombieDogShouldRun",&zombieDogShouldRun);;
// ------- ZOMBIE DOG - ACTIONS ------------ //
BehaviorTreeNetworkUtility::RegisterBehaviorTreeAction("zombieDogMeleeAction",&zombieDogMeleeAction,undefined,&zombieDogMeleeActionTerminate);;
AnimationStateNetwork::RegisterNotetrackHandlerFunction("dog_melee",&ZombieBehavior::zombieNotetrackMeleeFire);;
ZombieDogInterface::RegisterZombieDogInterfaceAttributes();
}
function ArchetypeZombieDogBlackboardInit() // self = AI
{
// CREATE BLACKBOARD
Blackboard::CreateBlackBoardForEntity( self );
// CREATE INTERFACE
ai::CreateInterfaceForEntity( self );
// USE UTILITY BLACKBOARD
self AiUtility::RegisterUtilityBlackboardAttributes();
// CREATE ZOMBIE DOG BLACKBOARD
Blackboard::RegisterBlackBoardAttribute(self,"_low_gravity","normal",undefined); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_low_gravity");#/ };
Blackboard::RegisterBlackBoardAttribute(self,"_should_run","walk",&BB_GetShouldRunStatus); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_should_run");#/ };
Blackboard::RegisterBlackBoardAttribute(self,"_should_howl","dont_howl",&BB_GetShouldHowlStatus); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_should_howl");#/ };
// REGISTER ANIMSCRIPTED CALLBACK
self.___ArchetypeOnAnimscriptedCallback = &ArchetypeZombieDogOnAnimscriptedCallback;
// ENABLE DEBUGGING IN ODYSSEY
/#self FinalizeTrackedBlackboardAttributes();#/;
// Custom attributes
self.kill_on_wine_coccon = true;
}
function private ArchetypeZombieDogOnAnimscriptedCallback( entity )
{
// UNREGISTER THE BLACKBOARD
entity.__blackboard = undefined;
// REREGISTER BLACKBOARD
entity ArchetypeZombieDogBlackboardInit();
}
function BB_GetShouldRunStatus()
{
/#
if ( ( isdefined( self.isPuppet ) && self.isPuppet ) )
{
return "run";
}
#/
if ( ( isdefined( self.hasSeenFavoriteEnemy ) && self.hasSeenFavoriteEnemy ) || ( ai::HasAiAttribute( self, "sprint" ) && ai::GetAiAttribute( self, "sprint" ) ) )
return "run";
return "walk";
}
function BB_GetShouldHowlStatus()
{
if ( self ai::has_behavior_attribute( "howl_chance" ) && ( isdefined( self.hasSeenFavoriteEnemy ) && self.hasSeenFavoriteEnemy ) )
{
if ( !isdefined( self.shouldHowl ) )
{
chance = self ai::get_behavior_attribute( "howl_chance" );
self.shouldHowl = RandomFloat( 1 ) <= chance;
}
if ( self.shouldHowl )
{
return "howl";
}
else
{
return "dont_howl";
}
}
return "dont_howl";
}
function GetYaw(org)
{
angles = VectorToAngles(org-self.origin);
return angles[1];
}
// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away.
function AbsYawToEnemy()
{
assert( IsDefined(self.enemy) );
yaw = self.angles[1] - GetYaw(self.enemy.origin);
yaw = AngleClamp180( yaw );
if (yaw < 0)
{
yaw = -1 * yaw;
}
return yaw;
}
function need_to_run()
{
run_dist_squared = ( (self ai::get_behavior_attribute( "min_run_dist" )) * (self ai::get_behavior_attribute( "min_run_dist" )) );
run_yaw = 20;
run_pitch = 30;
run_height = 64;
if ( self.health < self.maxhealth )
{
// dog took damage
return true;
}
if ( !IsDefined( self.enemy ) || !IsAlive( self.enemy ) )
{
return false;
}
if ( !self CanSee( self.enemy ) )
{
return false;
}
dist = DistanceSquared( self.origin, self.enemy.origin );
if ( dist > run_dist_squared )
{
return false;
}
height = self.origin[2] - self.enemy.origin[2];
if ( abs( height ) > run_height )
{
return false;
}
yaw = self AbsYawToEnemy();
if ( yaw > run_yaw )
{
return false;
}
pitch = AngleClamp180( VectorToAngles( self.origin - self.enemy.origin )[0] );
if ( abs( pitch ) > run_pitch )
{
return false;
}
return true;
}
function private is_target_valid( dog, target )
{
if( !IsDefined( target ) )
{
return false;
}
if( !IsAlive( target ) )
{
return false;
}
if (!(dog.team == "allies"))
{
if( !IsPlayer( target ) && SessionModeIsZombiesGame())
{
return false;
}
if( IsDefined(target.is_zombie) && target.is_zombie == true )
{
return false;
}
}
if( IsPlayer( target ) && target.sessionstate == "spectator" )
{
return false;
}
if( IsPlayer( target ) && target.sessionstate == "intermission" )
{
return false;
}
if( ( isdefined( self.intermission ) && self.intermission ) )
{
return false;
}
if( ( isdefined( target.ignoreme ) && target.ignoreme ) )
{
return false;
}
if( target IsNoTarget() )
{
return false;
}
if( dog.team == target.team )
{
return false;
}
//for additional level specific checks
if( IsPlayer( target ) && IsDefined( level.is_player_valid_override ) )
{
return [[ level.is_player_valid_override ]]( target );
}
return true;
}
function private get_favorite_enemy( dog )
{
dog_targets = [];
if ( SessionModeIsZombiesGame() )
{
if (self.team == "allies")
{
dog_targets = GetAITeamArray( level.zombie_team );
}
else
{
dog_targets = getplayers();
}
}
else
{
dog_targets = ArrayCombine( getplayers(), GetAIArray(), false, false );
}
least_hunted = dog_targets[0];
closest_target_dist_squared = undefined;
for( i = 0; i < dog_targets.size; i++ )
{
if ( !isdefined( dog_targets[i].hunted_by ) )
{
dog_targets[i].hunted_by = 0;
}
if( !is_target_valid( dog, dog_targets[i] ) )
{
continue;
}
if( !is_target_valid( dog, least_hunted ) )
{
least_hunted = dog_targets[i];
}
dist_squared = DistanceSquared( dog.origin, dog_targets[i].origin );
if( dog_targets[i].hunted_by <= least_hunted.hunted_by && ( !IsDefined( closest_target_dist_squared ) || dist_squared < closest_target_dist_squared ) )
{
least_hunted = dog_targets[i];
closest_target_dist_squared = dist_squared;
}
}
// do not return the default first player if he is invalid
if( !is_target_valid( dog, least_hunted ) )
{
return undefined;
}
else
{
least_hunted.hunted_by += 1;
return least_hunted;
}
}
function get_last_valid_position()
{
if ( IsPlayer( self ) )
return self.last_valid_position;
return self.origin;
}
function get_locomotion_target( behaviorTreeEntity )
{
last_valid_position = behaviorTreeEntity.favoriteenemy get_last_valid_position();
if ( !IsDefined( last_valid_position ) )
return undefined;
locomotion_target = last_valid_position;
if ( ai::has_behavior_attribute( "spacing_value" ) )
{
// calculate the point based on the spacing values
spacing_near_dist = ai::get_behavior_attribute( "spacing_near_dist" );
spacing_far_dist = ai::get_behavior_attribute( "spacing_far_dist" );
spacing_horz_dist = ai::get_behavior_attribute( "spacing_horz_dist" );
spacing_value = ai::get_behavior_attribute( "spacing_value" );
// get the offset from our target and get a vector perpendicular to it
to_enemy = behaviorTreeEntity.favoriteenemy.origin - behaviorTreeEntity.origin;
perp = VectorNormalize( ( -to_enemy[1], to_enemy[0], 0 ) );
// calculate the offset from the target
offset = perp * spacing_horz_dist * spacing_value;
// lerp to actual target position based on distance
spacing_dist = math::clamp( Length( to_enemy ), spacing_near_dist, spacing_far_dist );
lerp_amount = math::clamp( ( spacing_dist - spacing_near_dist ) / ( spacing_far_dist - spacing_near_dist ), 0.0, 1.0 );
desired_point = last_valid_position + ( offset * lerp_amount );
// get the closest pathable point
desired_point = GetClosestPointOnNavMesh( desired_point, spacing_horz_dist * 1.2, 16 );
if ( IsDefined( desired_point ) )
{
///#
//line( behaviorTreeEntity.origin, desired_point, ( 1.0, 1.0, 0.0 ), 0.9, 1 );
//#/
locomotion_target = desired_point;
}
}
return locomotion_target;
}
// ------- ZOMBIE DOG - TARGET SERVICE -----------//
function zombieDogTargetService( behaviorTreeEntity )
{
// don't target if the round is over
if ( ( isdefined( level.intermission ) && level.intermission ) )
{
behaviorTreeEntity ClearPath();
return;
}
// don't target if we're in puppet mode
/#
if ( ( isdefined( behaviortreeentity.isPuppet ) && behaviortreeentity.isPuppet ) )
{
return;
}
#/
// don't move if we don't have a target
if ( behaviorTreeEntity.ignoreall || behaviorTreeEntity.pacifist || ( isdefined( behaviorTreeEntity.favoriteenemy ) && !is_target_valid( behaviorTreeEntity, behaviorTreeEntity.favoriteenemy ) ) )
{
if ( isdefined( behaviorTreeEntity.favoriteenemy ) && isdefined( behaviorTreeEntity.favoriteenemy.hunted_by ) && behaviorTreeEntity.favoriteenemy.hunted_by > 0 )
{
// if we had a target before, decrement its hunted count so it knows we're not after it anymore
behaviorTreeEntity.favoriteenemy.hunted_by--;
}
behaviorTreeEntity.favoriteenemy = undefined;
behaviorTreeEntity.hasSeenFavoriteEnemy = false;
//if enemies are not valid, stay at the spot
if ( !behaviorTreeEntity.ignoreall )
{
behaviorTreeEntity SetGoal( behaviorTreeEntity.origin );
}
return;
}
// dog isn't in playable area yet
if ( ( isdefined( behaviorTreeEntity.ignoreme ) && behaviorTreeEntity.ignoreme ) )
{
return;
}
// calculate a favorite enemy if we don't have one (zombie mode does this in another script)
if( (!SessionModeIsZombiesGame() || behaviorTreeEntity.team == "allies") && !is_target_valid(behaviorTreeEntity, behaviorTreeEntity.favoriteenemy) )
{
behaviorTreeEntity.favoriteenemy = get_favorite_enemy( behaviorTreeEntity );
}
// have we seen our target yet?
if ( !( isdefined( behaviorTreeEntity.hasSeenFavoriteEnemy ) && behaviorTreeEntity.hasSeenFavoriteEnemy ) )
{
if ( IsDefined( behaviorTreeEntity.favoriteenemy ) && behaviorTreeEntity need_to_run() )
{
behaviorTreeEntity.hasSeenFavoriteEnemy = true;
}
}
// always move towards enemy
if ( IsDefined( behaviorTreeEntity.favoriteenemy ) )
{
if ( IsDefined( level.enemy_location_override_func ) )
{
goalPos = [[ level.enemy_location_override_func ]]( behaviorTreeEntity, behaviorTreeEntity.favoriteenemy );
if ( IsDefined( goalPos ) )
{
behaviorTreeEntity SetGoal( goalPos );
return;
}
}
locomotion_target = get_locomotion_target( behaviorTreeEntity );
if ( IsDefined( locomotion_target ) )
{
repathDist = 16;
if ( !IsDefined( behaviorTreeEntity.lastTargetPosition ) || DistanceSquared( behaviorTreeEntity.lastTargetPosition, locomotion_target ) > ( (repathDist) * (repathDist) ) || !behaviorTreeEntity HasPath() )
{
behaviorTreeEntity UsePosition( locomotion_target );
behaviorTreeEntity.lastTargetPosition = locomotion_target;
}
}
}
}
// ------- ZOMBIE DOG - SHOULD MELEE CONDITION -----------//
function zombieDogShouldMelee( behaviorTreeEntity )
{
// don't melee if we don't have a target
if ( behaviorTreeEntity.ignoreall || !is_target_valid( behaviorTreeEntity, behaviorTreeEntity.favoriteenemy ) )
{
return false;
}
if ( !( isdefined( level.intermission ) && level.intermission ) )
{
meleeDist = 6 * 12; // 6 ft
if ( DistanceSquared( behaviorTreeEntity.origin, behaviorTreeEntity.favoriteenemy.origin ) < ( (meleeDist) * (meleeDist) ) && behaviorTreeEntity CanSee( behaviorTreeEntity.favoriteenemy ) )
{
dog_eye = behaviorTreeEntity.origin + ( 0, 0, 40 );
enemy_eye = behaviorTreeEntity.favoriteenemy GetEye();
clip_mask = (1 << 0) | (1 << 3);
trace = PhysicsTrace( dog_eye, enemy_eye, (0, 0, 0), (0, 0, 0), self, clip_mask );
can_melee = ( trace[ "fraction" ] == 1.0 || ( IsDefined( trace[ "entity" ] ) && trace[ "entity" ] == behaviorTreeEntity.favoriteenemy ) );
if ( ( isdefined( can_melee ) && can_melee ) )
{
return true;
}
}
}
return false;
}
// ------- ZOMBIE DOG - SHOULD WALK CONDITION -----------//
function zombieDogShouldWalk( behaviorTreeEntity )
{
return BB_GetShouldRunStatus() == "walk";
}
// ------- ZOMBIE DOG - SHOULD RUN CONDITION -----------//
function zombieDogShouldRun( behaviorTreeEntity )
{
return BB_GetShouldRunStatus() == "run";
}
// ------- ZOMBIE DOG - MELEE ACTION -----------//
function use_low_attack()
{
if ( !IsDefined( self.enemy ) || !IsPlayer( self.enemy ) )
return false;
height_diff = self.enemy.origin[2] - self.origin[2];
low_enough = 30.0;
if ( height_diff < low_enough && self.enemy GetStance() == "prone" )
{
return true;
}
// check if the jumping melee attack origin would be in a solid
melee_origin = ( self.origin[0], self.origin[1], self.origin[2] + 65 );
enemy_origin = ( self.enemy.origin[0], self.enemy.origin[1], self.enemy.origin[2] + 32 );
if ( !BulletTracePassed( melee_origin, enemy_origin, false, self ) )
{
return true;
}
return false;
}
function zombieDogMeleeAction( behaviorTreeEntity, asmStateName )
{
behaviorTreeEntity ClearPath();
context = "high";
if ( behaviorTreeEntity use_low_attack() )
context = "low";
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_context", context );
AnimationStateNetworkUtility::RequestState( behaviorTreeEntity, asmStateName );
return 5;
}
function zombieDogMeleeActionTerminate( behaviorTreeEntity, asmStateName )
{
Blackboard::SetBlackBoardAttribute( behaviorTreeEntity, "_context", undefined );
return 4;
}
function zombieDogGravity( entity, attribute, oldValue, value )
{
Blackboard::SetBlackBoardAttribute( entity, "_low_gravity", value );
}
+69
View File
@@ -0,0 +1,69 @@
#using scripts\shared\ai\systems\blackboard;
#using scripts\shared\ai\systems\shared;
#using scripts\shared\ai\archetype_utility;
Line truncated
#namespace Blackboard;
//*****************************************************************************
// NOTE! When adding a new blackboard variable you must also declare the
// blackboard variable within the ast_definitions file found at:
//
// //t7/main/game/share/raw/animtables/ast_definitions.json
//
// This allows the Animation Selector Table system to determine how to create
// queries based on the blackboard variable.
//
// Also, all blackboard values must be lowercased! No convert to lower is
// perform when assigning values to the blackboard.
//
//*****************************************************************************
function RegisterVehicleBlackBoardAttributes()
{
Assert(IsVehicle(self), "RegisterVehicleBlackBoardAttributes: Should only be called on vehicles");
Blackboard::RegisterBlackBoardAttribute(self,"_speed",undefined,&BB_GetSpeed); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_speed");#/ };
Blackboard::RegisterBlackBoardAttribute(self,"_enemy_yaw",undefined,&BB_VehGetEnemyYaw); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_enemy_yaw");#/ };
}
function BB_GetSpeed()
{
velocity = self GetVelocity();
return Length( velocity );
}
function BB_VehGetEnemyYaw()
{
enemy = self.enemy;
if( !IsDefined( enemy ) )
return 0;
toEnemyYaw = VehGetPredictedYawToEnemy( self, 0.2 );
return toEnemyYaw;
}
function VehGetPredictedYawToEnemy( entity, lookAheadTime )
{
// don't run this more than once per frame
if( IsDefined(entity.predictedYawToEnemy) && IsDefined(entity.predictedYawToEnemyTime) && entity.predictedYawToEnemyTime == GetTime() )
return entity.predictedYawToEnemy;
selfPredictedPos = entity.origin;
moveAngle = entity.angles[1] + entity getMotionAngle();
selfPredictedPos += (cos( moveAngle ), sin( moveAngle ), 0) * 200.0 * lookAheadTime;
yaw = VectorToAngles(entity.enemy.origin - selfPredictedPos)[1] - entity.angles[1];
yaw = AbsAngleClamp360( yaw );
// cache
entity.predictedYawToEnemy = yaw;
entity.predictedYawToEnemyTime = GetTime();
return yaw;
}
+550
View File
@@ -0,0 +1,550 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\postfx_shared;
#using scripts\shared\util_shared;
Line truncated
#precache( "client_fx", "zombie/fx_margwa_teleport_zod_zmb" );
#precache( "client_fx", "zombie/fx_margwa_teleport_travel_zod_zmb" );
#precache( "client_fx", "zombie/fx_margwa_teleport_tell_zod_zmb" );
#precache( "client_fx", "zombie/fx_margwa_teleport_intro_zod_zmb" );
#precache( "client_fx", "zombie/fx_margwa_head_shot_zod_zmb" );
#precache( "client_fx", "zombie/fx_margwa_roar_zod_zmb" );
#precache( "client_fx", "zombie/fx_margwa_roar_purple_zod_zmb" );
#using_animtree( "generic" );
function autoexec main()
{
clientfield::register( "actor", "margwa_head_left", 1, 2, "int", &MargwaClientUtils::margwaHeadLeftCallback, !true, !true );
clientfield::register( "actor", "margwa_head_mid", 1, 2, "int", &MargwaClientUtils::margwaHeadMidCallback, !true, !true );
clientfield::register( "actor", "margwa_head_right", 1, 2, "int", &MargwaClientUtils::margwaHeadRightCallback, !true, !true );
clientfield::register( "actor", "margwa_fx_in", 1, 1, "counter", &MargwaClientUtils::margwaFxInCallback, !true, !true );
clientfield::register( "actor", "margwa_fx_out", 1, 1, "counter", &MargwaClientUtils::margwaFxOutCallback, !true, !true );
clientfield::register( "actor", "margwa_fx_spawn", 1, 1, "counter", &MargwaClientUtils::margwaFxSpawnCallback, !true, !true );
clientfield::register( "actor", "margwa_smash", 1, 1, "counter", &MargwaClientUtils::margwaSmashCallback, !true, !true );
clientfield::register( "actor", "margwa_head_left_hit", 1, 1, "counter", &MargwaClientUtils::margwaLeftHitCallback, !true, !true );
clientfield::register( "actor", "margwa_head_mid_hit", 1, 1, "counter", &MargwaClientUtils::margwaMidHitCallback, !true, !true );
clientfield::register( "actor", "margwa_head_right_hit", 1, 1, "counter", &MargwaClientUtils::margwaRightHitCallback, !true, !true );
clientfield::register( "actor", "margwa_head_killed", 1, 2, "int", &MargwaClientUtils::margwaHeadKilledCallback, !true, !true );
clientfield::register( "actor", "margwa_jaw", 1, 6, "int", &MargwaClientUtils::margwaJawCallback, !true, !true );
clientfield::register( "toplayer", "margwa_head_explosion", 1, 1, "counter", &MargwaClientUtils::margwaHeadExplosion, !true, !true );
clientfield::register( "scriptmover", "margwa_fx_travel", 1, 1, "int", &MargwaClientUtils::margwaFxTravelCallback, !true, !true );
clientfield::register( "scriptmover", "margwa_fx_travel_tell", 1, 1, "int", &MargwaClientUtils::margwaFxTravelTellCallback, !true, !true );
clientfield::register( "actor", "supermargwa", 1, 1, "int", undefined, !true, !true ); // set this bit when spawning supermargwa for ee quest
ai::add_archetype_spawn_function( "margwa", &MargwaClientUtils::margwaSpawn );
level._jaw = [];
level._jaw[ 1 ] = "idle_1";
level._jaw[ 3 ] = "idle_pain_head_l_explode";
level._jaw[ 4 ] = "idle_pain_head_m_explode";
level._jaw[ 5 ] = "idle_pain_head_r_explode";
level._jaw[ 6 ] = "react_stun";
level._jaw[ 8 ] = "react_idgun";
level._jaw[ 9 ] = "react_idgun_pack";
level._jaw[ 7 ] = "run_charge_f";
level._jaw[ 13 ] = "run_f";
level._jaw[ 14 ] = "smash_attack_1";
level._jaw[ 15 ] = "swipe";
level._jaw[ 16 ] = "swipe_player";
level._jaw[ 17 ] = "teleport_in";
level._jaw[ 18 ] = "teleport_out";
level._jaw[ 19 ] = "trv_jump_across_256";
level._jaw[ 20 ] = "trv_jump_down_128";
level._jaw[ 21 ] = "trv_jump_down_36";
level._jaw[ 22 ] = "trv_jump_down_96";
level._jaw[ 23 ] = "trv_jump_up_128";
level._jaw[ 24 ] = "trv_jump_up_36";
level._jaw[ 25 ] = "trv_jump_up_96";
}
function autoexec precache()
{
level._effect[ "fx_margwa_teleport_zod_zmb" ] = "zombie/fx_margwa_teleport_zod_zmb";
level._effect[ "fx_margwa_teleport_travel_zod_zmb" ] = "zombie/fx_margwa_teleport_travel_zod_zmb";
level._effect[ "fx_margwa_teleport_tell_zod_zmb" ] = "zombie/fx_margwa_teleport_tell_zod_zmb";
level._effect[ "fx_margwa_teleport_intro_zod_zmb" ] = "zombie/fx_margwa_teleport_intro_zod_zmb";
level._effect[ "fx_margwa_head_shot_zod_zmb" ] = "zombie/fx_margwa_head_shot_zod_zmb";
level._effect[ "fx_margwa_roar_zod_zmb" ] = "zombie/fx_margwa_roar_zod_zmb";
level._effect[ "fx_margwa_roar_purple_zod_zmb" ] = "zombie/fx_margwa_roar_purple_zod_zmb";
}
#namespace MargwaClientUtils;
function private margwaSpawn( localClientNum )
{
self util::waittill_dobj(localClientNum);
if (!isdefined(self))
return;
self SetAnim( "ai_margwa_head_l_closed_add", 1.0, 0.2, 1.0 );
self SetAnim( "ai_margwa_head_m_closed_add", 1.0, 0.2, 1.0 );
self SetAnim( "ai_margwa_head_r_closed_add", 1.0, 0.2, 1.0 );
for ( i = 1; i <= 7; i++ )
{
leftTentacle = "ai_margwa_tentacle_l_0" + i;
rightTentacle = "ai_margwa_tentacle_r_0" + i;
self SetAnim( leftTentacle, 1.0, 0.2, 1.0 );
self SetAnim( rightTentacle, 1.0, 0.2, 1.0 );
}
level._footstepCBFuncs[self.archetype] = &margwaProcessFootstep;
self.heads = [];
self.heads[ 1 ] = SpawnStruct();
self.heads[ 1 ].index = 1;
self.heads[ 1 ].prevHeadAnim = "ai_margwa_head_l_closed_add";
self.heads[ 1 ].jawBase = "ai_margwa_jaw_l_";
self.heads[ 2 ] = SpawnStruct();
self.heads[ 2 ].index = 2;
self.heads[ 2 ].prevHeadAnim = "ai_margwa_head_m_closed_add";
self.heads[ 2 ].jawBase = "ai_margwa_jaw_m_";
self.heads[ 3 ] = SpawnStruct();
self.heads[ 3 ].index = 3;
self.heads[ 3 ].prevHeadAnim = "ai_margwa_head_r_closed_add";
self.heads[ 3 ].jawBase = "ai_margwa_jaw_r_";
}
function private margwaHeadLeftCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( IsDefined( self.leftGlowFx ) )
{
StopFx( localClientNum, self.leftGlowFx );
}
self util::waittill_dobj(localClientNum);
if (!isdefined(self))
return;
switch ( newValue )
{
case 1:
self.heads[ 1 ].prevHeadAnim = "ai_margwa_head_l_open_add";
self SetAnim( "ai_margwa_head_l_open_add", 1.0, 0.1, 1.0 );
self ClearAnim( "ai_margwa_head_l_closed_add", 0.1 );
roar_effect = level._effect[ "fx_margwa_roar_zod_zmb" ];
if( isDefined( self.margwa_roar_effect ))
{
roar_effect = self.margwa_roar_effect;
}
if( self clientfield::get( "supermargwa" ) )
{
self.leftGlowFx = PlayFxOnTag( localClientNum, level._effect[ "fx_margwa_roar_purple_zod_zmb" ], self, "tag_head_left" );
}
else
{
self.leftGlowFx = PlayFxOnTag( localClientNum, roar_effect, self, "tag_head_left" );
}
break;
case 2:
self.heads[ 1 ].prevHeadAnim = "ai_margwa_head_l_closed_add";
self SetAnim( "ai_margwa_head_l_closed_add", 1.0, 0.1, 1.0 );
self ClearAnim( "ai_margwa_head_l_open_add", 0.1 );
self ClearAnim( "ai_margwa_head_l_smash_attack_1", 0.1 );
break;
case 3:
self.heads[ 1 ].prevHeadAnim = "ai_margwa_head_l_smash_attack_1";
self ClearAnim( "ai_margwa_head_l_open_add", 0.1 );
self ClearAnim( "ai_margwa_head_l_closed_add", 0.1 );
self SetAnimRestart( "ai_margwa_head_l_smash_attack_1", 1, 0.1, 1);
roar_effect = level._effect[ "fx_margwa_roar_zod_zmb" ];
if( isDefined( self.margwa_roar_effect ))
{
roar_effect = self.margwa_roar_effect;
}
if( self clientfield::get( "supermargwa" ) )
{
self.leftGlowFx = PlayFxOnTag( localClientNum, level._effect[ "fx_margwa_roar_purple_zod_zmb" ], self, "tag_head_left" );
}
else
{
self.leftGlowFx = PlayFxOnTag( localClientNum, roar_effect, self, "tag_head_left" );
}
self thread margwaStopSmashFx( localClientNum );
break;
}
}
function private margwaHeadMidCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( IsDefined( self.midGlowFx ) )
{
StopFx( localClientNum, self.midGlowFx );
}
self util::waittill_dobj(localClientNum);
if (!isdefined(self))
return;
switch ( newValue )
{
case 1:
self SetAnim( "ai_margwa_head_m_open_add", 1.0, 0.1, 1.0 );
self ClearAnim( "ai_margwa_head_m_closed_add", 0.1 );
roar_effect = level._effect[ "fx_margwa_roar_zod_zmb" ];
if( isDefined( self.margwa_roar_effect ))
{
roar_effect = self.margwa_roar_effect;
}
if( self clientfield::get( "supermargwa" ) )
{
self.midGlowFx = PlayFxOnTag( localClientNum, level._effect[ "fx_margwa_roar_purple_zod_zmb" ], self, "tag_head_mid" );
}
else
{
self.midGlowFx = PlayFxOnTag( localClientNum, roar_effect, self, "tag_head_mid" );
}
break;
case 2:
self SetAnim( "ai_margwa_head_m_closed_add", 1.0, 0.1, 1.0 );
self ClearAnim( "ai_margwa_head_m_open_add", 0.1 );
self ClearAnim( "ai_margwa_head_m_smash_attack_1", 0.1 );
break;
case 3:
self ClearAnim( "ai_margwa_head_m_open_add", 0.1 );
self ClearAnim( "ai_margwa_head_m_closed_add", 0.1 );
self SetAnimRestart( "ai_margwa_head_m_smash_attack_1", 1, 0.1, 1);
roar_effect = level._effect[ "fx_margwa_roar_zod_zmb" ];
if( isDefined( self.margwa_roar_effect ))
{
roar_effect = self.margwa_roar_effect;
}
if( self clientfield::get( "supermargwa" ) )
{
self.midGlowFx = PlayFxOnTag( localClientNum, level._effect[ "fx_margwa_roar_purple_zod_zmb" ], self, "tag_head_mid" );
}
else
{
self.midGlowFx = PlayFxOnTag( localClientNum, roar_effect, self, "tag_head_mid" );
}
self thread margwaStopSmashFx( localClientNum );
break;
}
}
function private margwaHeadRightCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( IsDefined( self.rightGlowFx ) )
{
StopFx( localClientNum, self.rightGlowFx );
}
self util::waittill_dobj(localClientNum);
if (!isdefined(self))
return;
switch ( newValue )
{
case 1:
self SetAnim( "ai_margwa_head_r_open_add", 1.0, 0.1, 1.0 );
self ClearAnim( "ai_margwa_head_r_closed_add", 0.1 );
roar_effect = level._effect[ "fx_margwa_roar_zod_zmb" ];
if( isDefined( self.margwa_roar_effect ))
{
roar_effect = self.margwa_roar_effect;
}
if( self clientfield::get( "supermargwa" ) )
{
self.rightGlowFx = PlayFxOnTag( localClientNum, level._effect[ "fx_margwa_roar_purple_zod_zmb" ], self, "tag_head_right" );
}
else
{
self.rightGlowFx = PlayFxOnTag( localClientNum, roar_effect, self, "tag_head_right" );
}
break;
case 2:
self SetAnim( "ai_margwa_head_r_closed_add", 1.0, 0.1, 1.0 );
self ClearAnim( "ai_margwa_head_r_open_add", 0.1 );
self ClearAnim( "ai_margwa_head_r_smash_attack_1", 0.1 );
break;
case 3:
self ClearAnim( "ai_margwa_head_r_open_add", 0.1 );
self ClearAnim( "ai_margwa_head_r_closed_add", 0.1 );
self SetAnimRestart( "ai_margwa_head_r_smash_attack_1", 1, 0.1, 1);
roar_effect = level._effect[ "fx_margwa_roar_zod_zmb" ];
if( isDefined( self.margwa_roar_effect ))
{
roar_effect = self.margwa_roar_effect;
}
if( self clientfield::get( "supermargwa" ) )
{
self.rightGlowFx = PlayFxOnTag( localClientNum, level._effect[ "fx_margwa_roar_purple_zod_zmb" ], self, "tag_head_right" );
}
else
{
self.rightGlowFx = PlayFxOnTag( localClientNum, roar_effect, self, "tag_head_right" );
}
self thread margwaStopSmashFx( localClientNum );
break;
}
}
function private margwaStopSmashFx( localClientNum )
{
self endon( "entityshutdown" );
wait( 0.6 );
if ( IsDefined( self.leftGlowFx ) )
{
StopFx( localClientNum, self.leftGlowFx );
}
if ( IsDefined( self.midGlowFx ) )
{
StopFx( localClientNum, self.midGlowFx );
}
if ( IsDefined( self.rightGlowFx ) )
{
StopFx( localClientNum, self.rightGlowFx );
}
}
function private margwaFxInCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
self.teleportFxIn = PlayFx( localClientNum, level._effect[ "fx_margwa_teleport_zod_zmb" ], self GetTagOrigin( "j_spine_1" ) );
}
}
function private margwaFxOutCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
tagPos = self GetTagOrigin( "j_spine_1" );
self.teleportFxOut = PlayFx( localClientNum, level._effect[ "fx_margwa_teleport_zod_zmb" ], tagPos );
}
}
function private margwaFxTravelCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
switch ( newValue )
{
case 0:
DeleteFx( localClientNum, self.travelerFx );
break;
case 1:
self.travelerFx = PlayFxOnTag( localClientNum, level._effect[ "fx_margwa_teleport_travel_zod_zmb" ], self, "tag_origin" );
break;
}
}
function private margwaFxTravelTellCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
switch ( newValue )
{
case 0:
DeleteFx( localClientNum, self.travelerTellFx );
self notify( "stop_margwaTravelTell" );
break;
case 1:
self.travelerTellFx = PlayFxOnTag( localClientNum, level._effect[ "fx_margwa_teleport_tell_zod_zmb" ], self, "tag_origin" );
self thread margwaTravelTellUpdate( localClientNum );
break;
}
}
function private margwaTravelTellUpdate( localClientNum )
{
self notify( "stop_margwaTravelTell" );
self endon( "stop_margwaTravelTell" );
self endon( "entityshutdown" );
player = GetLocalPlayer( localClientNum );
while ( 1 )
{
if( isdefined(player) )
{
dist_sq = DistanceSquared( player.origin, self.origin );
if ( dist_sq < 1000 * 1000 )
{
player PlayRumbleOnEntity( localClientNum, "tank_rumble" );
}
}
wait( 0.05 );
}
}
function private margwaFxSpawnCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
spawnfx = level._effect[ "fx_margwa_teleport_intro_zod_zmb" ];
if( isDefined( self.margwa_spawn_effect ))
{
spawnfx = self.margwa_spawn_effect;
}
if ( isDefined( self.margwa_play_spawn_effect ) )
{
self thread [[ self.margwa_play_spawn_effect ]]( localClientNum );
}
else
{
self.spawnFx = PlayFx( localClientNum, spawnfx, self GetTagOrigin( "j_spine_1" ) );
}
playsound(0, "zmb_margwa_spawn", self GetTagOrigin( "j_spine_1" ) );
}
}
function private margwaHeadExplosion( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
self postfx::PlayPostfxBundle( "pstfx_parasite_dmg" );
}
}
function margwaProcessFootstep( localClientNum, pos, surface, notetrack, bone )
{
e_player = GetLocalPlayer( localClientNum );
n_dist = DistanceSquared( pos, e_player.origin );
n_margwa_dist = ( GetDvarInt( "scr_margwa_footstep_eq_radius", 1000) * GetDvarInt( "scr_margwa_footstep_eq_radius", 1000) );
if(n_margwa_dist>0)
n_scale = ( n_margwa_dist - n_dist ) / n_margwa_dist;
else
return;
if( n_scale > 1 || n_scale < 0 ) return;
n_scale = n_scale * 0.25;
if( n_scale <= 0.01 ) return;
e_player Earthquake( n_scale, 0.1, pos, n_dist );
if( n_scale <= 0.25 && n_scale > 0.2 )
{
e_player PlayRumbleOnEntity( localClientNum, "shotgun_fire" );
}
else if( n_scale <= 0.2 && n_scale > 0.1 )
{
e_player PlayRumbleOnEntity( localClientNum, "damage_heavy" );
}
else
{
e_player PlayRumbleOnEntity( localClientNum, "reload_small" );
}
}
function private margwaSmashCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
e_player = GetLocalPlayer( localClientNum );
smashPos = self.origin + VectorScale( AnglesToForward( self.angles ), 60 );
distSq = DistanceSquared( smashPos, e_player.origin );
if ( distSq < 144 * 144 )
{
e_player Earthquake( 0.7, 0.25, e_player.origin, 3000 );
e_player PlayRumbleOnEntity( localClientNum, "shotgun_fire" );
}
else if ( distSq < 192 * 192 )
{
e_player Earthquake( 0.7, 0.25, e_player.origin, 1500 );
e_player PlayRumbleOnEntity( localClientNum, "damage_heavy" );
}
}
}
function private margwaLeftHitCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
effect = level._effect[ "fx_margwa_head_shot_zod_zmb" ];
if( isdefined( self.margwa_head_hit_fx ))
{
effect = self.margwa_head_hit_fx;
}
self.leftHitFx = PlayFxOnTag( localClientNum, effect, self, "tag_head_left" );
}
}
function private margwaMidHitCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
effect = level._effect[ "fx_margwa_head_shot_zod_zmb" ];
if( isdefined( self.margwa_head_hit_fx ))
{
effect = self.margwa_head_hit_fx;
}
self.midHitFx = PlayFxOnTag( localClientNum, effect, self, "tag_head_mid" );
}
}
function private margwaRightHitCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
effect = level._effect[ "fx_margwa_head_shot_zod_zmb" ];
if( isdefined( self.margwa_head_hit_fx ))
{
effect = self.margwa_head_hit_fx;
}
self.rightHitFx = PlayFxOnTag( localClientNum, effect, self, "tag_head_right" );
}
}
function private margwaHeadKilledCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
self.heads[ newValue ].killed = true;
}
}
function private margwaJawCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
foreach( head in self.heads )
{
if ( ( isdefined( head.killed ) && head.killed ) )
{
if ( IsDefined( head.prevJawAnim ) )
{
self ClearAnim( head.prevJawAnim, 0.2 );
}
if ( IsDefined( head.prevHeadAnim ) )
{
self ClearAnim( head.prevHeadAnim, 0.1 );
}
jawAnim = head.jawBase + level._jaw[ newValue ];
head.prevJawAnim = jawAnim;
self SetAnim( jawAnim, 1.0, 0.2, 1.0 );
}
}
}
}
+1589
View File
File diff suppressed because it is too large. Load diff
+202
View File
@@ -0,0 +1,202 @@
///////////////////////////////////////////////////////////////////////////////
//
// Margwa Defines
//
///////////////////////////////////////////////////////////////////////////////
// bones used for precision damage
// Mouth timing
// Melee ranges
// Smash attack
// Riotshield
// Mavnesh
// FX
///////////////////////////////////////////////////////////////////////////////
//
// Clientfields
//
///////////////////////////////////////////////////////////////////////////////
+288
View File
@@ -0,0 +1,288 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\postfx_shared;
#using scripts\shared\visionset_mgr_shared;
Line truncated
#precache( "client_fx", "dlc1/castle/fx_mech_wpn_flamethrower" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_armor_face" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_armor" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_armor" );
#precache( "client_fx", "dlc1/castle/fx_wpn_115_muz" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_armor" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_armor" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_armor" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_armor" );
#precache( "client_fx", "dlc1/castle/fx_mech_head_light" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_sparks" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_sparks" );
#precache( "client_fx", "dlc1/castle/fx_mech_foot_step" );
#precache( "client_fx", "dlc1/castle/fx_mech_light_dmg" );
#precache( "client_fx", "dlc1/castle/fx_mech_foot_step_steam" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_knee_sparks" );
#precache( "client_fx", "dlc1/castle/fx_mech_dmg_body_light" );
function autoexec main()
{
clientfield::register( "actor", "mechz_ft", 5000, 1, "int", &MechzClientUtils::mechzFlamethrowerCallback, !true, !true );
clientfield::register( "actor", "mechz_faceplate_detached", 5000, 1, "int", &MechzClientUtils::mechz_detach_faceplate, !true, !true );
clientfield::register( "actor", "mechz_powercap_detached", 5000, 1, "int", &MechzClientUtils::mechz_detach_powercap, !true, !true );
clientfield::register( "actor", "mechz_claw_detached", 5000, 1, "int", &MechzClientUtils::mechz_detach_claw, !true, !true );
clientfield::register( "actor", "mechz_115_gun_firing", 5000, 1, "int", &MechzClientUtils::mechz_115_gun_muzzle_flash, !true, !true );
clientfield::register( "actor", "mechz_rknee_armor_detached", 5000, 1, "int", &MechzClientUtils::mechz_detach_rknee_armor, !true, !true );
clientfield::register( "actor", "mechz_lknee_armor_detached", 5000, 1, "int", &MechzClientUtils::mechz_detach_lknee_armor, !true, !true );
clientfield::register( "actor", "mechz_rshoulder_armor_detached", 5000, 1, "int", &MechzClientUtils::mechz_detach_rshoulder_armor, !true, !true );
clientfield::register( "actor", "mechz_lshoulder_armor_detached", 5000, 1, "int", &MechzClientUtils::mechz_detach_lshoulder_armor, !true, !true );
clientfield::register( "actor", "mechz_headlamp_off", 5000, 2, "int", &MechzClientUtils::mechz_headlamp_off, !true, !true );
clientfield::register( "actor", "mechz_face", 1, 3, "int", &MechzClientUtils::mechzFaceCallback, !true, !true );
ai::add_archetype_spawn_function( "mechz", &MechzClientUtils::mechzSpawn );
level._mechz_face = [];
level._mechz_face[ 1 ] = "ai_face_zombie_generic_attack_1";
level._mechz_face[ 2 ] = "ai_face_zombie_generic_death_1";
level._mechz_face[ 3 ] = "ai_face_zombie_generic_idle_1";
level._mechz_face[ 4 ] = "ai_face_zombie_generic_pain_1";
}
function autoexec precache()
{
level._effect[ "fx_mech_wpn_flamethrower" ] = "dlc1/castle/fx_mech_wpn_flamethrower";
level._effect[ "fx_mech_dmg_armor_face" ] = "dlc1/castle/fx_mech_dmg_armor_face";
level._effect[ "fx_mech_dmg_armor" ] = "dlc1/castle/fx_mech_dmg_armor";
level._effect[ "fx_mech_dmg_armor" ] = "dlc1/castle/fx_mech_dmg_armor";
level._effect[ "fx_wpn_115_muz" ] = "dlc1/castle/fx_wpn_115_muz";
level._effect[ "fx_mech_dmg_armor" ] = "dlc1/castle/fx_mech_dmg_armor";
level._effect[ "fx_mech_dmg_armor" ] = "dlc1/castle/fx_mech_dmg_armor";
level._effect[ "fx_mech_dmg_armor" ] = "dlc1/castle/fx_mech_dmg_armor";
level._effect[ "fx_mech_dmg_armor" ] = "dlc1/castle/fx_mech_dmg_armor";
level._effect[ "fx_mech_head_light" ] = "dlc1/castle/fx_mech_head_light";
level._effect[ "fx_mech_dmg_sparks" ] = "dlc1/castle/fx_mech_dmg_sparks";
level._effect[ "fx_mech_dmg_knee_sparks" ] = "dlc1/castle/fx_mech_dmg_knee_sparks";
level._effect[ "fx_mech_dmg_sparks" ] = "dlc1/castle/fx_mech_dmg_sparks";
level._effect[ "fx_mech_foot_step" ] = "dlc1/castle/fx_mech_foot_step";
level._effect[ "fx_mech_light_dmg" ] = "dlc1/castle/fx_mech_light_dmg";
level._effect[ "fx_mech_foot_step_steam" ] = "dlc1/castle/fx_mech_foot_step_steam";
level._effect[ "fx_mech_dmg_body_light" ] = "dlc1/castle/fx_mech_dmg_body_light";
}
#namespace MechzClientUtils;
function private mechzSpawn( localClientNum )
{
level._footstepCBFuncs[ self.archetype ] = &mechzProcessFootstep;
// setting sound context with wait to ensure the entity is fully spawned
level thread mechzSndContext( self );
self.headlight_fx = PlayFXOnTag( localClientNum, level._effect[ "fx_mech_head_light" ], self, "tag_headlamp_FX" );
self.headlamp_on = true;
}
function mechzSndContext ( mechz )
{
wait 1;
if ( IsDefined( mechz ) )
{
mechz setsoundentcontext("movement", "normal");
}
}
function mechzProcessFootstep( localClientNum, pos, surface, notetrack, bone )
{
e_player = GetLocalPlayer( localClientNum );
n_dist = DistanceSquared( pos, e_player.origin );
n_mechz_dist = ( 1000 * 1000 );
if(n_mechz_dist>0)
n_scale = ( n_mechz_dist - n_dist ) / n_mechz_dist;
else
return;
if( n_scale > 1 || n_scale < 0 ) return;
if( n_scale <= 0.01 ) return;
earthquake_scale = n_scale * 0.1;
if( earthquake_scale > 0.01)
{
e_player Earthquake( earthquake_scale, 0.1, pos, n_dist );
}
if( n_scale <= 1 && n_scale > 0.8 )
{
e_player PlayRumbleOnEntity( localClientNum, "shotgun_fire" );
}
else if( n_scale <= 0.8 && n_scale > 0.4 )
{
e_player PlayRumbleOnEntity( localClientNum, "damage_heavy" );
}
else
{
e_player PlayRumbleOnEntity( localClientNum, "reload_small" );
}
fx = PlayFXOnTag( localClientNum, level._effect[ "fx_mech_foot_step" ], self, bone );
if( bone == "j_ball_le" )
{
steam_bone = "tag_foot_steam_le";
}
else
{
steam_bone = "tag_foot_steam_ri";
}
steam_fx = PlayFXOnTag( localClientNum, level._effect[ "fx_mech_foot_step_steam" ], self, steam_bone );
}
function private mechzFlamethrowerCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
switch ( newValue )
{
case 1:
self.fire_beam_id = BeamLaunch( localclientnum, self, "tag_flamethrower_fx", undefined, "none", "flamethrower_beam_3p_zm_mechz" );
self playsound (0 , "wpn_flame_thrower_start_mechz");
self.sndLoopID = self PlayLoopSound ("wpn_flame_thrower_loop_mechz");
break;
case 0:
self notify( "stopFlamethrower" );
if ( IsDefined( self.fire_beam_id ) )
{
BeamKill( localclientnum, self.fire_beam_id );
self playsound (0 , "wpn_flame_thrower_stop_mechz");
self stoploopsound ( self.sndLoopID );
}
break;
}
}
function mechz_detach_faceplate( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
pos = self gettagorigin( "j_faceplate" );
ang = self gettagangles( "j_faceplate" );
velocity = self GetVelocity();
dynent = CreateDynEntAndLaunch( localClientNum, "c_zom_mech_faceplate", pos, ang, self.origin, velocity );
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_armor_face" ], self, "j_faceplate" );
self setsoundentcontext("movement", "loud");
self playsound (0, "zmb_ai_mechz_faceplate");
}
function mechz_detach_powercap( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
pos = self gettagorigin( "tag_powersupply" );
ang = self gettagangles( "tag_powersupply" );
velocity = self GetVelocity();
dynent = CreateDynEntAndLaunch( localClientNum, "c_zom_mech_powersupply_cap", pos, ang, self.origin, velocity );
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_armor" ], self, "tag_powersupply" );
self playsound (0, "zmb_ai_mechz_destruction");
self.mechz_powercore_fx = playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_body_light" ], self, "tag_powersupply" );
}
function mechz_detach_claw( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if( IsDefined( level.mechz_detach_claw_override ) )
{
self [[level.mechz_detach_claw_override]]( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump );
return;
}
pos = self gettagorigin( "tag_gun_spin" );
ang = self gettagangles( "tag_gun_spin" );
velocity = self GetVelocity();
dynent = CreateDynEntAndLaunch( localClientNum, "c_zom_mech_gun_barrel", pos, ang, self.origin, velocity );
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_armor" ], self, "tag_gun_spin" );
self playsound (0, "zmb_ai_mechz_destruction");
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_sparks" ], self, "tag_gun_spin" );
}
function mechz_115_gun_muzzle_flash( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
playFXonTag( localClientNum, level._effect[ "fx_wpn_115_muz" ], self, "tag_gun_barrel2" );
}
function mechz_detach_rknee_armor( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
pos = self gettagorigin( "j_knee_attach_ri" );
ang = self gettagangles( "j_knee_attach_ri" );
velocity = self GetVelocity();
dynent = CreateDynEntAndLaunch( localClientNum, "c_zom_mech_armor_knee_right", pos, ang, pos, velocity );
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_armor" ], self, "j_knee_attach_ri" );
self playsound (0, "zmb_ai_mechz_destruction");
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_knee_sparks" ], self, "j_knee_attach_ri" );
}
function mechz_detach_lknee_armor( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
pos = self gettagorigin( "j_knee_attach_le" );
ang = self gettagangles( "j_knee_attach_le" );
velocity = self GetVelocity();
dynent = CreateDynEntAndLaunch( localClientNum, "c_zom_mech_armor_knee_left", pos, ang, pos, velocity );
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_armor" ], self, "j_knee_attach_le" );
self playsound (0, "zmb_ai_mechz_destruction");
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_knee_sparks" ], self, "j_knee_attach_le" );
}
function mechz_detach_rshoulder_armor( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
pos = self gettagorigin( "j_shoulderarmor_ri" );
ang = self gettagangles( "j_shoulderarmor_ri" );
velocity = self GetVelocity();
dynent = CreateDynEntAndLaunch( localClientNum, "c_zom_mech_armor_shoulder_right", pos, ang, pos, velocity );
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_armor" ], self, "j_shoulderarmor_ri" );
self playsound (0, "zmb_ai_mechz_destruction");
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_sparks" ], self, "j_shoulderarmor_ri" );
}
function mechz_detach_lshoulder_armor( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
pos = self gettagorigin( "j_shoulderarmor_le" );
ang = self gettagangles( "j_shoulderarmor_le" );
velocity = self GetVelocity();
dynent = CreateDynEntAndLaunch( localClientNum, "c_zom_mech_armor_shoulder_left", pos, ang, pos, velocity );
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_armor" ], self, "j_shoulderarmor_le" );
self playsound (0, "zmb_ai_mechz_destruction");
playFXonTag( localClientNum, level._effect[ "fx_mech_dmg_sparks" ], self, "j_shoulderarmor_le" );
}
function mechz_headlamp_off( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if( self.headlamp_on === true && newValue != 0 && isDefined( self.headlight_fx ))
{
StopFX( localClientNum, self.headlight_fx );
self.headlamp_on = false;
if( newValue == 2 )
{
playFXonTag( localClientNum, level._effect[ "fx_mech_foot_step" ], self, "tag_headlamp_fx" );
//play break fx and sound
}
}
}
function private mechzFaceCallback( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
if ( newValue )
{
if ( IsDefined( self.prevFaceAnim ) )
{
self ClearAnim( self.prevFaceAnim, 0.2 );
}
faceAnim = level._mechz_face[ newValue ];
self SetAnim( faceAnim, 1.0, 0.2, 1.0 );
self.prevFaceAnim = faceAnim;
}
}
+1711
View File
File diff suppressed because it is too large. Load diff
+162
View File
@@ -0,0 +1,162 @@
///////////////////////////////////////////////////////////////////////////////
//
// Mechz Defines
//
///////////////////////////////////////////////////////////////////////////////
// Melee ranges
// Mavnesh
// Models
// Tags
// FX
// Face anims
// Grenade
// Flamethrower
// Aiming
// Damage
// Detachable Part Health
// Berserk
// Stun
// React
+566
View File
@@ -0,0 +1,566 @@
#using scripts\shared\ai_shared;
#using scripts\shared\math_shared;
#using scripts\shared\spawner_shared;
#using scripts\shared\ai\archetype_utility;
Line truncated
#namespace Phalanx;
function private _AssignPhalanxStance( sentients, stance )
{
assert( IsArray( sentients ) );
foreach ( index, sentient in sentients )
{
if ( IsDefined( sentient) && IsAlive( sentient ) )
{
sentient ai::set_behavior_attribute( "phalanx_force_stance", stance );
}
}
}
function private _CreatePhalanxTier(
phalanxType, tier, phalanxPosition, forward, maxTierSize, spawner = undefined )
{
sentients = [];
if ( !IsSpawner( spawner ) )
{
spawner = _GetPhalanxSpawner( tier );
}
positions = _GetPhalanxPositions( phalanxType, tier );
angles = VectorToAngles( forward );
foreach ( index, position in positions )
{
if ( index >= maxTierSize )
{
break;
}
orientedPos = _RotateVec( position, angles[1] - 90 );
navMeshPosition = GetClosestPointOnNavMesh(
phalanxPosition + orientedPos, 200 );
// Make sure the spawner can actually spawn a sentient.
if ( !( spawner.spawnflags & 64 ) )
{
spawner.count++;
}
sentient = spawner spawner::spawn( true, "", navMeshPosition, angles );
_InitializeSentient( sentient );
// Wait till all sentients have been created.
{wait(.05);};
sentients[ sentients.size ] = sentient;
}
return sentients;
}
// Caps the maximum damage the phalanx takes from a single explosive entity.
function private _DampenExplosiveDamage(
inflictor, attacker, damage, flags, meansOfDamage, weapon, point, dir, hitLoc, offsetTime, boneIndex, modelIndex )
{
entity = self;
isExplosive = IsInArray(
array(
"MOD_GRENADE",
"MOD_GRENADE_SPLASH",
"MOD_PROJECTILE",
"MOD_PROJECTILE_SPLASH",
"MOD_EXPLOSIVE" ),
meansOfDamage );
if ( isExplosive && IsDefined( inflictor ) && IsDefined( inflictor.weapon ) )
{
weapon = inflictor.weapon;
distanceToEntity = Distance( entity.origin, inflictor.origin );
// Linear falloff from grenade distance.
fractionDistance = 1;
if ( weapon.explosionradius > 0 )
{
fractionDistance = ( weapon.explosionradius - distanceToEntity ) / weapon.explosionradius;
}
// This causes near exponential damage falloff since the original damage already considers radius at a near linear falloff.
return Int( Max( damage * fractionDistance, 1 ) );
}
return damage;
}
function private _GetPhalanxPositions( phalanxType, tier )
{
switch ( phalanxType )
{
case "phanalx_wedge":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( -64, -48, 0 ), ( 64, -48, 0 ), ( -128, -96, 0 ), ( 128, -96, 0 ) );
case "phalanx_tier2":
return array( ( -32, -96, 0 ), ( 32, -96, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_reverse_wedge":
switch( tier )
{
case "phalanx_tier1":
return array( ( -32, 0, 0 ), ( 32, 0, 0 ) );
case "phalanx_tier2":
return array( ( 0, -96, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_diagonal_left":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( -48, -64, 0 ), ( -96, -128, 0 ), ( -144, -192, 0 ) );
case "phalanx_tier2":
return array( ( 64, 0, 0 ), ( 16, -64, 0 ), ( -48, -128, 0 ), ( -112, -192, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_diagonal_right":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( 48, -64, 0 ), ( 96, -128, 0 ), ( 144, -192, 0 ) );
case "phalanx_tier2":
return array( ( -64, 0, 0 ), ( -16, -64, 0 ), ( 48, -128, 0 ), ( 112, -192, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_forward":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( 64, 0, 0 ), ( 128, 0, 0 ), ( 192, 0, 0 ) );
case "phalanx_tier2":
return array( ( -32, -64, 0 ), ( 32, -64, 0 ), ( 96, -64, 0 ), ( 160, -64, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_column":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( -64, 0, 0 ), ( 0, -64, 0 ), ( -64, -64, 0 ) );
case "phalanx_tier2":
return array( ( 0, -128, 0 ), ( -64, -128, 0 ), ( 0, -192, 0 ), ( -64, -192, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_column_right":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( 0, -64, 0 ), ( 0, -128, 0 ), ( 0, -192, 0 ) );
case "phalanx_tier2":
return array( );
case "phalanx_tier3":
return array( );
}
break;
default:
assert( "Unknown phalanx type \"" + phalanxType + "\"." );
}
assert( "Unknown phalanx tier \"" + tier + "\"." );
}
function private _GetPhalanxSpawner( tier )
{
spawner = GetSpawnerArray( tier, "targetname" );
assert( spawner.size >= 0,
"No spawners for the phalanx system were found, make sure you include " +
"the \"game/map_source/_prefabs/ai/robot_phalanx.map\" prefab within your " +
"map to use the system." );
assert( spawner.size == 1,
"Too many spawners for the phalanx system were found, make sure you " +
"don't include multiple copies of the " +
"\"game/map_source/_prefabs/ai/robot_phalanx.map\" prefab in your map." );
return spawner[0];
}
function private _HaltAdvance( sentients )
{
assert( IsArray( sentients ) );
foreach ( index, sentient in sentients )
{
if ( IsDefined( sentient ) && IsAlive( sentient ) && sentient HasPath() )
{
navMeshPosition = GetClosestPointOnNavMesh(
sentient.origin, 200 );
sentient UsePosition( navMeshPosition );
sentient ClearPath();
}
}
}
function private _HaltFire( sentients )
{
assert( IsArray( sentients ) );
foreach ( index, sentient in sentients )
{
if ( IsDefined( sentient ) && IsAlive( sentient ) )
{
sentient.ignoreall = true;
}
}
}
function _InitializeSentient( sentient )
{
assert( IsActor( sentient ) );
sentient ai::set_behavior_attribute( "phalanx", true );
if ( sentient.archetype === "human" )
{
sentient.allowPain = false;
}
sentient SetAvoidanceMask( "avoid none" );
if ( (isdefined(sentient.archetype) && ( sentient.archetype == "robot" )) )
{
sentient ai::set_behavior_attribute( "move_mode", "marching" );
sentient ai::set_behavior_attribute( "force_cover", true );
}
AiUtility::AddAIOverrideDamageCallback( sentient, &_DampenExplosiveDamage, true );
}
function private _MovePhalanxTier( sentients, phalanxType, tier, destination, forward )
{
positions = _GetPhalanxPositions( phalanxType, tier );
angles = VectorToAngles( forward );
assert( sentients.size <= positions.size,
"There must be enough positions for the phalanx tier to move to." );
foreach ( index, sentient in sentients )
{
if ( IsDefined( sentient ) && IsAlive( sentient ) )
{
assert( IsVec( positions[ index ] ),
"Must have a formation position for position(" + index + ") in tier " +
tier + " of formation " + phalanxType );
orientedPos = _RotateVec( positions[ index ], angles[1] - 90 );
navMeshPosition = GetClosestPointOnNavMesh(
destination + orientedPos, 200 );
sentient UsePosition( navMeshPosition );
}
}
}
function private _PruneDead( sentients )
{
liveSentients = [];
// Removes dead sentients and keeps living ones with the same array index.
foreach ( index, sentient in sentients )
{
if ( IsDefined( sentient ) && IsAlive( sentient ) )
{
liveSentients[ index ] = sentient;
}
}
return liveSentients;
}
function private _ReleaseSentient( sentient )
{
if ( IsDefined( sentient ) && IsAlive( sentient ) )
{
sentient ClearUsePosition();
sentient PathMode( "move delayed", true, RandomFloatRange( 0.5, 1 ) );
sentient ai::set_behavior_attribute( "phalanx", false );
// Wait a frame to make sure sentients's are released from the phalanx.
{wait(.05);};
if ( sentient.archetype === "human" )
{
sentient.allowPain = true;
}
sentient SetAvoidanceMask( "avoid all" );
AiUtility::RemoveAIOverrideDamageCallback( sentient, &_DampenExplosiveDamage );
if ( (isdefined(sentient.archetype) && ( sentient.archetype == "robot" )) )
{
sentient ai::set_behavior_attribute( "move_mode", "normal" );
sentient ai::set_behavior_attribute( "force_cover", false );
}
}
}
function private _ReleaseSentients( sentients )
{
foreach ( index, sentient in sentients )
{
_ResumeFire( sentient );
_ReleaseSentient( sentient );
// Release sentients slowly from their group.
wait RandomFloatRange( 0.5, 5 );
}
}
function private _ResumeFire( sentient )
{
if ( IsDefined( sentient ) && IsAlive( sentient ) )
{
sentient.ignoreall = false;
}
}
function private _ResumeFireSentients( sentients )
{
assert( IsArray( sentients ) );
foreach ( index, sentient in sentients )
{
_ResumeFire( sentient );
}
}
function private _RotateVec( vector, angle )
{
return ( vector[0] * Cos( angle ) - vector[1] * Sin( angle ),
vector[0] * Sin( angle ) + vector[1] * Cos( angle ),
vector[2] );
}
function private _UpdatePhalanxThread( phalanx )
{
while ( [[ phalanx ]]->_UpdatePhalanx() )
{
wait 1;
}
}
class Phalanx
{
// All tiers of sentients.
var sentientTiers_;
// Total starting sentients in the formation.
var startSentientCount_;
// Current count of all sentients in the formation.
var currentSentientCount_;
// Number of sentients that must die for the formation to scatter.
var breakingPoint_;
// Phalanx move positions
var startPosition_;
var endPosition_;
// Phalanx type
var phalanxType_;
// Mark whether the formation has already been scattered.
var scattered_;
constructor()
{
sentientTiers_ = [];
startSentientCount_ = 0;
currentSentientCount_ = 0;
breakingPoint_ = 0;
scattered_ = false;
}
destructor()
{
}
function private _UpdatePhalanx()
{
if ( scattered_ )
{
// Terminate the phalanx if someone else has already scattered the formation.
return false;
}
currentSentientCount_ = 0;
// Discard dead sentients from the phalanx.
foreach ( name, tier in sentientTiers_ )
{
sentientTiers_[ name ] = Phalanx::_PruneDead( tier );
currentSentientCount_ += sentientTiers_[ name ].size;
}
// Break up the phalanx if enough sentients died.
if ( currentSentientCount_ <= ( startSentientCount_ - breakingPoint_ ) )
{
ScatterPhalanx();
return false;
}
return true;
}
function HaltFire()
{
foreach ( name, tier in sentientTiers_ )
{
Phalanx::_HaltFire( tier );
}
}
function HaltAdvance()
{
if ( !scattered_ )
{
foreach ( name, tier in sentientTiers_ )
{
Phalanx::_HaltAdvance( tier );
}
}
}
function Initialize(
phalanxType,
origin,
destination,
breakingPoint,
maxTierSize = 10,
tierOneSpawner = undefined,
tierTwoSpawner = undefined,
tierThreeSpawner = undefined )
{
assert( IsString( phalanxType ) );
assert( IsInt( breakingPoint ) );
assert( IsVec( origin ) );
assert( IsVec( destination ) );
tierSpawners = [];
tierSpawners[ "phalanx_tier1" ] = tierOneSpawner;
tierSpawners[ "phalanx_tier2" ] = tierTwoSpawner;
tierSpawners[ "phalanx_tier3" ] = tierThreeSpawner;
maxTierSize = math::clamp( maxTierSize, 1, 10 );
forward = VectorNormalize( destination - origin );
foreach ( tierName in array( "phalanx_tier1", "phalanx_tier2", "phalanx_tier3" ) )
{
sentientTiers_[ tierName ] = Phalanx::_CreatePhalanxTier(
phalanxType, tierName, origin, forward, maxTierSize, tierSpawners[ tierName ] );
startSentientCount_ += sentientTiers_[ tierName ].size;
}
// The first tier facing the enemy always crouches.
Phalanx::_AssignPhalanxStance( sentientTiers_[ "phalanx_tier1" ], "crouch" );
// Assign phalanx positions to all sentient tiers.
foreach ( name, tier in sentientTiers_ )
{
Phalanx::_MovePhalanxTier(
sentientTiers_[ name ], phalanxType, name, destination, forward );
}
breakingPoint_ = breakingPoint;
startPosition_ = origin;
endPosition_ = destination;
phalanxType_ = phalanxType;
// Initiate the main update loop, a single thread that updates the phalanx.
self thread Phalanx::_UpdatePhalanxThread( self );
}
function ResumeAdvance()
{
if ( !scattered_ )
{
Phalanx::_AssignPhalanxStance( sentientTiers_[ "phalanx_tier1" ], "stand" );
// TODO(David Young 10-21-14): Too hardcoded, waiting for animation to complete.
wait 1;
forward = VectorNormalize( endPosition_ - startPosition_ );
Phalanx::_MovePhalanxTier(
sentientTiers_[ "phalanx_tier1" ], phalanxType_, "phalanx_tier1", endPosition_, forward );
Phalanx::_MovePhalanxTier(
sentientTiers_[ "phalanx_tier2" ], phalanxType_, "phalanx_tier2", endPosition_, forward );
Phalanx::_MovePhalanxTier(
sentientTiers_[ "phalanx_tier3" ], phalanxType_, "phalanx_tier3", endPosition_, forward );
Phalanx::_AssignPhalanxStance( sentientTiers_[ "phalanx_tier1" ], "crouch" );
}
}
function ResumeFire()
{
Phalanx::_ResumeFireSentients( sentientTiers_[ "phalanx_tier1" ] );
Phalanx::_ResumeFireSentients( sentientTiers_[ "phalanx_tier2" ] );
Phalanx::_ResumeFireSentients( sentientTiers_[ "phalanx_tier3" ] );
}
function ScatterPhalanx()
{
if ( !scattered_ )
{
scattered_ = true;
Phalanx::_ReleaseSentients( sentientTiers_[ "phalanx_tier1" ] );
sentientTiers_[ "phalanx_tier1" ] = [];
Phalanx::_AssignPhalanxStance( sentientTiers_[ "phalanx_tier2" ], "crouch" );
// Settling time for the tier.
wait RandomFloatRange( 5, 7 );
Phalanx::_ReleaseSentients( sentientTiers_[ "phalanx_tier2" ] );
sentientTiers_[ "phalanx_tier2" ] = [];
Phalanx::_AssignPhalanxStance( sentientTiers_[ "phalanx_tier3" ], "crouch" );
// Settling time for the tier.
wait RandomFloatRange( 5, 7 );
Phalanx::_ReleaseSentients( sentientTiers_[ "phalanx_tier3" ] );
sentientTiers_[ "phalanx_tier3" ] = [];
}
}
}
+138
View File
@@ -0,0 +1,138 @@
///////////////////////////////////////////////////////////////////////////////
//
// Formations
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Phalanx Tiers
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Formation Positions
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Phalanx Defines
//
///////////////////////////////////////////////////////////////////////////////
+538
View File
@@ -0,0 +1,538 @@
#using scripts\shared\ai_shared;
#using scripts\shared\math_shared;
#using scripts\shared\spawner_shared;
#using scripts\shared\ai\archetype_utility;
Line truncated
#namespace RobotPhalanx;
function private _AssignPhalanxStance( robots, stance )
{
assert( IsArray( robots ) );
foreach ( index, robot in robots )
{
if ( IsDefined( robot) && IsAlive( robot ) )
{
robot ai::set_behavior_attribute( "phalanx_force_stance", stance );
}
}
}
function private _CreatePhalanxTier(
phalanxType, tier, phalanxPosition, forward, maxTierSize, spawner = undefined )
{
robots = [];
if ( !IsSpawner( spawner ) )
{
spawner = _GetPhalanxSpawner( tier );
}
positions = _GetPhalanxPositions( phalanxType, tier );
angles = VectorToAngles( forward );
foreach ( index, position in positions )
{
if ( index >= maxTierSize )
{
break;
}
orientedPos = _RotateVec( position, angles[1] - 90 );
navMeshPosition = GetClosestPointOnNavMesh(
phalanxPosition + orientedPos, 200 );
// Make sure the spawner can actually spawn a robot.
if ( !( spawner.spawnflags & 64 ) )
{
spawner.count++;
}
robot = spawner spawner::spawn( true, "", navMeshPosition, angles );
if ( IsAlive( robot ) )
{
_InitializeRobot( robot );
// Wait till all robots have been created.
{wait(.05);};
robots[ robots.size ] = robot;
}
}
return robots;
}
// Caps the maximum damage the phalanx takes from a single explosive entity.
function private _DampenExplosiveDamage(
inflictor, attacker, damage, flags, meansOfDamage, weapon, point, dir, hitLoc, offsetTime, boneIndex, modelIndex )
{
entity = self;
isExplosive = IsInArray(
array(
"MOD_GRENADE",
"MOD_GRENADE_SPLASH",
"MOD_PROJECTILE",
"MOD_PROJECTILE_SPLASH",
"MOD_EXPLOSIVE" ),
meansOfDamage );
if ( isExplosive && IsDefined( inflictor ) && IsDefined( inflictor.weapon ) )
{
weapon = inflictor.weapon;
distanceToEntity = Distance( entity.origin, inflictor.origin );
// Linear falloff from grenade distance.
fractionDistance = 1;
if ( weapon.explosionradius > 0 )
{
fractionDistance = ( weapon.explosionradius - distanceToEntity ) / weapon.explosionradius;
}
// This causes near exponential damage falloff since the original damage already considers radius at a near linear falloff.
return Int( Max( damage * fractionDistance, 1 ) );
}
return damage;
}
function private _GetPhalanxPositions( phalanxType, tier )
{
switch ( phalanxType )
{
case "phanalx_wedge":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( -64, -48, 0 ), ( 64, -48, 0 ), ( -128, -96, 0 ), ( 128, -96, 0 ) );
case "phalanx_tier2":
return array( ( -32, -96, 0 ), ( 32, -96, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_diagonal_left":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( -48, -64, 0 ), ( -96, -128, 0 ), ( -144, -192, 0 ) );
case "phalanx_tier2":
return array( ( 64, 0, 0 ), ( 16, -64, 0 ), ( -48, -128, 0 ), ( -112, -192, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_diagonal_right":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( 48, -64, 0 ), ( 96, -128, 0 ), ( 144, -192, 0 ) );
case "phalanx_tier2":
return array( ( -64, 0, 0 ), ( -16, -64, 0 ), ( 48, -128, 0 ), ( 112, -192, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_forward":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( 64, 0, 0 ), ( 128, 0, 0 ), ( 192, 0, 0 ) );
case "phalanx_tier2":
return array( ( -32, -64, 0 ), ( 32, -64, 0 ), ( 96, -64, 0 ), ( 160, -64, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_column":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( -64, 0, 0 ), ( 0, -64, 0 ), ( -64, -64, 0 ) );
case "phalanx_tier2":
return array( ( 0, -128, 0 ), ( -64, -128, 0 ), ( 0, -192, 0 ), ( -64, -192, 0 ) );
case "phalanx_tier3":
return array( );
}
break;
case "phalanx_column_right":
switch( tier )
{
case "phalanx_tier1":
return array( ( 0, 0, 0 ), ( 0, -64, 0 ), ( 0, -128, 0 ), ( 0, -192, 0 ) );
case "phalanx_tier2":
return array( );
case "phalanx_tier3":
return array( );
}
break;
default:
assert( "Unknown phalanx type \"" + phalanxType + "\"." );
}
assert( "Unknown phalanx tier \"" + tier + "\"." );
}
function private _GetPhalanxSpawner( tier )
{
spawner = GetSpawnerArray( tier, "targetname" );
assert( spawner.size >= 0,
"No spawners for the robot phalanx system were found, make sure you include " +
"the \"game/map_source/_prefabs/ai/robot_phalanx.map\" prefab within your " +
"map to use the system." );
assert( spawner.size == 1,
"Too many spawners for the robot phalanx system were found, make sure you " +
"don't include multiple copies of the " +
"\"game/map_source/_prefabs/ai/robot_phalanx.map\" prefab in your map." );
return spawner[0];
}
function private _HaltAdvance( robots )
{
assert( IsArray( robots ) );
foreach ( index, robot in robots )
{
if ( IsDefined( robot) && IsAlive( robot ) && robot HasPath() )
{
navMeshPosition = GetClosestPointOnNavMesh(
robot.origin, 200 );
robot UsePosition( navMeshPosition );
robot ClearPath();
}
}
}
function private _HaltFire( robots )
{
assert( IsArray( robots ) );
foreach ( index, robot in robots )
{
if ( IsDefined( robot) && IsAlive( robot ) )
{
robot.ignoreall = true;
}
}
}
function private _InitializeRobot( robot )
{
assert( IsActor( robot ) );
robot ai::set_behavior_attribute( "phalanx", true );
robot ai::set_behavior_attribute( "move_mode", "marching" );
robot ai::set_behavior_attribute( "force_cover", true );
// robot.allowPain = false;
robot SetAvoidanceMask( "avoid none" );
AiUtility::AddAIOverrideDamageCallback( robot, &_DampenExplosiveDamage, true );
}
function private _MovePhalanxTier( robots, phalanxType, tier, destination, forward )
{
positions = _GetPhalanxPositions( phalanxType, tier );
angles = VectorToAngles( forward );
assert( robots.size <= positions.size,
"There must be enough positions for the phalanx tier to move to." );
foreach ( index, robot in robots )
{
if ( IsDefined( robot ) && IsAlive( robot ) )
{
assert( IsVec( positions[ index ] ),
"Must have a formation position for position(" + index + ") in tier " +
tier + " of formation " + phalanxType );
orientedPos = _RotateVec( positions[ index ], angles[1] - 90 );
navMeshPosition = GetClosestPointOnNavMesh(
destination + orientedPos, 200 );
robot UsePosition( navMeshPosition );
}
}
}
function private _PruneDead( robots )
{
liveRobots = [];
// Removes dead robots and keeps living ones with the same array index.
foreach ( index, robot in robots )
{
if ( IsDefined( robot ) && IsAlive( robot ) )
{
liveRobots[ index ] = robot;
}
}
return liveRobots;
}
function private _ReleaseRobot( robot )
{
if ( IsDefined( robot ) && IsAlive( robot ) )
{
robot ClearUsePosition();
robot PathMode( "move delayed", true, RandomFloatRange( 0.5, 1 ) );
robot ai::set_behavior_attribute( "phalanx", false );
// Wait a frame to make sure robot's are released from the phalanx.
{wait(.05);};
robot ai::set_behavior_attribute( "move_mode", "normal" );
robot ai::set_behavior_attribute( "force_cover", false );
// robot.allowPain = true;
robot SetAvoidanceMask( "avoid all" );
AiUtility::RemoveAIOverrideDamageCallback( robot, &_DampenExplosiveDamage );
}
}
function private _ReleaseRobots( robots )
{
foreach ( index, robot in robots )
{
_ResumeFire( robot );
_ReleaseRobot( robot );
// Release robots slowly from their group.
wait RandomFloatRange( 0.5, 5 );
}
}
function private _ResumeFire( robot )
{
if ( IsDefined( robot) && IsAlive( robot ) )
{
robot.ignoreall = false;
}
}
function private _ResumeFireRobots( robots )
{
assert( IsArray( robots ) );
foreach ( index, robot in robots )
{
_ResumeFire( robot );
}
}
function private _RotateVec( vector, angle )
{
return ( vector[0] * Cos( angle ) - vector[1] * Sin( angle ),
vector[0] * Sin( angle ) + vector[1] * Cos( angle ),
vector[2] );
}
function private _UpdatePhalanxThread( phalanx )
{
while ( [[ phalanx ]]->_UpdatePhalanx() )
{
wait 1;
}
}
class RobotPhalanx
{
// Directly manage each tier of robots.
var tier1Robots_;
var tier2Robots_;
var tier3Robots_;
// Total starting robots in the formation.
var startRobotCount_;
// Current count of all robots in the formation.
var currentRobotCount_;
// Number of robots that must die for the formation to scatter.
var breakingPoint_;
// Phalanx move positions
var startPosition_;
var endPosition_;
// Phalanx type
var phalanxType_;
// Mark whether the formation has already been scattered.
var scattered_;
constructor()
{
tier1Robots_ = [];
tier2Robots_ = [];
tier3Robots_ = [];
startRobotCount_ = 0;
currentRobotCount_ = 0;
breakingPoint_ = 0;
scattered_ = false;
}
destructor()
{
}
function private _UpdatePhalanx()
{
if ( scattered_ )
{
// Terminate the phalanx if someone else has already scattered the formation.
return false;
}
// Discard dead robots from the phalanx.
tier1Robots_ = RobotPhalanx::_PruneDead( tier1Robots_ );
tier2Robots_ = RobotPhalanx::_PruneDead( tier2Robots_ );
tier3Robots_ = RobotPhalanx::_PruneDead( tier3Robots_ );
currentRobotCount_ = tier1Robots_.size + tier2Robots_.size + tier2Robots_.size;
// Break up the phalanx if enough robots died.
if ( currentRobotCount_ <= ( startRobotCount_ - breakingPoint_ ) )
{
ScatterPhalanx();
return false;
}
return true;
}
function HaltFire()
{
RobotPhalanx::_HaltFire( tier1Robots_ );
RobotPhalanx::_HaltFire( tier2Robots_ );
RobotPhalanx::_HaltFire( tier3Robots_ );
}
function HaltAdvance()
{
if ( !scattered_ )
{
RobotPhalanx::_HaltAdvance( tier1Robots_ );
RobotPhalanx::_HaltAdvance( tier2Robots_ );
RobotPhalanx::_HaltAdvance( tier3Robots_ );
}
}
function Initialize(
phalanxType,
origin,
destination,
breakingPoint,
maxTierSize = 10,
tierOneSpawner = undefined,
tierTwoSpawner = undefined,
tierThreeSpawner = undefined )
{
assert( IsString( phalanxType ) );
assert( IsInt( breakingPoint ) );
assert( IsVec( origin ) );
assert( IsVec( destination ) );
maxTierSize = math::clamp( maxTierSize, 1, 10 );
forward = VectorNormalize( destination - origin );
tier1Robots_ = RobotPhalanx::_CreatePhalanxTier(
phalanxType, "phalanx_tier1", origin, forward, maxTierSize, tierOneSpawner );
tier2Robots_ = RobotPhalanx::_CreatePhalanxTier(
phalanxType, "phalanx_tier2", origin, forward, maxTierSize, tierTwoSpawner );
tier3Robots_ = RobotPhalanx::_CreatePhalanxTier(
phalanxType, "phalanx_tier3", origin, forward, maxTierSize, tierThreeSpawner );
// The first tier facing the enemy always crouches.
RobotPhalanx::_AssignPhalanxStance( tier1Robots_, "crouch" );
// Assign phalanx positions to all robot tiers.
RobotPhalanx::_MovePhalanxTier(
tier1Robots_, phalanxType, "phalanx_tier1", destination, forward );
RobotPhalanx::_MovePhalanxTier(
tier2Robots_, phalanxType, "phalanx_tier2", destination, forward );
RobotPhalanx::_MovePhalanxTier(
tier3Robots_, phalanxType, "phalanx_tier3", destination, forward );
startRobotCount_ = tier1Robots_.size + tier2Robots_.size + tier3Robots_.size;
breakingPoint_ = breakingPoint;
startPosition_ = origin;
endPosition_ = destination;
phalanxType_ = phalanxType;
// Initiate the main update loop, a single thread that updates the phalanx.
self thread RobotPhalanx::_UpdatePhalanxThread( self );
}
function ResumeAdvance()
{
if ( !scattered_ )
{
RobotPhalanx::_AssignPhalanxStance( tier1Robots_, "stand" );
// TODO(David Young 10-21-14): Too hardcoded, waiting for animation to complete.
wait 1;
forward = VectorNormalize( endPosition_ - startPosition_ );
RobotPhalanx::_MovePhalanxTier(
tier1Robots_, phalanxType_, "phalanx_tier1", endPosition_, forward );
RobotPhalanx::_MovePhalanxTier(
tier2Robots_, phalanxType_, "phalanx_tier2", endPosition_, forward );
RobotPhalanx::_MovePhalanxTier(
tier3Robots_, phalanxType_, "phalanx_tier3", endPosition_, forward );
RobotPhalanx::_AssignPhalanxStance( tier1Robots_, "crouch" );
}
}
function ResumeFire()
{
RobotPhalanx::_ResumeFireRobots( tier1Robots_ );
RobotPhalanx::_ResumeFireRobots( tier2Robots_ );
RobotPhalanx::_ResumeFireRobots( tier3Robots_ );
}
function ScatterPhalanx()
{
if ( !scattered_ )
{
scattered_ = true;
RobotPhalanx::_ReleaseRobots( tier1Robots_ );
tier1Robots_ = [];
RobotPhalanx::_AssignPhalanxStance( tier2Robots_, "crouch" );
// Settling time for the tier.
wait RandomFloatRange( 5, 7 );
RobotPhalanx::_ReleaseRobots( tier2Robots_ );
tier2Robots_ = [];
RobotPhalanx::_AssignPhalanxStance( tier3Robots_, "crouch" );
// Settling time for the tier.
wait RandomFloatRange( 5, 7 );
RobotPhalanx::_ReleaseRobots( tier3Robots_ );
tier3Robots_ = [];
}
}
}
+122
View File
@@ -0,0 +1,122 @@
///////////////////////////////////////////////////////////////////////////////
//
// Formations
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Phalanx Tiers
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Formation Positions
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Phalanx Defines
//
///////////////////////////////////////////////////////////////////////////////
+71
View File
@@ -0,0 +1,71 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\ai\systems\gib;
#using scripts\shared\system_shared;
Line truncated
function autoexec __init__sytem__() { system::register("skeleton",&__init__,undefined,undefined); }
function autoexec precache()
{
}
function __init__()
{
if( ai::shouldRegisterClientFieldForArchetype( "skeleton" ) )
{
clientfield::register(
"actor",
"skeleton",
1,
1,
"int",
&ZombieClientUtils::zombieHandler,
!true,
!true);
}
}
#namespace ZombieClientUtils;
function zombieHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( IsDefined( entity.archetype ) && entity.archetype != "zombie" )
{
return;
}
if ( !IsDefined( entity.initializedGibCallbacks ) || !entity.initializedGibCallbacks )
{
entity.initializedGibCallbacks = true;
GibClientUtils::AddGibCallback( localClientNum, entity, 8, &_gibCallback );
GibClientUtils::AddGibCallback( localClientNum, entity, 16, &_gibCallback );
GibClientUtils::AddGibCallback( localClientNum, entity, 32, &_gibCallback );
GibClientUtils::AddGibCallback( localClientNum, entity, 128, &_gibCallback );
GibClientUtils::AddGibCallback( localClientNum, entity, 256, &_gibCallback );
}
}
function private _gibCallback( localClientNum, entity, gibFlag )
{
switch (gibFlag)
{
case 8:
playsound(0, "zmb_zombie_head_gib", self.origin);
break;
case 16:
case 32:
case 128:
case 256:
playsound(0, "zmb_death_gibs", self.origin);
break;
}
}
+635
View File
@@ -0,0 +1,635 @@
#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;
Line truncated
#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);
}
+30
View File
@@ -0,0 +1,30 @@
// Melee ranges and timers
// Vision ranges
// used for getting points on the navmesh
///////////////////////////////////////////////////////////////////////////////
//
// Clientfields
//
///////////////////////////////////////////////////////////////////////////////
+80
View File
@@ -0,0 +1,80 @@
Line truncated
#namespace Blackboard;
function autoexec main()
{
_InitializeBlackboard();
}
function private _InitializeBlackboard()
{
level.__ai_blackboard = [];
level thread _UpdateEvents();
}
function private _UpdateEvents()
{
waitTime = 1 * .05;
updateMillis = waitTime * 1000;
while ( true )
{
foreach ( eventName, events in level.__ai_blackboard )
{
liveEvents = [];
// Reduce time to live, and only keep events that still have addition time to live.
foreach ( event in events )
{
event.ttl = event.ttl - updateMillis;
if ( event.ttl > 0 )
{
liveEvents[ liveEvents.size ] = event;
}
}
level.__ai_blackboard[ eventName ] = liveEvents;
}
wait waitTime;
}
}
function AddBlackboardEvent( eventName, data, timeToLiveInMillis )
{
/#
assert( IsString( eventName ), "Must pass in an event name when adding an event to the blackboard." );
assert( IsDefined( data ), "Must pass in some type of data to store as an event." );
assert( IsInt( timeToLiveInMillis ) && timeToLiveInMillis > 0,
"Must pass in a positive time to live value for the lifespan of the event." );
#/
event = SpawnStruct();
event.data = data;
event.timestamp = GetTime();
event.ttl = timeToLiveInMillis;
if ( !isdefined( level.__ai_blackboard[eventName] ) ) level.__ai_blackboard[eventName] = []; else if ( !IsArray( level.__ai_blackboard[eventName] ) ) level.__ai_blackboard[eventName] = array( level.__ai_blackboard[eventName] ); level.__ai_blackboard[eventName][level.__ai_blackboard[eventName].size]=event;;
}
function GetBlackboardEvents( eventName )
{
if ( IsDefined( level.__ai_blackboard[eventName] ) )
{
return level.__ai_blackboard[eventName];
}
return [];
}
function RemoveBlackboardEvents( eventName )
{
if ( IsDefined( level.__ai_blackboard[eventName] ) )
{
level.__ai_blackboard[eventName] = undefined;
}
}
+250
View File
@@ -0,0 +1,250 @@
function autoexec main()
{
/#
level.__ai_debugInterface = GetDvarInt( "ai_debugInterface" );
#/
}
function private _CheckValue( archetype, attributeName, value )
{
/#
attribute = level.__ai_interface[ archetype ][ attributeName ];
switch ( attribute[ "type" ] )
{
case "_interface_match":
possibleValues = attribute[ "values" ];
assert( !IsArray( possibleValues ) || IsInArray( possibleValues, value ),
"AI: \"" + value + "\" is not one of the allowed values for attribute \"" +
attributeName + "\"." );
break;
case "_interface_numeric":
maxValue = attribute[ "max_value" ];
minValue = attribute[ "min_value" ];
assert( IsInt( value ) || IsFloat( value ), "AI: Attribute \"" + attributeName +
"\" requires a numeric value and \"" + value + "\" does not qualify." );
assert( ( !IsDefined( maxValue ) && !IsDefined( minValue ) ) ||
( value <= maxValue && value >= minValue ),
"AI: \"" + value + "\" is outside the allowed range of (" + minValue + "," +
maxValue + ")." );
break;
case "_interface_vector":
if ( IsDefined( value ) )
assert( IsVec( value ), "AI: Attribute \"" + attributeName +
"\" requires a vector value and \"" + value + "\" does not qualify." );
break;
default:
assert( "AI: Unknown attribute type of \"" + attribute[ "type" ] +
"\" for attribute \"" + attributeName + "\"." );
break;
}
#/
}
function private _CheckPrerequisites( entity, attribute )
{
/#
assert( IsEntity( entity ), "AI: Must pass an entity to access an attribute." );
assert( IsActor( entity ) || IsVehicle( entity ), "AI: Must pass an actor or vehicle to access an attribute." );
assert( IsString( attribute ), "AI: Must pass in a valid attribute name." );
// In depth debugging, only necessary to debug if an AI or the AI Interface system has been setup properly.
if ( IsDefined( level.__ai_debugInterface ) && level.__ai_debugInterface > 0 )
{
assert( IsArray( entity.__interface ),
"AI: Entity(" + entity.archetype + ") must create an interface before accessing an " +
"attribute, see \"ai::CreateInterfaceForEntity\"." );
assert( IsArray( level.__ai_interface ),
"AI: No attributes have been registered with the AI interface system yet." );
assert( IsArray( level.__ai_interface[ entity.archetype ] ),
"AI: No attributes for archetype \"" + entity.archetype + "\" have been registered." );
assert( IsArray( level.__ai_interface[ entity.archetype ][ attribute ] ),
"AI: Attribute \"" + attribute + "\" has not been registered for archetype \"" +
entity.archetype + "\" yet." );
assert( IsString( level.__ai_interface[ entity.archetype ][ attribute ][ "type" ] ),
"AI: Attribute type is undefined for \"" + attribute + "\"." );
}
#/
}
function private _CheckRegistrationPrerequisites( archetype, attribute, callbackFunction )
{
/#
assert( IsString( archetype ), "AI: \"archetype\" value is mandatory for registration." );
assert( IsString( attribute ), "AI: \"attribute\" value is mandatory for registration." );
assert( !IsDefined( callbackFunction ) || IsFunctionPtr( callbackFunction ),
"AI: \"callbackFunction\" is optional but must be a function pointer if specified." );
#/
}
function private _InitializeLevelInterface( archetype )
{
if ( !IsDefined( level.__ai_interface ) )
{
level.__ai_interface = [];
}
if ( !IsDefined( level.__ai_interface[ archetype ] ) )
{
level.__ai_interface[ archetype ] = [];
}
}
#namespace ai;
function CreateInterfaceForEntity( entity )
{
if ( !IsDefined( entity.__interface ) )
{
entity.__interface = [];
}
}
function GetAiAttribute( entity, attribute )
{
/#
ai_interface::_CheckPrerequisites( entity, attribute );
#/
if ( !IsDefined( entity.__interface[ attribute ] ) )
{
return level.__ai_interface[ entity.archetype ][ attribute ][ "default_value" ];
}
return entity.__interface[ attribute ];
}
function HasAiAttribute( entity, attribute )
{
return
IsDefined( entity ) &&
IsDefined( attribute ) &&
IsDefined( entity.archetype ) &&
IsDefined( level.__ai_interface ) &&
IsDefined( level.__ai_interface[ entity.archetype ] ) &&
IsDefined( level.__ai_interface[ entity.archetype ][ attribute ] );
}
function RegisterMatchedInterface( archetype, attribute, defaultValue, possibleValues, callbackFunction )
{
/#
ai_interface::_CheckRegistrationPrerequisites( archetype, attribute, callbackFunction );
assert( !IsDefined( possibleValues ) || IsArray( possibleValues ),
"AI: \"possibleValues\" is optional but must be an array if specified." );
#/
ai_interface::_InitializeLevelInterface( archetype );
/#
assert( !IsDefined( level.__ai_interface[ archetype ][ attribute ] ),
"AI: \"" + attribute + "\" is already registered for archetype \"" + archetype + "\"" );
#/
level.__ai_interface[ archetype ][ attribute ] = [];
level.__ai_interface[ archetype ][ attribute ][ "callback" ] = callbackFunction;
level.__ai_interface[ archetype ][ attribute ][ "default_value" ] = defaultValue;
level.__ai_interface[ archetype ][ attribute ][ "type" ] = "_interface_match";
level.__ai_interface[ archetype ][ attribute ][ "values" ] = possibleValues;
/#
ai_interface::_CheckValue( archetype, attribute, defaultValue );
#/
}
function RegisterNumericInterface( archetype, attribute, defaultValue, minimum, maximum, callbackFunction )
{
/#
ai_interface::_CheckRegistrationPrerequisites( archetype, attribute, callbackFunction );
assert( !IsDefined( minimum ) || IsInt( minimum ) || IsFloat( minimum ),
"AI: \"minimum\" is optional but must be a numeric if specified." );
assert( !IsDefined( maximum ) || IsInt( maximum ) || IsFloat( maximum ),
"AI: \"maximum\" is optional but must be a numeric if specified." );
assert( ( !IsDefined( minimum ) && !IsDefined( maximum ) ) ||
( IsDefined( minimum ) && IsDefined( maximum ) ),
"AI: Either both a minimum and maximum must be defined or both undefined, not mixed.");
assert( ( !IsDefined( minimum ) && !IsDefined( maximum ) ) ||
( minimum <= maximum ),
"AI: Attribute \"" + attribute + "\" cannot specify a minimum greater than " +
"the attribute's maximum");
#/
ai_interface::_InitializeLevelInterface( archetype );
/#
assert( !IsDefined( level.__ai_interface[ archetype ][ attribute ] ),
"AI: \"" + attribute + "\" is already registered for archetype \"" + archetype + "\"" );
#/
level.__ai_interface[ archetype ][ attribute ] = [];
level.__ai_interface[ archetype ][ attribute ][ "callback" ] = callbackFunction;
level.__ai_interface[ archetype ][ attribute ][ "default_value" ] = defaultValue;
level.__ai_interface[ archetype ][ attribute ][ "max_value" ] = maximum;
level.__ai_interface[ archetype ][ attribute ][ "min_value" ] = minimum;
level.__ai_interface[ archetype ][ attribute ][ "type" ] = "_interface_numeric";
/#
ai_interface::_CheckValue( archetype, attribute, defaultValue );
#/
}
function RegisterVectorInterface( archetype, attribute, defaultValue, callbackFunction )
{
/#
ai_interface::_CheckRegistrationPrerequisites( archetype, attribute, callbackFunction );
#/
ai_interface::_InitializeLevelInterface( archetype );
/#
assert( !IsDefined( level.__ai_interface[ archetype ][ attribute ] ),
"AI: \"" + attribute + "\" is already registered for archetype \"" + archetype + "\"" );
#/
level.__ai_interface[ archetype ][ attribute ] = [];
level.__ai_interface[ archetype ][ attribute ][ "callback" ] = callbackFunction;
level.__ai_interface[ archetype ][ attribute ][ "default_value" ] = defaultValue;
level.__ai_interface[ archetype ][ attribute ][ "type" ] = "_interface_vector";
/#
ai_interface::_CheckValue( archetype, attribute, defaultValue );
#/
}
function SetAiAttribute( entity, attribute, value )
{
/#
ai_interface::_CheckPrerequisites( entity, attribute );
ai_interface::_CheckValue( entity.archetype, attribute, value );
#/
oldValue = entity.__interface[ attribute ];
if ( !IsDefined( oldValue ) )
{
oldValue = level.__ai_interface[ entity.archetype ][ attribute ][ "default_value" ];
}
entity.__interface[ attribute ] = value;
callback = level.__ai_interface[ entity.archetype ][ attribute ][ "callback" ];
if ( IsFunctionPtr( callback ) )
{
[[callback]]( entity, attribute, oldValue, value );
}
}
+324
View File
@@ -0,0 +1,324 @@
#using scripts\shared\ai_shared;
#using scripts\shared\array_shared;
#using scripts\shared\flag_shared;
#using scripts\shared\system_shared;
#using scripts\shared\trigger_shared;
#using scripts\shared\spawner_shared;
#using scripts\shared\util_shared;
Line truncated
#namespace AiSquads;
function autoexec __init__sytem__() { system::register("ai_squads",&__init__,undefined,undefined); }
function __init__()
{
level._squads = [];
actorSpawnerArray = GetActorSpawnerTeamArray( "axis" );
array::run_all( actorSpawnerArray, &spawner::add_spawn_function, &SquadMemberThink );
}
// ------- AI SQUAD -----------//
class Squad
{
var squadLeader;
var squadMembers;
var squadBreadCrumb;
constructor()
{
squadLeader = 0;
squadMembers = [];
squadBreadCrumb = [];
}
function addSquadBreadCrumbs( ai )
{
assert( squadLeader == ai );
if( Distance2DSquared( squadBreadCrumb, ai.origin ) >= 96 * 96 )
{
/# RecordCircle( ai.origin, 4, ( 0, 0, 1 ), "Animscript", ai ); #/
squadBreadCrumb = ai.origin;
}
}
function getSquadBreadCrumb()
{
return squadBreadCrumb;
}
function GetLeader()
{
return squadLeader;
}
function GetMembers()
{
return squadMembers;
}
function AddAIToSquad( ai )
{
if( !IsInArray( squadMembers, ai ) )
{
// TEMP : Turn off other behaviors of the robot, to make sure that he will respect and follow the squad
if( ai.archetype == "robot" )
{
ai ai::set_behavior_attribute( "move_mode", "squadmember" );
}
squadMembers[squadMembers.size] = ai;
}
}
function RemoveAIFromSqaud( ai )
{
if( IsInArray( squadMembers, ai ) )
{
ArrayRemoveValue( squadMembers, ai, false );
if( squadLeader === ai )
{
squadLeader = undefined;
}
}
}
function Think()
{
// update squad leader - pick the first one in the squadMembers list
// remove the squad if no one is left
if( (IsInt(squadLeader) && squadLeader == 0) || !IsDefined( squadLeader ) )
{
if( squadMembers.size > 0 )
{
squadLeader = squadMembers[0];
squadBreadCrumb = squadLeader.origin;
}
else
{
return false;
}
}
return true;
}
}
function private CreateSquad( squadName )
{
level._squads[squadName] = new Squad();
return level._squads[squadName];
}
function private RemoveSquad( squadName )
{
if( IsDefined( level._squads ) && IsDefined( level._squads[squadName] ) )
{
level._squads[squadName] = undefined;
}
}
function private GetSquad( squadName )
{
return level._squads[squadName];
}
function private ThinkSquad( squadName )
{
while(1)
{
if( [[ level._squads[squadName] ]]->Think() )
{
wait 0.5;
}
else
{
RemoveSquad( squadName );
break;
}
}
}
function private SquadMemberDeath()
{
self waittill( "death" );
if( IsDefined( self.squadName ) && IsDefined( level._squads[self.squadName] ) )
{
[[ level._squads[self.squadName] ]]->RemoveAIFromSqaud( self );
}
}
function private SquadMemberThink()
{
self endon("death");
if( !IsDefined( self.script_aisquadname ) )
return;
// wait for other ai systems to initialize
wait 0.5;
// Assign the squad name
self.squadName = self.script_aisquadname;
if( IsDefined( self.squadName ) )
{
// Create squad if it does not exist, then create it
if( !IsDefined( level._squads[self.squadName] ) )
{
squad = CreateSquad( self.squadName );
newSquadCreated = true;
}
else
{
squad = GetSquad( self.squadName );
}
// Add this AI as a member of the squad
[[squad]]->AddAIToSquad( self );
// Handle the death of the sqaudMember and remove him from squad
self thread SquadMemberDeath();
// start ticking this squad
if( ( isdefined( newSquadCreated ) && newSquadCreated ) )
{
level thread ThinkSquad( self.squadName );
}
// Keep updating and following the leader of the squad
while(1)
{
squadLeader = [[ level._squads[self.squadName] ]]->GetLeader();
if( IsDefined( squadLeader ) && !(IsInt(squadLeader) && squadLeader == 0) )
{
if( squadLeader == self )
{
/# RecordEntText( self.squadName + ": LEADER", self, ( 0, 1, 0 ), "Animscript" ); #/
/# RecordEntText( self.squadName + ": LEADER", self, ( 0, 1, 0 ), "Animscript" ); #/
/# RecordCircle( self.origin, 300, ( 1, .5, 0 ), "Animscript", self ); #/
if( IsDefined( self.enemy ) )
{
self SetGoal( self.enemy );
}
[[squad]]->addSquadBreadCrumbs( self );
}
else
{
/# RecordLine( self.origin, squadLeader.origin, ( 0, 1, 0 ), "Animscript", self ); #/
/# RecordEntText( self.squadName+ ": FOLLOWER", self, ( 0, 1, 0 ), "Animscript" ); #/
followPosition = [[squad]]->getSquadBreadCrumb();
followDistSq = Distance2DSquared( self.goalPos, followPosition );
if( IsDefined( squadLeader.enemy ) )
{
if( !IsDefined( self.enemy ) || ( IsDefined( self.enemy ) && self.enemy != squadLeader.enemy ) )
self SetEntityTarget( squadLeader.enemy, 1 );
}
if( IsDefined( self.goalPos ) && followDistSq >= 16 * 16 )
{
if( followDistSq >= 150 * 150 )
{
self ai::set_behavior_attribute( "sprint", true );
}
else
{
self ai::set_behavior_attribute( "sprint", false );
}
self SetGoal( followPosition, true );
}
}
}
wait 1;
}
}
}
// ------- UTILITY -----------//
function isFollowingSquadLeader( ai )
{
if( ai ai::get_behavior_attribute( "move_mode" ) != "squadmember" )
{
return false;
}
squadMember = isSquadMember( ai );
currentSquadLeader = getSquadLeader( ai ) ;
isAISquadLeader = IsDefined( currentSquadLeader ) && currentSquadLeader == ai;
if( squadMember && !isAISquadLeader )
{
return true;
}
return false;
}
function isSquadMember( ai )
{
if( IsDefined( ai.squadName ) )
{
squad = GetSquad( ai.squadName );
if( IsDefined( squad ) )
{
return IsInArray( [[squad]]->GetMembers(), ai );
}
}
return false;
}
function isSquadLeader( ai )
{
if( IsDefined( ai.squadName ) )
{
squad = GetSquad( ai.squadName );
if( IsDefined( squad ) )
{
squadLeader = [[squad]]->GetLeader();
return IsDefined( squadLeader ) && squadLeader == ai;
}
}
return false;
}
function getSquadLeader( ai )
{
if( IsDefined( ai.squadName ) )
{
squad = GetSquad( ai.squadName );
if( IsDefined( squad ) )
{
return [[squad]]->GetLeader();
}
}
return undefined;
}
@@ -0,0 +1,16 @@
#namespace AnimationSelectorTable;
function RegisterAnimationSelectorTableEvaluator( functionName, functionPtr )
{
if ( !IsDefined( level._astevaluatorscriptfunctions ) )
{
level._astevaluatorscriptfunctions = [];
}
functionName = ToLower( functionName );
Assert( IsDefined( functionName ) && IsDefined( functionPtr ) );
Assert( !IsDefined( level._astevaluatorscriptfunctions[functionName] ) );
level._astevaluatorscriptfunctions[functionName] = functionPtr;
}
Whitespace-only changes.
@@ -0,0 +1,90 @@
// ASM RUNNING STATUS FLAGS ------------------------------------------------------//
// TODO - Find a better way to not need to have this hardcoded state name.
// ASM NOTETRACK HANDLER DEFINES -----------------------------------------------//
// ASM AIM/SHOOT NODE DEFINES -------------------------------------------------//
// ASM MOCOMP ATTRIBUTES -----------------------------------------------------//
// NOTERACKS HANDLED BY ASM ---------------------------------------------------//
// AI_ANIM_MODE, DEFINED SAME AS CODE -----------------------------------------//
@@ -0,0 +1,79 @@
Line truncated
// Created and Implemented by Sumeet Jakatdar
#namespace AnimationStateNetwork;
// ------------- AnimationMocomp Initialization ----------- //
function autoexec InitAnimationMocomps()
{
level._AnimationMocomps = [];
}
// ------------- AnimationMocomp Utility ----------- //
function RunAnimationMocomp( mocompName, mocompStatus, asmEntity, mocompAnim, mocompAnimBlendOutTime, mocompDuration )
{
Assert( ( mocompStatus >= 0 && mocompStatus <= 2 ), "ASM - Unsupported MocompStatus " + mocompStatus + " for RunAnimationMocomp." );
Assert( IsDefined( level._AnimationMocomps[mocompName] ), "BT - RegisterAnimationMocomps with " + mocompName + " does not exists." );
if( mocompStatus == 0 )
mocompStatus = "asm_mocomp_start";
else if( mocompStatus == 1 )
mocompStatus = "asm_mocomp_update";
else
mocompStatus = "asm_mocomp_terminate";
animationMocompResult = asmEntity [[level._AnimationMocomps[mocompName][mocompStatus]]]( asmEntity, mocompAnim, mocompAnimBlendOutTime, "", mocompDuration );
return animationMocompResult;
}
// ------------- AnimationMocomp API ----------- //
function RegisterAnimationMocomp( mocompName, startFuncPtr, updateFuncPtr, terminateFuncPtr )
{
mocompName = ToLower( mocompName );
Assert( IsString( mocompName ), "ASM - mocompName for RegisterAnimationMocomps is not a string." );
Assert( !IsDefined(level._AnimationMocomps[mocompName]), "mocomp with name " + mocompName + " is already registered." );
level._AnimationMocomps[mocompName] = array();
// ASM_MOCOMP_START
Assert( IsDefined( startFuncPtr ) && IsFunctionPtr( startFuncPtr ), "BT - RegisterAnimationMocomps must have startFunctionPtr and it must be of type functionPtr." );
level._AnimationMocomps[mocompName]["asm_mocomp_start"] = startFuncPtr;
// ASM_MOCOMP_UPDATE
if( IsDefined( updateFuncPtr ) )
{
Assert( IsFunctionPtr( updateFuncPtr ), "BT - RegisterAnimationMocomps must have updateFuncPtr and it must be of type functionPtr." );
level._AnimationMocomps[mocompName]["asm_mocomp_update"] = updateFuncPtr;
}
else
{
level._AnimationMocomps[mocompName]["asm_mocomp_update"] = &AnimationMocompEmptyFunc;
}
// ASM_MOCOMP_UPDATE
if( IsDefined( terminateFuncPtr ) )
{
Assert( IsFunctionPtr( terminateFuncPtr ), "BT - RegisterAnimationMocomps must have terminateFuncPtr and it must be of type functionPtr." );
level._AnimationMocomps[mocompName]["asm_mocomp_terminate"] = terminateFuncPtr;
}
else
{
level._AnimationMocomps[mocompName]["asm_mocomp_terminate"] = &AnimationMocompEmptyFunc;
}
}
function AnimationMocompEmptyFunc( entity, mocompAnim, mocompAnimBlendOutTime, mocompAnimFlag, mocompDuration )
{
}
@@ -0,0 +1,80 @@
#using scripts\shared\ai\systems\blackboard;
Line truncated
#namespace AnimationStateNetwork;
// ------------- NotatrackHandler Initialization ----------- //
function autoexec InitNotetrackHandler()
{
level._NOTETRACK_HANDLER = [];
}
// ------------- NotetrackHandler internals ----------- //
function private RunNotetrackHandler( entity, notetracks )
{
assert( IsArray( notetracks ) );
for ( index = 0; index < notetracks.size; index++ )
{
HandleNotetrack( entity, notetracks[index] );
}
}
function private HandleNotetrack( entity, notetrack )
{
notetrackHandler = level._NOTETRACK_HANDLER[notetrack];
If( !IsDefined( notetrackHandler ) )
{
return;
}
if( IsFunctionPtr( notetrackHandler ) )
{
// run the notetrackHandler function
[[notetrackHandler]]( entity );
}
else
{
// assume notetrackHandler is setting a blackboard variable for entity
Blackboard::SetBlackBoardAttribute(
entity,
notetrackHandler.blackboardAttributeName,
notetrackHandler.blackBoardValue );
}
}
// ------------- NotatrackHandler Function utility ----------- //
function RegisterNotetrackHandlerFunction( notetrackName, notetrackFuncPtr )
{
Assert(
IsString( notetrackName ),
"NotetrackHandler - notetrackName must be of string type." );
Assert(
IsFunctionPtr( notetrackFuncPtr ),
"NotetrackHandler - notetrackFunc name must be of function pointer type." );
Assert(
!IsDefined( level._NOTETRACK_HANDLER[notetrackName] ),
"NotetrackHandler - notetrackName \"" + notetrackName + "\" is already registered." );
// add the notetrack handler to the global list
level._NOTETRACK_HANDLER[notetrackName] = notetrackFuncPtr;
}
// ------------- BlackBoard NotetrackHandler utility ----------- //
function RegisterBlackboardNotetrackHandler(
notetrackName, blackboardAttributeName, blackBoardValue )
{
notetrackHandler = SpawnStruct();
notetrackHandler.blackboardAttributeName = blackboardAttributeName;
notetrackHandler.blackBoardValue = blackBoardValue;
level._NOTETRACK_HANDLER[notetrackName] = notetrackHandler;
}
@@ -0,0 +1,25 @@
#using scripts\shared\ai\archetype_utility;
Line truncated
#namespace AnimationStateNetworkUtility;
// ------------------- Functions to be updated and maintained after the integration in T7 --------------------------------------//
function RequestState( entity, stateName )
{
assert( IsDefined( entity ) );
entity ASMRequestSubState( stateName );
}
function SearchAnimationMap( entity, aliasName )
{
if ( IsDefined( entity ) && IsDefined( aliasName ) )
{
animationName = entity AnimMappingSearch( IString( aliasName ));
if ( IsDefined( animationName ) )
return FindAnimByName( "generic", animationName );
}
}
+15
View File
@@ -0,0 +1,15 @@
// EMPTY ANIMSCRIPT AS WE DO NOT NEED AI TO RUN ANYTHING HERE
function main()
{
}
function end_script()
{
// This callback will be called when AI will change states from AIS_BEHAVE to AIS_SCRIPTED
// i.e when it will go from behavior state to animscripted state
if( IsDefined( self.___ArchetypeOnAnimscriptedCallback ) )
[[self.___ArchetypeOnAnimscriptedCallback]]( self );
}
+98
View File
@@ -0,0 +1,98 @@
// at 96 units distance, AI will prefer to walk instead of running to it.
// standing offset at covernodes based on the AI pose
// yaws used to check if the AI can see the enemy
// offsets used to check if the AI can see the enemy
// in sec, if AI has not seen enemy for 4 sec, then he will be considered hidden
// maximum time for idle action at cover, ir-respective of the animation length
// grenade throw ranges
// grenade throw modifiers
// Lower bound of time before a person of the same team can throw another grenade
// Upper bound of time before a person of the same team can throw another grenade
// Lower bound of time before a person that finished a scripted animation can throw a grenade
// Upper bound of time before a person that finished a scripted animation can throw a grenade
// Lower bound of time before being able to throw another grenade
// Upper bound of time before being able to throw another grenade
// New grenade throws must land atleast this far from any previous throw, within the GRENADE_THROW_TIME window.
// Don't throw a grenade if you are on team allies and it will land this close to a player (may want to change to any ally)
// Don't throw a grenade if there is a player in laststand this close to the target.
// Melee ranges and timers
// allowed 10 units to account for charge blend
// charge distance is the minimum distance required to even attempt melee
// Arrival
// Distance at which enmey is considered to be nearby
// Cover modes
//Sprint values
//delay between two sprints
//upto one more second delay between sprint
//additional compensation factor
//value between 0 and 100, probability of sprinting
// Cover shoot times
// *********** SUMEET TODO - Merge seeking and exposed reacquiring behavior together. ***********
// Reacquire/ Cover/Locomotion Seek
@@ -0,0 +1,363 @@
// Created and Implemented by Sumeet Jakatdar
Line truncated
#namespace BehaviorStateMachine;
// ------------- Behavior State Machine Script API registration ----------- //
function RegisterBSMScriptAPIInternal( functionName, scriptFunction )
{
if ( !IsDefined( level._bsmscriptfunctions ) )
{
level._bsmscriptfunctions = [];
}
// SUMEET TODO - remove the need of ToLower and have the functionNames defined in .gsh
functionName = ToLower( functionName );
Assert( IsDefined( scriptFunction ) && IsDefined( scriptFunction ), "BT - (RegisterBSMScriptAPI) No functionPtr defined or no functionName defined for BSMScript." );
Assert( !IsDefined( level._bsmscriptfunctions[functionName] ), "BT - (RegisterBSMScriptAPI) functionName is already defined for BSMScript." );
level._bsmscriptfunctions[functionName] = scriptFunction;
}
/*
class BehaviorState
{
var stateName;
var asmStateName;
var initFunc;
var updateFunc;
var terminateFunc;
var connections;
constructor()
{
connections = [];
}
destructor()
{
}
function GetConnections()
{
return connections;
}
}
class BehaviorConnection
{
var connectionName;
var toState;
var conditionFunc;
var endOfStateConnection;
var asmState;
constructor()
{
toState = NULL;
}
destructor()
{
}
}
class BSM
{
var stateMachineName;
var states;
var miscData;
var currentState;
var previousState;
constructor()
{
states = [];
}
destructor()
{
}
function AddState( stateName, asmStateName, initFunc, updateFunc, terminateFunc )
{
assert( !IsDefined( states[stateName] ), "BSM - State is already defined " + stateName );
states[stateName] = new BehaviorState();
states[stateName].initFunc = initFunc;
states[stateName].updateFunc = updateFunc;
states[stateName].terminateFunc = terminateFunc;
states[stateName].asmStateName = asmStateName;
states[stateName].stateName = stateName;
}
function AddConnection( connectionName, fromState, toState, conditionFunc, endOfStateConnection, asmState )
{
assert( IsDefined( states[fromState] ), "BSM - State is not defined " + fromState );
assert( IsDefined( states[toState] ), "BSM - State is not defined " + toState );
assert( !IsDefined( states[fromState].connections[connectionName] ), "BSM - Connection is already defined " + connectionName );
states[fromState].connections[connectionName] = new BehaviorConnection();
states[fromState].connections[connectionName].toState = toState;
states[fromState].connections[connectionName].conditionFunc = conditionFunc;
states[fromState].connections[connectionName].connectionName = connectionName;
states[fromState].connections[connectionName].endOfStateConnection = endOfStateConnection;
states[fromState].connections[connectionName].asmState = asmState;
}
function GetStates()
{
return states;
}
}
function CreateBSM( archetypeName, stateMachineName )
{
if( !IsDefined( level.__BSMs ) )
{
level.__BSMs = [];
}
if( !IsDefined( level.__BSMs[archetypeName] ) )
{
level.__BSMs[archetypeName] = [];
}
assert( !IsDefined( level.__BSMs[archetypeName][stateMachineName] ) );
newStateMachine = new BSM();
newStateMachine.stateMachineName = stateMachineName;
level.__BSMs[archetypeName][stateMachineName] = newStateMachine;
return level.__BSMs[archetypeName][stateMachineName];
}
function StartBSM( entity, stateMachineName )
{
assert( IsDefined( level.__BSMs[entity.archetype][stateMachineName] ) );
stateMachine = level.__BSMs[entity.archetype][stateMachineName];
assert( !IsDefined( stateMachine.currentState ) );
assert( !IsDefined( stateMachine.previousState ) );
// go through the states, find the first fit
foreach ( state in stateMachine.states )
{
if( !IsDefined( state.initFunc ) )
{
if( IsDefined( state.asmStateName ) )
{
newState = state;
break;
}
}
else
{
validState = [[state.initFunc]]( entity );
if( validState )
{
// found one state which has a valid ASM, set it as a current substate
if( IsDefined( state.asmStateName ) )
{
newState = state;
break;
}
}
}
}
if( IsDefined( newState ) )
{
stateMachine.currentState = newState;
AnimationStateNetworkUtility::RequestState( entity, newState.asmStateName );
return BHTN_RUNNING;
}
return BHTN_SUCCESS;
}
function UpdateBSM( entity, stateMachineName )
{
assert( IsDefined( level.__BSMs[entity.archetype][stateMachineName] ) );
stateMachine = level.__BSMs[entity.archetype][stateMachineName];
assert( IsDefined( stateMachine.currentState ) );
// run the update function for the current state
currentStateUpdatedResult = true;
if( IsDefined( stateMachine.currentState.updateFunc ) )
{
currentStateUpdatedResult = [[stateMachine.currentState.updateFunc]]( entity );
}
// We have a valid running state, go through all the connections ( if there are any )
connections = [[stateMachine.currentState]]->GetConnections();
if( !connections.size )
{
if( entity ASMGetStatus() == ASM_STATE_COMPLETE )
{
TerminateBSM( entity, stateMachineName );
return BHTN_SUCCESS;
}
}
else
{
nextValidState = undefined;
foreach ( connection in connections )
{
// execute endOfStateConnection only when current state is complete or is not valid anymore
if( IS_TRUE( connection.endOfStateConnection )
&& ( entity ASMGetStatus() != ASM_STATE_COMPLETE || !currentStateUpdatedResult )
)
{
/#RecordEntText( "EndOfStateConnection :" + connection.connectionName, entity, RED, "Animscript" );#/
continue;
}
if( IsDefined( connection.conditionFunc ) )
{
// check if the connection is valid
validConnection = [[connection.conditionFunc]]( entity );
if( !IsDefined( validConnection ) )
validConnection = true;
// Connection is valid, check if the toState has any condition associated with it, and execute it
if( validConnection )
{
/#RecordEntText( "Connection :" + connection.connectionName, entity, GREEN, "Animscript" );#/
if( !IsDefined( stateMachine.states[connection.toState].initFunc ) )
{
nextValidState = stateMachine.states[connection.toState];
break;
}
else
{
validToState = [[stateMachine.states[connection.toState].initFunc]]( entity );
if( validToState )
{
nextValidState = stateMachine.states[connection.toState];
break;
}
else
{
/#RecordEntText( "ToState :" + stateMachine.states[connection.toState].stateName, entity, RED, "Animscript" );#/
}
}
}
else
{
/#RecordEntText( "Connection :" + connection.connectionName, entity, RED, "Animscript" );#/
}
}
else
{
/#RecordEntText( "Connection :" + connection.connectionName, entity, GREEN, "Animscript" );#/
if( !IsDefined( stateMachine.states[connection.toState].initFunc ) )
{
nextValidState = stateMachine.states[connection.toState];
break;
}
else
{
validToState = [[stateMachine.states[connection.toState].initFunc]]( entity );
if( validToState )
{
nextValidState = stateMachine.states[connection.toState];
break;
}
else
{
/#RecordEntText( "ToState :" + stateMachine.states[connection.toState].stateName, entity, RED, "Animscript" );#/
}
}
}
}
// found a validNextState with ValidConnection, go there now
if( IsDefined( nextValidState ) )
{
// found a new valid state, execute terminateFunc of the current state
if( IsDefined( stateMachine.currentState.terminateFunc ) )
{
[[stateMachine.currentState.terminateFunc]]( entity );
}
/#RecordEntText( "ToState :" + stateMachine.states[connection.toState].stateName, entity, GREEN, "Animscript" );#/
stateMachine.previousState = stateMachine.currentState;
stateMachine.currentState = nextValidState;
assert( IsDefined( nextValidState.asmStateName ) );
AnimationStateNetworkUtility::RequestState( entity, nextValidState.asmStateName );
return BHTN_RUNNING;
}
else
{
/#RecordEntText( "ToState :" + stateMachine.states[connection.toState].stateName, entity, RED, "Animscript" );#/
}
}
// if there is no better connection, just update the current substate
if( entity ASMGetStatus() == ASM_STATE_COMPLETE )
{
TerminateBSM( entity, stateMachineName );
return BHTN_SUCCESS;
}
else
{
/#RecordEntText( "CurrentState :" + stateMachine.currentState.stateName, entity, BLUE, "Animscript" );#/
}
return BHTN_RUNNING;
}
function TerminateBSM( entity, stateMachineName )
{
assert( IsDefined( level.__BSMs[entity.archetype][stateMachineName] ) );
stateMachine = level.__BSMs[entity.archetype][stateMachineName];
// state machine is terminating and returning control to behavior tree, call terminateFunc of the current state
if( IsDefined( stateMachine.currentState ) && IsDefined( stateMachine.currentState.terminateFunc ) )
{
[[stateMachine.currentState.terminateFunc]]( entity );
}
stateMachine.currentState = undefined;
stateMachine.previousState = undefined;
return BHTN_SUCCESS;
}
*/
@@ -0,0 +1,19 @@
// These enums must match with StateMachineStatus in behavior_state_machine_db.h
// HACK - just to match status codes with the behavior tree, ease of use!
// HACK - just to match status codes with the behavior tree, ease of use!
+17
View File
@@ -0,0 +1,17 @@
// return status for BehaviorTreeNodes tick functions - BHTN_STATUS, order is important, BHTN_RUNNING needs to be highest
@@ -0,0 +1,71 @@
#namespace BehaviorTreeNetwork;
// ------------- BehaviorTreeScript ----------- //
function RegisterBehaviorTreeScriptAPIInternal( functionName, functionPtr )
{
if ( !IsDefined( level._BehaviorTreeScriptFunctions ) )
{
level._BehaviorTreeScriptFunctions = [];
}
// SUMEET TODO - remove the need of ToLower and have the functionNames defined in .gsh
functionName = ToLower( functionName );
Assert( IsDefined( functionName ) && IsDefined( functionPtr ), "BT - (RegisterBehaviorTreeScriptAPI) No functionPtr defined or no functionName defined for BehaviorTreeScript." );
Assert( !IsDefined( level._BehaviorTreeScriptFunctions[functionName] ), "BT - (RegisterBehaviorTreeScriptAPI) functionName is already defined for BehaviorTreeScript." );
level._BehaviorTreeScriptFunctions[functionName] = functionPtr;
}
// ------------- BehaviorTreeAction ----------- //
function RegisterBehaviorTreeActionInternal( actionName, startFuncPtr, updateFuncPtr, terminateFuncPtr )
{
if ( !IsDefined( level._BehaviorTreeActions ) )
{
level._BehaviorTreeActions = [];
}
actionName = ToLower( actionName );
Assert( IsString( actionName ), "BT - actionName for RegisterBehaviorTreeActionInternal is not a string." );
Assert( !IsDefined(level._BehaviorTreeActions[actionName]), "BT - (RegisterBehaviorTreeActionInternal) actionName " + actionName + " is already registered." );
level._BehaviorTreeActions[actionName] = array();
// BHTN_ACTION_START
if( IsDefined( startFuncPtr ) )
{
Assert( IsFunctionPtr( startFuncPtr ), "BT - BehaviorTreeAction startFuncPtr must be of type functionPtr." );
level._BehaviorTreeActions[actionName]["bhtn_action_start"] = startFuncPtr;
}
// BHTN_ACTION_UPDATE
if( IsDefined( updateFuncPtr ) )
{
Assert( IsFunctionPtr( updateFuncPtr ), "BT - BehaviorTreeAction updateFuncPtr must be of type functionPtr." );
level._BehaviorTreeActions[actionName]["bhtn_action_update"] = updateFuncPtr;
}
// BHTN_ACTION_TERMINATE
if( IsDefined( terminateFuncPtr ) )
{
Assert( IsFunctionPtr( terminateFuncPtr ), "BT - BehaviorTreeAction terminateFuncPtr must be of type functionPtr." );
level._BehaviorTreeActions[actionName]["bhtn_action_terminate"] = terminateFuncPtr;
}
}
// ------------- utility----------- //
#namespace BehaviorTreeNetworkUtility;
function RegisterBehaviorTreeScriptAPI( functionName, functionPtr )
{
BehaviorTreeNetwork::RegisterBehaviorTreeScriptAPIInternal( functionName, functionPtr );
}
function RegisterBehaviorTreeAction( actionName, startFuncPtr, updateFuncPtr, terminateFuncPtr )
{
BehaviorTreeNetwork::RegisterBehaviorTreeActionInternal( actionName, startFuncPtr, updateFuncPtr, terminateFuncPtr );
}
+89
View File
@@ -0,0 +1,89 @@
Line truncated
#namespace Blackboard;
function RegisterBlackBoardAttribute( entity, attributeName, defaultAttributeValue, getterFunction )
{
Assert( IsDefined( entity.__blackboard ), "Blackboard - No blackboard has been created for this entity." );
Assert( !IsDefined( entity.__blackboard[attributeName] ), "BlackBoard - Attribute " + attributeName + " already registered." );
if( IsDefined( getterFunction ) )
{
// When a blackboard is registered with getterFunction, then every single time its requested, it will
// execure getterFunction and return the value.
Assert( IsFunctionPtr( getterFunction ) );
entity.__blackboard[attributeName] = getterFunction;
}
else
{
// If there is no getterFunction, then it will just store the default value
if( !IsDefined( defaultAttributeValue ) )
{
defaultAttributeValue = undefined;
}
entity.__blackboard[attributeName] = defaultAttributeValue;
}
}
function GetBlackBoardAttribute( entity, attributeName )
{
if( IsFunctionPtr( entity.__blackboard[attributeName] ) )
{
getterFunction = entity.__blackboard[attributeName];
attributeValue = entity [[getterFunction]]();
/#
if( IsActor( entity ) )
entity updatetrackedblackboardattribute( attributeName );
#/
return attributeValue;
}
else
{
/#
if( IsActor( entity ) )
entity updatetrackedblackboardattribute( attributeName );
#/
return entity.__blackboard[attributeName];
}
}
function SetBlackBoardAttribute( entity, attributeName, attributeValue )
{
if ( IsDefined( entity.__blackboard[attributeName] ) )
{
if ( !IsDefined( attributeValue ) && IsFunctionPtr( entity.__blackboard[attributeName] ) )
{
// Attempts to clear attributes with getter functions is meaningless.
return;
}
Assert( !IsFunctionPtr( entity.__blackboard[attributeName] ), "Blackboard - Attribute value can not be set explicitly, as it has a getterFunction." );
}
entity.__blackboard[attributeName] = attributeValue;
/#
if ( IsActor( entity ) )
entity updatetrackedblackboardattribute( attributeName );
#/
}
function CreateBlackBoardForEntity( entity )
{
if( !IsDefined( entity.__blackboard ) )
{
entity.__blackboard = [];
}
if( !IsDefined( level._setBlackboardAttributeFunc ) )
{
level._setBlackboardAttributeFunc = &SetBlackBoardAttribute;
}
}
+424
View File
@@ -0,0 +1,424 @@
// -------------------------------------------- BLACKBOARD ATTRIBUTES --------------------------//
// Attribute names, keep it lowercase. "_" before the attribute name indicates that its being used by both AST and BT.
// Desired values should have DESIRED and "_desired_" in their definition. There should be a associated BB attribute existing already
// Eg. ATTRIBUTE and DESIRED_ATTRIBUTE and "_attribute" and "_desired_attribute". Try to keep them together please. :)
// Robots
//Zombies
//contains all the possible knockdown animations
//only has the quick to the ground knockdown animations
// will be set to YES if AI will look good if he reacts with flanking
// -------------------------------------------- BLACKBOARD DEFINES --------------------//
// -------------------------------------------- AIM RELATED --------------------------//
// -------------------------------------------- STANCES -------------------------------//
// -------------------------------------------- WEAPONS -------------------------------//
// -------------------------------------------- DAMAGE -------------------------------//
// -------------------------------------------- TACTICAL WALK -------------------------//
// -------------------------------------------- CORNER PREDICTION ----------------------//
// -------------------------------------------- ARRIVAL -------------------------------//
// -------------------------------------------- LOCMOTION PAIN ------------------------//
// -------------------------------------------- DAMAGE DIRECTION ------------------------//
// -------------------------------------------- SPECIAL DEATH ------------------------//
// -------------------------------------------- AWARENESS ------------------------//
// -------------------------------- STAIRS ------------------------//
// skip4 Not implemented yet
// -------------------------------------------- HITLOCS ------------------------//
// same as hitLocation_t enum in q_shared.h
+414
View File
@@ -0,0 +1,414 @@
#using scripts\shared\system_shared;
#using scripts\shared\ai\archetype_utility;
Line truncated
#namespace as_debug;
/#
function autoexec __init__sytem__() { system::register("as_debug",&__init__,undefined,undefined); }
#/
/#
function __init__()
{
level thread debugDvars();
}
function debugDvars()
{
while( 1 )
{
if( GetDvarInt( "debug_ai_clear_corpses", 0 ) )
{
delete_all_ai_corpses();
}
{wait(.05);};
}
}
function isDebugOn()
{
return ( (GetDvarInt("animDebug") == 1) || ( isdefined (anim.debugEnt) && anim.debugEnt == self ) );
}
function drawDebugLineInternal(fromPoint, toPoint, color, durationFrames)
{
//println ("Drawing line, color "+color[0]+","+color[1]+","+color[2]);
//player = getent("player", "classname" );
//println ( "Point1 : "+fromPoint+", Point2: "+toPoint+", player: "+player.origin );
for (i=0;i<durationFrames;i++)
{
line (fromPoint, toPoint, color);
{wait(.05);};
}
}
function drawDebugLine(fromPoint, toPoint, color, durationFrames)
{
if (isDebugOn())
{
thread drawDebugLineInternal(fromPoint, toPoint, color, durationFrames);
}
}
function debugLine(fromPoint, toPoint, color, durationFrames)
{
for (i=0;i<durationFrames*20;i++)
{
line (fromPoint, toPoint, color);
{wait(.05);};
}
}
function drawDebugCross(atPoint, radius, color, durationFrames)
{
atPoint_high = atPoint + ( 0, 0, radius );
atPoint_low = atPoint + ( 0, 0, -1*radius );
atPoint_left = atPoint + ( 0, radius, 0 );
atPoint_right = atPoint + ( 0, -1*radius, 0 );
atPoint_forward = atPoint + ( radius, 0, 0 );
atPoint_back = atPoint + (-1*radius, 0, 0 );
thread debugLine(atPoint_high, atPoint_low, color, durationFrames);
thread debugLine(atPoint_left, atPoint_right, color, durationFrames);
thread debugLine(atPoint_forward, atPoint_back, color, durationFrames);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////// BEGIN ANIMSCRIPT STATE DEBUGGING /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function UpdateDebugInfo()
{
self endon("death");
self.debugInfo = SpawnStruct();
self.debugInfo.enabled = GetDvarint( "ai_debugAnimscript") > 0;
debugClearState();
while(1)
{
// the waittillframeend causes a random crash in the VM
//waittillframeend;
{wait(.05);};
UpdateDebugInfoInternal();
{wait(.05);};
}
}
function UpdateDebugInfoInternal()
{
if( isdefined(anim.debugEnt) && (anim.debugEnt==self) )
{
doInfo = true;
}
else
{
doInfo = GetDvarint( "ai_debugAnimscript") > 0;
// check if there's a selected ent
if( doInfo )
{
ai_entNum = GetDvarint( "ai_debugEntIndex");
if( ai_entNum > -1 && ai_entNum != self getEntityNumber() )
{
doInfo = false;
}
}
// clear everything out if it was just switched on to make sure we start clean
if( !self.debugInfo.enabled && doInfo )
{
self.debugInfo.shouldClearOnAnimscriptChange = true;
}
self.debugInfo.enabled = doInfo;
}
}
function drawDebugEntText( text, ent, color, channel )
{
assert( isdefined(ent) );
if( !GetDvarint( "recorder_enableRec") )
{
if( !isdefined(ent.debugAnimScriptTime) || GetTime() > ent.debugAnimScriptTime )
{
ent.debugAnimScriptLevel = 0;
ent.debugAnimScriptTime = GetTime();
}
indentLevel = VectorScale( (0,0,-10), ent.debugAnimScriptLevel );
print3d( self.origin + (0,0,70) + indentLevel, text, color );
ent.debugAnimScriptLevel++;
}
else
{
recordEntText( text, ent, color, channel );
}
}
function debugPushState(stateName, extraInfo)
{
if( !GetDvarint( "ai_debugAnimscript") )
{
return;
}
// don't do anything if it's not the selected ent
ai_entNum = GetDvarint( "ai_debugEntIndex");
if( ai_entNum > -1 && ai_entNum != self getEntityNumber() )
{
return;
}
assert( isdefined(self.debugInfo.states) );
assert( isdefined(stateName) );
//recordEntText( "push: " + stateName, self, GREEN, "Animscript" );
state = SpawnStruct();
state.stateName = stateName;
state.stateLevel = self.debugInfo.stateLevel;
state.stateTime = GetTime();
state.stateValid = true;
self.debugInfo.stateLevel++;
if( isdefined(extraInfo) )
{
state.extraInfo = extraInfo + " ";
}
self.debugInfo.states[ self.debugInfo.states.size ] = state;
}
function debugAddStateInfo(stateName, extraInfo)
{
if( !GetDvarint( "ai_debugAnimscript") )
{
return;
}
// don't do anything if it's not the selected ent
ai_entNum = GetDvarint( "ai_debugEntIndex");
if( ai_entNum > -1 && ai_entNum != self getEntityNumber() )
{
return;
}
assert( isdefined(self.debugInfo.states) );
// find the first matching state from bottom
if( isdefined(stateName) )
{
for( i = self.debugInfo.states.size - 1; i >= 0; i-- )
{
assert( isdefined( self.debugInfo.states[i] ) );
if( self.debugInfo.states[i].stateName == stateName )
{
if( !isdefined(self.debugInfo.states[i].extraInfo) )
{
self.debugInfo.states[i].extraInfo = "";
}
self.debugInfo.states[i].extraInfo += extraInfo + " ";
break;
}
}
}
else if( self.debugInfo.states.size > 0 )
{
// add to the last one
lastIndex = self.debugInfo.states.size - 1;
assert( isdefined(self.debugInfo.states[lastIndex]) );
if( !isdefined(self.debugInfo.states[lastIndex].extraInfo) )
{
self.debugInfo.states[lastIndex].extraInfo = "";
}
self.debugInfo.states[lastIndex].extraInfo += extraInfo + " ";
}
}
function debugPopState(stateName, exitReason)
{
if( !GetDvarint( "ai_debugAnimscript") || self.debugInfo.states.size <= 0 )
{
return;
}
// don't do anything if it's not the selected ent
ai_entNum = GetDvarint( "ai_debugEntIndex");
if(!isdefined(self)|| !IsAlive(self))
{
return;
}
if( ai_entNum > -1 && ai_entNum != self getEntityNumber() )
{
return;
}
assert( isdefined(self.debugInfo.states) );
if( isdefined(stateName) )
{
//recordEntText( "pop: " + stateName, self, RED, "Animscript" );
// remove elements at and after stateName
for( i=0; i < self.debugInfo.states.size; i++ )
{
if( self.debugInfo.states[i].stateName == stateName && self.debugInfo.states[i].stateValid )
{
self.debugInfo.states[i].stateValid = false;
self.debugInfo.states[i].exitReason = exitReason;
self.debugInfo.stateLevel = self.debugInfo.states[i].stateLevel;
// invalidate all states below this one
for( j=i+1; j < self.debugInfo.states.size && self.debugInfo.states[j].stateLevel > self.debugInfo.states[i].stateLevel; j++ )
{
self.debugInfo.states[j].stateValid = false;
}
break;
}
}
}
else
{
//recordEntText( "pop", self, RED, "Animscript" );
// remove the last element
for( i = self.debugInfo.states.size - 1; i >= 0; i-- )
{
if( self.debugInfo.states[ i ].stateValid )
{
self.debugInfo.states[ i ].stateValid = false;
self.debugInfo.states[ i ].exitReason = exitReason;
self.debugInfo.stateLevel--;
break;
}
}
}
}
function debugClearState()
{
self.debugInfo.states = [];
self.debugInfo.stateLevel = 0;
self.debugInfo.shouldClearOnAnimscriptChange = false;
}
function debugShouldClearState()
{
if( isdefined(self.debugInfo) && isdefined(self.debugInfo.shouldClearOnAnimscriptChange) && self.debugInfo.shouldClearOnAnimscriptChange )
{
return true;
}
return false;
}
function debugCleanStateStack()
{
newArray = [];
for( i=0; i < self.debugInfo.states.size; i++ )
{
if( self.debugInfo.states[i].stateValid )
{
newArray[ newArray.size ] = self.debugInfo.states[i];
}
}
self.debugInfo.states = newArray;
}
function indent(depth)
{
indent = "";
for( i=0; i < depth; i++ )
{
indent += " ";
}
return indent;
}
function debugDrawWeightedPoints( entity, points, weights )
{
lowestValue = 0;
highestValue = 0;
for (index = 0; index < points.size; index++)
{
if ( weights[index] < lowestValue )
{
lowestValue = weights[index];
}
if ( weights[index] > highestValue )
{
highestValue = weights[index];
}
}
for (index = 0; index < points.size; index++)
{
debugDrawWeightedPoint(entity, points[index], weights[index],lowestValue,highestValue);
}
}
function debugDrawWeightedPoint( entity, point, weight, lowestValue, highestValue )
{
deltaValue = highestValue - lowestValue;
halfDeltaValue = deltaValue / 2.0;
midValue = lowestValue + deltaValue / 2.0;
if ( halfDeltaValue == 0 )
{
halfDeltaValue = 1;
}
if ( weight <= midValue )
{
// Bad places.
redColor = 1 - abs( ( weight - lowestValue ) / halfDeltaValue );
RecordCircle( point, 2, (redColor,0,0), "Script", entity );
}
else
{
// Good places.
greenColor = 1 - abs( ( highestValue - weight ) / halfDeltaValue );
RecordCircle( point, 2, (0,greenColor,0), "Script", entity );
}
}
function delete_all_ai_corpses()
{
SetDvar( "debug_ai_clear_corpses", 0 );
corpses = GetCorpseArray();
foreach( corpse in corpses )
{
if( IsActorCorpse( corpse ) )
{
corpse delete();
}
}
}
#/
@@ -0,0 +1,167 @@
#using scripts\codescripts\struct;
#using scripts\shared\ai\systems\gib;
#using scripts\shared\clientfield_shared;
Line truncated
function autoexec main()
{
clientfield::register(
"actor",
"destructible_character_state",
1,
21,
"int",
&DestructClientUtils::_DestructHandler,
!true,
!true);
destructibles = struct::get_script_bundles( "destructiblecharacterdef" );
processedBundles = [];
// Process each destructible bundle to allow quick access to information in the future.
foreach ( destructibleName, destructible in destructibles )
{
destructBundle = SpawnStruct();
destructBundle.pieceCount = destructible.pieceCount;
destructBundle.pieces = [];
destructBundle.name = destructibleName;
for ( index = 1; index <= destructBundle.pieceCount; index++ )
{
pieceStruct = SpawnStruct();
pieceStruct.gibmodel = GetStructField( destructible, "piece" + index + "_gibmodel" );
pieceStruct.gibtag = GetStructField( destructible, "piece" + index + "_gibtag" );
pieceStruct.gibfx = GetStructField( destructible, "piece" + index + "_gibfx" );
pieceStruct.gibfxtag = GetStructField( destructible, "piece" + index + "_gibeffecttag" );
pieceStruct.gibdynentfx = GetStructField( destructible, "piece" + index + "_gibdynentfx" );
pieceStruct.gibsound = GetStructField( destructible, "piece" + index + "_gibsound" );
pieceStruct.hitlocation = GetStructField( destructible, "piece" + index + "_hitlocation" );
pieceStruct.hidetag = GetStructField( destructible, "piece" + index + "_hidetag" );
pieceStruct.detachmodel = GetStructField( destructible, "piece" + index + "_detachmodel" );
destructBundle.pieces[ destructBundle.pieces.size ] = pieceStruct;
}
processedBundles[ destructibleName ] = destructBundle;
}
// Replaces all destructible character define bundles with their processed form to free unncessary script variables.
level.scriptbundles[ "destructiblecharacterdef" ] = processedBundles;
}
#namespace DestructClientUtils;
function private _DestructHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
destructFlags = (oldValue ^ newValue);
shouldSpawnGibs = (newValue & 1);
// Don't use the old clientfield value for new entities.
if ( bNewEnt )
{
destructFlags = (0 ^ newValue);
}
if ( !IsDefined(entity.destructibledef) )
{
return;
}
// Only look at destructible pieces, skip toggle bit.
currentDestructFlag = (1 << 1);
pieceNumber = 1;
// Handles any number of simultaneous gibbings.
while ( destructFlags >= currentDestructFlag )
{
if ( destructFlags & currentDestructFlag )
{
_DestructPiece( localClientNum, entity, pieceNumber, shouldSpawnGibs );
}
currentDestructFlag = currentDestructFlag << 1;
pieceNumber++;
}
entity._destruct_state = newValue;
}
function private _DestructPiece( localClientNum, entity, pieceNumber, shouldSpawnGibs )
{
if ( !IsDefined(entity.destructibledef) )
{
return;
}
destructBundle = struct::get_script_bundle( "destructiblecharacterdef", entity.destructibledef );
piece = destructBundle.pieces[ pieceNumber - 1 ];
if ( IsDefined( piece ) )
{
if ( shouldSpawnGibs )
{
GibClientUtils::_PlayGibFX( localClientNum, entity, piece.gibfx, piece.gibfxtag );
entity thread GibClientUtils::_GibPiece( localClientNum, entity, piece.gibmodel, piece.gibtag, piece.gibdynentfx );
GibClientUtils::_PlayGibSound( localClientNum, entity, piece.gibsound );
}
_HandleDestructCallbacks( localClientNum, entity, pieceNumber );
}
}
function private _GetDestructState( localClientNum, entity )
{
if ( IsDefined( entity._destruct_state ) )
{
return entity._destruct_state;
}
return 0;
}
function private _HandleDestructCallbacks( localClientNum, entity, pieceNumber )
{
if ( IsDefined( entity._destructCallbacks ) &&
IsDefined( entity._destructCallbacks[ pieceNumber ] ) )
{
foreach ( callback in entity._destructCallbacks[ pieceNumber ] )
{
if ( IsFunctionPtr( callback ) )
{
[[callback]]( localClientNum, entity, pieceNumber );
}
}
}
}
function AddDestructPieceCallback( localClientNum, entity, pieceNumber, callbackFunction )
{
assert( IsFunctionPtr( callbackFunction ) );
if ( !IsDefined( entity._destructCallbacks ) )
{
entity._destructCallbacks = [];
}
if ( !IsDefined( entity._destructCallbacks[ pieceNumber ] ) )
{
entity._destructCallbacks[ pieceNumber ] = [];
}
destructCallbacks = entity._destructCallbacks[ pieceNumber ];
destructCallbacks[ destructCallbacks.size ] = callbackFunction;
entity._destructCallbacks[ pieceNumber ] = destructCallbacks;
}
function IsPieceDestructed( localClientNum, entity, pieceNumber )
{
return (_GetDestructState( localClientNum, entity ) & (1 << pieceNumber));
}
// end #namespace DestructClientUtils;
@@ -0,0 +1,313 @@
#using scripts\codescripts\struct;
#using scripts\shared\clientfield_shared;
#using scripts\shared\math_shared;
#using scripts\shared\array_shared;
Line truncated
function autoexec main()
{
clientfield::register(
"actor",
"destructible_character_state",
1,
21,
"int" );
destructibles = struct::get_script_bundles( "destructiblecharacterdef" );
processedBundles = [];
// Process each destructible bundle to allow quick access to information in the future.
foreach ( destructibleName, destructible in destructibles )
{
destructBundle = SpawnStruct();
destructBundle.pieceCount = destructible.pieceCount;
destructBundle.pieces = [];
destructBundle.name = destructibleName;
for ( index = 1; index <= destructBundle.pieceCount; index++ )
{
pieceStruct = SpawnStruct();
pieceStruct.gibmodel = GetStructField( destructible, "piece" + index + "_gibmodel" );
pieceStruct.gibtag = GetStructField( destructible, "piece" + index + "_gibtag" );
pieceStruct.gibfx = GetStructField( destructible, "piece" + index + "_gibfx" );
pieceStruct.gibfxtag = GetStructField( destructible, "piece" + index + "_gibeffecttag" );
pieceStruct.gibdynentfx = GetStructField( destructible, "piece" + index + "_gibdynentfx" );
pieceStruct.gibsound = GetStructField( destructible, "piece" + index + "_gibsound" );
pieceStruct.hitlocation = GetStructField( destructible, "piece" + index + "_hitlocation" );
pieceStruct.hidetag = GetStructField( destructible, "piece" + index + "_hidetag" );
pieceStruct.detachmodel = GetStructField( destructible, "piece" + index + "_detachmodel" );
destructBundle.pieces[ destructBundle.pieces.size ] = pieceStruct;
}
processedBundles[ destructibleName ] = destructBundle;
}
// Replaces all destructible character define bundles with their processed form to free unncessary script variables.
level.scriptbundles[ "destructiblecharacterdef" ] = processedBundles;
}
#namespace DestructServerUtils;
function private _GetDestructState( entity )
{
if ( IsDefined( entity._destruct_state ) )
{
return entity._destruct_state;
}
return 0;
}
function private _SetDestructed( entity, destructFlag )
{
entity._destruct_state = (_GetDestructState( entity ) | destructFlag);
entity clientfield::set( "destructible_character_state", entity._destruct_state );
}
function CopyDestructState( originalEntity, newEntity )
{
newEntity._destruct_state = _GetDestructState( originalEntity );
ToggleSpawnGibs( newEntity, false );
ReapplyDestructedPieces( newEntity );
}
function DestructHitLocPieces( entity, hitLoc )
{
if ( IsDefined(entity.destructibledef) )
{
destructBundle = struct::get_script_bundle( "destructiblecharacterdef", entity.destructibledef );
for ( index = 1; index <= destructBundle.pieces.size; index++ )
{
piece = destructBundle.pieces[ index - 1 ];
if ( IsDefined( piece.hitlocation ) && piece.hitlocation == hitLoc )
{
DestructPiece( entity, index );
}
}
}
}
function DestructLeftArmPieces( entity )
{
DestructHitLocPieces( entity, "left_arm_upper" );
DestructHitLocPieces( entity, "left_arm_lower" );
DestructHitLocPieces( entity, "left_hand" );
}
function DestructLeftLegPieces( entity )
{
DestructHitLocPieces( entity, "left_leg_upper" );
DestructHitLocPieces( entity, "left_leg_lower" );
DestructHitLocPieces( entity, "left_foot" );
}
function DestructPiece( entity, pieceNumber )
{
/# assert( (1 <= pieceNumber && pieceNumber <= 20) ); #/
if ( IsDestructed( entity, pieceNumber ) )
{
return;
}
_SetDestructed( entity, (1 << pieceNumber) );
if ( !IsDefined(entity.destructibledef) )
{
return;
}
destructBundle = struct::get_script_bundle( "destructiblecharacterdef", entity.destructibledef );
piece = destructBundle.pieces[ pieceNumber - 1 ];
if ( IsDefined( piece.hidetag ) && entity HasPart( piece.hidetag ) )
{
entity HidePart( piece.hidetag );
}
if ( IsDefined( piece.detachmodel ) )
{
entity Detach( piece.detachmodel, "" );
}
}
// Destruct a specified number of pieces
// Use 0 to destruct all pieces
function DestructNumberRandomPieces( entity, num_pieces_to_destruct=0 )
{
// Array containing our desctructible pieces.
destructible_pieces_list = [];
// Get the total number of breakable pieces we have
destructablePieces = GetPieceCount( entity );
if (num_pieces_to_destruct == 0)
{
num_pieces_to_destruct = destructablePieces;
}
// Build the list of pieces
for ( i = 0; i < destructablePieces; i++ )
{
destructible_pieces_list[i] = i + 1;
}
// Shuffle the list of pieces so we break off pieces at random
destructible_pieces_list = array::randomize(destructible_pieces_list);
// Go through the randomized list breaking off pieces until we either run out of pieces or broke off all the pieces we needed to.
foreach(piece in destructible_pieces_list)
{
if(!IsDestructed(entity,piece))
{
DestructPiece(entity,piece);
num_pieces_to_destruct--;
if(num_pieces_to_destruct == 0 )
break;
}
}
}
function DestructRandomPieces( entity)
{
destructPieces = GetPieceCount( entity );
for ( index = 0; index < destructPieces; index++ )
{
if ( math::cointoss() )
{
DestructPiece( entity, index + 1 );
}
}
}
function DestructRightArmPieces( entity )
{
DestructHitLocPieces( entity, "right_arm_upper" );
DestructHitLocPieces( entity, "right_arm_lower" );
DestructHitLocPieces( entity, "right_hand" );
}
function DestructRightLegPieces( entity )
{
DestructHitLocPieces( entity, "right_leg_upper" );
DestructHitLocPieces( entity, "right_leg_lower" );
DestructHitLocPieces( entity, "right_foot" );
}
function GetPieceCount( entity )
{
if ( IsDefined(entity.destructibledef) )
{
destructBundle = struct::get_script_bundle( "destructiblecharacterdef", entity.destructibledef );
if ( IsDefined( destructBundle ) )
{
return destructBundle.piececount;
}
}
return 0;
}
function HandleDamage(
eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex, modelIndex )
{
entity = self;
if ( ( isdefined( entity.skipDeath ) && entity.skipDeath ) )
{
return iDamage;
}
if ( ( isdefined( entity.skipGibs ) && entity.skipGibs ) )
{
return iDamage;
}
ToggleSpawnGibs( entity, true );
DestructHitLocPieces( entity, sHitLoc );
return iDamage;
}
function IsDestructed( entity, pieceNumber )
{
/# assert( (1 <= pieceNumber && pieceNumber <= 20) ); #/
return (_GetDestructState( entity ) & (1 << pieceNumber));
}
function ReapplyDestructedPieces( entity )
{
if ( !IsDefined(entity.destructibledef) )
{
return;
}
destructBundle = struct::get_script_bundle( "destructiblecharacterdef", entity.destructibledef );
for ( index = 1; index <= destructBundle.pieces.size; index++ )
{
if ( !IsDestructed( entity, index ) )
{
continue;
}
piece = destructBundle.pieces[ index - 1];
if ( IsDefined( piece.hidetag ) && entity HasPart( piece.hidetag ) )
{
entity HidePart( piece.hidetag );
}
}
}
function ShowDestructedPieces( entity )
{
if ( !IsDefined(entity.destructibledef) )
{
return;
}
destructBundle = struct::get_script_bundle( "destructiblecharacterdef", entity.destructibledef );
for ( index = 1; index <= destructBundle.pieces.size; index++ )
{
piece = destructBundle.pieces[ index - 1];
if ( IsDefined( piece.hidetag ) && entity HasPart( piece.hidetag ) )
{
entity ShowPart( piece.hidetag );
}
}
}
function ToggleSpawnGibs( entity, shouldSpawnGibs )
{
if ( shouldSpawnGibs )
{
entity._destruct_state = _GetDestructState( entity ) | 1;
}
else
{
entity._destruct_state = _GetDestructState( entity ) & ~1;
}
entity clientfield::set( "destructible_character_state", entity._destruct_state );
}
// end #namespace DestructServerUtils;
@@ -0,0 +1,24 @@
// 20 possible states and 1 toggle bit to determine if spawning gib models should happen on the client.
// The toggle bit is used to predamage characters when they are spawned in, without creating gib models.
// (20 + 1) states
// Pieces numbers start at 1, and stop at DESTRUCT_MAX_PIECES
+421
View File
@@ -0,0 +1,421 @@
#using scripts\shared\math_shared;
#using scripts\shared\util_shared;
Line truncated
#precache( "lui_menu", "TempDialog" );
#precache( "lui_menu_data", "dialogText" );
#precache( "lui_menu_data", "title" );
// face.gsc
// Supposed to handle all facial and dialogue animations from regular scripts.
//#using_animtree ("generic_human"); - This file doesn't call animations directly.
function SayGenericDialogue(typeString)
{
if (level.disableGenericDialog)
{
return;
}
switch (typeString)
{
case "attack":
importance = 0.5;
break;
case "swing":
importance = 0.5;
typeString = "attack";
break;
case "flashbang":
importance = 0.7;
break;
case "pain_small":
importance = 0.4;
break;
case "pain_bullet":
wait(.01); //make sure dialog system has had a chance to deal with death before we play another line.
importance = 0.4;
break;
default:
/#println ("Unexpected generic dialog string: "+typeString);#/
importance = 0.3;
break;
}
SayGenericDialogueWithImportance(typeString, importance);
}
// Makes a character say the specified line in his voice, if he's not already saying something more
// important.
function SayGenericDialogueWithImportance(typeString, importance)
{
soundAlias = "dds_";
if( isdefined( self.dds_characterID ) )
{
soundAlias += self.dds_characterID;
}
else
{
/#printLn( "this AI does not have a dds_characterID" );#/
return;
}
soundAlias += "_" + typeString;
if(SoundExists(soundAlias))
{
self thread PlayFaceThread (undefined, soundAlias, importance);
}
}
function SetIdleFaceDelayed(facialAnimationArray)
{
self.a.idleFace = facialAnimationArray;
}
// Sets the facial expression to return to when not saying dialogue.
// The array is animation1, weight1, animation2, weight2, etc. The animations will play in turn - each time
// one finishes a new one will be chosen randomly based on weight.
function SetIdleFace(facialAnimationArray)
{
if (!anim.useFacialAnims)
{
return;
}
self.a.idleFace = facialAnimationArray;
self PlayIdleFace();
}
// Makes the character play the specified sound and animation. The anim and the sound are optional - you
// can just defined one if you don't have both.
// Generally, importance should be in the range of 0.6-0.8 for scripted dialogue.
// Importance is a float, from 0 to 1.
// 0.0 - Idle expressions
// 0.1-0.5 - most generic dialogue
// 0.6-0.8 - most scripted dialogue
// 0.9 - pain
// 1.0 - death
// Importance can also be one of these strings: "any", "pain" or "death", which specfies what sounds can
// interrupt this one.
function SaySpecificDialogue(facialanim, soundAlias, importance, notifyString, waitOrNot, timeToWait, toplayer)
{
///("SaySpecificDialog, facial: ",facialanim,", sound: ",soundAlias,", importance: "+importance+", notify: ",notifyString, ", WaitOrNot: ", waitOrNot, ", timeToWait: ", timeToWait);#/
self thread PlayFaceThread(facialanim, soundAlias, importance, notifyString, waitOrNot, timeToWait, toplayer);
}
// DEAD CODE REMOVAL
// Takes an array with a set of "anim" entries and a corresponding set of "weight" entries.
//ChooseAnimFromSet( animSet )
//{
// return; // Facial animations are now part of full body aniamtions
/*
if (!anim.useFacialAnims)
return;
// First, normalize the weights.
totalWeight = 0;
numAnims = animSet["anim"].size;
for ( i=0 ; i<numAnims ; i++ )
{
totalWeight += animSet["weight"][i];
}
for ( i=0 ; i<numAnims ; i++ )
{
animSet["weight"][i] = animSet["weight"][i] / totalWeight;
}
// Now choose an animation.
rand = RandomFloat(1);
runningTotal = 0;
chosenAnim = undefined;
for ( i=0 ; i<numAnims ; i++ )
{
runningTotal += animSet["weight"][i];
if (runningTotal >= rand)
{
chosenAnim = i;
break;
}
}
assert(isdefined(chosenAnim), "Logic error in ChooseAnimFromSet. Rand is " + rand + ".");
return animSet["anim"][chosenAnim];
*/
//}
//-----------------------------------------------------
// Housekeeping functions - these are for internal use
//-----------------------------------------------------
// PlayIdleFace doesn't force an idle animation to play - it will interrupt a current idle animation, but it
// won't play over a more important animation, like dialogue.
function PlayIdleFace()
{
return; // Idle facial animations are now in the full-body animations.
}
// PlayFaceThread is the workhorse of the system - it checks the importance, and if it's high enough, it
// plays the animation and/or sound specified.
// The waitOrNot parameter specifies what to do if another animation/sound is already playing.
// Options: "wait" or undefined. TimeToWait is an optional timeout time for waiting.
// Waiting faces are queued.
function PlayFaceThread( facialanim, str_script_alias, importance, notifyString, waitOrNot, timeToWait, toplayer )
{
//NOTE most of the described functionality was removed in COD2 (!)
//Talk about misleading comments...
//This must be called in it's own thread.
//
//The behavior has been simplifed greatly
// lines never wait
// a line stops the current line if at equal or higher priority
// a line doesn't play if at lower priority
//SBM 8/3/08
self endon( "death" );
if ( !isdefined( str_script_alias ) )
{
wait 1;
self notify( notifyString );
return; //no sound, don't do anything facials are now included into the main anims
}
str_notify_alias = str_script_alias; // Save this off because the script alias gets changed for females
if(!isdefined(level.NumberOfImportantPeopleTalking))level.NumberOfImportantPeopleTalking=0;
if(!isdefined(level.TalkNotifySeed))level.TalkNotifySeed=0;
if(!isdefined(notifyString))notifyString="PlayFaceThread " + str_script_alias;
if(!isdefined(self.a))self.a=SpawnStruct();
if(!isdefined(self.a.facialSoundDone))self.a.facialSoundDone=true;
if(!isdefined(self.isTalking))self.isTalking=false;
if ( self.isTalking ) //currently talking
{
//TUEY added equal to, so that the system won't spam the same notifies
if ( IsDefined( self.a.currentDialogImportance ) )
{
if ( importance < self.a.currentDialogImportance )
{
wait 1;
self notify( notifyString );
return; //skip it cause its not important
}
else if ( importance == self.a.currentDialogImportance )
{
if ( self.a.facialSoundAlias == str_script_alias )
{
return; //playing same alias twice? probably a script error somewhere
}
/# println( "WARNING: delaying alias " + self.a.facialSoundAlias + " to play " + str_script_alias ); #/
while ( self.isTalking ) //while is to fix a race condition
{
self waittill( "done speaking" ); //wait currently playing thread to finish
}
}
}
else
{
/# PrintLn("WARNING: interrupting alias "+self.a.facialSoundAlias+" to play "+str_script_alias); #/
self StopSound( self.a.facialSoundAlias );
self notify("cancel speaking");
while ( self.isTalking ) //while is to fix a race condition
{
self waittill( "done speaking" ); //wait currently playing thread to finish
}
}
}
assert( self.a.facialSoundDone );
assert( self.a.facialSoundAlias == undefined );
assert( self.a.facialSoundNotify == undefined );
assert( self.a.currentDialogImportance == undefined );
assert( !self.isTalking );
self notify( "bc_interrupt" );
self.isTalking = true;
self.a.facialSoundDone = false;
self.a.facialSoundNotify = notifyString;
self.a.facialSoundAlias = str_script_alias;
self.a.currentDialogImportance = importance;
if ( importance == 1.0 )
{
level.NumberOfImportantPeopleTalking += 1;
}
/#
if ( level.NumberOfImportantPeopleTalking > 1 )
{
println( "WARNING: multiple high priority dialogs are happening at once " + str_script_alias );
}
#/
uniqueNotify = notifyString + " " + level.TalkNotifySeed;
level.TalkNotifySeed += 1;
if ( isdefined( level.scr_sound ) && isdefined( level.scr_sound[ "generic" ] ) )
{
str_vox_file = level.scr_sound[ "generic" ][ str_script_alias ];
}
if ( isdefined( str_vox_file ) )
{
if ( SoundExists( str_vox_file ) )
{
if ( IsPlayer( toplayer ) )
{
self thread _play_sound_to_player_with_notify( str_vox_file, toplayer, uniqueNotify );
}
else
{
if ( isdefined( self GetTagOrigin( "J_Head" ) ) )
{
self PlaySoundWithNotify( str_vox_file, uniqueNotify, "J_Head" );
}
else
{
self PlaySoundWithNotify( str_vox_file, uniqueNotify );
}
}
}
else
{
/#
PrintLn( "WARNING: DIALOG VO SOUND: '" + str_script_alias + "' does not exist." );
self thread _missing_dialog( str_script_alias, str_vox_file, uniqueNotify );
#/
}
}
else
{
self thread _temp_dialog( str_script_alias, uniqueNotify );
}
self util::waittill_any( "death", "cancel speaking", uniqueNotify );
if ( importance == 1.0 )
{
level.NumberOfImportantPeopleTalking -= 1;
level.ImportantPeopleTalkingTime = GetTime();
}
if ( isdefined( self ) )
{
self.isTalking = false;
self.a.facialSoundDone = true;
self.a.facialSoundNotify = undefined;
self.a.facialSoundAlias = undefined;
self.a.currentDialogImportance = undefined;
self.lastSayTime = GetTime();
}
self notify( "done speaking", str_notify_alias );
self notify( notifyString );
}
// HACK: there is no way to get a notify from a sound on a specific client.
// Even though SoundGetPlaybackTime isn't 100% and won't be accurate with locs,
// it's our best bet for now.
function _play_sound_to_player_with_notify( soundAlias, toplayer, uniqueNotify )
{
self endon( "death" );
toplayer endon( "death" );
self PlaySoundToPlayer( soundAlias, toplayer );
n_playbackTime = SoundGetPlaybackTime( soundAlias );
if ( n_playbackTime > 0 )
{
wait n_playbackTime * .001;
}
else
{
wait 1.0;
}
self notify( uniqueNotify );
}
function private _temp_dialog( str_line, uniqueNotify, b_missing_vo = false )
{
SetDvar( "bgcache_disablewarninghints", 1 );
if ( !b_missing_vo && isdefined( self.propername ) )
{
str_line = self.propername + ": " + str_line;
}
foreach ( player in level.players )
{
if ( !isdefined( player GetLUIMenu( "TempDialog" ) ) )
{
player OpenLuiMenu( "TempDialog" );
}
player SetLuiMenuData( player GetLUIMenu( "TempDialog" ), "dialogText", str_line );
if ( b_missing_vo )
{
player SetLuiMenuData( player GetLUIMenu( "TempDialog" ), "title", "MISSING VO SOUND" );
}
else
{
player SetLuiMenuData( player GetLUIMenu( "TempDialog" ), "title", "TEMP VO" );
}
}
// wait time based on average words spoken (2 per second) and capped at 5 seconds total, -1 for propername
n_wait_time = ( StrTok( str_line, " ").size - 1 ) / 2;
n_wait_time = math::clamp( n_wait_time, 2, 5);
util::waittill_any_timeout( n_wait_time, "death", "cancel speaking" );
foreach ( player in level.players )
{
if ( isdefined( player GetLUIMenu( "TempDialog" ) ) )
{
player CloseLUIMenu( player GetLUIMenu( "TempDialog" ) );
}
}
SetDvar( "bgcache_disablewarninghints", 0 );
self notify( uniqueNotify );
}
function private _missing_dialog( str_script_alias, str_vox_file, uniqueNotify )
{
_temp_dialog( "script id: " + str_script_alias + " sound alias: " + str_vox_file, uniqueNotify, true );
}
function PlayFace_WaitForNotify(waitForString, notifyString, killmeString)
{
self endon ("death");
self endon (killmeString);
self waittill (waitForString);
self.a.faceWaitForResult = "notify";
self notify (notifyString);
}
function PlayFace_WaitForTime(time, notifyString, killmeString)
{
self endon ("death");
self endon (killmeString);
wait (time);
self.a.faceWaitForResult = "time";
self notify (notifyString);
}
+256
View File
@@ -0,0 +1,256 @@
#using scripts\codescripts\struct;
#using scripts\shared\ai\systems\destructible_character;
#using scripts\shared\ai\systems\gib;
Line truncated
function autoexec main()
{
fxBundles = struct::get_script_bundles( "fxcharacterdef" );
processedFxBundles = [];
// Process all fx character script bundles into a direct lookup form for future access.
foreach ( fxBundleName, fxBundle in fxBundles )
{
processedFxBundle = SpawnStruct();
processedFxBundle.effectCount = fxBundle.effectCount;
processedFxBundle.fx = [];
processedFxBundle.name = fxBundleName;
for ( index = 1; index <= fxBundle.effectCount; index++ )
{
fx = GetStructField( fxBundle, "effect" + index + "_fx" );
if ( IsDefined( fx ) )
{
fxStruct = SpawnStruct();
fxStruct.attachTag = GetStructField( fxBundle, "effect" + index + "_attachtag" );
fxStruct.fx = GetStructField( fxBundle, "effect" + index + "_fx" );
fxStruct.stopOnGib = FxClientUtils::_GibPartNameToGibFlag(
GetStructField( fxBundle, "effect" + index + "_stopongib" ) );
fxStruct.stopOnPieceDestroyed = GetStructField( fxBundle, "effect" + index + "_stoponpiecedestroyed" );
processedFxBundle.fx[ processedFxBundle.fx.size ] = fxStruct;
}
}
processedFxBundles[ fxBundleName ] = processedFxBundle;
}
// Replaces all fx character script bundles with their processed form to free unncessary script variables.
level.scriptbundles[ "fxcharacterdef" ] = processedFxBundles;
}
#namespace FxClientUtils;
function private _ConfigEntity( localClientNum, entity )
{
if ( !IsDefined( entity._fxCharacter ) )
{
entity._fxCharacter = [];
handledGibs = array(
8,
16,
32,
128,
256 );
foreach ( gibFlag in handledGibs )
{
GibClientUtils::AddGibCallback( localClientNum, entity, gibFlag, &_GibHandler );
}
for ( index = 1; index <= 20; index++ )
{
DestructClientUtils::AddDestructPieceCallback( localClientNum, entity, index, &_DestructHandler );
}
}
}
function private _DestructHandler( localClientNum, entity, pieceNumber )
{
if ( !IsDefined( entity._fxCharacter ) )
{
return;
}
foreach ( fxBundleName, fxBundleInst in entity._fxCharacter )
{
fxBundle = struct::get_script_bundle( "fxcharacterdef", fxBundleName );
for ( index = 0; index < fxBundle.fx.size; index++ )
{
if ( IsDefined( fxBundleInst[ index ] ) &&
fxBundle.fx[index].stopOnPieceDestroyed === pieceNumber )
{
StopFx( localClientNum, fxBundleInst[ index ] );
fxBundleInst[ index ] = undefined;
}
}
}
}
function private _GibHandler( localClientNum, entity, gibFlag )
{
if ( !IsDefined( entity._fxCharacter ) )
{
return;
}
foreach ( fxBundleName, fxBundleInst in entity._fxCharacter )
{
fxBundle = struct::get_script_bundle( "fxcharacterdef", fxBundleName );
for ( index = 0; index < fxBundle.fx.size; index++ )
{
if ( IsDefined( fxBundleInst[ index ] ) && fxBundle.fx[index].stopOnGib === gibFlag )
{
StopFx( localClientNum, fxBundleInst[ index ] );
fxBundleInst[ index ] = undefined;
}
}
}
}
function private _GibPartNameToGibFlag( gibPartName )
{
if ( IsDefined( gibPartName ) )
{
switch ( gibPartName )
{
case "head":
return 8;
case "right arm":
return 16;
case "left arm":
return 32;
case "right leg":
return 128;
case "left leg":
return 256;
}
}
}
function private _IsGibbed( localClientNum, entity, stopOnGibFlag )
{
if ( !IsDefined( stopOnGibFlag ) )
{
return false;
}
return GibClientUtils::IsGibbed( localClientNum, entity, stopOnGibFlag );
}
function private _IsPieceDestructed( localClientNum, entity, stopOnPieceDestroyed )
{
if ( !IsDefined( stopOnPieceDestroyed ) )
{
return false;
}
return DestructClientUtils::IsPieceDestructed( localClientNum, entity, stopOnPieceDestroyed );
}
function private _ShouldPlayFx( localClientNum, entity, fxStruct )
{
if ( _IsGibbed( localClientNum, entity, fxStruct.stopOnGib ) )
{
return false;
}
if ( _IsPieceDestructed( localClientNum, entity, fxStruct.stopOnPieceDestroyed ) )
{
return false;
}
return true;
}
function PlayFxBundle( localClientNum, entity, fxScriptBundle )
{
if ( !IsDefined( fxScriptBundle ) )
{
return;
}
_ConfigEntity( localClientNum, entity );
fxBundle = struct::get_script_bundle( "fxcharacterdef", fxScriptBundle );
if ( IsDefined( entity._fxCharacter[ fxBundle.name ] ) )
{
// FxBundle is already playing, early out.
return;
}
if ( IsDefined( fxBundle ) )
{
playingFx = [];
for ( index = 0; index < fxBundle.fx.size; index++ )
{
fxStruct = fxBundle.fx[ index ];
if ( _ShouldPlayFx( localClientNum, entity, fxStruct ) )
{
playingFx[ index ] =
GibClientUtils::_PlayGibFX( localClientNum, entity, fxStruct.fx, fxStruct.attachTag );
}
}
if ( playingFx.size > 0 )
{
entity._fxCharacter[ fxBundle.name ] = playingFx;
}
}
}
function StopAllFXBundles( localClientNum, entity )
{
_ConfigEntity( localClientNum, entity );
fxBundleNames = [];
foreach ( fxBundleName, fxBundle in entity._fxCharacter )
{
fxBundleNames[ fxBundleNames.size ] = fxBundleName;
}
foreach ( fxBundleName in fxBundleNames )
{
StopFxBundle( localClientNum, entity, fxBundleName );
}
}
function StopFxBundle( localClientNum, entity, fxScriptBundle )
{
if ( !IsDefined( fxScriptBundle ) )
{
return;
}
_ConfigEntity( localClientNum, entity );
fxBundle = struct::get_script_bundle( "fxcharacterdef", fxScriptBundle );
if ( IsDefined( entity._fxCharacter[ fxBundle.name ] ) )
{
foreach ( fx in entity._fxCharacter[ fxBundle.name ] )
{
if ( IsDefined( fx ) )
{
StopFx( localClientNum, fx );
}
}
entity._fxCharacter[ fxBundle.name ] = undefined;
}
}
+1
View File
@@ -0,0 +1 @@
+792
View File
@@ -0,0 +1,792 @@
#using scripts\codescripts\struct;
#using scripts\shared\clientfield_shared;
#using scripts\shared\util_shared;
Line truncated
#namespace GibClientUtils;
function autoexec main()
{
clientfield::register(
"actor",
"gib_state",
1,
(9),
"int",
&GibClientUtils::_GibHandler,
!true,
!true);
clientfield::register(
"playercorpse",
"gib_state",
1,
(9+3+3),
"int",
&GibClientUtils::_GibHandler,
!true,
!true);
gibDefinitions = struct::get_script_bundles("gibcharacterdef");
gibPieceLookup = [];
gibPieceLookup[2] = "annihilate";
gibPieceLookup[8] = "head";
gibPieceLookup[16] = "rightarm";
gibPieceLookup[32] = "leftarm";
gibPieceLookup[128] = "rightleg";
gibPieceLookup[256] = "leftleg";
processedBundles = [];
// Process each gib bundle to allow quick access to information in the future.
foreach ( definitionName, definition in gibDefinitions )
{
gibBundle = SpawnStruct();
gibBundle.gibs = [];
gibBundle.name = definitionName;
foreach ( gibPieceFlag, gibPieceName in gibPieceLookup )
{
gibStruct = SpawnStruct();
gibStruct.gibmodel = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibmodel" );
gibStruct.gibtag = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibtag" );
gibStruct.gibfx = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibfx" );
gibStruct.gibfxtag = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibeffecttag" );
gibStruct.gibdynentfx = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibdynentfx" );
gibStruct.gibsound = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibsound" );
gibBundle.gibs[ gibPieceFlag ] = gibStruct;
}
processedBundles[ definitionName ] = gibBundle;
}
// Replaces all gib character define bundles with their processed form to free unncessary script variables.
level.scriptbundles[ "gibcharacterdef" ] = processedBundles;
// Thread that handles any corpse annihilate request.
level thread _AnnihilateCorpse();
}
function private _AnnihilateCorpse()
{
while( true )
{
level waittill( "corpse_explode", localClientNum, body, origin );
if ( !util::is_mature() || util::is_gib_restricted_build() )
{
continue;
}
if ( IsDefined( body ) && _HasGibDef( body ) && body IsRagdoll() )
{
ClientEntGibHead( localClientNum, body );
ClientEntGibRightArm( localClientNum, body );
ClientEntGibLeftArm( localClientNum, body );
ClientEntGibRightLeg( localClientNum, body );
ClientEntGibLeftLeg( localClientNum, body );
}
// TODO(David Young 6-8-15): Human bodies are currently the only supported gibbable corpse.
// Need to add support to the gib definition to flag if a corpse can be annihilated instead.
if( IsDefined( body ) && _HasGibDef( body ) && body.archetype == "human" )
{
// Only allow human gibbing at a 50% rate.
if ( RandomInt( 100 ) >= 50 )
{
continue;
}
if ( IsDefined( origin ) && DistanceSquared( body.origin, origin ) <= ( 120 * 120 ) )
{
// Toggle hiding the ragdoll.
body.ignoreRagdoll = true;
body _GibEntity(
localClientNum,
2 |
16 |
32 |
(128+256),
true );
}
}
}
}
function private _CloneGibData( localClientNum, entity, clone )
{
clone.gib_data = SpawnStruct();
// Copy gib data.
clone.gib_data.gib_state = entity.gib_state;
clone.gib_data.gibdef = entity.gibdef;
// Copy all model data.
clone.gib_data.hatmodel = entity.hatmodel;
clone.gib_data.head = entity.head;
clone.gib_data.legdmg1 = entity.legdmg1;
clone.gib_data.legdmg2 = entity.legdmg2;
clone.gib_data.legdmg3 = entity.legdmg3;
clone.gib_data.legdmg4 = entity.legdmg4;
clone.gib_data.torsodmg1 = entity.torsodmg1;
clone.gib_data.torsodmg2 = entity.torsodmg2;
clone.gib_data.torsodmg3 = entity.torsodmg3;
clone.gib_data.torsodmg4 = entity.torsodmg4;
clone.gib_data.torsodmg5 = entity.torsodmg5;
}
function private _GetGibDef( entity )
{
if ( entity IsPlayer() || entity IsPlayerCorpse() )
{
return entity GetPlayerGibDef();
}
else if ( IsDefined( entity.gib_data ) )
{
return entity.gib_data.gibdef;
}
return entity.gibdef;
}
function private _GetGibbedState( localClientNum, entity )
{
if ( IsDefined( entity.gib_data ) && IsDefined( entity.gib_data.gib_state ) )
{
return entity.gib_data.gib_state;
}
else if ( IsDefined( entity.gib_state ) )
{
return entity.gib_state;
}
return 0;
}
function private _GetGibbedLegModel( localClientNum, entity )
{
gibState = _GetGibbedState( localClientNum, entity );
rightLegGibbed = (gibState & 128);
leftLegGibbed = (gibState & 256);
if ( rightLegGibbed && leftLegGibbed)
{
return (isdefined(entity.gib_data)?entity.gib_data.legDmg4:entity.legDmg4);
}
else if ( rightLegGibbed )
{
return (isdefined(entity.gib_data)?entity.gib_data.legDmg2:entity.legDmg2);
}
else if ( leftLegGibbed )
{
return (isdefined(entity.gib_data)?entity.gib_data.legDmg3:entity.legDmg3);
}
return (isdefined(entity.gib_data)?entity.gib_data.legDmg1:entity.legDmg1);
}
function private _GetGibExtraModel( localClientNumm, entity, gibFlag )
{
if ( gibFlag == 4 )
return (isdefined(entity.gib_data)?entity.gib_data.hatmodel:entity.hatmodel);
else if ( gibFlag == 8 )
return (isdefined(entity.gib_data)?entity.gib_data.head:entity.head);
else
AssertMsg( "Unable to find gib model." );
}
function private _GetGibbedTorsoModel( localClientNum, entity )
{
gibState = _GetGibbedState( localClientNum, entity );
rightArmGibbed = (gibState & 16);
leftArmGibbed = (gibState & 32);
if ( rightArmGibbed && leftArmGibbed )
{
return (isdefined(entity.gib_data)?entity.gib_data.torsoDmg2:entity.torsoDmg2);
// TODO(David Young 5-14-14): Currently AI's don't support both arms getting blown off.
// return GIB_TORSO_NO_ARMS_MODEL( entity );
}
else if ( rightArmGibbed )
{
return (isdefined(entity.gib_data)?entity.gib_data.torsoDmg2:entity.torsoDmg2);
}
else if ( leftArmGibbed )
{
return (isdefined(entity.gib_data)?entity.gib_data.torsoDmg3:entity.torsoDmg3);
}
return (isdefined(entity.gib_data)?entity.gib_data.torsoDmg1:entity.torsoDmg1);
}
function private _GibPieceTag( localClientNum, entity, gibFlag )
{
if ( !_HasGibDef( self ) )
{
return;
}
gibBundle = struct::get_script_bundle("gibcharacterdef",_GetGibDef( entity ));
gibPiece = gibBundle.gibs[ gibFlag ];
if ( IsDefined( gibPiece ) )
{
return gibPiece.gibfxtag;
}
}
function private _GibEntity( localClientNum, gibFlags, shouldSpawnGibs )
{
entity = self;
if ( !_HasGibDef( entity ) )
{
return;
}
// Skip the toggle flag, GIB_TOGGLE_GIB_MODEL_FLAG.
currentGibFlag = 2;
gibDir = undefined;
if ( entity IsPlayer() || entity IsPlayerCorpse() )
{
// TODO(David Young 6-8-15): Add support for yaw bits for AI as well.
yaw_bits = ( ( gibFlags >> 9 ) & ( ( 1 << 3 ) - 1 ) );
yaw = getanglefrombits( yaw_bits, 3 );
gibDir = AnglesToForward( ( 0, yaw, 0 ) );
}
gibBundle = struct::get_script_bundle("gibcharacterdef",_GetGibDef( entity ));
// Handles any number of simultaneous gibbings.
while ( gibFlags >= currentGibFlag )
{
if ( gibFlags & currentGibFlag )
{
gibPiece = gibBundle.gibs[ currentGibFlag ];
if ( IsDefined( gibPiece ) )
{
if ( shouldSpawnGibs )
{
entity thread _GibPiece( localClientNum, entity, gibPiece.gibmodel, gibPiece.gibtag, gibPiece.gibdynentfx, gibDir );
}
_PlayGibFX( localClientNum, entity, gibPiece.gibfx, gibPiece.gibfxtag );
_PlayGibSound( localClientNum, entity, gibPiece.gibsound );
if ( currentGibFlag == 2 )
{
entity Hide();
entity.ignoreRagdoll = true;
}
}
_HandleGibCallbacks( localClientNum, entity, currentGibFlag );
}
currentGibFlag = currentGibFlag << 1;
}
}
function private _SetGibbed( localClientNum, entity, gibFlag )
{
gib_state = (_GetGibbedState( localClientNum, entity ) | ( gibFlag & ( ( 1 << 9 ) - 1 ) ));
if ( IsDefined( entity.gib_data ) )
{
entity.gib_data.gib_state = gib_state;
}
else
{
entity.gib_state = gib_state;
}
}
function private _GibClientEntityInternal( localClientNum, entity, gibFlag )
{
if ( !util::is_mature() || util::is_gib_restricted_build() )
{
return;
}
if ( !IsDefined( entity ) || !_HasGibDef( entity ) )
{
return;
}
if ( entity.type !== "scriptmover" )
{
// Clientside gibbing currently only supports script models.
return;
}
if ( IsGibbed( localClientNum, entity, gibFlag ) )
{
return;
}
if ( !(_GetGibbedState( localClientNum, entity ) < 16) )
{
legModel = _GetGibbedLegModel( localClientNum, entity );
entity Detach( legModel, "" );
}
_SetGibbed( localClientNum, entity, gibFlag );
entity SetModel( _GetGibbedTorsoModel( localClientNum, entity ) );
entity Attach( _GetGibbedLegModel( localClientNum, entity ), "" );
entity _GibEntity( localClientNum, gibFlag, true );
}
function private _GibClientExtraInternal( localClientNum, entity, gibFlag )
{
if ( !util::is_mature() || util::is_gib_restricted_build() )
{
return;
}
if ( !IsDefined( entity ) )
{
return;
}
if ( entity.type !== "scriptmover" )
{
// Clientside gibbing currently only supports script models.
return;
}
if ( IsGibbed( localClientNum, entity, gibFlag ) )
{
return;
}
gibModel = _GetGibExtraModel( localClientNum, entity, gibFlag );
if ( IsDefined( gibModel ) && entity IsAttached( gibModel, "" ) )
{
entity Detach( gibModel, "" );
}
if ( gibFlag == 8 )
{
if ( IsDefined( (isdefined(entity.gib_data)?entity.gib_data.torsoDmg5:entity.torsoDmg5) ) )
{
entity Attach( (isdefined(entity.gib_data)?entity.gib_data.torsoDmg5:entity.torsoDmg5), "" );
}
}
_SetGibbed( localClientNum, entity, gibFlag );
entity _GibEntity( localClientNum, gibFlag, true );
}
function private _GibHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( entity IsPlayer() || entity IsPlayerCorpse() )
{
if ( !util::is_mature() || util::is_gib_restricted_build() )
{
return;
}
}
else
{
if ( IsDefined( entity.maturegib ) && entity.maturegib && !util::is_mature() )
{
return;
}
if ( IsDefined( entity.restrictedgib ) && entity.restrictedgib && !IsShowGibsEnabled() )
{
return;
}
}
gibFlags = (oldValue ^ newValue);
shouldSpawnGibs = (!(newValue & 1));
// Don't use the old clientfield value for new entities.
if ( bNewEnt )
{
gibFlags = (0 ^ newValue);
}
entity _GibEntity( localClientNum, gibFlags, shouldSpawnGibs );
entity.gib_state = newValue;
}
function _GibPiece( localClientNum, entity, gibModel, gibTag, gibFx, gibDir )
{
if ( !IsDefined( gibTag ) || !IsDefined( gibModel ) )
{
return;
}
startPosition = entity GetTagOrigin( gibTag );
startAngles = entity GetTagAngles( gibTag );
endPosition = startPosition;
endAngles = startAngles;
forwardVector = undefined;
if ( !IsDefined( startPosition ) || !IsDefined( startAngles ) )
{
return false;
}
if ( IsDefined( gibDir ) )
{
startPosition = (0,0,0);
forwardVector = gibDir;
// TODO: define the scale on the server from the damage
forwardVector *= RandomFloatRange( 100.0, 500.0 );
}
else
{
// Let a frame pass to approximate the linear and angular velocity of the tag.
{wait(.016);};
if ( IsDefined( entity ) )
{
endPosition = entity GetTagOrigin( gibTag );
endAngles = entity GetTagAngles( gibTag );
}
else
{
// Entity already removed.
endPosition = startPosition + ( AnglesToForward( startAngles ) * 10 );
endAngles = startAngles;
}
if ( !IsDefined( endPosition ) || !IsDefined( endAngles ) )
{
return false;
}
forwardVector = VectorNormalize( endPosition - startPosition );
forwardVector *= RandomFloatRange( 0.6, 1.0 );
forwardVector += ( RandomFloatRange( 0, 0.2 ), RandomFloatRange( 0, 0.2 ), RandomFloatRange( 0.2, 0.7 ) );
}
if( IsDefined( entity ) )
{
gibEntity = CreateDynEntAndLaunch( localClientNum, gibModel, endPosition, endAngles, startPosition, forwardVector, gibFx, true );
if ( IsDefined( gibEntity ) )
{
SetDynEntBodyRenderOptionsPacked( gibEntity, entity GetBodyRenderOptionsPacked() );
}
}
}
function private _HandleGibCallbacks( localClientNum, entity, gibFlag )
{
if ( IsDefined( entity._gibCallbacks ) &&
IsDefined( entity._gibCallbacks[gibFlag] ) )
{
foreach ( callback in entity._gibCallbacks[gibFlag] )
{
[[callback]]( localClientNum, entity, gibFlag );
}
}
}
function private _HandleGibAnnihilate( localClientNum )
{
entity = self;
entity endon( "entityshutdown" );
entity waittillmatch( "_anim_notify_", "gib_annihilate" );
GibClientUtils::ClientEntGibAnnihilate( localClientNum, entity );
}
function private _HandleGibHead( localClientNum )
{
entity = self;
entity endon( "entityshutdown" );
entity waittillmatch( "_anim_notify_", "gib = \"head\"" );
GibClientUtils::ClientEntGibHead( localClientNum, entity );
}
function private _HandleGibRightArm( localClientNum )
{
entity = self;
entity endon( "entityshutdown" );
entity waittillmatch( "_anim_notify_", "gib = \"arm_right\"" );
GibClientUtils::ClientEntGibRightArm( localClientNum, entity );
}
function private _HandleGibLeftArm( localClientNum )
{
entity = self;
entity endon( "entityshutdown" );
entity waittillmatch( "_anim_notify_", "gib = \"arm_left\"" );
GibClientUtils::ClientEntGibLeftArm( localClientNum, entity );
}
function private _HandleGibRightLeg( localClientNum )
{
entity = self;
entity endon( "entityshutdown" );
entity waittillmatch( "_anim_notify_", "gib = \"leg_right\"" );
GibClientUtils::ClientEntGibRightLeg( localClientNum, entity );
}
function private _HandleGibLeftLeg( localClientNum )
{
entity = self;
entity endon( "entityshutdown" );
entity waittillmatch( "_anim_notify_", "gib = \"leg_left\"" );
GibClientUtils::ClientEntGibLeftLeg( localClientNum, entity );
}
function private _HasGibDef( entity )
{
return ( IsDefined( entity.gib_data ) && IsDefined( entity.gib_data.gibdef ) ) ||
IsDefined( entity.gibdef ) ||
( ( entity GetPlayerGibDef() ) != "unknown" );
}
function _PlayGibFX( localClientNum, entity, fxFileName, fxTag )
{
if ( IsDefined( fxFileName ) && IsDefined( fxTag ) && entity hasDobj(localClientNum) )
{
fx = PlayFxOnTag( localClientNum, fxFileName, entity, fxTag );
if(isDefined(fx))
{
if(isDefined(entity.team))
{
SetFxTeam( localClientNum, fx, entity.team );
}
if( ( isdefined( level.SetGibFXToIgnorePause ) && level.SetGibFXToIgnorePause ) )
{
SetFXIgnorePause( localClientNum, fx, true );
}
}
return fx;
}
}
function _PlayGibSound( localClientNum, entity, soundAlias )
{
if ( IsDefined( soundAlias ) )
{
PlaySound( localClientNum, soundAlias, entity.origin );
}
}
/@
"Name: AddGibCallback( localClientNum, entity, gibFlag, callbackFunction )"
"Summary: Register a function callback that is called when the corresponding piece is gibbed."
"MandatoryArg: <num> : Client number."
"MandatoryArg: <entity> : Entity to add callbacks to."
"MandatoryArg: <num> : Gib piece to register for."
"MandatoryArg: <function> : Function to call, function is passed the localClientNum, entity, and gibFlag."
"Module: Gib"
@/
function AddGibCallback( localClientNum, entity, gibFlag, callbackFunction )
{
assert( IsFunctionPtr( callbackFunction ) );
if ( !IsDefined( entity._gibCallbacks ) )
{
entity._gibCallbacks = [];
}
if ( !IsDefined( entity._gibCallbacks[gibFlag] ) )
{
entity._gibCallbacks[gibFlag] = [];
}
gibCallbacks = entity._gibCallbacks[gibFlag];
gibCallbacks[gibCallbacks.size] = callbackFunction;
entity._gibCallbacks[gibFlag] = gibCallbacks;
}
function ClientEntGibAnnihilate( localClientNum, entity )
{
if ( !util::is_mature() || util::is_gib_restricted_build() )
{
return;
}
// Toggle hiding the ragdoll.
entity.ignoreRagdoll = true;
entity _GibEntity(
localClientNum,
2 |
16 |
32 |
(128+256),
true );
}
function ClientEntGibHead( localClientNum, entity )
{
_GibClientExtraInternal( localClientNum, entity, 4 );
_GibClientExtraInternal( localClientNum, entity, 8 );
}
function ClientEntGibLeftArm( localClientNum, entity )
{
if ( IsGibbed( localClientNum, entity, 16 ) )
{
return;
}
_GibClientEntityInternal( localClientNum, entity, 32 );
}
function ClientEntGibRightArm( localClientNum, entity )
{
if ( IsGibbed( localClientNum, entity, 32 ) )
{
return;
}
_GibClientEntityInternal( localClientNum, entity, 16 );
}
function ClientEntGibLeftLeg( localClientNum, entity )
{
_GibClientEntityInternal( localClientNum, entity, 256 );
}
function ClientEntGibRightLeg( localClientNum, entity )
{
_GibClientEntityInternal( localClientNum, entity, 128 );
}
function CreateScriptModelOfEntity( localClientNum, entity )
{
clone = Spawn( localClientNum, entity.origin, "script_model" );
clone.angles = entity.angles;
_CloneGibData( localClientNum, entity, clone );
gibState = _GetGibbedState( localClientNum, clone );
if ( !util::is_mature() || util::is_gib_restricted_build() )
{
// Don't display gibbed entities for non-mature clients.
gibState = 0;
}
if ( !(_GetGibbedState( localClientNum, entity ) < 16) )
{
// Attach torso or full body model.
clone SetModel( _GetGibbedTorsoModel( localClientNum, entity ) );
// Only attach separate legs if the body is damage already.
clone Attach( _GetGibbedLegModel( localClientNum, entity ), "" );
}
else
{
// Attach full body model.
clone SetModel( entity.model );
}
if ( (gibState & 8) )
{
// Attach head stump.
if ( IsDefined( (isdefined(clone.gib_data)?clone.gib_data.torsoDmg5:clone.torsoDmg5) ) )
{
clone Attach( (isdefined(clone.gib_data)?clone.gib_data.torsoDmg5:clone.torsoDmg5), "" );
}
}
else
{
// Attach head.
if ( IsDefined( (isdefined(clone.gib_data)?clone.gib_data.head:clone.head) ) )
{
clone Attach( (isdefined(clone.gib_data)?clone.gib_data.head:clone.head), "" );
}
if ( !(gibState & 4) && IsDefined( (isdefined(clone.gib_data)?clone.gib_data.hatmodel:clone.hatmodel) ) )
{
clone Attach( (isdefined(clone.gib_data)?clone.gib_data.hatmodel:clone.hatmodel), "" );
}
}
return clone;
}
function IsGibbed( localClientNum, entity, gibFlag )
{
return (_GetGibbedState( localClientNum, entity ) & gibFlag);
}
function IsUndamaged( localClientNum, entity )
{
return _GetGibbedState( localClientNum, entity ) == 0;
}
function GibEntity( localClientNum, gibFlags )
{
self _GibEntity( localClientNum, gibFlags, true );
self.gib_state = (_GetGibbedState( localClientNum, self ) | ( gibFlags & ( ( 1 << 9 ) - 1 ) ));
}
function HandleGibNotetracks( localClientNum )
{
entity = self;
entity thread _HandleGibAnnihilate( localClientNum );
entity thread _HandleGibHead( localClientNum );
entity thread _HandleGibRightArm( localClientNum );
entity thread _HandleGibLeftArm( localClientNum );
entity thread _HandleGibRightLeg( localClientNum );
entity thread _HandleGibLeftLeg( localClientNum );
}
function PlayerGibLeftArm( localClientNum )
{
self GibEntity( localClientNum, 32 );
}
function PlayerGibRightArm( localClientNum )
{
self GibEntity( localClientNum, 16 );
}
function PlayerGibLeftLeg( localClientNum )
{
self GibEntity( localClientNum, 256 );
}
function PlayerGibRightLeg( localClientNum )
{
self GibEntity( localClientNum, 128 );
}
function PlayerGibLegs( localClientNum )
{
self GibEntity( localClientNum, 128 );
self GibEntity( localClientNum, 256 );
}
function PlayerGibTag( localClientNum, gibFlag )
{
return _GibPieceTag( localClientNum, self, gibFlag );
}
+663
View File
@@ -0,0 +1,663 @@
#using scripts\codescripts\struct;
#using scripts\shared\clientfield_shared;
#using scripts\shared\throttle_shared;
#using scripts\shared\ai\systems\destructible_character;
#using scripts\shared\ai\systems\shared;
Line truncated
function private fields_equal( field_a, field_b)
{
if ( !isDefined(field_a) && !isDefined(field_b))
return true;
if ( isDefined(field_a) && isDefined(field_b) && field_a == field_b )
return true;
return false;
}
function private _IsDefaultPlayerGib( gibPieceFlag, gibStruct )
{
if ( !fields_equal( level.playerGibBundle.gibs[gibPieceFlag].gibdynentfx , gibStruct.gibdynentfx ) )
{
return false;
}
if ( !fields_equal( level.playerGibBundle.gibs[gibPieceFlag].gibfxtag, gibStruct.gibfxtag ) )
{
return false;
}
if ( !fields_equal( level.playerGibBundle.gibs[gibPieceFlag].gibfx, gibStruct.gibfx ) )
{
return false;
}
if ( !fields_equal( level.playerGibBundle.gibs[gibPieceFlag].gibtag, gibStruct.gibtag ) )
{
return false;
}
return true;
}
function autoexec main()
{
clientfield::register(
"actor",
"gib_state",
1,
(9),
"int" );
clientfield::register(
"playercorpse",
"gib_state",
1,
(9+3+3),
"int" );
gibDefinitions = struct::get_script_bundles("gibcharacterdef");
gibPieceLookup = [];
gibPieceLookup[2] = "annihilate";
gibPieceLookup[8] = "head";
gibPieceLookup[16] = "rightarm";
gibPieceLookup[32] = "leftarm";
gibPieceLookup[128] = "rightleg";
gibPieceLookup[256] = "leftleg";
processedBundles = [];
if ( SessionModeIsMultiplayerGame() )
{
level.playerGibBundle = SpawnStruct();
level.playerGibBundle.gibs = [];
level.playerGibBundle.name = "default_player";
level.playerGibBundle.gibs[2] = SpawnStruct();
level.playerGibBundle.gibs[8] = SpawnStruct();
level.playerGibBundle.gibs[32] = SpawnStruct();
level.playerGibBundle.gibs[256] = SpawnStruct();
level.playerGibBundle.gibs[16] = SpawnStruct();
level.playerGibBundle.gibs[128] = SpawnStruct();
level.playerGibBundle.gibs[2].gibfxtag = "j_spinelower";
level.playerGibBundle.gibs[2].gibfx = "blood/fx_blood_impact_exp_body_lg";
level.playerGibBundle.gibs[32].gibmodel = "c_t7_mp_battery_mpc_body1_s_larm";
level.playerGibBundle.gibs[32].gibdynentfx = "blood/fx_blood_gib_limb_trail_emitter";
level.playerGibBundle.gibs[32].gibfxtag = "j_elbow_le";
level.playerGibBundle.gibs[32].gibfx = "blood/fx_blood_gib_arm_sever_burst";
level.playerGibBundle.gibs[32].gibtag = "j_elbow_le";
level.playerGibBundle.gibs[256].gibmodel = "c_t7_mp_battery_mpc_body1_s_lleg";
level.playerGibBundle.gibs[256].gibdynentfx = "blood/fx_blood_gib_limb_trail_emitter";
level.playerGibBundle.gibs[256].gibfxtag = "j_knee_le";
level.playerGibBundle.gibs[256].gibfx = "blood/fx_blood_gib_leg_sever_burst";
level.playerGibBundle.gibs[256].gibtag = "j_knee_le";
level.playerGibBundle.gibs[16].gibmodel = "c_t7_mp_battery_mpc_body1_s_rarm";
level.playerGibBundle.gibs[16].gibdynentfx = "blood/fx_blood_gib_limb_trail_emitter";
level.playerGibBundle.gibs[16].gibfxtag = "j_elbow_ri";
level.playerGibBundle.gibs[16].gibfx = "blood/fx_blood_gib_arm_sever_burst_rt";
level.playerGibBundle.gibs[16].gibtag = "j_elbow_ri";
level.playerGibBundle.gibs[128].gibmodel = "c_t7_mp_battery_mpc_body1_s_rleg";
level.playerGibBundle.gibs[128].gibdynentfx = "blood/fx_blood_gib_limb_trail_emitter";
level.playerGibBundle.gibs[128].gibfxtag = "j_knee_ri";
level.playerGibBundle.gibs[128].gibfx = "blood/fx_blood_gib_leg_sever_burst_rt";
level.playerGibBundle.gibs[128].gibtag = "j_knee_ri";
}
// Process each gib bundle to allow quick access to information in the future.
foreach ( definitionName, definition in gibDefinitions )
{
gibBundle = SpawnStruct();
gibBundle.gibs = [];
gibBundle.name = definitionName;
default_player = false;
foreach ( gibPieceFlag, gibPieceName in gibPieceLookup )
{
gibStruct = SpawnStruct();
gibStruct.gibmodel = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibmodel" );
gibStruct.gibtag = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibtag" );
gibStruct.gibfx = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibfx" );
gibStruct.gibfxtag = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibeffecttag" );
gibStruct.gibdynentfx = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibdynentfx" );
gibStruct.gibsound = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibsound" );
gibStruct.gibhidetag = GetStructField( definition, gibPieceLookup[ gibPieceFlag ] + "_gibhidetag" );
if ( SessionModeIsMultiplayerGame() && _IsDefaultPlayerGib( gibPieceFlag, gibStruct ) )
{
default_player = true;
}
gibBundle.gibs[ gibPieceFlag ] = gibStruct;
}
if ( SessionModeIsMultiplayerGame() && default_player )
{
processedBundles[ definitionName ] = level.playerGibBundle;
}
else
{
processedBundles[ definitionName ] = gibBundle;
}
}
// Replaces all gib character define bundles with their processed form to free unncessary script variables.
level.scriptbundles[ "gibcharacterdef" ] = processedBundles;
if ( !IsDefined( level.gib_throttle ) )
{
level.gib_throttle = new Throttle();
// Two gibs every 0.2 seconds.
[[ level.gib_throttle ]]->Initialize( 2, 0.2 );
}
}
#namespace GibServerUtils;
function private _Annihilate( entity )
{
if ( IsDefined( entity ) )
{
entity NotSolid();
}
}
function private _GetGibExtraModel( entity, gibFlag )
{
if ( gibFlag == 4 )
return (isdefined(entity.gib_data)?entity.gib_data.hatmodel:entity.hatmodel);
else if ( gibFlag == 8 )
return (isdefined(entity.gib_data)?entity.gib_data.head:entity.head);
else
AssertMsg( "Unable to find gib model." );
}
// Used to solely gib equipment and the actor's head.
// Does not change the torso or leg models.
function private _GibExtra( entity, gibFlag )
{
if ( IsGibbed( entity, gibFlag ) )
{
return false;
}
if ( !_HasGibDef( entity ) )
{
return false;
}
entity thread _GibExtraInternal( entity, gibFlag );
return true;
}
function private _GibExtraInternal( entity, gibFlag )
{
// Allow simulatenous gibs to happen on the same frame, since the network
// cost is negligible if the gib is for the same entity.
if ( entity.gib_time !== GetTime() )
{
[[ level.gib_throttle ]]->WaitInQueue( entity );
}
if ( !IsDefined( entity ) )
{
return;
}
entity.gib_time = GetTime();
if ( IsGibbed( entity, gibFlag ) )
{
return false;
}
if ( gibFlag == 8 )
{
if ( IsDefined( (isdefined(entity.gib_data)?entity.gib_data.torsoDmg5:entity.torsoDmg5) ) )
{
entity Attach( (isdefined(entity.gib_data)?entity.gib_data.torsoDmg5:entity.torsoDmg5), "", true );
}
}
_SetGibbed( entity, gibFlag, undefined );
DestructServerUtils::ShowDestructedPieces( entity );
ShowHiddenGibPieces( entity );
gibModel = _GetGibExtraModel( entity, gibFlag );
if ( IsDefined( gibModel ) )
{
entity Detach( gibModel, "" );
}
DestructServerUtils::ReapplyDestructedPieces( entity );
ReapplyHiddenGibPieces( entity );
}
// Used to gib torso or leg pieces, not including equipment or the actor's head.
// Changes the torso and leg models.
function private _GibEntity( entity, gibFlag )
{
if ( IsGibbed( entity, gibFlag ) || !_HasGibPieces( entity, gibFlag ) )
{
return false;
}
if ( !_HasGibDef( entity ) )
{
return false;
}
entity thread _GibEntityInternal( entity, gibFlag );
return true;
}
function private _GibEntityInternal( entity, gibFlag )
{
// Allow simulatenous gibs to happen on the same frame, since the network
// cost is negligible if the gib is for the same entity.
if ( entity.gib_time !== GetTime() )
{
[[ level.gib_throttle ]]->WaitInQueue( entity );
}
if ( !IsDefined( entity ) )
{
return;
}
entity.gib_time = GetTime();
if ( IsGibbed( entity, gibFlag ) )
{
return;
}
DestructServerUtils::ShowDestructedPieces( entity );
ShowHiddenGibPieces( entity );
if ( !(_GetGibbedState( entity ) < 16) )
{
legModel = _GetGibbedLegModel( entity );
entity Detach( legModel );
}
_SetGibbed( entity, gibFlag, undefined );
entity SetModel( _GetGibbedTorsoModel( entity ) );
entity Attach( _GetGibbedLegModel( entity ) );
DestructServerUtils::ReapplyDestructedPieces( entity );
ReapplyHiddenGibPieces( entity );
}
function private _GetGibbedLegModel( entity )
{
gibState = _GetGibbedState( entity );
rightLegGibbed = (gibState & 128);
leftLegGibbed = (gibState & 256);
if ( rightLegGibbed && leftLegGibbed)
{
return (isdefined(entity.gib_data)?entity.gib_data.legDmg4:entity.legDmg4);
}
else if ( rightLegGibbed )
{
return (isdefined(entity.gib_data)?entity.gib_data.legDmg2:entity.legDmg2);
}
else if ( leftLegGibbed )
{
return (isdefined(entity.gib_data)?entity.gib_data.legDmg3:entity.legDmg3);
}
return (isdefined(entity.gib_data)?entity.gib_data.legDmg1:entity.legDmg1);
}
function private _GetGibbedState( entity )
{
if ( IsDefined( entity.gib_state ) )
{
return entity.gib_state;
}
return 0;
}
function private _GetGibbedTorsoModel( entity )
{
gibState = _GetGibbedState( entity );
rightArmGibbed = (gibState & 16);
leftArmGibbed = (gibState & 32);
if ( rightArmGibbed && leftArmGibbed )
{
return (isdefined(entity.gib_data)?entity.gib_data.torsoDmg2:entity.torsoDmg2);
// TODO(David Young 5-14-14): Currently AI's don't support both arms getting blown off.
// return GIB_TORSO_NO_ARMS_MODEL( entity );
}
else if ( rightArmGibbed )
{
return (isdefined(entity.gib_data)?entity.gib_data.torsoDmg2:entity.torsoDmg2);
}
else if ( leftArmGibbed )
{
return (isdefined(entity.gib_data)?entity.gib_data.torsoDmg3:entity.torsoDmg3);
}
return (isdefined(entity.gib_data)?entity.gib_data.torsoDmg1:entity.torsoDmg1);
}
function private _HasGibDef( entity )
{
return IsDefined( entity.gibdef );
}
function private _HasGibPieces( entity, gibFlag )
{
hasGibPieces = false;
gibState = _GetGibbedState( entity );
entity.gib_state = (gibState | ( gibFlag & ( ( 1 << 9 ) - 1 ) ));
if ( IsDefined( _GetGibbedTorsoModel( entity ) ) &&
IsDefined( _GetGibbedLegModel( entity ) ) )
{
hasGibPieces = true;
}
entity.gib_state = gibState;
return hasGibPieces;
}
function private _SetGibbed( entity, gibFlag, gibDir )
{
if ( IsDefined( gibDir ) )
{
angles = VectortoAngles( gibDir );
yaw = angles[1];
yaw_bits = getbitsforangle( yaw, 3 );
entity.gib_state = (( _GetGibbedState( entity )|gibFlag & ( ( 1 << 9 ) - 1 ) ) + ( yaw_bits << 9 ) );
}
else
{
entity.gib_state = (_GetGibbedState( entity ) | ( gibFlag & ( ( 1 << 9 ) - 1 ) ));
}
entity.gibbed = true;
entity clientfield::set( "gib_state", entity.gib_state );
}
function Annihilate( entity )
{
if ( !_HasGibDef( entity ) )
{
return false;
}
gibBundle = struct::get_script_bundle("gibcharacterdef",entity.gibdef);
if ( !IsDefined( gibBundle ) || !IsDefined( gibBundle.gibs ) )
{
return false;
}
gibPieceStruct = gibBundle.gibs[ 2 ];
// Make sure there is some sort of FX to play if we're annihilating the AI.
if ( IsDefined( gibPieceStruct ) )
{
if ( IsDefined( gibPieceStruct.gibfx ) )
{
_SetGibbed( entity, 2, undefined );
entity thread _Annihilate( entity );
return true;
}
}
return false;
}
function CopyGibState( originalEntity, newEntity )
{
newEntity.gib_state = _GetGibbedState( originalEntity );
ToggleSpawnGibs( newEntity, false );
ReapplyHiddenGibPieces( newEntity );
}
function IsGibbed( entity, gibFlag )
{
return (_GetGibbedState( entity ) & gibFlag);
}
function GibHat( entity )
{
return _GibExtra( entity, 4 );
}
function GibHead( entity )
{
GibHat( entity );
return _GibExtra( entity, 8 );
}
function GibLeftArm( entity )
{
// TODO(David Young 5-14-14): Currently AI's don't support both arms getting blown off.
if ( IsGibbed( entity, 16 ) )
{
return false;
}
if ( _GibEntity( entity, 32 ) )
{
DestructServerUtils::DestructLeftArmPieces( entity );
return true;
}
return false;
}
function GibRightArm( entity )
{
// TODO(David Young 5-14-14): Currently AI's don't support both arms getting blown off.
if ( IsGibbed( entity, 32 ) )
{
return false;
}
if ( _GibEntity( entity, 16 ) )
{
DestructServerUtils::DestructRightArmPieces( entity );
entity thread shared::DropAIWeapon();
return true;
}
return false;
}
function GibLeftLeg( entity )
{
if ( _GibEntity( entity, 256 ) )
{
DestructServerUtils::DestructLeftLegPieces( entity );
return true;
}
return false;
}
function GibRightLeg( entity )
{
if ( _GibEntity( entity, 128 ) )
{
DestructServerUtils::DestructRightLegPieces( entity );
return true;
}
return false;
}
function GibLegs( entity )
{
if ( _GibEntity( entity, (128+256) ) )
{
DestructServerUtils::DestructRightLegPieces( entity );
DestructServerUtils::DestructLeftLegPieces( entity );
return true;
}
return false;
}
function PlayerGibLeftArm( entity )
{
if ( IsDefined( entity.body ) )
{
dir = (1,0,0);
_SetGibbed( entity.body, 32, dir );
}
}
function PlayerGibRightArm( entity )
{
if ( IsDefined( entity.body ) )
{
dir = (1,0,0);
_SetGibbed( entity.body, 16, dir );
}
}
function PlayerGibLeftLeg( entity )
{
if ( IsDefined( entity.body ) )
{
dir = (1,0,0);
_SetGibbed( entity.body, 256, dir );
}
}
function PlayerGibRightLeg( entity )
{
if ( IsDefined( entity.body ) )
{
dir = (1,0,0);
_SetGibbed( entity.body, 128, dir );
}
}
function PlayerGibLegs( entity )
{
if ( IsDefined( entity.body ) )
{
dir = (1,0,0);
_SetGibbed( entity.body, 128, dir );
_SetGibbed( entity.body, 256, dir );
}
}
function PlayerGibLeftArmVel( entity, dir )
{
if ( IsDefined( entity.body ) )
{
_SetGibbed( entity.body, 32, dir );
}
}
function PlayerGibRightArmVel( entity, dir )
{
if ( IsDefined( entity.body ) )
{
_SetGibbed( entity.body, 16, dir );
}
}
function PlayerGibLeftLegVel( entity, dir )
{
if ( IsDefined( entity.body ) )
{
_SetGibbed( entity.body, 256, dir );
}
}
function PlayerGibRightLegVel( entity, dir )
{
if ( IsDefined( entity.body ) )
{
_SetGibbed( entity.body, 128, dir );
}
}
function PlayerGibLegsVel( entity, dir )
{
if ( IsDefined( entity.body ) )
{
_SetGibbed( entity.body, 128, dir );
_SetGibbed( entity.body, 256, dir );
}
}
function ReapplyHiddenGibPieces( entity )
{
if ( !_HasGibDef( entity ) )
{
return;
}
gibBundle = struct::get_script_bundle("gibcharacterdef",entity.gibdef);
foreach ( gibFlag, gib in gibBundle.gibs )
{
if ( !IsGibbed( entity, gibFlag ) )
{
continue;
}
if ( IsDefined( gib.gibhidetag ) && IsAlive( entity ) && entity HasPart( gib.gibhidetag ) )
{
if ( !( isdefined( entity.skipDeath ) && entity.skipDeath ) )
{
// Gighidetag's are only used for hiding hitlocations. If the entity is already dead or skipping death animations don't apply them.
entity HidePart( gib.gibhidetag, "", true );
}
}
}
}
function ShowHiddenGibPieces( entity )
{
if ( !_HasGibDef( entity ) )
{
return;
}
gibBundle = struct::get_script_bundle("gibcharacterdef",entity.gibdef);
foreach ( gibFlag, gib in gibBundle.gibs )
{
if ( IsDefined( gib.gibhidetag ) && entity HasPart( gib.gibhidetag ) )
{
entity ShowPart( gib.gibhidetag, "", true );
}
}
}
function ToggleSpawnGibs( entity, shouldSpawnGibs )
{
if ( !shouldSpawnGibs )
{
entity.gib_state = _GetGibbedState( entity ) | 1;
}
else
{
entity.gib_state = _GetGibbedState( entity ) & ~1;
}
entity clientfield::set( "gib_state", entity.gib_state );
}
+57
View File
@@ -0,0 +1,57 @@
// Server side only
// Server side only
// Server side only
// 7 Damage states and 1 toggle bit to determine if gib models should be spawned on the client.
// Less than GIB_TORSO_RIGHT_ARM_FLAG
// Client/Server scriptbundle
+414
View File
@@ -0,0 +1,414 @@
#using scripts\shared\gameskill_shared;
#using scripts\shared\name_shared;
#using scripts\shared\util_shared;
#using scripts\shared\ai\systems\debug;
#using scripts\shared\ai\systems\shared;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\systems\weaponList;
Line truncated
#using_animtree("generic");
//--------------------------------------------------------------------------------
// Weapon Initialization
//--------------------------------------------------------------------------------
function initWeapon( weapon )
{
self.weaponInfo[weapon.name] = SpawnStruct();
self.weaponInfo[weapon.name].position = "none";
self.weaponInfo[weapon.name].hasClip = true;
if ( IsDefined( weapon.ClipModel ) )
self.weaponInfo[weapon.name].useClip = true;
else
self.weaponInfo[weapon.name].useClip = false;
}
//--------------------------------------------------------------------------------
// Main
//--------------------------------------------------------------------------------
function main()
{
self.a = SpawnStruct();
self.a.weaponPos = [];
// Init weapons to "none"
// This is done here for now, but might be better to
// just have the converter set the empty weapon slots to "none" instead of ""
if ( self.weapon == level.weaponNone )
self AiUtility::setCurrentWeapon( level.weaponNone );
// primary weapon
self AiUtility::setPrimaryWeapon(self.weapon);
// secondary weapon
if ( self.secondaryweapon == level.weaponNone )
self AiUtility::setSecondaryWeapon( level.weaponNone );
self AiUtility::setSecondaryWeapon( self.secondaryweapon );
self AiUtility::setCurrentWeapon(self.primaryweapon);
self.initial_primaryweapon = self.primaryweapon;
self.initial_secondaryweapon = self.secondaryweapon;
self initWeapon( self.primaryweapon );
self initWeapon( self.secondaryweapon );
self initWeapon( self.sidearm );
self.weapon_positions = array( "left", "right", "chest", "back" );
for (i = 0; i < self.weapon_positions.size; i++)
{
self.a.weaponPos[self.weapon_positions[i]] = level.weaponNone;
}
self.lastWeapon = self.weapon;
self thread beginGrenadeTracking();
self thread globalGrenadeTracking();
firstInit();
// AI_TODO - Proper ammo tracking for rocketlauncher AI's
self.a.rockets = 3;
self.a.rocketVisible = true;
// Set initial states for poses
self.a.pose = "stand";
self.a.prevPose = self.a.pose;
self.a.movement = "stop";
self.a.special = "none";
self.a.gunHand = "none"; // Initialize so that PutGunInHand works properly.
shared::placeWeaponOn( self.primaryweapon, "right" );
if ( isdefined( self.secondaryweaponclass ) && self.secondaryweaponclass != "none" && self.secondaryweaponclass != "pistol" )
shared::placeWeaponOn( self.secondaryweapon, "back");
self.a.combatEndTime = GetTime();
self.a.nextGrenadeTryTime = 0;
// set up all the aiming stuff
self.a.isAiming = false;
self.rightAimLimit = 45;
self.leftAimLimit = -45;
self.upAimLimit = 45;
self.downAimLimit = -45;
// setup the speed variables
self.walk = false;
self.sprint = false;
self.a.postScriptFunc = undefined;
// use the GDT settings to start with
self.baseAccuracy = self.accuracy;
// set default accuracy mod
if( !isdefined(self.script_accuracy) )
self.script_accuracy = 1;
// scale baseAccuracy based on number of coop players
if( self.team == "axis" || self.team == "team3" )
self thread gameskill::axisAccuracyControl();
else if (self.team == "allies")
self thread gameskill::alliesAccuracyControl();
self.a.missTime = 0;
self.bulletsInClip = self.weapon.clipSize;
// state tracking
self.lastEnemySightTime = 0; // last time we saw our current enemy
self.combatTime = 0; // how long we've been in/out of combat
self.suppressed = false; // if we're currently suppressed
self.suppressedTime = 0; // how long we've been in/out of suppression
if ( self.team == "allies" )
self.suppressionThreshold = 0.75;
else
self.suppressionThreshold = 0.5;
// Random range makes the grenades less accurate and do less damage, but also makes it difficult to throw back.
if ( self.team == "allies" )
self.randomGrenadeRange = 0;
else
self.randomGrenadeRange = 128;
self.reacquire_state = 0;
}
function setNameAndRank()
{
self endon ( "death" );
self name::get();
}
function DoNothing()
{
}
function set_anim_playback_rate()
{
self.animplaybackrate = 0.9 + RandomFloat( 0.2 );
self.moveplaybackrate = 1;
}
function trackVelocity()
{
self endon ("death");
for (;;)
{
self.oldOrigin = self.origin;
wait (0.2);
}
}
/#
function checkApproachAngles( transTypes )
{
idealTransAngles[1] = 45;
idealTransAngles[2] = 0;
idealTransAngles[3] = -45;
idealTransAngles[4] = 90;
idealTransAngles[6] = -90;
idealTransAngles[7] = 135;
idealTransAngles[8] = 180;
idealTransAngles[9] = -135;
wait .05;
for ( i = 1; i <= 9; i++ )
{
for ( j = 0; j < transTypes.size; j++ )
{
trans = transTypes[j];
idealAdd = 0;
if ( trans == "left" || trans == "left_crouch" )
{
idealAdd = 90;
}
else if ( trans == "right" || trans == "right_crouch" )
{
idealAdd = -90;
}
if ( isdefined( anim.coverTransAngles[ trans ][i] ) )
{
correctAngle = AngleClamp180( idealTransAngles[i] + idealAdd );
actualAngle = AngleClamp180( anim.coverTransAngles[ trans ][i] );
if ( AbsAngleClamp180( actualAngle - correctAngle ) > 7 )
{
println( "^1Cover approach animation has bad yaw delta: anim.coverTrans[\"" + trans + "\"][" + i + "]; is ^2" + actualAngle + "^1, should be closer to ^2" + correctAngle + "^1." );
}
}
}
}
for ( i = 1; i <= 9; i++ )
{
for ( j = 0; j < transTypes.size; j++ )
{
trans = transTypes[j];
idealAdd = 0;
if ( trans == "left" || trans == "left_crouch" )
{
idealAdd = 90;
}
else if ( trans == "right" || trans == "right_crouch" )
{
idealAdd = -90;
}
if ( isdefined( anim.coverExitAngles[ trans ][i] ) )
{
correctAngle = AngleClamp180( -1 * (idealTransAngles[i] + idealAdd + 180) );
actualAngle = AngleClamp180( anim.coverExitAngles[ trans ][i] );
if ( AbsAngleClamp180( actualAngle - correctAngle ) > 7 )
{
println( "^1Cover exit animation has bad yaw delta: anim.coverTrans[\"" + trans + "\"][" + i + "]; is ^2" + actualAngle + "^1, should be closer to ^2" + correctAngle + "^1." );
}
}
}
}
}
#/
function getExitSplitTime( approachType, dir )
{
return anim.coverExitSplit[ approachType ][ dir ];
}
function getTransSplitTime( approachType, dir )
{
return anim.coverTransSplit[ approachType ][ dir ];
}
function firstInit()
{
// Initialization that should happen once per level
if ( IsDefined(anim.NotFirstTime) ) // Use this to trigger the first init
{
return;
}
anim.NotFirstTime = true;
anim.grenadeTimers["player_frag_grenade_sp"] = randomIntRange( 1000, 20000 );
anim.grenadeTimers["player_flash_grenade_sp"] = randomIntRange( 1000, 20000 );
anim.grenadeTimers["player_double_grenade"] = randomIntRange( 10000, 60000 );
anim.grenadeTimers["AI_frag_grenade_sp"] = randomIntRange( 0, 20000 );
anim.grenadeTimers["AI_flash_grenade_sp"] = randomIntRange( 0, 20000 );
anim.numGrenadesInProgressTowardsPlayer = 0;
anim.lastGrenadeLandedNearPlayerTime = -1000000;
anim.lastFragGrenadeToPlayerStart = -1000000;
thread setNextPlayerGrenadeTime();
if ( !isdefined( level.flag ) )
{
level.flag = [];
}
level.painAI = undefined;
anim.coverCrouchLeanPitch = -55;
}
function onPlayerConnect()
{
player = self;
firstInit();
player.invul = false;
}
function setNextPlayerGrenadeTime()
{
waittillframeend;
// might not be defined if load::main() wasn't called
if ( isdefined( anim.playerGrenadeRangeTime ) )
{
maxTime = int( anim.playerGrenadeRangeTime * 0.7 );
if ( maxTime < 1 )
{
maxTime = 1;
}
anim.grenadeTimers["player_frag_grenade_sp"] = randomIntRange( 0, maxTime );
anim.grenadeTimers["player_flash_grenade_sp"] = randomIntRange( 0, maxTime );
}
if ( isdefined( anim.playerDoubleGrenadeTime ) )
{
maxTime = int( anim.playerDoubleGrenadeTime );
minTime = int( maxTime / 2 );
if ( maxTime <= minTime )
{
maxTime = minTime + 1;
}
anim.grenadeTimers["player_double_grenade"] = randomIntRange( minTime, maxTime );
}
}
function AddToMissiles(grenade)
{
if(!isdefined(level.missileEntities))level.missileEntities=[];
if ( !isdefined( level.missileEntities ) ) level.missileEntities = []; else if ( !IsArray( level.missileEntities ) ) level.missileEntities = array( level.missileEntities ); level.missileEntities[level.missileEntities.size]=grenade;;
while ( IsDefined(grenade) )
{
{wait(.05);};
}
ArrayRemoveValue(level.missileEntities,grenade);
}
function globalGrenadeTracking()
{
if(!isdefined(level.missileEntities))level.missileEntities=[];
self endon ( "death" );
self thread globalGrenadeLauncherTracking();
self thread globalMissileTracking();
for ( ;; )
{
self waittill ( "grenade_fire", grenade, weapon );
grenade.owner=self;
grenade.weapon = weapon;
level thread AddToMissiles(grenade);
}
}
function globalGrenadeLauncherTracking()
{
self endon ( "death" );
for ( ;; )
{
self waittill ( "grenade_launcher_fire", grenade, weapon );
grenade.owner=self;
grenade.weapon = weapon;
level thread AddToMissiles(grenade);
}
}
function globalMissileTracking()
{
self endon ( "death" );
for ( ;; )
{
self waittill ( "missile_fire", grenade, weapon );
grenade.owner=self;
grenade.weapon = weapon;
level thread AddToMissiles(grenade);
}
}
function beginGrenadeTracking()
{
self endon ( "death" );
for ( ;; )
{
self waittill ( "grenade_fire", grenade, weapon );
grenade thread grenade_earthQuake();
}
}
function endOnDeath()
{
self waittill( "death" );
waittillframeend;
self notify ( "end_explode" );
}
function grenade_earthQuake() //self == grenade
{
self thread endOnDeath();
self endon( "end_explode" );
self waittill( "explode", position );
PlayRumbleOnPosition( "grenade_rumble", position );
earthquake( 0.3, 0.5, position, 400 );
}
//--------------------------------------------------------------------------------
// end script
//--------------------------------------------------------------------------------
function end_script()
{
}
+42
View File
@@ -0,0 +1,42 @@
Line truncated
// Note that this script is called from the level script command animscripted, only for AI. If animscripted
// is done on a script model, this script is not called - startscriptedanim is called directly.
#using_animtree("generic");
function main()
{
self endon ("death");
self notify ("killanimscript");
self notify ("clearSuppressionAttack");
self.codeScripted["root"] = %body; // TEMP!
self endon ("end_sequence");
self.a.script = "scripted";
self waittill("killanimscript");
}
#using_animtree("generic");
function init(notifyName, origin, angles, theAnim, AnimMode, root, rate, goalTime, lerpTime)
{
}
//--------------------------------------------------------------------------------
// end script
//--------------------------------------------------------------------------------
function end_script()
{
// This callback will be called when AI will change states from AIS_SCRIPTED to AIS_BEHAVE
// i.e when it will go from animscripted state to behavior state
if( IsDefined( self.___ArchetypeOnBehaveCallback ) )
[[self.___ArchetypeOnBehaveCallback]]( self );
}
+532
View File
@@ -0,0 +1,532 @@
#using scripts\shared\ai_shared;
#using scripts\shared\math_shared;
#using scripts\shared\sound_shared;
#using scripts\shared\throttle_shared;
#using scripts\shared\util_shared;
#using scripts\shared\ai\systems\debug;
#using scripts\shared\ai\systems\init;
#using scripts\shared\ai\archetype_utility;
#using scripts\shared\ai\systems\weaponList;
Line truncated
#using_animtree("generic");
function autoexec main()
{
level.ai_weapon_throttle = new Throttle();
[[ level.ai_weapon_throttle ]]->Initialize( 1, 0.1 );
}
function private _throwStowedWeapon( entity, weapon, weaponModel )
{
entity waittill( "death" );
if(IsDefined(entity))
{
weaponModel Unlink();
entity shared::ThrowWeapon( weapon, getTagForPos( "back" ), false );
}
weaponModel Delete();
}
function StowWeapon( weapon, positionOffset, orientationOffset )
{
entity = self;
if ( !IsDefined( positionOffset ) )
{
positionOffset = (0, 0, 0);
}
if ( !IsDefined( orientationOffset ) )
{
orientationOffset = (0, 0, 0);
}
weaponModel = Spawn( "script_model", (0, 0, 0) );
weaponModel SetModel( weapon.worldmodel );
weaponModel LinkTo( entity, "tag_stowed_back", positionOffset, orientationOffset );
entity thread _throwStowedWeapon( entity, weapon, weaponModel );
}
function placeWeaponOn( weapon, position )
{
self notify("weapon_position_change");
if ( IsString( weapon ) )
{
weapon = GetWeapon( weapon );
}
if (!isdefined(self.weaponInfo[weapon.name]))
self init::initWeapon( weapon );
curPosition = self.weaponInfo[weapon.name].position;
// make sure we're not out of sync
assert( curPosition == "none" || self.a.weaponPos[curPosition] == weapon );
if ( !IsArray( self.a.weaponPos ) )
{
self.a.weaponPos = [];
}
assert( IsArray( self.a.weaponPos ) );
assert( position == "none" || IsDefined( self.a.weaponPos[position] ), "Weapon position \"" + position + "\"" );
assert( IsWeapon( weapon ) );
// weapon already in place
if ( position != "none" && self.a.weaponPos[position] == weapon )
return;
//println("detach all (" + weapon.name + ", " + position + ")");
self detachAllWeaponModels();
// detach if we're already in a position
if ( curPosition != "none" )
self detachWeapon( weapon );
// nothing more to do
if ( position == "none" )
{
//println("update(1) all (" + weapon.name + ", " + position + ")");
self updateAttachedWeaponModels();
self AiUtility::setCurrentWeapon( level.weaponNone );
return;
}
if ( self.a.weaponPos[position] != level.weaponNone )
self detachWeapon( self.a.weaponPos[position] );
// to ensure that the correct tags for the active weapon are used, we need to make sure it gets attached first
if ( position == "left" || position == "right" )
{
self updateScriptWeaponInfoAndPos( weapon, position );
self AiUtility::setCurrentWeapon(weapon);
}
else
{
self updateScriptWeaponInfoAndPos( weapon, position );
}
self updateAttachedWeaponModels();
// make sure we don't have a weapon in each hand
assert( self.a.weaponPos["left"] == level.weaponNone || self.a.weaponPos["right"] == level.weaponNone );
}
function detachWeapon( weapon )
{
self.a.weaponPos[self.weaponInfo[weapon.name].position] = level.weaponNone;
self.weaponInfo[weapon.name].position = "none";
}
function updateScriptWeaponInfoAndPos( weapon, position )
{
self.weaponInfo[weapon.name].position = position;
self.a.weaponPos[position] = weapon;
}
function detachAllWeaponModels()
{
if( isdefined(self.weapon_positions) )
{
for ( index = 0; index < self.weapon_positions.size; index++ )
{
weapon = self.a.weaponPos[self.weapon_positions[index]];
if ( weapon == level.weaponNone )
continue;
self SetActorWeapon( level.weaponNone, self GetActorWeaponOptions() );
}
}
}
function updateAttachedWeaponModels()
{
if( isdefined(self.weapon_positions) )
{
for ( index = 0; index < self.weapon_positions.size; index++ )
{
weapon = self.a.weaponPos[self.weapon_positions[index]];
if ( weapon == level.weaponNone )
continue;
// weapon should only be assigned (means self.weapon will be set) to the weapon if its set to go to "right"
if( self.weapon_positions[index] != "right" )
continue;
self SetActorWeapon( weapon, self GetActorWeaponOptions() );
if ( self.weaponInfo[weapon.name].useClip && !self.weaponInfo[weapon.name].hasClip )
self hidepart( "tag_clip" );
}
}
}
function getTagForPos( position )
{
switch ( position )
{
case "chest":
return "tag_weapon_chest";
case "back":
return "tag_stowed_back";
case "left":
return "tag_weapon_left";
case "right":
return "tag_weapon_right";
case "hand":
return "tag_inhand";
default:
assertMsg( "unknown weapon placement position: " + position );
break;
}
}
function ThrowWeapon( weapon, positionTag, scavenger )
{
waitTime = 0.1;
linearScalar = 2.0;
angularScalar = 10;
// Calculate the linear and angular velocity to launch the weapon.
startPosition = self GetTagOrigin( positionTag );
startAngles = self GetTagAngles( positionTag );
// Wait two server frames to calculate velocity.
wait( waitTime );
if ( IsDefined( self ) )
{
endPosition = self GetTagOrigin( positionTag );
endAngles = self GetTagAngles( positionTag );
linearVelocity = ( endPosition - startPosition ) * ( 1.0 / waitTime ) * linearScalar;
angularVelocity = VectorNormalize( endAngles - startAngles ) * angularScalar;
throwWeapon = self DropWeapon( weapon, positionTag, linearVelocity, angularVelocity, scavenger );
if ( IsDefined( throwWeapon ) )
{
throwWeapon SetContents( throwWeapon SetContents( 0 ) & ~( (1 << 15) | (1 << 26) | (1 << 23) | (1 << 25) ) );
}
return throwWeapon;
}
}
function DropAIWeapon()
{
self endon("death");
if( self.weapon == level.weaponNone )
{
return;
}
if (( isdefined( self.script_nodropsecondaryweapon ) && self.script_nodropsecondaryweapon ) && (self.weapon == self.initial_secondaryweapon))
{
/#PrintLn("Not dropping secondary weapon '" + self.weapon.name + "'");#/
return;
}
else if (( isdefined( self.script_nodropsidearm ) && self.script_nodropsidearm ) && (self.weapon == self.sidearm))
{
/#PrintLn("Not dropping sidearm '" + self.weapon.name + "'");#/
return;
}
[[ level.ai_weapon_throttle ]]->WaitInQueue( self );
current_weapon = self.weapon;
dropWeaponName = player_weapon_drop( current_weapon );
position = self.weaponInfo[ current_weapon.name ].position;
shouldDropWeapon = !IsDefined( self.dontDropWeapon ) || self.dontDropWeapon === false;
if( current_weapon.isScavengable == false )
{
shouldDropWeapon = false;
}
if ( shouldDropWeapon && self.dropWeapon )
{
self.dontDropWeapon = true;
positionTag = getTagForPos( position );
ThrowWeapon( dropWeaponName, positionTag, false );
}
if ( self.weapon != level.weaponNone )
{
shared::placeWeaponOn( current_weapon, "none" );
if( self.weapon == self.primaryweapon )
{
self AiUtility::setPrimaryWeapon( level.weaponNone );
}
else if( self.weapon == self.secondaryweapon )
{
self AiUtility::setSecondaryWeapon( level.weaponNone );
}
}
self AiUtility::setCurrentWeapon( level.weaponNone );
}
function DropAllAIWeapons()
{
if (( isdefined( self.a.dropping_weapons ) && self.a.dropping_weapons ))
{
// already called
return;
}
if( !self.dropweapon )
{
if( self.weapon != level.weaponNone )
{
shared::placeWeaponOn( self.weapon, "none" );
self AiUtility::setCurrentWeapon( level.weaponNone );
}
return;
}
self.a.dropping_weapons = true;
self detachAllWeaponModels();
droppedSideArm = false;
if( isdefined(self.weapon_positions) )
{
for ( index = 0; index < self.weapon_positions.size; index++ )
{
weapon = self.a.weaponPos[ self.weapon_positions[ index ] ];
if ( weapon != level.weaponNone )
{
self.weaponInfo[ weapon.name ].position = "none";
self.a.weaponPos[ self.weapon_positions[ index ] ] = level.weaponNone;
if (( isdefined( self.script_nodropsecondaryweapon ) && self.script_nodropsecondaryweapon ) && (weapon == self.initial_secondaryweapon))
{
/#PrintLn("Not dropping secondary weapon '" + weapon.name + "'");#/
}
else if (( isdefined( self.script_nodropsidearm ) && self.script_nodropsidearm ) && (weapon == self.sidearm))
{
/#PrintLn("Not dropping sidearm '" + weapon.name + "'");#/
}
else
{
velocity = self GetVelocity();
speed = Length( velocity ) * 0.5;
weapon = player_weapon_drop(weapon);
droppedWeapon = self DropWeapon( weapon, self.weapon_positions[ index ], speed );
if ( self.sideArm != level.weaponNone )
{
if ( weapon == self.sideArm )
droppedSideArm = true;
}
}
}
}
}
if( !droppedSideArm && self.sideArm != level.weaponNone )
{
// 10% chance of dropping sidearm
if( RandomInt(100) <= 10 )
{
velocity = self GetVelocity();
speed = Length( velocity ) * 0.5;
droppedWeapon = self DropWeapon( self.sideArm, "chest", speed );
}
}
self AiUtility::setCurrentWeapon( level.weaponNone );
self.a.dropping_weapons = undefined;
}
function player_weapon_drop( weapon )
{
if ( IsSubStr( weapon.name, "rpg" ) )
{
return GetWeapon( "rpg_player" );
}
return weapon;
}
function HandleNoteTrack( note, flagName, customFunction, var1 )
{
}
// DoNoteTracks waits for and responds to standard noteTracks on the animation, returning when it gets an "end" or a "finish"
// For level scripts, a pointer to a custom function should be passed as the second argument, which handles notetracks not
// already handled by the generic function. This call should take the form DoNoteTracks(flagName,&customFunction);
// The custom function will be called for each notetrack not recognized, and will pass the notetrack name. Note that this
// function could be called multiple times for a single animation.
function DoNoteTracks( flagName, customFunction, debugIdentifier, var1 ) // debugIdentifier isn't even used. we should get rid of it.
{
for (;;)
{
self waittill (flagName, note);
if ( !isdefined( note ) )
{
note = "undefined";
}
val = self HandleNoteTrack( note, flagName, customFunction, var1 );
if ( isdefined( val ) )
{
return val;
}
}
}
function DoNoteTracksIntercept( flagName, interceptFunction, debugIdentifier ) // debugIdentifier isn't even used. we should get rid of it.
{
assert( isdefined( interceptFunction ) );
for (;;)
{
self waittill ( flagName, note );
if ( !isdefined( note ) )
{
note = "undefined";
}
intercepted = [[interceptFunction]]( note );
if ( isdefined( intercepted ) && intercepted )
{
continue;
}
val = self HandleNoteTrack( note, flagName );
if ( isdefined( val ) )
{
return val;
}
}
}
function DoNoteTracksPostCallback( flagName, postFunction )
{
assert( isdefined( postFunction ) );
for (;;)
{
self waittill ( flagName, note );
if ( !isdefined( note ) )
{
note = "undefined";
}
val = self HandleNoteTrack( note, flagName );
[[postFunction]]( note );
if ( isdefined( val ) )
{
return val;
}
}
}
// Don't call this function except as a thread you're going to kill - it lasts forever.
function DoNoteTracksForever(flagName, killString, customFunction, debugIdentifier)
{
DoNoteTracksForeverProc(&DoNoteTracks, flagName, killString, customFunction, debugIdentifier);
}
function DoNoteTracksForeverIntercept(flagName, killString, interceptFunction, debugIdentifier)
{
DoNoteTracksForeverProc(&DoNoteTracksIntercept, flagName, killString, interceptFunction, debugIdentifier );
}
function DoNoteTracksForeverProc( notetracksFunc, flagName, killString, customFunction, debugIdentifier )
{
if (isdefined (killString))
{
self endon (killString);
}
self endon ("killanimscript");
if (!isdefined(debugIdentifier))
{
debugIdentifier = "undefined";
}
for (;;)
{
time = GetTime();
returnedNote = [[notetracksFunc]](flagName, customFunction, debugIdentifier);
timetaken = GetTime() - time;
if ( timetaken < 0.05)
{
time = GetTime();
returnedNote = [[notetracksFunc]](flagName, customFunction, debugIdentifier);
timetaken = GetTime() - time;
if ( timetaken < 0.05)
{
/#println (GetTime()+" "+debugIdentifier+" shared::DoNoteTracksForever is trying to cause an infinite loop on anim "+flagName+", returned "+returnedNote+".");#/
wait ( 0.05 - timetaken );
}
}
//(GetTime()+" "+debugIdentifier+" DoNoteTracksForever returned in "+timetaken+" ms.");#/
}
}
// Designed for using DoNoteTracks on looping animations, so you can wait for a time instead of the "end" parameter
function DoNoteTracksForTime(time, flagName, customFunction, debugIdentifier)
{
ent = SpawnStruct();
ent thread doNoteTracksForTimeEndNotify(time);
DoNoteTracksForTimeProc(&DoNoteTracksForever, time, flagName, customFunction, debugIdentifier, ent);
}
function DoNoteTracksForTimeIntercept( time, flagName, interceptFunction, debugIdentifier)
{
ent = SpawnStruct();
ent thread doNoteTracksForTimeEndNotify(time);
DoNoteTracksForTimeProc(&DoNoteTracksForeverIntercept, time, flagName, interceptFunction, debugIdentifier, ent);
}
function DoNoteTracksForTimeProc( doNoteTracksForeverFunc, time, flagName, customFunction, debugIdentifier, ent)
{
ent endon ("stop_notetracks");
[[doNoteTracksForeverFunc]](flagName, undefined, customFunction, debugIdentifier);
}
function doNoteTracksForTimeEndNotify(time)
{
wait (time);
self notify ("stop_notetracks");
}
View File
Whitespace-only changes.
+28
View File
@@ -0,0 +1,28 @@
// node spawnflag macros
// node type macros
// node stances macros
// Yaw and Pitch utility macros
+177
View File
@@ -0,0 +1,177 @@
#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\system_shared;
Line truncated
/# #/
#precache( "client_fx", "electric/fx_elec_warlord_damage_1" );
#precache( "client_fx", "electric/fx_elec_warlord_damage_2" );
#precache( "client_fx", "electric/fx_elec_warlord_lower_damage_1" );
#precache( "client_fx", "electric/fx_elec_warlord_lower_damage_2" );
#precache( "client_fx", "explosions/fx_exp_warlord_death" );
#precache( "client_fx", "vehicle/fx_exhaust_jetpack_warlord_juke" );
#precache( "client_fx", "light/fx_light_eye_glow_warlord" );
#precache( "client_fx", "light/fx_light_body_glow_warlord" );
function autoexec __init__sytem__() { system::register("warlord",&__init__,undefined,undefined); }
function autoexec precache()
{
level._effect[ "fx_elec_warlord_damage_1" ] = "electric/fx_elec_warlord_damage_1";
level._effect[ "fx_elec_warlord_damage_2" ] = "electric/fx_elec_warlord_damage_2";
level._effect[ "fx_elec_warlord_lower_damage_1" ] = "electric/fx_elec_warlord_lower_damage_1";
level._effect[ "fx_elec_warlord_lower_damage_2" ] = "electric/fx_elec_warlord_lower_damage_2";
level._effect[ "fx_exp_warlord_death" ] = "explosions/fx_exp_warlord_death";
level._effect[ "fx_exhaust_jetpack_warlord_juke" ] = "vehicle/fx_exhaust_jetpack_warlord_juke";
level._effect[ "fx_light_eye_glow_warlord" ] = "light/fx_light_eye_glow_warlord";
level._effect[ "fx_light_body_glow_warlord" ] = "light/fx_light_body_glow_warlord";
}
function __init__()
{
if( ai::shouldRegisterClientFieldForArchetype( "warlord" ) )
{
clientfield::register(
"actor",
"warlord_type",
1,
2,
"int",
&WarlordClientUtils::warlordTypeHandler,
!true,
!true);
clientfield::register(
"actor",
"warlord_damage_state",
1,
2,
"int",
&WarlordClientUtils::warlordDamageStateHandler,
!true,
!true);
clientfield::register(
"actor",
"warlord_thruster_direction",
1,
3,
"int",
&WarlordClientUtils::warlordThrusterHandler,
!true,
!true);
clientfield::register(
"actor",
"warlord_lights_state",
1,
1,
"int",
&WarlordClientUtils::warlordLightsHandler,
!true,
!true);
}
}
#namespace WarlordClientUtils;
function warlordDamageStateHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( IsDefined( entity.damageStateFx ) )
{
StopFx( localClientNum, entity.damageStateFx );
entity.damageStateFx = undefined;
}
if ( IsDefined( entity.damageHeavyStateFx ) )
{
StopFx( localClientNum, entity.damageHeavyStateFx );
entity.damageHeavyStateFx = undefined;
}
switch ( newValue )
{
case 0:
break;
case 2:
entity.damageHeavyStateFx =
PlayFxOnTag( localClientNum, level._effect[ "fx_elec_warlord_damage_2" ], entity, "j_spine4");
PlayFxOnTag( localClientNum, level._effect[ "fx_elec_warlord_lower_damage_2" ], entity, "j_mainroot");
// break; // This is on purpose to enable multiple FX for HEAVY_DAMAGE
case 1:
entity.damageStateFx =
PlayFxOnTag( localClientNum, level._effect[ "fx_elec_warlord_damage_1" ], entity, "j_spine4");
PlayFxOnTag( localClientNum, level._effect[ "fx_elec_warlord_lower_damage_1" ], entity, "j_mainroot");
break;
case 3:
PlayFxOnTag( localClientNum, level._effect[ "fx_exp_warlord_death" ], entity, "j_spine4");
break;
}
}
function warlordTypeHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
entity.warlordType = newValue;
}
function warlordThrusterHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( IsDefined( entity.thrusterFx ) )
{
assert( IsArray( entity.thrusterFx ) );
for ( index = 0; index < entity.thrusterFx.size; index++ )
{
StopFx( localClientNum, entity.thrusterFx[index] );
}
}
entity.thrusterFx = [];
tags = [];
switch ( newValue )
{
case 0:
break;
case 1:
tags = array( "tag_jets_left_front", "tag_jets_right_front" );
break;
case 2:
tags = array( "tag_jets_left_back", "tag_jets_right_back" );
break;
case 3:
tags = array( "tag_jets_left_side" );
break;
case 4:
tags = array( "tag_jets_right_side" );
break;
}
for ( index = 0; index < tags.size; index++ )
{
entity.thrusterFx[ entity.thrusterFx.size] =
PlayFxOnTag( localClientNum, level._effect[ "fx_exhaust_jetpack_warlord_juke" ], entity, tags[index] );
}
}
function warlordLightsHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if( newvalue == 1 )
{
PlayFxOnTag( localClientNum, level._effect[ "fx_light_eye_glow_warlord" ], entity, "tag_eye");
PlayFxOnTag( localClientNum, level._effect[ "fx_light_body_glow_warlord" ], entity, "j_spine4");
}
}
// end of #namespace WarlordClientUtils
File diff suppressed because it is too large. Load diff
+204
View File
@@ -0,0 +1,204 @@
///////////////////////////////////////////////////////////////////////////////
//
// Tweakables
//
///////////////////////////////////////////////////////////////////////////////
// Millisecond cooldown time before the warlord can juke again.
// Anything less than 2000 is less than the juke animation playback length.
// Health pool the warlord uses when in the tug-of-war recharge state.
// The initial health added to the warlord's health pool when entering the recharge state.
// NOTE - This is truncated to WARLORD_RECHARGE_HEALTH_MAX and should be positive.
// The amount of health to give the warlord per second when recharging.
// Health percentage of the warlord to play the medium damage FX.
// Health percentage of the warlord to play the heavy damage FX.
// The starting health of the warlord's shield.
// The percent of health the shield has to be to turn orange.
// The percent of health the shield has to be to turn red.
// Maximum distance from the player the warlord will try to charge melee them.
// How often the warlord will attempt to charge melee.
// in seconds
///////////////////////////////////////////////////////////////////////////////
//
// Warlord Defines
//
///////////////////////////////////////////////////////////////////////////////
/#
#/
///////////////////////////////////////////////////////////////////////////////
//
// Clientfields
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Warlord Spawn Types
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// FX
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Sounds
//
///////////////////////////////////////////////////////////////////////////////
+128
View File
@@ -0,0 +1,128 @@
#using scripts\shared\clientfield_shared;
#using scripts\shared\ai\systems\gib;
#using scripts\shared\ai_shared;
#precache( "client_fx", "zombie/fx_val_chest_burst");
#precache( "client_fx", "fire/fx_fire_ai_human_arm_left_loop_optim");
#precache( "client_fx", "fire/fx_fire_ai_human_arm_right_loop_optim");
#precache( "client_fx", "fire/fx_fire_ai_human_arm_left_loop_optim");
#precache( "client_fx", "fire/fx_fire_ai_human_arm_right_loop_optim");
#precache( "client_fx", "fire/fx_fire_ai_human_torso_loop_optim");
#precache( "client_fx", "fire/fx_fire_ai_human_hip_left_loop_optim");
#precache( "client_fx", "fire/fx_fire_ai_human_hip_right_loop_optim");
#precache( "client_fx", "fire/fx_fire_ai_human_leg_left_loop_optim");
#precache( "client_fx", "fire/fx_fire_ai_human_leg_right_loop_optim");
#precache( "client_fx", "fire/fx_fire_ai_human_head_loop_optim");
function autoexec precache()
{
}
function autoexec main()
{
level._effect["zombie_special_day_effect"] = "zombie/fx_val_chest_burst";
ai::add_archetype_spawn_function( "zombie", &ZombieClientUtils::zombie_override_burn_fx );
clientfield::register(
"actor",
"zombie",
1,
1,
"int",
&ZombieClientUtils::zombieHandler,
!true,
!true);
clientfield::register(
"actor",
"zombie_special_day",
6001,
1,
"counter",
&ZombieClientUtils::zombieSpecialDayEffectsHandler,
!true,
!true);
}
#namespace ZombieClientUtils;
function zombieHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( IsDefined( entity.archetype ) && entity.archetype != "zombie" )
{
return;
}
if ( !IsDefined( entity.initializedGibCallbacks ) || !entity.initializedGibCallbacks )
{
entity.initializedGibCallbacks = true;
GibClientUtils::AddGibCallback( localClientNum, entity, 8, &_gibCallback );
GibClientUtils::AddGibCallback( localClientNum, entity, 16, &_gibCallback );
GibClientUtils::AddGibCallback( localClientNum, entity, 32, &_gibCallback );
GibClientUtils::AddGibCallback( localClientNum, entity, 128, &_gibCallback );
GibClientUtils::AddGibCallback( localClientNum, entity, 256, &_gibCallback );
}
}
function private _gibCallback( localClientNum, entity, gibFlag )
{
switch (gibFlag)
{
case 8:
playsound(0, "zmb_zombie_head_gib", self.origin + (0,0,60));
break;
case 16:
case 32:
case 128:
case 256:
playsound(0, "zmb_death_gibs", self.origin + (0,0,30));
break;
}
}
function zombieSpecialDayEffectsHandler( localClientNum, oldValue, newValue, bNewEnt, bInitialSnap, fieldName, wasDemoJump )
{
entity = self;
if ( IsDefined( entity.archetype ) && entity.archetype != "zombie" )
{
return;
}
origin = entity GetTagOrigin( "j_spine4" );
fx = PlayFX( localClientNum, level._effect["zombie_special_day_effect"], origin );
SetFXIgnorePause( localClientNum, fx, true );
}
function zombie_override_burn_fx( localClientNum )
{
if( SessionModeIsZombiesGame() )
{
if( !isdefined( self._effect ))
{
self._effect = [];
}
level._effect["fire_zombie_j_elbow_le_loop"] = "fire/fx_fire_ai_human_arm_left_loop_optim"; // hand and forearm fires
level._effect["fire_zombie_j_elbow_ri_loop"] = "fire/fx_fire_ai_human_arm_right_loop_optim";
level._effect["fire_zombie_j_shoulder_le_loop"] = "fire/fx_fire_ai_human_arm_left_loop_optim"; // upper arm fires
level._effect["fire_zombie_j_shoulder_ri_loop"] = "fire/fx_fire_ai_human_arm_right_loop_optim";
level._effect["fire_zombie_j_spine4_loop"] = "fire/fx_fire_ai_human_torso_loop_optim"; // upper torso fires
level._effect["fire_zombie_j_hip_le_loop"] = "fire/fx_fire_ai_human_hip_left_loop_optim"; // thigh fires
level._effect["fire_zombie_j_hip_ri_loop"] = "fire/fx_fire_ai_human_hip_right_loop_optim";
level._effect["fire_zombie_j_knee_le_loop"] = "fire/fx_fire_ai_human_leg_left_loop_optim"; // shin fires
level._effect["fire_zombie_j_knee_ri_loop"] = "fire/fx_fire_ai_human_leg_right_loop_optim";
level._effect["fire_zombie_j_head_loop"] = "fire/fx_fire_ai_human_head_loop_optim"; // head fire
}
}
+1603
View File
File diff suppressed because it is too large. Load diff
+62
View File
@@ -0,0 +1,62 @@
// Melee ranges and timers
// Vision ranges
// used for getting points on the navmesh
// Crawler blocking
// Juking
// Stumbling
// Pain
// Suicide
// Movement checking
///////////////////////////////////////////////////////////////////////////////
//
// Clientfields
//
///////////////////////////////////////////////////////////////////////////////
+121
View File
@@ -0,0 +1,121 @@
#using scripts\codescripts\struct;
#using scripts\shared\util_shared;
Line truncated
#precache( "client_fx", "zombie/fx_fire_torso_zmb" );
function autoexec init_fire_fx()
{
{wait(.016);};
if(!isdefined(level._effect))level._effect=[];
level._effect["character_fire_death_sm"] = "zombie/fx_fire_torso_zmb";
level._effect["character_fire_death_torso"] = "zombie/fx_fire_torso_zmb";
}
function on_fire_timeout( localClientNum )
{
self endon ("death");
self endon( "entityshutdown" );
// about the length of the flame fx
wait 12;
if (isdefined(self) && IsAlive(self))
{
self.is_on_fire = false;
self notify ("stop_flame_damage");
}
}
function flame_death_fx( localClientNum )
{
self endon( "death" );
self endon( "entityshutdown" );
if ( ( isdefined( self.is_on_fire ) && self.is_on_fire ) )
{
return;
}
self.is_on_fire = true;
self thread on_fire_timeout();
if( isdefined( level._effect ) && isdefined( level._effect["character_fire_death_torso"] ) )
{
fire_tag = "j_spinelower";
if( !isDefined( self GetTagOrigin( fire_tag))) //allows effect to play on parasite and insanity elementals
{
fire_tag = "tag_origin";
}
if ( !isDefined( self.isdog) || !self.isdog )
{
PlayFxOnTag( localClientNum, level._effect["character_fire_death_torso"], self, fire_tag );
}
}
else
{
/#
println( "^3ANIMSCRIPT WARNING: You are missing level._effect[\"character_fire_death_torso\"], please set it in your levelname_fx.gsc. Use \"env/fire/fx_fire_player_torso\"" );
#/
}
if( isdefined( level._effect ) && isdefined( level._effect["character_fire_death_sm"] ) )
{
if( self.archetype !== "parasite" && self.archetype !== "raps" )
{
wait 1;
tagArray = [];
tagArray[0] = "J_Elbow_LE";
tagArray[1] = "J_Elbow_RI";
tagArray[2] = "J_Knee_RI";
tagArray[3] = "J_Knee_LE";
tagArray = randomize_array( tagArray );
PlayFxOnTag( localClientNum, level._effect["character_fire_death_sm"], self, tagArray[0] );
wait 1;
tagArray[0] = "J_Wrist_RI";
tagArray[1] = "J_Wrist_LE";
if( !( isdefined( self.missinglegs ) && self.missinglegs ) )
{
tagArray[2] = "J_Ankle_RI";
tagArray[3] = "J_Ankle_LE";
}
tagArray = randomize_array( tagArray );
PlayFxOnTag( localClientNum, level._effect["character_fire_death_sm"], self, tagArray[0] );
PlayFxOnTag( localClientNum, level._effect["character_fire_death_sm"], self, tagArray[1] );
}
}
else
{
/#
println( "^3ANIMSCRIPT WARNING: You are missing level._effect[\"character_fire_death_sm\"], please set it in your levelname_fx.gsc. Use \"env/fire/fx_fire_zombie_md\"" );
#/
}
}
// MikeD( 9/30/2007 ): Taken from maps\_utility "array_randomize:, for some reason maps\_utility is included in a animscript
// somewhere, but I can't call it within in this... So I made a new one.
function randomize_array( array )
{
for( i = 0; i < array.size; i++ )
{
j = RandomInt( array.size );
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
+299
View File
@@ -0,0 +1,299 @@
#using scripts\codescripts\struct;
#using scripts\shared\util_shared;
#using scripts\shared\ai\zombie_shared;
#using scripts\shared\ai\zombie_utility;
#using scripts\shared\ai\systems\gib;
Line truncated
#precache( "fx", "zombie/fx_blood_torso_explo_zmb" );
#precache( "fx", "_t6/trail/fx_trail_blood_streak" );
#precache( "fx", "_t6/impacts/fx_flesh_hit_neck_fatal" );
function on_fire_timeout()
{
self endon ("death");
// about the length of the flame fx
if( isdefined(self.flame_fx_timeout) )
{
wait self.flame_fx_timeout;
}
else
{
wait 12;
}
if (isdefined(self) && IsAlive(self))
{
self.is_on_fire = false;
self notify ("stop_flame_damage");
}
}
function flame_death_fx()
{
self endon( "death" );
if (isdefined(self.is_on_fire) && self.is_on_fire )
{
return;
}
if( ( isdefined( self.disable_flame_fx ) && self.disable_flame_fx ) )
{
return;
}
self.is_on_fire = true;
self thread on_fire_timeout();
if( isdefined( level._effect ) && isdefined( level._effect["character_fire_death_torso"] ) )
{
fire_tag = "j_spinelower";
fire_death_torso_fx = level._effect["character_fire_death_torso"];
if( isDefined( self.weapon_specific_fire_death_torso_fx ))
{
fire_death_torso_fx = self.weapon_specific_fire_death_torso_fx;
}
if( !isDefined( self GetTagOrigin( fire_tag))) //allows effect to play on parasite and insanity elementals
{
fire_tag = "tag_origin";
}
if ( !isDefined( self.isdog) || !self.isdog )
{
PlayFxOnTag( fire_death_torso_fx, self, fire_tag );
}
self.weapon_specific_fire_death_torso_fx = undefined;
}
else
{
/#
println( "^3ANIMSCRIPT WARNING: You are missing level._effect[\"character_fire_death_torso\"], please set it in your levelname_fx.gsc. Use \"env/fire/fx_fire_player_torso\"" );
#/
}
if( isdefined( level._effect ) && isdefined( level._effect["character_fire_death_sm"] ) )
{
if( self.archetype !== "parasite" && self.archetype !== "raps" && self.archetype !== "spider")
{
fire_death_sm_fx = level._effect["character_fire_death_sm"];
if( isDefined( self.weapon_specific_fire_death_sm_fx ))
{
fire_death_sm_fx = self.weapon_specific_fire_death_sm_fx;
}
if( isDefined( self.weapon_specific_fire_death_torso_fx ))
{
fire_death_torso_fx = self.weapon_specific_fire_death_torso_fx;
}
wait 1;
tagArray = [];
tagArray[0] = "J_Elbow_LE";
tagArray[1] = "J_Elbow_RI";
tagArray[2] = "J_Knee_RI";
tagArray[3] = "J_Knee_LE";
tagArray = randomize_array( tagArray );
PlayFxOnTag( fire_death_sm_fx, self, tagArray[0] );
wait 1;
tagArray[0] = "J_Wrist_RI";
tagArray[1] = "J_Wrist_LE";
if( !isdefined( self.a.gib_ref ) || self.a.gib_ref != "no_legs" )
{
tagArray[2] = "J_Ankle_RI";
tagArray[3] = "J_Ankle_LE";
}
tagArray = randomize_array( tagArray );
PlayFxOnTag( fire_death_sm_fx, self, tagArray[0] );
PlayFxOnTag( fire_death_sm_fx, self, tagArray[1] );
self.weapon_specific_fire_death_sm_fx = undefined;
}
}
else
{
/#
println( "^3ANIMSCRIPT WARNING: You are missing level._effect[\"character_fire_death_sm\"], please set it in your levelname_fx.gsc. Use \"env/fire/fx_fire_zombie_md\"" );
#/
}
}
// MikeD( 9/30/2007 ): Taken from maps\_utility "array_randomize:, for some reason maps\_utility is included in a animscript
// somewhere, but I can't call it within in this... So I made a new one.
function randomize_array( array )
{
for( i = 0; i < array.size; i++ )
{
j = RandomInt( array.size );
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function set_last_gib_time()
{
anim notify( "stop_last_gib_time" );
anim endon( "stop_last_gib_time" );
{wait(.05);};
anim.lastGibTime = GetTime();
anim.totalGibs = RandomIntRange( anim.minGibs, anim.maxGibs );
}
function get_gib_ref( direction )
{
// If already set, then use it. Useful for canned gib deaths.
if( isdefined( self.a.gib_ref ) )
{
return;
}
// Don't gib if we haven't taken enough damage by the explosive
// Grenade damage usually range from 160 - 250, so we go above teh minimum
// so if the splash damage is near it's lowest, don't gib.
if( self.damageTaken < 165 )
{
return;
}
if( GetTime() > anim.lastGibTime + anim.gibDelay && anim.totalGibs > 0 )
{
anim.totalGibs--;
// MikeD( 5/5/2008 ): Allows multiple guys to GIB at once.
anim thread set_last_gib_time();
refs = [];
switch( direction )
{
case "right":
refs[refs.size] = "left_arm";
refs[refs.size] = "left_leg";
gib_ref = get_random( refs );
break;
case "left":
refs[refs.size] = "right_arm";
refs[refs.size] = "right_leg";
gib_ref = get_random( refs );
break;
case "forward":
refs[refs.size] = "right_arm";
refs[refs.size] = "left_arm";
refs[refs.size] = "right_leg";
refs[refs.size] = "left_leg";
refs[refs.size] = "guts";
refs[refs.size] = "no_legs";
gib_ref = get_random( refs );
break;
case "back":
refs[refs.size] = "right_arm";
refs[refs.size] = "left_arm";
refs[refs.size] = "right_leg";
refs[refs.size] = "left_leg";
refs[refs.size] = "no_legs";
gib_ref = get_random( refs );
break;
default: // "up"
refs[refs.size] = "right_arm";
refs[refs.size] = "left_arm";
refs[refs.size] = "right_leg";
refs[refs.size] = "left_leg";
refs[refs.size] = "no_legs";
refs[refs.size] = "guts";
gib_ref = get_random( refs );
break;
}
self.a.gib_ref = gib_ref;
}
else
{
self.a.gib_ref = undefined;
}
}
function get_random( array )
{
return array[RandomInt( array.size )];
}
function do_gib()
{
if( !isdefined( self.a.gib_ref ) )
{
return;
}
if (isdefined(self.is_on_fire) && self.is_on_fire)
{
return;
}
switch ( self.a.gib_ref )
{
case "right_arm":
GibServerUtils::GibRightArm( self );
break;
case "left_arm":
GibServerUtils::GibLeftArm( self );
break;
case "right_leg":
GibServerUtils::GibRightLeg( self );
break;
case "left_leg":
GibServerUtils::GibLeftLeg( self );
break;
case "no_legs":
GibServerUtils::GibLegs( self );
break;
case "head":
GibServerUtils::GibHead( self );
break;
case "guts":
// TODO(David Young 9-17-14): Currently no characters have gut torso models, unsupported.
break;
default:
AssertMsg( "Unknown gib_ref \"" + self.a.gib_ref + "\", unable to gib entity." );
break;
}
}
function precache_gib_fx()
{
anim._effect["animscript_gib_fx"] = "zombie/fx_blood_torso_explo_zmb";
anim._effect["animscript_gibtrail_fx"] = "_t6/trail/fx_trail_blood_streak";
// Not gib; split out into another function before this gets out of hand.
anim._effect["death_neckgrab_spurt"] = "_t6/impacts/fx_flesh_hit_neck_fatal";
}
Loaded 100 of 921 files, more files were not shown because too many files have changed in this diff. Show more