538 lines
20 KiB
Plaintext
538 lines
20 KiB
Plaintext
#using scripts\shared\ai_shared;
|
|
#using scripts\shared\math_shared;
|
|
#using scripts\shared\spawner_shared;
|
|
#using scripts\shared\ai\archetype_utility;
|
|
|
|
|
|
|
|
|
|
#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_ = [];
|
|
}
|
|
}
|
|
} |