boiii-scripts/mp/killstreaks/_airsupport.gsc
2023-04-13 17:30:38 +02:00

1290 lines
35 KiB
Plaintext

#using scripts\codescripts\struct;
#using scripts\shared\challenges_shared;
#using scripts\shared\damagefeedback_shared;
#using scripts\shared\math_shared;
#using scripts\shared\util_shared;
#using scripts\shared\weapons\_weaponobjects;
#using scripts\shared\weapons\_weapons;
#using scripts\mp\_util;
#namespace airsupport;
// some common functions between all the air kill streaks
function init()
{
if ( !isdefined( level.airsupportHeightScale ) )
level.airsupportHeightScale = 1;
level.airsupportHeightScale = GetDvarInt( "scr_airsupportHeightScale", level.airsupportHeightScale );
level.noFlyZones = [];
level.noFlyZones = GetEntArray("no_fly_zone","targetname");
airsupport_heights = struct::get_array("air_support_height","targetname");
/#
if ( airsupport_heights.size > 1 )
{
util::error( "Found more then one 'air_support_height' structs in the map" );
}
#/
airsupport_heights = GetEntArray("air_support_height","targetname");
/#
if ( airsupport_heights.size > 0 )
{
util::error( "Found an entity in the map with an 'air_support_height' targetname. There should be only structs." );
}
#/
heli_height_meshes = GetEntArray("heli_height_lock","classname");
/#
if ( heli_height_meshes.size > 1 )
{
util::error( "Found more then one 'heli_height_lock' classname in the map" );
}
#/
InitRotatingRig();
}
function finishHardpointLocationUsage( location, usedCallback )
{
self notify( "used" );
{wait(.05);};
if( isdefined( usedCallback ) )
{
return self [[usedCallback]]( location );
}
return true;
}
function finishDualHardpointLocationUsage( locationStart, locationEnd, usedCallback )
{
self notify( "used" );
{wait(.05);};
return self [[usedCallback]]( locationStart, locationEnd );
}
function endSelectionOnGameEnd()
{
self endon( "death" );
self endon( "disconnect" );
self endon( "cancel_location" );
self endon( "used" );
self endon( "host_migration_begin" );
level waittill( "game_ended" );
self notify( "game_ended" );
}
function endSelectionOnHostMigration()
{
self endon( "death" );
self endon( "disconnect" );
self endon( "cancel_location" );
self endon( "used" );
self endon( "game_ended" );
level waittill( "host_migration_begin" );
self notify( "cancel_location" );
}
function endSelectionThink()
{
assert( IsPlayer( self ) );
assert( IsAlive( self ) );
assert( isdefined( self.selectingLocation ) );
assert( self.selectingLocation == true );
self thread endSelectionOnGameEnd();
self thread endSelectionOnHostMigration();
event = self util::waittill_any_return( "death", "disconnect", "cancel_location", "game_ended", "used", "weapon_change", "emp_jammed" );
if ( event != "disconnect" )
{
self.selectingLocation = undefined;
self thread clearUpLocationSelection();
}
if ( event != "used" )
{
// wake threads waiting for locations
self notify( "confirm_location", undefined, undefined );
}
}
function clearUpLocationSelection()
{
event = self util::waittill_any_return( "death", "disconnect", "game_ended", "used", "weapon_change", "emp_jammed", "weapon_change_complete" );
if ( event != "disconnect" )
{
self endLocationSelection();
}
}
function stopLoopSoundAfterTime( time )
{
self endon ( "death" );
wait ( time );
self stoploopsound( 2 );
}
function calculateFallTime( flyHeight )
{
// this is the value that code uses
gravity = GetDvarint( "bg_gravity" );
time = sqrt( (2 * flyHeight) / gravity );
return time;
}
function calculateReleaseTime( flyTime, flyHeight, flySpeed, bombSpeedScale )
{
falltime = calculateFallTime( flyHeight );
// bomb horizontal velocity is not the same as the plane speed so we need to take this
// into account when calculating the bomb time
bomb_x = (flySpeed * bombSpeedScale) * falltime;
release_time = bomb_x / flySpeed;
return ( (flyTime * 0.5) - release_time);
}
function getMinimumFlyHeight()
{
airsupport_height = struct::get( "air_support_height", "targetname");
if ( isdefined(airsupport_height) )
{
planeFlyHeight = airsupport_height.origin[2];
}
else
{
/#
PrintLn("WARNING: Missing air_support_height entity in the map. Using default height.");
#/
// original system
planeFlyHeight = 850;
if ( isdefined( level.airsupportHeightScale ) )
{
level.airsupportHeightScale = GetDvarInt( "scr_airsupportHeightScale", level.airsupportHeightScale );
planeFlyHeight *= GetDvarInt( "scr_airsupportHeightScale", level.airsupportHeightScale );
}
if ( isdefined( level.forceAirsupportMapHeight ) )
{
planeFlyHeight += level.forceAirsupportMapHeight;
}
}
return planeFlyHeight;
}
function callStrike( flightPlan )
{
level.bomberDamagedEnts = [];
level.bomberDamagedEntsCount = 0;
level.bomberDamagedEntsIndex = 0;
assert( flightPlan.distance != 0, "callStrike can not be passed a zero fly distance");
planeHalfDistance = flightPlan.distance / 2;
path = getStrikePath( flightPlan.target, flightPlan.height, planeHalfDistance );
startPoint = path["start"];
endPoint = path["end"];
flightPlan.height = path["height"];
direction = path["direction"];
// Make the plane fly by
d = length( startPoint - endPoint );
flyTime = ( d / flightPlan.speed );
bombTime = calculateReleaseTime( flyTime, flightPlan.height, flightPlan.speed, flightPlan.bombSpeedScale);
if (bombTime < 0)
{
bombTime = 0;
}
assert( flyTime > bombTime );
flightPlan.owner endon("disconnect");
requiredDeathCount = flightPlan.owner.deathCount;
side = VectorCross( anglestoforward( direction ), (0,0,1) );
plane_seperation = 25;
side_offset = VectorScale( side, plane_seperation );
level thread planeStrike( flightPlan.owner, requiredDeathCount, startPoint, endPoint, bombTime, flyTime, flightPlan.speed, flightPlan.bombSpeedScale, direction, flightPlan.planeSpawnCallback );
wait( flightPlan.planeSpacing );
level thread planeStrike( flightPlan.owner, requiredDeathCount, startPoint+side_offset, endPoint+side_offset, bombTime, flyTime, flightPlan.speed, flightPlan.bombSpeedScale, direction, flightPlan.planeSpawnCallback );
wait( flightPlan.planeSpacing );
side_offset = VectorScale( side, -1 * plane_seperation );
level thread planeStrike( flightPlan.owner, requiredDeathCount, startPoint+side_offset, endPoint+side_offset, bombTime, flyTime, flightPlan.speed, flightPlan.bombSpeedScale, direction, flightPlan.planeSpawnCallback );
}
function planeStrike( owner, requiredDeathCount, pathStart, pathEnd, bombTime, flyTime, flyspeed, bombSpeedScale, direction, planeSpawnedFunction )
{
// plane spawning randomness = up to 125 units, biased towards 0
// radius of bomb damage is 512
if ( !isdefined( owner ) )
return;
// bomb_x = (flySpeed * bombSpeedScale) * bombTime;
// origin = VectorScale(pathEnd - pathStart, 0.5) + pathStart;
// plane = spawnplane( owner, "script_model", origin );
// Spawn the planes
plane = spawnplane( owner, "script_model", pathStart );
plane.angles = direction;
plane moveTo( pathEnd, flyTime, 0, 0 );
thread debug_plane_line( flyTime, flyspeed, pathStart, pathEnd );
if ( isdefined(planeSpawnedFunction) )
{
plane [[planeSpawnedFunction]]( owner, requiredDeathCount, pathStart, pathEnd, bombTime, bombSpeedScale, flyTime, flyspeed );
}
// Delete the plane after its flyby
wait flyTime;
plane notify( "delete" );
plane delete();
}
/////////////////////////////////////////////////////////////////////////////
// TARGETING
function determineGroundPoint( player, position )
{
ground = (position[0], position[1], player.origin[2]);
trace = bullettrace(ground + (0,0,10000), ground, false, undefined );
return trace["position"];
}
function determineTargetPoint( player, position )
{
point = determineGroundPoint( player, position );
return clampTarget( point );
}
function getMinTargetHeight()
{
return level.spawnMins[2] - 500;
}
function getMaxTargetHeight()
{
return level.spawnMaxs[2] + 500;
}
function clampTarget( target )
{
min = getMinTargetHeight();
max = getMaxTargetHeight();
if ( target[2] < min )
target[2] = min;
if ( target[2] > max )
target[2] = max;
return target;
}
/////////////////////////////////////////////////////////////////////////////
// NO FLY ZONE
function _insideCylinder( point, base, radius, height )
{
// only going to test if the point is above the height
// if the point is below the cylinder going to treat it
// as being inside
if ( isdefined( height ) )
{
if ( point[2] > base[2] + height )
return false;
}
dist = Distance2D( point, base );
if ( dist < radius )
return true;
return false;
}
function _insideNoFlyZoneByIndex( point, index, disregardHeight )
{
height = level.noFlyZones[index].height;
if ( isdefined(disregardHeight ) )
height = undefined;
return _insideCylinder( point, level.noFLyZones[index].origin, level.noFlyZones[index].radius, height );
}
// if not in a no fly zone then it just returns the height of the point passed in
function getNoFlyZoneHeight( point )
{
height = point[2];
origin = undefined;
for ( i = 0; i < level.noFlyZones.size; i++ )
{
if ( _insideNoFlyZoneByIndex( point, i ) )
{
if ( height < level.noFlyZones[i].height )
{
height = level.noFlyZones[i].height;
origin = level.noFlyZones[i].origin;
}
}
}
if ( !isdefined( origin ) )
return point[2];
return origin[2] + height;
}
function insideNoFlyZones( point, disregardHeight )
{
noFlyZones = [];
for ( i = 0; i < level.noFlyZones.size; i++ )
{
if ( _insideNoFlyZoneByIndex( point, i, disregardHeight ) )
{
noFlyZones[noFlyZones.size] = i;
}
}
return noFlyZones;
}
function crossesNoFlyZone( start, end )
{
for ( i = 0; i < level.noFlyZones.size; i++ )
{
point = math::closest_point_on_line( level.noFlyZones[i].origin + (0,0,(0.5 * level.noFlyZones[i].height)), start, end );
dist = Distance2D( point, level.noFlyZones[i].origin );
if ( point[2] > ( level.noFlyZones[i].origin[2] + level.noFlyZones[i].height ) )
continue;
if ( dist < level.noFlyZones[i].radius )
{
return i;
}
}
return undefined;
}
function crossesNoFlyZones( start, end )
{
zones = [];
for ( i = 0; i < level.noFlyZones.size; i++ )
{
point = math::closest_point_on_line( level.noFlyZones[i].origin, start, end );
dist = Distance2D( point, level.noFlyZones[i].origin );
if ( point[2] > ( level.noFlyZones[i].origin[2] + level.noFlyZones[i].height ) )
continue;
if ( dist < level.noFlyZones[i].radius )
{
zones[zones.size] = i;
}
}
return zones;
}
function getNoFlyZoneHeightCrossed( start, end, minHeight )
{
height = minHeight;
for ( i = 0; i < level.noFlyZones.size; i++ )
{
point = math::closest_point_on_line( level.noFlyZones[i].origin, start, end );
dist = Distance2D( point, level.noFlyZones[i].origin );
if ( dist < level.noFlyZones[i].radius )
{
if ( height < level.noFlyZones[i].height )
height = level.noFlyZones[i].height;
}
}
return height;
}
function _shouldIgnoreNoFlyZone( noFlyZone, noFlyZones )
{
if ( !isdefined( noFlyZone ) )
return true;
for ( i = 0; i < noFlyZones.size; i ++ )
{
if ( isdefined( noFlyZones[i] ) && noFlyZones[i] == noFlyZone )
return true;
}
return false;
}
function _shouldIgnoreStartGoalNoFlyZone( noFlyZone, startNoFlyZones, goalNoFlyZones )
{
if ( !isdefined( noFlyZone ) )
return true;
if ( _shouldIgnoreNoFlyZone( noFlyZone, startNoFlyZones ) )
return true;
if ( _shouldIgnoreNoFlyZone( noFlyZone, goalNoFlyZones ) )
return true;
return false;
}
function getHeliPath( start, goal )
{
startNoFlyZones = insideNoFlyZones( start, true );
thread debug_line( start, goal, (1,1,1) );
goalNoFlyZones = insideNoFlyZones( goal );
// if the end point is in a no fly zone then raise the height to the top of the zone
if ( goalNoFlyZones.size )
{
goal = ( goal[0], goal[1], getNoFlyZoneHeight( goal ) );
}
goal_points = calculatePath(start, goal, startNoFlyZones, goalNoFlyZones );
if ( !isdefined( goal_points ) )
return undefined;
Assert(goal_points.size >= 1 );
return goal_points;
}
function followPath( path, doneNotify, stopAtGoal )
{
for ( i = 0; i < (path.size - 1); i++ )
{
self SetVehGoalPos( path[i], false );
thread debug_line( self.origin, path[i], (1,1,0) );
self waittill("goal" );
}
self SetVehGoalPos( path[path.size - 1], stopAtGoal );
thread debug_line( self.origin, path[i], (1,1,0) );
self waittill("goal" );
if ( isdefined( doneNotify ) )
{
self notify(doneNotify);
}
}
function setGoalPosition( goal, doneNotify, stopAtGoal )
{
if ( !isdefined( stopAtGoal ) )
stopAtGoal = true;
// should test the start to see if it is inside of a no fly zone
// and try and make the vehicle leave the no fly zone in as short of
// a path possible while still moving intelligently
start = self.origin;
goal_points = getHeliPath(start, goal );
if ( !isdefined(goal_points) )
{
goal_points = [];
goal_points[0] = goal;
}
followPath( goal_points, doneNotify, stopAtGoal );
}
function clearPath( start, end, startNoFlyZone, goalNoFlyZone )
{
noFlyZones = crossesNoFlyZones( start, end );
for ( i = 0 ; i < noFlyZones.size; i++ )
{
if ( !_shouldIgnoreStartGoalNoFlyZone( noFlyZones[i], startNoFlyZone, goalNoFlyZone) )
{
return false;
}
}
return true;
}
function append_array( dst, src )
{
for ( i= 0; i < src.size; i++ )
{
dst[ dst.size ]= src[ i ];
}
}
function calculatePath_r( start, end, points, startNoFlyZones, goalNoFlyZones, depth )
{
depth--;
if ( depth <= 0 )
{
points[points.size] = end;
return points;
}
noFlyZones = crossesNoFlyZones( start, end );
for ( i = 0; i < noFlyZones.size; i++ )
{
noFlyZone = noFlyZones[i];
// simple path right now
// probably need to modify this so it tests the new lines found
if ( !_shouldIgnoreStartGoalNoFlyZone( noFlyZone, startNoFlyZones, goalNoFlyZones) )
{
return undefined;
}
}
points[points.size] = end;
return points;
}
function calculatePath( start, end, startNoFlyZones, goalNoFlyZones )
{
points = [];
// PrintLn( "starting path calc: " + start + " " + end );
// points[0] = start;
points = calculatePath_r( start, end, points, startNoFlyZones, goalNoFlyZones, 3 );
if ( !isdefined(points) )
return undefined;
Assert( points.size >= 1 );
debug_sphere( points[points.size - 1], 10, (1,0,0), 1, 1000 );
point = start;
// PrintLn( "Path Calculated: " + points.size );
for ( i = 0 ; i < points.size; i++ )
{
thread debug_line( point, points[i], (0,1,0) );
debug_sphere( points[i], 10, (0,0,1), 1, 1000 );
point = points[i];
}
return points;
}
function _getStrikePathStartAndEnd( goal, yaw, halfDistance )
{
direction = (0,yaw,0);
startPoint = goal + VectorScale( anglestoforward( direction ), -1 * halfDistance );
endPoint = goal + VectorScale( anglestoforward( direction ), halfDistance );
noFlyZone = crossesNoFlyZone( startPoint, endPoint );
path = [];
if ( isdefined( noFlyZone ) )
{
path["noFlyZone"] = noFlyZone;
startPoint = ( startPoint[0], startPoint[1], level.noFlyZones[noFlyZone].origin[2] + level.noFlyZones[noFlyZone].height );
endPoint = ( endPoint[0], endPoint[1], startPoint[2] );
}
else
{
path["noFlyZone"] = undefined;
}
path["start"] = startPoint;
path["end"] = endPoint;
path["direction"] = direction;
return path;
}
function getStrikePath( target, height, halfDistance, yaw )
{
noFlyZoneHeight = getNoFlyZoneHeight( target );
worldHeight = target[2] + height;
if ( noFlyZoneHeight > worldHeight )
{
worldHeight = noFlyZoneHeight;
}
goal = ( target[0], target[1], worldHeight );
path = [];
if ( !isdefined( yaw ) || yaw != "random" )
{
// try a few times to find a path that is not through a no fly zone
for ( i = 0; i < 3; i++ )
{
path = _getStrikePathStartAndEnd( goal, randomint( 360 ), halfDistance );
if ( !isdefined( path["noFlyZone"] ) )
{
break;
}
}
}
else
{
path = _getStrikePathStartAndEnd( goal, yaw, halfDistance );
}
path["height"] = worldHeight - target[2];
return path;
}
function doGlassDamage(pos, radius, max, min, mod)
{
wait(RandomFloatRange(0.05, 0.15));
glassRadiusDamage( pos, radius, max, min, mod );
}
function entLOSRadiusDamage( ent, pos, radius, max, min, owner, eInflictor )
{
dist = distance(pos, ent.damageCenter);
if ( ent.isPlayer || ent.isActor )
{
assumed_ceiling_height = 800; // check for very high ceilings
eye_position = ent.entity GetEye();
head_height = eye_position[2];
debug_display_time = 40 * 100;
// check if there is a path to this entity above his feet. if not, they're probably indoors
trace = weapons::damage_trace( ent.entity.origin, ent.entity.origin + (0,0,assumed_ceiling_height), 0, undefined );
indoors = (trace["fraction"] != 1);
if ( indoors )
{
// the follow check will still fail indoors if the bomb is detonated above the player
// and the ceiling is under 130 units. This second check will have line of site to
// the "ceiling height" point. I dont want to change it at this point.
test_point = trace["position"];
debug_star(test_point, (0,1,0), debug_display_time);
trace = weapons::damage_trace( (test_point[0],test_point[1],head_height) , (pos[0],pos[1], head_height), 0, undefined );
indoors = (trace["fraction"] != 1);
if ( indoors )
{
debug_star((pos[0],pos[1], head_height), (0,1,0), debug_display_time);
// give them a distance advantage for being indoors.
dist *= 4;
if ( dist > radius )
return false;
}
else
{
debug_star((pos[0],pos[1], head_height), (1,0,0), debug_display_time);
trace = weapons::damage_trace( (pos[0],pos[1], head_height), pos, 0, undefined );
indoors = (trace["fraction"] != 1);
if ( indoors )
{
debug_star(pos, (0,1,0), debug_display_time);
// give them a distance advantage for being indoors.
dist *= 4;
if ( dist > radius )
return false;
}
else
{
debug_star(pos, (1,0,0), debug_display_time);
}
}
}
else
{
debug_star(ent.entity.origin + (0,0,assumed_ceiling_height), (1,0,0), debug_display_time );
}
}
ent.damage = int(max + (min-max)*dist/radius);
ent.pos = pos;
ent.damageOwner = owner;
ent.eInflictor = eInflictor;
return true;
}
////////////////////////////////////////////////////////////////////////////
function GetMapCenter()
{
minimapOrigins = getEntArray( "minimap_corner", "targetname" );
if( miniMapOrigins.size )
{
return math::find_box_center( miniMapOrigins[0].origin, miniMapOrigins[1].origin );
}
return ( 0, 0, 0 );
}
function GetRandomMapPoint( x_offset, y_offset, map_x_percentage, map_y_percentage )
{
minimapOrigins = getEntArray( "minimap_corner", "targetname" );
if( miniMapOrigins.size )
{
rand_x = 0;
rand_y = 0;
if( miniMapOrigins[0].origin[0] < miniMapOrigins[1].origin[0] )
{
rand_x = RandomFloatRange( miniMapOrigins[0].origin[0] * map_x_percentage, miniMapOrigins[1].origin[0] * map_x_percentage );
rand_y = RandomFloatRange( miniMapOrigins[0].origin[1] * map_y_percentage, miniMapOrigins[1].origin[1] * map_y_percentage );
}
else
{
rand_x = RandomFloatRange( miniMapOrigins[1].origin[0] * map_x_percentage, miniMapOrigins[0].origin[0] * map_x_percentage );
rand_y = RandomFloatRange( miniMapOrigins[1].origin[1] * map_y_percentage, miniMapOrigins[0].origin[1] * map_y_percentage );
}
return ( x_offset + rand_x, y_offset + rand_y, 0 );
}
return ( x_offset, y_offset, 0 );
}
function GetMaxMapWidth()
{
minimapOrigins = getEntArray( "minimap_corner", "targetname" );
if( miniMapOrigins.size )
{
x = abs( miniMapOrigins[0].origin[0] - miniMapOrigins[1].origin[0] );
y = abs( miniMapOrigins[0].origin[1] - miniMapOrigins[1].origin[1] );
return max( x, y );
}
return 0;
}
function InitRotatingRig()
{
level.airsupport_rotator = spawn( "script_model", GetMapCenter() + ( (isdefined(level.rotator_x_offset)?level.rotator_x_offset:0), (isdefined(level.rotator_y_offset)?level.rotator_y_offset:0), 1200 ) );
level.airsupport_rotator setModel( "tag_origin" );
level.airsupport_rotator.angles = ( 0, 115, 0 );
level.airsupport_rotator hide();
level.airsupport_rotator thread RotateRig();
level.airsupport_rotator thread SwayRig();
}
function RotateRig()
{
for (;;)
{
self rotateyaw( -360, 60 );
wait ( 60 );
}
}
function SwayRig()
{
centerOrigin = self.origin;
for (;;)
{
z = randomIntRange( -200, -100 );
time = randomIntRange( 3, 6 );
self moveto( centerOrigin + (0,0,z), time, 1, 1 );
wait ( time );
z = randomIntRange( 100, 200 );
time = randomIntRange( 3, 6 );
self moveto( centerOrigin + (0,0,z), time, 1, 1 );
wait ( time );
}
}
function StopRotation( time )
{
self endon( "death" );
wait( time );
self StopLoopSound();
}
function FlattenYaw( goal )
{
self endon( "death" );
increment = 3;
if ( self.angles[1] > goal )
{
increment = increment * -1;
}
while( abs( self.angles[1] - goal ) > 3 )
{
self.angles = (self.angles[0], self.angles[1] + increment, self.angles[2] );
{wait(.05);};
}
}
function FlattenRoll()
{
self endon( "death" );
while (self.angles[2] < 0)
{
self.angles = (self.angles[0], self.angles[1], self.angles[2] + 2.5 );
{wait(.05);};
}
}
function Leave( duration )
{
self unlink();
self thread StopRotation( 1 );
tries = 10;
yaw = 0;
while( tries > 0 )
{
exitVector = ( anglestoforward( self.angles + ( 0, yaw, 0 ) ) * 20000 );
exitPoint = ( self.origin[0] + exitVector[0], self.origin[1] + exitVector[1], self.origin[2] - 2500);
exitPoint = self.origin + exitVector;
nfz = airsupport::crossesNoFlyZone (self.origin, exitPoint);
if( isdefined(nfz))
{
if ( tries != 1 )
{
if ( tries % 2 == 1)
{
yaw = yaw * -1;
}
else
{
yaw = yaw + 10;
yaw = yaw * -1;
}
}
tries--;
}
else
{
tries = 0;
}
}
self thread FlattenYaw( self.angles[1] + yaw );
if (self.angles[2] != 0)
{
self thread FlattenRoll();
}
if ( IsVehicle( self ) )
{
self SetSpeed( ( ( Length( exitVector ) / duration ) / 17.6 ), 60 );
self SetVehGoalPos( exitPoint, false, false );
}
else
{
self moveto( exitPoint, duration, 0, 0 );
}
self notify ( "leaving");
}
function GetRandomHelicopterStartOrigin()
{
dist = -1 * GetDvarInt( "scr_supplydropIncomingDistance", 10000 );
pathRandomness = 100;
direction = ( 0, RandomIntRange( -2, 3 ), 0 );
start_origin = ( AnglesToForward( direction ) * dist );
start_origin += ( ( randomfloat( 2 ) - 1 ) * pathRandomness, ( randomfloat( 2 ) - 1 ) * pathRandomness, 0 );
/#
if ( GetDvarInt( "scr_noflyzones_debug", 0 ) )
{
if ( level.noFlyZones.size )
{
index = RandomIntRange( 0, level.noFlyZones.size );
delta = level.noFlyZones[ index ].origin;
delta = ( delta[0] + RandomInt( 10 ), delta[ 1 ] + RandomInt( 10 ), 0 );
delta = VectorNormalize( delta );
start_origin = ( delta * dist );
}
}
#/
return start_origin;
}
////////////////////////////////////////////////////////////////////////////
// debug
function debug_no_fly_zones()
{
/#
for ( i = 0; i < level.noFlyZones.size; i++ )
{
debug_airsupport_cylinder( level.noFlyZones[i].origin, level.noFlyZones[i].radius, level.noFlyZones[i].height, (1,1,1), undefined, 5000 );
}
#/
}
function debug_plane_line( flyTime, flyspeed,pathStart, pathEnd )
{
thread debug_line( pathStart, pathEnd, (1,1,1) );
delta = VectorNormalize(pathEnd - pathStart);
for ( i = 0; i < flyTime; i++ )
{
thread debug_star( pathStart + VectorScale(delta, i * flyspeed), (1,0,0) );
}
}
function debug_draw_bomb_explosion(prevpos)
{
self notify("draw_explosion");
{wait(.05);};
self endon("draw_explosion");
self waittill("projectile_impact", weapon, position );
thread debug_line( prevpos, position, (.5,1,0) );
thread debug_star( position, (1,0,0) );
}
function debug_draw_bomb_path( projectile, color, time )
{
/#
self endon("death");
level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen
if ( !isdefined( color ) )
{
color = (.5,1,0);
}
if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1.0 )
{
prevpos = self.origin;
while(isdefined ( self.origin ) )
{
thread debug_line( prevpos, self.origin, color, time );
prevpos = self.origin;
if ( isdefined(projectile) && projectile )
{
thread debug_draw_bomb_explosion( prevpos );
}
wait .2;
}
}
#/
}
function debug_print3d_simple( message, ent, offset, frames )
{
/#
level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen
if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1.0 )
{
if( isdefined( frames ) )
thread draw_text( message, ( 0.8, 0.8, 0.8 ), ent, offset, frames );
else
thread draw_text( message, ( 0.8, 0.8, 0.8 ), ent, offset, 0 );
}
#/
}
function draw_text( msg, color, ent, offset, frames )
{
/#
if( frames == 0 )
{
while ( isdefined( ent ) && isdefined( ent.origin ) )
{
print3d( ent.origin+offset, msg , color, 0.5, 4 );
{wait(.05);};
}
}
else
{
for( i=0; i < frames; i++ )
{
if( !isdefined( ent ) )
break;
print3d( ent.origin+offset, msg , color, 0.5, 4 );
{wait(.05);};
}
}
#/
}
function debug_print3d( message, color, ent, origin_offset, frames )
{
/#
level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen
if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1.0 )
self thread draw_text( message, color, ent, origin_offset, frames );
#/
}
function debug_line( from, to, color, time, depthTest )
{
/#
level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen
if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1.0 )
{
if ( DistanceSquared( from, to ) < 0.01 )
return;
if ( !isdefined(time) )
{
time = 1000;
}
if ( !isdefined(depthTest) )
{
depthTest = true;
}
Line( from, to, color, 1, depthTest, time);
}
#/
}
function debug_star( origin, color, time )
{
/#
level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen
if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1.0 )
{
if ( !isdefined(time) )
{
time = 1000;
}
if ( !isdefined(color) )
{
color = (1,1,1);
}
debugstar( origin, time, color );
}
#/
}
function debug_circle( origin, radius, color, time )
{
/#
level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen
if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1 )
{
if ( !isdefined(time) )
{
time = 1000;
}
if ( !isdefined(color) )
{
color = (1,1,1);
}
circle( origin, radius, color, true, true, time );
}
#/
}
function debug_sphere( origin, radius, color, alpha, time )
{
/#
level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen
if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1 )
{
if ( !isdefined(time) )
{
time = 1000;
}
if ( !isdefined(color) )
{
color = (1,1,1);
}
sides = Int(10 * ( 1 + Int(radius / 100) ));
sphere( origin, radius, color, alpha, true, sides, time );
}
#/
}
function debug_airsupport_cylinder( origin, radius, height, color, mustRenderHeight, time )
{
/#
level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen
if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1 )
{
debug_cylinder( origin, radius, height, color, mustRenderHeight, time );
}
#/
}
function debug_cylinder( origin, radius, height, color, mustRenderHeight, time )
{
/#
subdivision = 600;
{
if ( !isdefined(time) )
{
time = 1000;
}
if ( !isdefined(color) )
{
color = (1,1,1);
}
count = (height/subdivision);
for ( i = 0; i < count; i++ )
{
point = origin + ( 0, 0, i * subdivision );
circle( point, radius, color, true, true, time );
}
if( isdefined( mustRenderHeight ) )
{
point = origin + ( 0, 0, mustRenderHeight );
circle( point, radius, color, true, true, time );
}
}
#/
}
function getPointOnLine( startPoint, endPoint, ratio )
{
nextPoint = ( startPoint[0] + ( ( endPoint[0] - startPoint[0] ) * ratio ) ,
startPoint[1] + ( ( endPoint[1] - startPoint[1] ) * ratio ) ,
startPoint[2] + ( ( endPoint[2] - startPoint[2] ) * ratio ) );
return nextPoint;
}
function canTargetPlayerWithSpecialty()
{
if ( self HasPerk( "specialty_nottargetedbyairsupport" ) ||
( isdefined( self.specialty_nottargetedbyairsupport ) && self.specialty_nottargetedbyairsupport ) )
{
if ( !isdefined( self.notTargettedAI_underMinSpeedTimer ) || self.notTargettedAI_underMinSpeedTimer < GetDvarInt( "perk_nottargetedbyai_graceperiod" ) )
return false;
}
return true;
}
function monitorSpeed( spawnProtectionTime )
{
self endon( "death" );
self endon( "disconnect" );
if ( self HasPerk( "specialty_nottargetedbyairsupport" ) == false )
{
return;
}
GetDvarString( "perk_nottargetted_graceperiod" );
gracePeriod = GetDvarInt( "perk_nottargetedbyai_graceperiod" );
minspeed = GetDvarInt( "perk_nottargetedbyai_min_speed" );
minspeedSq = minspeed * minspeed;
waitPeriod = 0.25;
waitPeriodMilliseconds = waitPeriod * 1000;
if ( minspeedSq == 0 ) // will never fail min speed check below so early out.
return;
self.notTargettedAI_underMinSpeedTimer = 0;
if ( isdefined ( spawnProtectionTime ) )
{
wait( spawnProtectionTime );
}
while(1)
{
velocity = self GetVelocity();
speedsq = lengthsquared( velocity );
if ( speedSq < minspeedSq )
{
self.notTargettedAI_underMinSpeedTimer += waitPeriodMilliseconds;
}
else
{
self.notTargettedAI_underMinSpeedTimer = 0;
}
wait( waitPeriod );
}
}
function clearmonitoredspeed()
{
if ( isdefined ( self.notTargettedAI_underMinSpeedTimer ) )
self.notTargettedAI_underMinSpeedTimer = 0;
}