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

2449 lines
64 KiB
Plaintext

#using scripts\codescripts\struct;
#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;
#namespace colors;
/*
Color coded AI travel system
A colorCode is a color( red, blue, yellow, cyan, green, purple or orange ) and a #.
When a trigger and AI and node are color grouped in Radiant, they get a unique color and #.
When when a color coded trigger is hit, that colorCode is "fired" and any AI that are in colorCoded mode get their goalnode set.
AI can be forced to a color generically. For example if an AI is forced to the color "red" and a trigger fires off "red15", the AI
will go to that node even if the AI doesn't have script_color_allies( or axis ) "red15". This is mainly for friendlies.
AI can also have their script_careful variable set, which prevents them from advancing to a node or a volume if an enemy is occupying it.
They will move up if the zone is cleared.
Volumes can also be tagged with a code and a number like r6. So if an enemy is present in this volume, no AI will run to any node tagged r6.
*/
function autoexec __init__sytem__() { system::register("colors",&__init__,&__main__,undefined); }
function __init__()
{
nodes = GetAllNodes();
// friendly spawner global stuff
level flag::init( "player_looks_away_from_spawner" );
level flag::init( "friendly_spawner_locked" );
// can be turned on and off to control friendly_respawn_trigger
level flag::init( "respawn_friendlies" );
level.arrays_of_colorCoded_nodes = [];
level.arrays_of_colorCoded_nodes[ "axis" ] = [];
level.arrays_of_colorCoded_nodes[ "allies" ] = [];
level.colorCoded_volumes = [];
level.colorCoded_volumes[ "axis" ] = [];
level.colorCoded_volumes[ "allies" ] = [];
volumes = GetEntArray( "info_volume", "classname" );
// go through all the nodes and if they have color codes then add them too
for( i=0;i<nodes.size;i++ )
{
if( IsDefined( nodes[ i ].script_color_allies ) )
{
nodes[ i ] add_node_to_global_arrays( nodes[ i ].script_color_allies, "allies" );
}
if( IsDefined( nodes[ i ].script_color_axis ) )
{
nodes[ i ] add_node_to_global_arrays( nodes[ i ].script_color_axis, "axis" );
}
}
// volumes that have color codes wait for trigger and then fire their colorcode to control fixednodesafe volumes
for( i=0;i<volumes.size;i++ )
{
if( IsDefined( volumes[ i ].script_color_allies ) )
{
volumes[ i ] add_volume_to_global_arrays( volumes[ i ].script_color_allies, "allies" );
}
if( IsDefined( volumes[ i ].script_color_axis ) )
{
volumes[ i ] add_volume_to_global_arrays( volumes[ i ].script_color_axis, "axis" );
}
}
/#
level.colorNodes_debug_array = [];
level.colorNodes_debug_array[ "allies" ] = [];
level.colorNodes_debug_array[ "axis" ] = [];
#/
level.color_node_type_function = [];
add_cover_node( "BAD NODE" );
add_cover_node( "Cover Stand" );
add_cover_node( "Cover Crouch" );
add_cover_node( "Cover Prone" );
add_cover_node( "Cover Crouch Window" );
add_cover_node( "Cover Right" );
add_cover_node( "Cover Left" );
add_cover_node( "Cover Wide Left" );
add_cover_node( "Cover Wide Right" );
add_cover_node( "Cover Pillar" );
add_cover_node( "Conceal Stand" );
add_cover_node( "Conceal Crouch" );
add_cover_node( "Conceal Prone" );
add_cover_node( "Reacquire" );
add_cover_node( "Balcony" );
add_cover_node( "Scripted" );
add_cover_node( "Begin" );
add_cover_node( "End" );
add_cover_node( "Turret" );
add_path_node( "Guard" );
add_path_node( "Exposed" );
add_path_node( "Path" );
level.colorList = [];
level.colorList[ level.colorList.size ] = "r";
level.colorList[ level.colorList.size ] = "b";
level.colorList[ level.colorList.size ] = "y";
level.colorList[ level.colorList.size ] = "c";
level.colorList[ level.colorList.size ] = "g";
level.colorList[ level.colorList.size ] = "p";
level.colorList[ level.colorList.size ] = "o";
level.colorCheckList[ "red" ] = "r";
level.colorCheckList[ "r" ] = "r";
level.colorCheckList[ "blue" ] = "b";
level.colorCheckList[ "b" ] = "b";
level.colorCheckList[ "yellow" ]= "y";
level.colorCheckList[ "y" ] = "y";
level.colorCheckList[ "cyan" ] = "c";
level.colorCheckList[ "c" ] = "c";
level.colorCheckList[ "green" ] = "g";
level.colorCheckList[ "g" ] = "g";
level.colorCheckList[ "purple" ]= "p";
level.colorCheckList[ "p" ] = "p";
level.colorCheckList[ "orange" ]= "o";
level.colorCheckList[ "o" ] = "o";
level.currentColorForced = [];
level.currentColorForced[ "allies" ] = [];
level.currentColorForced[ "axis" ] = [];
level.lastColorForced = [];
level.lastColorForced[ "allies" ] = [];
level.lastColorForced[ "axis" ] = [];
for( i = 0; i < level.colorList.size; i++ )
{
level.arrays_of_colorForced_ai[ "allies" ][ level.colorList[ i ] ] = [];
level.arrays_of_colorForced_ai[ "axis" ][ level.colorList[ i ] ] = [];
level.currentColorForced[ "allies" ][ level.colorList[ i ] ] = undefined;
level.currentColorForced[ "axis" ][ level.colorList[ i ] ] = undefined;
}
/#
thread debugDvars();
thread debugColorFriendlies();
#/
// Fix for leak, there's probably a better place to clean up this array
// level thread cleanup_arrays();
}
function __main__()
{
foreach ( trig in trigger::get_all() )
{
if ( isdefined( trig.script_color_allies ) )
{
trig thread trigger_issues_orders( trig.script_color_allies, "allies" );
}
if ( isdefined( trig.script_color_axis ) )
{
trig thread trigger_issues_orders( trig.script_color_axis, "axis" );
}
}
}
// Not working
//function cleanup_arrays()
//{
// while ( true )
// {
// while ( !GetDvarInt( "scr_scrub_array", 0 ) )
// {
// WAIT_SERVER_FRAME;
// }
//
// a_backup = ArrayCopy( level.arrays_of_colorForced_ai );
//
// array::cleanup( level.arrays_of_colorForced_ai, true );
//
// WAIT_ABOUT( 1 );
// }
//}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// DEBUG //////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/#
function debugDvars()
{
if ( GetDvarString( "debug_colornodes" ) == "" )
{
SetDvar( "debug_colornodes", "0" );
}
while ( true )
{
if ( GetDvarInt( "debug_colornodes" ) > 0 )
{
thread debug_colornodes();
}
wait .05;
}
}
function get_team_substr()
{
if( self.team == "allies" )
{
if( !isdefined( self.node.script_color_allies_old ) )
{
return;
}
return self.node.script_color_allies_old;
}
if( self.team == "axis" )
{
if( !isdefined( self.node.script_color_axis_old ) )
{
return;
}
return self.node.script_color_axis_old;
}
}
function try_to_draw_line_to_node()
{
if( !isdefined( self.node ) )
{
return;
}
if( !isdefined( self.script_forceColor ) )
{
return;
}
substr = get_team_substr();
if( !isdefined( substr ) )
{
return;
}
if( !issubstr( substr, self.script_forceColor ) )
{
return;
}
RecordLine( self.origin +( 0, 0, 25 ), self.node.origin, _get_debug_color( self.script_forceColor ), "Script", self );
line( self.origin +( 0, 0, 25 ), self.node.origin, _get_debug_color( self.script_forceColor ) );
}
function _get_debug_color( str_color )
{
switch( str_color )
{
case "r":
case "red":
return ( 1, 0, 0 );
break;
case "g":
case "green":
return ( 0, 1, 0 );
break;
case "b":
case "blue":
return ( 0, 0, 1 );
break;
case "y":
case "yellow":
return ( 1, 1, 0 );
break;
case "o":
case "orange":
return ( 1, .5, 0 );
break;
case "c":
case "cyan":
return ( 0, 1, 1 );
break;
case "p":
case "purple":
return ( 1, 0, 1 );
break;
default:
PrintLn( "Debug color " + str_color + " not set in debug::_get_debug_color(). Please add this color." );
return ( 0, 0, 0 );
break;
}
}
function debug_colornodes()
{
array = [];
array[ "axis" ] = [];
array[ "allies" ] = [];
array[ "neutral" ] = [];
foreach ( ai in GetAIArray() )
{
if ( !isdefined( ai.currentColorCode ) )
{
continue;
}
array[ ai.team ][ ai.currentColorCode ] = true;
color = ( 1, 1, 1 );
if ( isdefined( ai.script_forceColor ) )
{
color = _get_debug_color( ai.script_forceColor );
}
RecordEntText( ai.currentColorCode, ai, color, "Script");
Print3d( ai.origin + ( 0, 0, 25 ), ai.currentColorCode, color, 1, 0.7 );
// axis don't do forcecolor behavior, they do follow the leader for force color
if ( ai.team == "axis" )
{
continue;
}
ai try_to_draw_line_to_node();
}
draw_colorNodes( array, "allies" );
draw_colorNodes( array, "axis" );
}
function draw_colorNodes( array, team )
{
keys = GetArrayKeys( array[ team ] );
for ( i = 0; i < keys.size; i++ )
{
color = _get_debug_color( GetSubStr( keys[ i ], 0, 1 ) );
if ( isdefined( level.colorNodes_debug_array[ team ][ keys[ i ] ] ) )
{
a_team_nodes = level.colorNodes_debug_array[ team ][ keys[ i ] ];
for ( p = 0; p < a_team_nodes.size; p++ )
{
// there can be multiple colors on the nodes, avoid drawing them on top of each other.
Print3d( a_team_nodes[ p ].origin, "N-" + keys[ i ], color, 1, 0.7 );
if ( GetDvarString( "debug_colornodes" ) == "2" && isdefined( a_team_nodes[ p ].script_color_allies_old ) )
{
if ( isdefined( a_team_nodes[ p ].color_user )
&& IsAlive( a_team_nodes[ p ].color_user )
&& isdefined( a_team_nodes[ p ].color_user.script_forceColor )
)
{
print3d( a_team_nodes[ p ].origin + ( 0, 0, -5 ), "N-" + a_team_nodes[ p ].script_color_allies_old, _get_debug_color( a_team_nodes[ p ].color_user.script_forceColor ), 0.5, 0.4 );
}
else
{
print3d( a_team_nodes[ p ].origin + ( 0, 0, -5 ), "N-" + a_team_nodes[ p ].script_color_allies_old, color, 0.5, 0.4 );
}
}
}
}
}
}
function debugColorFriendlies()
{
level.debug_color_friendlies = [];
level.debug_color_huds = [];
level thread debugColorFriendliesToggleWatch();
for( ;; )
{
level waittill( "updated_color_friendlies" );
draw_color_friendlies();
}
}
function debugColorFriendliesToggleWatch()
{
just_turned_on = false;
just_turned_off = false;
while( 1 )
{
if( GetDvarString( "debug_colornodes" ) == "1" && !just_turned_on )
{
just_turned_on = true;
just_turned_off = false;
draw_color_friendlies();
}
if( GetDvarString( "debug_colornodes" ) != "1" && !just_turned_off )
{
just_turned_off = true;
just_turned_on = false;
draw_color_friendlies();
}
wait( 0.25 );
}
}
function get_script_palette()
{
rgb = [];
rgb[ "r" ] = ( 1, 0, 0 );
rgb[ "o" ] = ( 1, .5, 0 );
rgb[ "y" ] = ( 1, 1, 0 );
rgb[ "g" ] = ( 0, 1, 0 );
rgb[ "c" ] = ( 0, 1, 1 );
rgb[ "b" ] = ( 0, 0, 1 );
rgb[ "p" ] = ( 1, 0, 1 );
return rgb;
}
function draw_color_friendlies()
{
level endon( "updated_color_friendlies" );
keys = getarraykeys( level.debug_color_friendlies );
colored_friendlies = [];
colors = [];
colors[ colors.size ] = "r";
colors[ colors.size ] = "o";
colors[ colors.size ] = "y";
colors[ colors.size ] = "g";
colors[ colors.size ] = "c";
colors[ colors.size ] = "b";
colors[ colors.size ] = "p";
rgb = colors::get_script_palette();
for( i=0; i < colors.size; i++ )
{
colored_friendlies[ colors[ i ] ] = 0;
}
for( i=0; i < keys.size; i++ )
{
color = level.debug_color_friendlies[ keys[ i ] ];
colored_friendlies[ color ]++;
}
for( i=0; i < level.debug_color_huds.size; i++ )
{
level.debug_color_huds[ i ] destroy();
}
level.debug_color_huds = [];
if( GetDvarString( "debug_colornodes" ) != "1" )
{
return;
}
const x = 15;
y = 365;
const offset_y = 25;
for( i=0; i < colors.size; i++ )
{
if( colored_friendlies[ colors[ i ] ] <= 0 )
{
continue;
}
for( p=0; p < colored_friendlies[ colors[ i ] ]; p++ )
{
overlay = newHudElem();
overlay.x = x + 25*p;
overlay.y = y;
overlay setshader( "white", 16, 16 );
overlay.alignX = "left";
overlay.alignY = "bottom";
overlay.alpha = 1;
overlay.color = rgb[ colors[ i ] ];
level.debug_color_huds[ level.debug_color_huds.size ] = overlay;
}
y += offset_y;
}
}
#/
function player_init_color_grouping( )
{
thread player_color_node();
}
function convert_color_to_short_string()
{
// shorten the forcecolors string to a single letter
self.script_forceColor = level.colorCheckList[ self.script_forceColor ];
}
function goto_current_ColorIndex()
{
if( !IsDefined( self.currentColorCode ) )
{
return;
}
nodes = level.arrays_of_colorCoded_nodes[ self.team ][ self.currentColorCode ];
if ( !isdefined( nodes ) ) nodes = []; else if ( !IsArray( nodes ) ) nodes = array( nodes ); nodes[nodes.size]=level.colorCoded_volumes[ self.team ][ self.currentColorCode ];;
self left_color_node();
// can be deleted/killed during left_color_node
if( !isalive( self ) )
{
return;
}
// can lose color during left_color_node
if( !has_color() )
{
return;
}
for( i=0; i <nodes.size; i++ )
{
node = nodes[ i ];
if ( isalive( node.color_user ) && !IsPlayer(node.color_user) )
{
continue;
}
self thread ai_sets_goal_with_delay( node );
thread decrementColorUsers( node );
return;
}
/#println( "AI with export " + self.export + " was told to go to color node but had no node to go to." );#/
}
function get_color_list()
{
colorList = [];
// returns an array of the acceptable color letters
colorList[ colorList.size ] = "r";
colorList[ colorList.size ] = "b";
colorList[ colorList.size ] = "y";
colorList[ colorList.size ] = "c";
colorList[ colorList.size ] = "g";
colorList[ colorList.size ] = "p";
colorList[ colorList.size ] = "o";
return colorList;
}
function get_colorcodes_from_trigger( color_team, team )
{
colorCodes = strtok( color_team, " " );
colors = [];
colorCodesByColorIndex = [];
usable_colorCodes = [];
colorList = get_color_list();
for( i = 0; i < colorCodes.size; i++ )
{
color = undefined;
for( p = 0; p < colorList.size; p++ )
{
if( issubstr( colorCodes[ i ], colorList[ p ] ) )
{
color = colorList[ p ];
break;
}
}
// does this order actually tie to existing nodes?
if( !IsDefined( level.arrays_of_colorCoded_nodes[ team ][ colorCodes[ i ] ] )
&& !IsDefined( level.colorCoded_volumes[ team ][ colorCodes[ i ] ] )
)
{
continue;
}
assert( IsDefined( color ), "Trigger at origin " + self getorigin() + " had strange color index " + colorCodes[ i ] );
colorCodesByColorIndex[ color ] = colorCodes[ i ];
colors[ colors.size ] = color;
usable_colorCodes[ usable_colorCodes.size ] = colorCodes[ i ];
}
// color codes that don't tie to existing nodes have been culled
colorCodes = usable_colorCodes;
array = [];
array[ "colorCodes" ] = colorCodes;
array[ "colorCodesByColorIndex" ] = colorCodesByColorIndex;
array[ "colors" ] = colors;
return array;
}
function trigger_issues_orders( color_team, team )
{
self endon( "death" );
array = get_colorcodes_from_trigger( color_team, team );
colorCodes = array[ "colorCodes" ];
colorCodesByColorIndex = array[ "colorCodesByColorIndex" ];
colors = array[ "colors" ];
if ( isdefined( self.target ) )
{
a_s_targets = struct::get_array( self.target, "targetname" );
foreach( s_target in a_s_targets )
{
if ( ( s_target.script_string === "hero_catch_up" ) )
{
if(!isdefined(self.a_s_hero_catch_up))self.a_s_hero_catch_up=[];
if ( !isdefined( self.a_s_hero_catch_up ) ) self.a_s_hero_catch_up = []; else if ( !IsArray( self.a_s_hero_catch_up ) ) self.a_s_hero_catch_up = array( self.a_s_hero_catch_up ); self.a_s_hero_catch_up[self.a_s_hero_catch_up.size]=s_target;;
if( isdefined( s_target.script_num ) )//Check struct for distance override
{
self.num_hero_catch_up_dist = s_target.script_num;
}
}
}
}
for( ;; )
{
self waittill( "trigger" );
if( IsDefined( self.activated_color_trigger ) )
{
// activated by an _utility::activate_trigger() call, so don't bother running activate_color_trigger() again.
self.activated_color_trigger = undefined;
continue;
}
if( !IsDefined( self.color_enabled ) || ( IsDefined( self.color_enabled ) && self.color_enabled ) )
{
activate_color_trigger_internal( colorCodes, colors, team, colorCodesByColorIndex );
}
trigger_auto_disable();
}
}
function trigger_auto_disable()
{
if( !IsDefined( self.script_color_stay_on ) )
{
self.script_color_stay_on = false;
}
if( !IsDefined( self.color_enabled ) )
{
if( ( isdefined( self.script_color_stay_on ) && self.script_color_stay_on ) )
{
self.color_enabled = true;
}
else
{
self.color_enabled = false;
}
}
}
function activate_color_trigger( team )
{
if ( team == "allies" )
{
self thread get_colorcodes_and_activate_trigger( self.script_color_allies, team );
}
else
{
self thread get_colorcodes_and_activate_trigger( self.script_color_axis, team );
}
}
function get_colorcodes_and_activate_trigger( color_team, team )
{
array = get_colorcodes_from_trigger( color_team, team );
colorCodes = array[ "colorCodes" ];
colorCodesByColorIndex = array[ "colorCodesByColorIndex" ];
colors = array[ "colors" ];
activate_color_trigger_internal( colorCodes, colors, team, colorCodesByColorIndex );
}
function private is_target_visible( target )
{
n_player_fov = GetDvarFloat( "cg_fov" );
n_dot_check = cos( n_player_fov );
v_pos = target;
if ( !IsVec( target ) )
{
v_pos = target.origin;
}
foreach ( player in level.players )
{
v_eye = player GetEye();
v_facing = AnglesToForward( player GetPlayerAngles() );
v_to_ent = VectorNormalize( v_pos - v_eye );
n_dot = VectorDot( v_facing, v_to_ent );
if ( n_dot > n_dot_check )
{
return true;
}
else if ( IsVec( target ) )
{
a_trace = BulletTrace( v_eye, target, false, player );
if ( a_trace["fraction"] == 1 )
{
return true;
}
}
else if ( target SightConeTrace( v_eye, player ) != 0 )
{
return true;
}
}
return false;
}
// Teleports a hero once it's safe to do so (no telefragging, no player visibility)."
//
// Will bail if one of the teleport points is further from the goal than the hero.
//
function hero_catch_up_teleport( s_teleport, n_min_dist_from_player = 400.0, b_disable_colors = false, func_callback )
{
self notify( "_hero_catch_up_teleport_" );
self endon( "_hero_catch_up_teleport_" );
self endon( "stop_hero_catch_up_teleport" );
const n_telefrag_radius = 16.0;
const n_teleport_cooldown_ms = 2000;
n_min_player_dist_sq = n_min_dist_from_player * n_min_dist_from_player;
self endon( "death" );
a_teleport = s_teleport;
if ( !isdefined( a_teleport ) ) a_teleport = []; else if ( !IsArray( a_teleport ) ) a_teleport = array( a_teleport );;
a_teleport = array::randomize( a_teleport );
while ( true )
{
b_player_nearby = false;
foreach( player in level.players )
{
if ( DistanceSquared( player.origin, self.origin ) < n_min_player_dist_sq )
{
b_player_nearby = true;
break;
}
}
if ( !b_player_nearby )
{
n_ai_dist = -1.0;
if ( isdefined( self.goal ) )
{
n_ai_dist = self CalcPathLength( self.node );
}
foreach( s in a_teleport )
{
if ( PositionWouldTelefrag( s.origin ) )
{
continue;
}
if ( isdefined( s.teleport_cooldown ) )
{
if ( GetTime() < s.teleport_cooldown )
{
continue;
}
}
// Don't teleport on top of heroes.
//
if ( self.team == "allies" && isdefined( level.heroes ) )
{
hit_hero = ArrayGetClosest( s.origin, level.heroes, n_telefrag_radius );
if ( isdefined( hit_hero ) )
{
continue;
}
}
// Distance from teleport location to the goal node.
if ( isdefined( self.node ) && n_ai_dist >= 0.0 )
{
// If the teleport position would put us further from our goal, we're close enough.
n_teleport_distance = PathDistance( s.origin, self.node.origin );
if ( n_teleport_distance > n_ai_dist )
{
return;
}
}
// Don't teleport if someone's looking at them.
//
if ( is_target_visible( self ) )
{
continue;
}
// Don't teleport if someone's looking at the destination.
//
if ( is_target_visible( s.origin ) )
{
continue;
}
if ( isdefined( self.script_forceColor ) || b_disable_colors )
{
if ( self ForceTeleport( s.origin, s.angles, true, true ) )
{
self PathMode( "move allowed" );
s.teleport_cooldown = GetTime() + n_teleport_cooldown_ms;
self notify( "hero_catch_up_teleport" );
if ( b_disable_colors )
{
disable();
}
else
{
self colors::set_force_color( self.script_forceColor );
}
if ( isdefined( func_callback ) )
{
self [[ func_callback ]]();
}
return;
}
}
}
}
else
{
return;//A player is close, no need to keep checking
}
// Try again soon.
wait 0.5;
}
}
function activate_color_trigger_internal( colorCodes, colors, team, colorCodesByColorIndex )
{
// remove all the dead from any colors this trigger effects
// a trigger should never effect the same color twice
for( i = 0; i < colorCodes.size; i++ )
{
if( !IsDefined( level.arrays_of_colorCoded_spawners[ team ][ colorCodes[ i ] ] ) )
{
continue;
}
// remove deleted spawners
ArrayRemoveValue( level.arrays_of_colorCoded_spawners[ team ][ colorCodes[ i ] ], undefined );
// set the .currentColorCode on each appropriate spawner
for( p = 0; p < level.arrays_of_colorCoded_spawners[ team ][ colorCodes[ i ] ].size; p++ )
{
level.arrays_of_colorCoded_spawners[ team ][ colorCodes[ i ] ][ p ].currentColorCode = colorCodes[ i ];
}
}
for( i = 0; i < colors.size; i++ )
{
// remove the dead from the color forced ai
level.arrays_of_colorForced_ai[ team ][ colors[ i ] ] = array::remove_dead( level.arrays_of_colorForced_ai[ team ][ colors[ i ] ] );
// set the last color forced so we can compare it with current when we tell guys to go to nodes,
// so they can prefer new nodes over old ones, so they move up
level.lastColorForced[ team ][ colors[ i ] ] = level.currentColorForced[ team ][ colors[ i ] ];
// set the destination of the color forced spawners
level.currentColorForced[ team ][ colors[ i ] ] = colorCodesByColorIndex[ colors[ i ] ];
/#
assert( IsDefined( level.arrays_of_colorCoded_nodes[ team ][ level.currentColorForced[ team ][ colors[ i ] ] ] )
|| IsDefined( level.colorCoded_volumes[ team ][ level.currentColorForced[ team ][ colors[ i ] ] ] ),
"Trigger tried to set colorCode " + colors[ i ] + " but there are no nodes for " + team + " that use that color combo." );
#/
}
ai_array = [];
for ( i = 0; i < colorCodes.size; i++ )
{
// no need to run this again if it's still the current forced color
if ( same_color_code_as_last_time( team, colors[ i ] ) )
{
continue;
}
colorCode = colorCodes[ i ];
if ( !IsDefined( level.arrays_of_colorCoded_ai[ team ][ colorCode ] ) )
{
continue;
}
ai_array[ colorCode ] = issue_leave_node_order_to_ai_and_get_ai( colorCode, colors[ i ], team );
if ( isdefined( self.a_s_hero_catch_up ) && ai_array.size > 0 )
{
if( isdefined( ai_array[ colorCode ] ) )
{
for ( j = 0; j < ai_array[ colorCode ].size; j++ )
{
ai = ai_array[ colorCode ][ j ];
if ( ( isdefined( ai.is_hero ) && ai.is_hero ) && IsDefined( ai.script_forceColor ) )
{
ai thread hero_catch_up_teleport( self.a_s_hero_catch_up );
}
}
}
}
}
for( i = 0; i < colorCodes.size; i++ )
{
colorCode = colorCodes[ i ];
if ( !IsDefined( ai_array[ colorCode ] ) )
{
continue;
}
// no need to run this again if it's still the current forced color
if( same_color_code_as_last_time( team, colors[ i ] ) )
{
continue;
}
if ( !IsDefined( level.arrays_of_colorCoded_ai[ team ][ colorCode ] ) )
{
continue;
}
issue_color_order_to_ai( colorCode, colors[ i ], team, ai_array[ colorCode ] );
}
}
function same_color_code_as_last_time( team, color )
{
if( !IsDefined( level.lastColorForced[ team ][ color ] ) )
{
return false;
}
return level.lastColorForced[ team ][ color ] == level.currentColorForced[ team ][ color ];
}
function process_cover_node_with_last_in_mind_allies( node, lastColor )
{
// nodes that were in the last color order go at the end
if( issubstr( node.script_color_allies, lastColor ) )
{
self.cover_nodes_last[ self.cover_nodes_last.size ] = node;
}
else
{
self.cover_nodes_first[ self.cover_nodes_first.size ] = node;
}
}
function process_cover_node_with_last_in_mind_axis( node, lastColor )
{
// nodes that were in the last color order go at the end
if( issubstr( node.script_color_axis, lastColor ) )
{
self.cover_nodes_last[ self.cover_nodes_last.size ] = node;
}
else
{
self.cover_nodes_first[ self.cover_nodes_first.size ] = node;
}
}
function process_cover_node( node, null )
{
self.cover_nodes_first[ self.cover_nodes_first.size ] = node;
}
function process_path_node( node, null )
{
self.path_nodes[ self.path_nodes.size ] = node;
}
function prioritize_colorCoded_nodes( team, colorCode, color )
{
nodes = level.arrays_of_colorCoded_nodes[ team ][ colorCode ];
// need a place to store the nodes externally so we can put the pathnodes in the back
ent = spawnstruct();
ent.path_nodes = [];
ent.cover_nodes_first = [];
ent.cover_nodes_last = [];
lastColorForced_exists = IsDefined( level.lastColorForced[ team ][ color ] );
// fills ent.path_nodes or .cover_nodes depending on node type
for( i=0 ; i < nodes.size; i++ )
{
node = nodes[ i ];
ent [ [ level.color_node_type_function[ node.type ][ lastColorForced_exists ][ team ] ] ]( node, level.lastColorForced[ team ][ color ] );
}
ent.cover_nodes_first = array::randomize( ent.cover_nodes_first );
nodes = ent.cover_nodes_first;
// put the path nodes at the end of the array so they're less favored
for( i=0; i < ent.cover_nodes_last.size; i++ )
{
nodes[ nodes.size ] = ent.cover_nodes_last[ i ];
}
for( i=0; i < ent.path_nodes.size; i++ )
{
nodes[ nodes.size ] = ent.path_nodes[ i ];
}
level.arrays_of_colorCoded_nodes[ team ][ colorCode ] = nodes;
}
function get_prioritized_colorCoded_nodes( team, colorCode, color )
{
if ( IsDefined( level.arrays_of_colorCoded_nodes[ team ][ colorCode ] ) )
return level.arrays_of_colorCoded_nodes[ team ][ colorCode ];
if ( IsDefined( level.colorCoded_volumes[ team ][ colorCode ] ) )
return level.colorCoded_volumes[ team ][ colorCode ];
}
function issue_leave_node_order_to_ai_and_get_ai( colorCode, color, team )
{
// remove dead from this specific colorCode
level.arrays_of_colorCoded_ai[ team ][ colorCode ] = array::remove_dead( level.arrays_of_colorCoded_ai[ team ][ colorCode ] );
ai = level.arrays_of_colorCoded_ai[ team ][ colorCode ];
ai = ArrayCombine( ai, level.arrays_of_colorForced_ai[ team ][ color ], true, false );
newArray = [];
for( i=0;i<ai.size;i++ )
{
// ignore AI that are already going to this colorCode
if( IsDefined( ai[ i ].currentColorCode ) && ai[ i ].currentColorCode == colorCode )
{
continue;
}
newArray[ newArray.size ] = ai[ i ];
}
ai = newArray;
if( !ai.size )
{
return;
}
for( i=0; i < ai.size; i++ )
{
ai[ i ] left_color_node();
}
return ai;
}
function issue_color_order_to_ai( colorCode, color, team, ai )
{
original_ai_array = ai;
prioritize_colorCoded_nodes( team, colorCode, color );
nodes = get_prioritized_colorCoded_nodes( team, colorCode, color );
/#
level.colorNodes_debug_array[ team ][ colorCode ] = nodes;
#/
/#
if( nodes.size < ai.size )
{
println( "^3Warning, ColorNumber system tried to make " + ai.size + " AI go to " + nodes.size + " nodes." );
}
#/
counter = 0;
ai_count = ai.size;
for( i=0; i < nodes.size; i++ )
{
node = nodes[ i ];
// add guys to the nodes with the fewest AI on them
if( isalive( node.color_user ) )
{
continue;
}
closestAI = ArraySort( ai, node.origin, true, 1 )[0];
assert( isalive( closestAI ) );
ArrayRemoveValue( ai, closestAI );
closestAI take_color_node( node, colorCode, self, counter );
counter++;
if( !ai.size )
{
return;
}
}
}
function take_color_node( node, colorCode, trigger, counter )
{
self notify( "stop_color_move" );
self.script_careful = true;
self.currentColorCode = colorCode;
self thread process_color_order_to_ai( node, trigger, counter );
}
function player_color_node()
{
// detect if the player gets a node and set its .color_user
for( ;; )
{
playerNode = undefined;
if( !IsDefined( self.node ) )
{
{wait(.05);};
continue;
}
olduser = self.node.color_user;
playerNode = self.node;
playerNode.color_user = self;
for( ;; )
{
if( !IsDefined( self.node ) )
{
break;
}
if( self.node != playerNode )
{
break;
}
{wait(.05);};
}
playerNode.color_user = undefined;
playerNode color_node_finds_a_user();
}
}
function color_node_finds_a_user()
{
if ( IsDefined( self.script_color_allies ) )
{
color_node_finds_user_from_colorcodes( self.script_color_allies, "allies" );
}
if ( IsDefined( self.script_color_axis ) )
{
color_node_finds_user_from_colorcodes( self.script_color_axis, "axis" );
}
}
function color_node_finds_user_from_colorcodes( colorCodeString, team )
{
if ( IsDefined( self.color_user ) )
{
// If we successfully found a guy for this node then we shouldnt go find another
return;
}
colorCodes = strtok( colorCodeString, " " );
array::thread_all_ents( colorCodes,&color_node_finds_user_for_colorCode, team );
}
function color_node_finds_user_for_colorCode( colorCode, team )
{
color = colorCode[ 0 ];
assert( colorIsLegit( color ), "Color " + color + " is not legit" );
if ( !IsDefined( level.currentColorForced[ team ][ color ] ) )
{
// AI of this color are not assigned to a colornode currently
return;
}
if ( level.currentColorForced[ team ][ color ] != colorCode )
{
// AI of this color are not currently assigned to our colorCode
return;
}
ai = get_force_color_guys( team, color );
if ( !ai.size )
{
return;
}
for ( i = 0; i < ai.size; i++ )
{
guy = ai[ i ];
if ( guy occupies_colorCode( colorCode ) )
{
continue;
}
// found a guy that should use this node, so assign and get out
guy take_color_node( self, colorCode );
return;
}
}
function occupies_colorCode( colorCode )
{
if ( !IsDefined( self.currentColorCode ) )
{
return false;
}
return self.currentColorCode == colorCode;
}
function ai_sets_goal_with_delay( node )
{
self endon( "death" );
delay = my_current_node_delays();
if ( delay )
{
wait( delay );
}
ai_sets_goal( node );
}
function ai_sets_goal( node )
{
// makes AI stop trying to run to their chain of nodes in _spawner go_to_node
self notify( "stop_going_to_node" );
set_goal_and_volume( node );
volume = level.colorCoded_volumes[ self.team ][ self.currentColorCode ];
// SUMEET TODO - Do we need support for careful logic anymore?
//if ( IS_TRUE( self.script_careful ) )
//{
// thread careful_logic( node, volume );
//}
}
function set_goal_and_volume( node )
{
if ( IsDefined( self._colors_go_line ) )
{
self notify( "colors_go_line_done" );
self._colors_go_line = undefined;
}
if( ( isdefined( node.radius ) && node.radius ) )
{
self.goalradius = node.radius;
}
if ( ( isdefined( node.script_forcegoal ) && node.script_forcegoal ) )
{
self thread color_force_goal(node);
}
else
{
self SetGoal( node );
}
volume = level.colorCoded_volumes[ self.team ][ self.currentColorCode ];
if ( IsDefined( volume ) )
{
self SetGoal( volume );
}
else
{
self ClearFixedNodeSafeVolume();
}
if( IsDefined( node.fixedNodeSafeRadius ) )
{
self.fixedNodeSafeRadius = node.fixedNodeSafeRadius;
}
else
{
self.fixedNodeSafeRadius = 64;
}
}
function color_force_goal(node)
{
self endon("death");
self thread ai::force_goal( node, undefined, true, "stop_color_forcegoal", true );
self util::waittill_either( "goal", "stop_color_move" );
self notify( "stop_color_forcegoal" );
}
function careful_logic( node, volume )
{
self endon( "death" );
self endon( "stop_being_careful" );
self endon( "stop_going_to_node" );
thread recover_from_careful_disable( node );
for ( ;; )
{
wait_until_an_enemy_is_in_safe_area( node, volume );
use_big_goal_until_goal_is_safe( node, volume );
set_goal_and_volume( node );
}
}
function recover_from_careful_disable( node )
{
self endon( "death" );
self endon( "stop_going_to_node" );
self waittill( "stop_being_careful" );
set_goal_and_volume( node );
}
function use_big_goal_until_goal_is_safe( node, volume )
{
self.goalradius = 1024;
self SetGoal( self.origin );
if ( IsDefined( volume ) )
{
for ( ;; )
{
wait( 1 );
if ( self isKnownEnemyInRadius( node.origin, self.fixedNodeSafeRadius ) )
{
continue;
}
if ( self isKnownEnemyInVolume( volume ) )
{
continue;
}
return;
}
}
else
{
for ( ;; )
{
if ( !( self isKnownEnemyInRadius( node.origin, self.fixedNodeSafeRadius ) ) )
{
return;
}
wait( 1 );
}
}
}
function wait_until_an_enemy_is_in_safe_area( node, volume )
{
if ( IsDefined( volume ) )
{
for ( ;; )
{
if ( self IsKnownEnemyInRadius( node.origin, self.fixedNodeSafeRadius ) )
{
return;
}
if ( self IsKnownEnemyInVolume( volume ) )
{
return;
}
wait( 1 );
}
}
else
{
for ( ;; )
{
if ( self IsKnownEnemyInRadius( node.origin, self.fixedNodeSafeRadius ) )
{
return;
}
wait( 1 );
}
}
}
function my_current_node_delays()
{
if ( !IsDefined( self.node ) )
{
return false;
}
return self.node util::script_delay();
}
function process_color_order_to_ai( node, trigger, counter )
{
thread decrementColorUsers( node );
self endon( "stop_color_move" );
self endon( "death" );
if ( IsDefined( trigger ) )
{
trigger util::script_delay();
}
// wait if need to wait on a flag
if( IsDefined( trigger ) )
{
if( IsDefined( trigger.script_flag_wait ) )
level flag::wait_till( trigger.script_flag_wait );
}
if ( !my_current_node_delays() )
{
if ( IsDefined( counter ) )
{
wait( counter * RandomFloatRange( 0.2, 0.35 ) );
}
}
self ai_sets_goal( node );
// record the node so the guy can find out who has his node, and get that guys
self.color_ordered_node_assignment = node;
for( ;; )
{
self waittill( "node_taken", taker );
if( taker == self )
{
// give time for the player to claim the node
{wait(.05);};
}
// lost our node so try to get a new one
node = get_best_available_new_colored_node();
if( IsDefined( node ) )
{
assert( !isalive( node.color_user ), "Node already had color user!" );
if( isalive( self.color_node.color_user ) && self.color_node.color_user == self )
{
self.color_node.color_user = undefined;
}
self.color_node = node;
node.color_user = self;
self ai_sets_goal( node );
}
}
}
function get_best_available_colored_node()
{
assert( self.team != "neutral" );
assert( IsDefined( self.script_forceColor ), "AI with export " + self.export + " lost his script_forcecolor.. somehow." );
colorCode = level.currentColorForced[ self.team ][ self.script_forceColor ];
nodes = get_prioritized_colorCoded_nodes( self.team, colorCode, self.script_forcecolor );
assert( nodes.size > 0, "Tried to make guy with export " + self.export + " go to forcecolor " + self.script_forceColor + " but there are no nodes of that color enabled" );
for( i=0; i < nodes.size; i++ )
{
if( !isalive( nodes[ i ].color_user ) )
{
return nodes[ i ];
}
}
}
function get_best_available_new_colored_node()
{
assert( self.team != "neutral" );
assert( IsDefined( self.script_forceColor ), "AI with export " + self.export + " lost his script_forcecolor.. somehow." );
colorCode = level.currentColorForced[ self.team ][ self.script_forceColor ];
nodes = get_prioritized_colorCoded_nodes( self.team, colorCode, self.script_forcecolor );
assert( nodes.size > 0, "Tried to make guy with export " + self.export + " go to forcecolor " + self.script_forceColor + " but there are no nodes of that color enabled" );
nodes = ArraySort( nodes, self.origin );
for( i=0; i < nodes.size; i++ )
{
if( !isalive( nodes[ i ].color_user ) )
{
return nodes[ i ];
}
}
}
function process_stop_short_of_node( node )
{
self endon( "stopScript" );
self endon( "death" );
if( IsDefined( self.node ) )
{
return;
}
// first check to see if we're right near it
if( distancesquared( node.origin, self.origin ) < 32*32 )
{
reached_node_but_could_not_claim_it( node );
return;
}
// if we're far away, maybe somebody cut us off then took our node, now we're stuck in limbo
// so wait one second, if we're still in stop script( ie no killanimscripts ) then push the guy
// off the node
currentTime = gettime();
wait_for_killanimscript_or_time( 1 );
newTime = gettime();
// did we break out of stop fast enough to indicate we continued moving? If not, then reclaim the node
if( newTime - currentTime >= 1000 )
{
reached_node_but_could_not_claim_it( node );
}
}
function wait_for_killanimscript_or_time( timer )
{
self endon( "killanimscript" );
wait( timer );
}
function reached_node_but_could_not_claim_it( node )
{
ai = GetAIArray();
for( i=0;i<ai.size;i++ )
{
if( !IsDefined( ai[ i ].node ) )
{
continue;
}
if( ai[ i ].node != node )
{
continue;
}
ai[ i ] notify( "eject_from_my_node" );
wait( 1 );
self notify( "eject_from_my_node" );
return true;
}
return false;
}
function decrementColorUsers( node )
{
node.color_user = self;
self.color_node = node;
/#
self.color_node_debug_val = true;
#/
self endon( "stop_color_move" );
self waittill( "death" );
self.color_node.color_user = undefined;
}
function colorIsLegit( color )
{
for( i = 0; i < level.colorList.size; i++ )
{
if( color == level.colorList[ i ] )
{
return true;
}
}
return false;
}
function add_volume_to_global_arrays( colorCode, team )
{
colors = strtok( colorCode, " " );
for( p = 0; p < colors.size; p++ )
{
assert( !IsDefined( level.colorCoded_volumes[ team ][ colors[ p ] ] ), "Multiple info_volumes exist with color code " + colors[ p ] );
level.colorCoded_volumes[ team ][ colors[ p ] ] = self;
}
}
function add_node_to_global_arrays( colorCode, team )
{
self.color_user = undefined;
colors = strtok( colorCode, " " );
for( p = 0; p < colors.size; p++ )
{
if( IsDefined( level.arrays_of_colorCoded_nodes[ team ] ) && IsDefined( level.arrays_of_colorCoded_nodes[ team ][ colors[ p ] ] ) )
{
// array already exists so add this color coded node to that color code array.
if ( !isdefined( level.arrays_of_colorCoded_nodes[ team ][ colors[ p ] ] ) ) level.arrays_of_colorCoded_nodes[ team ][ colors[ p ] ] = []; else if ( !IsArray( level.arrays_of_colorCoded_nodes[ team ][ colors[ p ] ] ) ) level.arrays_of_colorCoded_nodes[ team ][ colors[ p ] ] = array( level.arrays_of_colorCoded_nodes[ team ][ colors[ p ] ] ); level.arrays_of_colorCoded_nodes[ team ][ colors[ p ] ][level.arrays_of_colorCoded_nodes[ team ][ colors[ p ] ].size]=self;;
continue;
}
// array doesn't exist so we have to initialize all the variables related to this color coding.
level.arrays_of_colorCoded_nodes[ team ][ colors[ p ] ][ 0 ] = self;
level.arrays_of_colorCoded_ai[ team ][ colors[ p ] ] = [];
level.arrays_of_colorCoded_spawners[ team ][ colors[ p ] ] = [];
}
}
function left_color_node()
{
/#
self.color_node_debug_val = undefined;
#/
if( !IsDefined( self.color_node ) )
{
return;
}
if( IsDefined( self.color_node.color_user ) && self.color_node.color_user == self )
{
self.color_node.color_user = undefined;
}
self.color_node = undefined;
self notify( "stop_color_move" );
}
function GetColorNumberArray()
{
array = [];
if( issubstr( self.classname, "axis" ) || issubstr( self.classname, "enemy" ) )
{
array[ "team" ] = "axis";
array[ "colorTeam" ] = self.script_color_axis;
}
if(( issubstr( self.classname, "ally" ) ) ||( issubstr( self.classname, "civilian" ) ) )
{
array[ "team" ] = "allies";
array[ "colorTeam" ] = self.script_color_allies;
}
if( !IsDefined( array[ "colorTeam" ] ) )
{
array = undefined;
}
return array;
}
function removeSpawnerFromColorNumberArray()
{
colorNumberArray = GetColorNumberArray();
if( !IsDefined( colorNumberArray ) )
{
return;
}
team = colorNumberArray[ "team" ];
colorTeam = colorNumberArray[ "colorTeam" ];
// remove this spawner from any array it was in
colors = strtok( colorTeam, " " );
for( i=0;i<colors.size;i++ )
{
ArrayRemoveValue( level.arrays_of_colorCoded_spawners[ team ][ colors[ i ] ], self );
}
}
function add_cover_node( type )
{
level.color_node_type_function[ type ][ true ][ "allies" ] =&process_cover_node_with_last_in_mind_allies;
level.color_node_type_function[ type ][ true ][ "axis" ] =&process_cover_node_with_last_in_mind_axis;
level.color_node_type_function[ type ][ false ][ "allies" ] =&process_cover_node;
level.color_node_type_function[ type ][ false ][ "axis" ] =&process_cover_node;
}
function add_path_node( type )
{
level.color_node_type_function[ type ][ true ][ "allies" ] =&process_path_node;
level.color_node_type_function[ type ][ false ][ "allies" ] =&process_path_node;
level.color_node_type_function[ type ][ true ][ "axis" ] =&process_path_node;
level.color_node_type_function[ type ][ false ][ "axis" ] =&process_path_node;
}
// ColorNode respawn system
function colorNode_spawn_reinforcement( classname, fromColor )
{
level endon( "kill_color_replacements" );
friendly_spawners_type = getClassColorHash(classname, fromColor);
while(level.friendly_spawners_types[friendly_spawners_type] > 0)
{
spawn = undefined;
for( ;; )
{
if( !level flag::get( "respawn_friendlies" ) )
{
if( !IsDefined( level.friendly_respawn_vision_checker_thread ) )
thread friendly_spawner_vision_checker();
// have to break if respawn_friendlies gets enabled because that disables the
// fov check that toggles player_looks_away_from_spawner.
for ( ;; )
{
level flag::wait_till_any( array( "player_looks_away_from_spawner", "respawn_friendlies" ) );
level flag::wait_till_clear( "friendly_spawner_locked" );
if ( level flag::get( "player_looks_away_from_spawner" ) || level flag::get( "respawn_friendlies" ) )
{
break;
}
}
level flag::set( "friendly_spawner_locked" );
}
spawner = get_color_spawner( classname, fromColor );
spawner.count = 1;
level.friendly_spawners_types[friendly_spawners_type] = level.friendly_spawners_types[friendly_spawners_type] - 1;
spawner util::script_wait();
spawn = spawner spawner::spawn();
if( spawner::spawn_failed( spawn ) )
{
thread lock_spawner_for_awhile();
wait( 1 );
continue;
}
level notify( "reinforcement_spawned", spawn );
break;
}
// figure out which color the spawned guy should be
for( ;; )
{
if( !IsDefined( fromColor ) )
{
break;
}
if( get_color_from_order( fromColor, level.current_color_order ) == "none" )
{
break;
}
fromColor = level.current_color_order[ fromColor ];
}
if( IsDefined( fromColor ) )
{
spawn set_force_color( fromColor );
}
thread lock_spawner_for_awhile();
if( IsDefined( level.friendly_startup_thread ) )
{
spawn thread [ [ level.friendly_startup_thread ] ]();
}
spawn thread colorNode_replace_on_death();
}
}
function colorNode_replace_on_death()
{
level endon( "kill_color_replacements" );
assert( isalive( self ), "Tried to do replace on death on something that was not alive" );
self endon( "_disable_reinforcement" );
if( self.team == "axis" )
{
return;
}
if ( IsDefined( self.replace_on_death ) )
{
return;
}
self.replace_on_death = true;
assert( !IsDefined( self.respawn_on_death ), "Guy with export " + self.export + " tried to run respawn on death twice." );
// when a red or green guy dies, an orange guy becomes a red guy
// when an orange guy dies, a yellow guy becomes an orange guy
classname = self.classname;
color = self.script_forceColor;
// if we spawn a new guy with spawn_reinforcement, he needs to get his color assignment before he checks his forcecolor
waittillframeend;
if( isalive( self ) )
{
// could've died in waittillframeend
self waittill( "death" );
}
color_order = level.current_color_order;
if( !IsDefined( self.script_forceColor ) )
{
return;
}
//Create only 1 respawn thread per class/color
friendly_spawners_type = getClassColorHash(classname, self.script_forceColor);
if(!isdefined(level.friendly_spawners_types) || !isdefined(level.friendly_spawners_types[friendly_spawners_type])
|| level.friendly_spawners_types[friendly_spawners_type] <= 0)
{
level.friendly_spawners_types[friendly_spawners_type] = 1;
// spawn a replacement yellow guy
thread colorNode_spawn_reinforcement( classname, self.script_forceColor );
}
else
{
level.friendly_spawners_types[friendly_spawners_type] = level.friendly_spawners_types[friendly_spawners_type] + 1;
}
if( IsDefined( self ) && IsDefined( self.script_forceColor ) )
{
color = self.script_forceColor;
}
if( IsDefined( self ) && IsDefined( self.origin ) )
{
origin = self.origin;
}
// a replacement has been spawned, so now promote somebody to our color
for( ;; )
{
if( get_color_from_order( color, color_order ) == "none" )
{
return;
}
correct_colored_friendlies = get_force_color_guys( "allies", color_order[ color ] );
//correct_colored_friendlies = remove_heroes( correct_colored_friendlies );//TODO T7 - bring over hero system if we need it
correct_colored_friendlies = array::filter_classname( correct_colored_friendlies, true, classname );
if( !correct_colored_friendlies.size )
{
// nobody of the correct color existed, so give them more time to spawn
wait( 2 );
continue;
}
players = GetPlayers();
//correct_colored_guy = _utility::getClosest( players[0].origin, correct_colored_friendlies );
correct_colored_guy = ArraySort( correct_colored_friendlies, players[0].origin, 1 )[0];
assert( correct_colored_guy.script_forceColor != color, "Tried to replace a " + color + " guy with a guy of the same color!" );
// have to wait until the end of the frame because the guy may have just spawned and been given his forcecolor,
// and you cant give a guy forcecolor twice in one frame currently.
waittillframeend;
if( !isalive( correct_colored_guy ) )
{
// if he died during the frame then try again!
continue;
}
correct_colored_guy set_force_color( color );
// should something special happen when a guy is promoted? Like a change in threatbias group?
if( IsDefined( level.friendly_promotion_thread ) )
{
correct_colored_guy [ [ level.friendly_promotion_thread ] ]( color );
}
color = color_order[ color ];
}
}
function get_color_from_order( color, color_order )
{
if( !IsDefined( color ) )
{
return "none";
}
if( !IsDefined( color_order ) )
{
return "none";
}
if( !IsDefined( color_order[ color ] ) )
{
return "none";
}
return color_order[ color ];
}
function friendly_spawner_vision_checker()
{
level.friendly_respawn_vision_checker_thread = true;
// checks to see if the player is looking at the friendly spawner
successes = 0;
for( ;; )
{
level flag::wait_till_clear( "respawn_friendlies" );
wait( 1 );
// friendly_respawn is disabled but if the player is far enough away and looking away
// from the spawner then we can still spawn from it.
if( !IsDefined( level.respawn_spawner ) )
{
continue;
}
spawner = level.respawn_spawner;
players = GetPlayers();
player_sees_spawner = false;
for( q = 0; q < players.size; q++ )
{
difference_vec = players[q].origin - spawner.origin;
if( length( difference_vec ) < 200 )
{
player_sees_spawner();
player_sees_spawner = true;
break;
}
forward = anglesToForward(( 0, players[q] getplayerangles()[ 1 ], 0 ) );
difference = vectornormalize( difference_vec );
dot = vectordot( forward, difference );
if( dot < 0.2 )
{
player_sees_spawner();
player_sees_spawner = true;
break;
}
successes++;
if( successes < 3 )
{
continue;
}
}
if( player_sees_spawner )
{
continue;
}
// player has been looking away for 3 seconds
level flag::set( "player_looks_away_from_spawner" );
}
}
function get_color_spawner( classname, fromColor )
{
// make sure we don't assume that this array is defined!
specificFromColor = false;
if( IsDefined( level.respawn_spawners_specific ) && IsDefined( level.respawn_spawners_specific[fromColor] ) )
{
specificFromColor = true;
}
if( !IsDefined( level.respawn_spawner ) )
{
// make sure we're not using color-specific respawners instead
if( !IsDefined( fromColor ) || !specificFromColor )
{
ASSERTMSG( "Tried to spawn a guy but neither level.respawn_spawner or level.respawn_spawners_specific is defined. Either set it to a spawner or use targetname trigger_friendly_respawn triggers. HINT: has the player hit a friendly_respawn_trigger for ALL allied color groups in the map by the time the player has reached this point?" );
}
}
// if the classname is not set, just use the global respawn spawner
if( !IsDefined( classname ) )
{
if( IsDefined( fromColor ) && specificFromColor )
{
return level.respawn_spawners_specific[fromColor];
}
else
{
return level.respawn_spawner;
}
}
spawners = GetEntArray( "color_spawner", "targetname" );
class_spawners = [];
for( i=0; i < spawners.size; i++ )
{
class_spawners[ spawners[ i ].classname ] = spawners[ i ];
}
// find the spawner that has the supplied classname as a substr
spawner = undefined;
keys = getarraykeys( class_spawners );
for( i=0; i < keys.size; i++ )
{
if( !issubstr( class_spawners[ keys[ i ] ].classname, classname ) )
{
continue;
}
spawner = class_spawners[ keys[ i ] ];
break;
}
// spawner = class_spawners[ classname ];
if( !IsDefined( spawner ) )
{
if( IsDefined( fromColor ) && specificFromColor )
{
return level.respawn_spawners_specific[fromColor];
}
else
{
return level.respawn_spawner;
}
}
if( IsDefined( fromColor ) && specificFromColor )
{
spawner.origin = level.respawn_spawners_specific[fromColor].origin;
}
else
{
spawner.origin = level.respawn_spawner.origin;
}
return spawner;
}
function getClassColorHash(classname, fromcolor )
{
//Create only 1 respawn thread per class/color
classColorHash = classname;
if(isdefined(fromcolor))
{
classColorHash += "##" + fromcolor;
}
return classColorHash;
}
function lock_spawner_for_awhile()
{
level flag::set( "friendly_spawner_locked" );
wait( 2 );
level flag::clear( "friendly_spawner_locked" );
}
function player_sees_spawner()
{
level flag::clear( "player_looks_away_from_spawner" );
}
function kill_color_replacements()
{
// kills ALL color respawning
level flag::clear( "friendly_spawner_locked" );
level notify( "kill_color_replacements" );
level.friendly_spawners_types = undefined;
ai = GetAIArray();
array::thread_all( ai,&remove_replace_on_death );
}
function remove_replace_on_death()
{
self.replace_on_death = undefined;
}
/@
"Name: set_force_color( <_color> )"
"Summary: Sets a guy's force color"
"Module: Color"
"CallOn: An AI"
"Example: guy set_force_color( "p" );"
"SPMP: singleplayer"
@/
function set_force_color( _color )
{
// shorten and lowercase the ai's forcecolor to a single letter
color = shortenColor( _color );
assert( colorIsLegit( color ), "Tried to set force color on an undefined color: " + color );
if( !IsActor( self ) )
{
set_force_color_spawner( color );
return;
}
assert( isalive( self ), "Tried to set force color on a dead / undefined entity." );
self.fixedNodeSafeRadius = 64;
self.script_color_axis = undefined;
self.script_color_allies = undefined;
self.old_forcecolor = undefined;
if( IsDefined( self.script_forcecolor ) )
{
// first remove the guy from the force color array he used to belong to
ArrayRemoveValue( level.arrays_of_colorForced_ai[ self.team ][ self.script_forcecolor ], self );
}
self.script_forceColor = color;
// get added to the new array of AI that are forced to this color
if ( !isdefined( level.arrays_of_colorForced_ai[ self.team ][ self.script_forceColor ] ) ) level.arrays_of_colorForced_ai[ self.team ][ self.script_forceColor ] = []; else if ( !IsArray( level.arrays_of_colorForced_ai[ self.team ][ self.script_forceColor ] ) ) level.arrays_of_colorForced_ai[ self.team ][ self.script_forceColor ] = array( level.arrays_of_colorForced_ai[ self.team ][ self.script_forceColor ] ); level.arrays_of_colorForced_ai[ self.team ][ self.script_forceColor ][level.arrays_of_colorForced_ai[ self.team ][ self.script_forceColor ].size]=self;;
level thread remove_colorForced_ai_when_dead( self );
// set it here so that he continues in script as the correct color
self thread new_color_being_set( color );
}
function remove_colorForced_ai_when_dead( ai )
{
script_forceColor = ai.script_forceColor;
team = ai.team;
ai waittill( "death" );
level.arrays_of_colorForced_ai[ team ][ script_forceColor ] = array::remove_undefined( level.arrays_of_colorForced_ai[ team ][ script_forceColor ] );
}
function shortenColor( color )
{
Assert( IsDefined( level.colorCheckList[ ToLower( color ) ] ), "Tried to set force color on an undefined color: " + color );
return level.colorCheckList[ ToLower( color ) ];
}
function set_force_color_spawner( color )
{
self.script_forceColor = color;
self.old_forceColor = undefined;
}
function new_color_being_set( color )
{
self notify( "new_color_being_set" );
self.new_force_color_being_set = true;
left_color_node();
self endon( "new_color_being_set" );
self endon( "death" );
// insure we're only getting one color change, multiple in one frame will get overwritten.
waittillframeend;
waittillframeend;
if ( IsDefined( self.script_forceColor ) )
{
// grab the current colorCode that AI of this color are forced to, if there is one
self.currentColorCode = level.currentColorForced[ self.team ][ self.script_forceColor ];
self thread goto_current_ColorIndex();
}
self.new_force_color_being_set = undefined;
self notify( "done_setting_new_color" );
/#
update_debug_friendlycolor();
#/
}
function update_debug_friendlycolor_on_death()
{
self notify( "debug_color_update" );
self endon( "debug_color_update" );
self waittill( "death" );
/#
a_keys = GetArrayKeys( level.debug_color_friendlies );
foreach( n_key in a_keys )
{
ai = GetEntByNum( n_key );
if ( !IsAlive( ai ) )
{
ArrayRemoveIndex( level.debug_color_friendlies, n_key, true );
}
}
#/
// updates the debug color friendlies info
level notify( "updated_color_friendlies" );
}
function update_debug_friendlycolor()
{
self thread update_debug_friendlycolor_on_death();
if ( isdefined( self.script_forceColor ) )
{
level.debug_color_friendlies[ self GetEntityNumber() ] = self.script_forceColor;
}
else
{
level.debug_color_friendlies[ self GetEntityNumber() ] = undefined;
}
// updates the debug color friendlies info
level notify( "updated_color_friendlies" );
}
function has_color()
{
// can lose color during the waittillframeend in left_color_node
if ( self.team == "axis" )
{
return IsDefined( self.script_color_axis ) || IsDefined( self.script_forceColor );
}
return IsDefined( self.script_color_allies ) || IsDefined( self.script_forceColor );
}
/@
"Name: get_force_color()"
"Summary: Returns a guy's force color"
"Module: Color"
"CallOn: An AI"
"Example: color = guy get_force_color()"
"SPMP: singleplayer"
@/
function get_force_color()
{
color = self.script_forceColor;
return color;
}
/@
"Name: get_force_color_guys( <team>, <color> )"
"Summary: Returns all alive ai of a certain force color."
"Module: AI"
"CallOn: "
"Example: red_guys = get_force_color_guys( "allies", "r" );"
"MandatoryArg: <team> : the team of the guys to check"
"MandatoryArg: <color> : the color value of the guys you want to collect"
"SPMP: singleplayer"
@/
function get_force_color_guys( team, color )
{
ai = GetAITeamArray( team );
guys = [];
for( i = 0; i < ai.size; i++ )
{
guy = ai[ i ];
if( !IsDefined( guy.script_forceColor ) )
{
continue;
}
if( guy.script_forceColor != color )
{
continue;
}
guys[ guys.size ] = guy;
}
return guys;
}
function get_all_force_color_friendlies()
{
ai = GetAITeamArray( "allies" );
guys = [];
for( i = 0; i < ai.size; i++ )
{
guy = ai[ i ];
if( !IsDefined( guy.script_forceColor ) )
{
continue;
}
guys[ guys.size ] = guy;
}
return guys;
}
/@
"Name: disable()"
"Summary: disables an ai's force color. Essentially takes him off the color chain."
"Module: Color"
"CallOn: An AI"
"Example: guy colors::disable();"
"SPMP: singleplayer"
@/
function disable( stop_being_careful )
{
if( IsDefined( self.new_force_color_being_set ) )
{
self endon( "death" );
// setting force color happens after waittillframeend so we need to wait until it finishes
// setting before we disable it, so a set followed by a disable will send the guy to a node.
self waittill( "done_setting_new_color" );
}
// SUMEET_TODO - AI should stop going to node/running careful right when color is disabled.
// Added this late on Black Ops2 ( 08/10/2012 ) hence not making it global. Might introduce issues
// on other maps.
if( ( isdefined( stop_being_careful ) && stop_being_careful ) )
{
self notify( "stop_going_to_node" );
self notify( "stop_being_careful" );
}
self clearFixedNodeSafeVolume();
// any color on this guy?
if( !IsDefined( self.script_forceColor ) )
{
return;
}
assert( !IsDefined( self.old_forcecolor ), "Tried to disable forcecolor on a guy that somehow had a old_forcecolor already. Investigate!!!" );
self.old_forceColor = self.script_forceColor;
// first remove the guy from the force color array he used to belong to
ArrayRemoveValue( level.arrays_of_colorForced_ai[ self.team ][ self.script_forcecolor ], self );
// self _colors::removeAIFromColorNumberArray();
left_color_node();
self.script_forceColor = undefined;
self.currentColorCode = undefined;
/#
update_debug_friendlycolor();
#/
}
/@
"Name: enable()"
"Summary: Re-enables an ai's force color. Only works on guys that have had a forceColor set previously."
"Module: Color"
"CallOn: An AI"
"Example: guy colors::enable();"
"SPMP: singleplayer"
@/
function enable()
{
if ( IsDefined( self.script_forceColor ) )
{
return;
}
if ( !IsDefined( self.old_forceColor ) )
{
return;
}
set_force_color( self.old_forcecolor );
self.old_forceColor = undefined;
}
function is_color_ai()
{
return ( isdefined( self.script_forcecolor ) || isdefined( self.old_forcecolor ) );
}
/#
function insure_player_does_not_set_forcecolor_twice_in_one_frame()
{
assert( !IsDefined( self.setforcecolor ), "Tried to set forceColor on an ai twice in one frame. Don't spam set_force_color." );
self.setforcecolor = true;
waittillframeend;
if ( !isalive( self ) )
{
return;
}
self.setforcecolor = undefined;
}
#/