Shooting SAMs: Make random priority be truely random. Fix that sams would fire when target inside MEZ. S-75 is now CLOS guided.

pull/25/head
Nikolai V. Chr 2 years ago
parent be55f12802
commit 665ad95582

@ -332,7 +332,7 @@ var fire_control = func(mp, my_pos) {
var score = 0;
var priority = getprop("priority");
if (priority==PRIO_NORM) {
# nop
score = rand()-0.5;
} elsif (priority==PRIO_HIGH) {
score = target_altitude;
} elsif (priority==PRIO_LOW) {
@ -358,6 +358,7 @@ var fire_control = func(mp, my_pos) {
}
if ( target_distance * M2NM > missile_max_distance ) { return [mp,false,0,0]; }
if ( target_distance * M2NM < missile_min_distance ) { return [mp,false,0,0]; }
@ -377,7 +378,7 @@ var fire_control = func(mp, my_pos) {
var score = 0;
var priority = getprop("priority");
if (priority==PRIO_NORM) {
# nop
score = rand()-0.5;
} elsif (priority==PRIO_HIGH) {
score = target_altitude;
} elsif (priority==PRIO_LOW) {
@ -409,6 +410,7 @@ var reload = func(force = 1) {
if (armament.AIM.new(i,missile_name,missile_brevity, midflight) != -1) {
#if statement just in case reload was called before all missiles were fired. Cause avoid calling search() on same missile twice.
armament.AIM.active[i].radarZ = radar_elevation_above_terrain_m;
armament.AIM.active[i].start();
}
}
@ -426,6 +428,9 @@ var reload = func(force = 1) {
}
var autoreload = func() {
if ( getprop("/carrier/sunk") == 1 or getprop("/carrier/disabled") == 1) {
return;
}
if (reloading) {
reloading = 0;
if (ACTIVE_MISSILE > NUM_MISSILES or ROUNDS == 0) {
@ -497,7 +502,7 @@ var missile_launch = func(mp, launchtime, my_pos) {
setprop("sam/missiles",(NUM_MISSILES+1-ACTIVE_MISSILE));
clearSingleLock();
return;
} elsif ((systime() - launchtime) > (lockon_time*2) or radar_logic.isNotBehindTerrain(mp)[0] == 0) {
} elsif ((systime() - launchtime) > (lockon_time*1.5) or radar_logic.isNotBehindTerrain(mp)[0] == 0) {
# launch cancelled so it dont forever goes in this loop and dont allow for other firings.
if (lu != nil) {
lu.tracking = 0;
@ -510,7 +515,7 @@ var missile_launch = func(mp, launchtime, my_pos) {
clearSingleLock();
return;
} else {
info = info~" (tracking)";
info = sprintf("%s (tracking %d nm at %d ft)", info, armament.contact.get_range(), armament.contact.get_altitude());
if (systime()-missile_release_time > 1.5) {
var tgt_dir = target_bearing-getprop("orientation/heading-deg");
var max_dir = launch_update_time*align_speed_dps;

@ -154,6 +154,7 @@ var LBM2SLUGS = 1/SLUGS2LBM;
var slugs_to_lbm = SLUGS2LBM;# since various aircraft use this from outside missile, leaving it for backwards compat.
var first_in_air = FALSE;# first missile is in the air, other missiles should not write to MP.
var first_in_air_max_sec = 30;
var versionString = getprop("sim/version/flightgear");
var version = split(".", versionString);
@ -212,6 +213,7 @@ var contactPoint = nil;
# isPainted() - Tells if this target is still being radar tracked by the launch platform, only used in semi-radar guided missiles.
# isLaserPainted() - Tells if this target is still being tracked by the launch platform, only used by laser guided ordnance.
# isRadiating(coord) - Tell if anti-radiation missile is hit by radiation from target. coord is the weapon position.
# isCommandActive()
# isVirtual() - Tells if the target is just a position, and should not be considered for damage.
# get_closure_rate() - closure rate in kt
@ -314,8 +316,8 @@ var AIM = {
m.asc = getprop(m.nodeString~"attack-steering-cue-enabled");# Bool. ASC enabled.
# navigation, guiding and seekerhead
m.max_seeker_dev = getprop(m.nodeString~"seeker-field-deg") / 2; # missiles own seekers total FOV diameter.
m.guidance = getprop(m.nodeString~"guidance"); # heat/radar/semi-radar/laser/gps/vision/unguided/level/gyro-pitch/radiation/inertial/remote/remote-stable
m.guidanceLaw = getprop(m.nodeString~"navigation"); # guidance-law: direct/OPN/PN/APN/PNxxyy/APNxxyy (use direct for pure pursuit, use PN for A/A missiles, use APN for modern SAM missiles PN for older, use PNxxyy/APNxxyy for surface to air where xx is degrees to aim above target, yy is seconds it will do that). GPN is APN for winged glidebombs.
m.guidance = getprop(m.nodeString~"guidance"); # heat/radar/semi-radar/laser/gps/vision/unguided/level/gyro-pitch/radiation/inertial/remote/remote-stable/command
m.guidanceLaw = getprop(m.nodeString~"navigation"); # guidance-law: direct/OPN/PN/APN/PNxxyy/APNxxyy/LOS (use direct for pure pursuit, use PN for A/A missiles, use APN for modern SAM missiles PN for older, use PNxxyy/APNxxyy for surface to air where xx is degrees to aim above target, yy is seconds it will do that). GPN is APN for winged glidebombs.
m.guidanceLawHorizInit = getprop(m.nodeString~"navigation-init-pure-15"); # Bool. Guide in horizontal plane using pure pursuit until target with 15 deg of nose, before switching to <navigation>
m.pro_constant = getprop(m.nodeString~"proportionality-constant"); # Constant for how sensitive proportional navigation is to target speed/acc. Normally between 3-6. [optional]
m.all_aspect = getprop(m.nodeString~"all-aspect"); # bool. set to false if missile only locks on reliably to rear of target aircraft
@ -336,9 +338,11 @@ var AIM = {
# engine
m.force_lbf_1 = getprop(m.nodeString~"thrust-lbf-stage-1"); # stage 1 thrust [optional]
m.force_lbf_2 = getprop(m.nodeString~"thrust-lbf-stage-2"); # stage 2 thrust [optional]
m.force_lbf_3 = getprop(m.nodeString~"thrust-lbf-stage-3"); # stage 3 thrust [optional]
m.stage_1_duration = getprop(m.nodeString~"stage-1-duration-sec"); # stage 1 duration [optional]
m.stage_gap_duration = getprop(m.nodeString~"stage-gap-duration-sec"); # gap duration between stage 1 and 2 [optional]
m.stage_2_duration = getprop(m.nodeString~"stage-2-duration-sec"); # stage 2 duration [optional]
m.stage_3_duration = getprop(m.nodeString~"stage-3-duration-sec"); # stage 3 duration [optional]
m.weight_fuel_lbm = getprop(m.nodeString~"weight-fuel-lbm"); # fuel weight [optional]. If this property is not present, it won't lose weight as the fuel is used.
m.vector_thrust = getprop(m.nodeString~"vector-thrust"); # Boolean. This will make less drag due to high G turns while engine is running. [optional]
m.engineEnabled = getprop(m.nodeString~"engine-enabled"); # Boolean. If engine will start at all. [optional]
@ -531,6 +535,9 @@ var AIM = {
if (m.force_lbf_2 == nil) {
m.force_lbf_2 = 0;
}
if (m.force_lbf_3 == nil) {
m.force_lbf_3 = 0;
}
if(m.stage_gap_duration == nil) {
m.stage_gap_duration = 0;
}
@ -540,6 +547,9 @@ var AIM = {
if(m.stage_2_duration == nil) {
m.stage_2_duration = 0;
}
if(m.stage_3_duration == nil) {
m.stage_3_duration = 0;
}
if (m.destruct_when_free == nil) {
m.destruct_when_free = FALSE;
}
@ -783,6 +793,8 @@ var AIM = {
m.vert_closing_rate_fps = -1;
m.usingTGPPoint = 0;
m.rotate_token = 0;
m.CREv = 0;
m.CREh = 0;
#
# Terrain following
@ -810,7 +822,8 @@ var AIM = {
m.maxMach = 0;
m.maxMach1 = 0;#stage 1
m.maxMach2 = 0;#stage 2
m.maxMach3 = 0;#stage 2 end
m.maxMach3 = 0;#stage 3
m.maxMach4 = 0;#stage 3 end
m.energyBleedKt = 0;
#
@ -1422,6 +1435,8 @@ var AIM = {
me.force_lbf_1 = 0;
me.stage_2_duration = 0;
me.force_lbf_2 = 0;
me.stage_3_duration = 0;
me.force_lbf_3 = 0;
me.stage_gap_duration = 0;
me.drop_time = 10000;
me.inert = TRUE;
@ -1649,10 +1664,12 @@ var AIM = {
# find the fuel consumption - lbm/sec
var impulse1 = me.force_lbf_1 * me.stage_1_duration; # lbf*s
var impulse2 = me.force_lbf_2 * me.stage_2_duration; # lbf*s
me.impulseT = impulse1 + impulse2; # lbf*s
var impulse3 = me.force_lbf_3 * me.stage_3_duration; # lbf*s
me.impulseT = impulse1 + impulse2 + impulse3; # lbf*s
me.fuel_per_impulse = me.weight_fuel_lbm / me.impulseT;# lbm/(lbf*s)
me.fuel_per_sec_1 = (me.fuel_per_impulse * impulse1) / me.stage_1_duration;# lbm/s
me.fuel_per_sec_2 = (me.fuel_per_impulse * impulse2) / me.stage_2_duration;# lbm/s
me.fuel_per_sec_1 = me.stage_1_duration == 0?0:(me.fuel_per_impulse * impulse1) / me.stage_1_duration;# lbm/s
me.fuel_per_sec_2 = me.stage_2_duration == 0?0:(me.fuel_per_impulse * impulse2) / me.stage_2_duration;# lbm/s
me.fuel_per_sec_3 = me.stage_3_duration == 0?0:(me.fuel_per_impulse * impulse3) / me.stage_3_duration;# lbm/s
me.printExtendedStats();
@ -1720,7 +1737,9 @@ var AIM = {
}
var nav = "";
var nav2 = "";
if (me.guidanceLaw == "direct") {
if (me.guidanceLaw == "LOS") {
nav = "Line-of-sight";
} elsif (me.guidanceLaw == "direct") {
nav = "Pure pursuit."
} elsif (me.guidanceLaw == "OPN") {
nav = "Original Proportional navigation. Proportionality constant is "~me.pro_constant;
@ -1744,7 +1763,9 @@ var AIM = {
nav2 = sprintf("Before APN it will aim %d degrees above target for %d seconds.",xx,yy);
}
var stages = 0;
if (me.force_lbf_1 > 0 and me.stage_1_duration > 0 and me.force_lbf_2 > 0 and me.stage_2_duration > 0) {
if (me.force_lbf_1 > 0 and me.stage_1_duration > 0 and me.force_lbf_2 > 0 and me.stage_2_duration > 0 and me.force_lbf_3 > 0 and me.stage_3_duration > 0) {
stages = 3;
} elsif (me.force_lbf_1 > 0 and me.stage_1_duration > 0 and me.force_lbf_2 > 0 and me.stage_2_duration > 0) {
stages = 2;
} elsif (me.force_lbf_1 > 0 and me.stage_1_duration > 0) {
stages = 1;
@ -1866,6 +1887,9 @@ var AIM = {
if (me.stage_gap_duration > 0) {
me.printStats("Stage 1 to 2 time gap: %.1f seconds.", me.stage_gap_duration);
}
if (stages > 2) {
me.printStats("Stage 3: %d lbf for %.1f seconds.", me.force_lbf_3, me.stage_3_duration);
}
}
me.printStats("%s",vector);
if (!me.weight_fuel_lbm) {
@ -2137,9 +2161,12 @@ var AIM = {
if (me.speed_m > me.maxMach2 and me.life_time > (me.drop_time + me.stage_1_duration) and me.life_time <= (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration)) {
me.maxMach2 = me.speed_m;
}
if (me.maxMach3 == 0 and me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration)) {
if (me.speed_m > me.maxMach3 and me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration) and me.life_time <= (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration+me.stage_3_duration)) {
me.maxMach3 = me.speed_m;
}
if (me.maxMach4 == 0 and me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration+me.stage_3_duration)) {
me.maxMach4 = me.speed_m;
}
me.Cd = me.drag(me.speed_m,me.myG);
@ -2448,7 +2475,10 @@ var AIM = {
if (me.exploded == TRUE) {
me.printStats("%s max absolute %.2f Mach. Max relative %.2f Mach. Max alt %6d ft. Terminal %.2f mach.", me.type, me.maxMach, me.maxMach-me.startMach, me.maxAlt, me.speed_m);
me.printStats("%s max relative %d ft/s.", me.type, me.maxFPS-me.startFPS);
me.printStats(" Absolute %.2f Mach in stage 1. Absolute %.2f Mach in stage 2. Absolute %.2f mach propulsion end.", me.maxMach1, me.maxMach2, me.maxMach3);
me.printStats(" Absolute %.2f Mach in stage 1.", me.maxMach1);
if (me.force_lbf_2 > 0) me.printStats(" Absolute %.2f Mach in stage 2.", me.maxMach2);
if (me.force_lbf_3 > 0) me.printStats(" Absolute %.2f Mach in stage 3.", me.maxMach3);
if (me.maxMach4 > 0) me.printStats(" Absolute %.2f mach propulsion end.", me.maxMach4);
me.printStats(" Fired at %s from %.2f Mach, %5d ft at %3d NM distance. Flew %.1f NM.", me.callsign, me.startMach, me.startAlt, me.startDist * M2NM, me.ac_init.direct_distance_to(me.coord)*M2NM);
# We exploded, and start the sound propagation towards the plane
me.sndSpeed = me.sound_fps;
@ -2523,8 +2553,10 @@ var AIM = {
# consume fuel
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration)) {
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration+me.stage_3_duration)) {
me.weight_current = me.weight_launch_lbm - me.weight_fuel_lbm;
} elsif (me.life_time > (me.drop_time + me.stage_1_duration+me.stage_gap_duration + me.stage_2_duration)) {
me.weight_current = me.weight_current - me.fuel_per_sec_3 * me.dt;
} elsif (me.life_time > (me.drop_time + me.stage_1_duration+me.stage_gap_duration)) {
me.weight_current = me.weight_current - me.fuel_per_sec_2 * me.dt;
} elsif (me.life_time > me.drop_time and me.life_time < (me.drop_time + me.stage_1_duration)) {
@ -2534,59 +2566,7 @@ var AIM = {
me.mass = me.weight_current * LBM2SLUGS;
# telemetry
if (me.data == TRUE) {
me.eta = me.free == TRUE or me.vert_closing_rate_fps == -1?-1:(me["t_go"]!=nil?me.t_go:(me.dist_curr*M2FT)/me.vert_closing_rate_fps);
if (me.eta < 0) me.eta = -1;
me.hit = 50;# in percent
if (me.life_time > me.drop_time+me.stage_1_duration + me.gnd_launch?(me.stage_2_duration + me.stage_gap_duration):0) {
# little less optimistic after reaching topspeed
if (me.selfdestruct_time-me.life_time < me.eta) {
# reduce alot if eta later than lifespan
me.hit -= 75;
} elsif (me.eta != -1 and (me.selfdestruct_time-me.life_time) != 0) {
# if its hitting late in life, then reduce
me.hit -= (me.eta / (me.selfdestruct_time-me.life_time)) * 25;
}
if (me.eta > 0) {
# penalty if eta is high
me.hit -= me.clamp(40*me.eta/(me.life_time*0.85), 0, 40);
}
if (me.eta < 0) {
# penalty if eta is incomputable
me.hit -= 75;
}
}
if (me.curr_deviation_h != nil and me.dist_curr > 50) {
# penalty for target being off-bore
me.hit -= math.abs(me.curr_deviation_h)/2.5;
}
if (me.guiding == TRUE and me.t_speed_fps != nil and me.old_speed_fps > me.t_speed_fps and me.t_speed_fps != 0) {
# bonus for traveling faster than target
me.hit += me.clamp((me.old_speed_fps / me.t_speed_fps)*15,-25,50);
}
if (me.free == TRUE or (me.gnd_launch and (me.chaffLock or me.flareLock))) {
# penalty for not longer guiding
me.hit -= 75;
}
me.hit = int(me.clamp(me.hit, 0, 90));
me.ai.getNode("ETA").setIntValue(me.eta);
me.ai.getNode("hit").setIntValue(me.hit);
if (me.gnd_launch) {
setprop("sam/impact"~me.ID,me.eta);
setprop("sam/hit"~me.ID,me.hit);
}
if (me["prevETA"] != nil) {
if (me.prevETA < me.eta) {
# reset the lowest eta to allow it to increase.
AIM.setETA(nil);
}
AIM.setETA(me.eta, me["prevETA"]);
}
me.prevETA = me["eta"];
}
me.sendTelemetry();
if (me.life_time - me.last_noti > me.noti_time and getprop("payload/armament/msg")) {
# notify in flight using Emesary.
@ -2680,6 +2660,62 @@ var AIM = {
}
},
sendTelemetry: func {
if (me.data == TRUE) {
me.eta = me.free == TRUE or me.vert_closing_rate_fps == -1?-1:(me["t_go"]!=nil?me.t_go:(me.dist_curr*M2FT)/me.vert_closing_rate_fps);
if (me.eta < 0) me.eta = -1;
me.hit = 50;# in percent
if (me.life_time > me.drop_time+me.stage_1_duration + me.gnd_launch?(me.stage_2_duration + me.stage_gap_duration):0) {
# little less optimistic after reaching topspeed
if (me.selfdestruct_time-me.life_time < me.eta) {
# reduce alot if eta later than lifespan
me.hit -= 75;
} elsif (me.eta != -1 and (me.selfdestruct_time-me.life_time) != 0) {
# if its hitting late in life, then reduce
me.hit -= (me.eta / (me.selfdestruct_time-me.life_time)) * 25;
}
if (me.eta > 0) {
# penalty if eta is high
me.hit -= me.clamp(40*me.eta/(me.life_time*0.85), 0, 40);
}
if (me.eta < 0) {
# penalty if eta is incomputable
me.hit -= 75;
}
}
if (me.curr_deviation_h != nil and me.dist_curr > 50) {
# penalty for target being off-bore
me.hit -= math.abs(me.curr_deviation_h)/2.5;
}
if (me.guiding == TRUE and me.t_speed_fps != nil and me.old_speed_fps > me.t_speed_fps and me.t_speed_fps != 0) {
# bonus for traveling faster than target
me.hit += me.clamp((me.old_speed_fps / me.t_speed_fps)*15,-25,50);
}
if (me.free == TRUE or (me.gnd_launch and (me.chaffLock or me.flareLock))) {
# penalty for not longer guiding
me.hit -= 75;
}
me.hit = int(me.clamp(me.hit, 0, 90));
me.ai.getNode("ETA").setIntValue(me.eta);
me.ai.getNode("hit").setIntValue(me.hit);
if (me.gnd_launch) {
setprop("sam/impact"~me.ID,me.eta);
setprop("sam/hit"~me.ID,me.hit);
}
if (me["prevETA"] != nil) {
if (me.prevETA < me.eta) {
# reset the lowest eta to allow it to increase.
AIM.setETA(nil);
}
AIM.setETA(me.eta, me["prevETA"]);
}
me.prevETA = me["eta"];
}
},
getGPS: func(x, y, z, pitch, head=nil, roll=nil) {
#
# get Coord from body position. x,y,z must be in meters.
@ -2816,8 +2852,10 @@ var AIM = {
#
me.thrust_lbf = 0;# pounds force (lbf)
if (me.engineEnabled) {
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration)) {
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration + me.stage_3_duration)) {
me.thrust_lbf = 0;
} elsif (me.life_time > me.stage_1_duration + me.stage_gap_duration + me.drop_time + me.stage_2_duration) {
me.thrust_lbf = me.force_lbf_3;
} elsif (me.life_time > me.stage_1_duration + me.stage_gap_duration + me.drop_time) {
me.thrust_lbf = me.force_lbf_2;
} elsif (me.life_time > me.drop_time and me.life_time < me.drop_time+me.stage_1_duration) {
@ -2903,7 +2941,7 @@ var AIM = {
},
setFirst: func() {
if (me.smoke_prop.getValue() == TRUE) {
if (me.smoke_prop.getValue() == TRUE and me.life_time < first_in_air_max_sec) {
if (me.first == TRUE or first_in_air == FALSE) {
# report position over MP for MP animation of smoke trail.
me.first = TRUE;
@ -2915,7 +2953,7 @@ var AIM = {
me.mpAltft.setDoubleValue(me.coord.alt()*M2FT);
}
}
} elsif (me.first == TRUE and me.life_time > me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration) {
} elsif (me.first == TRUE and (me.life_time >= first_in_air_max_sec or me.life_time > me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration)) {
# this weapon was reporting its position over MP, but now its fuel has used up. So allow for another to do that.
me.resetFirst();
}
@ -3136,8 +3174,8 @@ var AIM = {
me.chaffLast = me.chaffNumber;
me.chaffTime = getprop("sim/time/elapsed-sec");
me.aspectDeg = me.aspectToExhaust(me.coord, me.Tgt) / 180;# 0 = viewing engine, 1 = front
me.semi = me.guidance == "semi-radar"?0.5:1;
me.chaffChance = (1-me.chaffResistance);#*me.semi;
me.semi = me.guidance == "semi-radar"?1:1;
me.chaffChance = (1-me.chaffResistance)*me.semi;
me.chaffLock = rand() < (me.chaffChance - (me.chaffChance * 0.5 * me.aspectDeg));# 50% less chance to be fooled if front aspect
if (me.chaffLock == TRUE) {
@ -3224,6 +3262,11 @@ var AIM = {
me.printStats(me.type~": Not guiding (lost radar reflection, gave up)");
me.free = TRUE;
}
} elsif (me.guidance == "command" and (me.Tgt == nil or !me.Tgt.isCommandActive())) {
# if its command guided and the control no longer sends commands
me.guiding = FALSE;
me.printStats(me.type~": Not guiding (no commands from controller, gave up)");
me.free = TRUE;
} elsif (me.guidance == "radiation" and me.is_radiating_me(me.Tgt) == FALSE) {
# if its radiation guided and the target is not illuminating us with radiation
me.guiding = FALSE;
@ -3639,7 +3682,7 @@ var AIM = {
# augmented proportional navigation for heading #
#################################################
if (me.guidanceLaw == "direct") {
if (me.guidanceLaw == "direct" or (me.guidanceLaw == "LOS" and me.life_time < 4)) {
# pure pursuit
me.raw_steer_signal_head = me.curr_deviation_h;
if (me.cruise_or_loft == FALSE) {
@ -3738,8 +3781,30 @@ var AIM = {
me.t_go = me.myMath.dotProduct(me.R_tm,me.R_tm)/me.myMath.dotProduct(me.R_tm, me.V_tm);
#printf("time_to_go %.1f, closing %d",me.t_go,me.vert_closing_rate_fps);
# Horizontal PN:
if (me.apn == 1) {
# Horizontal homing:
if (me.guidanceLaw == "LOS") {
me.K1 = 2.5;
me.K2 = 10.0;
me.R_m = me.ac_init.distance_to(me.coord)*M2FT;
me.course_to_missile = me.ac_init.course_to(me.coord);
me.course_to_target = me.ac_init.course_to(me.t_coord);
me.CREh_old = me.CREh;
# cross range error:
me.CREh = me.R_m*math.sin(me.clamp(geo.normdeg180(me.course_to_target - me.course_to_missile),-89,89)*D2R);
me.CREh_dot = (me.CREh - me.CREh_old)/me.dt;
me.acc_lateral_fps2 = me.K1*me.CREh_dot + me.K2*me.CREh;
me.toBody = math.cos(geo.normdeg180(me.hdg-me.course_to_target)*D2R);
if (me.toBody==0) me.toBody=0.0001;
me.acc_lateral_fps2 /= me.toBody;
me.velocity_vector_length_fps = me.clamp(me.old_speed_horz_fps, 0.0001, 1000000);
me.commanded_lateral_vector_length_fps = me.acc_lateral_fps2*me.dt;
me.raw_steer_signal_head = R2D*me.commanded_lateral_vector_length_fps/me.velocity_vector_length_fps;
#me.raw_steer_signal_head = me.curr_deviation_h;
} elsif (me.apn == 1) {
# APN (constant best at 5, but the higher value the more sensitive to noise)
# Augmented proportional navigation. Takes target acc. into account. Invented for SAMs.
me.toBody = math.cos(me.curr_deviation_h*D2R);#convert perpendicular LOS acc. to perpendicular body acc.
@ -3823,8 +3888,26 @@ var AIM = {
me.last_t_elev_norm_speed = me.t_LOS_elev_norm_speed;
#printf("Target acc. perpendicular to LOS (positive up): %.1f G.", me.t_LOS_elev_norm_acc/g_fps);
# Vertical PN:
if (me.apn == 1) {
# Vertical homing:
if (me.guidanceLaw == "LOS") {
me.R_m = me.ac_init.direct_distance_to(me.coord)*M2FT;
me.pitch_to_missile = me.myMath.getPitch(me.ac_init,me.coord);
me.pitch_to_target = me.myMath.getPitch(me.ac_init,me.t_coord);
me.CREv_old = me.CREv;
# Cross range error
me.CREv = me.R_m*math.sin((me.pitch_to_target - me.pitch_to_missile)*D2R);
me.CREv_dot = (me.CREv - me.CREv_old)/me.dt;
me.acc_upwards_fps2 = me.K1*me.CREv_dot + me.K2*me.CREv;
# Convert perpendicular LOS acc. to perpendicular body acc.
me.toBody = math.cos((me.pitch - me.pitch_to_target)*D2R);
if (me.toBody==0) me.toBody=0.00001;
me.acc_upwards_fps2 /= me.toBody;
# Apply the acc.
me.velocity_vector_length_fps = me.clamp(me.old_speed_fps, 0.0001, 1000000);
me.commanded_upwards_vector_length_fps = me.acc_upwards_fps2*me.dt;
me.raw_steer_signal_elev = R2D*me.commanded_upwards_vector_length_fps/me.velocity_vector_length_fps;
} elsif (me.apn == 1) {
# APN (constant best at 5, but the higher value the more sensitive to noise)
# Augmented proportional navigation. Takes target acc. into account. Invented for SAMs.
me.toBody = math.cos(me.curr_deviation_e*D2R);#convert perpendicular LOS acc. to perpendicular body acc.

@ -139,6 +139,13 @@ var Contact = {
return isNotBehindTerrain(me.node)[0];
},
isCommandActive: func {
if (getprop("/carrier/sunk") == 1) {
return 0;
}
return isNotBehindTerrain(me.node)[0];
},
getUnique: func () {
if (me.unique == nil) {
me.unique = me.node.getNode("unique");

@ -31,7 +31,7 @@ var geooutgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("sa2mp.geo",geoRo
# bridge should be tuned to be around 90% of the packet size full.
geooutgoingBridge.TransmitFrequencySeconds = 0.75;
geooutgoingBridge.MPStringMaxLen = 150;
geooutgoingBridge.MPStringMaxLen = 225;
emesary_mp_bridge.IncomingMPBridge.startMPBridge(geoRoutedNotifications, 18, emesary.GlobalTransmitter);

@ -41,13 +41,11 @@ var isInEngagementEnvelope = func (target_radial_airspeed, target_ground_distanc
}
var midflight = func (struct) {
if (struct.guidance == "semi-radar") {
# This makes the SAM system keep lock on target when missile in-flight and no longer tracking the target.
# Usage is to make the RWR lock sound go off in targets cockpit.
thread.lock(mutexLock);
semi_active_track = struct.callsign;
thread.unlock(mutexLock);
}
# This makes the SAM system keep lock on target when missile in-flight and no longer tracking the target.
# Usage is to make the RWR lock sound go off in targets cockpit.
thread.lock(mutexLock);
semi_active_track = struct.callsign;
thread.unlock(mutexLock);
return {};
};

@ -332,7 +332,7 @@ var fire_control = func(mp, my_pos) {
var score = 0;
var priority = getprop("priority");
if (priority==PRIO_NORM) {
# nop
score = rand()-0.5;
} elsif (priority==PRIO_HIGH) {
score = target_altitude;
} elsif (priority==PRIO_LOW) {
@ -358,6 +358,7 @@ var fire_control = func(mp, my_pos) {
}
if ( target_distance * M2NM > missile_max_distance ) { return [mp,false,0,0]; }
if ( target_distance * M2NM < missile_min_distance ) { return [mp,false,0,0]; }
@ -377,7 +378,7 @@ var fire_control = func(mp, my_pos) {
var score = 0;
var priority = getprop("priority");
if (priority==PRIO_NORM) {
# nop
score = rand()-0.5;
} elsif (priority==PRIO_HIGH) {
score = target_altitude;
} elsif (priority==PRIO_LOW) {
@ -409,6 +410,7 @@ var reload = func(force = 1) {
if (armament.AIM.new(i,missile_name,missile_brevity, midflight) != -1) {
#if statement just in case reload was called before all missiles were fired. Cause avoid calling search() on same missile twice.
armament.AIM.active[i].radarZ = radar_elevation_above_terrain_m;
armament.AIM.active[i].start();
}
}
@ -426,6 +428,9 @@ var reload = func(force = 1) {
}
var autoreload = func() {
if ( getprop("/carrier/sunk") == 1 or getprop("/carrier/disabled") == 1) {
return;
}
if (reloading) {
reloading = 0;
if (ACTIVE_MISSILE > NUM_MISSILES or ROUNDS == 0) {
@ -497,7 +502,7 @@ var missile_launch = func(mp, launchtime, my_pos) {
setprop("sam/missiles",(NUM_MISSILES+1-ACTIVE_MISSILE));
clearSingleLock();
return;
} elsif ((systime() - launchtime) > (lockon_time*2) or radar_logic.isNotBehindTerrain(mp)[0] == 0) {
} elsif ((systime() - launchtime) > (lockon_time*1.5) or radar_logic.isNotBehindTerrain(mp)[0] == 0) {
# launch cancelled so it dont forever goes in this loop and dont allow for other firings.
if (lu != nil) {
lu.tracking = 0;
@ -510,7 +515,7 @@ var missile_launch = func(mp, launchtime, my_pos) {
clearSingleLock();
return;
} else {
info = info~" (tracking)";
info = sprintf("%s (tracking %d nm at %d ft)", info, armament.contact.get_range(), armament.contact.get_altitude());
if (systime()-missile_release_time > 1.5) {
var tgt_dir = target_bearing-getprop("orientation/heading-deg");
var max_dir = launch_update_time*align_speed_dps;

@ -154,6 +154,7 @@ var LBM2SLUGS = 1/SLUGS2LBM;
var slugs_to_lbm = SLUGS2LBM;# since various aircraft use this from outside missile, leaving it for backwards compat.
var first_in_air = FALSE;# first missile is in the air, other missiles should not write to MP.
var first_in_air_max_sec = 30;
var versionString = getprop("sim/version/flightgear");
var version = split(".", versionString);
@ -212,6 +213,7 @@ var contactPoint = nil;
# isPainted() - Tells if this target is still being radar tracked by the launch platform, only used in semi-radar guided missiles.
# isLaserPainted() - Tells if this target is still being tracked by the launch platform, only used by laser guided ordnance.
# isRadiating(coord) - Tell if anti-radiation missile is hit by radiation from target. coord is the weapon position.
# isCommandActive()
# isVirtual() - Tells if the target is just a position, and should not be considered for damage.
# get_closure_rate() - closure rate in kt
@ -314,8 +316,8 @@ var AIM = {
m.asc = getprop(m.nodeString~"attack-steering-cue-enabled");# Bool. ASC enabled.
# navigation, guiding and seekerhead
m.max_seeker_dev = getprop(m.nodeString~"seeker-field-deg") / 2; # missiles own seekers total FOV diameter.
m.guidance = getprop(m.nodeString~"guidance"); # heat/radar/semi-radar/laser/gps/vision/unguided/level/gyro-pitch/radiation/inertial/remote/remote-stable
m.guidanceLaw = getprop(m.nodeString~"navigation"); # guidance-law: direct/OPN/PN/APN/PNxxyy/APNxxyy (use direct for pure pursuit, use PN for A/A missiles, use APN for modern SAM missiles PN for older, use PNxxyy/APNxxyy for surface to air where xx is degrees to aim above target, yy is seconds it will do that). GPN is APN for winged glidebombs.
m.guidance = getprop(m.nodeString~"guidance"); # heat/radar/semi-radar/laser/gps/vision/unguided/level/gyro-pitch/radiation/inertial/remote/remote-stable/command
m.guidanceLaw = getprop(m.nodeString~"navigation"); # guidance-law: direct/OPN/PN/APN/PNxxyy/APNxxyy/LOS (use direct for pure pursuit, use PN for A/A missiles, use APN for modern SAM missiles PN for older, use PNxxyy/APNxxyy for surface to air where xx is degrees to aim above target, yy is seconds it will do that). GPN is APN for winged glidebombs.
m.guidanceLawHorizInit = getprop(m.nodeString~"navigation-init-pure-15"); # Bool. Guide in horizontal plane using pure pursuit until target with 15 deg of nose, before switching to <navigation>
m.pro_constant = getprop(m.nodeString~"proportionality-constant"); # Constant for how sensitive proportional navigation is to target speed/acc. Normally between 3-6. [optional]
m.all_aspect = getprop(m.nodeString~"all-aspect"); # bool. set to false if missile only locks on reliably to rear of target aircraft
@ -336,9 +338,11 @@ var AIM = {
# engine
m.force_lbf_1 = getprop(m.nodeString~"thrust-lbf-stage-1"); # stage 1 thrust [optional]
m.force_lbf_2 = getprop(m.nodeString~"thrust-lbf-stage-2"); # stage 2 thrust [optional]
m.force_lbf_3 = getprop(m.nodeString~"thrust-lbf-stage-3"); # stage 3 thrust [optional]
m.stage_1_duration = getprop(m.nodeString~"stage-1-duration-sec"); # stage 1 duration [optional]
m.stage_gap_duration = getprop(m.nodeString~"stage-gap-duration-sec"); # gap duration between stage 1 and 2 [optional]
m.stage_2_duration = getprop(m.nodeString~"stage-2-duration-sec"); # stage 2 duration [optional]
m.stage_3_duration = getprop(m.nodeString~"stage-3-duration-sec"); # stage 3 duration [optional]
m.weight_fuel_lbm = getprop(m.nodeString~"weight-fuel-lbm"); # fuel weight [optional]. If this property is not present, it won't lose weight as the fuel is used.
m.vector_thrust = getprop(m.nodeString~"vector-thrust"); # Boolean. This will make less drag due to high G turns while engine is running. [optional]
m.engineEnabled = getprop(m.nodeString~"engine-enabled"); # Boolean. If engine will start at all. [optional]
@ -531,6 +535,9 @@ var AIM = {
if (m.force_lbf_2 == nil) {
m.force_lbf_2 = 0;
}
if (m.force_lbf_3 == nil) {
m.force_lbf_3 = 0;
}
if(m.stage_gap_duration == nil) {
m.stage_gap_duration = 0;
}
@ -540,6 +547,9 @@ var AIM = {
if(m.stage_2_duration == nil) {
m.stage_2_duration = 0;
}
if(m.stage_3_duration == nil) {
m.stage_3_duration = 0;
}
if (m.destruct_when_free == nil) {
m.destruct_when_free = FALSE;
}
@ -783,6 +793,8 @@ var AIM = {
m.vert_closing_rate_fps = -1;
m.usingTGPPoint = 0;
m.rotate_token = 0;
m.CREv = 0;
m.CREh = 0;
#
# Terrain following
@ -810,7 +822,8 @@ var AIM = {
m.maxMach = 0;
m.maxMach1 = 0;#stage 1
m.maxMach2 = 0;#stage 2
m.maxMach3 = 0;#stage 2 end
m.maxMach3 = 0;#stage 3
m.maxMach4 = 0;#stage 3 end
m.energyBleedKt = 0;
#
@ -1422,6 +1435,8 @@ var AIM = {
me.force_lbf_1 = 0;
me.stage_2_duration = 0;
me.force_lbf_2 = 0;
me.stage_3_duration = 0;
me.force_lbf_3 = 0;
me.stage_gap_duration = 0;
me.drop_time = 10000;
me.inert = TRUE;
@ -1649,10 +1664,12 @@ var AIM = {
# find the fuel consumption - lbm/sec
var impulse1 = me.force_lbf_1 * me.stage_1_duration; # lbf*s
var impulse2 = me.force_lbf_2 * me.stage_2_duration; # lbf*s
me.impulseT = impulse1 + impulse2; # lbf*s
var impulse3 = me.force_lbf_3 * me.stage_3_duration; # lbf*s
me.impulseT = impulse1 + impulse2 + impulse3; # lbf*s
me.fuel_per_impulse = me.weight_fuel_lbm / me.impulseT;# lbm/(lbf*s)
me.fuel_per_sec_1 = (me.fuel_per_impulse * impulse1) / me.stage_1_duration;# lbm/s
me.fuel_per_sec_2 = (me.fuel_per_impulse * impulse2) / me.stage_2_duration;# lbm/s
me.fuel_per_sec_1 = me.stage_1_duration == 0?0:(me.fuel_per_impulse * impulse1) / me.stage_1_duration;# lbm/s
me.fuel_per_sec_2 = me.stage_2_duration == 0?0:(me.fuel_per_impulse * impulse2) / me.stage_2_duration;# lbm/s
me.fuel_per_sec_3 = me.stage_3_duration == 0?0:(me.fuel_per_impulse * impulse3) / me.stage_3_duration;# lbm/s
me.printExtendedStats();
@ -1720,7 +1737,9 @@ var AIM = {
}
var nav = "";
var nav2 = "";
if (me.guidanceLaw == "direct") {
if (me.guidanceLaw == "LOS") {
nav = "Line-of-sight";
} elsif (me.guidanceLaw == "direct") {
nav = "Pure pursuit."
} elsif (me.guidanceLaw == "OPN") {
nav = "Original Proportional navigation. Proportionality constant is "~me.pro_constant;
@ -1744,7 +1763,9 @@ var AIM = {
nav2 = sprintf("Before APN it will aim %d degrees above target for %d seconds.",xx,yy);
}
var stages = 0;
if (me.force_lbf_1 > 0 and me.stage_1_duration > 0 and me.force_lbf_2 > 0 and me.stage_2_duration > 0) {
if (me.force_lbf_1 > 0 and me.stage_1_duration > 0 and me.force_lbf_2 > 0 and me.stage_2_duration > 0 and me.force_lbf_3 > 0 and me.stage_3_duration > 0) {
stages = 3;
} elsif (me.force_lbf_1 > 0 and me.stage_1_duration > 0 and me.force_lbf_2 > 0 and me.stage_2_duration > 0) {
stages = 2;
} elsif (me.force_lbf_1 > 0 and me.stage_1_duration > 0) {
stages = 1;
@ -1866,6 +1887,9 @@ var AIM = {
if (me.stage_gap_duration > 0) {
me.printStats("Stage 1 to 2 time gap: %.1f seconds.", me.stage_gap_duration);
}
if (stages > 2) {
me.printStats("Stage 3: %d lbf for %.1f seconds.", me.force_lbf_3, me.stage_3_duration);
}
}
me.printStats("%s",vector);
if (!me.weight_fuel_lbm) {
@ -2137,9 +2161,12 @@ var AIM = {
if (me.speed_m > me.maxMach2 and me.life_time > (me.drop_time + me.stage_1_duration) and me.life_time <= (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration)) {
me.maxMach2 = me.speed_m;
}
if (me.maxMach3 == 0 and me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration)) {
if (me.speed_m > me.maxMach3 and me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration) and me.life_time <= (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration+me.stage_3_duration)) {
me.maxMach3 = me.speed_m;
}
if (me.maxMach4 == 0 and me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration+me.stage_3_duration)) {
me.maxMach4 = me.speed_m;
}
me.Cd = me.drag(me.speed_m,me.myG);
@ -2448,7 +2475,10 @@ var AIM = {
if (me.exploded == TRUE) {
me.printStats("%s max absolute %.2f Mach. Max relative %.2f Mach. Max alt %6d ft. Terminal %.2f mach.", me.type, me.maxMach, me.maxMach-me.startMach, me.maxAlt, me.speed_m);
me.printStats("%s max relative %d ft/s.", me.type, me.maxFPS-me.startFPS);
me.printStats(" Absolute %.2f Mach in stage 1. Absolute %.2f Mach in stage 2. Absolute %.2f mach propulsion end.", me.maxMach1, me.maxMach2, me.maxMach3);
me.printStats(" Absolute %.2f Mach in stage 1.", me.maxMach1);
if (me.force_lbf_2 > 0) me.printStats(" Absolute %.2f Mach in stage 2.", me.maxMach2);
if (me.force_lbf_3 > 0) me.printStats(" Absolute %.2f Mach in stage 3.", me.maxMach3);
if (me.maxMach4 > 0) me.printStats(" Absolute %.2f mach propulsion end.", me.maxMach4);
me.printStats(" Fired at %s from %.2f Mach, %5d ft at %3d NM distance. Flew %.1f NM.", me.callsign, me.startMach, me.startAlt, me.startDist * M2NM, me.ac_init.direct_distance_to(me.coord)*M2NM);
# We exploded, and start the sound propagation towards the plane
me.sndSpeed = me.sound_fps;
@ -2523,8 +2553,10 @@ var AIM = {
# consume fuel
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration)) {
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration+me.stage_3_duration)) {
me.weight_current = me.weight_launch_lbm - me.weight_fuel_lbm;
} elsif (me.life_time > (me.drop_time + me.stage_1_duration+me.stage_gap_duration + me.stage_2_duration)) {
me.weight_current = me.weight_current - me.fuel_per_sec_3 * me.dt;
} elsif (me.life_time > (me.drop_time + me.stage_1_duration+me.stage_gap_duration)) {
me.weight_current = me.weight_current - me.fuel_per_sec_2 * me.dt;
} elsif (me.life_time > me.drop_time and me.life_time < (me.drop_time + me.stage_1_duration)) {
@ -2534,59 +2566,7 @@ var AIM = {
me.mass = me.weight_current * LBM2SLUGS;
# telemetry
if (me.data == TRUE) {
me.eta = me.free == TRUE or me.vert_closing_rate_fps == -1?-1:(me["t_go"]!=nil?me.t_go:(me.dist_curr*M2FT)/me.vert_closing_rate_fps);
if (me.eta < 0) me.eta = -1;
me.hit = 50;# in percent
if (me.life_time > me.drop_time+me.stage_1_duration + me.gnd_launch?(me.stage_2_duration + me.stage_gap_duration):0) {
# little less optimistic after reaching topspeed
if (me.selfdestruct_time-me.life_time < me.eta) {
# reduce alot if eta later than lifespan
me.hit -= 75;
} elsif (me.eta != -1 and (me.selfdestruct_time-me.life_time) != 0) {
# if its hitting late in life, then reduce
me.hit -= (me.eta / (me.selfdestruct_time-me.life_time)) * 25;
}
if (me.eta > 0) {
# penalty if eta is high
me.hit -= me.clamp(40*me.eta/(me.life_time*0.85), 0, 40);
}
if (me.eta < 0) {
# penalty if eta is incomputable
me.hit -= 75;
}
}
if (me.curr_deviation_h != nil and me.dist_curr > 50) {
# penalty for target being off-bore
me.hit -= math.abs(me.curr_deviation_h)/2.5;
}
if (me.guiding == TRUE and me.t_speed_fps != nil and me.old_speed_fps > me.t_speed_fps and me.t_speed_fps != 0) {
# bonus for traveling faster than target
me.hit += me.clamp((me.old_speed_fps / me.t_speed_fps)*15,-25,50);
}
if (me.free == TRUE or (me.gnd_launch and (me.chaffLock or me.flareLock))) {
# penalty for not longer guiding
me.hit -= 75;
}
me.hit = int(me.clamp(me.hit, 0, 90));
me.ai.getNode("ETA").setIntValue(me.eta);
me.ai.getNode("hit").setIntValue(me.hit);
if (me.gnd_launch) {
setprop("sam/impact"~me.ID,me.eta);
setprop("sam/hit"~me.ID,me.hit);
}
if (me["prevETA"] != nil) {
if (me.prevETA < me.eta) {
# reset the lowest eta to allow it to increase.
AIM.setETA(nil);
}
AIM.setETA(me.eta, me["prevETA"]);
}
me.prevETA = me["eta"];
}
me.sendTelemetry();
if (me.life_time - me.last_noti > me.noti_time and getprop("payload/armament/msg")) {
# notify in flight using Emesary.
@ -2680,6 +2660,62 @@ var AIM = {
}
},
sendTelemetry: func {
if (me.data == TRUE) {
me.eta = me.free == TRUE or me.vert_closing_rate_fps == -1?-1:(me["t_go"]!=nil?me.t_go:(me.dist_curr*M2FT)/me.vert_closing_rate_fps);
if (me.eta < 0) me.eta = -1;
me.hit = 50;# in percent
if (me.life_time > me.drop_time+me.stage_1_duration + me.gnd_launch?(me.stage_2_duration + me.stage_gap_duration):0) {
# little less optimistic after reaching topspeed
if (me.selfdestruct_time-me.life_time < me.eta) {
# reduce alot if eta later than lifespan
me.hit -= 75;
} elsif (me.eta != -1 and (me.selfdestruct_time-me.life_time) != 0) {
# if its hitting late in life, then reduce
me.hit -= (me.eta / (me.selfdestruct_time-me.life_time)) * 25;
}
if (me.eta > 0) {
# penalty if eta is high
me.hit -= me.clamp(40*me.eta/(me.life_time*0.85), 0, 40);
}
if (me.eta < 0) {
# penalty if eta is incomputable
me.hit -= 75;
}
}
if (me.curr_deviation_h != nil and me.dist_curr > 50) {
# penalty for target being off-bore
me.hit -= math.abs(me.curr_deviation_h)/2.5;
}
if (me.guiding == TRUE and me.t_speed_fps != nil and me.old_speed_fps > me.t_speed_fps and me.t_speed_fps != 0) {
# bonus for traveling faster than target
me.hit += me.clamp((me.old_speed_fps / me.t_speed_fps)*15,-25,50);
}
if (me.free == TRUE or (me.gnd_launch and (me.chaffLock or me.flareLock))) {
# penalty for not longer guiding
me.hit -= 75;
}
me.hit = int(me.clamp(me.hit, 0, 90));
me.ai.getNode("ETA").setIntValue(me.eta);
me.ai.getNode("hit").setIntValue(me.hit);
if (me.gnd_launch) {
setprop("sam/impact"~me.ID,me.eta);
setprop("sam/hit"~me.ID,me.hit);
}
if (me["prevETA"] != nil) {
if (me.prevETA < me.eta) {
# reset the lowest eta to allow it to increase.
AIM.setETA(nil);
}
AIM.setETA(me.eta, me["prevETA"]);
}
me.prevETA = me["eta"];
}
},
getGPS: func(x, y, z, pitch, head=nil, roll=nil) {
#
# get Coord from body position. x,y,z must be in meters.
@ -2816,8 +2852,10 @@ var AIM = {
#
me.thrust_lbf = 0;# pounds force (lbf)
if (me.engineEnabled) {
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration)) {
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration + me.stage_3_duration)) {
me.thrust_lbf = 0;
} elsif (me.life_time > me.stage_1_duration + me.stage_gap_duration + me.drop_time + me.stage_2_duration) {
me.thrust_lbf = me.force_lbf_3;
} elsif (me.life_time > me.stage_1_duration + me.stage_gap_duration + me.drop_time) {
me.thrust_lbf = me.force_lbf_2;
} elsif (me.life_time > me.drop_time and me.life_time < me.drop_time+me.stage_1_duration) {
@ -2903,7 +2941,7 @@ var AIM = {
},
setFirst: func() {
if (me.smoke_prop.getValue() == TRUE) {
if (me.smoke_prop.getValue() == TRUE and me.life_time < first_in_air_max_sec) {
if (me.first == TRUE or first_in_air == FALSE) {
# report position over MP for MP animation of smoke trail.
me.first = TRUE;
@ -2915,7 +2953,7 @@ var AIM = {
me.mpAltft.setDoubleValue(me.coord.alt()*M2FT);
}
}
} elsif (me.first == TRUE and me.life_time > me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration) {
} elsif (me.first == TRUE and (me.life_time >= first_in_air_max_sec or me.life_time > me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration)) {
# this weapon was reporting its position over MP, but now its fuel has used up. So allow for another to do that.
me.resetFirst();
}
@ -3136,8 +3174,8 @@ var AIM = {
me.chaffLast = me.chaffNumber;
me.chaffTime = getprop("sim/time/elapsed-sec");
me.aspectDeg = me.aspectToExhaust(me.coord, me.Tgt) / 180;# 0 = viewing engine, 1 = front
me.semi = me.guidance == "semi-radar"?0.5:1;
me.chaffChance = (1-me.chaffResistance);#*me.semi;
me.semi = me.guidance == "semi-radar"?1:1;
me.chaffChance = (1-me.chaffResistance)*me.semi;
me.chaffLock = rand() < (me.chaffChance - (me.chaffChance * 0.5 * me.aspectDeg));# 50% less chance to be fooled if front aspect
if (me.chaffLock == TRUE) {
@ -3224,6 +3262,11 @@ var AIM = {
me.printStats(me.type~": Not guiding (lost radar reflection, gave up)");
me.free = TRUE;
}
} elsif (me.guidance == "command" and (me.Tgt == nil or !me.Tgt.isCommandActive())) {
# if its command guided and the control no longer sends commands
me.guiding = FALSE;
me.printStats(me.type~": Not guiding (no commands from controller, gave up)");
me.free = TRUE;
} elsif (me.guidance == "radiation" and me.is_radiating_me(me.Tgt) == FALSE) {
# if its radiation guided and the target is not illuminating us with radiation
me.guiding = FALSE;
@ -3639,7 +3682,7 @@ var AIM = {
# augmented proportional navigation for heading #
#################################################
if (me.guidanceLaw == "direct") {
if (me.guidanceLaw == "direct" or (me.guidanceLaw == "LOS" and me.life_time < 4)) {
# pure pursuit
me.raw_steer_signal_head = me.curr_deviation_h;
if (me.cruise_or_loft == FALSE) {
@ -3738,8 +3781,30 @@ var AIM = {
me.t_go = me.myMath.dotProduct(me.R_tm,me.R_tm)/me.myMath.dotProduct(me.R_tm, me.V_tm);
#printf("time_to_go %.1f, closing %d",me.t_go,me.vert_closing_rate_fps);
# Horizontal PN:
if (me.apn == 1) {
# Horizontal homing:
if (me.guidanceLaw == "LOS") {
me.K1 = 2.5;
me.K2 = 10.0;
me.R_m = me.ac_init.distance_to(me.coord)*M2FT;
me.course_to_missile = me.ac_init.course_to(me.coord);
me.course_to_target = me.ac_init.course_to(me.t_coord);
me.CREh_old = me.CREh;
# cross range error:
me.CREh = me.R_m*math.sin(me.clamp(geo.normdeg180(me.course_to_target - me.course_to_missile),-89,89)*D2R);
me.CREh_dot = (me.CREh - me.CREh_old)/me.dt;
me.acc_lateral_fps2 = me.K1*me.CREh_dot + me.K2*me.CREh;
me.toBody = math.cos(geo.normdeg180(me.hdg-me.course_to_target)*D2R);
if (me.toBody==0) me.toBody=0.0001;
me.acc_lateral_fps2 /= me.toBody;
me.velocity_vector_length_fps = me.clamp(me.old_speed_horz_fps, 0.0001, 1000000);
me.commanded_lateral_vector_length_fps = me.acc_lateral_fps2*me.dt;
me.raw_steer_signal_head = R2D*me.commanded_lateral_vector_length_fps/me.velocity_vector_length_fps;
#me.raw_steer_signal_head = me.curr_deviation_h;
} elsif (me.apn == 1) {
# APN (constant best at 5, but the higher value the more sensitive to noise)
# Augmented proportional navigation. Takes target acc. into account. Invented for SAMs.
me.toBody = math.cos(me.curr_deviation_h*D2R);#convert perpendicular LOS acc. to perpendicular body acc.
@ -3823,8 +3888,26 @@ var AIM = {
me.last_t_elev_norm_speed = me.t_LOS_elev_norm_speed;
#printf("Target acc. perpendicular to LOS (positive up): %.1f G.", me.t_LOS_elev_norm_acc/g_fps);
# Vertical PN:
if (me.apn == 1) {
# Vertical homing:
if (me.guidanceLaw == "LOS") {
me.R_m = me.ac_init.direct_distance_to(me.coord)*M2FT;
me.pitch_to_missile = me.myMath.getPitch(me.ac_init,me.coord);
me.pitch_to_target = me.myMath.getPitch(me.ac_init,me.t_coord);
me.CREv_old = me.CREv;
# Cross range error
me.CREv = me.R_m*math.sin((me.pitch_to_target - me.pitch_to_missile)*D2R);
me.CREv_dot = (me.CREv - me.CREv_old)/me.dt;
me.acc_upwards_fps2 = me.K1*me.CREv_dot + me.K2*me.CREv;
# Convert perpendicular LOS acc. to perpendicular body acc.
me.toBody = math.cos((me.pitch - me.pitch_to_target)*D2R);
if (me.toBody==0) me.toBody=0.00001;
me.acc_upwards_fps2 /= me.toBody;
# Apply the acc.
me.velocity_vector_length_fps = me.clamp(me.old_speed_fps, 0.0001, 1000000);
me.commanded_upwards_vector_length_fps = me.acc_upwards_fps2*me.dt;
me.raw_steer_signal_elev = R2D*me.commanded_upwards_vector_length_fps/me.velocity_vector_length_fps;
} elsif (me.apn == 1) {
# APN (constant best at 5, but the higher value the more sensitive to noise)
# Augmented proportional navigation. Takes target acc. into account. Invented for SAMs.
me.toBody = math.cos(me.curr_deviation_e*D2R);#convert perpendicular LOS acc. to perpendicular body acc.

@ -139,6 +139,13 @@ var Contact = {
return isNotBehindTerrain(me.node)[0];
},
isCommandActive: func {
if (getprop("/carrier/sunk") == 1) {
return 0;
}
return isNotBehindTerrain(me.node)[0];
},
getUnique: func () {
if (me.unique == nil) {
me.unique = me.node.getNode("unique");

@ -283,8 +283,8 @@
<long-name type="string">Volga-M</long-name>
<vol-search type="double">0.00</vol-search>
<vol-track type="double">0.00</vol-track>
<guidance type="string">semi-radar</guidance>
<navigation type="string">PN1010</navigation> <!-- horizontal APN, vertical aim 5 degrees above for 6 seconds -->
<guidance type="string">command</guidance>
<navigation type="string">LOS</navigation> <!-- horizontal APN, vertical aim 5 degrees above for 6 seconds -->
<max-fire-range-nm type="int">36</max-fire-range-nm> <!-- -->
<FCS-field-deg type="int">360</FCS-field-deg>
<seeker-field-deg type="int">360</seeker-field-deg>
@ -342,10 +342,26 @@
<impact1 type="double">-1</impact1>
<impact2 type="double">-1</impact2>
<impact3 type="double">-1</impact3>
<impact4 type="double">-1</impact4>
<impact5 type="double">-1</impact5>
<impact6 type="double">-1</impact6>
<impact7 type="double">-1</impact7>
<impact8 type="double">-1</impact8>
<impact9 type="double">-1</impact9>
<impact10 type="double">-1</impact10>
<impact11 type="double">-1</impact11>
<hit0 type="double">-1</hit0>
<hit1 type="double">-1</hit1>
<hit2 type="double">-1</hit2>
<hit3 type="double">-1</hit3>
<hit4 type="double">-1</hit4>
<hit5 type="double">-1</hit5>
<hit6 type="double">-1</hit6>
<hit7 type="double">-1</hit7>
<hit8 type="double">-1</hit8>
<hit9 type="double">-1</hit9>
<hit10 type="double">-1</hit10>
<hit11 type="double">-1</hit11>
<missiles type="double">1</missiles>
<timeleft type="double">600</timeleft>
<damage type="double">100</damage>

@ -332,7 +332,7 @@ var fire_control = func(mp, my_pos) {
var score = 0;
var priority = getprop("priority");
if (priority==PRIO_NORM) {
# nop
score = rand()-0.5;
} elsif (priority==PRIO_HIGH) {
score = target_altitude;
} elsif (priority==PRIO_LOW) {
@ -358,6 +358,7 @@ var fire_control = func(mp, my_pos) {
}
if ( target_distance * M2NM > missile_max_distance ) { return [mp,false,0,0]; }
if ( target_distance * M2NM < missile_min_distance ) { return [mp,false,0,0]; }
@ -377,7 +378,7 @@ var fire_control = func(mp, my_pos) {
var score = 0;
var priority = getprop("priority");
if (priority==PRIO_NORM) {
# nop
score = rand()-0.5;
} elsif (priority==PRIO_HIGH) {
score = target_altitude;
} elsif (priority==PRIO_LOW) {
@ -409,6 +410,7 @@ var reload = func(force = 1) {
if (armament.AIM.new(i,missile_name,missile_brevity, midflight) != -1) {
#if statement just in case reload was called before all missiles were fired. Cause avoid calling search() on same missile twice.
armament.AIM.active[i].radarZ = radar_elevation_above_terrain_m;
armament.AIM.active[i].start();
}
}
@ -426,6 +428,9 @@ var reload = func(force = 1) {
}
var autoreload = func() {
if ( getprop("/carrier/sunk") == 1 or getprop("/carrier/disabled") == 1) {
return;
}
if (reloading) {
reloading = 0;
if (ACTIVE_MISSILE > NUM_MISSILES or ROUNDS == 0) {
@ -497,7 +502,7 @@ var missile_launch = func(mp, launchtime, my_pos) {
setprop("sam/missiles",(NUM_MISSILES+1-ACTIVE_MISSILE));
clearSingleLock();
return;
} elsif ((systime() - launchtime) > (lockon_time*2) or radar_logic.isNotBehindTerrain(mp)[0] == 0) {
} elsif ((systime() - launchtime) > (lockon_time*1.5) or radar_logic.isNotBehindTerrain(mp)[0] == 0) {
# launch cancelled so it dont forever goes in this loop and dont allow for other firings.
if (lu != nil) {
lu.tracking = 0;
@ -510,7 +515,7 @@ var missile_launch = func(mp, launchtime, my_pos) {
clearSingleLock();
return;
} else {
info = info~" (tracking)";
info = sprintf("%s (tracking %d nm at %d ft)", info, armament.contact.get_range(), armament.contact.get_altitude());
if (systime()-missile_release_time > 1.5) {
var tgt_dir = target_bearing-getprop("orientation/heading-deg");
var max_dir = launch_update_time*align_speed_dps;

@ -154,6 +154,7 @@ var LBM2SLUGS = 1/SLUGS2LBM;
var slugs_to_lbm = SLUGS2LBM;# since various aircraft use this from outside missile, leaving it for backwards compat.
var first_in_air = FALSE;# first missile is in the air, other missiles should not write to MP.
var first_in_air_max_sec = 30;
var versionString = getprop("sim/version/flightgear");
var version = split(".", versionString);
@ -212,6 +213,7 @@ var contactPoint = nil;
# isPainted() - Tells if this target is still being radar tracked by the launch platform, only used in semi-radar guided missiles.
# isLaserPainted() - Tells if this target is still being tracked by the launch platform, only used by laser guided ordnance.
# isRadiating(coord) - Tell if anti-radiation missile is hit by radiation from target. coord is the weapon position.
# isCommandActive()
# isVirtual() - Tells if the target is just a position, and should not be considered for damage.
# get_closure_rate() - closure rate in kt
@ -314,8 +316,8 @@ var AIM = {
m.asc = getprop(m.nodeString~"attack-steering-cue-enabled");# Bool. ASC enabled.
# navigation, guiding and seekerhead
m.max_seeker_dev = getprop(m.nodeString~"seeker-field-deg") / 2; # missiles own seekers total FOV diameter.
m.guidance = getprop(m.nodeString~"guidance"); # heat/radar/semi-radar/laser/gps/vision/unguided/level/gyro-pitch/radiation/inertial/remote/remote-stable
m.guidanceLaw = getprop(m.nodeString~"navigation"); # guidance-law: direct/OPN/PN/APN/PNxxyy/APNxxyy (use direct for pure pursuit, use PN for A/A missiles, use APN for modern SAM missiles PN for older, use PNxxyy/APNxxyy for surface to air where xx is degrees to aim above target, yy is seconds it will do that). GPN is APN for winged glidebombs.
m.guidance = getprop(m.nodeString~"guidance"); # heat/radar/semi-radar/laser/gps/vision/unguided/level/gyro-pitch/radiation/inertial/remote/remote-stable/command
m.guidanceLaw = getprop(m.nodeString~"navigation"); # guidance-law: direct/OPN/PN/APN/PNxxyy/APNxxyy/LOS (use direct for pure pursuit, use PN for A/A missiles, use APN for modern SAM missiles PN for older, use PNxxyy/APNxxyy for surface to air where xx is degrees to aim above target, yy is seconds it will do that). GPN is APN for winged glidebombs.
m.guidanceLawHorizInit = getprop(m.nodeString~"navigation-init-pure-15"); # Bool. Guide in horizontal plane using pure pursuit until target with 15 deg of nose, before switching to <navigation>
m.pro_constant = getprop(m.nodeString~"proportionality-constant"); # Constant for how sensitive proportional navigation is to target speed/acc. Normally between 3-6. [optional]
m.all_aspect = getprop(m.nodeString~"all-aspect"); # bool. set to false if missile only locks on reliably to rear of target aircraft
@ -336,9 +338,11 @@ var AIM = {
# engine
m.force_lbf_1 = getprop(m.nodeString~"thrust-lbf-stage-1"); # stage 1 thrust [optional]
m.force_lbf_2 = getprop(m.nodeString~"thrust-lbf-stage-2"); # stage 2 thrust [optional]
m.force_lbf_3 = getprop(m.nodeString~"thrust-lbf-stage-3"); # stage 3 thrust [optional]
m.stage_1_duration = getprop(m.nodeString~"stage-1-duration-sec"); # stage 1 duration [optional]
m.stage_gap_duration = getprop(m.nodeString~"stage-gap-duration-sec"); # gap duration between stage 1 and 2 [optional]
m.stage_2_duration = getprop(m.nodeString~"stage-2-duration-sec"); # stage 2 duration [optional]
m.stage_3_duration = getprop(m.nodeString~"stage-3-duration-sec"); # stage 3 duration [optional]
m.weight_fuel_lbm = getprop(m.nodeString~"weight-fuel-lbm"); # fuel weight [optional]. If this property is not present, it won't lose weight as the fuel is used.
m.vector_thrust = getprop(m.nodeString~"vector-thrust"); # Boolean. This will make less drag due to high G turns while engine is running. [optional]
m.engineEnabled = getprop(m.nodeString~"engine-enabled"); # Boolean. If engine will start at all. [optional]
@ -531,6 +535,9 @@ var AIM = {
if (m.force_lbf_2 == nil) {
m.force_lbf_2 = 0;
}
if (m.force_lbf_3 == nil) {
m.force_lbf_3 = 0;
}
if(m.stage_gap_duration == nil) {
m.stage_gap_duration = 0;
}
@ -540,6 +547,9 @@ var AIM = {
if(m.stage_2_duration == nil) {
m.stage_2_duration = 0;
}
if(m.stage_3_duration == nil) {
m.stage_3_duration = 0;
}
if (m.destruct_when_free == nil) {
m.destruct_when_free = FALSE;
}
@ -783,6 +793,8 @@ var AIM = {
m.vert_closing_rate_fps = -1;
m.usingTGPPoint = 0;
m.rotate_token = 0;
m.CREv = 0;
m.CREh = 0;
#
# Terrain following
@ -810,7 +822,8 @@ var AIM = {
m.maxMach = 0;
m.maxMach1 = 0;#stage 1
m.maxMach2 = 0;#stage 2
m.maxMach3 = 0;#stage 2 end
m.maxMach3 = 0;#stage 3
m.maxMach4 = 0;#stage 3 end
m.energyBleedKt = 0;
#
@ -1422,6 +1435,8 @@ var AIM = {
me.force_lbf_1 = 0;
me.stage_2_duration = 0;
me.force_lbf_2 = 0;
me.stage_3_duration = 0;
me.force_lbf_3 = 0;
me.stage_gap_duration = 0;
me.drop_time = 10000;
me.inert = TRUE;
@ -1649,10 +1664,12 @@ var AIM = {
# find the fuel consumption - lbm/sec
var impulse1 = me.force_lbf_1 * me.stage_1_duration; # lbf*s
var impulse2 = me.force_lbf_2 * me.stage_2_duration; # lbf*s
me.impulseT = impulse1 + impulse2; # lbf*s
var impulse3 = me.force_lbf_3 * me.stage_3_duration; # lbf*s
me.impulseT = impulse1 + impulse2 + impulse3; # lbf*s
me.fuel_per_impulse = me.weight_fuel_lbm / me.impulseT;# lbm/(lbf*s)
me.fuel_per_sec_1 = (me.fuel_per_impulse * impulse1) / me.stage_1_duration;# lbm/s
me.fuel_per_sec_2 = (me.fuel_per_impulse * impulse2) / me.stage_2_duration;# lbm/s
me.fuel_per_sec_1 = me.stage_1_duration == 0?0:(me.fuel_per_impulse * impulse1) / me.stage_1_duration;# lbm/s
me.fuel_per_sec_2 = me.stage_2_duration == 0?0:(me.fuel_per_impulse * impulse2) / me.stage_2_duration;# lbm/s
me.fuel_per_sec_3 = me.stage_3_duration == 0?0:(me.fuel_per_impulse * impulse3) / me.stage_3_duration;# lbm/s
me.printExtendedStats();
@ -1720,7 +1737,9 @@ var AIM = {
}
var nav = "";
var nav2 = "";
if (me.guidanceLaw == "direct") {
if (me.guidanceLaw == "LOS") {
nav = "Line-of-sight";
} elsif (me.guidanceLaw == "direct") {
nav = "Pure pursuit."
} elsif (me.guidanceLaw == "OPN") {
nav = "Original Proportional navigation. Proportionality constant is "~me.pro_constant;
@ -1744,7 +1763,9 @@ var AIM = {
nav2 = sprintf("Before APN it will aim %d degrees above target for %d seconds.",xx,yy);
}
var stages = 0;
if (me.force_lbf_1 > 0 and me.stage_1_duration > 0 and me.force_lbf_2 > 0 and me.stage_2_duration > 0) {
if (me.force_lbf_1 > 0 and me.stage_1_duration > 0 and me.force_lbf_2 > 0 and me.stage_2_duration > 0 and me.force_lbf_3 > 0 and me.stage_3_duration > 0) {
stages = 3;
} elsif (me.force_lbf_1 > 0 and me.stage_1_duration > 0 and me.force_lbf_2 > 0 and me.stage_2_duration > 0) {
stages = 2;
} elsif (me.force_lbf_1 > 0 and me.stage_1_duration > 0) {
stages = 1;
@ -1866,6 +1887,9 @@ var AIM = {
if (me.stage_gap_duration > 0) {
me.printStats("Stage 1 to 2 time gap: %.1f seconds.", me.stage_gap_duration);
}
if (stages > 2) {
me.printStats("Stage 3: %d lbf for %.1f seconds.", me.force_lbf_3, me.stage_3_duration);
}
}
me.printStats("%s",vector);
if (!me.weight_fuel_lbm) {
@ -2137,9 +2161,12 @@ var AIM = {
if (me.speed_m > me.maxMach2 and me.life_time > (me.drop_time + me.stage_1_duration) and me.life_time <= (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration)) {
me.maxMach2 = me.speed_m;
}
if (me.maxMach3 == 0 and me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration)) {
if (me.speed_m > me.maxMach3 and me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration) and me.life_time <= (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration+me.stage_3_duration)) {
me.maxMach3 = me.speed_m;
}
if (me.maxMach4 == 0 and me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration+me.stage_3_duration)) {
me.maxMach4 = me.speed_m;
}
me.Cd = me.drag(me.speed_m,me.myG);
@ -2448,7 +2475,10 @@ var AIM = {
if (me.exploded == TRUE) {
me.printStats("%s max absolute %.2f Mach. Max relative %.2f Mach. Max alt %6d ft. Terminal %.2f mach.", me.type, me.maxMach, me.maxMach-me.startMach, me.maxAlt, me.speed_m);
me.printStats("%s max relative %d ft/s.", me.type, me.maxFPS-me.startFPS);
me.printStats(" Absolute %.2f Mach in stage 1. Absolute %.2f Mach in stage 2. Absolute %.2f mach propulsion end.", me.maxMach1, me.maxMach2, me.maxMach3);
me.printStats(" Absolute %.2f Mach in stage 1.", me.maxMach1);
if (me.force_lbf_2 > 0) me.printStats(" Absolute %.2f Mach in stage 2.", me.maxMach2);
if (me.force_lbf_3 > 0) me.printStats(" Absolute %.2f Mach in stage 3.", me.maxMach3);
if (me.maxMach4 > 0) me.printStats(" Absolute %.2f mach propulsion end.", me.maxMach4);
me.printStats(" Fired at %s from %.2f Mach, %5d ft at %3d NM distance. Flew %.1f NM.", me.callsign, me.startMach, me.startAlt, me.startDist * M2NM, me.ac_init.direct_distance_to(me.coord)*M2NM);
# We exploded, and start the sound propagation towards the plane
me.sndSpeed = me.sound_fps;
@ -2523,8 +2553,10 @@ var AIM = {
# consume fuel
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration)) {
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration+me.stage_2_duration+me.stage_3_duration)) {
me.weight_current = me.weight_launch_lbm - me.weight_fuel_lbm;
} elsif (me.life_time > (me.drop_time + me.stage_1_duration+me.stage_gap_duration + me.stage_2_duration)) {
me.weight_current = me.weight_current - me.fuel_per_sec_3 * me.dt;
} elsif (me.life_time > (me.drop_time + me.stage_1_duration+me.stage_gap_duration)) {
me.weight_current = me.weight_current - me.fuel_per_sec_2 * me.dt;
} elsif (me.life_time > me.drop_time and me.life_time < (me.drop_time + me.stage_1_duration)) {
@ -2534,59 +2566,7 @@ var AIM = {
me.mass = me.weight_current * LBM2SLUGS;
# telemetry
if (me.data == TRUE) {
me.eta = me.free == TRUE or me.vert_closing_rate_fps == -1?-1:(me["t_go"]!=nil?me.t_go:(me.dist_curr*M2FT)/me.vert_closing_rate_fps);
if (me.eta < 0) me.eta = -1;
me.hit = 50;# in percent
if (me.life_time > me.drop_time+me.stage_1_duration + me.gnd_launch?(me.stage_2_duration + me.stage_gap_duration):0) {
# little less optimistic after reaching topspeed
if (me.selfdestruct_time-me.life_time < me.eta) {
# reduce alot if eta later than lifespan
me.hit -= 75;
} elsif (me.eta != -1 and (me.selfdestruct_time-me.life_time) != 0) {
# if its hitting late in life, then reduce
me.hit -= (me.eta / (me.selfdestruct_time-me.life_time)) * 25;
}
if (me.eta > 0) {
# penalty if eta is high
me.hit -= me.clamp(40*me.eta/(me.life_time*0.85), 0, 40);
}
if (me.eta < 0) {
# penalty if eta is incomputable
me.hit -= 75;
}
}
if (me.curr_deviation_h != nil and me.dist_curr > 50) {
# penalty for target being off-bore
me.hit -= math.abs(me.curr_deviation_h)/2.5;
}
if (me.guiding == TRUE and me.t_speed_fps != nil and me.old_speed_fps > me.t_speed_fps and me.t_speed_fps != 0) {
# bonus for traveling faster than target
me.hit += me.clamp((me.old_speed_fps / me.t_speed_fps)*15,-25,50);
}
if (me.free == TRUE or (me.gnd_launch and (me.chaffLock or me.flareLock))) {
# penalty for not longer guiding
me.hit -= 75;
}
me.hit = int(me.clamp(me.hit, 0, 90));
me.ai.getNode("ETA").setIntValue(me.eta);
me.ai.getNode("hit").setIntValue(me.hit);
if (me.gnd_launch) {
setprop("sam/impact"~me.ID,me.eta);
setprop("sam/hit"~me.ID,me.hit);
}
if (me["prevETA"] != nil) {
if (me.prevETA < me.eta) {
# reset the lowest eta to allow it to increase.
AIM.setETA(nil);
}
AIM.setETA(me.eta, me["prevETA"]);
}
me.prevETA = me["eta"];
}
me.sendTelemetry();
if (me.life_time - me.last_noti > me.noti_time and getprop("payload/armament/msg")) {
# notify in flight using Emesary.
@ -2680,6 +2660,62 @@ var AIM = {
}
},
sendTelemetry: func {
if (me.data == TRUE) {
me.eta = me.free == TRUE or me.vert_closing_rate_fps == -1?-1:(me["t_go"]!=nil?me.t_go:(me.dist_curr*M2FT)/me.vert_closing_rate_fps);
if (me.eta < 0) me.eta = -1;
me.hit = 50;# in percent
if (me.life_time > me.drop_time+me.stage_1_duration + me.gnd_launch?(me.stage_2_duration + me.stage_gap_duration):0) {
# little less optimistic after reaching topspeed
if (me.selfdestruct_time-me.life_time < me.eta) {
# reduce alot if eta later than lifespan
me.hit -= 75;
} elsif (me.eta != -1 and (me.selfdestruct_time-me.life_time) != 0) {
# if its hitting late in life, then reduce
me.hit -= (me.eta / (me.selfdestruct_time-me.life_time)) * 25;
}
if (me.eta > 0) {
# penalty if eta is high
me.hit -= me.clamp(40*me.eta/(me.life_time*0.85), 0, 40);
}
if (me.eta < 0) {
# penalty if eta is incomputable
me.hit -= 75;
}
}
if (me.curr_deviation_h != nil and me.dist_curr > 50) {
# penalty for target being off-bore
me.hit -= math.abs(me.curr_deviation_h)/2.5;
}
if (me.guiding == TRUE and me.t_speed_fps != nil and me.old_speed_fps > me.t_speed_fps and me.t_speed_fps != 0) {
# bonus for traveling faster than target
me.hit += me.clamp((me.old_speed_fps / me.t_speed_fps)*15,-25,50);
}
if (me.free == TRUE or (me.gnd_launch and (me.chaffLock or me.flareLock))) {
# penalty for not longer guiding
me.hit -= 75;
}
me.hit = int(me.clamp(me.hit, 0, 90));
me.ai.getNode("ETA").setIntValue(me.eta);
me.ai.getNode("hit").setIntValue(me.hit);
if (me.gnd_launch) {
setprop("sam/impact"~me.ID,me.eta);
setprop("sam/hit"~me.ID,me.hit);
}
if (me["prevETA"] != nil) {
if (me.prevETA < me.eta) {
# reset the lowest eta to allow it to increase.
AIM.setETA(nil);
}
AIM.setETA(me.eta, me["prevETA"]);
}
me.prevETA = me["eta"];
}
},
getGPS: func(x, y, z, pitch, head=nil, roll=nil) {
#
# get Coord from body position. x,y,z must be in meters.
@ -2816,8 +2852,10 @@ var AIM = {
#
me.thrust_lbf = 0;# pounds force (lbf)
if (me.engineEnabled) {
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration)) {
if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration + me.stage_3_duration)) {
me.thrust_lbf = 0;
} elsif (me.life_time > me.stage_1_duration + me.stage_gap_duration + me.drop_time + me.stage_2_duration) {
me.thrust_lbf = me.force_lbf_3;
} elsif (me.life_time > me.stage_1_duration + me.stage_gap_duration + me.drop_time) {
me.thrust_lbf = me.force_lbf_2;
} elsif (me.life_time > me.drop_time and me.life_time < me.drop_time+me.stage_1_duration) {
@ -2903,7 +2941,7 @@ var AIM = {
},
setFirst: func() {
if (me.smoke_prop.getValue() == TRUE) {
if (me.smoke_prop.getValue() == TRUE and me.life_time < first_in_air_max_sec) {
if (me.first == TRUE or first_in_air == FALSE) {
# report position over MP for MP animation of smoke trail.
me.first = TRUE;
@ -2915,7 +2953,7 @@ var AIM = {
me.mpAltft.setDoubleValue(me.coord.alt()*M2FT);
}
}
} elsif (me.first == TRUE and me.life_time > me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration) {
} elsif (me.first == TRUE and (me.life_time >= first_in_air_max_sec or me.life_time > me.drop_time + me.stage_1_duration + me.stage_gap_duration + me.stage_2_duration)) {
# this weapon was reporting its position over MP, but now its fuel has used up. So allow for another to do that.
me.resetFirst();
}
@ -3136,8 +3174,8 @@ var AIM = {
me.chaffLast = me.chaffNumber;
me.chaffTime = getprop("sim/time/elapsed-sec");
me.aspectDeg = me.aspectToExhaust(me.coord, me.Tgt) / 180;# 0 = viewing engine, 1 = front
me.semi = me.guidance == "semi-radar"?0.5:1;
me.chaffChance = (1-me.chaffResistance);#*me.semi;
me.semi = me.guidance == "semi-radar"?1:1;
me.chaffChance = (1-me.chaffResistance)*me.semi;
me.chaffLock = rand() < (me.chaffChance - (me.chaffChance * 0.5 * me.aspectDeg));# 50% less chance to be fooled if front aspect
if (me.chaffLock == TRUE) {
@ -3224,6 +3262,11 @@ var AIM = {
me.printStats(me.type~": Not guiding (lost radar reflection, gave up)");
me.free = TRUE;
}
} elsif (me.guidance == "command" and (me.Tgt == nil or !me.Tgt.isCommandActive())) {
# if its command guided and the control no longer sends commands
me.guiding = FALSE;
me.printStats(me.type~": Not guiding (no commands from controller, gave up)");
me.free = TRUE;
} elsif (me.guidance == "radiation" and me.is_radiating_me(me.Tgt) == FALSE) {
# if its radiation guided and the target is not illuminating us with radiation
me.guiding = FALSE;
@ -3639,7 +3682,7 @@ var AIM = {
# augmented proportional navigation for heading #
#################################################
if (me.guidanceLaw == "direct") {
if (me.guidanceLaw == "direct" or (me.guidanceLaw == "LOS" and me.life_time < 4)) {
# pure pursuit
me.raw_steer_signal_head = me.curr_deviation_h;
if (me.cruise_or_loft == FALSE) {
@ -3738,8 +3781,30 @@ var AIM = {
me.t_go = me.myMath.dotProduct(me.R_tm,me.R_tm)/me.myMath.dotProduct(me.R_tm, me.V_tm);
#printf("time_to_go %.1f, closing %d",me.t_go,me.vert_closing_rate_fps);
# Horizontal PN:
if (me.apn == 1) {
# Horizontal homing:
if (me.guidanceLaw == "LOS") {
me.K1 = 2.5;
me.K2 = 10.0;
me.R_m = me.ac_init.distance_to(me.coord)*M2FT;
me.course_to_missile = me.ac_init.course_to(me.coord);
me.course_to_target = me.ac_init.course_to(me.t_coord);
me.CREh_old = me.CREh;
# cross range error:
me.CREh = me.R_m*math.sin(me.clamp(geo.normdeg180(me.course_to_target - me.course_to_missile),-89,89)*D2R);
me.CREh_dot = (me.CREh - me.CREh_old)/me.dt;
me.acc_lateral_fps2 = me.K1*me.CREh_dot + me.K2*me.CREh;
me.toBody = math.cos(geo.normdeg180(me.hdg-me.course_to_target)*D2R);
if (me.toBody==0) me.toBody=0.0001;
me.acc_lateral_fps2 /= me.toBody;
me.velocity_vector_length_fps = me.clamp(me.old_speed_horz_fps, 0.0001, 1000000);
me.commanded_lateral_vector_length_fps = me.acc_lateral_fps2*me.dt;
me.raw_steer_signal_head = R2D*me.commanded_lateral_vector_length_fps/me.velocity_vector_length_fps;
#me.raw_steer_signal_head = me.curr_deviation_h;
} elsif (me.apn == 1) {
# APN (constant best at 5, but the higher value the more sensitive to noise)
# Augmented proportional navigation. Takes target acc. into account. Invented for SAMs.
me.toBody = math.cos(me.curr_deviation_h*D2R);#convert perpendicular LOS acc. to perpendicular body acc.
@ -3823,8 +3888,26 @@ var AIM = {
me.last_t_elev_norm_speed = me.t_LOS_elev_norm_speed;
#printf("Target acc. perpendicular to LOS (positive up): %.1f G.", me.t_LOS_elev_norm_acc/g_fps);
# Vertical PN:
if (me.apn == 1) {
# Vertical homing:
if (me.guidanceLaw == "LOS") {
me.R_m = me.ac_init.direct_distance_to(me.coord)*M2FT;
me.pitch_to_missile = me.myMath.getPitch(me.ac_init,me.coord);
me.pitch_to_target = me.myMath.getPitch(me.ac_init,me.t_coord);
me.CREv_old = me.CREv;
# Cross range error
me.CREv = me.R_m*math.sin((me.pitch_to_target - me.pitch_to_missile)*D2R);
me.CREv_dot = (me.CREv - me.CREv_old)/me.dt;
me.acc_upwards_fps2 = me.K1*me.CREv_dot + me.K2*me.CREv;
# Convert perpendicular LOS acc. to perpendicular body acc.
me.toBody = math.cos((me.pitch - me.pitch_to_target)*D2R);
if (me.toBody==0) me.toBody=0.00001;
me.acc_upwards_fps2 /= me.toBody;
# Apply the acc.
me.velocity_vector_length_fps = me.clamp(me.old_speed_fps, 0.0001, 1000000);
me.commanded_upwards_vector_length_fps = me.acc_upwards_fps2*me.dt;
me.raw_steer_signal_elev = R2D*me.commanded_upwards_vector_length_fps/me.velocity_vector_length_fps;
} elsif (me.apn == 1) {
# APN (constant best at 5, but the higher value the more sensitive to noise)
# Augmented proportional navigation. Takes target acc. into account. Invented for SAMs.
me.toBody = math.cos(me.curr_deviation_e*D2R);#convert perpendicular LOS acc. to perpendicular body acc.

@ -139,6 +139,13 @@ var Contact = {
return isNotBehindTerrain(me.node)[0];
},
isCommandActive: func {
if (getprop("/carrier/sunk") == 1) {
return 0;
}
return isNotBehindTerrain(me.node)[0];
},
getUnique: func () {
if (me.unique == nil) {
me.unique = me.node.getNode("unique");

Loading…
Cancel
Save