1290 lines
35 KiB
Plaintext
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;
|
|
}
|