boiii-scripts/shared/ai/phalanx.gsc
2023-04-13 17:30:38 +02:00

566 lines
21 KiB
Plaintext

#using scripts\shared\ai_shared;
#using scripts\shared\math_shared;
#using scripts\shared\spawner_shared;
#using scripts\shared\ai\archetype_utility;
#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" ] = [];
}
}
}