From e26ecd5c0f651cb24a7a60c7639085c6e96e1911 Mon Sep 17 00:00:00 2001 From: "Nikolai V. Chr" Date: Fri, 25 Nov 2022 01:36:41 +0100 Subject: [PATCH] Update to latest vector, missile-code and damage. --- BUK-M2/BUK-M2-set.xml | 178 +++--- BUK-M2/Models/buk-m2.xml | 84 +-- BUK-M2/Nasal/damage.nas | 307 ++++++--- BUK-M2/Nasal/vector.nas | 180 +++++- Frigate/Frigate-set.xml | 23 +- Frigate/Nasal/damage.nas | 307 ++++++--- Frigate/Nasal/vector.nas | 180 +++++- Frigate/Sounds/sound.xml | 17 - MIM-104D/MIM-104D-set.xml | 47 +- MIM-104D/Nasal/damage.nas | 293 ++++++--- MIM-104D/Nasal/fdm.nas | 3 +- MIM-104D/Nasal/guided-missiles.nas | 667 +++++++++++--------- MIM-104D/Nasal/vector.nas | 98 ++- S-300/Models/ClamShell.xml | 2 +- S-300/Models/FlapLid_mast.xml | 29 + S-300/Models/s-300.xml | 2 +- S-300/Nasal/damage.nas | 307 ++++++--- S-300/Nasal/fdm.nas | 73 ++- S-300/Nasal/vector.nas | 180 +++++- S-300/S-300-set.xml | 188 ++---- S-75/Nasal/damage.nas | 291 ++++++--- S-75/Nasal/guided-missiles.nas | 667 +++++++++++--------- S-75/Nasal/vector.nas | 98 ++- S-75/S-75-set.xml | 15 +- SA-6/Nasal/damage.nas | 312 ++++++--- SA-6/Nasal/fire-control.nas | 2 +- SA-6/Nasal/guided-missiles.nas | 701 ++++++++++++--------- SA-6/Nasal/vector.nas | 100 ++- SA-6/SA-6-set.xml | 43 +- ZSU-23/Nasal/damage.nas | 337 +++++++--- ZSU-23/Nasal/fdm.nas | 104 +-- ZSU-23/Nasal/vector.nas | 180 +++++- ZSU-23/zsu-23-set.xml | 143 ++--- depot/Nasal/damage.nas | 307 ++++++--- depot/Nasal/fdm.nas | 40 +- depot/Nasal/vector.nas | 180 +++++- depot/Sounds/sound.xml | 17 - depot/depot-common.xml | 17 +- fleet/Nasal/damage.nas | 307 ++++++--- fleet/Nasal/fdm.nas | 2 +- fleet/Nasal/vector.nas | 180 +++++- fleet/fleet-set.xml | 110 ++-- gci-radar/Nasal/damage.nas | 307 ++++++--- gci-radar/Nasal/gci.nas | 1 + gci-radar/Nasal/vector.nas | 180 +++++- gci-radar/Sounds/sound.xml | 16 - gci-radar/gci-set.xml | 20 +- groundtarget/Nasal/damage.nas | 337 +++++++--- groundtarget/Nasal/fdm.nas | 115 ++-- groundtarget/Nasal/vector.nas | 127 +++- groundtarget/truck-set.xml | 15 + missile-frigate/Models/missile_frigate.xml | 40 +- missile-frigate/Nasal/damage.nas | 307 ++++++--- missile-frigate/Nasal/vector.nas | 180 +++++- missile-frigate/missile-frigate-set.xml | 115 ++-- 55 files changed, 6237 insertions(+), 2841 deletions(-) create mode 100644 S-300/Models/FlapLid_mast.xml diff --git a/BUK-M2/BUK-M2-set.xml b/BUK-M2/BUK-M2-set.xml index 18a638f..f4cc1fe 100644 --- a/BUK-M2/BUK-M2-set.xml +++ b/BUK-M2/BUK-M2-set.xml @@ -1,64 +1,9 @@ - - - - false - - - - - - - 15.0 - - - 0.5 - 4.0 - - - 0.32 - 0.45 - - - 30.0 - - - 20.0 - - true - - - - - pinto + pinto, Nikolai V. Chr. BUK-M2 BUK-M2 is a modern Russian self-propelled surface to air missile system. alpha @@ -114,6 +59,36 @@ 2000.0 + + Follow + lookat + false + + + true + rotors/main/blade[0]/flap-deg + rotors/main/blade[1]/flap-deg + rotors/main/blade[2]/position-deg + /position/latitude-deg + /position/longitude-deg + /position/altitude-ft + + /orientation/heading-deg + 0 + 0 + 50 + 50 + 25 + 70 + + false + + + + Aircraft/BUK-M2/Hud/hud.xml true @@ -126,24 +101,13 @@ true - - - Systems/fbw.xml - - - Systems/fdm.xml - - - - Systems/views.xml - - 500 0 0 + @@ -152,31 +116,7 @@ true - - - - nasal - - - - - - enemies - - dialog-show - enemies - - - - - enemies - - dialog-show - priority - - + @@ -254,9 +194,33 @@ 50 - + + 2.25 + true + 80 + false + true + false + false + false + false + false + 2.25 + false + Aircraft/BUK-M2/Models/Armament/Weapons/ + true + true + rotors/main/blade[0]/flap-deg + rotors/main/blade[1]/flap-deg + rotors/main/blade[2]/flap-deg + rotors/main/blade[2]/position-deg + true + true + 1 + controls/armament + station true true true @@ -266,6 +230,7 @@ 0 false false + @@ -279,6 +244,7 @@ 0 + 0 @@ -298,18 +264,18 @@ - Leto - swamp - pinto - fb237 - Wilco-1 - Kokos - J-Mav16 - S - Raider1 - Slobb - 5H1N0B1 - SNOWY1 + target1 + target2 + target3 + target4 + target5 + target6 + target7 + target8 + target9 + targetA + targetB + targetC 0 diff --git a/BUK-M2/Models/buk-m2.xml b/BUK-M2/Models/buk-m2.xml index c2ba2c4..6ccc6a0 100644 --- a/BUK-M2/Models/buk-m2.xml +++ b/BUK-M2/Models/buk-m2.xml @@ -210,89 +210,7 @@ + ]]> diff --git a/BUK-M2/Nasal/damage.nas b/BUK-M2/Nasal/damage.nas index 7a9cb23..44b753a 100644 --- a/BUK-M2/Nasal/damage.nas +++ b/BUK-M2/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 2.5;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 80;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -66,7 +68,7 @@ var shells = { var warheads = { # [id,lbs,anti surface,cluster,(name)] - "AGM-65": [ 0, 126.00,1,0], + "AGM-65B": [ 0, 126.00,1,0], "AGM-84": [ 1, 488.00,1,0], "AGM-88": [ 2, 146.00,1,0], "MK-82SE": [ 3, 192.00,1,0],# snake eye @@ -75,12 +77,12 @@ var warheads = { "AGM-158": [ 6, 1000.00,1,0], "ALARM": [ 7, 450.00,1,0], "AM 39 Exocet": [ 8, 364.00,1,0], - "AS 37 Martel": [ 9, 330.00,1,0], + "AS 37 Martel": [ 9, 330.00,1,0],# Also : AJ 168 Martel "AS30L": [10, 529.00,1,0], "BL755": [11, 100.00,1,1],# 800lb bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-87": [12, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-105": [13, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. - "AJ 168 Martel": [14, 330.00,1,0], + "AS 37 Armat": [14, 330.00,1,0], "FAB-100": [15, 92.59,1,0], "FAB-250": [16, 202.85,1,0], "FAB-500": [17, 564.38,1,0], @@ -88,7 +90,7 @@ var warheads = { "GBU-24": [19, 945.00,1,0], "GBU-31": [20, 945.00,1,0], "GBU-54": [21, 190.00,1,0], - "GBU-10": [22, 2000.00,1,0], + "GBU-10": [22, 945.00,1,0], "GBU-16": [23, 450.00,1,0], "HVAR": [24, 7.50,1,0],#P51 "KAB-500": [25, 564.38,1,0], @@ -124,14 +126,14 @@ var warheads = { "AIM-7F": [55, 88.00,0,0], "AGM-62": [56, 2000.00,1,0], "AIM-9L": [57, 20.80,0,0], - "d-7": [58, 44.00,0,0],#deprecated + "AGM-65D": [58, 126.00,1,0], "AIM-132": [59, 22.05,0,0], - "d-8": [60, 20.80,0,0],#deprecated + "Apache AP": [60, 110.23,0,1],# Real mass of bomblet. (x 10). Anti runway. "KN-06": [61, 315.00,0,0], "9M317": [62, 145.00,0,0], - "d9": [63, 27.00,0,0],#deprecated + "GEM": [63, 185.00,0,0],#MIM-104D "R.550 Magic": [64, 26.45,0,0],# also called majic - "d-0": [65, 30.00,0,0],#deprecated + "5Ya23": [65, 414.00,0,0],#Volga-M "R.550 Magic 2": [66, 27.00,0,0], "R.530": [67, 55.00,0,0], "MK-82AIR": [68, 192.00,1,0], @@ -162,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -224,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -256,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -267,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -289,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -297,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -310,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -319,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -345,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -378,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -423,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -436,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -455,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -466,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -606,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -614,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -624,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -643,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -667,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -710,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -736,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -810,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -818,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -838,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -846,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -1005,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1039,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1053,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1078,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1137,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1150,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1176,16 +1300,17 @@ var code_ct = func () { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1203,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1282,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/BUK-M2/Nasal/vector.nas b/BUK-M2/Nasal/vector.nas index 36ae539..621b0cb 100644 --- a/BUK-M2/Nasal/vector.nas +++ b/BUK-M2/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.93 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -13,6 +13,8 @@ var Math = { # When doing euler angles (from pilots point of view): yaw = yaw left, pitch = rotate up, roll = roll right. # FG rotations: heading = yaw right, pitch = rotate up, roll = roll right. # + # Cartesian is right-handed coord system. + # clamp: func(v, min, max) { v < min ? min : v > max ? max : v }, convertCoords: func (x,y,z) { @@ -37,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -55,9 +59,27 @@ var Math = { return R2D * math.acos(me.value); }, + # Rodrigues' rotation formula. Use unitVectorAxis as axis, and rotate around it. + rotateVectorAroundVector: func (a, unitVectorAxis, angle) { + return me.plus( + me.plus( + me.product(math.cos(angle*D2R),a), + me.product(math.sin(angle*D2R), me.crossProduct(unitVectorAxis,a)) + ), + me.product((1-math.cos(angle*D2R))*me.dotProduct(unitVectorAxis,a), unitVectorAxis) + ); + }, + + #Rotate a certain amound of degrees towards 'towardsMe'. + rotateVectorTowardsVector: func (a, towardsMe, angle) { + return me.rotateVectorAroundVector(a, me.normalize(me.crossProduct(a, towardsMe)), angle); + }, + # length of vector magnitudeVector: func (a) { - return math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2)); + me.mag = 1; + call(func {me.mag = math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2))},nil,nil,var err =[]);#strange bug in Nasal can sometimes mess up sqrt + return me.mag; }, # dot product of 2 vectors @@ -65,8 +87,20 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { + me.rollM = me.rollMatrix(roll); + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) + yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); @@ -74,15 +108,34 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) - yawPitchRollVector: func (yaw, pitch, roll, vector) { - me.rollM = me.rollMatrix(roll); + # rotate a vector. Order: yaw, pitch + yawPitchVector: func (yaw, pitch, vector) { me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.pitchM, me.yawM); return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: pitch, yaw + pitchYawVector: func (pitch, yaw, vector) { + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.pitchM); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + + # rotate a vector. Order: pitch + pitchVector: func (pitch, vector) { + me.pitchM = me.pitchMatrix(pitch); + return me.multiplyMatrixWithVector(me.pitchM, vector); + }, + # multiply 3x3 matrix with vector multiplyMatrixWithVector: func (matrix, vector) { return [matrix[0]*vector[0]+matrix[1]*vector[1]+matrix[2]*vector[2], @@ -107,10 +160,10 @@ var Math = { # matrix for pitching pitchMatrix: func (pitch) { - pitch = pitch * D2R; - return [math.cos(pitch),0,-math.sin(pitch), + pitch = -pitch * D2R; + return [math.cos(pitch),0,math.sin(pitch), 0,1,0, - math.sin(pitch),0,math.cos(pitch)]; + -math.sin(pitch),0,math.cos(pitch)]; }, # matrix for yawing @@ -126,7 +179,8 @@ var Math = { me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); if (me.horz != 0) { me.pitch = math.atan2(vector[2],me.horz)*R2D; - me.hdg = math.asin(-vector[1]/me.horz)*R2D; + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; if (vector[0] < 0) { # south @@ -144,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -242,8 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, + + # Project a onto ontoMe. + projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety + return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); + }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -268,7 +369,7 @@ var Math = { # print vector to console format: func (v) { - return sprintf("(%.1f, %.1f, %.1f)",v[0],v[1],v[2]); + return sprintf("(%.2f, %.2f, %.2f)",v[0],v[1],v[2]); }, # make vector length 1.0 @@ -288,6 +389,20 @@ var Math = { return me.magnitudeVector(me.crossProduct(me.minus(L2,L1), me.minus(L1,P)))/me.magnitudeVector(me.minus(L2,L1)); }, + + interpolateVector: func (start, end, fraction) { + me.xx = (start[0]*(1-fraction) +end[0]*fraction); + me.yy = (start[1]*(1-fraction) +end[1]*fraction); + me.zz = (start[2]*(1-fraction) +end[2]*fraction); + + return [me.xx, me.yy, me.zz]; + }, + + # move distance 'along' from start towards end. Total dist from start to end is dist. + alongVector: func (start, end, dist, along) { + me.fraction = along/dist; + return me.interpolateVector(start, end, me.fraction); + }, # Orthogonal projection of a vector `vec` onto another `ref` !!can throw an exception if the referential vector is null!!. orthogonalProjection: func(vec, ref){ @@ -319,9 +434,9 @@ var Math = { #| 0 cos(roll) -sin(roll) | #| 0 sin(roll) cos(roll) | # -#| cos(pitch) 0 -sin(pitch) | -#| 0 1 0 | -#| sin(pitch) 0 cos(pitch) | +#| cos(-pitch) 0 sin(-pitch) | +#| 0 1 0 | +#| -sin(-pitch) 0 cos(-pitch) | # #| cos(yaw) -sin(yaw) 0 | #| sin(yaw) cos(yaw) 0 | @@ -336,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/Frigate/Frigate-set.xml b/Frigate/Frigate-set.xml index 1ffc595..2d3737e 100644 --- a/Frigate/Frigate-set.xml +++ b/Frigate/Frigate-set.xml @@ -58,7 +58,7 @@ - pinto and onox + pinto, Nikolai V. Chr., onox Frigate alpha 0.1 @@ -123,6 +123,7 @@ 0 + @@ -227,7 +228,20 @@ 50 - + + 20 + true + 200 + false + true + false + false + false + false + false + 2.25 + false + Aircraft/Frigate/Models/emesary/ true @@ -239,6 +253,7 @@ 0 false false + @@ -259,8 +274,4 @@ - - - - diff --git a/Frigate/Nasal/damage.nas b/Frigate/Nasal/damage.nas index 032a8ed..44b753a 100644 --- a/Frigate/Nasal/damage.nas +++ b/Frigate/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 20;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 200;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -66,7 +68,7 @@ var shells = { var warheads = { # [id,lbs,anti surface,cluster,(name)] - "AGM-65": [ 0, 126.00,1,0], + "AGM-65B": [ 0, 126.00,1,0], "AGM-84": [ 1, 488.00,1,0], "AGM-88": [ 2, 146.00,1,0], "MK-82SE": [ 3, 192.00,1,0],# snake eye @@ -75,12 +77,12 @@ var warheads = { "AGM-158": [ 6, 1000.00,1,0], "ALARM": [ 7, 450.00,1,0], "AM 39 Exocet": [ 8, 364.00,1,0], - "AS 37 Martel": [ 9, 330.00,1,0], + "AS 37 Martel": [ 9, 330.00,1,0],# Also : AJ 168 Martel "AS30L": [10, 529.00,1,0], "BL755": [11, 100.00,1,1],# 800lb bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-87": [12, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-105": [13, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. - "AJ 168 Martel": [14, 330.00,1,0], + "AS 37 Armat": [14, 330.00,1,0], "FAB-100": [15, 92.59,1,0], "FAB-250": [16, 202.85,1,0], "FAB-500": [17, 564.38,1,0], @@ -88,7 +90,7 @@ var warheads = { "GBU-24": [19, 945.00,1,0], "GBU-31": [20, 945.00,1,0], "GBU-54": [21, 190.00,1,0], - "GBU-10": [22, 2000.00,1,0], + "GBU-10": [22, 945.00,1,0], "GBU-16": [23, 450.00,1,0], "HVAR": [24, 7.50,1,0],#P51 "KAB-500": [25, 564.38,1,0], @@ -124,14 +126,14 @@ var warheads = { "AIM-7F": [55, 88.00,0,0], "AGM-62": [56, 2000.00,1,0], "AIM-9L": [57, 20.80,0,0], - "d-7": [58, 44.00,0,0],#deprecated + "AGM-65D": [58, 126.00,1,0], "AIM-132": [59, 22.05,0,0], - "d-8": [60, 20.80,0,0],#deprecated + "Apache AP": [60, 110.23,0,1],# Real mass of bomblet. (x 10). Anti runway. "KN-06": [61, 315.00,0,0], "9M317": [62, 145.00,0,0], - "d9": [63, 27.00,0,0],#deprecated + "GEM": [63, 185.00,0,0],#MIM-104D "R.550 Magic": [64, 26.45,0,0],# also called majic - "d-0": [65, 30.00,0,0],#deprecated + "5Ya23": [65, 414.00,0,0],#Volga-M "R.550 Magic 2": [66, 27.00,0,0], "R.530": [67, 55.00,0,0], "MK-82AIR": [68, 192.00,1,0], @@ -162,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -224,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -256,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -267,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -289,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -297,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -310,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -319,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -345,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -378,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -423,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -436,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -455,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -466,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -606,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -614,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -624,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -643,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -667,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -710,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -736,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -810,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -818,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -838,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -846,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -1005,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1039,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1053,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1078,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1137,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1150,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1176,16 +1300,17 @@ var code_ct = func () { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1203,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1282,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/Frigate/Nasal/vector.nas b/Frigate/Nasal/vector.nas index 36ae539..621b0cb 100644 --- a/Frigate/Nasal/vector.nas +++ b/Frigate/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.93 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -13,6 +13,8 @@ var Math = { # When doing euler angles (from pilots point of view): yaw = yaw left, pitch = rotate up, roll = roll right. # FG rotations: heading = yaw right, pitch = rotate up, roll = roll right. # + # Cartesian is right-handed coord system. + # clamp: func(v, min, max) { v < min ? min : v > max ? max : v }, convertCoords: func (x,y,z) { @@ -37,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -55,9 +59,27 @@ var Math = { return R2D * math.acos(me.value); }, + # Rodrigues' rotation formula. Use unitVectorAxis as axis, and rotate around it. + rotateVectorAroundVector: func (a, unitVectorAxis, angle) { + return me.plus( + me.plus( + me.product(math.cos(angle*D2R),a), + me.product(math.sin(angle*D2R), me.crossProduct(unitVectorAxis,a)) + ), + me.product((1-math.cos(angle*D2R))*me.dotProduct(unitVectorAxis,a), unitVectorAxis) + ); + }, + + #Rotate a certain amound of degrees towards 'towardsMe'. + rotateVectorTowardsVector: func (a, towardsMe, angle) { + return me.rotateVectorAroundVector(a, me.normalize(me.crossProduct(a, towardsMe)), angle); + }, + # length of vector magnitudeVector: func (a) { - return math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2)); + me.mag = 1; + call(func {me.mag = math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2))},nil,nil,var err =[]);#strange bug in Nasal can sometimes mess up sqrt + return me.mag; }, # dot product of 2 vectors @@ -65,8 +87,20 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { + me.rollM = me.rollMatrix(roll); + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) + yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); @@ -74,15 +108,34 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) - yawPitchRollVector: func (yaw, pitch, roll, vector) { - me.rollM = me.rollMatrix(roll); + # rotate a vector. Order: yaw, pitch + yawPitchVector: func (yaw, pitch, vector) { me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.pitchM, me.yawM); return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: pitch, yaw + pitchYawVector: func (pitch, yaw, vector) { + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.pitchM); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + + # rotate a vector. Order: pitch + pitchVector: func (pitch, vector) { + me.pitchM = me.pitchMatrix(pitch); + return me.multiplyMatrixWithVector(me.pitchM, vector); + }, + # multiply 3x3 matrix with vector multiplyMatrixWithVector: func (matrix, vector) { return [matrix[0]*vector[0]+matrix[1]*vector[1]+matrix[2]*vector[2], @@ -107,10 +160,10 @@ var Math = { # matrix for pitching pitchMatrix: func (pitch) { - pitch = pitch * D2R; - return [math.cos(pitch),0,-math.sin(pitch), + pitch = -pitch * D2R; + return [math.cos(pitch),0,math.sin(pitch), 0,1,0, - math.sin(pitch),0,math.cos(pitch)]; + -math.sin(pitch),0,math.cos(pitch)]; }, # matrix for yawing @@ -126,7 +179,8 @@ var Math = { me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); if (me.horz != 0) { me.pitch = math.atan2(vector[2],me.horz)*R2D; - me.hdg = math.asin(-vector[1]/me.horz)*R2D; + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; if (vector[0] < 0) { # south @@ -144,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -242,8 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, + + # Project a onto ontoMe. + projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety + return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); + }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -268,7 +369,7 @@ var Math = { # print vector to console format: func (v) { - return sprintf("(%.1f, %.1f, %.1f)",v[0],v[1],v[2]); + return sprintf("(%.2f, %.2f, %.2f)",v[0],v[1],v[2]); }, # make vector length 1.0 @@ -288,6 +389,20 @@ var Math = { return me.magnitudeVector(me.crossProduct(me.minus(L2,L1), me.minus(L1,P)))/me.magnitudeVector(me.minus(L2,L1)); }, + + interpolateVector: func (start, end, fraction) { + me.xx = (start[0]*(1-fraction) +end[0]*fraction); + me.yy = (start[1]*(1-fraction) +end[1]*fraction); + me.zz = (start[2]*(1-fraction) +end[2]*fraction); + + return [me.xx, me.yy, me.zz]; + }, + + # move distance 'along' from start towards end. Total dist from start to end is dist. + alongVector: func (start, end, dist, along) { + me.fraction = along/dist; + return me.interpolateVector(start, end, me.fraction); + }, # Orthogonal projection of a vector `vec` onto another `ref` !!can throw an exception if the referential vector is null!!. orthogonalProjection: func(vec, ref){ @@ -319,9 +434,9 @@ var Math = { #| 0 cos(roll) -sin(roll) | #| 0 sin(roll) cos(roll) | # -#| cos(pitch) 0 -sin(pitch) | -#| 0 1 0 | -#| sin(pitch) 0 cos(pitch) | +#| cos(-pitch) 0 sin(-pitch) | +#| 0 1 0 | +#| -sin(-pitch) 0 cos(-pitch) | # #| cos(yaw) -sin(yaw) 0 | #| sin(yaw) cos(yaw) 0 | @@ -336,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/Frigate/Sounds/sound.xml b/Frigate/Sounds/sound.xml index f42ec74..f66a3e9 100644 --- a/Frigate/Sounds/sound.xml +++ b/Frigate/Sounds/sound.xml @@ -1,22 +1,5 @@ - - diff --git a/MIM-104D/MIM-104D-set.xml b/MIM-104D/MIM-104D-set.xml index aa6cbae..00cd04a 100644 --- a/MIM-104D/MIM-104D-set.xml +++ b/MIM-104D/MIM-104D-set.xml @@ -257,7 +257,20 @@ 50 - + + 5 + true + 80 + false + true + false + false + false + false + false + 2.25 + false + Aircraft/MIM-104D/Models/Armament/Weapons/ true @@ -295,17 +308,17 @@ true 360 - 400 + 400 25 15000 - 0 + 0 12 0 0 185 1000 - 0.10 + 0.10 2 0.45 @@ -365,19 +378,19 @@ 100 - - Leto - swamp - pinto - fb237 - Wilco-1 - Kokos - J-Mav16 - S - Raider1 - Slobb - 5H1N0B1 - SNOWY1 + + target1 + target2 + target3 + target4 + target5 + target6 + target7 + target8 + target9 + targetA + targetB + targetC 0 diff --git a/MIM-104D/Nasal/damage.nas b/MIM-104D/Nasal/damage.nas index 7278de1..44b753a 100644 --- a/MIM-104D/Nasal/damage.nas +++ b/MIM-104D/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 5;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 80;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -162,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -224,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -256,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -267,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -289,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -297,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -310,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -319,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -345,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -378,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -423,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -436,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -455,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -466,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -606,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -614,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -624,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -643,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -667,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -710,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -736,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -810,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -818,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -838,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -846,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -1005,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1039,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1053,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1078,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1137,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1150,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1171,21 +1295,22 @@ var code_ct = func () { if (getprop("payload/armament/msg")) { setprop("sim/rendering/redout/enabled", TRUE); #call(func{fgcommand('dialog-close', multiplayer.dialog.dialog.prop())},nil,var err= []);# props.Node.new({"dialog-name": "location-in-air"})); - #call(func{multiplayer.dialog.del();},nil,var err= []); + call(func{multiplayer.dialog.del();},nil,var err= []); if (!getprop("gear/gear[0]/wow")) { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1203,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1282,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/MIM-104D/Nasal/fdm.nas b/MIM-104D/Nasal/fdm.nas index 9145b3e..8469eb6 100644 --- a/MIM-104D/Nasal/fdm.nas +++ b/MIM-104D/Nasal/fdm.nas @@ -52,7 +52,8 @@ var PositionUpdater = func () { # Set new position setprop("/position/latitude-deg", position.lat()); setprop("/position/longitude-deg", position.lon()); - setprop("/position/altitude-ft", getprop("/position/ground-elev-ft")); + var elev = geo.elevation(position.lat(),position.lon()); + if (elev != nil) setprop("/position/altitude-ft", elev*M2FT); if ( getprop("/carrier/sunk") == 0 ) { diff --git a/MIM-104D/Nasal/guided-missiles.nas b/MIM-104D/Nasal/guided-missiles.nas index 2bd8a61..4e2a1ec 100644 --- a/MIM-104D/Nasal/guided-missiles.nas +++ b/MIM-104D/Nasal/guided-missiles.nas @@ -1,12 +1,12 @@ ######################################################################################### -####### +####### ####### Guided/Cruise missiles, rockets and dumb/glide/guided bombs code for Flightgear. ####### ####### License: GPL 2.0 ####### ####### Authors: ####### Alexis Bory, Fabien Barbier, Richard Harrison, Justin Nicholson, Nikolai V. Chr., Axel Paccalin, Colin Geniet -####### +####### ####### The file vector.nas needs to be available in namespace 'vector'. ####### ####### In addition, some code is derived from work by: @@ -41,7 +41,7 @@ # Laser and semi-radar guided munitions need the target to be painted to keep lock. Notice gps guided munition that are all aspect will never lose lock, # whether they can 'see' the target or not. Anti-radiation missiles will need the target to send radiation towards the missile. # Set DEBUG_STATS and/or DEBUG_FLIGHT to true to check how the missile works during flight, when you are designing a weapon. -# +# # # Usage: # @@ -60,10 +60,10 @@ # the weapon did not have time to arm before hitting ground. # To drop the munition, without arming it nor igniting its engine, call eject(). # Remote guidance requires the use of midFlightFunction (see below) to transmit guidance parameters, in the remote_yaw and remote_pitch fields of the output. -# +# # # Limitations: -# +# # The weapons use a simplified flight model that does not have AoA or sideslip. Mass balance, rotational inertia, wind is also not implemented. They also do not roll due to aerodynmic effects. # If you fire a weapon and have HoT enabled in flightgear, they likely will not hit very precise. # The weapons are highly dependent on framerate, so low frame rate will make them hit imprecise. @@ -218,7 +218,7 @@ var contactPoint = nil; var AIM = { lowestETA: nil, #done - new : func (p, type = "AIM-9", sign = "Sidewinder", midFlightFunction = nil, nasalPosition = nil) { + new : func (p, type = "AIM-9L", sign = "Sidewinder", midFlightFunction = nil, nasalPosition = nil) { if(AIM.active[p] != nil) { #do not make new missile logic if one exist for this pylon. return -1; @@ -252,19 +252,15 @@ var AIM = { if (m.SwSoundVol.getValue() == nil) { m.SwSoundVol.setDoubleValue(0); } - m.inacc = getprop("payload/armament/tgp-inacc-system");# set to false, unless using the f16 m.tacview_support = getprop("payload/armament/tacview");# set to false, unless using an aircraft that has tacview m.gnd_launch = getprop("payload/armament/gnd-launch");#true to be a SAM or ship if (m.gnd_launch == nil) { m.gnd_launch = 0; } - if (m.inacc == nil) { - m.inacc = 0; - } if (m.tacview_support == nil) { m.tacview_support = 0; } - + m.ID = p; m.stationName = AcModel.getNode("armament/station-name").getValue(); if (m.nasalPosition == nil) { @@ -279,7 +275,7 @@ var AIM = { if (fox2_unique_id >100) fox2_unique_id = -100; m.unique_id = fox2_unique_id; m.nodeString = "payload/armament/"~m.type_lc~"/"; - + if (m.tacview_support) { m.tacviewID = 11000 + int(math.floor(rand()*10000)); } @@ -315,6 +311,7 @@ var AIM = { m.radarY = getprop(m.nodeString~"FCS-y"); # This is handy for SAMs with radar on a mast. m.radarZ = getprop(m.nodeString~"FCS-z"); # In future I will add direction to it also, for now its center gimbal is along -x axis. m.expand_min = getprop(m.nodeString~"expand-min-fire-range"); # Bool. Default false. If min fire range should expand with closing rate. Mainly use this for A/A missiles. + 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 @@ -384,36 +381,40 @@ var AIM = { m.dlz_enabled = getprop(m.nodeString~"DLZ"); # Supports dynamic launch zone info. For now only works with A/A. [optional] m.dlz_opt_alt = getprop(m.nodeString~"DLZ-optimal-alt-feet"); # Minimum altitude required to hit the target at max range. m.dlz_opt_mach = getprop(m.nodeString~"DLZ-optimal-closing-mach"); # Closing speed required to hit the target at max range at minimum altitude. - # detailed drag settings + # detailed drag settings m.Cd_plume = getprop(m.nodeString~"exhaust-plume-parasitic-drag-factor"); # Default 1. For AIM-120, Naval Postgraduate School paper suggest around 0.6. It will reduce drag during burn. m.simple_drag = getprop(m.nodeString~"simplified-induced-drag"); # bool. Default true. If enabled, the properties below wont be used: m.wing_aspect_ratio = getprop(m.nodeString~"wing-aspect-ratio"); # span^2/wing_area. Default to 8 m.wing_eff = getprop(m.nodeString~"wing-efficiency-relative-to-an-elliptical-planform"); # Default to 0.85 - + if (m.cold_detect_range_nm == nil) { # backwards compatibility m.cold_detect_range_nm = m.max_fire_range_nm; - } + } m.detect_range_curr_nm = m.cold_detect_range_nm; - + if (m.max_seeker_dev == nil) { m.max_seeker_dev = 15; } if (m.beam_width_deg == nil) { m.beam_width_deg = 4; - } + } m.beam_width_deg *= 0.5; - + if (m.eject_speed == nil) { m.eject_speed = 0; - } - + } + if (m.rail_forward == TRUE) { m.rail_pitch_deg = 0; m.rail_head_deg = 0; } + if (m.asc == nil) { + m.asc = 0; + } + if (m.wing_aspect_ratio == nil) { m.wing_aspect_ratio = 8.0; } @@ -433,7 +434,7 @@ var AIM = { if (m.radarOrigin == nil) { m.radarOrigin = 1; } - + if (m.guideWhileDrop == nil) { m.guideWhileDrop = 0; } @@ -453,7 +454,7 @@ var AIM = { if (m.seeker_filter == nil) { m.seeker_filter = 0; } - + if (m.ready_time == nil) { m.ready_time = 0; } @@ -487,19 +488,19 @@ var AIM = { if(m.canSwitch == nil) { m.canSwitch = FALSE; } - + if(m.terminal_alt_factor == nil) { m.terminal_alt_factor = 2; } - + if(m.terminal_rise_time == nil) { m.terminal_rise_time = 6; } - + if(m.terminal_dive_time == nil) { m.terminal_dive_time = 4; } - + if (m.weight_fuel_lbm == nil) { m.weight_fuel_lbm = 0; } @@ -616,7 +617,7 @@ var AIM = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("ai/models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild(m.type_lc, i, 0) == nil) { @@ -631,16 +632,16 @@ var AIM = { m.ai.getNode("valid", 1).setBoolValue(0); m.ai.getNode("name", 1).setValue(type); m.ai.getNode("sign", 1).setValue(sign); - m.ai.getNode("callsign", 1).setValue(type); + m.ai.getNode("callsign", 1).setValue(sprintf("%s_%d", type, m.unique_id)); m.ai.getNode("missile", 1).setBoolValue(1); - - + + var id_model = m.weapon_model ~ m.ID ~ ".xml"; m.model.getNode("path", 1).setValue(id_model); m.model.getNode("enable-hot", 1).setBoolValue(0);# This is if people forget to set it in xml. m.model.getNode("name", 1).setValue(m.typeLong);# this helps in debugging. - - + + # Create the AI position and orientation properties. m.latN = m.ai.getNode("position/latitude-deg", 1); @@ -649,7 +650,7 @@ var AIM = { m.hdgN = m.ai.getNode("orientation/true-heading-deg", 1); m.pitchN = m.ai.getNode("orientation/pitch-deg", 1); m.rollN = m.ai.getNode("orientation/roll-deg", 1); - + m.mpLat = getprop("payload/armament/MP-lat");# properties to be used for showing missile over MP. m.mpLon = getprop("payload/armament/MP-lon"); m.mpAlt = getprop("payload/armament/MP-alt"); @@ -665,7 +666,7 @@ var AIM = { } } - + m.target_air = find("A", m.class)==-1?FALSE:TRUE; m.target_sea = find("M", m.class)==-1?FALSE:TRUE;#use M for marine, since S can be confused with surface. @@ -676,8 +677,8 @@ var AIM = { m.coord = geo.Coord.new().set_latlon(0, 0, 0); m.t_coord = nil; - - + + # # Seekerhead @@ -699,7 +700,7 @@ var AIM = { m.seam_scan = 0; m.cooling_last_time = 0; m.cool_total_time = 0; - + # # Emesary damage system # @@ -789,7 +790,7 @@ var AIM = { m.nextGroundElevation = 0; # next Ground Elevation m.nextGroundElevationMem = [-10000, -1]; m.terrainStage = 0; - + # # Rail # @@ -822,7 +823,7 @@ var AIM = { m.chaffTime = 0; m.chaffLock = FALSE; m.flarespeed_fps = nil; - + # # Telemetry # @@ -839,7 +840,7 @@ var AIM = { m.tooLowSpeedTime = -1; m.lostLOS = FALSE; - + # # LOAL # @@ -859,9 +860,9 @@ var AIM = { m.pendingLaunchSound = -1; m.explodeSound = TRUE; - - - + + + m.standby();# these loops will run until released or deleted. #for multithreading @@ -870,7 +871,7 @@ var AIM = { return AIM.active[m.ID] = m; }, - + del: func { # can be called at any time, before or during flight. # @@ -946,9 +947,9 @@ var AIM = { me.ccrp_speed_east_fps = -me.init_rel_vec[1]; me.ccrp_speed_north_fps = me.init_rel_vec[0]; } - + me.ccrp_t = 0.0; - + me.ccrp_altC = me.ccrp_agl; me.ccrp_vel_z = -me.ccrp_speed_down_fps*FT2M;#positive upwards me.ccrp_fps_z = -me.ccrp_speed_down_fps; @@ -977,11 +978,11 @@ var AIM = { me.ccrp_q = 0.5 * me.ccrp_rho * me.ccrp_fps_x * me.ccrp_fps_x; me.ccrp_deacc = (me.ccrp_Cd * me.ccrp_q * me.ref_area_sqft) / me.ccrp_mass; me.ccrp_acc = -me.ccrp_deacc * FT2M; - + me.ccrp_fps_x_final = me.ccrp_t*me.ccrp_acc+me.ccrp_fps_x;# calc final horz speed me.ccrp_fps_x_average = (me.ccrp_fps_x-(me.ccrp_fps_x-me.ccrp_fps_x_final)*0.5); me.ccrp_mach_average = me.ccrp_fps_x_average / me.ccrp_rs[1]; - + me.ccrp_Cd = me.drag(me.ccrp_mach_average); me.ccrp_q = 0.5 * me.ccrp_rho * me.ccrp_fps_x_average * me.ccrp_fps_x_average; me.ccrp_deacc = (me.ccrp_Cd * me.ccrp_q * me.ref_area_sqft) / me.ccrp_mass; @@ -994,15 +995,15 @@ var AIM = { # we calc heading from composite speeds, due to alpha and beta might influence direction bombs will fall: me.ccrp_heading = geo.normdeg(math.atan2(me.ccrp_speed_east_fps,me.ccrp_speed_north_fps)*R2D); me.ccrpPos.apply_course_distance(me.ccrp_heading, me.ccrp_dist); - + #printf("Will fall %0.1f NM ahead of aircraft.", me.dist*M2NM); me.ccrp_elev = me.ccrp_alti-me.ccrp_agl;#faster me.ccrpPos.set_alt(me.ccrp_elev); - + me.ccrp_distCCRP = me.ccrpPos.distance_to(me.Tgt.get_Coord()); return me.ccrp_distCCRP; }, - + getCCIPadv: func (maxFallTime_sec, timeStep) { # for non flat areas. Lower falltime or higher timestep means using less CPU time. # returns nil for higher than maxFallTime_sec. Else a vector with [Coord, hasTimeToArm]. @@ -1021,7 +1022,7 @@ var AIM = { me.ccip_speed_east_fps = -me.init_rel_vec[1]; me.ccip_speed_north_fps = me.init_rel_vec[0]; } - + me.ccip_t = 0.0; me.ccip_dt = timeStep; me.ccip_fps_z = -me.ccip_speed_down_fps; @@ -1033,7 +1034,7 @@ var AIM = { me.ccip_mass = me.ccip_bomb.weight_launch_lbm * LBM2SLUGS; me.ccipPos = geo.Coord.new(geo.aircraft_position()); - + # we calc heading from composite speeds, due to alpha and beta might influence direction bombs will fall: if(me.ccip_fps_x == 0) return nil; me.ccip_heading = geo.normdeg(math.atan2(me.ccip_speed_east_fps,me.ccip_speed_north_fps)*R2D); @@ -1051,20 +1052,20 @@ var AIM = { me.ccip_Cd = me.ccip_bomb.drag(me.ccip_mach); me.ccip_deacc = (me.ccip_Cd * me.ccip_q * me.ccip_bomb.ref_area_sqft) / me.ccip_mass; me.ccip_fps -= me.ccip_deacc*me.ccip_dt; - + # new components and pitch me.ccip_fps_z = me.ccip_fps*math.sin(me.ccip_pitch); me.ccip_fps_x = me.ccip_fps*math.cos(me.ccip_pitch); me.ccip_fps_z -= g_fps * me.ccip_dt; me.ccip_pitch = math.atan2(me.ccip_fps_z, me.ccip_fps_x); - + # new position me.ccip_altC = me.ccip_altC + me.ccip_fps_z*me.ccip_dt*FT2M; me.ccip_dist = me.ccip_fps_x*me.ccip_dt*FT2M; me.ccip_oldPos = geo.Coord.new(me.ccipPos); me.ccipPos.apply_course_distance(me.ccip_heading, me.ccip_dist); me.ccipPos.set_alt(me.ccip_altC); - + # test terrain me.ccip_grnd = geo.elevation(me.ccipPos.lat(),me.ccipPos.lon()); if (me.ccip_grnd != nil) { @@ -1080,11 +1081,11 @@ var AIM = { } } else { return nil; - } + } } return nil; }, - + getTerrain: func (from, to) { me.xyz = {"x":from.x(), "y":from.y(), "z":from.z()}; me.dir = {"x":to.x()-from.x(), "y":to.y()-from.y(), "z":to.z()-from.z()}; @@ -1096,7 +1097,7 @@ var AIM = { } return nil; }, - + getCCIPsimple: func (maxFallTime_sec, timeStep) { #faster, but only works over level ground me.ccip_agl = getprop("position/altitude-agl-ft")*FT2M; @@ -1118,7 +1119,7 @@ var AIM = { me.ccip_speed_east_fps = -me.init_rel_vec[1]; me.ccip_speed_north_fps = me.init_rel_vec[0]; } - + me.ccip_t = 0.0; me.ccip_dt = timeStep; me.ccip_altC = me.ccip_agl; @@ -1151,11 +1152,11 @@ var AIM = { me.ccip_q = 0.5 * me.ccip_rho * me.ccip_fps_x * me.ccip_fps_x; me.ccip_deacc = (me.ccip_Cd * me.ccip_q * me.ccip_bomb.ref_area_sqft) / me.ccip_mass; me.ccip_acc = -me.ccip_deacc * FT2M; - + me.ccip_fps_x_final = me.ccip_t*me.ccip_acc+me.ccip_fps_x;# calc final horz speed me.ccip_fps_x_average = (me.ccip_fps_x-(me.ccip_fps_x-me.ccip_fps_x_final)*0.5); me.ccip_mach_average = me.ccip_fps_x_average / me.ccip_rs[1]; - + me.ccip_Cd = me.ccip_bomb.drag(me.ccip_mach_average); me.ccip_q = 0.5 * me.ccip_rho * me.ccip_fps_x_average * me.ccip_fps_x_average; me.ccip_deacc = (me.ccip_Cd * me.ccip_q * me.ccip_bomb.ref_area_sqft) / me.ccip_mass; @@ -1168,7 +1169,7 @@ var AIM = { # we calc heading from composite speeds, due to alpha and beta might influence direction bombs will fall: me.ccip_heading = geo.normdeg(math.atan2(me.ccip_speed_east_fps,me.ccip_speed_north_fps)*R2D); me.ccipPos.apply_course_distance(me.ccip_heading, me.ccip_dist); - + me.ccip_elev = me.ccip_alti-me.ccip_agl;#faster me.ccipPos.set_alt(me.ccip_elev); return [me.ccipPos,me.arming_time=90?me.angleFromRight:-me.angleFromRight;#0 is up, 180 normalized - me.angleInHUD = geo.normdeg180(me.angleInHUD-OurRoll.getValue()); - me.degOnHUD = math.atan2(vector.Math.magnitudeVector(me.idHere),me.Tgt.get_range()*NM2M)*R2D;#deg/sec - #printf("ASC: %.1f deg loft. Inv trigo angle: %d deg. Lead %.1f deg. atan2(%d,%d)", me.idealLoft, me.angleInHUD, me.degOnHUD,vector.Math.magnitudeVector(me.idHere),me.Tgt.get_range()*NM2M); - return [me.angleInHUD, me.degOnHUD];# 0,0 is center of ASEC. Thats the origin of ASC (Attack Steering Cue) + + me.asc_av_spd_mps = 900;# should be per missile + + me.horiz_intercept = me.get_intercept(me.Tgt.get_bearing(), me.Tgt.get_range()*NM2M, me.Tgt.get_heading(), me.Tgt.get_Speed()*KT2MPS, me.asc_av_spd_mps, geo.aircraft_position(), OurHdg.getValue()); + # [me.timeToIntercept, me.interceptHeading, me.interceptCoord, me.interceptDist, me.interceptRelativeBearing + if(me.horiz_intercept == nil) { + #print("No aim120 intercept"); + return nil; + } + + me.loft_cue = 0; + if (me["dlz_opt"] != nil and me["dlz_nez"] != nil) { + me.tgtRange = me.Tgt.get_range(); + me.loft_cue = me.map(me.Tgt.get_range(), me.dlz_nez, (me.dlz_opt+me.max_fire_range_nm)*0.5, 0, 15); + me.loft_cue *= me.map(ourAlt.getValue(), 0, 40000, 3, 1); + } + + # Do not find a relative bearing that is so great that radar loses track of target: + me.maxBearing = me.fcs_fov - 10;# margin is 10 degrees + me.relativeBearing = geo.normdeg180(me.horiz_intercept[1]-me.Tgt.get_bearing()); + if (me.relativeBearing > me.maxBearing) { + me.horiz_intercept[1] = me.Tgt.get_bearing() + me.maxBearing; + } elsif (me.relativeBearing < -me.maxBearing) { + me.horiz_intercept[1] = me.Tgt.get_bearing() - me.maxBearing; + } + + return [me.horiz_intercept[1], math.clamp(me.loft_cue, 0, 45)]; # attack-bearing, loft-cue + }, + + get_intercept: func (bearingToRunner, dist_m, runnerHeading, runnerSpeed, chaserSpeed, chaserCoord, chaserHeading) { + # from Leto + # needs: bearingToRunner_deg, dist_m, runnerHeading_deg, runnerSpeed_mps, chaserSpeed_mps, chaserCoord + # dist_m > 0 and chaserSpeed > 0 + + if (dist_m < 500) { + return nil; + } + + me.trigAngle = 90-bearingToRunner; + me.RunnerPosition = [dist_m*math.cos(me.trigAngle*D2R), dist_m*math.sin(me.trigAngle*D2R),0]; + me.ChaserPosition = [0,0,0]; + + me.VectorFromRunner = vector.Math.minus(me.ChaserPosition, me.RunnerPosition); + me.runner_heading = 90-runnerHeading; + me.RunnerVelocity = [runnerSpeed*math.cos(me.runner_heading*D2R), runnerSpeed*math.sin(me.runner_heading*D2R),0]; + + me.a = chaserSpeed * chaserSpeed - runnerSpeed * runnerSpeed; + me.b = 2 * vector.Math.dotProduct(me.VectorFromRunner, me.RunnerVelocity); + me.c = -dist_m * dist_m; + + if ((me.b*me.b-4*me.a*me.c)<0) { + # intercept not possible + return nil; + } + + me.t1 = (-me.b+math.sqrt(me.b*me.b-4*me.a*me.c))/(2*me.a); + me.t2 = (-me.b-math.sqrt(me.b*me.b-4*me.a*me.c))/(2*me.a); + + if (me.t1 < 0 and me.t2 < 0) { + # intercept not possible + return nil; + } + + me.timeToIntercept = 0; + if (me.t1 > 0 and me.t2 > 0) { + me.timeToIntercept = math.min(me.t1, me.t2); + } else { + me.timeToIntercept = math.max(me.t1, me.t2); + } + me.InterceptPosition = vector.Math.plus(me.RunnerPosition, vector.Math.product(me.timeToIntercept, me.RunnerVelocity)); + + me.ChaserVelocity = vector.Math.product(1/me.timeToIntercept, vector.Math.minus(me.InterceptPosition, me.ChaserPosition)); + + me.interceptAngle = vector.Math.angleBetweenVectors([0,1,0], me.ChaserVelocity); + me.interceptHeading = geo.normdeg(me.ChaserVelocity[0]<0?-me.interceptAngle:me.interceptAngle); + + me.interceptDist = chaserSpeed*me.timeToIntercept; + + me.interceptCoord = geo.Coord.new(chaserCoord); + me.interceptCoord = me.interceptCoord.apply_course_distance(me.interceptHeading, me.interceptDist); + me.interceptRelativeBearing = geo.normdeg180(me.interceptHeading-chaserHeading); + + return [me.timeToIntercept, me.interceptHeading, me.interceptCoord, me.interceptDist, me.interceptRelativeBearing]; }, setContacts: func (vect) { @@ -1250,17 +1318,21 @@ var AIM = { commandDir: func (heading_deg, pitch_deg) { # commands are relative to aircraft bore if (me.status == MISSILE_FLYING) return; - me.command_dir_heading = heading_deg; - me.command_dir_pitch = pitch_deg; - me.slave_to_radar = FALSE; + if (vector.Math.angleBetweenVectors(vector.Math.eulerToCartesian2(-heading_deg, pitch_deg), [1,0,0]) <= me.fcs_fov) { + me.command_dir_heading = heading_deg; + me.command_dir_pitch = pitch_deg; + me.slave_to_radar = FALSE; + } me.printCode("Bore/dir command: heading %0.1f pitch %0.1f", heading_deg, pitch_deg); }, commandRadar: func (idle_heading = 0, idle_elevation = 0) { # command that radar is looking at a target, slave to that. if (!me.seam_support or me.status == MISSILE_FLYING) return; - me.command_dir_heading = idle_heading; - me.command_dir_pitch = idle_elevation; + if (me.status != MISSILE_LOCK) { + me.command_dir_heading = idle_heading; + me.command_dir_pitch = idle_elevation; + } me.slave_to_radar = TRUE; me.printCode("Slave radar command. Idle: heading %0.1f pitch %0.1f", idle_heading, idle_elevation); }, @@ -1372,7 +1444,7 @@ var AIM = { me.status = MISSILE_FLYING; if (vect!= nil) { - + # sets a vector of contacts the weapons will try to lock onto # For LOAL weapons. # see also setContacts() @@ -1381,7 +1453,7 @@ var AIM = { me.contacts = []; } me.launchSoundProp.setBoolValue(FALSE); - + if (me.engineEnabled and me.stage_1_duration > 0 and me.force_lbf_1 > 0 and me.drop_time < 1.75) { me.pendingLaunchSound = me.drop_time; } elsif (me.drop_time != 10000 and (!me.engineEnabled or me.stage_1_duration == 0 or me.force_lbf_1 == 0)) { @@ -1411,7 +1483,7 @@ var AIM = { # me.rail_head_deg = me.Tgt.get_bearing()-OurHdg.getValue(); #} me.railvec = vector.Math.eulerToCartesian3X(-me.rail_head_deg, me.rail_pitch_deg,0); - me.veccy = vector.Math.yawPitchRollVector(-ac_hdg,ac_pitch,ac_roll,me.railvec); + me.veccy = vector.Math.rollPitchYawVector(ac_roll,ac_pitch,-ac_hdg,me.railvec); me.carty = vector.Math.cartesianToEuler(me.veccy); me.msl_pitch = me.carty[1]; me.defaultHeading = me.Tgt != nil?me.Tgt.get_bearing():0;#90 deg tubes align to target heading, else north @@ -1442,26 +1514,19 @@ var AIM = { init_coord.set_xyz(pos.x, pos.y, pos.z); } else { init_coord = me.getGPS(me.x, me.y, me.z, ac_pitch, ac_hdg); - } + } # Set submodel initial position: var mlat = init_coord.lat(); var mlon = init_coord.lon(); var malt = init_coord.alt() * M2FT; - + me.coord = geo.Coord.new(init_coord); # Get target position. if (me.Tgt != nil) { - if (me.inacc) { - if (!me.target_pnt) { - me.Tgt.inac_x = 0; - me.Tgt.inac_y = 0; - me.Tgt.inac_z = 0; - } - if (me.Tgt == contactPoint) { - me.usingTGPPoint = 1; - } + if (me.Tgt == contactPoint) { + me.usingTGPPoint = 1; } me.t_coord = me.Tgt.get_Coord(); me.maddog = FALSE; @@ -1658,7 +1723,7 @@ var AIM = { if (me.guidanceLaw == "direct") { nav = "Pure pursuit." } elsif (me.guidanceLaw == "OPN") { - nav = "Original Proportional navigation. Proportionality constant is "~me.pro_constant; + nav = "Original Proportional navigation. Proportionality constant is "~me.pro_constant; } elsif (me.guidanceLaw == "PN") { nav = "Proportional navigation. Proportionality constant is "~me.pro_constant; } elsif (me.guidanceLaw == "APN") { @@ -1689,7 +1754,7 @@ var AIM = { vector = "Vectored thrust." } - + me.printStats("****************************************************"); me.printStats("Stats for %s", me.typeLong); me.printStats("Damage ID is %d, notification ID is %d, unique ID is %d", me.typeID, me.typeID+21,me.unique_id); @@ -1697,7 +1762,7 @@ var AIM = { me.printStats("Fire range %.1f-%.1f NM", me.min_fire_range_nm, me.max_fire_range_nm); if (me.expand_min) { me.printStats("Min fire range will expand with closing rate."); - } + } me.printStats("Can be fired againts %s targets", classes); me.printStats("Pilot will call out %s when firing.",me.brevity); me.printStats("Launch platform detection field of view is +-%d degrees.",me.fcs_fov); @@ -1770,7 +1835,7 @@ var AIM = { } else { me.printStats("Weapon will not snap up, follow terrain or sea skim."); } - + me.printStats("After propulsion ends, it will max steer up to %d degree pitch.",me.maxPitch); if(me.Tgt == nil) { me.printStats("Note: Ordnance was released with no lock or destination target."); @@ -1892,7 +1957,7 @@ var AIM = { } elsif (me.guidance == "heat") { me.printStats("Resistance to flares is %d%%.",me.flareResistance*100); } - } + } me.printStats("MISC:"); if (me.data) { me.printStats("Will transmit telemetry data back to launch platform."); @@ -1907,7 +1972,7 @@ var AIM = { } me.printStats("****************************************************"); }, - + setNewTargetInFlight: func (tagt) { me.Tgt = tagt; me.callsign = tagt==nil?"Unknown":damage.processCallsign(me.Tgt.get_Callsign()); @@ -1920,13 +1985,6 @@ var AIM = { me.semiLostLock = FALSE; me.heatLostLock = FALSE; me.hasGuided = FALSE; - if (me.inacc) { - if (!me.target_pnt and tagt!=nil) { - me.Tgt.inac_x = 0; - me.Tgt.inac_y = 0; - me.Tgt.inac_z = 0; - } - } }, flight: func { @@ -1957,7 +2015,7 @@ var AIM = { } me.elapsed = systime(); - + me.dt = (me.elapsed - me.elapsed_last)*speedUp.getValue(); me.elapsed_last = me.elapsed; @@ -1970,15 +2028,15 @@ var AIM = { me.life_time += me.dt; me.handleMidFlightFunc(); - + if (me.hasGuided and me.maddog) { me.maddog = FALSE; me.printStats("Maddog stage over, guided at "~me.callsign); } - + if (me.guidanceEnabled and me.free == FALSE and !me.newTargetAssigned and (me.canSwitch or (me.loal and me.maddog)) and size(me.contacts) > 0 and (me.dist_curr_direct==-1 or me.dist_curr_direct>me.reportDist)) { # me.reaquire must also be enabled for me.canSwitch to work - + if (me.Tgt==nil or me.hasGuided == FALSE or (me.canSwitch and (me.fovLost or me.lostLOS or me.radLostLock or me.semiLostLock or me.heatLostLock)) and me.life_time > me.nextFovCheck) { # test next contact me.numberContacts = size(me.contacts); @@ -1994,7 +2052,7 @@ var AIM = { me.newTargetAssigned = FALSE; } else { me.setNewTargetInFlight(me.newTgt); - } + } } } @@ -2016,7 +2074,7 @@ var AIM = { me.callsign = "Unknown"; me.newTargetAssigned = FALSE; } - } elsif (me.inacc and me.Tgt != nil and me.Tgt.get_type() == POINT and me.guidance == "laser" and me.usingTGPPoint and contactPoint == nil) { + } elsif (me.Tgt != nil and me.Tgt.get_type() == POINT and me.guidance == "laser" and me.usingTGPPoint and contactPoint == nil) { # if laser illuminated lock is lost on a targetpod target: if (!(me.canSwitch and me.reaquire)) { me.Tgt = nil; @@ -2027,7 +2085,7 @@ var AIM = { me.callsign = "Unknown"; me.newTargetAssigned = FALSE; } - } elsif (me.inacc and me.Tgt == nil and me.guidance == "laser" and me.usingTGPPoint and contactPoint != nil) { + } elsif (me.Tgt == nil and me.guidance == "laser" and me.usingTGPPoint and contactPoint != nil) { # see if we can regain lock on new laser spot: if (me.newLock(contactPoint)) { me.setNewTargetInFlight(contactPoint); @@ -2046,17 +2104,17 @@ var AIM = { me.deploy = me.clamp(me.extrapolate(me.life_time, me.rail_passed_time, me.rail_passed_time+me.deploy_time,0,1),0,1); } me.deploy_prop.setDoubleValue(me.deploy); - + me.thrust_lbf = me.thrust();# pounds force (lbf) - + # Get total old speed, thats what we will use in next loop. me.old_speed_horz_fps = math.sqrt((me.speed_east_fps*me.speed_east_fps)+(me.speed_north_fps*me.speed_north_fps)); me.old_speed_fps = math.sqrt((me.old_speed_horz_fps*me.old_speed_horz_fps)+(me.speed_down_fps*me.speed_down_fps)); me.setRadarProperties(me.old_speed_fps); - + # Get air density and speed of sound (fps): me.rs = me.rho_sndspeed(me.altN.getValue() + me.density_alt_diff); @@ -2086,13 +2144,13 @@ var AIM = { me.Cd = me.drag(me.speed_m,me.myG); me.speed_change_fps = me.speedChange(me.thrust_lbf, me.rho, me.Cd); - + if (me.last_dt != 0) { me.speed_change_fps = me.speed_change_fps + me.energyBleed(me.g, me.altN.getValue() + me.density_alt_diff); } - + ################### #### Guidance.##### @@ -2139,7 +2197,7 @@ var AIM = { me.prevETA = nil; } } - me.observing = me.guidance; + me.observing = me.guidance; } elsif (me.guidance != "unguided" and (me.rail == FALSE or me.rail_passed == TRUE) and me.guidanceEnabled and me.free == FALSE and me.t_coord == nil and (me.newTargetAssigned or (me.canSwitch and (me.fovLost or me.lostLOS or me.radLostLock or me.semiLostLock or me.heatLostLock) or (me.loal and me.maddog)))) { # check for too low speed not performed on purpuse, difference between flying straight on A/P and making manouvres. @@ -2212,11 +2270,11 @@ var AIM = { # missile still on rail, lets calculate its speed relative to the wind coming in from the aircraft nose. me.rail_speed_into_wind = me.rail_speed_into_wind + me.speed_change_fps; } else { - # gravity acc makes the weapon pitch down + # gravity acc makes the weapon pitch down me.pitch = math.atan2(-me.speed_down_fps, me.speed_horizontal_fps ) * R2D; } - + if (me.rail == TRUE and me.rail_passed == FALSE) { me.u = noseAir.getValue();# airstream from nose #var v = getprop("velocities/vBody-fps");# airstream from side @@ -2228,7 +2286,7 @@ var AIM = { me.hdg = OurHdg.getValue(); } else { me.railvec = me.myMath.eulerToCartesian3X(-me.rail_head_deg, me.rail_pitch_deg,0); - me.veccy = me.myMath.yawPitchRollVector(-OurHdg.getValue(),OurPitch.getValue(),OurRoll.getValue(),me.railvec); + me.veccy = me.myMath.rollPitchYawVector(OurRoll.getValue(),OurPitch.getValue(),-OurHdg.getValue(),me.railvec); me.carty = me.myMath.cartesianToEuler(me.veccy); me.defaultHeading = me.Tgt != nil?me.Tgt.get_bearing():0;#90 deg tubes align to target heading, else north me.pitch = me.carty[1]; @@ -2236,9 +2294,9 @@ var AIM = { } me.speed_on_rail = math.max(me.rail_speed_into_wind - me.opposing_wind, 0); - + me.movement_on_rail = me.speed_on_rail * me.dt; - + me.rail_pos = me.rail_pos + me.movement_on_rail; if (me.rail_forward == TRUE) { me.x = me.x - (me.movement_on_rail * FT2M);# negative cause positive is rear in body coordinates @@ -2311,7 +2369,7 @@ var AIM = { #me.t_coord.set_alt(me.flare_alt_ft * FT2M); } } - + # performance logging: # #var q = 0.5 * rho * me.old_speed_fps * me.old_speed_fps; @@ -2385,8 +2443,8 @@ var AIM = { # check stats while flying: # me.printFlight("Mach %04.2f , time %05.1f s , thrust %05.1f lbf , G-force %05.2f", me.speed_m, me.life_time, me.thrust_lbf, me.g); - me.printFlight("Alt %07.1f ft , direct distance to target %04.1f NM", me.alt_ft, (me.Tgt!=nil and me.direct_dist_m!=nil)?me.direct_dist_m*M2NM:-1); - + me.printFlight("Alt %07.1f ft , direct distance to target %04.1f NM", me.alt_ft, (me.Tgt!=nil and me.direct_dist_m!=nil)?me.direct_dist_m*M2NM:-1); + 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); @@ -2412,14 +2470,14 @@ var AIM = { if (me.Tgt == nil and me.rail == TRUE and me.rail_pitch_deg==90 and me.rail_passed == FALSE) { #for ejection seat to be oriented correct, wont be ran for missiles with target such as the frigate. - + # model of seat is loaded such that face is pointing in -Z FG coords. - + # Get the vector pointing up from aircraft var a = me.myMath.eulerToCartesian3Z(-OurHdg.getValue(), OurPitch.getValue(), OurRoll.getValue()); #printf("HPR: %0.4f %0.4f %0.4f",OurHdg.getValue(),OurPitch.getValue(),OurRoll.getValue()); #printf(" UP: %0.4f %0.4f %0.4f",a[0],a[1],a[2]); - + # get the pitch and heading of that vector var euler = me.myMath.cartesianToEuler(a); me.pitch = euler[1]; @@ -2431,19 +2489,19 @@ var AIM = { me.hdg = OurHdg.getValue(); } me.hdgN.setDoubleValue(me.hdg); - + # get vector pointing out from aircraft nose var nose = me.myMath.eulerToCartesian3X(-OurHdg.getValue(), OurPitch.getValue(), OurRoll.getValue()); - + # get vector pointing perpendicular to seat travel direction (most upward possible) var face = me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, 0); - + # convert to angle pointing out from face if pilot turns head without nodding and looks downward face = me.myMath.product(-1,face); - + # get angle between face and aircraft-nose vectors var turnFace = me.myMath.angleBetweenVectors(face,nose); - + if (me.myMath.angleBetweenVectors(nose,me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, turnFace)))>me.myMath.angleBetweenVectors(nose,me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, -turnFace)))) { # if angle between aircraft nose and the face of pilot is greater than the roll of seat then the roll should be opposite #print("looking1 "~me.myMath.cartesianToEuler(me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, turnFace)))[0]); @@ -2452,7 +2510,7 @@ var AIM = { } else { #turnFace *= -1; } - + # roll the seat so pilot faces forward me.rollN.setDoubleValue(turnFace); #print("looking2 "~me.myMath.cartesianToEuler(me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, turnFace)))[0]); @@ -2477,7 +2535,7 @@ var AIM = { # 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 @@ -2506,7 +2564,7 @@ var AIM = { 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; @@ -2514,7 +2572,7 @@ var AIM = { 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); @@ -2529,7 +2587,7 @@ var AIM = { } me.prevETA = me["eta"]; } - + if (me.life_time - me.last_noti > me.noti_time and getprop("payload/armament/msg")) { # notify in flight using Emesary. me.last_noti = me.life_time; @@ -2540,9 +2598,9 @@ var AIM = { } me.last_dt = me.dt; - + me.prevGuidance = me.guidance; - + if (me.counter > -1 and !me.ai.getNode("valid").getBoolValue()) { # TODO: Why is this placed so late? Don't remember. me.ai.getNode("valid").setBoolValue(1); @@ -2558,21 +2616,21 @@ var AIM = { # # # - ############################################################################################################# + ############################################################################################################# } }, handleMidFlightFunc: func { if(me.mfFunction != nil) { - - me.settings = me.mfFunction({ time_s: me.life_time, + + me.settings = me.mfFunction({ time_s: me.life_time, dist_m: me.dist_curr_direct, - mach: me.speed_m, - weapon_position: me.coord, - guidance: me.guidance, - seeker_detect_range: me.detect_range_curr_nm, - seeker_fov: me.max_seeker_dev, - weapon_pitch: me.pitch, + mach: me.speed_m, + weapon_position: me.coord, + guidance: me.guidance, + seeker_detect_range: me.detect_range_curr_nm, + seeker_fov: me.max_seeker_dev, + weapon_pitch: me.pitch, weapon_heading: me.hdg, callsign: me.callsign, deviation_deg: me["fov_radial"], @@ -2638,7 +2696,7 @@ var AIM = { me.ac_roll = roll; } me.ac_pitch = pitch; - + if (head == nil) { me.ac_hdg = OurHdg.getValue(); } else { @@ -2681,7 +2739,7 @@ var AIM = { me.mlat = me.ac.lat() + me.out[0]; me.mlon = me.ac.lon() + me.out[1]; me.malt = (me.ac.alt() * M2FT) + me.out[2]; - + me.c = geo.Coord.new(); me.c.set_latlon(me.mlat, me.mlon, me.malt * FT2M); @@ -2690,7 +2748,7 @@ var AIM = { drag: func (mach, N=nil) { # Nikolai V. Chr.: Made the drag calc more in line with big missiles as opposed to small bullets. - # + # # The old equations were based on curves for a conventional shell/bullet (no boat-tail), # and derived from Davic Culps code in AIBallistic. me.Cd0 = 0; @@ -2707,7 +2765,7 @@ var AIM = { me.Cd0 = (0.2965 * math.pow(mach, -2.1506) + 0.073766412) * 8 * (me.Cd_base+me.Cd_delta*me.deploy); } } - + if (!me.simple_drag) { if (me.vector_thrust and me.thrust_lbf>0) N=N*0.35; if (mach < 1.1) { @@ -2768,12 +2826,12 @@ var AIM = { me.thrust_lbf = 0; } } - + #me.force_cutoff_s = 0;# seen charts of real (aim9m) that thrust dont stop instantly, but fades out. This term would say hwo long it takes to fade out. Need to rework fuel consumption first. Maybe in future. #if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_2_duration-me.force_cutoff_s) and me.life_time < (me.drop_time + me.stage_1_duration + me.stage_2_duration)) { # me.thrust_lbf = me.extrapolate(me.life_time - (me.drop_time + me.stage_1_duration + me.stage_2_duration - me.force_cutoff_s),0,me.force_cutoff_s,me.thrust_lbf,0); #} - + if (me.thrust_lbf < 1) { me.smoke_prop.setBoolValue(0); } else { @@ -2786,7 +2844,7 @@ var AIM = { # Calculate speed change from last update. # # Acceleration = thrust/mass - drag/mass; - + me.acc = thrust_lbf / me.mass; me.q = 0.5 * rho * me.old_speed_fps * me.old_speed_fps;# dynamic pressure me.drag_acc = (Cd * me.q * me.ref_area_sqft) / me.mass; @@ -2842,7 +2900,7 @@ var AIM = { bleed0at25g: func () { me.loss_mps = 0 + ((me.last_dt - 0)/(7 - 0))*(-750 - 0); return me.loss_mps*M2FT; - }, + }, setFirst: func() { if (me.smoke_prop.getValue() == TRUE) { @@ -2883,10 +2941,10 @@ var AIM = { if(me.myG > me.max_g_current) { me.MyCoef = me.overload_limiter(me.hdg, me.pitch, me.track_signal_e, me.track_signal_h, me.old_speed_fps, me.dt, me.max_g_current); - + me.track_signal_h = me.MyCoef[0]; me.track_signal_e = me.MyCoef[1]; - + me.myGnew = me.steering_speed_G(me.hdg, me.pitch, me.track_signal_e, me.track_signal_h, me.old_speed_fps, me.dt); #me.printFlight(sprintf("G2 %.2f", myG)~sprintf(" - Coeff %.2f", MyCoef)); if (me.limitGs == FALSE) { @@ -2955,7 +3013,7 @@ var AIM = { # # navigation and guidance # - + me.raw_steer_signal_elev = 0; me.raw_steer_signal_head = 0; @@ -2973,7 +3031,7 @@ var AIM = { me.curr_deviation_h = me.t_course - me.hdg; #var (t_course, me.dist_curr) = courseAndDistance(me.coord, me.t_coord); - #me.dist_curr = me.dist_curr * NM2M; + #me.dist_curr = me.dist_curr * NM2M; me.curr_deviation_h = geo.normdeg180(me.curr_deviation_h); @@ -2982,7 +3040,7 @@ var AIM = { me.printFlightDetails("Altitude above launch platform = %07.1f ft", M2FT * (me.coord.alt()-me.ac.alt())); me.printFlightDetails("Altitude. Target %07.1f. Missile %07.1f. Atan2 %04.1f degs", me.t_coord.alt()*M2FT, me.coord.alt()*M2FT, math.atan2( me.t_coord.alt()-me.coord.alt(), me.dist_curr ) * R2D); - + if (math.abs(me.curr_deviation_h) < 15) { me.guidanceLawHorizInit = 0; @@ -3079,7 +3137,7 @@ var AIM = { 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.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) { @@ -3165,7 +3223,7 @@ var AIM = { } else { me.printStats(me.type~": Not guiding (lost radar reflection, 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; @@ -3177,10 +3235,10 @@ var AIM = { } else { me.printStats(me.type~": Not guiding (lost radiation, gave up)"); me.free = TRUE; - } + } } elsif ((me.dist_curr_direct*M2NM > me.detect_range_curr_nm or !me.FOV_check(me.hdg, me.pitch, me.curr_deviation_h, me.curr_deviation_e, me.max_seeker_dev, me.myMath)) and me.guidance != "gps" and me.guidance != "inertial") { # target is not in missile seeker view anymore - + if (me.fovLost == FALSE and me.detect_range_curr_nm != 0) { me.normFOV = me.FOV_check_norm(me.hdg, me.pitch, me.curr_deviation_h, me.curr_deviation_e, me.max_seeker_dev, me.myMath); me.printStats(me.type~": "~me.callsign~" is not in seeker view. (%d%% in view, %d%% in range)", me.normFOV*100, 100*me.dist_curr_direct*M2NM / me.detect_range_curr_nm);#~me.viewLost); @@ -3243,7 +3301,7 @@ var AIM = { canSeekerKeepUp: func () { me.globalVectorToTarget = me.myMath.eulerToCartesian3X(-me.t_course, me.t_elev_deg, 0); - me.localVectorTarget = me.myMath.rollPitchYawVector(0, -me.pitch, me.hdg, me.globalVectorToTarget); + me.localVectorTarget = me.myMath.yawPitchVector(me.hdg, -me.pitch, me.globalVectorToTarget); if (me.counter == me.counter_last+1 and !me.newTargetAssigned and me["localVectorSeeker"] != nil and (me.guidance == "heat" or me.guidance == "vision") and me.prevGuidance == me.guidance and me.prevTarget == me.Tgt) { # calculate if the seeker can keep up with the angular change of the target # @@ -3251,11 +3309,11 @@ var AIM = { # if (!me.caged) { # Gyro is stabilized - me.localVectorSeeker = me.myMath.rollPitchYawVector(0, -me.last_track_e, me.last_track_h, me.localVectorSeeker); + me.localVectorSeeker = me.myMath.yawPitchVector(me.last_track_h, -me.last_track_e, me.localVectorSeeker); } me.angleSeekerToTarget = me.myMath.angleBetweenVectors(me.localVectorSeeker, me.localVectorTarget); me.deviation_per_sec = me.angleSeekerToTarget/me.dt; - + if (me.deviation_per_sec > me.angular_speed) { if (me.angleSeekerToTarget < me.beam_width_deg) { me.max_seekertrack = me.angular_speed * me.dt; @@ -3288,7 +3346,7 @@ var AIM = { me.cruise_or_loft = FALSE;# If true then this method handles the vertical component of guiding. me.time_before_snap_up = me.drop_time * 3; me.limitGs = FALSE; - + if(me.loft_alt != 0 and me.snapUp == FALSE) { # this is for Air to ground/sea cruise-missile (SCALP, Sea-Eagle, Taurus, Tomahawk, RB-15...) @@ -3359,7 +3417,7 @@ var AIM = { howmany = howmany + 1; #We finalize the vector dir = {"x":me.geoPlus4.x()-me.coord.x(), "y":me.geoPlus4.y()-me.coord.y(), "z":me.geoPlus4.z()-me.coord.z()}; - #We measure distance to be sure that the ground intersection is closer than geoPlus4 + #We measure distance to be sure that the ground intersection is closer than geoPlus4 distance_Target = me.coord.direct_distance_to(me.geoPlus4); # Check for terrain between own aircraft and other: GroundIntersectResult = get_cart_ground_intersection(xyz, dir); @@ -3462,7 +3520,7 @@ var AIM = { me.printGuideDetails("Moving up"); me.raw_steer_signal_elev = -me.pitch + math.atan2(me.t_alt_delta_ft, me.old_speed_fps * me.dt * 5) * R2D; } else { - # that means a dive angle of 22.5° (a bit less + # that means a dive angle of 22.5° (a bit less # coz me.alt is in feet) (I let this alt in feet on purpose (more this figure is low, more the future pitch is high) me.printGuideDetails("Moving down"); me.slope = me.clamp(me.t_alt_delta_ft / 300, -7.5, 0);# the lower the desired alt is, the steeper the slope. @@ -3485,10 +3543,10 @@ var AIM = { # me.rotate_token = TRUE; # me.printGuide("Is last turn, snap-up/PN takes it from here..") # } - } elsif (me.snapUp == TRUE and me.t_elev_deg > me.clamp(-80/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 6 + } elsif (me.snapUp == TRUE and me.t_elev_deg > me.clamp(-50/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 6 and me.t_elev_deg < me.loft_angle #and me.t_elev_deg > -7.5 and me.dive_token == FALSE) { - # lofting: due to target is more than 10 miles out and we havent reached + # lofting: due to target is more than 10 miles out and we havent reached # our desired cruising alt, and the elevation to target is less than lofting angle. # The -7.5 limit, is so the seeker don't lose track of target when lofting. if (me.life_time < me.time_before_snap_up and me.coord.alt() * M2FT < me.loft_alt) { @@ -3504,7 +3562,7 @@ var AIM = { } me.cruise_or_loft = TRUE; } elsif (me.snapUp == TRUE and me.coord.alt() > me.t_coord.alt() and me.last_cruise_or_loft == TRUE - and me.t_elev_deg > me.clamp(-80/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 5.5) { + and me.t_elev_deg > me.clamp(-50/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 5.5) { # cruising: keeping altitude since target is below and more than -45 degs down me.ratio = (g_fps * me.dt)/me.old_speed_fps; @@ -3538,7 +3596,7 @@ var AIM = { me.track_signal_h = 0; me.printGuide("Trying to keep current %04.1f deg pitch.", me.pitch); }, - + level5: func () { me.track_signal_e = (5-me.pitch) * !me.free; me.track_signal_h = 0; @@ -3557,7 +3615,7 @@ var AIM = { me.track_signal_e = me.raw_steer_signal_elev * !me.free; me.track_signal_h = me.raw_steer_signal_head * !me.free; }, - + remoteControl: func () { me.track_signal_e = me.remote_control_pitch * me.dt * !me.free; me.track_signal_h = me.remote_control_yaw * me.dt * !me.free; @@ -3582,7 +3640,7 @@ var AIM = { ################################################# if (me.guidanceLaw == "direct") { - # pure pursuit + # pure pursuit me.raw_steer_signal_head = me.curr_deviation_h; if (me.cruise_or_loft == FALSE) { me.raw_steer_signal_elev = me.curr_deviation_e; @@ -3623,7 +3681,7 @@ var AIM = { me.last = me.life_time; me.next = me.seeker_filter*0.15*rand();# duration for this noise factor, till a new is computed. } - + me.horz_closing_rate_fps = ((me.dist_last - me.dist_curr)*M2FT)/me.dt+me.horz_closing_rate_fps;#clamped due to cruise missiles that can fly slower than target. me.horz_closing_rate_fps *= 0.5;# average over 2 frames me.printGuideDetails("Horz closing rate: %05d ft/sec", me.horz_closing_rate_fps); @@ -3634,13 +3692,13 @@ var AIM = { # This is especially important before we really gain speed against a receding fast target. me.course_deviation = geo.normdeg180(me.t_course-me.last_t_course); - + me.line_of_sight_rate_rps = (D2R*me.course_deviation)/me.dt;#positive clockwise me.printGuideDetails("LOS rate: %06.4f rad/s", me.line_of_sight_rate_rps); - me.t_velocity = me.myMath.getCartesianVelocity(me.Tgt.get_heading(), me.Tgt.get_Pitch(), me.Tgt.get_Roll(), me.Tgt.get_uBody(), me.Tgt.get_vBody(), me.Tgt.get_wBody()); - + me.t_velocity = me.myMath.getCartesianVelocity(-me.Tgt.get_heading(), me.Tgt.get_Pitch(), me.Tgt.get_Roll(), me.Tgt.get_uBody(), me.Tgt.get_vBody(), me.Tgt.get_wBody()); + if ((me.flareLock == FALSE and me.chaffLock == FALSE) or me.t_heading == nil) { me.euler = me.myMath.cartesianToEuler(me.t_velocity); if (me.euler[0] != nil) { @@ -3653,7 +3711,7 @@ var AIM = { } elsif (me.flarespeed_fps != nil) { me.t_speed_fps = me.flarespeed_fps;#true airspeed } - + me.t_horz_speed_fps = math.sqrt(me.t_velocity[0]*me.t_velocity[0]+me.t_velocity[1]*me.t_velocity[1]); me.t_LOS_norm_head_deg = me.t_course + 90;#when looking at target this direction will be 90 deg right of target me.t_LOS_norm_speed_fps = math.cos((me.t_LOS_norm_head_deg - me.t_heading)*D2R)*me.t_horz_speed_fps; @@ -3714,13 +3772,13 @@ var AIM = { #printf("horz acc = %.1f + %.1f", proportionality_constant*line_of_sight_rate_rps*horz_closing_rate_fps, proportionality_constant*t_LOS_norm_acc/2); if (me.guidanceLawHorizInit) { - # pure horiz pursuit + # pure horiz pursuit me.raw_steer_signal_head = me.curr_deviation_h; } # now translate that sideways acc to an angle: - - + + #printf("Proportional lead: %0.1f deg horz", -(me.curr_deviation_h-me.raw_steer_signal_head)); @@ -3746,12 +3804,12 @@ var AIM = { # proportional navigation for elevation # ######################################### #me.print(me.guidanceLaw~" in fully control"); - + me.line_of_sight_rate_up_rps = (D2R*(me.t_elev_deg-me.last_t_elev_deg))/me.dt; # calculate target acc as normal to LOS line: (up acc is positive) me.t_approach_bearing = me.t_course + 180; - + # used to do this with trigonometry, but vector math is simpler to understand: (they give same result though) me.t_LOS_elev_norm_speed = me.scalarProj(me.t_heading,me.t_pitch,me.t_speed_fps,me.t_approach_bearing,me.t_elev_deg*-1 +90); @@ -3759,7 +3817,7 @@ var AIM = { if (me.last_t_elev_norm_speed == nil) { me.last_t_elev_norm_speed = me.t_LOS_elev_norm_speed; } - + me.t_LOS_elev_norm_acc = (me.t_LOS_elev_norm_speed - me.last_t_elev_norm_speed)/me.dt; me.last_t_elev_norm_speed = me.t_LOS_elev_norm_speed; @@ -3796,7 +3854,7 @@ var AIM = { me.raw_steer_signal_elev = me.dt*me.radians_up_per_sec*R2D; } - # now compensate for the predicted gravity drop of attitude: + # now compensate for the predicted gravity drop of attitude: me.attitudePN = (math.atan2(-(me.speed_down_fps+g_fps * me.dt), me.speed_horizontal_fps ) - math.atan2(-me.speed_down_fps, me.speed_horizontal_fps )) * R2D; me.gravComp = -me.attitudePN; #printf("Gravity compensation %0.2f degs", me.gravComp); @@ -3847,6 +3905,7 @@ var AIM = { ####Ground interaction me.ground = geo.elevation(me.coord.lat(), me.coord.lon()) or 0; + me.terrainImpact = 0; if(me.ground > me.coord.alt()) { me.event = "exploded"; if(me.life_time < me.arming_time) { @@ -3861,9 +3920,9 @@ var AIM = { #me.direct_dist_m = me.coord.direct_distance_to(me.Tgt.get_Coord()); } if ((me.Tgt != nil and me.direct_dist_m != nil) or me.Tgt == nil) { - me.coord.set_alt(me.ground); - me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); - return TRUE; + me.terrainImpact = 1; + #me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); + #return TRUE; } } @@ -3872,14 +3931,20 @@ var AIM = { # (use xyz coord to avoid the strange behaviour of comparing geo coordinates). for (var i=me.crc_frames_look_back; i >= 0; i-=1){ me.crc_coord[i] = (i != 0) ? me.crc_coord[i-1] : me.coord.xyz(); - me.crc_t_coord[i] = (i != 0) ? me.crc_t_coord[i-1] : (me.inacc?me.Tgt.get_Coord(0).xyz():me.t_coord.xyz()); + me.crc_t_coord[i] = (i != 0) ? me.crc_t_coord[i-1] : me.t_coord.xyz(); me.crc_range[i] = (i != 0) ? me.crc_range[i-1] : me.myMath.magnitudeVector( - me.myMath.minus(me.crc_coord[0], + me.myMath.minus(me.crc_coord[0], me.crc_t_coord[0])); } - if (me.crc_coord[1] == nil or me.crc_t_coord[1] == nil or me.crc_range[1] == nil) - return FALSE; # Wait for the buffer to fill at least once. + if (me.crc_coord[1] == nil or me.crc_t_coord[1] == nil or me.crc_range[1] == nil) { + if (me.terrainImpact) { + me.coord.set_alt(me.ground); + me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); + return TRUE; + } + return FALSE; # Wait for the buffer to fill at least once. + } if (me.life_time > me.arming_time) { # Distance to target increase. @@ -3888,7 +3953,7 @@ var AIM = { me.subframeClosestRangeCoord(); # Provides `me.crc_closestRange` and `me.crc_missileCoord`. me.explode("Passed target.", me.crc_missileCoord, me.crc_closestRange); - return TRUE; + return TRUE; } if (me.life_time > me.selfdestruct_time or (me.destruct_when_free == TRUE and me.free == TRUE)) { me.explode("Selfdestructed.", me.coord); @@ -3901,14 +3966,19 @@ var AIM = { me.explode("Selfdestructed.", me.coord); return TRUE; } + if (me.terrainImpact) { + me.coord.set_alt(me.ground); + me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); + return TRUE; + } return FALSE; }, - + #! brief: Recursive function to compute the closest range in the past mfd frames. #! param fei: The frame to compute first (0: current frame, 1: previous frame, ...). #! param mfd: The maximum amount of frames available if needed. - #! input me.crc_coord: The coordinates of the missile in the last frames. - #! input me.crc_t_coord: The coordinates of the target in the last frames. + #! input me.crc_coord: The coordinates of the missile in the last frames. + #! input me.crc_t_coord: The coordinates of the target in the last frames. #! output me.crc_missileCoord: The coordinates of the missile when it was the closest to the target in the mfd+1 last frames. #! output me.crc_closestRange: The range of the missile when it was the closest to the target in the mfd+1 last frames. subframeClosestRangeCoord : func(fei=0, mfd=nil) { @@ -3923,10 +3993,10 @@ var AIM = { die("Argument exception: The fei (Frames End Index) cannot be negative."); # Ensure the availability of the frame-end data. - if(me.crc_coord[fei] == nil or me.crc_t_coord[fei] == nil) + if(me.crc_coord[fei] == nil or me.crc_t_coord[fei] == nil) die("No coordinates available for the end of the frame."); - # Indices for the coordinates at frame start (fsi) and frame end (fei); + # Indices for the coordinates at frame start (fsi) and frame end (fei); var fsi = fei + 1; # Buffers used for unprocessed result, set to the frame-end values in case of unavailable frame-start data; @@ -3934,7 +4004,7 @@ var AIM = { var targetCoord = me.crc_t_coord[fei]; # Check for availability of the frame-start data - if(me.crc_coord[fsi] != nil and me.crc_t_coord[fsi] != nil){ + if(me.crc_coord[fsi] != nil and me.crc_t_coord[fsi] != nil){ # Get the origin coordinates and speed of the missile and it's target for the current frame. # The units are in m for distances and frames for time. var misCoord = me.crc_coord[fsi]; @@ -3957,7 +4027,7 @@ var AIM = { else if (t < 0) if (mfd > 1) # If we can recursively compute the previous frame: return me.subframeClosestRangeCoord(fei+1, mfd-1); # Return it's result instead, and stop here. - else + else t = 0; # Set it to 0 to prevent extrapolation. # Compute (interpolate) the position of the missile and it's target when their range is the closest. @@ -3971,11 +4041,11 @@ var AIM = { me.crc_missileCoord.set_xyz(missileCoord[0], missileCoord[1], missileCoord[2]); me.crc_missileCoord.alt();# TODO: once fixed in FG this line is no longer needed. }, - + log: func (str) { damage.damageLog.push(str); }, - + notifyInFlight: func (lat,lon,alt,rdar,typeID,typ,unique,thrustOn,callsign, heading, pitch, speed, is_deleted=0) { ## thrustON cannot be named 'thrust' as FG for some reason will then think its a function (probably fixed by the way call() now is used) var msg = notifications.ArmamentInFlightNotification.new("mfly", unique, is_deleted?damage.DESTROY:damage.MOVE, 21+typeID); @@ -3994,15 +4064,16 @@ var AIM = { msg.Pitch = pitch; msg.Heading = heading; msg.u_fps = speed; + #msg.isValid(); notifications.geoBridgedTransmitter.NotifyAll(msg); #print("fox2.nas: transmit in flight"); #f14.debugRecipient.Receive(msg); }, - + notifyCrater: func (lat,lon,alt,big,heading,static) { var uni = int(rand()*15000000); var msg = notifications.StaticNotification.new("stat", uni, 1, big); - + msg.Position.set_latlon(lat,lon,alt); msg.IsDistinct = 0; msg.Heading = heading; @@ -4011,13 +4082,13 @@ var AIM = { #f14.debugRecipient.Receive(msg); damage.statics["obj_"~uni] = [static, lat,lon,alt, heading,big]; }, - + notifyHit: func (RelativeAltitude, Distance, callsign, Bearing, reason, typeID, type, self) { var msg = notifications.ArmamentNotification.new("mhit", 4, 21+typeID); msg.RelativeAltitude = RelativeAltitude; msg.Bearing = Bearing; msg.Distance = Distance; - msg.RemoteCallsign = callsign; # RJHTODO: maybe handle flares / chaff + msg.RemoteCallsign = callsign; # RJHTODO: maybe handle flares / chaff if (self) { msg.Callsign = callsign; msg.FromIncomingBridge = 1; @@ -4041,17 +4112,17 @@ var AIM = { } elsif (me.chaffLock) { reason = "Locked onto chaff."; } - + me.coord = coordinates; # Set the current missile coordinates at the explosion point. - + if(getprop("payload/armament/msg")) { thread.lock(mutexTimer); append(AIM.timerQueue, [AIM, AIM.notifyInFlight, [me.coord.lat(), me.coord.lon(), me.coord.alt(),0,me.typeID,me.type,me.unique_id,0,"", me.hdg, me.pitch, 0, 0], -1]); thread.unlock(mutexTimer); } - + var wh_mass = (event == "exploded" and !me.inert) ? me.weight_whead_lbm : 0; #will report 0 mass if did not have time to arm - + thread.lock(mutexTimer); append(AIM.timerQueue, [me,impact_report,[coordinates, wh_mass, "munition", me.type, me.new_speed_fps*FT2M],0]); thread.unlock(mutexTimer); @@ -4080,7 +4151,7 @@ var AIM = { me.printStats("%s time %.1f", phrase, me.life_time); if(getprop("payload/armament/msg") and hitPrimaryTarget and wh_mass > 0){ thread.lock(mutexTimer); - append(AIM.timerQueue, [AIM, AIM.notifyHit, [coordinates.alt() - (me.inacc?me.Tgt.get_Coord(0).alt():me.t_coord.alt()),range,me.callsign,coordinates.course_to(me.inacc?me.Tgt.get_Coord(0):me.t_coord),reason,me.typeID, me.typeLong, 0], -1]); + append(AIM.timerQueue, [AIM, AIM.notifyHit, [coordinates.alt() - me.t_coord.alt(),range,me.callsign,coordinates.course_to(me.t_coord),reason,me.typeID, me.typeLong, 0], -1]); thread.unlock(mutexTimer); } else { thread.lock(mutexTimer); @@ -4096,7 +4167,7 @@ var AIM = { thread.unlock(mutexTimer); } } - + me.ai.getNode("valid", 1).setBoolValue(0); thread.lock(mutexTimer); append(AIM.timerQueue, [me, me.setModelRemoved, [], -1]); @@ -4110,7 +4181,7 @@ var AIM = { } me.Tgt = nil; }, - + multiExplosion: func (explode_coord, event, wh_mass) { # hit everything that is nearby except for target itself. me.sendout = 0; @@ -4119,9 +4190,7 @@ var AIM = { continue; } var min_distance = me.testMe.get_Coord().direct_distance_to(explode_coord); - if (me.inacc) { - min_distance = me.testMe.get_Coord(0).direct_distance_to(explode_coord); - } + if (min_distance < me.reportDist and (me.Tgt == nil or me.testMe.getUnique() != me.Tgt.getUnique())) { var phrase = sprintf("%s %s: %.1f meters from: %s", me.type,event, min_distance, me.testMe.get_Callsign()); me.printStats(phrase); @@ -4196,7 +4265,7 @@ var AIM = { if (me.noCommonTarget) { return nil; } - if (me.inacc and me.target_pnt and contactPoint != nil) { + if (me.target_pnt and contactPoint != nil) { return contactPoint; } else { return contact; @@ -4269,9 +4338,9 @@ var AIM = { } me.cooling_last_time = me.cool_elapsed; me.detect_range_curr_nm = me.extrapolate(me.warm, 0, 1, me.cold_detect_range_nm, me.warm_detect_range_nm); - #me.detect_range_curr_nm *= me.seam_scan?0.5:1; # source for this is not credible. + me.detect_range_curr_nm *= me.seam_scan?0.65:1;#GR1F-16CJ-34-1-1 page 1-402 } else { - #me.detect_range_curr_nm = (me.seam_scan?0.5:1)*me.max_fire_range_nm; # no credible source for this + me.detect_range_curr_nm = (me.seam_scan?0.65:1)*me.max_fire_range_nm;#GR1F-16CJ-34-1-1 page 1-402 } }, @@ -4279,7 +4348,7 @@ var AIM = { # call this only before firing if (!(me.tagt.get_type() == AIR and me.tagt.get_Speed()<15) and ((me.guidance != "semi-radar" or me.is_painted(me.tagt) == TRUE) and (me.guidance !="laser" or me.is_laser_painted(me.tagt) == TRUE)) and (me.guidance != "radiation" or me.is_radiating_aircraft(me.tagt) == TRUE) - and me.rng < me.max_fire_range_nm and me.rng > me.getCurrentMinFireRange(me.tagt) and me.FOV_check(OurHdg.getValue(),OurPitch.getValue(),me.total_horiz, me.total_elev, me.slave_to_radar?(me.guidance == "heat" or me.guidance == "vision"?math.min(me.max_seeker_dev, me.fcs_fov):me.fcs_fov):me.max_seeker_dev, vector.Math) + and me.rng < me.max_fire_range_nm and me.rng > me.getCurrentMinFireRange(me.tagt) and me.FOV_check(OurHdg.getValue(),OurPitch.getValue(),me.total_horiz, me.total_elev, me.slave_to_radar or contactPoint==me.tagt?(me.guidance == "heat" or me.guidance == "vision"?math.min(me.max_seeker_dev, me.fcs_fov):me.fcs_fov):me.max_seeker_dev, vector.Math) and (me.rng < me.detect_range_curr_nm or (me.guidance != "radar" and me.guidance != "semi-radar" and me.guidance != "heat" and me.guidance != "vision" and me.guidance != "heat" and me.guidance != "radiation")) and (me.guidance != "heat" or (me.all_aspect == TRUE or me.rear_aspect(geo.aircraft_position(), me.tagt) == TRUE)) and me.checkForView()) { @@ -4305,7 +4374,7 @@ var AIM = { me.geodPos = aircraftToCart({x:-me.radarX, y:me.radarY, z: -me.radarZ}); me.launchCoord.set_xyz(me.geodPos.x, me.geodPos.y, me.geodPos.z); } - + me.potentialCoord = me.tagt.get_Coord(); me.xyz = {"x":me.launchCoord.x(), "y":me.launchCoord.y(), "z":me.launchCoord.z()}; me.directionLOS = {"x":me.potentialCoord.x()-me.launchCoord.x(), "y":me.potentialCoord.y()-me.launchCoord.y(), "z":me.potentialCoord.z()-me.launchCoord.z()}; @@ -4327,7 +4396,7 @@ var AIM = { } return TRUE; }, - + checkForViewInFlight: func (tagt) { if (me.guidance != "gps" and me.guidance != "inertial") { me.launchCoord = me.coord; @@ -4365,7 +4434,7 @@ var AIM = { #if(me.slaveContact != nil) printf("class failed %d %d %d",me.slaveContact.isValid() == TRUE,me.slaveContact.get_type() == AIR,me.target_air == TRUE); return FALSE; }, - + newLock: func (tagt) { # for switching to new lock during flight. #reorder to gain performance @@ -4478,7 +4547,7 @@ var AIM = { # Check if in range and in the seeker FOV. if (me.checkForLock()) { me.printSearch("uncaged-search ready for lock"); - + me.convertGlobalToSeekerViewDirection(me.tagt.get_bearing(), me.tagt.getElevation(), OurHdg.getValue(), OurPitch.getValue(), OurRoll.getValue()); me.testSeeker(); if (me.inBeam) { @@ -4581,7 +4650,7 @@ var AIM = { if (me.checkForClass()) { me.tagt = me.slaveContact; me.rng = me.tagt.get_range(); - + # Check if in range and in the seeker FOV. me.total_elev = deviation_normdeg(OurPitch.getValue(), me.tagt.getElevation()); # deg. me.total_horiz = deviation_normdeg(OurHdg.getValue(), me.tagt.get_bearing()); # deg. @@ -4616,7 +4685,7 @@ var AIM = { settimer(func me.update_lock(), 0.1); }, - convertGlobalToSeekerViewDirection: func (bearing, elevation, heading, pitch, roll) { + convertGlobalToSeekerViewDirection: func (bearing, elevation, heading, pitch, roll) { me.target_x = math.cos(bearing*D2R)*math.cos(elevation*D2R); me.target_y = -math.sin(bearing*D2R)*math.cos(elevation*D2R); me.target_z = math.sin(elevation*D2R); @@ -4627,7 +4696,7 @@ var AIM = { me.rotation = vector.Math.multiplyMatrices(me.rollLaunchvehicle, vector.Math.multiplyMatrices(me.pitchLaunchvehicle, me.yawLaunchvehicle)); me.target_vector_from_seekers_view = vector.Math.multiplyMatrixWithVector(me.rotation, me.target_vector); me.angles = vector.Math.cartesianToEuler(me.target_vector_from_seekers_view); - + me.seeker_head_target = me.angles[0]==nil?0:geo.normdeg180(me.angles[0]); me.seeker_elev_target = me.angles[1]; }, @@ -4708,7 +4777,7 @@ var AIM = { if (me.seeker_last_time != 0) { me.seeker_time = me.seeker_elapsed - me.seeker_last_time; me.seeker_max_move = me.seeker_time*me.angular_speed; - + # Build unit vector components for seeker and target location in aircraft frame: me.seeker_x = math.cos(me.seeker_head*D2R)*math.cos(me.seeker_elev*D2R); me.seeker_y = -math.sin(me.seeker_head*D2R)*math.cos(me.seeker_elev*D2R); @@ -4716,9 +4785,9 @@ var AIM = { me.ideal_seeker_deviation = vector.Math.angleBetweenVectors([me.seeker_x,me.seeker_y,me.seeker_z],[me.target_x,me.target_y,me.target_z]); me.ideal_total_seeker_deviation = vector.Math.angleBetweenVectors(me.seeker_reset, [me.target_x,me.target_y,me.target_z]); - + if (me.ideal_seeker_deviation > me.seeker_max_move) { - me.new_seeker_vector = vector.Math.rotateVectorTowardsVector([me.seeker_x,me.seeker_y,me.seeker_z],[me.target_x,me.target_y,me.target_z],me.seeker_max_move); + me.new_seeker_vector = vector.Math.rotateVectorTowardsVector([me.seeker_x,me.seeker_y,me.seeker_z],[me.target_x,me.target_y,me.target_z],me.seeker_max_move); me.new_seeker_deviation = vector.Math.angleBetweenVectors(me.seeker_reset, me.new_seeker_vector); if (me.new_seeker_deviation < me.max_seeker_dev) { @@ -4761,7 +4830,7 @@ var AIM = { # Compute HUD diamond position. if ( use_fg_default_hud == TRUE) { var h_rad = (90 - me.seeker_head) * D2R; - var e_rad = (90 - me.seeker_elev) * D2R; + var e_rad = (90 - me.seeker_elev) * D2R; var devs = develev_to_devroll(h_rad, e_rad); var combined_dev_deg = devs[0]; var combined_dev_length = devs[1]; @@ -4812,12 +4881,16 @@ var AIM = { return; } elsif (me.deleted == TRUE) { return; + } elsif (me.slave_to_radar and me.caged and me.getContact() != me.Tgt) { + me.printSearch("target switch"); + me.return_to_search(); + return; } me.printSearch("lock"); # Time interval since lock time or last track loop. - #if (me.status == MISSILE_LOCK) { + #if (me.status == MISSILE_LOCK) { # Status = locked. Get target position relative to our aircraft. - + #} #me.time = props.globals.getNode("/sim/time/elapsed-sec", 1).getValue(); @@ -4829,7 +4902,7 @@ var AIM = { me.computeSeekerPos(); if (me.status != MISSILE_STANDBY ) {#TODO: should this also check for starting up? me.in_view = me.check_t_in_fov(); - + if (me.in_view == FALSE) { me.printSearch("out of view"); me.return_to_search(); @@ -4880,7 +4953,7 @@ var AIM = { me.return_to_search(); return; } - + settimer(func me.update_lock(), deltaSec.getValue()==0?0.5:0.1); return; } @@ -4915,7 +4988,7 @@ var AIM = { #if (me.status==MISSILE_FLYING) printf("1: %.1f out of %.1f, deviation %.1f, %.1f", me.fov_radial, fov_radius, deviation_hori, deviation_elev); return FALSE; }, - + FOV_check_norm: func (meHeading, mePitch, deviation_hori, deviation_elev, fov_radius,vect) { # we measure the geodesic angle between current attitude and the attitude to target. me.meVector = vect.eulerToCartesian3X(-meHeading, mePitch, 0); @@ -5014,10 +5087,10 @@ var AIM = { var explode_smoke_path = path_base~"explode-smoke-id-" ~ me.ID; me.explode_smoke_prop = props.globals.initNode( explode_smoke_path, FALSE, "BOOL", TRUE); - + var explode_water_path = path_base~"explode-water-id-" ~ me.ID; me.explode_water_prop = props.globals.initNode( explode_water_path, FALSE, "BOOL", TRUE); - + var explode_angle_path = path_base~"explode-angle"; me.explode_angle_prop = props.globals.initNode( explode_angle_path, 0.0, "DOUBLE", TRUE); @@ -5043,7 +5116,7 @@ var AIM = { me.msl_prop.setBoolValue(FALSE); me.smoke_prop.setBoolValue(FALSE); var info = geodinfo(me.coord.lat(), me.coord.lon()); - + if (hitGround) { if (info == nil) { me.explode_water_prop.setBoolValue(FALSE); @@ -5090,7 +5163,7 @@ var AIM = { siz = 1; crater_model = getprop("payload/armament/models") ~ "crater_big.xml"; } - + if (crater_model != "" and me.weight_whead_lbm > 150) { var static = geo.put_model(crater_model, me.coord.lat(), me.coord.lon()); #print("put crater"); @@ -5160,7 +5233,7 @@ var AIM = { # next speed vector me.vector_next_x = math.cos(me.steer_deg*D2R)*s_fps; me.vector_next_y = math.sin(me.steer_deg*D2R)*s_fps; - + # present speed vector me.vector_now_x = s_fps; me.vector_now_y = 0; @@ -5168,76 +5241,76 @@ var AIM = { # Delta velocity: subtract the vectors from each other and get the magnitude me.dv = me.myMath.minus([me.vector_now_x,me.vector_now_y,0],[me.vector_next_x,me.vector_next_y,0]); me.dv = me.myMath.magnitudeVector(me.dv); - + # calculate g-force # dv/dt=a me.g = (me.dv/dt) / g_fps; return me.g; }, - + steering_speed_G: func(meHeading, mePitch, steering_e_deg, steering_h_deg, s_fps, dt) { # Get G number from steering (e, h) in deg, speed in ft/s. me.meVector = me.myMath.eulerToCartesian3X(-meHeading, mePitch, 0); me.meVectorN= me.myMath.normalize(me.meVector); - me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now + me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now me.itVector = me.myMath.eulerToCartesian3X(-(meHeading+steering_h_deg), mePitch+steering_e_deg, 0); me.itVector = me.myMath.normalize(me.itVector); me.itVector = me.myMath.product(s_fps, me.itVector);#velocity vector if doing that steering - + # Delta lateral velocity me.dv = me.myMath.minus(me.itVector, me.meVector); - me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dv); + me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dv); me.dv = me.myMath.magnitudeVector(me.dv); - + # calculate g-force # dv/dt=a me.g = (me.dv/dt) / g_fps; return me.g; }, - + overload_limiter: func(meHeading, mePitch, steering_e_deg, steering_h_deg, s_fps, dt, gMax) { # The missile desires to rotate a certain amount # This function will limit that steering to prevent it from exceeding the max G it should be able to do at this air density - + if (gMax == 0 or (steering_e_deg == 0 and steering_h_deg == 0)) { return [0,0]; } - + # Get G number from steering (e, h) in deg, speed in ft/s. me.meVector = me.myMath.eulerToCartesian3X(-meHeading, mePitch, 0); me.meVectorN= me.myMath.normalize(me.meVector); - me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now + me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now me.itVector = me.myMath.eulerToCartesian3X(-(meHeading+steering_h_deg), mePitch+steering_e_deg, 0); me.itVectorN= me.myMath.normalize(me.itVector); me.itVector = me.myMath.product(s_fps, me.itVectorN);#velocity vector if doing that steering - + # Delta lateral velocity me.dvAcc = me.myMath.minus(me.itVector, me.meVector); - me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dvAcc); + me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dvAcc); me.dv = me.myMath.magnitudeVector(me.dv); - + # calculate g-force # dv/dt=a me.g_load = (me.dv/dt) / g_fps; - + me.exceed_g = gMax/me.g_load; - + if (me.exceed_g >= 1) { return [steering_h_deg, steering_e_deg]; } - + # Now do something that works okay for small desired steerings, but fails for big steerings. Hence the 0.9 factor. me.dvAcc9 = me.myMath.product(me.exceed_g*0.9, me.dvAcc); me.itVector = me.myMath.plus(me.dvAcc9, me.meVector); - + me.euler = me.myMath.cartesianToEuler(me.itVector); - + if (me.euler[0] == nil) { me.euler[0] = meHeading; } - + return [geo.normdeg180(me.euler[0]-meHeading), me.euler[1]-mePitch]; }, @@ -5370,7 +5443,7 @@ var AIM = { thread.unlock(mutexTimer); } }, - + timerLoop: func { thread.lock(mutexTimer); AIM.tq = AIM.timerQueue; @@ -5380,7 +5453,7 @@ var AIM = { AIM.timerCall(cmd); } }, - + timerCall: func (cmd) { if (cmd != nil) { if (cmd[3] == -1) { @@ -5395,7 +5468,7 @@ var AIM = { } } }, - + pop_front: func (vector) { if (size(vector)==0) return [nil, vector]; var new_vector = []; @@ -5406,9 +5479,9 @@ var AIM = { } return [vector[0],new_vector]; }, - + timers: {}, - + timerQueue: [], active: {}, @@ -5504,16 +5577,18 @@ var hud_radius_m = 0.100; #was in hud var develev_to_devroll = func(dev_rad, elev_rad) { + if (math.sin(dev_rad) == 0 or math.sin(elev_rad) == 0) return [0,0,0]; + if (math.cos(dev_rad) == 0 or math.cos(elev_rad) == 0) return [0,20,1]; var clamped = 0; # Deviation length on the HUD (at level flight), # 0.6686m = distance eye <-> virtual HUD screen. var h_dev = eye_hud_m / ( math.sin(dev_rad) / math.cos(dev_rad) ); var v_dev = eye_hud_m / ( math.sin(elev_rad) / math.cos(elev_rad) ); # Angle between HUD center/top <-> HUD center/symbol position. - # -90° left, 0° up, 90° right, +/- 180° down. + # -90° left, 0° up, 90° right, +/- 180° down. var dev_deg = math.atan2( h_dev, v_dev ) * R2D; # Correction with own a/c roll. - var combined_dev_deg = dev_deg - OurRoll.getValue(); + var combined_dev_deg = dev_deg; # Lenght HUD center <-> symbol pos on the HUD: var combined_dev_length = math.sqrt((h_dev*h_dev)+(v_dev*v_dev)); # clamp and squeeze the top of the display area so the symbol follow the egg shaped HUD limits. @@ -5522,7 +5597,7 @@ var develev_to_devroll = func(dev_rad, elev_rad) { if ( abs_combined_dev_deg >= 0 and abs_combined_dev_deg < 90 ) { var coef = ( 90 - abs_combined_dev_deg ) * 0.00075; if ( coef > 0.050 ) { coef = 0.050 } - clamp -= coef; + clamp -= coef; } if ( combined_dev_length > clamp ) { combined_dev_length = clamp; diff --git a/MIM-104D/Nasal/vector.nas b/MIM-104D/Nasal/vector.nas index 71aa769..621b0cb 100644 --- a/MIM-104D/Nasal/vector.nas +++ b/MIM-104D/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.97 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -39,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -85,21 +87,24 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.rollM, me.multiplyMatrices(me.pitchM, me.yawM)); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.rollM, me.multiplyMatrices(me.pitchM, me.yawM)); return me.multiplyMatrixWithVector(me.rotation, vector); }, @@ -119,6 +124,12 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + # rotate a vector. Order: pitch pitchVector: func (pitch, vector) { me.pitchM = me.pitchMatrix(pitch); @@ -187,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -285,13 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, # Project a onto ontoMe. projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -398,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/S-300/Models/ClamShell.xml b/S-300/Models/ClamShell.xml index e59ea0f..27f0708 100644 --- a/S-300/Models/ClamShell.xml +++ b/S-300/Models/ClamShell.xml @@ -8,7 +8,7 @@ spin Cube /sim/multiplay/online - 5.0 + 20.0 3.95612 0 diff --git a/S-300/Models/FlapLid_mast.xml b/S-300/Models/FlapLid_mast.xml new file mode 100644 index 0000000..9425cf5 --- /dev/null +++ b/S-300/Models/FlapLid_mast.xml @@ -0,0 +1,29 @@ + + + + + FlapLid_mast.ac + + + rotate + FlapLid.001 + sim/multiplay/generic/float[8] + -1.0 + 180 + + 3.95612 + -0.000003 + 19.88614 + 3.95612 + -0.000003 + 20.88614 + + + + + + + + + \ No newline at end of file diff --git a/S-300/Models/s-300.xml b/S-300/Models/s-300.xml index 0d5275f..8bd8363 100644 --- a/S-300/Models/s-300.xml +++ b/S-300/Models/s-300.xml @@ -119,7 +119,7 @@ - FlapLid_mast.ac + FlapLid_mast.xml flaplid 0 diff --git a/S-300/Nasal/damage.nas b/S-300/Nasal/damage.nas index 0d7cbc3..44b753a 100644 --- a/S-300/Nasal/damage.nas +++ b/S-300/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 3;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 80;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -66,7 +68,7 @@ var shells = { var warheads = { # [id,lbs,anti surface,cluster,(name)] - "AGM-65": [ 0, 126.00,1,0], + "AGM-65B": [ 0, 126.00,1,0], "AGM-84": [ 1, 488.00,1,0], "AGM-88": [ 2, 146.00,1,0], "MK-82SE": [ 3, 192.00,1,0],# snake eye @@ -75,12 +77,12 @@ var warheads = { "AGM-158": [ 6, 1000.00,1,0], "ALARM": [ 7, 450.00,1,0], "AM 39 Exocet": [ 8, 364.00,1,0], - "AS 37 Martel": [ 9, 330.00,1,0], + "AS 37 Martel": [ 9, 330.00,1,0],# Also : AJ 168 Martel "AS30L": [10, 529.00,1,0], "BL755": [11, 100.00,1,1],# 800lb bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-87": [12, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-105": [13, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. - "AJ 168 Martel": [14, 330.00,1,0], + "AS 37 Armat": [14, 330.00,1,0], "FAB-100": [15, 92.59,1,0], "FAB-250": [16, 202.85,1,0], "FAB-500": [17, 564.38,1,0], @@ -88,7 +90,7 @@ var warheads = { "GBU-24": [19, 945.00,1,0], "GBU-31": [20, 945.00,1,0], "GBU-54": [21, 190.00,1,0], - "GBU-10": [22, 2000.00,1,0], + "GBU-10": [22, 945.00,1,0], "GBU-16": [23, 450.00,1,0], "HVAR": [24, 7.50,1,0],#P51 "KAB-500": [25, 564.38,1,0], @@ -124,14 +126,14 @@ var warheads = { "AIM-7F": [55, 88.00,0,0], "AGM-62": [56, 2000.00,1,0], "AIM-9L": [57, 20.80,0,0], - "d-7": [58, 44.00,0,0],#deprecated + "AGM-65D": [58, 126.00,1,0], "AIM-132": [59, 22.05,0,0], - "d-8": [60, 20.80,0,0],#deprecated + "Apache AP": [60, 110.23,0,1],# Real mass of bomblet. (x 10). Anti runway. "KN-06": [61, 315.00,0,0], "9M317": [62, 145.00,0,0], - "d9": [63, 27.00,0,0],#deprecated + "GEM": [63, 185.00,0,0],#MIM-104D "R.550 Magic": [64, 26.45,0,0],# also called majic - "d-0": [65, 30.00,0,0],#deprecated + "5Ya23": [65, 414.00,0,0],#Volga-M "R.550 Magic 2": [66, 27.00,0,0], "R.530": [67, 55.00,0,0], "MK-82AIR": [68, 192.00,1,0], @@ -162,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -224,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -256,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -267,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -289,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -297,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -310,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -319,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -345,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -378,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -423,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -436,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -455,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -466,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -606,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -614,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -624,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -643,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -667,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -710,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -736,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -810,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -818,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -838,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -846,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -1005,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1039,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1053,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1078,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1137,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1150,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1176,16 +1300,17 @@ var code_ct = func () { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1203,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1282,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/S-300/Nasal/fdm.nas b/S-300/Nasal/fdm.nas index 9145b3e..4d5658a 100644 --- a/S-300/Nasal/fdm.nas +++ b/S-300/Nasal/fdm.nas @@ -18,7 +18,7 @@ #with("updateloop"); # Number of iterations per second -var frequency = 60.0; +var frequency = 10.0; # Change in heading per second at full rudder deflection var heading_ps = 0.5; @@ -33,6 +33,75 @@ setprop("/carrier/roll-offset",0); setprop("/carrier/sunk",0); var PositionUpdater = func () { + var position = geo.aircraft_position(); + var elev0 = geo.elevation(position.lat(),position.lon()); + + if (elev0 == nil) elev0 = 0; + + var geodPos = aircraftToCart({x:0, y:50, z: 0}); + var co = geo.Coord.new(); + co.set_xyz(geodPos.x, geodPos.y, geodPos.z); + var elev = geo.elevation(co.lat(),co.lon()); + if (elev != nil) { + setprop("sim/multiplay/generic/float[10]", elev-elev0); + setprop("controls/armament/station[1]/offsets/z-m", elev-elev0+3.8); + setprop("controls/armament/station[2]/offsets/z-m", elev-elev0+3.8); + setprop("controls/armament/station[3]/offsets/z-m", elev-elev0+3.8); + setprop("controls/armament/station[4]/offsets/z-m", elev-elev0+3.8); + } else { + setprop("sim/multiplay/generic/float[10]", 0); + setprop("controls/armament/station[1]/offsets/z-m", 3.8); + setprop("controls/armament/station[2]/offsets/z-m", 3.8); + setprop("controls/armament/station[3]/offsets/z-m", 3.8); + setprop("controls/armament/station[4]/offsets/z-m", 3.8); + } + + geodPos = aircraftToCart({x:40, y:-40, z: 0}); + co = geo.Coord.new(); + co.set_xyz(geodPos.x, geodPos.y, geodPos.z); + elev = geo.elevation(co.lat(),co.lon()); + if (elev != nil) { + setprop("sim/multiplay/generic/float[11]", elev-elev0); + } else { + setprop("sim/multiplay/generic/float[11]", 0); + } + + # not used: + geodPos = aircraftToCart({x:0, y:-40, z: 0}); + co = geo.Coord.new(); + co.set_xyz(geodPos.x, geodPos.y, geodPos.z); + elev = geo.elevation(co.lat(),co.lon()); + if (elev != nil) { + setprop("sim/multiplay/generic/float[12]", elev-elev0); + } else { + setprop("sim/multiplay/generic/float[12]", 0); + } + + geodPos = aircraftToCart({x:-40, y:-40, z: 0}); + co = geo.Coord.new(); + co.set_xyz(geodPos.x, geodPos.y, geodPos.z); + elev = geo.elevation(co.lat(),co.lon()); + if (elev != nil) { + setprop("sim/multiplay/generic/float[13]", elev-elev0); + } else { + setprop("sim/multiplay/generic/float[13]", 0); + } + + # not used: + geodPos = aircraftToCart({x:40, y:40, z: 0}); + co = geo.Coord.new(); + co.set_xyz(geodPos.x, geodPos.y, geodPos.z); + elev = geo.elevation(co.lat(),co.lon()); + if (elev != nil) { + setprop("sim/multiplay/generic/float[14]", elev-elev0); + } else { + setprop("sim/multiplay/generic/float[14]", 0); + } + + + settimer( PositionUpdater, 1/frequency ); + return; + var position = geo.aircraft_position(); @@ -75,4 +144,4 @@ var PositionUpdater = func () { }; -#PositionUpdater(); \ No newline at end of file +PositionUpdater(); \ No newline at end of file diff --git a/S-300/Nasal/vector.nas b/S-300/Nasal/vector.nas index 36ae539..621b0cb 100644 --- a/S-300/Nasal/vector.nas +++ b/S-300/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.93 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -13,6 +13,8 @@ var Math = { # When doing euler angles (from pilots point of view): yaw = yaw left, pitch = rotate up, roll = roll right. # FG rotations: heading = yaw right, pitch = rotate up, roll = roll right. # + # Cartesian is right-handed coord system. + # clamp: func(v, min, max) { v < min ? min : v > max ? max : v }, convertCoords: func (x,y,z) { @@ -37,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -55,9 +59,27 @@ var Math = { return R2D * math.acos(me.value); }, + # Rodrigues' rotation formula. Use unitVectorAxis as axis, and rotate around it. + rotateVectorAroundVector: func (a, unitVectorAxis, angle) { + return me.plus( + me.plus( + me.product(math.cos(angle*D2R),a), + me.product(math.sin(angle*D2R), me.crossProduct(unitVectorAxis,a)) + ), + me.product((1-math.cos(angle*D2R))*me.dotProduct(unitVectorAxis,a), unitVectorAxis) + ); + }, + + #Rotate a certain amound of degrees towards 'towardsMe'. + rotateVectorTowardsVector: func (a, towardsMe, angle) { + return me.rotateVectorAroundVector(a, me.normalize(me.crossProduct(a, towardsMe)), angle); + }, + # length of vector magnitudeVector: func (a) { - return math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2)); + me.mag = 1; + call(func {me.mag = math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2))},nil,nil,var err =[]);#strange bug in Nasal can sometimes mess up sqrt + return me.mag; }, # dot product of 2 vectors @@ -65,8 +87,20 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { + me.rollM = me.rollMatrix(roll); + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) + yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); @@ -74,15 +108,34 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) - yawPitchRollVector: func (yaw, pitch, roll, vector) { - me.rollM = me.rollMatrix(roll); + # rotate a vector. Order: yaw, pitch + yawPitchVector: func (yaw, pitch, vector) { me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.pitchM, me.yawM); return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: pitch, yaw + pitchYawVector: func (pitch, yaw, vector) { + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.pitchM); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + + # rotate a vector. Order: pitch + pitchVector: func (pitch, vector) { + me.pitchM = me.pitchMatrix(pitch); + return me.multiplyMatrixWithVector(me.pitchM, vector); + }, + # multiply 3x3 matrix with vector multiplyMatrixWithVector: func (matrix, vector) { return [matrix[0]*vector[0]+matrix[1]*vector[1]+matrix[2]*vector[2], @@ -107,10 +160,10 @@ var Math = { # matrix for pitching pitchMatrix: func (pitch) { - pitch = pitch * D2R; - return [math.cos(pitch),0,-math.sin(pitch), + pitch = -pitch * D2R; + return [math.cos(pitch),0,math.sin(pitch), 0,1,0, - math.sin(pitch),0,math.cos(pitch)]; + -math.sin(pitch),0,math.cos(pitch)]; }, # matrix for yawing @@ -126,7 +179,8 @@ var Math = { me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); if (me.horz != 0) { me.pitch = math.atan2(vector[2],me.horz)*R2D; - me.hdg = math.asin(-vector[1]/me.horz)*R2D; + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; if (vector[0] < 0) { # south @@ -144,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -242,8 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, + + # Project a onto ontoMe. + projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety + return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); + }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -268,7 +369,7 @@ var Math = { # print vector to console format: func (v) { - return sprintf("(%.1f, %.1f, %.1f)",v[0],v[1],v[2]); + return sprintf("(%.2f, %.2f, %.2f)",v[0],v[1],v[2]); }, # make vector length 1.0 @@ -288,6 +389,20 @@ var Math = { return me.magnitudeVector(me.crossProduct(me.minus(L2,L1), me.minus(L1,P)))/me.magnitudeVector(me.minus(L2,L1)); }, + + interpolateVector: func (start, end, fraction) { + me.xx = (start[0]*(1-fraction) +end[0]*fraction); + me.yy = (start[1]*(1-fraction) +end[1]*fraction); + me.zz = (start[2]*(1-fraction) +end[2]*fraction); + + return [me.xx, me.yy, me.zz]; + }, + + # move distance 'along' from start towards end. Total dist from start to end is dist. + alongVector: func (start, end, dist, along) { + me.fraction = along/dist; + return me.interpolateVector(start, end, me.fraction); + }, # Orthogonal projection of a vector `vec` onto another `ref` !!can throw an exception if the referential vector is null!!. orthogonalProjection: func(vec, ref){ @@ -319,9 +434,9 @@ var Math = { #| 0 cos(roll) -sin(roll) | #| 0 sin(roll) cos(roll) | # -#| cos(pitch) 0 -sin(pitch) | -#| 0 1 0 | -#| sin(pitch) 0 cos(pitch) | +#| cos(-pitch) 0 sin(-pitch) | +#| 0 1 0 | +#| -sin(-pitch) 0 cos(-pitch) | # #| cos(yaw) -sin(yaw) 0 | #| sin(yaw) cos(yaw) 0 | @@ -336,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/S-300/S-300-set.xml b/S-300/S-300-set.xml index 1e1c290..3ae83a9 100644 --- a/S-300/S-300-set.xml +++ b/S-300/S-300-set.xml @@ -1,64 +1,9 @@ - - - - false - - - - - - - 15.0 - - - 0.5 - 4.0 - - - 0.32 - 0.45 - - - 30.0 - - - 20.0 - - true - - - - - pinto + pinto, Nikolai V. Chr., onox S-300 S-300 (SA-10) is a Russian self-propelled surface to air missile system. alpha @@ -94,11 +39,6 @@ true gui/1.png - - exterior - true - gui/2.png - @@ -129,7 +69,7 @@ - + Follow lookat false @@ -173,24 +113,13 @@ true - - - Systems/fbw.xml - - - Systems/fdm.xml - - - - Systems/views.xml - - 500 0 0 + @@ -199,51 +128,7 @@ true - - - - nasal - - - - - - enemies - - dialog-show - enemies - - - - - enemies - - dialog-show - priority - - - - - - nasal - - - - - - - nasal - - - + @@ -274,39 +159,33 @@ - 0 - false -5.63782 - -1.1407 + 48.8593 3.8 - false -6.53782 - -0.540702 + 49.4593 3.8 - false -5.63782 - 1.1407 + 51.1407 3.8 - false -6.53782 - 0.540702 + 50.540702 3.8 - false @@ -321,9 +200,32 @@ 50 - + + 3 + true + 80 + false + true + false + false + false + false + false + 2.25 + false + Aircraft/S-300/Models/Armament/Weapons/ + true + true + rotors/main/blade[0]/flap-deg + rotors/main/blade[1]/flap-deg + rotors/main/blade[2]/flap-deg + rotors/main/blade[2]/position-deg + true + 1 + controls/armament + station true true true @@ -368,19 +270,19 @@ 100 - - Leto - swamp - pinto - fb237 - Wilco-1 - Kokos - J-Mav16 - S - Raider1 - Slobb - 5H1N0B1 - SNOWY1 + + target1 + target2 + target3 + target4 + target5 + target6 + target7 + target8 + target9 + targetA + targetB + targetC 0 diff --git a/S-75/Nasal/damage.nas b/S-75/Nasal/damage.nas index 76a02b6..44b753a 100644 --- a/S-75/Nasal/damage.nas +++ b/S-75/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 2.25;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 80;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -162,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -224,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -256,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -267,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -289,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -297,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -310,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -319,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -345,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -378,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -423,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -436,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -455,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -466,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -606,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -614,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -624,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -643,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -667,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -710,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -736,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -810,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -818,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -838,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -846,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -1005,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1039,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1053,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1078,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1137,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1150,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1176,16 +1300,17 @@ var code_ct = func () { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1203,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1282,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/S-75/Nasal/guided-missiles.nas b/S-75/Nasal/guided-missiles.nas index 2bd8a61..4e2a1ec 100644 --- a/S-75/Nasal/guided-missiles.nas +++ b/S-75/Nasal/guided-missiles.nas @@ -1,12 +1,12 @@ ######################################################################################### -####### +####### ####### Guided/Cruise missiles, rockets and dumb/glide/guided bombs code for Flightgear. ####### ####### License: GPL 2.0 ####### ####### Authors: ####### Alexis Bory, Fabien Barbier, Richard Harrison, Justin Nicholson, Nikolai V. Chr., Axel Paccalin, Colin Geniet -####### +####### ####### The file vector.nas needs to be available in namespace 'vector'. ####### ####### In addition, some code is derived from work by: @@ -41,7 +41,7 @@ # Laser and semi-radar guided munitions need the target to be painted to keep lock. Notice gps guided munition that are all aspect will never lose lock, # whether they can 'see' the target or not. Anti-radiation missiles will need the target to send radiation towards the missile. # Set DEBUG_STATS and/or DEBUG_FLIGHT to true to check how the missile works during flight, when you are designing a weapon. -# +# # # Usage: # @@ -60,10 +60,10 @@ # the weapon did not have time to arm before hitting ground. # To drop the munition, without arming it nor igniting its engine, call eject(). # Remote guidance requires the use of midFlightFunction (see below) to transmit guidance parameters, in the remote_yaw and remote_pitch fields of the output. -# +# # # Limitations: -# +# # The weapons use a simplified flight model that does not have AoA or sideslip. Mass balance, rotational inertia, wind is also not implemented. They also do not roll due to aerodynmic effects. # If you fire a weapon and have HoT enabled in flightgear, they likely will not hit very precise. # The weapons are highly dependent on framerate, so low frame rate will make them hit imprecise. @@ -218,7 +218,7 @@ var contactPoint = nil; var AIM = { lowestETA: nil, #done - new : func (p, type = "AIM-9", sign = "Sidewinder", midFlightFunction = nil, nasalPosition = nil) { + new : func (p, type = "AIM-9L", sign = "Sidewinder", midFlightFunction = nil, nasalPosition = nil) { if(AIM.active[p] != nil) { #do not make new missile logic if one exist for this pylon. return -1; @@ -252,19 +252,15 @@ var AIM = { if (m.SwSoundVol.getValue() == nil) { m.SwSoundVol.setDoubleValue(0); } - m.inacc = getprop("payload/armament/tgp-inacc-system");# set to false, unless using the f16 m.tacview_support = getprop("payload/armament/tacview");# set to false, unless using an aircraft that has tacview m.gnd_launch = getprop("payload/armament/gnd-launch");#true to be a SAM or ship if (m.gnd_launch == nil) { m.gnd_launch = 0; } - if (m.inacc == nil) { - m.inacc = 0; - } if (m.tacview_support == nil) { m.tacview_support = 0; } - + m.ID = p; m.stationName = AcModel.getNode("armament/station-name").getValue(); if (m.nasalPosition == nil) { @@ -279,7 +275,7 @@ var AIM = { if (fox2_unique_id >100) fox2_unique_id = -100; m.unique_id = fox2_unique_id; m.nodeString = "payload/armament/"~m.type_lc~"/"; - + if (m.tacview_support) { m.tacviewID = 11000 + int(math.floor(rand()*10000)); } @@ -315,6 +311,7 @@ var AIM = { m.radarY = getprop(m.nodeString~"FCS-y"); # This is handy for SAMs with radar on a mast. m.radarZ = getprop(m.nodeString~"FCS-z"); # In future I will add direction to it also, for now its center gimbal is along -x axis. m.expand_min = getprop(m.nodeString~"expand-min-fire-range"); # Bool. Default false. If min fire range should expand with closing rate. Mainly use this for A/A missiles. + 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 @@ -384,36 +381,40 @@ var AIM = { m.dlz_enabled = getprop(m.nodeString~"DLZ"); # Supports dynamic launch zone info. For now only works with A/A. [optional] m.dlz_opt_alt = getprop(m.nodeString~"DLZ-optimal-alt-feet"); # Minimum altitude required to hit the target at max range. m.dlz_opt_mach = getprop(m.nodeString~"DLZ-optimal-closing-mach"); # Closing speed required to hit the target at max range at minimum altitude. - # detailed drag settings + # detailed drag settings m.Cd_plume = getprop(m.nodeString~"exhaust-plume-parasitic-drag-factor"); # Default 1. For AIM-120, Naval Postgraduate School paper suggest around 0.6. It will reduce drag during burn. m.simple_drag = getprop(m.nodeString~"simplified-induced-drag"); # bool. Default true. If enabled, the properties below wont be used: m.wing_aspect_ratio = getprop(m.nodeString~"wing-aspect-ratio"); # span^2/wing_area. Default to 8 m.wing_eff = getprop(m.nodeString~"wing-efficiency-relative-to-an-elliptical-planform"); # Default to 0.85 - + if (m.cold_detect_range_nm == nil) { # backwards compatibility m.cold_detect_range_nm = m.max_fire_range_nm; - } + } m.detect_range_curr_nm = m.cold_detect_range_nm; - + if (m.max_seeker_dev == nil) { m.max_seeker_dev = 15; } if (m.beam_width_deg == nil) { m.beam_width_deg = 4; - } + } m.beam_width_deg *= 0.5; - + if (m.eject_speed == nil) { m.eject_speed = 0; - } - + } + if (m.rail_forward == TRUE) { m.rail_pitch_deg = 0; m.rail_head_deg = 0; } + if (m.asc == nil) { + m.asc = 0; + } + if (m.wing_aspect_ratio == nil) { m.wing_aspect_ratio = 8.0; } @@ -433,7 +434,7 @@ var AIM = { if (m.radarOrigin == nil) { m.radarOrigin = 1; } - + if (m.guideWhileDrop == nil) { m.guideWhileDrop = 0; } @@ -453,7 +454,7 @@ var AIM = { if (m.seeker_filter == nil) { m.seeker_filter = 0; } - + if (m.ready_time == nil) { m.ready_time = 0; } @@ -487,19 +488,19 @@ var AIM = { if(m.canSwitch == nil) { m.canSwitch = FALSE; } - + if(m.terminal_alt_factor == nil) { m.terminal_alt_factor = 2; } - + if(m.terminal_rise_time == nil) { m.terminal_rise_time = 6; } - + if(m.terminal_dive_time == nil) { m.terminal_dive_time = 4; } - + if (m.weight_fuel_lbm == nil) { m.weight_fuel_lbm = 0; } @@ -616,7 +617,7 @@ var AIM = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("ai/models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild(m.type_lc, i, 0) == nil) { @@ -631,16 +632,16 @@ var AIM = { m.ai.getNode("valid", 1).setBoolValue(0); m.ai.getNode("name", 1).setValue(type); m.ai.getNode("sign", 1).setValue(sign); - m.ai.getNode("callsign", 1).setValue(type); + m.ai.getNode("callsign", 1).setValue(sprintf("%s_%d", type, m.unique_id)); m.ai.getNode("missile", 1).setBoolValue(1); - - + + var id_model = m.weapon_model ~ m.ID ~ ".xml"; m.model.getNode("path", 1).setValue(id_model); m.model.getNode("enable-hot", 1).setBoolValue(0);# This is if people forget to set it in xml. m.model.getNode("name", 1).setValue(m.typeLong);# this helps in debugging. - - + + # Create the AI position and orientation properties. m.latN = m.ai.getNode("position/latitude-deg", 1); @@ -649,7 +650,7 @@ var AIM = { m.hdgN = m.ai.getNode("orientation/true-heading-deg", 1); m.pitchN = m.ai.getNode("orientation/pitch-deg", 1); m.rollN = m.ai.getNode("orientation/roll-deg", 1); - + m.mpLat = getprop("payload/armament/MP-lat");# properties to be used for showing missile over MP. m.mpLon = getprop("payload/armament/MP-lon"); m.mpAlt = getprop("payload/armament/MP-alt"); @@ -665,7 +666,7 @@ var AIM = { } } - + m.target_air = find("A", m.class)==-1?FALSE:TRUE; m.target_sea = find("M", m.class)==-1?FALSE:TRUE;#use M for marine, since S can be confused with surface. @@ -676,8 +677,8 @@ var AIM = { m.coord = geo.Coord.new().set_latlon(0, 0, 0); m.t_coord = nil; - - + + # # Seekerhead @@ -699,7 +700,7 @@ var AIM = { m.seam_scan = 0; m.cooling_last_time = 0; m.cool_total_time = 0; - + # # Emesary damage system # @@ -789,7 +790,7 @@ var AIM = { m.nextGroundElevation = 0; # next Ground Elevation m.nextGroundElevationMem = [-10000, -1]; m.terrainStage = 0; - + # # Rail # @@ -822,7 +823,7 @@ var AIM = { m.chaffTime = 0; m.chaffLock = FALSE; m.flarespeed_fps = nil; - + # # Telemetry # @@ -839,7 +840,7 @@ var AIM = { m.tooLowSpeedTime = -1; m.lostLOS = FALSE; - + # # LOAL # @@ -859,9 +860,9 @@ var AIM = { m.pendingLaunchSound = -1; m.explodeSound = TRUE; - - - + + + m.standby();# these loops will run until released or deleted. #for multithreading @@ -870,7 +871,7 @@ var AIM = { return AIM.active[m.ID] = m; }, - + del: func { # can be called at any time, before or during flight. # @@ -946,9 +947,9 @@ var AIM = { me.ccrp_speed_east_fps = -me.init_rel_vec[1]; me.ccrp_speed_north_fps = me.init_rel_vec[0]; } - + me.ccrp_t = 0.0; - + me.ccrp_altC = me.ccrp_agl; me.ccrp_vel_z = -me.ccrp_speed_down_fps*FT2M;#positive upwards me.ccrp_fps_z = -me.ccrp_speed_down_fps; @@ -977,11 +978,11 @@ var AIM = { me.ccrp_q = 0.5 * me.ccrp_rho * me.ccrp_fps_x * me.ccrp_fps_x; me.ccrp_deacc = (me.ccrp_Cd * me.ccrp_q * me.ref_area_sqft) / me.ccrp_mass; me.ccrp_acc = -me.ccrp_deacc * FT2M; - + me.ccrp_fps_x_final = me.ccrp_t*me.ccrp_acc+me.ccrp_fps_x;# calc final horz speed me.ccrp_fps_x_average = (me.ccrp_fps_x-(me.ccrp_fps_x-me.ccrp_fps_x_final)*0.5); me.ccrp_mach_average = me.ccrp_fps_x_average / me.ccrp_rs[1]; - + me.ccrp_Cd = me.drag(me.ccrp_mach_average); me.ccrp_q = 0.5 * me.ccrp_rho * me.ccrp_fps_x_average * me.ccrp_fps_x_average; me.ccrp_deacc = (me.ccrp_Cd * me.ccrp_q * me.ref_area_sqft) / me.ccrp_mass; @@ -994,15 +995,15 @@ var AIM = { # we calc heading from composite speeds, due to alpha and beta might influence direction bombs will fall: me.ccrp_heading = geo.normdeg(math.atan2(me.ccrp_speed_east_fps,me.ccrp_speed_north_fps)*R2D); me.ccrpPos.apply_course_distance(me.ccrp_heading, me.ccrp_dist); - + #printf("Will fall %0.1f NM ahead of aircraft.", me.dist*M2NM); me.ccrp_elev = me.ccrp_alti-me.ccrp_agl;#faster me.ccrpPos.set_alt(me.ccrp_elev); - + me.ccrp_distCCRP = me.ccrpPos.distance_to(me.Tgt.get_Coord()); return me.ccrp_distCCRP; }, - + getCCIPadv: func (maxFallTime_sec, timeStep) { # for non flat areas. Lower falltime or higher timestep means using less CPU time. # returns nil for higher than maxFallTime_sec. Else a vector with [Coord, hasTimeToArm]. @@ -1021,7 +1022,7 @@ var AIM = { me.ccip_speed_east_fps = -me.init_rel_vec[1]; me.ccip_speed_north_fps = me.init_rel_vec[0]; } - + me.ccip_t = 0.0; me.ccip_dt = timeStep; me.ccip_fps_z = -me.ccip_speed_down_fps; @@ -1033,7 +1034,7 @@ var AIM = { me.ccip_mass = me.ccip_bomb.weight_launch_lbm * LBM2SLUGS; me.ccipPos = geo.Coord.new(geo.aircraft_position()); - + # we calc heading from composite speeds, due to alpha and beta might influence direction bombs will fall: if(me.ccip_fps_x == 0) return nil; me.ccip_heading = geo.normdeg(math.atan2(me.ccip_speed_east_fps,me.ccip_speed_north_fps)*R2D); @@ -1051,20 +1052,20 @@ var AIM = { me.ccip_Cd = me.ccip_bomb.drag(me.ccip_mach); me.ccip_deacc = (me.ccip_Cd * me.ccip_q * me.ccip_bomb.ref_area_sqft) / me.ccip_mass; me.ccip_fps -= me.ccip_deacc*me.ccip_dt; - + # new components and pitch me.ccip_fps_z = me.ccip_fps*math.sin(me.ccip_pitch); me.ccip_fps_x = me.ccip_fps*math.cos(me.ccip_pitch); me.ccip_fps_z -= g_fps * me.ccip_dt; me.ccip_pitch = math.atan2(me.ccip_fps_z, me.ccip_fps_x); - + # new position me.ccip_altC = me.ccip_altC + me.ccip_fps_z*me.ccip_dt*FT2M; me.ccip_dist = me.ccip_fps_x*me.ccip_dt*FT2M; me.ccip_oldPos = geo.Coord.new(me.ccipPos); me.ccipPos.apply_course_distance(me.ccip_heading, me.ccip_dist); me.ccipPos.set_alt(me.ccip_altC); - + # test terrain me.ccip_grnd = geo.elevation(me.ccipPos.lat(),me.ccipPos.lon()); if (me.ccip_grnd != nil) { @@ -1080,11 +1081,11 @@ var AIM = { } } else { return nil; - } + } } return nil; }, - + getTerrain: func (from, to) { me.xyz = {"x":from.x(), "y":from.y(), "z":from.z()}; me.dir = {"x":to.x()-from.x(), "y":to.y()-from.y(), "z":to.z()-from.z()}; @@ -1096,7 +1097,7 @@ var AIM = { } return nil; }, - + getCCIPsimple: func (maxFallTime_sec, timeStep) { #faster, but only works over level ground me.ccip_agl = getprop("position/altitude-agl-ft")*FT2M; @@ -1118,7 +1119,7 @@ var AIM = { me.ccip_speed_east_fps = -me.init_rel_vec[1]; me.ccip_speed_north_fps = me.init_rel_vec[0]; } - + me.ccip_t = 0.0; me.ccip_dt = timeStep; me.ccip_altC = me.ccip_agl; @@ -1151,11 +1152,11 @@ var AIM = { me.ccip_q = 0.5 * me.ccip_rho * me.ccip_fps_x * me.ccip_fps_x; me.ccip_deacc = (me.ccip_Cd * me.ccip_q * me.ccip_bomb.ref_area_sqft) / me.ccip_mass; me.ccip_acc = -me.ccip_deacc * FT2M; - + me.ccip_fps_x_final = me.ccip_t*me.ccip_acc+me.ccip_fps_x;# calc final horz speed me.ccip_fps_x_average = (me.ccip_fps_x-(me.ccip_fps_x-me.ccip_fps_x_final)*0.5); me.ccip_mach_average = me.ccip_fps_x_average / me.ccip_rs[1]; - + me.ccip_Cd = me.ccip_bomb.drag(me.ccip_mach_average); me.ccip_q = 0.5 * me.ccip_rho * me.ccip_fps_x_average * me.ccip_fps_x_average; me.ccip_deacc = (me.ccip_Cd * me.ccip_q * me.ccip_bomb.ref_area_sqft) / me.ccip_mass; @@ -1168,7 +1169,7 @@ var AIM = { # we calc heading from composite speeds, due to alpha and beta might influence direction bombs will fall: me.ccip_heading = geo.normdeg(math.atan2(me.ccip_speed_east_fps,me.ccip_speed_north_fps)*R2D); me.ccipPos.apply_course_distance(me.ccip_heading, me.ccip_dist); - + me.ccip_elev = me.ccip_alti-me.ccip_agl;#faster me.ccipPos.set_alt(me.ccip_elev); return [me.ccipPos,me.arming_time=90?me.angleFromRight:-me.angleFromRight;#0 is up, 180 normalized - me.angleInHUD = geo.normdeg180(me.angleInHUD-OurRoll.getValue()); - me.degOnHUD = math.atan2(vector.Math.magnitudeVector(me.idHere),me.Tgt.get_range()*NM2M)*R2D;#deg/sec - #printf("ASC: %.1f deg loft. Inv trigo angle: %d deg. Lead %.1f deg. atan2(%d,%d)", me.idealLoft, me.angleInHUD, me.degOnHUD,vector.Math.magnitudeVector(me.idHere),me.Tgt.get_range()*NM2M); - return [me.angleInHUD, me.degOnHUD];# 0,0 is center of ASEC. Thats the origin of ASC (Attack Steering Cue) + + me.asc_av_spd_mps = 900;# should be per missile + + me.horiz_intercept = me.get_intercept(me.Tgt.get_bearing(), me.Tgt.get_range()*NM2M, me.Tgt.get_heading(), me.Tgt.get_Speed()*KT2MPS, me.asc_av_spd_mps, geo.aircraft_position(), OurHdg.getValue()); + # [me.timeToIntercept, me.interceptHeading, me.interceptCoord, me.interceptDist, me.interceptRelativeBearing + if(me.horiz_intercept == nil) { + #print("No aim120 intercept"); + return nil; + } + + me.loft_cue = 0; + if (me["dlz_opt"] != nil and me["dlz_nez"] != nil) { + me.tgtRange = me.Tgt.get_range(); + me.loft_cue = me.map(me.Tgt.get_range(), me.dlz_nez, (me.dlz_opt+me.max_fire_range_nm)*0.5, 0, 15); + me.loft_cue *= me.map(ourAlt.getValue(), 0, 40000, 3, 1); + } + + # Do not find a relative bearing that is so great that radar loses track of target: + me.maxBearing = me.fcs_fov - 10;# margin is 10 degrees + me.relativeBearing = geo.normdeg180(me.horiz_intercept[1]-me.Tgt.get_bearing()); + if (me.relativeBearing > me.maxBearing) { + me.horiz_intercept[1] = me.Tgt.get_bearing() + me.maxBearing; + } elsif (me.relativeBearing < -me.maxBearing) { + me.horiz_intercept[1] = me.Tgt.get_bearing() - me.maxBearing; + } + + return [me.horiz_intercept[1], math.clamp(me.loft_cue, 0, 45)]; # attack-bearing, loft-cue + }, + + get_intercept: func (bearingToRunner, dist_m, runnerHeading, runnerSpeed, chaserSpeed, chaserCoord, chaserHeading) { + # from Leto + # needs: bearingToRunner_deg, dist_m, runnerHeading_deg, runnerSpeed_mps, chaserSpeed_mps, chaserCoord + # dist_m > 0 and chaserSpeed > 0 + + if (dist_m < 500) { + return nil; + } + + me.trigAngle = 90-bearingToRunner; + me.RunnerPosition = [dist_m*math.cos(me.trigAngle*D2R), dist_m*math.sin(me.trigAngle*D2R),0]; + me.ChaserPosition = [0,0,0]; + + me.VectorFromRunner = vector.Math.minus(me.ChaserPosition, me.RunnerPosition); + me.runner_heading = 90-runnerHeading; + me.RunnerVelocity = [runnerSpeed*math.cos(me.runner_heading*D2R), runnerSpeed*math.sin(me.runner_heading*D2R),0]; + + me.a = chaserSpeed * chaserSpeed - runnerSpeed * runnerSpeed; + me.b = 2 * vector.Math.dotProduct(me.VectorFromRunner, me.RunnerVelocity); + me.c = -dist_m * dist_m; + + if ((me.b*me.b-4*me.a*me.c)<0) { + # intercept not possible + return nil; + } + + me.t1 = (-me.b+math.sqrt(me.b*me.b-4*me.a*me.c))/(2*me.a); + me.t2 = (-me.b-math.sqrt(me.b*me.b-4*me.a*me.c))/(2*me.a); + + if (me.t1 < 0 and me.t2 < 0) { + # intercept not possible + return nil; + } + + me.timeToIntercept = 0; + if (me.t1 > 0 and me.t2 > 0) { + me.timeToIntercept = math.min(me.t1, me.t2); + } else { + me.timeToIntercept = math.max(me.t1, me.t2); + } + me.InterceptPosition = vector.Math.plus(me.RunnerPosition, vector.Math.product(me.timeToIntercept, me.RunnerVelocity)); + + me.ChaserVelocity = vector.Math.product(1/me.timeToIntercept, vector.Math.minus(me.InterceptPosition, me.ChaserPosition)); + + me.interceptAngle = vector.Math.angleBetweenVectors([0,1,0], me.ChaserVelocity); + me.interceptHeading = geo.normdeg(me.ChaserVelocity[0]<0?-me.interceptAngle:me.interceptAngle); + + me.interceptDist = chaserSpeed*me.timeToIntercept; + + me.interceptCoord = geo.Coord.new(chaserCoord); + me.interceptCoord = me.interceptCoord.apply_course_distance(me.interceptHeading, me.interceptDist); + me.interceptRelativeBearing = geo.normdeg180(me.interceptHeading-chaserHeading); + + return [me.timeToIntercept, me.interceptHeading, me.interceptCoord, me.interceptDist, me.interceptRelativeBearing]; }, setContacts: func (vect) { @@ -1250,17 +1318,21 @@ var AIM = { commandDir: func (heading_deg, pitch_deg) { # commands are relative to aircraft bore if (me.status == MISSILE_FLYING) return; - me.command_dir_heading = heading_deg; - me.command_dir_pitch = pitch_deg; - me.slave_to_radar = FALSE; + if (vector.Math.angleBetweenVectors(vector.Math.eulerToCartesian2(-heading_deg, pitch_deg), [1,0,0]) <= me.fcs_fov) { + me.command_dir_heading = heading_deg; + me.command_dir_pitch = pitch_deg; + me.slave_to_radar = FALSE; + } me.printCode("Bore/dir command: heading %0.1f pitch %0.1f", heading_deg, pitch_deg); }, commandRadar: func (idle_heading = 0, idle_elevation = 0) { # command that radar is looking at a target, slave to that. if (!me.seam_support or me.status == MISSILE_FLYING) return; - me.command_dir_heading = idle_heading; - me.command_dir_pitch = idle_elevation; + if (me.status != MISSILE_LOCK) { + me.command_dir_heading = idle_heading; + me.command_dir_pitch = idle_elevation; + } me.slave_to_radar = TRUE; me.printCode("Slave radar command. Idle: heading %0.1f pitch %0.1f", idle_heading, idle_elevation); }, @@ -1372,7 +1444,7 @@ var AIM = { me.status = MISSILE_FLYING; if (vect!= nil) { - + # sets a vector of contacts the weapons will try to lock onto # For LOAL weapons. # see also setContacts() @@ -1381,7 +1453,7 @@ var AIM = { me.contacts = []; } me.launchSoundProp.setBoolValue(FALSE); - + if (me.engineEnabled and me.stage_1_duration > 0 and me.force_lbf_1 > 0 and me.drop_time < 1.75) { me.pendingLaunchSound = me.drop_time; } elsif (me.drop_time != 10000 and (!me.engineEnabled or me.stage_1_duration == 0 or me.force_lbf_1 == 0)) { @@ -1411,7 +1483,7 @@ var AIM = { # me.rail_head_deg = me.Tgt.get_bearing()-OurHdg.getValue(); #} me.railvec = vector.Math.eulerToCartesian3X(-me.rail_head_deg, me.rail_pitch_deg,0); - me.veccy = vector.Math.yawPitchRollVector(-ac_hdg,ac_pitch,ac_roll,me.railvec); + me.veccy = vector.Math.rollPitchYawVector(ac_roll,ac_pitch,-ac_hdg,me.railvec); me.carty = vector.Math.cartesianToEuler(me.veccy); me.msl_pitch = me.carty[1]; me.defaultHeading = me.Tgt != nil?me.Tgt.get_bearing():0;#90 deg tubes align to target heading, else north @@ -1442,26 +1514,19 @@ var AIM = { init_coord.set_xyz(pos.x, pos.y, pos.z); } else { init_coord = me.getGPS(me.x, me.y, me.z, ac_pitch, ac_hdg); - } + } # Set submodel initial position: var mlat = init_coord.lat(); var mlon = init_coord.lon(); var malt = init_coord.alt() * M2FT; - + me.coord = geo.Coord.new(init_coord); # Get target position. if (me.Tgt != nil) { - if (me.inacc) { - if (!me.target_pnt) { - me.Tgt.inac_x = 0; - me.Tgt.inac_y = 0; - me.Tgt.inac_z = 0; - } - if (me.Tgt == contactPoint) { - me.usingTGPPoint = 1; - } + if (me.Tgt == contactPoint) { + me.usingTGPPoint = 1; } me.t_coord = me.Tgt.get_Coord(); me.maddog = FALSE; @@ -1658,7 +1723,7 @@ var AIM = { if (me.guidanceLaw == "direct") { nav = "Pure pursuit." } elsif (me.guidanceLaw == "OPN") { - nav = "Original Proportional navigation. Proportionality constant is "~me.pro_constant; + nav = "Original Proportional navigation. Proportionality constant is "~me.pro_constant; } elsif (me.guidanceLaw == "PN") { nav = "Proportional navigation. Proportionality constant is "~me.pro_constant; } elsif (me.guidanceLaw == "APN") { @@ -1689,7 +1754,7 @@ var AIM = { vector = "Vectored thrust." } - + me.printStats("****************************************************"); me.printStats("Stats for %s", me.typeLong); me.printStats("Damage ID is %d, notification ID is %d, unique ID is %d", me.typeID, me.typeID+21,me.unique_id); @@ -1697,7 +1762,7 @@ var AIM = { me.printStats("Fire range %.1f-%.1f NM", me.min_fire_range_nm, me.max_fire_range_nm); if (me.expand_min) { me.printStats("Min fire range will expand with closing rate."); - } + } me.printStats("Can be fired againts %s targets", classes); me.printStats("Pilot will call out %s when firing.",me.brevity); me.printStats("Launch platform detection field of view is +-%d degrees.",me.fcs_fov); @@ -1770,7 +1835,7 @@ var AIM = { } else { me.printStats("Weapon will not snap up, follow terrain or sea skim."); } - + me.printStats("After propulsion ends, it will max steer up to %d degree pitch.",me.maxPitch); if(me.Tgt == nil) { me.printStats("Note: Ordnance was released with no lock or destination target."); @@ -1892,7 +1957,7 @@ var AIM = { } elsif (me.guidance == "heat") { me.printStats("Resistance to flares is %d%%.",me.flareResistance*100); } - } + } me.printStats("MISC:"); if (me.data) { me.printStats("Will transmit telemetry data back to launch platform."); @@ -1907,7 +1972,7 @@ var AIM = { } me.printStats("****************************************************"); }, - + setNewTargetInFlight: func (tagt) { me.Tgt = tagt; me.callsign = tagt==nil?"Unknown":damage.processCallsign(me.Tgt.get_Callsign()); @@ -1920,13 +1985,6 @@ var AIM = { me.semiLostLock = FALSE; me.heatLostLock = FALSE; me.hasGuided = FALSE; - if (me.inacc) { - if (!me.target_pnt and tagt!=nil) { - me.Tgt.inac_x = 0; - me.Tgt.inac_y = 0; - me.Tgt.inac_z = 0; - } - } }, flight: func { @@ -1957,7 +2015,7 @@ var AIM = { } me.elapsed = systime(); - + me.dt = (me.elapsed - me.elapsed_last)*speedUp.getValue(); me.elapsed_last = me.elapsed; @@ -1970,15 +2028,15 @@ var AIM = { me.life_time += me.dt; me.handleMidFlightFunc(); - + if (me.hasGuided and me.maddog) { me.maddog = FALSE; me.printStats("Maddog stage over, guided at "~me.callsign); } - + if (me.guidanceEnabled and me.free == FALSE and !me.newTargetAssigned and (me.canSwitch or (me.loal and me.maddog)) and size(me.contacts) > 0 and (me.dist_curr_direct==-1 or me.dist_curr_direct>me.reportDist)) { # me.reaquire must also be enabled for me.canSwitch to work - + if (me.Tgt==nil or me.hasGuided == FALSE or (me.canSwitch and (me.fovLost or me.lostLOS or me.radLostLock or me.semiLostLock or me.heatLostLock)) and me.life_time > me.nextFovCheck) { # test next contact me.numberContacts = size(me.contacts); @@ -1994,7 +2052,7 @@ var AIM = { me.newTargetAssigned = FALSE; } else { me.setNewTargetInFlight(me.newTgt); - } + } } } @@ -2016,7 +2074,7 @@ var AIM = { me.callsign = "Unknown"; me.newTargetAssigned = FALSE; } - } elsif (me.inacc and me.Tgt != nil and me.Tgt.get_type() == POINT and me.guidance == "laser" and me.usingTGPPoint and contactPoint == nil) { + } elsif (me.Tgt != nil and me.Tgt.get_type() == POINT and me.guidance == "laser" and me.usingTGPPoint and contactPoint == nil) { # if laser illuminated lock is lost on a targetpod target: if (!(me.canSwitch and me.reaquire)) { me.Tgt = nil; @@ -2027,7 +2085,7 @@ var AIM = { me.callsign = "Unknown"; me.newTargetAssigned = FALSE; } - } elsif (me.inacc and me.Tgt == nil and me.guidance == "laser" and me.usingTGPPoint and contactPoint != nil) { + } elsif (me.Tgt == nil and me.guidance == "laser" and me.usingTGPPoint and contactPoint != nil) { # see if we can regain lock on new laser spot: if (me.newLock(contactPoint)) { me.setNewTargetInFlight(contactPoint); @@ -2046,17 +2104,17 @@ var AIM = { me.deploy = me.clamp(me.extrapolate(me.life_time, me.rail_passed_time, me.rail_passed_time+me.deploy_time,0,1),0,1); } me.deploy_prop.setDoubleValue(me.deploy); - + me.thrust_lbf = me.thrust();# pounds force (lbf) - + # Get total old speed, thats what we will use in next loop. me.old_speed_horz_fps = math.sqrt((me.speed_east_fps*me.speed_east_fps)+(me.speed_north_fps*me.speed_north_fps)); me.old_speed_fps = math.sqrt((me.old_speed_horz_fps*me.old_speed_horz_fps)+(me.speed_down_fps*me.speed_down_fps)); me.setRadarProperties(me.old_speed_fps); - + # Get air density and speed of sound (fps): me.rs = me.rho_sndspeed(me.altN.getValue() + me.density_alt_diff); @@ -2086,13 +2144,13 @@ var AIM = { me.Cd = me.drag(me.speed_m,me.myG); me.speed_change_fps = me.speedChange(me.thrust_lbf, me.rho, me.Cd); - + if (me.last_dt != 0) { me.speed_change_fps = me.speed_change_fps + me.energyBleed(me.g, me.altN.getValue() + me.density_alt_diff); } - + ################### #### Guidance.##### @@ -2139,7 +2197,7 @@ var AIM = { me.prevETA = nil; } } - me.observing = me.guidance; + me.observing = me.guidance; } elsif (me.guidance != "unguided" and (me.rail == FALSE or me.rail_passed == TRUE) and me.guidanceEnabled and me.free == FALSE and me.t_coord == nil and (me.newTargetAssigned or (me.canSwitch and (me.fovLost or me.lostLOS or me.radLostLock or me.semiLostLock or me.heatLostLock) or (me.loal and me.maddog)))) { # check for too low speed not performed on purpuse, difference between flying straight on A/P and making manouvres. @@ -2212,11 +2270,11 @@ var AIM = { # missile still on rail, lets calculate its speed relative to the wind coming in from the aircraft nose. me.rail_speed_into_wind = me.rail_speed_into_wind + me.speed_change_fps; } else { - # gravity acc makes the weapon pitch down + # gravity acc makes the weapon pitch down me.pitch = math.atan2(-me.speed_down_fps, me.speed_horizontal_fps ) * R2D; } - + if (me.rail == TRUE and me.rail_passed == FALSE) { me.u = noseAir.getValue();# airstream from nose #var v = getprop("velocities/vBody-fps");# airstream from side @@ -2228,7 +2286,7 @@ var AIM = { me.hdg = OurHdg.getValue(); } else { me.railvec = me.myMath.eulerToCartesian3X(-me.rail_head_deg, me.rail_pitch_deg,0); - me.veccy = me.myMath.yawPitchRollVector(-OurHdg.getValue(),OurPitch.getValue(),OurRoll.getValue(),me.railvec); + me.veccy = me.myMath.rollPitchYawVector(OurRoll.getValue(),OurPitch.getValue(),-OurHdg.getValue(),me.railvec); me.carty = me.myMath.cartesianToEuler(me.veccy); me.defaultHeading = me.Tgt != nil?me.Tgt.get_bearing():0;#90 deg tubes align to target heading, else north me.pitch = me.carty[1]; @@ -2236,9 +2294,9 @@ var AIM = { } me.speed_on_rail = math.max(me.rail_speed_into_wind - me.opposing_wind, 0); - + me.movement_on_rail = me.speed_on_rail * me.dt; - + me.rail_pos = me.rail_pos + me.movement_on_rail; if (me.rail_forward == TRUE) { me.x = me.x - (me.movement_on_rail * FT2M);# negative cause positive is rear in body coordinates @@ -2311,7 +2369,7 @@ var AIM = { #me.t_coord.set_alt(me.flare_alt_ft * FT2M); } } - + # performance logging: # #var q = 0.5 * rho * me.old_speed_fps * me.old_speed_fps; @@ -2385,8 +2443,8 @@ var AIM = { # check stats while flying: # me.printFlight("Mach %04.2f , time %05.1f s , thrust %05.1f lbf , G-force %05.2f", me.speed_m, me.life_time, me.thrust_lbf, me.g); - me.printFlight("Alt %07.1f ft , direct distance to target %04.1f NM", me.alt_ft, (me.Tgt!=nil and me.direct_dist_m!=nil)?me.direct_dist_m*M2NM:-1); - + me.printFlight("Alt %07.1f ft , direct distance to target %04.1f NM", me.alt_ft, (me.Tgt!=nil and me.direct_dist_m!=nil)?me.direct_dist_m*M2NM:-1); + 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); @@ -2412,14 +2470,14 @@ var AIM = { if (me.Tgt == nil and me.rail == TRUE and me.rail_pitch_deg==90 and me.rail_passed == FALSE) { #for ejection seat to be oriented correct, wont be ran for missiles with target such as the frigate. - + # model of seat is loaded such that face is pointing in -Z FG coords. - + # Get the vector pointing up from aircraft var a = me.myMath.eulerToCartesian3Z(-OurHdg.getValue(), OurPitch.getValue(), OurRoll.getValue()); #printf("HPR: %0.4f %0.4f %0.4f",OurHdg.getValue(),OurPitch.getValue(),OurRoll.getValue()); #printf(" UP: %0.4f %0.4f %0.4f",a[0],a[1],a[2]); - + # get the pitch and heading of that vector var euler = me.myMath.cartesianToEuler(a); me.pitch = euler[1]; @@ -2431,19 +2489,19 @@ var AIM = { me.hdg = OurHdg.getValue(); } me.hdgN.setDoubleValue(me.hdg); - + # get vector pointing out from aircraft nose var nose = me.myMath.eulerToCartesian3X(-OurHdg.getValue(), OurPitch.getValue(), OurRoll.getValue()); - + # get vector pointing perpendicular to seat travel direction (most upward possible) var face = me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, 0); - + # convert to angle pointing out from face if pilot turns head without nodding and looks downward face = me.myMath.product(-1,face); - + # get angle between face and aircraft-nose vectors var turnFace = me.myMath.angleBetweenVectors(face,nose); - + if (me.myMath.angleBetweenVectors(nose,me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, turnFace)))>me.myMath.angleBetweenVectors(nose,me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, -turnFace)))) { # if angle between aircraft nose and the face of pilot is greater than the roll of seat then the roll should be opposite #print("looking1 "~me.myMath.cartesianToEuler(me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, turnFace)))[0]); @@ -2452,7 +2510,7 @@ var AIM = { } else { #turnFace *= -1; } - + # roll the seat so pilot faces forward me.rollN.setDoubleValue(turnFace); #print("looking2 "~me.myMath.cartesianToEuler(me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, turnFace)))[0]); @@ -2477,7 +2535,7 @@ var AIM = { # 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 @@ -2506,7 +2564,7 @@ var AIM = { 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; @@ -2514,7 +2572,7 @@ var AIM = { 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); @@ -2529,7 +2587,7 @@ var AIM = { } me.prevETA = me["eta"]; } - + if (me.life_time - me.last_noti > me.noti_time and getprop("payload/armament/msg")) { # notify in flight using Emesary. me.last_noti = me.life_time; @@ -2540,9 +2598,9 @@ var AIM = { } me.last_dt = me.dt; - + me.prevGuidance = me.guidance; - + if (me.counter > -1 and !me.ai.getNode("valid").getBoolValue()) { # TODO: Why is this placed so late? Don't remember. me.ai.getNode("valid").setBoolValue(1); @@ -2558,21 +2616,21 @@ var AIM = { # # # - ############################################################################################################# + ############################################################################################################# } }, handleMidFlightFunc: func { if(me.mfFunction != nil) { - - me.settings = me.mfFunction({ time_s: me.life_time, + + me.settings = me.mfFunction({ time_s: me.life_time, dist_m: me.dist_curr_direct, - mach: me.speed_m, - weapon_position: me.coord, - guidance: me.guidance, - seeker_detect_range: me.detect_range_curr_nm, - seeker_fov: me.max_seeker_dev, - weapon_pitch: me.pitch, + mach: me.speed_m, + weapon_position: me.coord, + guidance: me.guidance, + seeker_detect_range: me.detect_range_curr_nm, + seeker_fov: me.max_seeker_dev, + weapon_pitch: me.pitch, weapon_heading: me.hdg, callsign: me.callsign, deviation_deg: me["fov_radial"], @@ -2638,7 +2696,7 @@ var AIM = { me.ac_roll = roll; } me.ac_pitch = pitch; - + if (head == nil) { me.ac_hdg = OurHdg.getValue(); } else { @@ -2681,7 +2739,7 @@ var AIM = { me.mlat = me.ac.lat() + me.out[0]; me.mlon = me.ac.lon() + me.out[1]; me.malt = (me.ac.alt() * M2FT) + me.out[2]; - + me.c = geo.Coord.new(); me.c.set_latlon(me.mlat, me.mlon, me.malt * FT2M); @@ -2690,7 +2748,7 @@ var AIM = { drag: func (mach, N=nil) { # Nikolai V. Chr.: Made the drag calc more in line with big missiles as opposed to small bullets. - # + # # The old equations were based on curves for a conventional shell/bullet (no boat-tail), # and derived from Davic Culps code in AIBallistic. me.Cd0 = 0; @@ -2707,7 +2765,7 @@ var AIM = { me.Cd0 = (0.2965 * math.pow(mach, -2.1506) + 0.073766412) * 8 * (me.Cd_base+me.Cd_delta*me.deploy); } } - + if (!me.simple_drag) { if (me.vector_thrust and me.thrust_lbf>0) N=N*0.35; if (mach < 1.1) { @@ -2768,12 +2826,12 @@ var AIM = { me.thrust_lbf = 0; } } - + #me.force_cutoff_s = 0;# seen charts of real (aim9m) that thrust dont stop instantly, but fades out. This term would say hwo long it takes to fade out. Need to rework fuel consumption first. Maybe in future. #if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_2_duration-me.force_cutoff_s) and me.life_time < (me.drop_time + me.stage_1_duration + me.stage_2_duration)) { # me.thrust_lbf = me.extrapolate(me.life_time - (me.drop_time + me.stage_1_duration + me.stage_2_duration - me.force_cutoff_s),0,me.force_cutoff_s,me.thrust_lbf,0); #} - + if (me.thrust_lbf < 1) { me.smoke_prop.setBoolValue(0); } else { @@ -2786,7 +2844,7 @@ var AIM = { # Calculate speed change from last update. # # Acceleration = thrust/mass - drag/mass; - + me.acc = thrust_lbf / me.mass; me.q = 0.5 * rho * me.old_speed_fps * me.old_speed_fps;# dynamic pressure me.drag_acc = (Cd * me.q * me.ref_area_sqft) / me.mass; @@ -2842,7 +2900,7 @@ var AIM = { bleed0at25g: func () { me.loss_mps = 0 + ((me.last_dt - 0)/(7 - 0))*(-750 - 0); return me.loss_mps*M2FT; - }, + }, setFirst: func() { if (me.smoke_prop.getValue() == TRUE) { @@ -2883,10 +2941,10 @@ var AIM = { if(me.myG > me.max_g_current) { me.MyCoef = me.overload_limiter(me.hdg, me.pitch, me.track_signal_e, me.track_signal_h, me.old_speed_fps, me.dt, me.max_g_current); - + me.track_signal_h = me.MyCoef[0]; me.track_signal_e = me.MyCoef[1]; - + me.myGnew = me.steering_speed_G(me.hdg, me.pitch, me.track_signal_e, me.track_signal_h, me.old_speed_fps, me.dt); #me.printFlight(sprintf("G2 %.2f", myG)~sprintf(" - Coeff %.2f", MyCoef)); if (me.limitGs == FALSE) { @@ -2955,7 +3013,7 @@ var AIM = { # # navigation and guidance # - + me.raw_steer_signal_elev = 0; me.raw_steer_signal_head = 0; @@ -2973,7 +3031,7 @@ var AIM = { me.curr_deviation_h = me.t_course - me.hdg; #var (t_course, me.dist_curr) = courseAndDistance(me.coord, me.t_coord); - #me.dist_curr = me.dist_curr * NM2M; + #me.dist_curr = me.dist_curr * NM2M; me.curr_deviation_h = geo.normdeg180(me.curr_deviation_h); @@ -2982,7 +3040,7 @@ var AIM = { me.printFlightDetails("Altitude above launch platform = %07.1f ft", M2FT * (me.coord.alt()-me.ac.alt())); me.printFlightDetails("Altitude. Target %07.1f. Missile %07.1f. Atan2 %04.1f degs", me.t_coord.alt()*M2FT, me.coord.alt()*M2FT, math.atan2( me.t_coord.alt()-me.coord.alt(), me.dist_curr ) * R2D); - + if (math.abs(me.curr_deviation_h) < 15) { me.guidanceLawHorizInit = 0; @@ -3079,7 +3137,7 @@ var AIM = { 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.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) { @@ -3165,7 +3223,7 @@ var AIM = { } else { me.printStats(me.type~": Not guiding (lost radar reflection, 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; @@ -3177,10 +3235,10 @@ var AIM = { } else { me.printStats(me.type~": Not guiding (lost radiation, gave up)"); me.free = TRUE; - } + } } elsif ((me.dist_curr_direct*M2NM > me.detect_range_curr_nm or !me.FOV_check(me.hdg, me.pitch, me.curr_deviation_h, me.curr_deviation_e, me.max_seeker_dev, me.myMath)) and me.guidance != "gps" and me.guidance != "inertial") { # target is not in missile seeker view anymore - + if (me.fovLost == FALSE and me.detect_range_curr_nm != 0) { me.normFOV = me.FOV_check_norm(me.hdg, me.pitch, me.curr_deviation_h, me.curr_deviation_e, me.max_seeker_dev, me.myMath); me.printStats(me.type~": "~me.callsign~" is not in seeker view. (%d%% in view, %d%% in range)", me.normFOV*100, 100*me.dist_curr_direct*M2NM / me.detect_range_curr_nm);#~me.viewLost); @@ -3243,7 +3301,7 @@ var AIM = { canSeekerKeepUp: func () { me.globalVectorToTarget = me.myMath.eulerToCartesian3X(-me.t_course, me.t_elev_deg, 0); - me.localVectorTarget = me.myMath.rollPitchYawVector(0, -me.pitch, me.hdg, me.globalVectorToTarget); + me.localVectorTarget = me.myMath.yawPitchVector(me.hdg, -me.pitch, me.globalVectorToTarget); if (me.counter == me.counter_last+1 and !me.newTargetAssigned and me["localVectorSeeker"] != nil and (me.guidance == "heat" or me.guidance == "vision") and me.prevGuidance == me.guidance and me.prevTarget == me.Tgt) { # calculate if the seeker can keep up with the angular change of the target # @@ -3251,11 +3309,11 @@ var AIM = { # if (!me.caged) { # Gyro is stabilized - me.localVectorSeeker = me.myMath.rollPitchYawVector(0, -me.last_track_e, me.last_track_h, me.localVectorSeeker); + me.localVectorSeeker = me.myMath.yawPitchVector(me.last_track_h, -me.last_track_e, me.localVectorSeeker); } me.angleSeekerToTarget = me.myMath.angleBetweenVectors(me.localVectorSeeker, me.localVectorTarget); me.deviation_per_sec = me.angleSeekerToTarget/me.dt; - + if (me.deviation_per_sec > me.angular_speed) { if (me.angleSeekerToTarget < me.beam_width_deg) { me.max_seekertrack = me.angular_speed * me.dt; @@ -3288,7 +3346,7 @@ var AIM = { me.cruise_or_loft = FALSE;# If true then this method handles the vertical component of guiding. me.time_before_snap_up = me.drop_time * 3; me.limitGs = FALSE; - + if(me.loft_alt != 0 and me.snapUp == FALSE) { # this is for Air to ground/sea cruise-missile (SCALP, Sea-Eagle, Taurus, Tomahawk, RB-15...) @@ -3359,7 +3417,7 @@ var AIM = { howmany = howmany + 1; #We finalize the vector dir = {"x":me.geoPlus4.x()-me.coord.x(), "y":me.geoPlus4.y()-me.coord.y(), "z":me.geoPlus4.z()-me.coord.z()}; - #We measure distance to be sure that the ground intersection is closer than geoPlus4 + #We measure distance to be sure that the ground intersection is closer than geoPlus4 distance_Target = me.coord.direct_distance_to(me.geoPlus4); # Check for terrain between own aircraft and other: GroundIntersectResult = get_cart_ground_intersection(xyz, dir); @@ -3462,7 +3520,7 @@ var AIM = { me.printGuideDetails("Moving up"); me.raw_steer_signal_elev = -me.pitch + math.atan2(me.t_alt_delta_ft, me.old_speed_fps * me.dt * 5) * R2D; } else { - # that means a dive angle of 22.5° (a bit less + # that means a dive angle of 22.5° (a bit less # coz me.alt is in feet) (I let this alt in feet on purpose (more this figure is low, more the future pitch is high) me.printGuideDetails("Moving down"); me.slope = me.clamp(me.t_alt_delta_ft / 300, -7.5, 0);# the lower the desired alt is, the steeper the slope. @@ -3485,10 +3543,10 @@ var AIM = { # me.rotate_token = TRUE; # me.printGuide("Is last turn, snap-up/PN takes it from here..") # } - } elsif (me.snapUp == TRUE and me.t_elev_deg > me.clamp(-80/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 6 + } elsif (me.snapUp == TRUE and me.t_elev_deg > me.clamp(-50/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 6 and me.t_elev_deg < me.loft_angle #and me.t_elev_deg > -7.5 and me.dive_token == FALSE) { - # lofting: due to target is more than 10 miles out and we havent reached + # lofting: due to target is more than 10 miles out and we havent reached # our desired cruising alt, and the elevation to target is less than lofting angle. # The -7.5 limit, is so the seeker don't lose track of target when lofting. if (me.life_time < me.time_before_snap_up and me.coord.alt() * M2FT < me.loft_alt) { @@ -3504,7 +3562,7 @@ var AIM = { } me.cruise_or_loft = TRUE; } elsif (me.snapUp == TRUE and me.coord.alt() > me.t_coord.alt() and me.last_cruise_or_loft == TRUE - and me.t_elev_deg > me.clamp(-80/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 5.5) { + and me.t_elev_deg > me.clamp(-50/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 5.5) { # cruising: keeping altitude since target is below and more than -45 degs down me.ratio = (g_fps * me.dt)/me.old_speed_fps; @@ -3538,7 +3596,7 @@ var AIM = { me.track_signal_h = 0; me.printGuide("Trying to keep current %04.1f deg pitch.", me.pitch); }, - + level5: func () { me.track_signal_e = (5-me.pitch) * !me.free; me.track_signal_h = 0; @@ -3557,7 +3615,7 @@ var AIM = { me.track_signal_e = me.raw_steer_signal_elev * !me.free; me.track_signal_h = me.raw_steer_signal_head * !me.free; }, - + remoteControl: func () { me.track_signal_e = me.remote_control_pitch * me.dt * !me.free; me.track_signal_h = me.remote_control_yaw * me.dt * !me.free; @@ -3582,7 +3640,7 @@ var AIM = { ################################################# if (me.guidanceLaw == "direct") { - # pure pursuit + # pure pursuit me.raw_steer_signal_head = me.curr_deviation_h; if (me.cruise_or_loft == FALSE) { me.raw_steer_signal_elev = me.curr_deviation_e; @@ -3623,7 +3681,7 @@ var AIM = { me.last = me.life_time; me.next = me.seeker_filter*0.15*rand();# duration for this noise factor, till a new is computed. } - + me.horz_closing_rate_fps = ((me.dist_last - me.dist_curr)*M2FT)/me.dt+me.horz_closing_rate_fps;#clamped due to cruise missiles that can fly slower than target. me.horz_closing_rate_fps *= 0.5;# average over 2 frames me.printGuideDetails("Horz closing rate: %05d ft/sec", me.horz_closing_rate_fps); @@ -3634,13 +3692,13 @@ var AIM = { # This is especially important before we really gain speed against a receding fast target. me.course_deviation = geo.normdeg180(me.t_course-me.last_t_course); - + me.line_of_sight_rate_rps = (D2R*me.course_deviation)/me.dt;#positive clockwise me.printGuideDetails("LOS rate: %06.4f rad/s", me.line_of_sight_rate_rps); - me.t_velocity = me.myMath.getCartesianVelocity(me.Tgt.get_heading(), me.Tgt.get_Pitch(), me.Tgt.get_Roll(), me.Tgt.get_uBody(), me.Tgt.get_vBody(), me.Tgt.get_wBody()); - + me.t_velocity = me.myMath.getCartesianVelocity(-me.Tgt.get_heading(), me.Tgt.get_Pitch(), me.Tgt.get_Roll(), me.Tgt.get_uBody(), me.Tgt.get_vBody(), me.Tgt.get_wBody()); + if ((me.flareLock == FALSE and me.chaffLock == FALSE) or me.t_heading == nil) { me.euler = me.myMath.cartesianToEuler(me.t_velocity); if (me.euler[0] != nil) { @@ -3653,7 +3711,7 @@ var AIM = { } elsif (me.flarespeed_fps != nil) { me.t_speed_fps = me.flarespeed_fps;#true airspeed } - + me.t_horz_speed_fps = math.sqrt(me.t_velocity[0]*me.t_velocity[0]+me.t_velocity[1]*me.t_velocity[1]); me.t_LOS_norm_head_deg = me.t_course + 90;#when looking at target this direction will be 90 deg right of target me.t_LOS_norm_speed_fps = math.cos((me.t_LOS_norm_head_deg - me.t_heading)*D2R)*me.t_horz_speed_fps; @@ -3714,13 +3772,13 @@ var AIM = { #printf("horz acc = %.1f + %.1f", proportionality_constant*line_of_sight_rate_rps*horz_closing_rate_fps, proportionality_constant*t_LOS_norm_acc/2); if (me.guidanceLawHorizInit) { - # pure horiz pursuit + # pure horiz pursuit me.raw_steer_signal_head = me.curr_deviation_h; } # now translate that sideways acc to an angle: - - + + #printf("Proportional lead: %0.1f deg horz", -(me.curr_deviation_h-me.raw_steer_signal_head)); @@ -3746,12 +3804,12 @@ var AIM = { # proportional navigation for elevation # ######################################### #me.print(me.guidanceLaw~" in fully control"); - + me.line_of_sight_rate_up_rps = (D2R*(me.t_elev_deg-me.last_t_elev_deg))/me.dt; # calculate target acc as normal to LOS line: (up acc is positive) me.t_approach_bearing = me.t_course + 180; - + # used to do this with trigonometry, but vector math is simpler to understand: (they give same result though) me.t_LOS_elev_norm_speed = me.scalarProj(me.t_heading,me.t_pitch,me.t_speed_fps,me.t_approach_bearing,me.t_elev_deg*-1 +90); @@ -3759,7 +3817,7 @@ var AIM = { if (me.last_t_elev_norm_speed == nil) { me.last_t_elev_norm_speed = me.t_LOS_elev_norm_speed; } - + me.t_LOS_elev_norm_acc = (me.t_LOS_elev_norm_speed - me.last_t_elev_norm_speed)/me.dt; me.last_t_elev_norm_speed = me.t_LOS_elev_norm_speed; @@ -3796,7 +3854,7 @@ var AIM = { me.raw_steer_signal_elev = me.dt*me.radians_up_per_sec*R2D; } - # now compensate for the predicted gravity drop of attitude: + # now compensate for the predicted gravity drop of attitude: me.attitudePN = (math.atan2(-(me.speed_down_fps+g_fps * me.dt), me.speed_horizontal_fps ) - math.atan2(-me.speed_down_fps, me.speed_horizontal_fps )) * R2D; me.gravComp = -me.attitudePN; #printf("Gravity compensation %0.2f degs", me.gravComp); @@ -3847,6 +3905,7 @@ var AIM = { ####Ground interaction me.ground = geo.elevation(me.coord.lat(), me.coord.lon()) or 0; + me.terrainImpact = 0; if(me.ground > me.coord.alt()) { me.event = "exploded"; if(me.life_time < me.arming_time) { @@ -3861,9 +3920,9 @@ var AIM = { #me.direct_dist_m = me.coord.direct_distance_to(me.Tgt.get_Coord()); } if ((me.Tgt != nil and me.direct_dist_m != nil) or me.Tgt == nil) { - me.coord.set_alt(me.ground); - me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); - return TRUE; + me.terrainImpact = 1; + #me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); + #return TRUE; } } @@ -3872,14 +3931,20 @@ var AIM = { # (use xyz coord to avoid the strange behaviour of comparing geo coordinates). for (var i=me.crc_frames_look_back; i >= 0; i-=1){ me.crc_coord[i] = (i != 0) ? me.crc_coord[i-1] : me.coord.xyz(); - me.crc_t_coord[i] = (i != 0) ? me.crc_t_coord[i-1] : (me.inacc?me.Tgt.get_Coord(0).xyz():me.t_coord.xyz()); + me.crc_t_coord[i] = (i != 0) ? me.crc_t_coord[i-1] : me.t_coord.xyz(); me.crc_range[i] = (i != 0) ? me.crc_range[i-1] : me.myMath.magnitudeVector( - me.myMath.minus(me.crc_coord[0], + me.myMath.minus(me.crc_coord[0], me.crc_t_coord[0])); } - if (me.crc_coord[1] == nil or me.crc_t_coord[1] == nil or me.crc_range[1] == nil) - return FALSE; # Wait for the buffer to fill at least once. + if (me.crc_coord[1] == nil or me.crc_t_coord[1] == nil or me.crc_range[1] == nil) { + if (me.terrainImpact) { + me.coord.set_alt(me.ground); + me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); + return TRUE; + } + return FALSE; # Wait for the buffer to fill at least once. + } if (me.life_time > me.arming_time) { # Distance to target increase. @@ -3888,7 +3953,7 @@ var AIM = { me.subframeClosestRangeCoord(); # Provides `me.crc_closestRange` and `me.crc_missileCoord`. me.explode("Passed target.", me.crc_missileCoord, me.crc_closestRange); - return TRUE; + return TRUE; } if (me.life_time > me.selfdestruct_time or (me.destruct_when_free == TRUE and me.free == TRUE)) { me.explode("Selfdestructed.", me.coord); @@ -3901,14 +3966,19 @@ var AIM = { me.explode("Selfdestructed.", me.coord); return TRUE; } + if (me.terrainImpact) { + me.coord.set_alt(me.ground); + me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); + return TRUE; + } return FALSE; }, - + #! brief: Recursive function to compute the closest range in the past mfd frames. #! param fei: The frame to compute first (0: current frame, 1: previous frame, ...). #! param mfd: The maximum amount of frames available if needed. - #! input me.crc_coord: The coordinates of the missile in the last frames. - #! input me.crc_t_coord: The coordinates of the target in the last frames. + #! input me.crc_coord: The coordinates of the missile in the last frames. + #! input me.crc_t_coord: The coordinates of the target in the last frames. #! output me.crc_missileCoord: The coordinates of the missile when it was the closest to the target in the mfd+1 last frames. #! output me.crc_closestRange: The range of the missile when it was the closest to the target in the mfd+1 last frames. subframeClosestRangeCoord : func(fei=0, mfd=nil) { @@ -3923,10 +3993,10 @@ var AIM = { die("Argument exception: The fei (Frames End Index) cannot be negative."); # Ensure the availability of the frame-end data. - if(me.crc_coord[fei] == nil or me.crc_t_coord[fei] == nil) + if(me.crc_coord[fei] == nil or me.crc_t_coord[fei] == nil) die("No coordinates available for the end of the frame."); - # Indices for the coordinates at frame start (fsi) and frame end (fei); + # Indices for the coordinates at frame start (fsi) and frame end (fei); var fsi = fei + 1; # Buffers used for unprocessed result, set to the frame-end values in case of unavailable frame-start data; @@ -3934,7 +4004,7 @@ var AIM = { var targetCoord = me.crc_t_coord[fei]; # Check for availability of the frame-start data - if(me.crc_coord[fsi] != nil and me.crc_t_coord[fsi] != nil){ + if(me.crc_coord[fsi] != nil and me.crc_t_coord[fsi] != nil){ # Get the origin coordinates and speed of the missile and it's target for the current frame. # The units are in m for distances and frames for time. var misCoord = me.crc_coord[fsi]; @@ -3957,7 +4027,7 @@ var AIM = { else if (t < 0) if (mfd > 1) # If we can recursively compute the previous frame: return me.subframeClosestRangeCoord(fei+1, mfd-1); # Return it's result instead, and stop here. - else + else t = 0; # Set it to 0 to prevent extrapolation. # Compute (interpolate) the position of the missile and it's target when their range is the closest. @@ -3971,11 +4041,11 @@ var AIM = { me.crc_missileCoord.set_xyz(missileCoord[0], missileCoord[1], missileCoord[2]); me.crc_missileCoord.alt();# TODO: once fixed in FG this line is no longer needed. }, - + log: func (str) { damage.damageLog.push(str); }, - + notifyInFlight: func (lat,lon,alt,rdar,typeID,typ,unique,thrustOn,callsign, heading, pitch, speed, is_deleted=0) { ## thrustON cannot be named 'thrust' as FG for some reason will then think its a function (probably fixed by the way call() now is used) var msg = notifications.ArmamentInFlightNotification.new("mfly", unique, is_deleted?damage.DESTROY:damage.MOVE, 21+typeID); @@ -3994,15 +4064,16 @@ var AIM = { msg.Pitch = pitch; msg.Heading = heading; msg.u_fps = speed; + #msg.isValid(); notifications.geoBridgedTransmitter.NotifyAll(msg); #print("fox2.nas: transmit in flight"); #f14.debugRecipient.Receive(msg); }, - + notifyCrater: func (lat,lon,alt,big,heading,static) { var uni = int(rand()*15000000); var msg = notifications.StaticNotification.new("stat", uni, 1, big); - + msg.Position.set_latlon(lat,lon,alt); msg.IsDistinct = 0; msg.Heading = heading; @@ -4011,13 +4082,13 @@ var AIM = { #f14.debugRecipient.Receive(msg); damage.statics["obj_"~uni] = [static, lat,lon,alt, heading,big]; }, - + notifyHit: func (RelativeAltitude, Distance, callsign, Bearing, reason, typeID, type, self) { var msg = notifications.ArmamentNotification.new("mhit", 4, 21+typeID); msg.RelativeAltitude = RelativeAltitude; msg.Bearing = Bearing; msg.Distance = Distance; - msg.RemoteCallsign = callsign; # RJHTODO: maybe handle flares / chaff + msg.RemoteCallsign = callsign; # RJHTODO: maybe handle flares / chaff if (self) { msg.Callsign = callsign; msg.FromIncomingBridge = 1; @@ -4041,17 +4112,17 @@ var AIM = { } elsif (me.chaffLock) { reason = "Locked onto chaff."; } - + me.coord = coordinates; # Set the current missile coordinates at the explosion point. - + if(getprop("payload/armament/msg")) { thread.lock(mutexTimer); append(AIM.timerQueue, [AIM, AIM.notifyInFlight, [me.coord.lat(), me.coord.lon(), me.coord.alt(),0,me.typeID,me.type,me.unique_id,0,"", me.hdg, me.pitch, 0, 0], -1]); thread.unlock(mutexTimer); } - + var wh_mass = (event == "exploded" and !me.inert) ? me.weight_whead_lbm : 0; #will report 0 mass if did not have time to arm - + thread.lock(mutexTimer); append(AIM.timerQueue, [me,impact_report,[coordinates, wh_mass, "munition", me.type, me.new_speed_fps*FT2M],0]); thread.unlock(mutexTimer); @@ -4080,7 +4151,7 @@ var AIM = { me.printStats("%s time %.1f", phrase, me.life_time); if(getprop("payload/armament/msg") and hitPrimaryTarget and wh_mass > 0){ thread.lock(mutexTimer); - append(AIM.timerQueue, [AIM, AIM.notifyHit, [coordinates.alt() - (me.inacc?me.Tgt.get_Coord(0).alt():me.t_coord.alt()),range,me.callsign,coordinates.course_to(me.inacc?me.Tgt.get_Coord(0):me.t_coord),reason,me.typeID, me.typeLong, 0], -1]); + append(AIM.timerQueue, [AIM, AIM.notifyHit, [coordinates.alt() - me.t_coord.alt(),range,me.callsign,coordinates.course_to(me.t_coord),reason,me.typeID, me.typeLong, 0], -1]); thread.unlock(mutexTimer); } else { thread.lock(mutexTimer); @@ -4096,7 +4167,7 @@ var AIM = { thread.unlock(mutexTimer); } } - + me.ai.getNode("valid", 1).setBoolValue(0); thread.lock(mutexTimer); append(AIM.timerQueue, [me, me.setModelRemoved, [], -1]); @@ -4110,7 +4181,7 @@ var AIM = { } me.Tgt = nil; }, - + multiExplosion: func (explode_coord, event, wh_mass) { # hit everything that is nearby except for target itself. me.sendout = 0; @@ -4119,9 +4190,7 @@ var AIM = { continue; } var min_distance = me.testMe.get_Coord().direct_distance_to(explode_coord); - if (me.inacc) { - min_distance = me.testMe.get_Coord(0).direct_distance_to(explode_coord); - } + if (min_distance < me.reportDist and (me.Tgt == nil or me.testMe.getUnique() != me.Tgt.getUnique())) { var phrase = sprintf("%s %s: %.1f meters from: %s", me.type,event, min_distance, me.testMe.get_Callsign()); me.printStats(phrase); @@ -4196,7 +4265,7 @@ var AIM = { if (me.noCommonTarget) { return nil; } - if (me.inacc and me.target_pnt and contactPoint != nil) { + if (me.target_pnt and contactPoint != nil) { return contactPoint; } else { return contact; @@ -4269,9 +4338,9 @@ var AIM = { } me.cooling_last_time = me.cool_elapsed; me.detect_range_curr_nm = me.extrapolate(me.warm, 0, 1, me.cold_detect_range_nm, me.warm_detect_range_nm); - #me.detect_range_curr_nm *= me.seam_scan?0.5:1; # source for this is not credible. + me.detect_range_curr_nm *= me.seam_scan?0.65:1;#GR1F-16CJ-34-1-1 page 1-402 } else { - #me.detect_range_curr_nm = (me.seam_scan?0.5:1)*me.max_fire_range_nm; # no credible source for this + me.detect_range_curr_nm = (me.seam_scan?0.65:1)*me.max_fire_range_nm;#GR1F-16CJ-34-1-1 page 1-402 } }, @@ -4279,7 +4348,7 @@ var AIM = { # call this only before firing if (!(me.tagt.get_type() == AIR and me.tagt.get_Speed()<15) and ((me.guidance != "semi-radar" or me.is_painted(me.tagt) == TRUE) and (me.guidance !="laser" or me.is_laser_painted(me.tagt) == TRUE)) and (me.guidance != "radiation" or me.is_radiating_aircraft(me.tagt) == TRUE) - and me.rng < me.max_fire_range_nm and me.rng > me.getCurrentMinFireRange(me.tagt) and me.FOV_check(OurHdg.getValue(),OurPitch.getValue(),me.total_horiz, me.total_elev, me.slave_to_radar?(me.guidance == "heat" or me.guidance == "vision"?math.min(me.max_seeker_dev, me.fcs_fov):me.fcs_fov):me.max_seeker_dev, vector.Math) + and me.rng < me.max_fire_range_nm and me.rng > me.getCurrentMinFireRange(me.tagt) and me.FOV_check(OurHdg.getValue(),OurPitch.getValue(),me.total_horiz, me.total_elev, me.slave_to_radar or contactPoint==me.tagt?(me.guidance == "heat" or me.guidance == "vision"?math.min(me.max_seeker_dev, me.fcs_fov):me.fcs_fov):me.max_seeker_dev, vector.Math) and (me.rng < me.detect_range_curr_nm or (me.guidance != "radar" and me.guidance != "semi-radar" and me.guidance != "heat" and me.guidance != "vision" and me.guidance != "heat" and me.guidance != "radiation")) and (me.guidance != "heat" or (me.all_aspect == TRUE or me.rear_aspect(geo.aircraft_position(), me.tagt) == TRUE)) and me.checkForView()) { @@ -4305,7 +4374,7 @@ var AIM = { me.geodPos = aircraftToCart({x:-me.radarX, y:me.radarY, z: -me.radarZ}); me.launchCoord.set_xyz(me.geodPos.x, me.geodPos.y, me.geodPos.z); } - + me.potentialCoord = me.tagt.get_Coord(); me.xyz = {"x":me.launchCoord.x(), "y":me.launchCoord.y(), "z":me.launchCoord.z()}; me.directionLOS = {"x":me.potentialCoord.x()-me.launchCoord.x(), "y":me.potentialCoord.y()-me.launchCoord.y(), "z":me.potentialCoord.z()-me.launchCoord.z()}; @@ -4327,7 +4396,7 @@ var AIM = { } return TRUE; }, - + checkForViewInFlight: func (tagt) { if (me.guidance != "gps" and me.guidance != "inertial") { me.launchCoord = me.coord; @@ -4365,7 +4434,7 @@ var AIM = { #if(me.slaveContact != nil) printf("class failed %d %d %d",me.slaveContact.isValid() == TRUE,me.slaveContact.get_type() == AIR,me.target_air == TRUE); return FALSE; }, - + newLock: func (tagt) { # for switching to new lock during flight. #reorder to gain performance @@ -4478,7 +4547,7 @@ var AIM = { # Check if in range and in the seeker FOV. if (me.checkForLock()) { me.printSearch("uncaged-search ready for lock"); - + me.convertGlobalToSeekerViewDirection(me.tagt.get_bearing(), me.tagt.getElevation(), OurHdg.getValue(), OurPitch.getValue(), OurRoll.getValue()); me.testSeeker(); if (me.inBeam) { @@ -4581,7 +4650,7 @@ var AIM = { if (me.checkForClass()) { me.tagt = me.slaveContact; me.rng = me.tagt.get_range(); - + # Check if in range and in the seeker FOV. me.total_elev = deviation_normdeg(OurPitch.getValue(), me.tagt.getElevation()); # deg. me.total_horiz = deviation_normdeg(OurHdg.getValue(), me.tagt.get_bearing()); # deg. @@ -4616,7 +4685,7 @@ var AIM = { settimer(func me.update_lock(), 0.1); }, - convertGlobalToSeekerViewDirection: func (bearing, elevation, heading, pitch, roll) { + convertGlobalToSeekerViewDirection: func (bearing, elevation, heading, pitch, roll) { me.target_x = math.cos(bearing*D2R)*math.cos(elevation*D2R); me.target_y = -math.sin(bearing*D2R)*math.cos(elevation*D2R); me.target_z = math.sin(elevation*D2R); @@ -4627,7 +4696,7 @@ var AIM = { me.rotation = vector.Math.multiplyMatrices(me.rollLaunchvehicle, vector.Math.multiplyMatrices(me.pitchLaunchvehicle, me.yawLaunchvehicle)); me.target_vector_from_seekers_view = vector.Math.multiplyMatrixWithVector(me.rotation, me.target_vector); me.angles = vector.Math.cartesianToEuler(me.target_vector_from_seekers_view); - + me.seeker_head_target = me.angles[0]==nil?0:geo.normdeg180(me.angles[0]); me.seeker_elev_target = me.angles[1]; }, @@ -4708,7 +4777,7 @@ var AIM = { if (me.seeker_last_time != 0) { me.seeker_time = me.seeker_elapsed - me.seeker_last_time; me.seeker_max_move = me.seeker_time*me.angular_speed; - + # Build unit vector components for seeker and target location in aircraft frame: me.seeker_x = math.cos(me.seeker_head*D2R)*math.cos(me.seeker_elev*D2R); me.seeker_y = -math.sin(me.seeker_head*D2R)*math.cos(me.seeker_elev*D2R); @@ -4716,9 +4785,9 @@ var AIM = { me.ideal_seeker_deviation = vector.Math.angleBetweenVectors([me.seeker_x,me.seeker_y,me.seeker_z],[me.target_x,me.target_y,me.target_z]); me.ideal_total_seeker_deviation = vector.Math.angleBetweenVectors(me.seeker_reset, [me.target_x,me.target_y,me.target_z]); - + if (me.ideal_seeker_deviation > me.seeker_max_move) { - me.new_seeker_vector = vector.Math.rotateVectorTowardsVector([me.seeker_x,me.seeker_y,me.seeker_z],[me.target_x,me.target_y,me.target_z],me.seeker_max_move); + me.new_seeker_vector = vector.Math.rotateVectorTowardsVector([me.seeker_x,me.seeker_y,me.seeker_z],[me.target_x,me.target_y,me.target_z],me.seeker_max_move); me.new_seeker_deviation = vector.Math.angleBetweenVectors(me.seeker_reset, me.new_seeker_vector); if (me.new_seeker_deviation < me.max_seeker_dev) { @@ -4761,7 +4830,7 @@ var AIM = { # Compute HUD diamond position. if ( use_fg_default_hud == TRUE) { var h_rad = (90 - me.seeker_head) * D2R; - var e_rad = (90 - me.seeker_elev) * D2R; + var e_rad = (90 - me.seeker_elev) * D2R; var devs = develev_to_devroll(h_rad, e_rad); var combined_dev_deg = devs[0]; var combined_dev_length = devs[1]; @@ -4812,12 +4881,16 @@ var AIM = { return; } elsif (me.deleted == TRUE) { return; + } elsif (me.slave_to_radar and me.caged and me.getContact() != me.Tgt) { + me.printSearch("target switch"); + me.return_to_search(); + return; } me.printSearch("lock"); # Time interval since lock time or last track loop. - #if (me.status == MISSILE_LOCK) { + #if (me.status == MISSILE_LOCK) { # Status = locked. Get target position relative to our aircraft. - + #} #me.time = props.globals.getNode("/sim/time/elapsed-sec", 1).getValue(); @@ -4829,7 +4902,7 @@ var AIM = { me.computeSeekerPos(); if (me.status != MISSILE_STANDBY ) {#TODO: should this also check for starting up? me.in_view = me.check_t_in_fov(); - + if (me.in_view == FALSE) { me.printSearch("out of view"); me.return_to_search(); @@ -4880,7 +4953,7 @@ var AIM = { me.return_to_search(); return; } - + settimer(func me.update_lock(), deltaSec.getValue()==0?0.5:0.1); return; } @@ -4915,7 +4988,7 @@ var AIM = { #if (me.status==MISSILE_FLYING) printf("1: %.1f out of %.1f, deviation %.1f, %.1f", me.fov_radial, fov_radius, deviation_hori, deviation_elev); return FALSE; }, - + FOV_check_norm: func (meHeading, mePitch, deviation_hori, deviation_elev, fov_radius,vect) { # we measure the geodesic angle between current attitude and the attitude to target. me.meVector = vect.eulerToCartesian3X(-meHeading, mePitch, 0); @@ -5014,10 +5087,10 @@ var AIM = { var explode_smoke_path = path_base~"explode-smoke-id-" ~ me.ID; me.explode_smoke_prop = props.globals.initNode( explode_smoke_path, FALSE, "BOOL", TRUE); - + var explode_water_path = path_base~"explode-water-id-" ~ me.ID; me.explode_water_prop = props.globals.initNode( explode_water_path, FALSE, "BOOL", TRUE); - + var explode_angle_path = path_base~"explode-angle"; me.explode_angle_prop = props.globals.initNode( explode_angle_path, 0.0, "DOUBLE", TRUE); @@ -5043,7 +5116,7 @@ var AIM = { me.msl_prop.setBoolValue(FALSE); me.smoke_prop.setBoolValue(FALSE); var info = geodinfo(me.coord.lat(), me.coord.lon()); - + if (hitGround) { if (info == nil) { me.explode_water_prop.setBoolValue(FALSE); @@ -5090,7 +5163,7 @@ var AIM = { siz = 1; crater_model = getprop("payload/armament/models") ~ "crater_big.xml"; } - + if (crater_model != "" and me.weight_whead_lbm > 150) { var static = geo.put_model(crater_model, me.coord.lat(), me.coord.lon()); #print("put crater"); @@ -5160,7 +5233,7 @@ var AIM = { # next speed vector me.vector_next_x = math.cos(me.steer_deg*D2R)*s_fps; me.vector_next_y = math.sin(me.steer_deg*D2R)*s_fps; - + # present speed vector me.vector_now_x = s_fps; me.vector_now_y = 0; @@ -5168,76 +5241,76 @@ var AIM = { # Delta velocity: subtract the vectors from each other and get the magnitude me.dv = me.myMath.minus([me.vector_now_x,me.vector_now_y,0],[me.vector_next_x,me.vector_next_y,0]); me.dv = me.myMath.magnitudeVector(me.dv); - + # calculate g-force # dv/dt=a me.g = (me.dv/dt) / g_fps; return me.g; }, - + steering_speed_G: func(meHeading, mePitch, steering_e_deg, steering_h_deg, s_fps, dt) { # Get G number from steering (e, h) in deg, speed in ft/s. me.meVector = me.myMath.eulerToCartesian3X(-meHeading, mePitch, 0); me.meVectorN= me.myMath.normalize(me.meVector); - me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now + me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now me.itVector = me.myMath.eulerToCartesian3X(-(meHeading+steering_h_deg), mePitch+steering_e_deg, 0); me.itVector = me.myMath.normalize(me.itVector); me.itVector = me.myMath.product(s_fps, me.itVector);#velocity vector if doing that steering - + # Delta lateral velocity me.dv = me.myMath.minus(me.itVector, me.meVector); - me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dv); + me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dv); me.dv = me.myMath.magnitudeVector(me.dv); - + # calculate g-force # dv/dt=a me.g = (me.dv/dt) / g_fps; return me.g; }, - + overload_limiter: func(meHeading, mePitch, steering_e_deg, steering_h_deg, s_fps, dt, gMax) { # The missile desires to rotate a certain amount # This function will limit that steering to prevent it from exceeding the max G it should be able to do at this air density - + if (gMax == 0 or (steering_e_deg == 0 and steering_h_deg == 0)) { return [0,0]; } - + # Get G number from steering (e, h) in deg, speed in ft/s. me.meVector = me.myMath.eulerToCartesian3X(-meHeading, mePitch, 0); me.meVectorN= me.myMath.normalize(me.meVector); - me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now + me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now me.itVector = me.myMath.eulerToCartesian3X(-(meHeading+steering_h_deg), mePitch+steering_e_deg, 0); me.itVectorN= me.myMath.normalize(me.itVector); me.itVector = me.myMath.product(s_fps, me.itVectorN);#velocity vector if doing that steering - + # Delta lateral velocity me.dvAcc = me.myMath.minus(me.itVector, me.meVector); - me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dvAcc); + me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dvAcc); me.dv = me.myMath.magnitudeVector(me.dv); - + # calculate g-force # dv/dt=a me.g_load = (me.dv/dt) / g_fps; - + me.exceed_g = gMax/me.g_load; - + if (me.exceed_g >= 1) { return [steering_h_deg, steering_e_deg]; } - + # Now do something that works okay for small desired steerings, but fails for big steerings. Hence the 0.9 factor. me.dvAcc9 = me.myMath.product(me.exceed_g*0.9, me.dvAcc); me.itVector = me.myMath.plus(me.dvAcc9, me.meVector); - + me.euler = me.myMath.cartesianToEuler(me.itVector); - + if (me.euler[0] == nil) { me.euler[0] = meHeading; } - + return [geo.normdeg180(me.euler[0]-meHeading), me.euler[1]-mePitch]; }, @@ -5370,7 +5443,7 @@ var AIM = { thread.unlock(mutexTimer); } }, - + timerLoop: func { thread.lock(mutexTimer); AIM.tq = AIM.timerQueue; @@ -5380,7 +5453,7 @@ var AIM = { AIM.timerCall(cmd); } }, - + timerCall: func (cmd) { if (cmd != nil) { if (cmd[3] == -1) { @@ -5395,7 +5468,7 @@ var AIM = { } } }, - + pop_front: func (vector) { if (size(vector)==0) return [nil, vector]; var new_vector = []; @@ -5406,9 +5479,9 @@ var AIM = { } return [vector[0],new_vector]; }, - + timers: {}, - + timerQueue: [], active: {}, @@ -5504,16 +5577,18 @@ var hud_radius_m = 0.100; #was in hud var develev_to_devroll = func(dev_rad, elev_rad) { + if (math.sin(dev_rad) == 0 or math.sin(elev_rad) == 0) return [0,0,0]; + if (math.cos(dev_rad) == 0 or math.cos(elev_rad) == 0) return [0,20,1]; var clamped = 0; # Deviation length on the HUD (at level flight), # 0.6686m = distance eye <-> virtual HUD screen. var h_dev = eye_hud_m / ( math.sin(dev_rad) / math.cos(dev_rad) ); var v_dev = eye_hud_m / ( math.sin(elev_rad) / math.cos(elev_rad) ); # Angle between HUD center/top <-> HUD center/symbol position. - # -90° left, 0° up, 90° right, +/- 180° down. + # -90° left, 0° up, 90° right, +/- 180° down. var dev_deg = math.atan2( h_dev, v_dev ) * R2D; # Correction with own a/c roll. - var combined_dev_deg = dev_deg - OurRoll.getValue(); + var combined_dev_deg = dev_deg; # Lenght HUD center <-> symbol pos on the HUD: var combined_dev_length = math.sqrt((h_dev*h_dev)+(v_dev*v_dev)); # clamp and squeeze the top of the display area so the symbol follow the egg shaped HUD limits. @@ -5522,7 +5597,7 @@ var develev_to_devroll = func(dev_rad, elev_rad) { if ( abs_combined_dev_deg >= 0 and abs_combined_dev_deg < 90 ) { var coef = ( 90 - abs_combined_dev_deg ) * 0.00075; if ( coef > 0.050 ) { coef = 0.050 } - clamp -= coef; + clamp -= coef; } if ( combined_dev_length > clamp ) { combined_dev_length = clamp; diff --git a/S-75/Nasal/vector.nas b/S-75/Nasal/vector.nas index 71aa769..621b0cb 100644 --- a/S-75/Nasal/vector.nas +++ b/S-75/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.97 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -39,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -85,21 +87,24 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.rollM, me.multiplyMatrices(me.pitchM, me.yawM)); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.rollM, me.multiplyMatrices(me.pitchM, me.yawM)); return me.multiplyMatrixWithVector(me.rotation, vector); }, @@ -119,6 +124,12 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + # rotate a vector. Order: pitch pitchVector: func (pitch, vector) { me.pitchM = me.pitchMatrix(pitch); @@ -187,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -285,13 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, # Project a onto ontoMe. projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -398,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/S-75/S-75-set.xml b/S-75/S-75-set.xml index 5fbbf94..619f717 100644 --- a/S-75/S-75-set.xml +++ b/S-75/S-75-set.xml @@ -240,7 +240,20 @@ - + + 2.25 + true + 80 + false + true + false + false + false + false + false + 2.25 + false + Aircraft/S-75/Models/Armament/Weapons/ true diff --git a/SA-6/Nasal/damage.nas b/SA-6/Nasal/damage.nas index 8810af3..44b753a 100644 --- a/SA-6/Nasal/damage.nas +++ b/SA-6/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 4;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 80;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -162,9 +164,42 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], - "3M9": [96, 125.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID @@ -225,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -257,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -268,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -290,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -298,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -311,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -320,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -346,10 +382,9 @@ var DamageRecipient = } } } - + # Missile approach warning: - var callsign = getprop("sim/multiplay/callsign"); - callsign = size(callsign) < 8 ? callsign : left(callsign,7); + var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; if (!radarOn) return emesary.Transmitter.ReceiptStatus_OK;# this should be little more complex later var heading = getprop("orientation/heading-deg"); @@ -380,8 +415,28 @@ var DamageRecipient = # debug.dump(notification); # # - var callsign = getprop("sim/multiplay/callsign"); - callsign = size(callsign) < 8 ? callsign : left(callsign,7); + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } + var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit if (m28_auto) mig28.engagedBy(notification.Callsign); @@ -426,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -439,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -458,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -469,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -609,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -617,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -627,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -646,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -670,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -713,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -739,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -813,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -821,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -841,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -849,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -1008,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1042,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1056,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1081,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1140,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1153,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1174,21 +1295,22 @@ var code_ct = func () { if (getprop("payload/armament/msg")) { setprop("sim/rendering/redout/enabled", TRUE); #call(func{fgcommand('dialog-close', multiplayer.dialog.dialog.prop())},nil,var err= []);# props.Node.new({"dialog-name": "location-in-air"})); - #call(func{multiplayer.dialog.del();},nil,var err= []); + call(func{multiplayer.dialog.del();},nil,var err= []); if (!getprop("gear/gear[0]/wow")) { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1206,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1244,6 +1367,20 @@ var printDamageLog = func { print(); } +var processCallsign = func (callsign) { + # Convert the callsign to one that emesary can work with. + var l = size(callsign); + callsign = l < 8?callsign:left(callsign, 7); + var newCallsign = ""; + for(var ii = 0; ii < l; ii += 1) { + var ev = emesary.TransferString.getalphanumericchar(substr(callsign,ii,1)); + if (ev != nil) { + newCallsign ~= ev; + } + } + return newCallsign; +} + #TODO testing: var writeDamageLog = func { @@ -1271,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/SA-6/Nasal/fire-control.nas b/SA-6/Nasal/fire-control.nas index 710830e..ed28c8f 100644 --- a/SA-6/Nasal/fire-control.nas +++ b/SA-6/Nasal/fire-control.nas @@ -53,7 +53,7 @@ var missile_name = "m3m9"; var missile_brevity = "3M9"; var missile_max_distance = 18; #max distance of target in nm when it will fire var missile_min_distance = 0.5; #minimum distance in nm when it will fire -var lockon_time = 28; #time in seconds it takes to lock on and get a firing solution on a target +var lockon_time = 24; #time in seconds it takes to lock on and get a firing solution on a target var fire_minimum_interval = 7;# time since last track was initiated till a new can be initiated var same_target_max_missiles = 2;# max number of missiles in air against same target diff --git a/SA-6/Nasal/guided-missiles.nas b/SA-6/Nasal/guided-missiles.nas index 7e467b7..4e2a1ec 100644 --- a/SA-6/Nasal/guided-missiles.nas +++ b/SA-6/Nasal/guided-missiles.nas @@ -1,12 +1,12 @@ ######################################################################################### -####### +####### ####### Guided/Cruise missiles, rockets and dumb/glide/guided bombs code for Flightgear. ####### ####### License: GPL 2.0 ####### ####### Authors: ####### Alexis Bory, Fabien Barbier, Richard Harrison, Justin Nicholson, Nikolai V. Chr., Axel Paccalin, Colin Geniet -####### +####### ####### The file vector.nas needs to be available in namespace 'vector'. ####### ####### In addition, some code is derived from work by: @@ -41,7 +41,7 @@ # Laser and semi-radar guided munitions need the target to be painted to keep lock. Notice gps guided munition that are all aspect will never lose lock, # whether they can 'see' the target or not. Anti-radiation missiles will need the target to send radiation towards the missile. # Set DEBUG_STATS and/or DEBUG_FLIGHT to true to check how the missile works during flight, when you are designing a weapon. -# +# # # Usage: # @@ -60,10 +60,10 @@ # the weapon did not have time to arm before hitting ground. # To drop the munition, without arming it nor igniting its engine, call eject(). # Remote guidance requires the use of midFlightFunction (see below) to transmit guidance parameters, in the remote_yaw and remote_pitch fields of the output. -# +# # # Limitations: -# +# # The weapons use a simplified flight model that does not have AoA or sideslip. Mass balance, rotational inertia, wind is also not implemented. They also do not roll due to aerodynmic effects. # If you fire a weapon and have HoT enabled in flightgear, they likely will not hit very precise. # The weapons are highly dependent on framerate, so low frame rate will make them hit imprecise. @@ -218,7 +218,7 @@ var contactPoint = nil; var AIM = { lowestETA: nil, #done - new : func (p, type = "AIM-9", sign = "Sidewinder", midFlightFunction = nil, nasalPosition = nil) { + new : func (p, type = "AIM-9L", sign = "Sidewinder", midFlightFunction = nil, nasalPosition = nil) { if(AIM.active[p] != nil) { #do not make new missile logic if one exist for this pylon. return -1; @@ -252,19 +252,15 @@ var AIM = { if (m.SwSoundVol.getValue() == nil) { m.SwSoundVol.setDoubleValue(0); } - m.inacc = getprop("payload/armament/tgp-inacc-system");# set to false, unless using the f16 m.tacview_support = getprop("payload/armament/tacview");# set to false, unless using an aircraft that has tacview m.gnd_launch = getprop("payload/armament/gnd-launch");#true to be a SAM or ship if (m.gnd_launch == nil) { m.gnd_launch = 0; } - if (m.inacc == nil) { - m.inacc = 0; - } if (m.tacview_support == nil) { m.tacview_support = 0; } - + m.ID = p; m.stationName = AcModel.getNode("armament/station-name").getValue(); if (m.nasalPosition == nil) { @@ -279,7 +275,7 @@ var AIM = { if (fox2_unique_id >100) fox2_unique_id = -100; m.unique_id = fox2_unique_id; m.nodeString = "payload/armament/"~m.type_lc~"/"; - + if (m.tacview_support) { m.tacviewID = 11000 + int(math.floor(rand()*10000)); } @@ -315,6 +311,7 @@ var AIM = { m.radarY = getprop(m.nodeString~"FCS-y"); # This is handy for SAMs with radar on a mast. m.radarZ = getprop(m.nodeString~"FCS-z"); # In future I will add direction to it also, for now its center gimbal is along -x axis. m.expand_min = getprop(m.nodeString~"expand-min-fire-range"); # Bool. Default false. If min fire range should expand with closing rate. Mainly use this for A/A missiles. + 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 @@ -384,36 +381,40 @@ var AIM = { m.dlz_enabled = getprop(m.nodeString~"DLZ"); # Supports dynamic launch zone info. For now only works with A/A. [optional] m.dlz_opt_alt = getprop(m.nodeString~"DLZ-optimal-alt-feet"); # Minimum altitude required to hit the target at max range. m.dlz_opt_mach = getprop(m.nodeString~"DLZ-optimal-closing-mach"); # Closing speed required to hit the target at max range at minimum altitude. - # detailed drag settings + # detailed drag settings m.Cd_plume = getprop(m.nodeString~"exhaust-plume-parasitic-drag-factor"); # Default 1. For AIM-120, Naval Postgraduate School paper suggest around 0.6. It will reduce drag during burn. m.simple_drag = getprop(m.nodeString~"simplified-induced-drag"); # bool. Default true. If enabled, the properties below wont be used: m.wing_aspect_ratio = getprop(m.nodeString~"wing-aspect-ratio"); # span^2/wing_area. Default to 8 m.wing_eff = getprop(m.nodeString~"wing-efficiency-relative-to-an-elliptical-planform"); # Default to 0.85 - + if (m.cold_detect_range_nm == nil) { # backwards compatibility m.cold_detect_range_nm = m.max_fire_range_nm; - } + } m.detect_range_curr_nm = m.cold_detect_range_nm; - + if (m.max_seeker_dev == nil) { m.max_seeker_dev = 15; } if (m.beam_width_deg == nil) { m.beam_width_deg = 4; - } + } m.beam_width_deg *= 0.5; - + if (m.eject_speed == nil) { m.eject_speed = 0; - } - + } + if (m.rail_forward == TRUE) { m.rail_pitch_deg = 0; m.rail_head_deg = 0; } + if (m.asc == nil) { + m.asc = 0; + } + if (m.wing_aspect_ratio == nil) { m.wing_aspect_ratio = 8.0; } @@ -433,7 +434,7 @@ var AIM = { if (m.radarOrigin == nil) { m.radarOrigin = 1; } - + if (m.guideWhileDrop == nil) { m.guideWhileDrop = 0; } @@ -453,7 +454,7 @@ var AIM = { if (m.seeker_filter == nil) { m.seeker_filter = 0; } - + if (m.ready_time == nil) { m.ready_time = 0; } @@ -487,19 +488,19 @@ var AIM = { if(m.canSwitch == nil) { m.canSwitch = FALSE; } - + if(m.terminal_alt_factor == nil) { m.terminal_alt_factor = 2; } - + if(m.terminal_rise_time == nil) { m.terminal_rise_time = 6; } - + if(m.terminal_dive_time == nil) { m.terminal_dive_time = 4; } - + if (m.weight_fuel_lbm == nil) { m.weight_fuel_lbm = 0; } @@ -509,10 +510,10 @@ var AIM = { if (m.vector_thrust == nil) { m.vector_thrust = FALSE; } - if (m.flareResistance == nil) { + if (m.flareResistance == nil or !m.gnd_launch) { m.flareResistance = 0.85; } - if (m.chaffResistance == nil) { + if (m.chaffResistance == nil or !m.gnd_launch) { m.chaffResistance = 0.85; } if (m.guidanceLaw == nil) { @@ -616,7 +617,7 @@ var AIM = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("ai/models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild(m.type_lc, i, 0) == nil) { @@ -631,16 +632,16 @@ var AIM = { m.ai.getNode("valid", 1).setBoolValue(0); m.ai.getNode("name", 1).setValue(type); m.ai.getNode("sign", 1).setValue(sign); - m.ai.getNode("callsign", 1).setValue(type); + m.ai.getNode("callsign", 1).setValue(sprintf("%s_%d", type, m.unique_id)); m.ai.getNode("missile", 1).setBoolValue(1); - - + + var id_model = m.weapon_model ~ m.ID ~ ".xml"; m.model.getNode("path", 1).setValue(id_model); m.model.getNode("enable-hot", 1).setBoolValue(0);# This is if people forget to set it in xml. m.model.getNode("name", 1).setValue(m.typeLong);# this helps in debugging. - - + + # Create the AI position and orientation properties. m.latN = m.ai.getNode("position/latitude-deg", 1); @@ -649,7 +650,7 @@ var AIM = { m.hdgN = m.ai.getNode("orientation/true-heading-deg", 1); m.pitchN = m.ai.getNode("orientation/pitch-deg", 1); m.rollN = m.ai.getNode("orientation/roll-deg", 1); - + m.mpLat = getprop("payload/armament/MP-lat");# properties to be used for showing missile over MP. m.mpLon = getprop("payload/armament/MP-lon"); m.mpAlt = getprop("payload/armament/MP-alt"); @@ -665,7 +666,7 @@ var AIM = { } } - + m.target_air = find("A", m.class)==-1?FALSE:TRUE; m.target_sea = find("M", m.class)==-1?FALSE:TRUE;#use M for marine, since S can be confused with surface. @@ -676,8 +677,8 @@ var AIM = { m.coord = geo.Coord.new().set_latlon(0, 0, 0); m.t_coord = nil; - - + + # # Seekerhead @@ -699,7 +700,7 @@ var AIM = { m.seam_scan = 0; m.cooling_last_time = 0; m.cool_total_time = 0; - + # # Emesary damage system # @@ -789,7 +790,7 @@ var AIM = { m.nextGroundElevation = 0; # next Ground Elevation m.nextGroundElevationMem = [-10000, -1]; m.terrainStage = 0; - + # # Rail # @@ -822,7 +823,7 @@ var AIM = { m.chaffTime = 0; m.chaffLock = FALSE; m.flarespeed_fps = nil; - + # # Telemetry # @@ -839,7 +840,7 @@ var AIM = { m.tooLowSpeedTime = -1; m.lostLOS = FALSE; - + # # LOAL # @@ -859,9 +860,9 @@ var AIM = { m.pendingLaunchSound = -1; m.explodeSound = TRUE; - - - + + + m.standby();# these loops will run until released or deleted. #for multithreading @@ -870,7 +871,7 @@ var AIM = { return AIM.active[m.ID] = m; }, - + del: func { # can be called at any time, before or during flight. # @@ -946,9 +947,9 @@ var AIM = { me.ccrp_speed_east_fps = -me.init_rel_vec[1]; me.ccrp_speed_north_fps = me.init_rel_vec[0]; } - + me.ccrp_t = 0.0; - + me.ccrp_altC = me.ccrp_agl; me.ccrp_vel_z = -me.ccrp_speed_down_fps*FT2M;#positive upwards me.ccrp_fps_z = -me.ccrp_speed_down_fps; @@ -977,11 +978,11 @@ var AIM = { me.ccrp_q = 0.5 * me.ccrp_rho * me.ccrp_fps_x * me.ccrp_fps_x; me.ccrp_deacc = (me.ccrp_Cd * me.ccrp_q * me.ref_area_sqft) / me.ccrp_mass; me.ccrp_acc = -me.ccrp_deacc * FT2M; - + me.ccrp_fps_x_final = me.ccrp_t*me.ccrp_acc+me.ccrp_fps_x;# calc final horz speed me.ccrp_fps_x_average = (me.ccrp_fps_x-(me.ccrp_fps_x-me.ccrp_fps_x_final)*0.5); me.ccrp_mach_average = me.ccrp_fps_x_average / me.ccrp_rs[1]; - + me.ccrp_Cd = me.drag(me.ccrp_mach_average); me.ccrp_q = 0.5 * me.ccrp_rho * me.ccrp_fps_x_average * me.ccrp_fps_x_average; me.ccrp_deacc = (me.ccrp_Cd * me.ccrp_q * me.ref_area_sqft) / me.ccrp_mass; @@ -994,15 +995,15 @@ var AIM = { # we calc heading from composite speeds, due to alpha and beta might influence direction bombs will fall: me.ccrp_heading = geo.normdeg(math.atan2(me.ccrp_speed_east_fps,me.ccrp_speed_north_fps)*R2D); me.ccrpPos.apply_course_distance(me.ccrp_heading, me.ccrp_dist); - + #printf("Will fall %0.1f NM ahead of aircraft.", me.dist*M2NM); me.ccrp_elev = me.ccrp_alti-me.ccrp_agl;#faster me.ccrpPos.set_alt(me.ccrp_elev); - + me.ccrp_distCCRP = me.ccrpPos.distance_to(me.Tgt.get_Coord()); return me.ccrp_distCCRP; }, - + getCCIPadv: func (maxFallTime_sec, timeStep) { # for non flat areas. Lower falltime or higher timestep means using less CPU time. # returns nil for higher than maxFallTime_sec. Else a vector with [Coord, hasTimeToArm]. @@ -1021,7 +1022,7 @@ var AIM = { me.ccip_speed_east_fps = -me.init_rel_vec[1]; me.ccip_speed_north_fps = me.init_rel_vec[0]; } - + me.ccip_t = 0.0; me.ccip_dt = timeStep; me.ccip_fps_z = -me.ccip_speed_down_fps; @@ -1033,7 +1034,7 @@ var AIM = { me.ccip_mass = me.ccip_bomb.weight_launch_lbm * LBM2SLUGS; me.ccipPos = geo.Coord.new(geo.aircraft_position()); - + # we calc heading from composite speeds, due to alpha and beta might influence direction bombs will fall: if(me.ccip_fps_x == 0) return nil; me.ccip_heading = geo.normdeg(math.atan2(me.ccip_speed_east_fps,me.ccip_speed_north_fps)*R2D); @@ -1051,20 +1052,20 @@ var AIM = { me.ccip_Cd = me.ccip_bomb.drag(me.ccip_mach); me.ccip_deacc = (me.ccip_Cd * me.ccip_q * me.ccip_bomb.ref_area_sqft) / me.ccip_mass; me.ccip_fps -= me.ccip_deacc*me.ccip_dt; - + # new components and pitch me.ccip_fps_z = me.ccip_fps*math.sin(me.ccip_pitch); me.ccip_fps_x = me.ccip_fps*math.cos(me.ccip_pitch); me.ccip_fps_z -= g_fps * me.ccip_dt; me.ccip_pitch = math.atan2(me.ccip_fps_z, me.ccip_fps_x); - + # new position me.ccip_altC = me.ccip_altC + me.ccip_fps_z*me.ccip_dt*FT2M; me.ccip_dist = me.ccip_fps_x*me.ccip_dt*FT2M; me.ccip_oldPos = geo.Coord.new(me.ccipPos); me.ccipPos.apply_course_distance(me.ccip_heading, me.ccip_dist); me.ccipPos.set_alt(me.ccip_altC); - + # test terrain me.ccip_grnd = geo.elevation(me.ccipPos.lat(),me.ccipPos.lon()); if (me.ccip_grnd != nil) { @@ -1080,11 +1081,11 @@ var AIM = { } } else { return nil; - } + } } return nil; }, - + getTerrain: func (from, to) { me.xyz = {"x":from.x(), "y":from.y(), "z":from.z()}; me.dir = {"x":to.x()-from.x(), "y":to.y()-from.y(), "z":to.z()-from.z()}; @@ -1096,7 +1097,7 @@ var AIM = { } return nil; }, - + getCCIPsimple: func (maxFallTime_sec, timeStep) { #faster, but only works over level ground me.ccip_agl = getprop("position/altitude-agl-ft")*FT2M; @@ -1118,7 +1119,7 @@ var AIM = { me.ccip_speed_east_fps = -me.init_rel_vec[1]; me.ccip_speed_north_fps = me.init_rel_vec[0]; } - + me.ccip_t = 0.0; me.ccip_dt = timeStep; me.ccip_altC = me.ccip_agl; @@ -1151,11 +1152,11 @@ var AIM = { me.ccip_q = 0.5 * me.ccip_rho * me.ccip_fps_x * me.ccip_fps_x; me.ccip_deacc = (me.ccip_Cd * me.ccip_q * me.ccip_bomb.ref_area_sqft) / me.ccip_mass; me.ccip_acc = -me.ccip_deacc * FT2M; - + me.ccip_fps_x_final = me.ccip_t*me.ccip_acc+me.ccip_fps_x;# calc final horz speed me.ccip_fps_x_average = (me.ccip_fps_x-(me.ccip_fps_x-me.ccip_fps_x_final)*0.5); me.ccip_mach_average = me.ccip_fps_x_average / me.ccip_rs[1]; - + me.ccip_Cd = me.ccip_bomb.drag(me.ccip_mach_average); me.ccip_q = 0.5 * me.ccip_rho * me.ccip_fps_x_average * me.ccip_fps_x_average; me.ccip_deacc = (me.ccip_Cd * me.ccip_q * me.ccip_bomb.ref_area_sqft) / me.ccip_mass; @@ -1168,7 +1169,7 @@ var AIM = { # we calc heading from composite speeds, due to alpha and beta might influence direction bombs will fall: me.ccip_heading = geo.normdeg(math.atan2(me.ccip_speed_east_fps,me.ccip_speed_north_fps)*R2D); me.ccipPos.apply_course_distance(me.ccip_heading, me.ccip_dist); - + me.ccip_elev = me.ccip_alti-me.ccip_agl;#faster me.ccipPos.set_alt(me.ccip_elev); return [me.ccipPos,me.arming_time=90?me.angleFromRight:-me.angleFromRight;#0 is up, 180 normalized - me.angleInHUD = geo.normdeg180(me.angleInHUD-OurRoll.getValue()); - me.degOnHUD = math.atan2(vector.Math.magnitudeVector(me.idHere),me.Tgt.get_range()*NM2M)*R2D;#deg/sec - #printf("ASC: %.1f deg loft. Inv trigo angle: %d deg. Lead %.1f deg. atan2(%d,%d)", me.idealLoft, me.angleInHUD, me.degOnHUD,vector.Math.magnitudeVector(me.idHere),me.Tgt.get_range()*NM2M); - return [me.angleInHUD, me.degOnHUD];# 0,0 is center of ASEC. Thats the origin of ASC (Attack Steering Cue) + + me.asc_av_spd_mps = 900;# should be per missile + + me.horiz_intercept = me.get_intercept(me.Tgt.get_bearing(), me.Tgt.get_range()*NM2M, me.Tgt.get_heading(), me.Tgt.get_Speed()*KT2MPS, me.asc_av_spd_mps, geo.aircraft_position(), OurHdg.getValue()); + # [me.timeToIntercept, me.interceptHeading, me.interceptCoord, me.interceptDist, me.interceptRelativeBearing + if(me.horiz_intercept == nil) { + #print("No aim120 intercept"); + return nil; + } + + me.loft_cue = 0; + if (me["dlz_opt"] != nil and me["dlz_nez"] != nil) { + me.tgtRange = me.Tgt.get_range(); + me.loft_cue = me.map(me.Tgt.get_range(), me.dlz_nez, (me.dlz_opt+me.max_fire_range_nm)*0.5, 0, 15); + me.loft_cue *= me.map(ourAlt.getValue(), 0, 40000, 3, 1); + } + + # Do not find a relative bearing that is so great that radar loses track of target: + me.maxBearing = me.fcs_fov - 10;# margin is 10 degrees + me.relativeBearing = geo.normdeg180(me.horiz_intercept[1]-me.Tgt.get_bearing()); + if (me.relativeBearing > me.maxBearing) { + me.horiz_intercept[1] = me.Tgt.get_bearing() + me.maxBearing; + } elsif (me.relativeBearing < -me.maxBearing) { + me.horiz_intercept[1] = me.Tgt.get_bearing() - me.maxBearing; + } + + return [me.horiz_intercept[1], math.clamp(me.loft_cue, 0, 45)]; # attack-bearing, loft-cue + }, + + get_intercept: func (bearingToRunner, dist_m, runnerHeading, runnerSpeed, chaserSpeed, chaserCoord, chaserHeading) { + # from Leto + # needs: bearingToRunner_deg, dist_m, runnerHeading_deg, runnerSpeed_mps, chaserSpeed_mps, chaserCoord + # dist_m > 0 and chaserSpeed > 0 + + if (dist_m < 500) { + return nil; + } + + me.trigAngle = 90-bearingToRunner; + me.RunnerPosition = [dist_m*math.cos(me.trigAngle*D2R), dist_m*math.sin(me.trigAngle*D2R),0]; + me.ChaserPosition = [0,0,0]; + + me.VectorFromRunner = vector.Math.minus(me.ChaserPosition, me.RunnerPosition); + me.runner_heading = 90-runnerHeading; + me.RunnerVelocity = [runnerSpeed*math.cos(me.runner_heading*D2R), runnerSpeed*math.sin(me.runner_heading*D2R),0]; + + me.a = chaserSpeed * chaserSpeed - runnerSpeed * runnerSpeed; + me.b = 2 * vector.Math.dotProduct(me.VectorFromRunner, me.RunnerVelocity); + me.c = -dist_m * dist_m; + + if ((me.b*me.b-4*me.a*me.c)<0) { + # intercept not possible + return nil; + } + + me.t1 = (-me.b+math.sqrt(me.b*me.b-4*me.a*me.c))/(2*me.a); + me.t2 = (-me.b-math.sqrt(me.b*me.b-4*me.a*me.c))/(2*me.a); + + if (me.t1 < 0 and me.t2 < 0) { + # intercept not possible + return nil; + } + + me.timeToIntercept = 0; + if (me.t1 > 0 and me.t2 > 0) { + me.timeToIntercept = math.min(me.t1, me.t2); + } else { + me.timeToIntercept = math.max(me.t1, me.t2); + } + me.InterceptPosition = vector.Math.plus(me.RunnerPosition, vector.Math.product(me.timeToIntercept, me.RunnerVelocity)); + + me.ChaserVelocity = vector.Math.product(1/me.timeToIntercept, vector.Math.minus(me.InterceptPosition, me.ChaserPosition)); + + me.interceptAngle = vector.Math.angleBetweenVectors([0,1,0], me.ChaserVelocity); + me.interceptHeading = geo.normdeg(me.ChaserVelocity[0]<0?-me.interceptAngle:me.interceptAngle); + + me.interceptDist = chaserSpeed*me.timeToIntercept; + + me.interceptCoord = geo.Coord.new(chaserCoord); + me.interceptCoord = me.interceptCoord.apply_course_distance(me.interceptHeading, me.interceptDist); + me.interceptRelativeBearing = geo.normdeg180(me.interceptHeading-chaserHeading); + + return [me.timeToIntercept, me.interceptHeading, me.interceptCoord, me.interceptDist, me.interceptRelativeBearing]; }, setContacts: func (vect) { @@ -1250,17 +1318,21 @@ var AIM = { commandDir: func (heading_deg, pitch_deg) { # commands are relative to aircraft bore if (me.status == MISSILE_FLYING) return; - me.command_dir_heading = heading_deg; - me.command_dir_pitch = pitch_deg; - me.slave_to_radar = FALSE; + if (vector.Math.angleBetweenVectors(vector.Math.eulerToCartesian2(-heading_deg, pitch_deg), [1,0,0]) <= me.fcs_fov) { + me.command_dir_heading = heading_deg; + me.command_dir_pitch = pitch_deg; + me.slave_to_radar = FALSE; + } me.printCode("Bore/dir command: heading %0.1f pitch %0.1f", heading_deg, pitch_deg); }, commandRadar: func (idle_heading = 0, idle_elevation = 0) { # command that radar is looking at a target, slave to that. if (!me.seam_support or me.status == MISSILE_FLYING) return; - me.command_dir_heading = idle_heading; - me.command_dir_pitch = idle_elevation; + if (me.status != MISSILE_LOCK) { + me.command_dir_heading = idle_heading; + me.command_dir_pitch = idle_elevation; + } me.slave_to_radar = TRUE; me.printCode("Slave radar command. Idle: heading %0.1f pitch %0.1f", idle_heading, idle_elevation); }, @@ -1372,7 +1444,7 @@ var AIM = { me.status = MISSILE_FLYING; if (vect!= nil) { - + # sets a vector of contacts the weapons will try to lock onto # For LOAL weapons. # see also setContacts() @@ -1381,7 +1453,7 @@ var AIM = { me.contacts = []; } me.launchSoundProp.setBoolValue(FALSE); - + if (me.engineEnabled and me.stage_1_duration > 0 and me.force_lbf_1 > 0 and me.drop_time < 1.75) { me.pendingLaunchSound = me.drop_time; } elsif (me.drop_time != 10000 and (!me.engineEnabled or me.stage_1_duration == 0 or me.force_lbf_1 == 0)) { @@ -1411,7 +1483,7 @@ var AIM = { # me.rail_head_deg = me.Tgt.get_bearing()-OurHdg.getValue(); #} me.railvec = vector.Math.eulerToCartesian3X(-me.rail_head_deg, me.rail_pitch_deg,0); - me.veccy = vector.Math.yawPitchRollVector(-ac_hdg,ac_pitch,ac_roll,me.railvec); + me.veccy = vector.Math.rollPitchYawVector(ac_roll,ac_pitch,-ac_hdg,me.railvec); me.carty = vector.Math.cartesianToEuler(me.veccy); me.msl_pitch = me.carty[1]; me.defaultHeading = me.Tgt != nil?me.Tgt.get_bearing():0;#90 deg tubes align to target heading, else north @@ -1442,26 +1514,19 @@ var AIM = { init_coord.set_xyz(pos.x, pos.y, pos.z); } else { init_coord = me.getGPS(me.x, me.y, me.z, ac_pitch, ac_hdg); - } + } # Set submodel initial position: var mlat = init_coord.lat(); var mlon = init_coord.lon(); var malt = init_coord.alt() * M2FT; - + me.coord = geo.Coord.new(init_coord); # Get target position. if (me.Tgt != nil) { - if (me.inacc) { - if (!me.target_pnt) { - me.Tgt.inac_x = 0; - me.Tgt.inac_y = 0; - me.Tgt.inac_z = 0; - } - if (me.Tgt == contactPoint) { - me.usingTGPPoint = 1; - } + if (me.Tgt == contactPoint) { + me.usingTGPPoint = 1; } me.t_coord = me.Tgt.get_Coord(); me.maddog = FALSE; @@ -1658,7 +1723,7 @@ var AIM = { if (me.guidanceLaw == "direct") { nav = "Pure pursuit." } elsif (me.guidanceLaw == "OPN") { - nav = "Original Proportional navigation. Proportionality constant is "~me.pro_constant; + nav = "Original Proportional navigation. Proportionality constant is "~me.pro_constant; } elsif (me.guidanceLaw == "PN") { nav = "Proportional navigation. Proportionality constant is "~me.pro_constant; } elsif (me.guidanceLaw == "APN") { @@ -1689,7 +1754,7 @@ var AIM = { vector = "Vectored thrust." } - + me.printStats("****************************************************"); me.printStats("Stats for %s", me.typeLong); me.printStats("Damage ID is %d, notification ID is %d, unique ID is %d", me.typeID, me.typeID+21,me.unique_id); @@ -1697,7 +1762,7 @@ var AIM = { me.printStats("Fire range %.1f-%.1f NM", me.min_fire_range_nm, me.max_fire_range_nm); if (me.expand_min) { me.printStats("Min fire range will expand with closing rate."); - } + } me.printStats("Can be fired againts %s targets", classes); me.printStats("Pilot will call out %s when firing.",me.brevity); me.printStats("Launch platform detection field of view is +-%d degrees.",me.fcs_fov); @@ -1770,7 +1835,7 @@ var AIM = { } else { me.printStats("Weapon will not snap up, follow terrain or sea skim."); } - + me.printStats("After propulsion ends, it will max steer up to %d degree pitch.",me.maxPitch); if(me.Tgt == nil) { me.printStats("Note: Ordnance was released with no lock or destination target."); @@ -1892,7 +1957,7 @@ var AIM = { } elsif (me.guidance == "heat") { me.printStats("Resistance to flares is %d%%.",me.flareResistance*100); } - } + } me.printStats("MISC:"); if (me.data) { me.printStats("Will transmit telemetry data back to launch platform."); @@ -1907,10 +1972,10 @@ var AIM = { } me.printStats("****************************************************"); }, - + setNewTargetInFlight: func (tagt) { me.Tgt = tagt; - me.callsign = tagt==nil?"Unknown":me.Tgt.get_Callsign(); + me.callsign = tagt==nil?"Unknown":damage.processCallsign(me.Tgt.get_Callsign()); me.printStatsDetails("Target set to %s", me.callsign); me.newTargetAssigned = tagt==nil?FALSE:TRUE; me.t_coord = tagt==nil?nil:me.Tgt.get_Coord(); @@ -1920,13 +1985,6 @@ var AIM = { me.semiLostLock = FALSE; me.heatLostLock = FALSE; me.hasGuided = FALSE; - if (me.inacc) { - if (!me.target_pnt and tagt!=nil) { - me.Tgt.inac_x = 0; - me.Tgt.inac_y = 0; - me.Tgt.inac_z = 0; - } - } }, flight: func { @@ -1957,7 +2015,7 @@ var AIM = { } me.elapsed = systime(); - + me.dt = (me.elapsed - me.elapsed_last)*speedUp.getValue(); me.elapsed_last = me.elapsed; @@ -1970,15 +2028,15 @@ var AIM = { me.life_time += me.dt; me.handleMidFlightFunc(); - + if (me.hasGuided and me.maddog) { me.maddog = FALSE; me.printStats("Maddog stage over, guided at "~me.callsign); } - + if (me.guidanceEnabled and me.free == FALSE and !me.newTargetAssigned and (me.canSwitch or (me.loal and me.maddog)) and size(me.contacts) > 0 and (me.dist_curr_direct==-1 or me.dist_curr_direct>me.reportDist)) { # me.reaquire must also be enabled for me.canSwitch to work - + if (me.Tgt==nil or me.hasGuided == FALSE or (me.canSwitch and (me.fovLost or me.lostLOS or me.radLostLock or me.semiLostLock or me.heatLostLock)) and me.life_time > me.nextFovCheck) { # test next contact me.numberContacts = size(me.contacts); @@ -1994,7 +2052,7 @@ var AIM = { me.newTargetAssigned = FALSE; } else { me.setNewTargetInFlight(me.newTgt); - } + } } } @@ -2016,7 +2074,7 @@ var AIM = { me.callsign = "Unknown"; me.newTargetAssigned = FALSE; } - } elsif (me.inacc and me.Tgt != nil and me.Tgt.get_type() == POINT and me.guidance == "laser" and me.usingTGPPoint and contactPoint == nil) { + } elsif (me.Tgt != nil and me.Tgt.get_type() == POINT and me.guidance == "laser" and me.usingTGPPoint and contactPoint == nil) { # if laser illuminated lock is lost on a targetpod target: if (!(me.canSwitch and me.reaquire)) { me.Tgt = nil; @@ -2027,7 +2085,7 @@ var AIM = { me.callsign = "Unknown"; me.newTargetAssigned = FALSE; } - } elsif (me.inacc and me.Tgt == nil and me.guidance == "laser" and me.usingTGPPoint and contactPoint != nil) { + } elsif (me.Tgt == nil and me.guidance == "laser" and me.usingTGPPoint and contactPoint != nil) { # see if we can regain lock on new laser spot: if (me.newLock(contactPoint)) { me.setNewTargetInFlight(contactPoint); @@ -2046,17 +2104,17 @@ var AIM = { me.deploy = me.clamp(me.extrapolate(me.life_time, me.rail_passed_time, me.rail_passed_time+me.deploy_time,0,1),0,1); } me.deploy_prop.setDoubleValue(me.deploy); - + me.thrust_lbf = me.thrust();# pounds force (lbf) - + # Get total old speed, thats what we will use in next loop. me.old_speed_horz_fps = math.sqrt((me.speed_east_fps*me.speed_east_fps)+(me.speed_north_fps*me.speed_north_fps)); me.old_speed_fps = math.sqrt((me.old_speed_horz_fps*me.old_speed_horz_fps)+(me.speed_down_fps*me.speed_down_fps)); me.setRadarProperties(me.old_speed_fps); - + # Get air density and speed of sound (fps): me.rs = me.rho_sndspeed(me.altN.getValue() + me.density_alt_diff); @@ -2086,13 +2144,13 @@ var AIM = { me.Cd = me.drag(me.speed_m,me.myG); me.speed_change_fps = me.speedChange(me.thrust_lbf, me.rho, me.Cd); - + if (me.last_dt != 0) { me.speed_change_fps = me.speed_change_fps + me.energyBleed(me.g, me.altN.getValue() + me.density_alt_diff); } - + ################### #### Guidance.##### @@ -2139,7 +2197,7 @@ var AIM = { me.prevETA = nil; } } - me.observing = me.guidance; + me.observing = me.guidance; } elsif (me.guidance != "unguided" and (me.rail == FALSE or me.rail_passed == TRUE) and me.guidanceEnabled and me.free == FALSE and me.t_coord == nil and (me.newTargetAssigned or (me.canSwitch and (me.fovLost or me.lostLOS or me.radLostLock or me.semiLostLock or me.heatLostLock) or (me.loal and me.maddog)))) { # check for too low speed not performed on purpuse, difference between flying straight on A/P and making manouvres. @@ -2212,11 +2270,11 @@ var AIM = { # missile still on rail, lets calculate its speed relative to the wind coming in from the aircraft nose. me.rail_speed_into_wind = me.rail_speed_into_wind + me.speed_change_fps; } else { - # gravity acc makes the weapon pitch down + # gravity acc makes the weapon pitch down me.pitch = math.atan2(-me.speed_down_fps, me.speed_horizontal_fps ) * R2D; } - + if (me.rail == TRUE and me.rail_passed == FALSE) { me.u = noseAir.getValue();# airstream from nose #var v = getprop("velocities/vBody-fps");# airstream from side @@ -2228,7 +2286,7 @@ var AIM = { me.hdg = OurHdg.getValue(); } else { me.railvec = me.myMath.eulerToCartesian3X(-me.rail_head_deg, me.rail_pitch_deg,0); - me.veccy = me.myMath.yawPitchRollVector(-OurHdg.getValue(),OurPitch.getValue(),OurRoll.getValue(),me.railvec); + me.veccy = me.myMath.rollPitchYawVector(OurRoll.getValue(),OurPitch.getValue(),-OurHdg.getValue(),me.railvec); me.carty = me.myMath.cartesianToEuler(me.veccy); me.defaultHeading = me.Tgt != nil?me.Tgt.get_bearing():0;#90 deg tubes align to target heading, else north me.pitch = me.carty[1]; @@ -2236,9 +2294,9 @@ var AIM = { } me.speed_on_rail = math.max(me.rail_speed_into_wind - me.opposing_wind, 0); - + me.movement_on_rail = me.speed_on_rail * me.dt; - + me.rail_pos = me.rail_pos + me.movement_on_rail; if (me.rail_forward == TRUE) { me.x = me.x - (me.movement_on_rail * FT2M);# negative cause positive is rear in body coordinates @@ -2311,7 +2369,7 @@ var AIM = { #me.t_coord.set_alt(me.flare_alt_ft * FT2M); } } - + # performance logging: # #var q = 0.5 * rho * me.old_speed_fps * me.old_speed_fps; @@ -2385,8 +2443,8 @@ var AIM = { # check stats while flying: # me.printFlight("Mach %04.2f , time %05.1f s , thrust %05.1f lbf , G-force %05.2f", me.speed_m, me.life_time, me.thrust_lbf, me.g); - me.printFlight("Alt %07.1f ft , direct distance to target %04.1f NM", me.alt_ft, (me.Tgt!=nil and me.direct_dist_m!=nil)?me.direct_dist_m*M2NM:-1); - + me.printFlight("Alt %07.1f ft , direct distance to target %04.1f NM", me.alt_ft, (me.Tgt!=nil and me.direct_dist_m!=nil)?me.direct_dist_m*M2NM:-1); + 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); @@ -2412,14 +2470,14 @@ var AIM = { if (me.Tgt == nil and me.rail == TRUE and me.rail_pitch_deg==90 and me.rail_passed == FALSE) { #for ejection seat to be oriented correct, wont be ran for missiles with target such as the frigate. - + # model of seat is loaded such that face is pointing in -Z FG coords. - + # Get the vector pointing up from aircraft var a = me.myMath.eulerToCartesian3Z(-OurHdg.getValue(), OurPitch.getValue(), OurRoll.getValue()); #printf("HPR: %0.4f %0.4f %0.4f",OurHdg.getValue(),OurPitch.getValue(),OurRoll.getValue()); #printf(" UP: %0.4f %0.4f %0.4f",a[0],a[1],a[2]); - + # get the pitch and heading of that vector var euler = me.myMath.cartesianToEuler(a); me.pitch = euler[1]; @@ -2431,19 +2489,19 @@ var AIM = { me.hdg = OurHdg.getValue(); } me.hdgN.setDoubleValue(me.hdg); - + # get vector pointing out from aircraft nose var nose = me.myMath.eulerToCartesian3X(-OurHdg.getValue(), OurPitch.getValue(), OurRoll.getValue()); - + # get vector pointing perpendicular to seat travel direction (most upward possible) var face = me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, 0); - + # convert to angle pointing out from face if pilot turns head without nodding and looks downward face = me.myMath.product(-1,face); - + # get angle between face and aircraft-nose vectors var turnFace = me.myMath.angleBetweenVectors(face,nose); - + if (me.myMath.angleBetweenVectors(nose,me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, turnFace)))>me.myMath.angleBetweenVectors(nose,me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, -turnFace)))) { # if angle between aircraft nose and the face of pilot is greater than the roll of seat then the roll should be opposite #print("looking1 "~me.myMath.cartesianToEuler(me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, turnFace)))[0]); @@ -2452,7 +2510,7 @@ var AIM = { } else { #turnFace *= -1; } - + # roll the seat so pilot faces forward me.rollN.setDoubleValue(turnFace); #print("looking2 "~me.myMath.cartesianToEuler(me.myMath.product(-1,me.myMath.eulerToCartesian3Z(-me.hdg, me.pitch, turnFace)))[0]); @@ -2477,7 +2535,7 @@ var AIM = { # 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 @@ -2506,7 +2564,7 @@ var AIM = { 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; @@ -2514,7 +2572,7 @@ var AIM = { 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); @@ -2529,7 +2587,7 @@ var AIM = { } me.prevETA = me["eta"]; } - + if (me.life_time - me.last_noti > me.noti_time and getprop("payload/armament/msg")) { # notify in flight using Emesary. me.last_noti = me.life_time; @@ -2540,12 +2598,15 @@ var AIM = { } me.last_dt = me.dt; - + me.prevGuidance = me.guidance; - - if (me.counter > -1) { + + if (me.counter > -1 and !me.ai.getNode("valid").getBoolValue()) { # TODO: Why is this placed so late? Don't remember. me.ai.getNode("valid").setBoolValue(1); + thread.lock(mutexTimer); + append(AIM.timerQueue, [me, me.setModelAdded, [], -1]); + thread.unlock(mutexTimer); } ############################################################################################################# # @@ -2555,21 +2616,21 @@ var AIM = { # # # - ############################################################################################################# + ############################################################################################################# } }, handleMidFlightFunc: func { if(me.mfFunction != nil) { - - me.settings = me.mfFunction({ time_s: me.life_time, + + me.settings = me.mfFunction({ time_s: me.life_time, dist_m: me.dist_curr_direct, - mach: me.speed_m, - weapon_position: me.coord, - guidance: me.guidance, - seeker_detect_range: me.detect_range_curr_nm, - seeker_fov: me.max_seeker_dev, - weapon_pitch: me.pitch, + mach: me.speed_m, + weapon_position: me.coord, + guidance: me.guidance, + seeker_detect_range: me.detect_range_curr_nm, + seeker_fov: me.max_seeker_dev, + weapon_pitch: me.pitch, weapon_heading: me.hdg, callsign: me.callsign, deviation_deg: me["fov_radial"], @@ -2635,7 +2696,7 @@ var AIM = { me.ac_roll = roll; } me.ac_pitch = pitch; - + if (head == nil) { me.ac_hdg = OurHdg.getValue(); } else { @@ -2678,7 +2739,7 @@ var AIM = { me.mlat = me.ac.lat() + me.out[0]; me.mlon = me.ac.lon() + me.out[1]; me.malt = (me.ac.alt() * M2FT) + me.out[2]; - + me.c = geo.Coord.new(); me.c.set_latlon(me.mlat, me.mlon, me.malt * FT2M); @@ -2687,7 +2748,7 @@ var AIM = { drag: func (mach, N=nil) { # Nikolai V. Chr.: Made the drag calc more in line with big missiles as opposed to small bullets. - # + # # The old equations were based on curves for a conventional shell/bullet (no boat-tail), # and derived from Davic Culps code in AIBallistic. me.Cd0 = 0; @@ -2704,7 +2765,7 @@ var AIM = { me.Cd0 = (0.2965 * math.pow(mach, -2.1506) + 0.073766412) * 8 * (me.Cd_base+me.Cd_delta*me.deploy); } } - + if (!me.simple_drag) { if (me.vector_thrust and me.thrust_lbf>0) N=N*0.35; if (mach < 1.1) { @@ -2765,12 +2826,12 @@ var AIM = { me.thrust_lbf = 0; } } - + #me.force_cutoff_s = 0;# seen charts of real (aim9m) that thrust dont stop instantly, but fades out. This term would say hwo long it takes to fade out. Need to rework fuel consumption first. Maybe in future. #if (me.life_time > (me.drop_time + me.stage_1_duration + me.stage_2_duration-me.force_cutoff_s) and me.life_time < (me.drop_time + me.stage_1_duration + me.stage_2_duration)) { # me.thrust_lbf = me.extrapolate(me.life_time - (me.drop_time + me.stage_1_duration + me.stage_2_duration - me.force_cutoff_s),0,me.force_cutoff_s,me.thrust_lbf,0); #} - + if (me.thrust_lbf < 1) { me.smoke_prop.setBoolValue(0); } else { @@ -2783,7 +2844,7 @@ var AIM = { # Calculate speed change from last update. # # Acceleration = thrust/mass - drag/mass; - + me.acc = thrust_lbf / me.mass; me.q = 0.5 * rho * me.old_speed_fps * me.old_speed_fps;# dynamic pressure me.drag_acc = (Cd * me.q * me.ref_area_sqft) / me.mass; @@ -2839,7 +2900,7 @@ var AIM = { bleed0at25g: func () { me.loss_mps = 0 + ((me.last_dt - 0)/(7 - 0))*(-750 - 0); return me.loss_mps*M2FT; - }, + }, setFirst: func() { if (me.smoke_prop.getValue() == TRUE) { @@ -2880,10 +2941,10 @@ var AIM = { if(me.myG > me.max_g_current) { me.MyCoef = me.overload_limiter(me.hdg, me.pitch, me.track_signal_e, me.track_signal_h, me.old_speed_fps, me.dt, me.max_g_current); - + me.track_signal_h = me.MyCoef[0]; me.track_signal_e = me.MyCoef[1]; - + me.myGnew = me.steering_speed_G(me.hdg, me.pitch, me.track_signal_e, me.track_signal_h, me.old_speed_fps, me.dt); #me.printFlight(sprintf("G2 %.2f", myG)~sprintf(" - Coeff %.2f", MyCoef)); if (me.limitGs == FALSE) { @@ -2952,7 +3013,7 @@ var AIM = { # # navigation and guidance # - + me.raw_steer_signal_elev = 0; me.raw_steer_signal_head = 0; @@ -2970,7 +3031,7 @@ var AIM = { me.curr_deviation_h = me.t_course - me.hdg; #var (t_course, me.dist_curr) = courseAndDistance(me.coord, me.t_coord); - #me.dist_curr = me.dist_curr * NM2M; + #me.dist_curr = me.dist_curr * NM2M; me.curr_deviation_h = geo.normdeg180(me.curr_deviation_h); @@ -2979,7 +3040,7 @@ var AIM = { me.printFlightDetails("Altitude above launch platform = %07.1f ft", M2FT * (me.coord.alt()-me.ac.alt())); me.printFlightDetails("Altitude. Target %07.1f. Missile %07.1f. Atan2 %04.1f degs", me.t_coord.alt()*M2FT, me.coord.alt()*M2FT, math.atan2( me.t_coord.alt()-me.coord.alt(), me.dist_curr ) * R2D); - + if (math.abs(me.curr_deviation_h) < 15) { me.guidanceLawHorizInit = 0; @@ -3075,8 +3136,9 @@ 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.chaffLock = rand() < (1-me.chaffResistance - ((1-me.chaffResistance) * 0.5 * me.aspectDeg));# 50% less chance to be fooled if front aspect + me.semi = me.guidance == "semi-radar"?0.5: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) { me.printStats(me.type~": Missile locked on chaff from "~me.callsign); @@ -3161,7 +3223,7 @@ var AIM = { } else { me.printStats(me.type~": Not guiding (lost radar reflection, 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; @@ -3173,10 +3235,10 @@ var AIM = { } else { me.printStats(me.type~": Not guiding (lost radiation, gave up)"); me.free = TRUE; - } + } } elsif ((me.dist_curr_direct*M2NM > me.detect_range_curr_nm or !me.FOV_check(me.hdg, me.pitch, me.curr_deviation_h, me.curr_deviation_e, me.max_seeker_dev, me.myMath)) and me.guidance != "gps" and me.guidance != "inertial") { # target is not in missile seeker view anymore - + if (me.fovLost == FALSE and me.detect_range_curr_nm != 0) { me.normFOV = me.FOV_check_norm(me.hdg, me.pitch, me.curr_deviation_h, me.curr_deviation_e, me.max_seeker_dev, me.myMath); me.printStats(me.type~": "~me.callsign~" is not in seeker view. (%d%% in view, %d%% in range)", me.normFOV*100, 100*me.dist_curr_direct*M2NM / me.detect_range_curr_nm);#~me.viewLost); @@ -3239,7 +3301,7 @@ var AIM = { canSeekerKeepUp: func () { me.globalVectorToTarget = me.myMath.eulerToCartesian3X(-me.t_course, me.t_elev_deg, 0); - me.localVectorTarget = me.myMath.rollPitchYawVector(0, -me.pitch, me.hdg, me.globalVectorToTarget); + me.localVectorTarget = me.myMath.yawPitchVector(me.hdg, -me.pitch, me.globalVectorToTarget); if (me.counter == me.counter_last+1 and !me.newTargetAssigned and me["localVectorSeeker"] != nil and (me.guidance == "heat" or me.guidance == "vision") and me.prevGuidance == me.guidance and me.prevTarget == me.Tgt) { # calculate if the seeker can keep up with the angular change of the target # @@ -3247,11 +3309,11 @@ var AIM = { # if (!me.caged) { # Gyro is stabilized - me.localVectorSeeker = me.myMath.rollPitchYawVector(0, -me.last_track_e, me.last_track_h, me.localVectorSeeker); + me.localVectorSeeker = me.myMath.yawPitchVector(me.last_track_h, -me.last_track_e, me.localVectorSeeker); } me.angleSeekerToTarget = me.myMath.angleBetweenVectors(me.localVectorSeeker, me.localVectorTarget); me.deviation_per_sec = me.angleSeekerToTarget/me.dt; - + if (me.deviation_per_sec > me.angular_speed) { if (me.angleSeekerToTarget < me.beam_width_deg) { me.max_seekertrack = me.angular_speed * me.dt; @@ -3284,7 +3346,7 @@ var AIM = { me.cruise_or_loft = FALSE;# If true then this method handles the vertical component of guiding. me.time_before_snap_up = me.drop_time * 3; me.limitGs = FALSE; - + if(me.loft_alt != 0 and me.snapUp == FALSE) { # this is for Air to ground/sea cruise-missile (SCALP, Sea-Eagle, Taurus, Tomahawk, RB-15...) @@ -3355,7 +3417,7 @@ var AIM = { howmany = howmany + 1; #We finalize the vector dir = {"x":me.geoPlus4.x()-me.coord.x(), "y":me.geoPlus4.y()-me.coord.y(), "z":me.geoPlus4.z()-me.coord.z()}; - #We measure distance to be sure that the ground intersection is closer than geoPlus4 + #We measure distance to be sure that the ground intersection is closer than geoPlus4 distance_Target = me.coord.direct_distance_to(me.geoPlus4); # Check for terrain between own aircraft and other: GroundIntersectResult = get_cart_ground_intersection(xyz, dir); @@ -3458,7 +3520,7 @@ var AIM = { me.printGuideDetails("Moving up"); me.raw_steer_signal_elev = -me.pitch + math.atan2(me.t_alt_delta_ft, me.old_speed_fps * me.dt * 5) * R2D; } else { - # that means a dive angle of 22.5° (a bit less + # that means a dive angle of 22.5° (a bit less # coz me.alt is in feet) (I let this alt in feet on purpose (more this figure is low, more the future pitch is high) me.printGuideDetails("Moving down"); me.slope = me.clamp(me.t_alt_delta_ft / 300, -7.5, 0);# the lower the desired alt is, the steeper the slope. @@ -3481,10 +3543,10 @@ var AIM = { # me.rotate_token = TRUE; # me.printGuide("Is last turn, snap-up/PN takes it from here..") # } - } elsif (me.snapUp == TRUE and me.t_elev_deg > me.clamp(-80/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 6 + } elsif (me.snapUp == TRUE and me.t_elev_deg > me.clamp(-50/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 6 and me.t_elev_deg < me.loft_angle #and me.t_elev_deg > -7.5 and me.dive_token == FALSE) { - # lofting: due to target is more than 10 miles out and we havent reached + # lofting: due to target is more than 10 miles out and we havent reached # our desired cruising alt, and the elevation to target is less than lofting angle. # The -7.5 limit, is so the seeker don't lose track of target when lofting. if (me.life_time < me.time_before_snap_up and me.coord.alt() * M2FT < me.loft_alt) { @@ -3500,7 +3562,7 @@ var AIM = { } me.cruise_or_loft = TRUE; } elsif (me.snapUp == TRUE and me.coord.alt() > me.t_coord.alt() and me.last_cruise_or_loft == TRUE - and me.t_elev_deg > me.clamp(-80/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 5.5) { + and me.t_elev_deg > me.clamp(-50/me.speed_m,-30,-5) and me.dist_curr * M2NM > me.speed_m * 5.5) { # cruising: keeping altitude since target is below and more than -45 degs down me.ratio = (g_fps * me.dt)/me.old_speed_fps; @@ -3534,7 +3596,7 @@ var AIM = { me.track_signal_h = 0; me.printGuide("Trying to keep current %04.1f deg pitch.", me.pitch); }, - + level5: func () { me.track_signal_e = (5-me.pitch) * !me.free; me.track_signal_h = 0; @@ -3553,7 +3615,7 @@ var AIM = { me.track_signal_e = me.raw_steer_signal_elev * !me.free; me.track_signal_h = me.raw_steer_signal_head * !me.free; }, - + remoteControl: func () { me.track_signal_e = me.remote_control_pitch * me.dt * !me.free; me.track_signal_h = me.remote_control_yaw * me.dt * !me.free; @@ -3578,7 +3640,7 @@ var AIM = { ################################################# if (me.guidanceLaw == "direct") { - # pure pursuit + # pure pursuit me.raw_steer_signal_head = me.curr_deviation_h; if (me.cruise_or_loft == FALSE) { me.raw_steer_signal_elev = me.curr_deviation_e; @@ -3619,7 +3681,7 @@ var AIM = { me.last = me.life_time; me.next = me.seeker_filter*0.15*rand();# duration for this noise factor, till a new is computed. } - + me.horz_closing_rate_fps = ((me.dist_last - me.dist_curr)*M2FT)/me.dt+me.horz_closing_rate_fps;#clamped due to cruise missiles that can fly slower than target. me.horz_closing_rate_fps *= 0.5;# average over 2 frames me.printGuideDetails("Horz closing rate: %05d ft/sec", me.horz_closing_rate_fps); @@ -3630,13 +3692,13 @@ var AIM = { # This is especially important before we really gain speed against a receding fast target. me.course_deviation = geo.normdeg180(me.t_course-me.last_t_course); - + me.line_of_sight_rate_rps = (D2R*me.course_deviation)/me.dt;#positive clockwise me.printGuideDetails("LOS rate: %06.4f rad/s", me.line_of_sight_rate_rps); - me.t_velocity = me.myMath.getCartesianVelocity(me.Tgt.get_heading(), me.Tgt.get_Pitch(), me.Tgt.get_Roll(), me.Tgt.get_uBody(), me.Tgt.get_vBody(), me.Tgt.get_wBody()); - + me.t_velocity = me.myMath.getCartesianVelocity(-me.Tgt.get_heading(), me.Tgt.get_Pitch(), me.Tgt.get_Roll(), me.Tgt.get_uBody(), me.Tgt.get_vBody(), me.Tgt.get_wBody()); + if ((me.flareLock == FALSE and me.chaffLock == FALSE) or me.t_heading == nil) { me.euler = me.myMath.cartesianToEuler(me.t_velocity); if (me.euler[0] != nil) { @@ -3649,7 +3711,7 @@ var AIM = { } elsif (me.flarespeed_fps != nil) { me.t_speed_fps = me.flarespeed_fps;#true airspeed } - + me.t_horz_speed_fps = math.sqrt(me.t_velocity[0]*me.t_velocity[0]+me.t_velocity[1]*me.t_velocity[1]); me.t_LOS_norm_head_deg = me.t_course + 90;#when looking at target this direction will be 90 deg right of target me.t_LOS_norm_speed_fps = math.cos((me.t_LOS_norm_head_deg - me.t_heading)*D2R)*me.t_horz_speed_fps; @@ -3710,13 +3772,13 @@ var AIM = { #printf("horz acc = %.1f + %.1f", proportionality_constant*line_of_sight_rate_rps*horz_closing_rate_fps, proportionality_constant*t_LOS_norm_acc/2); if (me.guidanceLawHorizInit) { - # pure horiz pursuit + # pure horiz pursuit me.raw_steer_signal_head = me.curr_deviation_h; } # now translate that sideways acc to an angle: - - + + #printf("Proportional lead: %0.1f deg horz", -(me.curr_deviation_h-me.raw_steer_signal_head)); @@ -3742,12 +3804,12 @@ var AIM = { # proportional navigation for elevation # ######################################### #me.print(me.guidanceLaw~" in fully control"); - + me.line_of_sight_rate_up_rps = (D2R*(me.t_elev_deg-me.last_t_elev_deg))/me.dt; # calculate target acc as normal to LOS line: (up acc is positive) me.t_approach_bearing = me.t_course + 180; - + # used to do this with trigonometry, but vector math is simpler to understand: (they give same result though) me.t_LOS_elev_norm_speed = me.scalarProj(me.t_heading,me.t_pitch,me.t_speed_fps,me.t_approach_bearing,me.t_elev_deg*-1 +90); @@ -3755,7 +3817,7 @@ var AIM = { if (me.last_t_elev_norm_speed == nil) { me.last_t_elev_norm_speed = me.t_LOS_elev_norm_speed; } - + me.t_LOS_elev_norm_acc = (me.t_LOS_elev_norm_speed - me.last_t_elev_norm_speed)/me.dt; me.last_t_elev_norm_speed = me.t_LOS_elev_norm_speed; @@ -3792,7 +3854,7 @@ var AIM = { me.raw_steer_signal_elev = me.dt*me.radians_up_per_sec*R2D; } - # now compensate for the predicted gravity drop of attitude: + # now compensate for the predicted gravity drop of attitude: me.attitudePN = (math.atan2(-(me.speed_down_fps+g_fps * me.dt), me.speed_horizontal_fps ) - math.atan2(-me.speed_down_fps, me.speed_horizontal_fps )) * R2D; me.gravComp = -me.attitudePN; #printf("Gravity compensation %0.2f degs", me.gravComp); @@ -3843,6 +3905,7 @@ var AIM = { ####Ground interaction me.ground = geo.elevation(me.coord.lat(), me.coord.lon()) or 0; + me.terrainImpact = 0; if(me.ground > me.coord.alt()) { me.event = "exploded"; if(me.life_time < me.arming_time) { @@ -3857,9 +3920,9 @@ var AIM = { #me.direct_dist_m = me.coord.direct_distance_to(me.Tgt.get_Coord()); } if ((me.Tgt != nil and me.direct_dist_m != nil) or me.Tgt == nil) { - me.coord.set_alt(me.ground); - me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); - return TRUE; + me.terrainImpact = 1; + #me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); + #return TRUE; } } @@ -3868,14 +3931,20 @@ var AIM = { # (use xyz coord to avoid the strange behaviour of comparing geo coordinates). for (var i=me.crc_frames_look_back; i >= 0; i-=1){ me.crc_coord[i] = (i != 0) ? me.crc_coord[i-1] : me.coord.xyz(); - me.crc_t_coord[i] = (i != 0) ? me.crc_t_coord[i-1] : (me.inacc?me.Tgt.get_Coord(0).xyz():me.t_coord.xyz()); + me.crc_t_coord[i] = (i != 0) ? me.crc_t_coord[i-1] : me.t_coord.xyz(); me.crc_range[i] = (i != 0) ? me.crc_range[i-1] : me.myMath.magnitudeVector( - me.myMath.minus(me.crc_coord[0], + me.myMath.minus(me.crc_coord[0], me.crc_t_coord[0])); } - if (me.crc_coord[1] == nil or me.crc_t_coord[1] == nil or me.crc_range[1] == nil) - return FALSE; # Wait for the buffer to fill at least once. + if (me.crc_coord[1] == nil or me.crc_t_coord[1] == nil or me.crc_range[1] == nil) { + if (me.terrainImpact) { + me.coord.set_alt(me.ground); + me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); + return TRUE; + } + return FALSE; # Wait for the buffer to fill at least once. + } if (me.life_time > me.arming_time) { # Distance to target increase. @@ -3884,7 +3953,7 @@ var AIM = { me.subframeClosestRangeCoord(); # Provides `me.crc_closestRange` and `me.crc_missileCoord`. me.explode("Passed target.", me.crc_missileCoord, me.crc_closestRange); - return TRUE; + return TRUE; } if (me.life_time > me.selfdestruct_time or (me.destruct_when_free == TRUE and me.free == TRUE)) { me.explode("Selfdestructed.", me.coord); @@ -3897,14 +3966,19 @@ var AIM = { me.explode("Selfdestructed.", me.coord); return TRUE; } + if (me.terrainImpact) { + me.coord.set_alt(me.ground); + me.explode("Hit terrain.", me.coord, me.direct_dist_m, me.event); + return TRUE; + } return FALSE; }, - + #! brief: Recursive function to compute the closest range in the past mfd frames. #! param fei: The frame to compute first (0: current frame, 1: previous frame, ...). #! param mfd: The maximum amount of frames available if needed. - #! input me.crc_coord: The coordinates of the missile in the last frames. - #! input me.crc_t_coord: The coordinates of the target in the last frames. + #! input me.crc_coord: The coordinates of the missile in the last frames. + #! input me.crc_t_coord: The coordinates of the target in the last frames. #! output me.crc_missileCoord: The coordinates of the missile when it was the closest to the target in the mfd+1 last frames. #! output me.crc_closestRange: The range of the missile when it was the closest to the target in the mfd+1 last frames. subframeClosestRangeCoord : func(fei=0, mfd=nil) { @@ -3919,10 +3993,10 @@ var AIM = { die("Argument exception: The fei (Frames End Index) cannot be negative."); # Ensure the availability of the frame-end data. - if(me.crc_coord[fei] == nil or me.crc_t_coord[fei] == nil) + if(me.crc_coord[fei] == nil or me.crc_t_coord[fei] == nil) die("No coordinates available for the end of the frame."); - # Indices for the coordinates at frame start (fsi) and frame end (fei); + # Indices for the coordinates at frame start (fsi) and frame end (fei); var fsi = fei + 1; # Buffers used for unprocessed result, set to the frame-end values in case of unavailable frame-start data; @@ -3930,7 +4004,7 @@ var AIM = { var targetCoord = me.crc_t_coord[fei]; # Check for availability of the frame-start data - if(me.crc_coord[fsi] != nil and me.crc_t_coord[fsi] != nil){ + if(me.crc_coord[fsi] != nil and me.crc_t_coord[fsi] != nil){ # Get the origin coordinates and speed of the missile and it's target for the current frame. # The units are in m for distances and frames for time. var misCoord = me.crc_coord[fsi]; @@ -3953,7 +4027,7 @@ var AIM = { else if (t < 0) if (mfd > 1) # If we can recursively compute the previous frame: return me.subframeClosestRangeCoord(fei+1, mfd-1); # Return it's result instead, and stop here. - else + else t = 0; # Set it to 0 to prevent extrapolation. # Compute (interpolate) the position of the missile and it's target when their range is the closest. @@ -3967,11 +4041,11 @@ var AIM = { me.crc_missileCoord.set_xyz(missileCoord[0], missileCoord[1], missileCoord[2]); me.crc_missileCoord.alt();# TODO: once fixed in FG this line is no longer needed. }, - + log: func (str) { damage.damageLog.push(str); }, - + notifyInFlight: func (lat,lon,alt,rdar,typeID,typ,unique,thrustOn,callsign, heading, pitch, speed, is_deleted=0) { ## thrustON cannot be named 'thrust' as FG for some reason will then think its a function (probably fixed by the way call() now is used) var msg = notifications.ArmamentInFlightNotification.new("mfly", unique, is_deleted?damage.DESTROY:damage.MOVE, 21+typeID); @@ -3990,15 +4064,16 @@ var AIM = { msg.Pitch = pitch; msg.Heading = heading; msg.u_fps = speed; + #msg.isValid(); notifications.geoBridgedTransmitter.NotifyAll(msg); #print("fox2.nas: transmit in flight"); #f14.debugRecipient.Receive(msg); }, - + notifyCrater: func (lat,lon,alt,big,heading,static) { var uni = int(rand()*15000000); var msg = notifications.StaticNotification.new("stat", uni, 1, big); - + msg.Position.set_latlon(lat,lon,alt); msg.IsDistinct = 0; msg.Heading = heading; @@ -4007,13 +4082,13 @@ var AIM = { #f14.debugRecipient.Receive(msg); damage.statics["obj_"~uni] = [static, lat,lon,alt, heading,big]; }, - + notifyHit: func (RelativeAltitude, Distance, callsign, Bearing, reason, typeID, type, self) { var msg = notifications.ArmamentNotification.new("mhit", 4, 21+typeID); msg.RelativeAltitude = RelativeAltitude; msg.Bearing = Bearing; msg.Distance = Distance; - msg.RemoteCallsign = callsign; # RJHTODO: maybe handle flares / chaff + msg.RemoteCallsign = callsign; # RJHTODO: maybe handle flares / chaff if (self) { msg.Callsign = callsign; msg.FromIncomingBridge = 1; @@ -4037,17 +4112,17 @@ var AIM = { } elsif (me.chaffLock) { reason = "Locked onto chaff."; } - + me.coord = coordinates; # Set the current missile coordinates at the explosion point. - + if(getprop("payload/armament/msg")) { thread.lock(mutexTimer); append(AIM.timerQueue, [AIM, AIM.notifyInFlight, [me.coord.lat(), me.coord.lon(), me.coord.alt(),0,me.typeID,me.type,me.unique_id,0,"", me.hdg, me.pitch, 0, 0], -1]); thread.unlock(mutexTimer); } - + var wh_mass = (event == "exploded" and !me.inert) ? me.weight_whead_lbm : 0; #will report 0 mass if did not have time to arm - + thread.lock(mutexTimer); append(AIM.timerQueue, [me,impact_report,[coordinates, wh_mass, "munition", me.type, me.new_speed_fps*FT2M],0]); thread.unlock(mutexTimer); @@ -4076,7 +4151,7 @@ var AIM = { me.printStats("%s time %.1f", phrase, me.life_time); if(getprop("payload/armament/msg") and hitPrimaryTarget and wh_mass > 0){ thread.lock(mutexTimer); - append(AIM.timerQueue, [AIM, AIM.notifyHit, [coordinates.alt() - (me.inacc?me.Tgt.get_Coord(0).alt():me.t_coord.alt()),range,me.callsign,coordinates.course_to(me.inacc?me.Tgt.get_Coord(0):me.t_coord),reason,me.typeID, me.typeLong, 0], -1]); + append(AIM.timerQueue, [AIM, AIM.notifyHit, [coordinates.alt() - me.t_coord.alt(),range,me.callsign,coordinates.course_to(me.t_coord),reason,me.typeID, me.typeLong, 0], -1]); thread.unlock(mutexTimer); } else { thread.lock(mutexTimer); @@ -4092,8 +4167,11 @@ var AIM = { thread.unlock(mutexTimer); } } - + me.ai.getNode("valid", 1).setBoolValue(0); + thread.lock(mutexTimer); + append(AIM.timerQueue, [me, me.setModelRemoved, [], -1]); + thread.unlock(mutexTimer); if (event == "exploded" and !me.inert and wh_mass > 0) { me.animate_explosion(hitGround); me.explodeSound = TRUE; @@ -4103,7 +4181,7 @@ var AIM = { } me.Tgt = nil; }, - + multiExplosion: func (explode_coord, event, wh_mass) { # hit everything that is nearby except for target itself. me.sendout = 0; @@ -4112,15 +4190,13 @@ var AIM = { continue; } var min_distance = me.testMe.get_Coord().direct_distance_to(explode_coord); - if (me.inacc) { - min_distance = me.testMe.get_Coord(0).direct_distance_to(explode_coord); - } + if (min_distance < me.reportDist and (me.Tgt == nil or me.testMe.getUnique() != me.Tgt.getUnique())) { var phrase = sprintf("%s %s: %.1f meters from: %s", me.type,event, min_distance, me.testMe.get_Callsign()); me.printStats(phrase); if(getprop("payload/armament/msg") and wh_mass > 0){ - var cs = me.testMe.get_Callsign(); + var cs = damage.processCallsign(me.testMe.get_Callsign()); var cc = me.testMe.get_Coord(); thread.lock(mutexTimer); append(AIM.timerQueue, [AIM, AIM.notifyHit, [explode_coord.alt() - cc.alt(),min_distance,cs,explode_coord.course_to(cc),"mhit1",me.typeID, me.typeLong,0], -1]); @@ -4134,8 +4210,7 @@ var AIM = { var min_distance = geo.aircraft_position().direct_distance_to(explode_coord); if (min_distance < me.reportDist) { # hitting oneself :) - var cs = getprop("sim/multiplay/callsign"); - cs = size(cs) < 8 ? cs : left(cs,7); + var cs = damage.processCallsign(getprop("sim/multiplay/callsign")); var phrase = sprintf("%s %s: %.1f meters from: %s", me.type,event, min_distance, cs);# if we mention ourself then we need to explicit add ourself as author. me.printStats(phrase); if (wh_mass > 0) { @@ -4190,7 +4265,7 @@ var AIM = { if (me.noCommonTarget) { return nil; } - if (me.inacc and me.target_pnt and contactPoint != nil) { + if (me.target_pnt and contactPoint != nil) { return contactPoint; } else { return contact; @@ -4263,9 +4338,9 @@ var AIM = { } me.cooling_last_time = me.cool_elapsed; me.detect_range_curr_nm = me.extrapolate(me.warm, 0, 1, me.cold_detect_range_nm, me.warm_detect_range_nm); - #me.detect_range_curr_nm *= me.seam_scan?0.5:1; # source for this is not credible. + me.detect_range_curr_nm *= me.seam_scan?0.65:1;#GR1F-16CJ-34-1-1 page 1-402 } else { - #me.detect_range_curr_nm = (me.seam_scan?0.5:1)*me.max_fire_range_nm; # no credible source for this + me.detect_range_curr_nm = (me.seam_scan?0.65:1)*me.max_fire_range_nm;#GR1F-16CJ-34-1-1 page 1-402 } }, @@ -4273,7 +4348,7 @@ var AIM = { # call this only before firing if (!(me.tagt.get_type() == AIR and me.tagt.get_Speed()<15) and ((me.guidance != "semi-radar" or me.is_painted(me.tagt) == TRUE) and (me.guidance !="laser" or me.is_laser_painted(me.tagt) == TRUE)) and (me.guidance != "radiation" or me.is_radiating_aircraft(me.tagt) == TRUE) - and me.rng < me.max_fire_range_nm and me.rng > me.getCurrentMinFireRange(me.tagt) and me.FOV_check(OurHdg.getValue(),OurPitch.getValue(),me.total_horiz, me.total_elev, me.slave_to_radar?(me.guidance == "heat" or me.guidance == "vision"?math.min(me.max_seeker_dev, me.fcs_fov):me.fcs_fov):me.max_seeker_dev, vector.Math) + and me.rng < me.max_fire_range_nm and me.rng > me.getCurrentMinFireRange(me.tagt) and me.FOV_check(OurHdg.getValue(),OurPitch.getValue(),me.total_horiz, me.total_elev, me.slave_to_radar or contactPoint==me.tagt?(me.guidance == "heat" or me.guidance == "vision"?math.min(me.max_seeker_dev, me.fcs_fov):me.fcs_fov):me.max_seeker_dev, vector.Math) and (me.rng < me.detect_range_curr_nm or (me.guidance != "radar" and me.guidance != "semi-radar" and me.guidance != "heat" and me.guidance != "vision" and me.guidance != "heat" and me.guidance != "radiation")) and (me.guidance != "heat" or (me.all_aspect == TRUE or me.rear_aspect(geo.aircraft_position(), me.tagt) == TRUE)) and me.checkForView()) { @@ -4299,7 +4374,7 @@ var AIM = { me.geodPos = aircraftToCart({x:-me.radarX, y:me.radarY, z: -me.radarZ}); me.launchCoord.set_xyz(me.geodPos.x, me.geodPos.y, me.geodPos.z); } - + me.potentialCoord = me.tagt.get_Coord(); me.xyz = {"x":me.launchCoord.x(), "y":me.launchCoord.y(), "z":me.launchCoord.z()}; me.directionLOS = {"x":me.potentialCoord.x()-me.launchCoord.x(), "y":me.potentialCoord.y()-me.launchCoord.y(), "z":me.potentialCoord.z()-me.launchCoord.z()}; @@ -4321,7 +4396,7 @@ var AIM = { } return TRUE; }, - + checkForViewInFlight: func (tagt) { if (me.guidance != "gps" and me.guidance != "inertial") { me.launchCoord = me.coord; @@ -4359,7 +4434,7 @@ var AIM = { #if(me.slaveContact != nil) printf("class failed %d %d %d",me.slaveContact.isValid() == TRUE,me.slaveContact.get_type() == AIR,me.target_air == TRUE); return FALSE; }, - + newLock: func (tagt) { # for switching to new lock during flight. #reorder to gain performance @@ -4472,7 +4547,7 @@ var AIM = { # Check if in range and in the seeker FOV. if (me.checkForLock()) { me.printSearch("uncaged-search ready for lock"); - + me.convertGlobalToSeekerViewDirection(me.tagt.get_bearing(), me.tagt.getElevation(), OurHdg.getValue(), OurPitch.getValue(), OurRoll.getValue()); me.testSeeker(); if (me.inBeam) { @@ -4575,7 +4650,7 @@ var AIM = { if (me.checkForClass()) { me.tagt = me.slaveContact; me.rng = me.tagt.get_range(); - + # Check if in range and in the seeker FOV. me.total_elev = deviation_normdeg(OurPitch.getValue(), me.tagt.getElevation()); # deg. me.total_horiz = deviation_normdeg(OurHdg.getValue(), me.tagt.get_bearing()); # deg. @@ -4605,12 +4680,12 @@ var AIM = { me.Tgt = me.tagt; - me.callsign = me.Tgt.get_Callsign(); + me.callsign = damage.processCallsign(me.Tgt.get_Callsign()); settimer(func me.update_lock(), 0.1); }, - convertGlobalToSeekerViewDirection: func (bearing, elevation, heading, pitch, roll) { + convertGlobalToSeekerViewDirection: func (bearing, elevation, heading, pitch, roll) { me.target_x = math.cos(bearing*D2R)*math.cos(elevation*D2R); me.target_y = -math.sin(bearing*D2R)*math.cos(elevation*D2R); me.target_z = math.sin(elevation*D2R); @@ -4621,7 +4696,7 @@ var AIM = { me.rotation = vector.Math.multiplyMatrices(me.rollLaunchvehicle, vector.Math.multiplyMatrices(me.pitchLaunchvehicle, me.yawLaunchvehicle)); me.target_vector_from_seekers_view = vector.Math.multiplyMatrixWithVector(me.rotation, me.target_vector); me.angles = vector.Math.cartesianToEuler(me.target_vector_from_seekers_view); - + me.seeker_head_target = me.angles[0]==nil?0:geo.normdeg180(me.angles[0]); me.seeker_elev_target = me.angles[1]; }, @@ -4702,7 +4777,7 @@ var AIM = { if (me.seeker_last_time != 0) { me.seeker_time = me.seeker_elapsed - me.seeker_last_time; me.seeker_max_move = me.seeker_time*me.angular_speed; - + # Build unit vector components for seeker and target location in aircraft frame: me.seeker_x = math.cos(me.seeker_head*D2R)*math.cos(me.seeker_elev*D2R); me.seeker_y = -math.sin(me.seeker_head*D2R)*math.cos(me.seeker_elev*D2R); @@ -4710,9 +4785,9 @@ var AIM = { me.ideal_seeker_deviation = vector.Math.angleBetweenVectors([me.seeker_x,me.seeker_y,me.seeker_z],[me.target_x,me.target_y,me.target_z]); me.ideal_total_seeker_deviation = vector.Math.angleBetweenVectors(me.seeker_reset, [me.target_x,me.target_y,me.target_z]); - + if (me.ideal_seeker_deviation > me.seeker_max_move) { - me.new_seeker_vector = vector.Math.rotateVectorTowardsVector([me.seeker_x,me.seeker_y,me.seeker_z],[me.target_x,me.target_y,me.target_z],me.seeker_max_move); + me.new_seeker_vector = vector.Math.rotateVectorTowardsVector([me.seeker_x,me.seeker_y,me.seeker_z],[me.target_x,me.target_y,me.target_z],me.seeker_max_move); me.new_seeker_deviation = vector.Math.angleBetweenVectors(me.seeker_reset, me.new_seeker_vector); if (me.new_seeker_deviation < me.max_seeker_dev) { @@ -4755,7 +4830,7 @@ var AIM = { # Compute HUD diamond position. if ( use_fg_default_hud == TRUE) { var h_rad = (90 - me.seeker_head) * D2R; - var e_rad = (90 - me.seeker_elev) * D2R; + var e_rad = (90 - me.seeker_elev) * D2R; var devs = develev_to_devroll(h_rad, e_rad); var combined_dev_deg = devs[0]; var combined_dev_length = devs[1]; @@ -4806,12 +4881,16 @@ var AIM = { return; } elsif (me.deleted == TRUE) { return; + } elsif (me.slave_to_radar and me.caged and me.getContact() != me.Tgt) { + me.printSearch("target switch"); + me.return_to_search(); + return; } me.printSearch("lock"); # Time interval since lock time or last track loop. - #if (me.status == MISSILE_LOCK) { + #if (me.status == MISSILE_LOCK) { # Status = locked. Get target position relative to our aircraft. - + #} #me.time = props.globals.getNode("/sim/time/elapsed-sec", 1).getValue(); @@ -4823,7 +4902,7 @@ var AIM = { me.computeSeekerPos(); if (me.status != MISSILE_STANDBY ) {#TODO: should this also check for starting up? me.in_view = me.check_t_in_fov(); - + if (me.in_view == FALSE) { me.printSearch("out of view"); me.return_to_search(); @@ -4874,7 +4953,7 @@ var AIM = { me.return_to_search(); return; } - + settimer(func me.update_lock(), deltaSec.getValue()==0?0.5:0.1); return; } @@ -4909,7 +4988,7 @@ var AIM = { #if (me.status==MISSILE_FLYING) printf("1: %.1f out of %.1f, deviation %.1f, %.1f", me.fov_radial, fov_radius, deviation_hori, deviation_elev); return FALSE; }, - + FOV_check_norm: func (meHeading, mePitch, deviation_hori, deviation_elev, fov_radius,vect) { # we measure the geodesic angle between current attitude and the attitude to target. me.meVector = vect.eulerToCartesian3X(-meHeading, mePitch, 0); @@ -5008,10 +5087,10 @@ var AIM = { var explode_smoke_path = path_base~"explode-smoke-id-" ~ me.ID; me.explode_smoke_prop = props.globals.initNode( explode_smoke_path, FALSE, "BOOL", TRUE); - + var explode_water_path = path_base~"explode-water-id-" ~ me.ID; me.explode_water_prop = props.globals.initNode( explode_water_path, FALSE, "BOOL", TRUE); - + var explode_angle_path = path_base~"explode-angle"; me.explode_angle_prop = props.globals.initNode( explode_angle_path, 0.0, "DOUBLE", TRUE); @@ -5037,7 +5116,7 @@ var AIM = { me.msl_prop.setBoolValue(FALSE); me.smoke_prop.setBoolValue(FALSE); var info = geodinfo(me.coord.lat(), me.coord.lon()); - + if (hitGround) { if (info == nil) { me.explode_water_prop.setBoolValue(FALSE); @@ -5084,7 +5163,7 @@ var AIM = { siz = 1; crater_model = getprop("payload/armament/models") ~ "crater_big.xml"; } - + if (crater_model != "" and me.weight_whead_lbm > 150) { var static = geo.put_model(crater_model, me.coord.lat(), me.coord.lon()); #print("put crater"); @@ -5154,7 +5233,7 @@ var AIM = { # next speed vector me.vector_next_x = math.cos(me.steer_deg*D2R)*s_fps; me.vector_next_y = math.sin(me.steer_deg*D2R)*s_fps; - + # present speed vector me.vector_now_x = s_fps; me.vector_now_y = 0; @@ -5162,76 +5241,76 @@ var AIM = { # Delta velocity: subtract the vectors from each other and get the magnitude me.dv = me.myMath.minus([me.vector_now_x,me.vector_now_y,0],[me.vector_next_x,me.vector_next_y,0]); me.dv = me.myMath.magnitudeVector(me.dv); - + # calculate g-force # dv/dt=a me.g = (me.dv/dt) / g_fps; return me.g; }, - + steering_speed_G: func(meHeading, mePitch, steering_e_deg, steering_h_deg, s_fps, dt) { # Get G number from steering (e, h) in deg, speed in ft/s. me.meVector = me.myMath.eulerToCartesian3X(-meHeading, mePitch, 0); me.meVectorN= me.myMath.normalize(me.meVector); - me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now + me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now me.itVector = me.myMath.eulerToCartesian3X(-(meHeading+steering_h_deg), mePitch+steering_e_deg, 0); me.itVector = me.myMath.normalize(me.itVector); me.itVector = me.myMath.product(s_fps, me.itVector);#velocity vector if doing that steering - + # Delta lateral velocity me.dv = me.myMath.minus(me.itVector, me.meVector); - me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dv); + me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dv); me.dv = me.myMath.magnitudeVector(me.dv); - + # calculate g-force # dv/dt=a me.g = (me.dv/dt) / g_fps; return me.g; }, - + overload_limiter: func(meHeading, mePitch, steering_e_deg, steering_h_deg, s_fps, dt, gMax) { # The missile desires to rotate a certain amount # This function will limit that steering to prevent it from exceeding the max G it should be able to do at this air density - + if (gMax == 0 or (steering_e_deg == 0 and steering_h_deg == 0)) { return [0,0]; } - + # Get G number from steering (e, h) in deg, speed in ft/s. me.meVector = me.myMath.eulerToCartesian3X(-meHeading, mePitch, 0); me.meVectorN= me.myMath.normalize(me.meVector); - me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now + me.meVector = me.myMath.product(s_fps, me.meVectorN);#velocity vector now me.itVector = me.myMath.eulerToCartesian3X(-(meHeading+steering_h_deg), mePitch+steering_e_deg, 0); me.itVectorN= me.myMath.normalize(me.itVector); me.itVector = me.myMath.product(s_fps, me.itVectorN);#velocity vector if doing that steering - + # Delta lateral velocity me.dvAcc = me.myMath.minus(me.itVector, me.meVector); - me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dvAcc); + me.dv = me.myMath.projVectorOnPlane(me.meVectorN, me.dvAcc); me.dv = me.myMath.magnitudeVector(me.dv); - + # calculate g-force # dv/dt=a me.g_load = (me.dv/dt) / g_fps; - + me.exceed_g = gMax/me.g_load; - + if (me.exceed_g >= 1) { return [steering_h_deg, steering_e_deg]; } - + # Now do something that works okay for small desired steerings, but fails for big steerings. Hence the 0.9 factor. me.dvAcc9 = me.myMath.product(me.exceed_g*0.9, me.dvAcc); me.itVector = me.myMath.plus(me.dvAcc9, me.meVector); - + me.euler = me.myMath.cartesianToEuler(me.itVector); - + if (me.euler[0] == nil) { me.euler[0] = meHeading; } - + return [geo.normdeg180(me.euler[0]-meHeading), me.euler[1]-mePitch]; }, @@ -5364,7 +5443,7 @@ var AIM = { thread.unlock(mutexTimer); } }, - + timerLoop: func { thread.lock(mutexTimer); AIM.tq = AIM.timerQueue; @@ -5374,7 +5453,7 @@ var AIM = { AIM.timerCall(cmd); } }, - + timerCall: func (cmd) { if (cmd != nil) { if (cmd[3] == -1) { @@ -5389,7 +5468,7 @@ var AIM = { } } }, - + pop_front: func (vector) { if (size(vector)==0) return [nil, vector]; var new_vector = []; @@ -5400,9 +5479,9 @@ var AIM = { } return [vector[0],new_vector]; }, - + timers: {}, - + timerQueue: [], active: {}, @@ -5428,6 +5507,14 @@ var AIM = { thread.unlock(mutexETA); return retur; }, + + setModelAdded: func { + setprop("ai/models/model-added", me.ai.getPath()); + }, + + setModelRemoved: func { + setprop("ai/models/model-removed", me.ai.getPath()); + }, }; var backtrace = func(desc = nil, dump_vars = 1, skip_level = 0, levels = 3) { var d = (desc == nil) ? "" : " '" ~ desc ~ "'"; @@ -5490,16 +5577,18 @@ var hud_radius_m = 0.100; #was in hud var develev_to_devroll = func(dev_rad, elev_rad) { + if (math.sin(dev_rad) == 0 or math.sin(elev_rad) == 0) return [0,0,0]; + if (math.cos(dev_rad) == 0 or math.cos(elev_rad) == 0) return [0,20,1]; var clamped = 0; # Deviation length on the HUD (at level flight), # 0.6686m = distance eye <-> virtual HUD screen. var h_dev = eye_hud_m / ( math.sin(dev_rad) / math.cos(dev_rad) ); var v_dev = eye_hud_m / ( math.sin(elev_rad) / math.cos(elev_rad) ); # Angle between HUD center/top <-> HUD center/symbol position. - # -90° left, 0° up, 90° right, +/- 180° down. + # -90° left, 0° up, 90° right, +/- 180° down. var dev_deg = math.atan2( h_dev, v_dev ) * R2D; # Correction with own a/c roll. - var combined_dev_deg = dev_deg - OurRoll.getValue(); + var combined_dev_deg = dev_deg; # Lenght HUD center <-> symbol pos on the HUD: var combined_dev_length = math.sqrt((h_dev*h_dev)+(v_dev*v_dev)); # clamp and squeeze the top of the display area so the symbol follow the egg shaped HUD limits. @@ -5508,7 +5597,7 @@ var develev_to_devroll = func(dev_rad, elev_rad) { if ( abs_combined_dev_deg >= 0 and abs_combined_dev_deg < 90 ) { var coef = ( 90 - abs_combined_dev_deg ) * 0.00075; if ( coef > 0.050 ) { coef = 0.050 } - clamp -= coef; + clamp -= coef; } if ( combined_dev_length > clamp ) { combined_dev_length = clamp; @@ -5573,4 +5662,4 @@ var printff = func{ spamLoop(); var timerLooper = maketimer(0, AIM, AIM.timerLoop); -timerLooper.start(); \ No newline at end of file +timerLooper.start(); diff --git a/SA-6/Nasal/vector.nas b/SA-6/Nasal/vector.nas index 62b3bcc..621b0cb 100644 --- a/SA-6/Nasal/vector.nas +++ b/SA-6/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.97 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -39,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -85,21 +87,24 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.rollM, me.multiplyMatrices(me.pitchM, me.yawM)); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.rollM, me.multiplyMatrices(me.pitchM, me.yawM)); return me.multiplyMatrixWithVector(me.rotation, vector); }, @@ -119,6 +124,12 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + # rotate a vector. Order: pitch pitchVector: func (pitch, vector) { me.pitchM = me.pitchMatrix(pitch); @@ -187,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -285,13 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, # Project a onto ontoMe. projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -397,4 +450,33 @@ var Math = { # # -}; \ No newline at end of file +}; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/SA-6/SA-6-set.xml b/SA-6/SA-6-set.xml index f003c9e..b559e1b 100644 --- a/SA-6/SA-6-set.xml +++ b/SA-6/SA-6-set.xml @@ -238,7 +238,20 @@ 50 - + + 4 + true + 80 + false + true + false + false + false + false + false + 2.25 + false + Aircraft/SA-6/Models/Armament/Weapons/ true @@ -263,7 +276,7 @@ 96 3M9 - Anti Aircraft Guided Missile + 3M9M3 Surface to Air Missile 18 1.42 1333 @@ -343,19 +356,19 @@ 100 - - Leto - swamp - pinto - fb237 - Wilco-1 - Kokos - J-Mav16 - S - Raider1 - Slobb - 5H1N0B1 - SNOWY1 + + target1 + target2 + target3 + target4 + target5 + target6 + target7 + target8 + target9 + targetA + targetB + targetC 0 diff --git a/ZSU-23/Nasal/damage.nas b/ZSU-23/Nasal/damage.nas index 6b270d8..44b753a 100644 --- a/ZSU-23/Nasal/damage.nas +++ b/ZSU-23/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 2;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 5;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -37,24 +39,30 @@ var shells = { # 0.20 means a direct hit will disable 20% of the failure modes on average. # or, 0.20 also means a direct hit can do 20 hitpoints damage. # - "M70 rocket": [0,0.250], #135mm - "S-5 rocket": [1,0.200], # 55mm - "M55 shell": [2,0.100], # 30mm - "KCA shell": [3,0.100], # 30mm - "GSh-30": [4,0.100], # 30mm mig29/su27 - "GAU-8/A": [5,0.100], # 30mm - "Mk3Z": [6,0.100], # 30mm Jaguar - "BK27": [7,0.070], # 27mm - "GSh-23": [8,0.065], # 23mm - "M61A1 shell": [9,0.050], # 20mm F14, F15, F16 - "50 BMG": [10,0.015], # 12.7mm (non-explosive) + # Damage roughly proportional to projectile weight. + # If weight isn't listed here, it was estimated from dimensions (proportional to diameter^2 * length). + # Approximate formulae for cannons: + # damage ~ weight / 3.6 (in g) + # or damage ~ diameter^2 * length / 1.6e6 (in mm) + # + "M70 rocket": [0,0.500], # 135mm, ~5kg warhead + "S-5 rocket": [1,0.200], # 55mm, ~1-2kg warhead + "M55 shell": [2,0.060], # 30x113mm, 220g + "KCA shell": [3,0.100], # 30x173mm, 360g + "GSh-30": [4,0.095], # 30x165mm mig29/su27 + "GAU-8/A": [5,0.100], # 30x173mm, 360g + "Mk3Z": [6,0.060], # 30x113mm Jaguar, 220g + "BK27": [7,0.070], # 27x145mm, 270g + "GSh-23": [8,0.040], # 23x115mm, + "M61A1 shell": [9,0.030], # 20x102mm F14, F15, F16, 100g + "50 BMG": [10,0.015], # 12.7mm (non-explosive) "7.62": [11,0.005], # 7.62mm (non-explosive) - "Hydra-70": [12,0.250], # F-16/A-6 LAU-68 and LAU-61 - "SNEB": [13,0.250], # Jaguar - "DEFA 554": [14,0.100], # 30mm Mirage - "20mm APDS": [15,0.050], # CIWS - "LAU-10": [16,0.225], # 127mm -}; + "Hydra-70": [12,0.500], # 70mm, F-16/A-6 LAU-68 and LAU-61, ~4-6kg warhead + "SNEB": [13,0.500], # 68mm, Jaguar + "DEFA 554": [14,0.060], # 30x113mm Mirage, 220g + "20mm APDS": [15,0.030], # CIWS + "LAU-10": [16,0.500], # 127mm, ~4-7kg warhead +}; # lbs of warheads is explosive+fragmentation+fuse, so total warhead mass. @@ -82,7 +90,7 @@ var warheads = { "GBU-24": [19, 945.00,1,0], "GBU-31": [20, 945.00,1,0], "GBU-54": [21, 190.00,1,0], - "GBU-10": [22, 2000.00,1,0], + "GBU-10": [22, 945.00,1,0], "GBU-16": [23, 450.00,1,0], "HVAR": [24, 7.50,1,0],#P51 "KAB-500": [25, 564.38,1,0], @@ -123,9 +131,9 @@ var warheads = { "Apache AP": [60, 110.23,0,1],# Real mass of bomblet. (x 10). Anti runway. "KN-06": [61, 315.00,0,0], "9M317": [62, 145.00,0,0], - "d9": [63, 27.00,0,0],#deprecated + "GEM": [63, 185.00,0,0],#MIM-104D "R.550 Magic": [64, 26.45,0,0],# also called majic - "d-0": [65, 30.00,0,0],#deprecated + "5Ya23": [65, 414.00,0,0],#Volga-M "R.550 Magic 2": [66, 27.00,0,0], "R.530": [67, 55.00,0,0], "MK-82AIR": [68, 192.00,1,0], @@ -156,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -218,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -250,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -261,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -283,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -291,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -304,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -313,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -339,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -372,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -417,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -430,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -449,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -460,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -600,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -608,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -618,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -637,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -661,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -704,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -730,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -804,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -812,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -832,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -840,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -999,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1033,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1047,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1072,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1131,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1144,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1170,16 +1300,17 @@ var code_ct = func () { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1197,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1276,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/ZSU-23/Nasal/fdm.nas b/ZSU-23/Nasal/fdm.nas index 4135a2e..f7df6b9 100644 --- a/ZSU-23/Nasal/fdm.nas +++ b/ZSU-23/Nasal/fdm.nas @@ -23,29 +23,31 @@ print("loading fdm"); var frequency = 60.0; # Change in heading per second at full rudder deflection -var heading_ps = 0.5; +var heading_ps = 20; -time_last = 0; -sim_speed = 1; +var time_last = 0; +var sim_speed = 1; + +var distance = 0; + +var speed = 15; -setprop("/carrier/pitch-deg",0); -setprop("/carrier/pitch-offset",0); -setprop("/carrier/roll-deg",0); -setprop("/carrier/roll-offset",0); setprop("/carrier/sunk",0); -var arrived = 0; +setprop("/orientation/pitch-deg", 0); + +#var arrived = 0; var PositionUpdater = func () { settimer( PositionUpdater, 1/frequency ); var position = geo.aircraft_position(); - if ( arrived == 0 and getprop("/carrier/sunk") == 0 and getprop("/autopilot/route-manager/wp-last/dist") != nil and getprop("/autopilot/route-manager/wp-last/dist") < 1 ) { - setprop("/sim/multiplay/chat",getprop("sim/multiplay/callsign") ~ " has arrived safely!"); - print("arrived"); - arrived = 1; + #if ( arrived == 0 and getprop("/carrier/sunk") == 0 and getprop("/autopilot/route-manager/wp-last/dist") != nil and getprop("/autopilot/route-manager/wp-last/dist") < 1 ) { + #setprop("/sim/multiplay/chat",getprop("sim/multiplay/callsign") ~ " has arrived safely!"); + # print("arrived"); + # arrived = 1; #return; - } + #} var time_now = getprop("/sim/time/elapsed-sec"); var dt = (time_now - time_last) * sim_speed; @@ -53,47 +55,61 @@ var PositionUpdater = func () { time_last = time_now; - #var heading = getprop("/orientation/heading-deg"); - #var speed = getprop("/velocities/groundspeed-kt"); - var rudder = getprop("/surface-positions/rudder-pos-norm"); if ( getprop("/carrier/sunk") == 0 and getprop("/autopilot/route-manager/active") == 1 ) { - #for event - var speed = 25; + var cur_waypoint = getprop("/autopilot/route-manager/current-wp"); - var cur_wp_lon = getprop("/autopilot/route-manager/route/wp[" ~ cur_waypoint ~ "]/longitude-deg"); - var cur_wp_lat = getprop("/autopilot/route-manager/route/wp[" ~ cur_waypoint ~ "]/latitude-deg"); - var rm_destination = geo.Coord.new().set_latlon(cur_wp_lat,cur_wp_lon); - - - var heading = position.course_to(rm_destination); - - var distance = speed * globals.KT2MPS * dt; - position.apply_course_distance(heading, distance); - - # Set new position - setprop("/position/latitude-deg", position.lat()); - setprop("/position/longitude-deg", position.lon()); - var g_alt = getprop("/position/ground-elev-ft")+1; + if (cur_waypoint > -1) { + var cur_wp_lon = getprop("/autopilot/route-manager/route/wp[" ~ cur_waypoint ~ "]/longitude-deg"); + var cur_wp_lat = getprop("/autopilot/route-manager/route/wp[" ~ cur_waypoint ~ "]/latitude-deg"); + var rm_destination = geo.Coord.new().set_latlon(cur_wp_lat,cur_wp_lon); + + var heading = getprop("/orientation/heading-deg"); + + var course = position.course_to(rm_destination); + + distance = speed * KT2MPS * dt; + + var turn_max = heading_ps * dt; + + var turn = math.clamp(geo.normdeg180(course-heading),-turn_max,turn_max); + + heading += turn; + + heading = geo.normdeg(heading); + + position.apply_course_distance(heading, distance); + + + + # Set new position + setprop("/position/latitude-deg", position.lat()); + setprop("/position/longitude-deg", position.lon()); + + setprop("/orientation/heading-deg", heading); + setprop("velocities/groundspeed-kt",speed); + } else { + setprop("velocities/groundspeed-kt",0); + } + var g_alt = geo.elevation(position.lat(),position.lon());#getprop("/position/ground-elev-ft"); + if (g_alt == nil) g_alt = 0; + g_alt *= M2FT; + + var alt_diff = g_alt - getprop("/position/altitude-ft"); + + var pitch = math.atan2(alt_diff, distance*M2FT)*R2D; + + setprop("/orientation/pitch-deg", pitch); + setprop("/position/altitude-ft",g_alt); - - - - - # Update heading - var course = heading + rudder * heading_ps * dt; - setprop("/orientation/heading-deg", course); - setprop("velocities/groundspeed-kt",speed); } else { setprop("velocities/groundspeed-kt",0); } - #set pitch - setprop("/orientation/pitch-deg",getprop("/carrier/pitch-deg") + getprop("/carrier/pitch-offset")); - + #set roll - setprop("/orientation/roll-deg",getprop("/carrier/roll-deg") + getprop("/carrier/roll-offset")); + setprop("/orientation/roll-deg", 0); }; diff --git a/ZSU-23/Nasal/vector.nas b/ZSU-23/Nasal/vector.nas index 36ae539..621b0cb 100644 --- a/ZSU-23/Nasal/vector.nas +++ b/ZSU-23/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.93 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -13,6 +13,8 @@ var Math = { # When doing euler angles (from pilots point of view): yaw = yaw left, pitch = rotate up, roll = roll right. # FG rotations: heading = yaw right, pitch = rotate up, roll = roll right. # + # Cartesian is right-handed coord system. + # clamp: func(v, min, max) { v < min ? min : v > max ? max : v }, convertCoords: func (x,y,z) { @@ -37,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -55,9 +59,27 @@ var Math = { return R2D * math.acos(me.value); }, + # Rodrigues' rotation formula. Use unitVectorAxis as axis, and rotate around it. + rotateVectorAroundVector: func (a, unitVectorAxis, angle) { + return me.plus( + me.plus( + me.product(math.cos(angle*D2R),a), + me.product(math.sin(angle*D2R), me.crossProduct(unitVectorAxis,a)) + ), + me.product((1-math.cos(angle*D2R))*me.dotProduct(unitVectorAxis,a), unitVectorAxis) + ); + }, + + #Rotate a certain amound of degrees towards 'towardsMe'. + rotateVectorTowardsVector: func (a, towardsMe, angle) { + return me.rotateVectorAroundVector(a, me.normalize(me.crossProduct(a, towardsMe)), angle); + }, + # length of vector magnitudeVector: func (a) { - return math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2)); + me.mag = 1; + call(func {me.mag = math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2))},nil,nil,var err =[]);#strange bug in Nasal can sometimes mess up sqrt + return me.mag; }, # dot product of 2 vectors @@ -65,8 +87,20 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { + me.rollM = me.rollMatrix(roll); + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) + yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); @@ -74,15 +108,34 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) - yawPitchRollVector: func (yaw, pitch, roll, vector) { - me.rollM = me.rollMatrix(roll); + # rotate a vector. Order: yaw, pitch + yawPitchVector: func (yaw, pitch, vector) { me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.pitchM, me.yawM); return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: pitch, yaw + pitchYawVector: func (pitch, yaw, vector) { + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.pitchM); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + + # rotate a vector. Order: pitch + pitchVector: func (pitch, vector) { + me.pitchM = me.pitchMatrix(pitch); + return me.multiplyMatrixWithVector(me.pitchM, vector); + }, + # multiply 3x3 matrix with vector multiplyMatrixWithVector: func (matrix, vector) { return [matrix[0]*vector[0]+matrix[1]*vector[1]+matrix[2]*vector[2], @@ -107,10 +160,10 @@ var Math = { # matrix for pitching pitchMatrix: func (pitch) { - pitch = pitch * D2R; - return [math.cos(pitch),0,-math.sin(pitch), + pitch = -pitch * D2R; + return [math.cos(pitch),0,math.sin(pitch), 0,1,0, - math.sin(pitch),0,math.cos(pitch)]; + -math.sin(pitch),0,math.cos(pitch)]; }, # matrix for yawing @@ -126,7 +179,8 @@ var Math = { me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); if (me.horz != 0) { me.pitch = math.atan2(vector[2],me.horz)*R2D; - me.hdg = math.asin(-vector[1]/me.horz)*R2D; + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; if (vector[0] < 0) { # south @@ -144,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -242,8 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, + + # Project a onto ontoMe. + projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety + return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); + }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -268,7 +369,7 @@ var Math = { # print vector to console format: func (v) { - return sprintf("(%.1f, %.1f, %.1f)",v[0],v[1],v[2]); + return sprintf("(%.2f, %.2f, %.2f)",v[0],v[1],v[2]); }, # make vector length 1.0 @@ -288,6 +389,20 @@ var Math = { return me.magnitudeVector(me.crossProduct(me.minus(L2,L1), me.minus(L1,P)))/me.magnitudeVector(me.minus(L2,L1)); }, + + interpolateVector: func (start, end, fraction) { + me.xx = (start[0]*(1-fraction) +end[0]*fraction); + me.yy = (start[1]*(1-fraction) +end[1]*fraction); + me.zz = (start[2]*(1-fraction) +end[2]*fraction); + + return [me.xx, me.yy, me.zz]; + }, + + # move distance 'along' from start towards end. Total dist from start to end is dist. + alongVector: func (start, end, dist, along) { + me.fraction = along/dist; + return me.interpolateVector(start, end, me.fraction); + }, # Orthogonal projection of a vector `vec` onto another `ref` !!can throw an exception if the referential vector is null!!. orthogonalProjection: func(vec, ref){ @@ -319,9 +434,9 @@ var Math = { #| 0 cos(roll) -sin(roll) | #| 0 sin(roll) cos(roll) | # -#| cos(pitch) 0 -sin(pitch) | -#| 0 1 0 | -#| sin(pitch) 0 cos(pitch) | +#| cos(-pitch) 0 sin(-pitch) | +#| 0 1 0 | +#| -sin(-pitch) 0 cos(-pitch) | # #| cos(yaw) -sin(yaw) 0 | #| sin(yaw) cos(yaw) 0 | @@ -336,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/ZSU-23/zsu-23-set.xml b/ZSU-23/zsu-23-set.xml index cf3c003..ecf0837 100644 --- a/ZSU-23/zsu-23-set.xml +++ b/ZSU-23/zsu-23-set.xml @@ -1,65 +1,16 @@ - - false - - - - - - 15.0 - - - 0.5 - 4.0 - - - 0.32 - 0.45 - - - 30.0 - - - 20.0 - - true - - - - - - pinto, onox, Rudolf + + Rudolf, pinto, Nikolai V. Chr., onox ZSU-23-4M "Shilka" AAA defence system alpha 0.1 @@ -104,37 +55,34 @@ - + - 0 - - 0 - 0 - 0 - 0 + 0 + + 0 + 0 + 0 + 0 + false + - - Systems/fbw.xml - - - Systems/fdm.xml - - - Systems/radar-pos.xml\ - + + Systems/radar-pos.xml\ + - + - + true + @@ -242,9 +190,45 @@ 0 + + + target1 + target2 + target3 + target4 + target5 + target6 + target7 + target8 + target9 + targetA + targetB + targetC + + + 0 + 1 + + 2 + true + 5 + false + true + false + false + false + false + false + 2.25 + false + + rotors/main/blade[0]/flap-deg + rotors/main/blade[1]/flap-deg + rotors/main/blade[2]/flap-deg + rotors/main/blade[2]/position-deg Aircraft/ZSU-23/Models/emesary/ true true @@ -265,14 +249,19 @@ Aircraft/ZSU-23/Nasal/fdm.nas - - - Aircraft/ZSU-23/Nasal/damage.nas - + + Aircraft/ZSU-23/Nasal/vector.nas + + + Aircraft/ZSU-23/Nasal/radar-logic.nas + + + Aircraft/ZSU-23/Nasal/damage.nas + + + Aircraft/ZSU-23/Nasal/fire-control.nas + - - - diff --git a/depot/Nasal/damage.nas b/depot/Nasal/damage.nas index 5db1ccc..44b753a 100644 --- a/depot/Nasal/damage.nas +++ b/depot/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 10;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 900;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -66,7 +68,7 @@ var shells = { var warheads = { # [id,lbs,anti surface,cluster,(name)] - "AGM-65": [ 0, 126.00,1,0], + "AGM-65B": [ 0, 126.00,1,0], "AGM-84": [ 1, 488.00,1,0], "AGM-88": [ 2, 146.00,1,0], "MK-82SE": [ 3, 192.00,1,0],# snake eye @@ -75,12 +77,12 @@ var warheads = { "AGM-158": [ 6, 1000.00,1,0], "ALARM": [ 7, 450.00,1,0], "AM 39 Exocet": [ 8, 364.00,1,0], - "AS 37 Martel": [ 9, 330.00,1,0], + "AS 37 Martel": [ 9, 330.00,1,0],# Also : AJ 168 Martel "AS30L": [10, 529.00,1,0], "BL755": [11, 100.00,1,1],# 800lb bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-87": [12, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-105": [13, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. - "AJ 168 Martel": [14, 330.00,1,0], + "AS 37 Armat": [14, 330.00,1,0], "FAB-100": [15, 92.59,1,0], "FAB-250": [16, 202.85,1,0], "FAB-500": [17, 564.38,1,0], @@ -88,7 +90,7 @@ var warheads = { "GBU-24": [19, 945.00,1,0], "GBU-31": [20, 945.00,1,0], "GBU-54": [21, 190.00,1,0], - "GBU-10": [22, 2000.00,1,0], + "GBU-10": [22, 945.00,1,0], "GBU-16": [23, 450.00,1,0], "HVAR": [24, 7.50,1,0],#P51 "KAB-500": [25, 564.38,1,0], @@ -124,14 +126,14 @@ var warheads = { "AIM-7F": [55, 88.00,0,0], "AGM-62": [56, 2000.00,1,0], "AIM-9L": [57, 20.80,0,0], - "d-7": [58, 44.00,0,0],#deprecated + "AGM-65D": [58, 126.00,1,0], "AIM-132": [59, 22.05,0,0], - "d-8": [60, 20.80,0,0],#deprecated + "Apache AP": [60, 110.23,0,1],# Real mass of bomblet. (x 10). Anti runway. "KN-06": [61, 315.00,0,0], "9M317": [62, 145.00,0,0], - "d9": [63, 27.00,0,0],#deprecated + "GEM": [63, 185.00,0,0],#MIM-104D "R.550 Magic": [64, 26.45,0,0],# also called majic - "d-0": [65, 30.00,0,0],#deprecated + "5Ya23": [65, 414.00,0,0],#Volga-M "R.550 Magic 2": [66, 27.00,0,0], "R.530": [67, 55.00,0,0], "MK-82AIR": [68, 192.00,1,0], @@ -162,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -224,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -256,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -267,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -289,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -297,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -310,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -319,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -345,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -378,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -423,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -436,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -455,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -466,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -606,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -614,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -624,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -643,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -667,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -710,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -736,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -810,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -818,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -838,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -846,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -1005,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1039,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1053,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1078,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1137,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1150,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1176,16 +1300,17 @@ var code_ct = func () { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1203,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1282,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/depot/Nasal/fdm.nas b/depot/Nasal/fdm.nas index 725da8d..40df393 100644 --- a/depot/Nasal/fdm.nas +++ b/depot/Nasal/fdm.nas @@ -42,26 +42,26 @@ var PositionUpdater = func () { var type = getprop("sim/multiplay/generic/int[17]"); if (type != last_type) { if (damage.hp_max == damage.hp) { - if (type == 0) {damage.hp_max=900;damage.hp=900;} - if (type == 1) {damage.hp_max=500;damage.hp=500;} - if (type == 2) {damage.hp_max=200;damage.hp=200;} - if (type == 3) {damage.hp_max=300;damage.hp=300;} - if (type == 4) {damage.hp_max=900;damage.hp=900;} - if (type == 5) {damage.hp_max=1500;damage.hp=1500;} - if (type == 6) {damage.hp_max=450;damage.hp=450;} - if (type == 7) {damage.hp_max=0;damage.hp=0;} - if (type == 8) {damage.hp_max=300;damage.hp=300;} - if (type == 9) {damage.hp_max=300;damage.hp=300;} - if (type == 10) {damage.hp_max=500;damage.hp=500;} - if (type == 11) {damage.hp_max=200;damage.hp=200;} - if (type == 12) {damage.hp_max=0;damage.hp=0;} - if (type == 14) {damage.hp_max=600;damage.hp=600;} - if (type == 15) {damage.hp_max=1400;damage.hp=1400;} - if (type == 16) {damage.hp_max=100;damage.hp=100;} - if (type == 17) {damage.hp_max=300;damage.hp=300;} - if (type == 18) {damage.hp_max=50;damage.hp=50;} - if (type == 19) {damage.hp_max=2500;damage.hp=2500;} - if (type == 20) {damage.hp_max=300;damage.hp=300;} + if (type == 0) {damage.hp_max=900;damage.hp=damage.hp_max;} + if (type == 1) {damage.hp_max=500;damage.hp=damage.hp_max;} + if (type == 2) {damage.hp_max=200;damage.hp=damage.hp_max;} + if (type == 3) {damage.hp_max=300;damage.hp=damage.hp_max;} + if (type == 4) {damage.hp_max=900;damage.hp=damage.hp_max;} + if (type == 5) {damage.hp_max=1500;damage.hp=damage.hp_max;} + if (type == 6) {damage.hp_max=450;damage.hp=damage.hp_max;} + if (type == 7) {damage.hp_max=0;damage.hp=damage.hp_max;} + if (type == 8) {damage.hp_max=300;damage.hp=damage.hp_max;} + if (type == 9) {damage.hp_max=300;damage.hp=damage.hp_max;} + if (type == 10) {damage.hp_max=500;damage.hp=damage.hp_max;} + if (type == 11) {damage.hp_max=200;damage.hp=damage.hp_max;} + if (type == 12) {damage.hp_max=0;damage.hp=damage.hp_max;} + if (type == 14) {damage.hp_max=600;damage.hp=damage.hp_max;} + if (type == 15) {damage.hp_max=1400;damage.hp=damage.hp_max;} + if (type == 16) {damage.hp_max=100;damage.hp=damage.hp_max;} + if (type == 17) {damage.hp_max=300;damage.hp=damage.hp_max;} + if (type == 18) {damage.hp_max=50;damage.hp=damage.hp_max;} + if (type == 19) {damage.hp_max=2500;damage.hp=damage.hp_max;} + if (type == 20) {damage.hp_max=300;damage.hp=damage.hp_max;} last_type = type; } else { print("Can only switch type when not damaged!!"); diff --git a/depot/Nasal/vector.nas b/depot/Nasal/vector.nas index 36ae539..621b0cb 100644 --- a/depot/Nasal/vector.nas +++ b/depot/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.93 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -13,6 +13,8 @@ var Math = { # When doing euler angles (from pilots point of view): yaw = yaw left, pitch = rotate up, roll = roll right. # FG rotations: heading = yaw right, pitch = rotate up, roll = roll right. # + # Cartesian is right-handed coord system. + # clamp: func(v, min, max) { v < min ? min : v > max ? max : v }, convertCoords: func (x,y,z) { @@ -37,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -55,9 +59,27 @@ var Math = { return R2D * math.acos(me.value); }, + # Rodrigues' rotation formula. Use unitVectorAxis as axis, and rotate around it. + rotateVectorAroundVector: func (a, unitVectorAxis, angle) { + return me.plus( + me.plus( + me.product(math.cos(angle*D2R),a), + me.product(math.sin(angle*D2R), me.crossProduct(unitVectorAxis,a)) + ), + me.product((1-math.cos(angle*D2R))*me.dotProduct(unitVectorAxis,a), unitVectorAxis) + ); + }, + + #Rotate a certain amound of degrees towards 'towardsMe'. + rotateVectorTowardsVector: func (a, towardsMe, angle) { + return me.rotateVectorAroundVector(a, me.normalize(me.crossProduct(a, towardsMe)), angle); + }, + # length of vector magnitudeVector: func (a) { - return math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2)); + me.mag = 1; + call(func {me.mag = math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2))},nil,nil,var err =[]);#strange bug in Nasal can sometimes mess up sqrt + return me.mag; }, # dot product of 2 vectors @@ -65,8 +87,20 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { + me.rollM = me.rollMatrix(roll); + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) + yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); @@ -74,15 +108,34 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) - yawPitchRollVector: func (yaw, pitch, roll, vector) { - me.rollM = me.rollMatrix(roll); + # rotate a vector. Order: yaw, pitch + yawPitchVector: func (yaw, pitch, vector) { me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.pitchM, me.yawM); return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: pitch, yaw + pitchYawVector: func (pitch, yaw, vector) { + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.pitchM); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + + # rotate a vector. Order: pitch + pitchVector: func (pitch, vector) { + me.pitchM = me.pitchMatrix(pitch); + return me.multiplyMatrixWithVector(me.pitchM, vector); + }, + # multiply 3x3 matrix with vector multiplyMatrixWithVector: func (matrix, vector) { return [matrix[0]*vector[0]+matrix[1]*vector[1]+matrix[2]*vector[2], @@ -107,10 +160,10 @@ var Math = { # matrix for pitching pitchMatrix: func (pitch) { - pitch = pitch * D2R; - return [math.cos(pitch),0,-math.sin(pitch), + pitch = -pitch * D2R; + return [math.cos(pitch),0,math.sin(pitch), 0,1,0, - math.sin(pitch),0,math.cos(pitch)]; + -math.sin(pitch),0,math.cos(pitch)]; }, # matrix for yawing @@ -126,7 +179,8 @@ var Math = { me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); if (me.horz != 0) { me.pitch = math.atan2(vector[2],me.horz)*R2D; - me.hdg = math.asin(-vector[1]/me.horz)*R2D; + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; if (vector[0] < 0) { # south @@ -144,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -242,8 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, + + # Project a onto ontoMe. + projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety + return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); + }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -268,7 +369,7 @@ var Math = { # print vector to console format: func (v) { - return sprintf("(%.1f, %.1f, %.1f)",v[0],v[1],v[2]); + return sprintf("(%.2f, %.2f, %.2f)",v[0],v[1],v[2]); }, # make vector length 1.0 @@ -288,6 +389,20 @@ var Math = { return me.magnitudeVector(me.crossProduct(me.minus(L2,L1), me.minus(L1,P)))/me.magnitudeVector(me.minus(L2,L1)); }, + + interpolateVector: func (start, end, fraction) { + me.xx = (start[0]*(1-fraction) +end[0]*fraction); + me.yy = (start[1]*(1-fraction) +end[1]*fraction); + me.zz = (start[2]*(1-fraction) +end[2]*fraction); + + return [me.xx, me.yy, me.zz]; + }, + + # move distance 'along' from start towards end. Total dist from start to end is dist. + alongVector: func (start, end, dist, along) { + me.fraction = along/dist; + return me.interpolateVector(start, end, me.fraction); + }, # Orthogonal projection of a vector `vec` onto another `ref` !!can throw an exception if the referential vector is null!!. orthogonalProjection: func(vec, ref){ @@ -319,9 +434,9 @@ var Math = { #| 0 cos(roll) -sin(roll) | #| 0 sin(roll) cos(roll) | # -#| cos(pitch) 0 -sin(pitch) | -#| 0 1 0 | -#| sin(pitch) 0 cos(pitch) | +#| cos(-pitch) 0 sin(-pitch) | +#| 0 1 0 | +#| -sin(-pitch) 0 cos(-pitch) | # #| cos(yaw) -sin(yaw) 0 | #| sin(yaw) cos(yaw) 0 | @@ -336,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/depot/Sounds/sound.xml b/depot/Sounds/sound.xml index f42ec74..f66a3e9 100644 --- a/depot/Sounds/sound.xml +++ b/depot/Sounds/sound.xml @@ -1,22 +1,5 @@ - - diff --git a/depot/depot-common.xml b/depot/depot-common.xml index 831466b..7f0a98f 100644 --- a/depot/depot-common.xml +++ b/depot/depot-common.xml @@ -3,7 +3,7 @@ - pinto + pinto, Nikolai V. Chr., FGUK depot alpha 0.2 @@ -50,6 +50,7 @@ 0 1 0 + @@ -67,6 +68,20 @@ + + 10 + true + 900 + false + true + false + false + false + false + false + 2.25 + false + Aircraft/depot/Models/emesary/ true diff --git a/fleet/Nasal/damage.nas b/fleet/Nasal/damage.nas index 6ee9e4d..44b753a 100644 --- a/fleet/Nasal/damage.nas +++ b/fleet/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 20;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 200;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 1; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -66,7 +68,7 @@ var shells = { var warheads = { # [id,lbs,anti surface,cluster,(name)] - "AGM-65": [ 0, 126.00,1,0], + "AGM-65B": [ 0, 126.00,1,0], "AGM-84": [ 1, 488.00,1,0], "AGM-88": [ 2, 146.00,1,0], "MK-82SE": [ 3, 192.00,1,0],# snake eye @@ -75,12 +77,12 @@ var warheads = { "AGM-158": [ 6, 1000.00,1,0], "ALARM": [ 7, 450.00,1,0], "AM 39 Exocet": [ 8, 364.00,1,0], - "AS 37 Martel": [ 9, 330.00,1,0], + "AS 37 Martel": [ 9, 330.00,1,0],# Also : AJ 168 Martel "AS30L": [10, 529.00,1,0], "BL755": [11, 100.00,1,1],# 800lb bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-87": [12, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-105": [13, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. - "AJ 168 Martel": [14, 330.00,1,0], + "AS 37 Armat": [14, 330.00,1,0], "FAB-100": [15, 92.59,1,0], "FAB-250": [16, 202.85,1,0], "FAB-500": [17, 564.38,1,0], @@ -88,7 +90,7 @@ var warheads = { "GBU-24": [19, 945.00,1,0], "GBU-31": [20, 945.00,1,0], "GBU-54": [21, 190.00,1,0], - "GBU-10": [22, 2000.00,1,0], + "GBU-10": [22, 945.00,1,0], "GBU-16": [23, 450.00,1,0], "HVAR": [24, 7.50,1,0],#P51 "KAB-500": [25, 564.38,1,0], @@ -124,14 +126,14 @@ var warheads = { "AIM-7F": [55, 88.00,0,0], "AGM-62": [56, 2000.00,1,0], "AIM-9L": [57, 20.80,0,0], - "d-7": [58, 44.00,0,0],#deprecated + "AGM-65D": [58, 126.00,1,0], "AIM-132": [59, 22.05,0,0], - "d-8": [60, 20.80,0,0],#deprecated + "Apache AP": [60, 110.23,0,1],# Real mass of bomblet. (x 10). Anti runway. "KN-06": [61, 315.00,0,0], "9M317": [62, 145.00,0,0], - "d9": [63, 27.00,0,0],#deprecated + "GEM": [63, 185.00,0,0],#MIM-104D "R.550 Magic": [64, 26.45,0,0],# also called majic - "d-0": [65, 30.00,0,0],#deprecated + "5Ya23": [65, 414.00,0,0],#Volga-M "R.550 Magic 2": [66, 27.00,0,0], "R.530": [67, 55.00,0,0], "MK-82AIR": [68, 192.00,1,0], @@ -162,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -224,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -256,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -267,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -289,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -297,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -310,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -319,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -345,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -378,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -423,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -436,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -455,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -466,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -606,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -614,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -624,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -643,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -667,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -710,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -736,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -810,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -818,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -838,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -846,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -1005,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1039,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1053,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1078,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1137,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1150,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1176,16 +1300,17 @@ var code_ct = func () { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1203,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1282,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/fleet/Nasal/fdm.nas b/fleet/Nasal/fdm.nas index 49101f2..b53dfb8 100644 --- a/fleet/Nasal/fdm.nas +++ b/fleet/Nasal/fdm.nas @@ -77,7 +77,7 @@ var PositionUpdater = func () { # Set new position setprop("/position/latitude-deg", position.lat()); setprop("/position/longitude-deg", position.lon()); - var g_alt = getprop("/position/ground-elev-ft")+1; + var g_alt = getprop("/position/ground-elev-ft"); setprop("/position/altitude-ft",g_alt); # Update heading diff --git a/fleet/Nasal/vector.nas b/fleet/Nasal/vector.nas index 36ae539..621b0cb 100644 --- a/fleet/Nasal/vector.nas +++ b/fleet/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.93 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -13,6 +13,8 @@ var Math = { # When doing euler angles (from pilots point of view): yaw = yaw left, pitch = rotate up, roll = roll right. # FG rotations: heading = yaw right, pitch = rotate up, roll = roll right. # + # Cartesian is right-handed coord system. + # clamp: func(v, min, max) { v < min ? min : v > max ? max : v }, convertCoords: func (x,y,z) { @@ -37,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -55,9 +59,27 @@ var Math = { return R2D * math.acos(me.value); }, + # Rodrigues' rotation formula. Use unitVectorAxis as axis, and rotate around it. + rotateVectorAroundVector: func (a, unitVectorAxis, angle) { + return me.plus( + me.plus( + me.product(math.cos(angle*D2R),a), + me.product(math.sin(angle*D2R), me.crossProduct(unitVectorAxis,a)) + ), + me.product((1-math.cos(angle*D2R))*me.dotProduct(unitVectorAxis,a), unitVectorAxis) + ); + }, + + #Rotate a certain amound of degrees towards 'towardsMe'. + rotateVectorTowardsVector: func (a, towardsMe, angle) { + return me.rotateVectorAroundVector(a, me.normalize(me.crossProduct(a, towardsMe)), angle); + }, + # length of vector magnitudeVector: func (a) { - return math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2)); + me.mag = 1; + call(func {me.mag = math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2))},nil,nil,var err =[]);#strange bug in Nasal can sometimes mess up sqrt + return me.mag; }, # dot product of 2 vectors @@ -65,8 +87,20 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { + me.rollM = me.rollMatrix(roll); + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) + yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); @@ -74,15 +108,34 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) - yawPitchRollVector: func (yaw, pitch, roll, vector) { - me.rollM = me.rollMatrix(roll); + # rotate a vector. Order: yaw, pitch + yawPitchVector: func (yaw, pitch, vector) { me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.pitchM, me.yawM); return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: pitch, yaw + pitchYawVector: func (pitch, yaw, vector) { + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.pitchM); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + + # rotate a vector. Order: pitch + pitchVector: func (pitch, vector) { + me.pitchM = me.pitchMatrix(pitch); + return me.multiplyMatrixWithVector(me.pitchM, vector); + }, + # multiply 3x3 matrix with vector multiplyMatrixWithVector: func (matrix, vector) { return [matrix[0]*vector[0]+matrix[1]*vector[1]+matrix[2]*vector[2], @@ -107,10 +160,10 @@ var Math = { # matrix for pitching pitchMatrix: func (pitch) { - pitch = pitch * D2R; - return [math.cos(pitch),0,-math.sin(pitch), + pitch = -pitch * D2R; + return [math.cos(pitch),0,math.sin(pitch), 0,1,0, - math.sin(pitch),0,math.cos(pitch)]; + -math.sin(pitch),0,math.cos(pitch)]; }, # matrix for yawing @@ -126,7 +179,8 @@ var Math = { me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); if (me.horz != 0) { me.pitch = math.atan2(vector[2],me.horz)*R2D; - me.hdg = math.asin(-vector[1]/me.horz)*R2D; + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; if (vector[0] < 0) { # south @@ -144,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -242,8 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, + + # Project a onto ontoMe. + projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety + return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); + }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -268,7 +369,7 @@ var Math = { # print vector to console format: func (v) { - return sprintf("(%.1f, %.1f, %.1f)",v[0],v[1],v[2]); + return sprintf("(%.2f, %.2f, %.2f)",v[0],v[1],v[2]); }, # make vector length 1.0 @@ -288,6 +389,20 @@ var Math = { return me.magnitudeVector(me.crossProduct(me.minus(L2,L1), me.minus(L1,P)))/me.magnitudeVector(me.minus(L2,L1)); }, + + interpolateVector: func (start, end, fraction) { + me.xx = (start[0]*(1-fraction) +end[0]*fraction); + me.yy = (start[1]*(1-fraction) +end[1]*fraction); + me.zz = (start[2]*(1-fraction) +end[2]*fraction); + + return [me.xx, me.yy, me.zz]; + }, + + # move distance 'along' from start towards end. Total dist from start to end is dist. + alongVector: func (start, end, dist, along) { + me.fraction = along/dist; + return me.interpolateVector(start, end, me.fraction); + }, # Orthogonal projection of a vector `vec` onto another `ref` !!can throw an exception if the referential vector is null!!. orthogonalProjection: func(vec, ref){ @@ -319,9 +434,9 @@ var Math = { #| 0 cos(roll) -sin(roll) | #| 0 sin(roll) cos(roll) | # -#| cos(pitch) 0 -sin(pitch) | -#| 0 1 0 | -#| sin(pitch) 0 cos(pitch) | +#| cos(-pitch) 0 sin(-pitch) | +#| 0 1 0 | +#| -sin(-pitch) 0 cos(-pitch) | # #| cos(yaw) -sin(yaw) 0 | #| sin(yaw) cos(yaw) 0 | @@ -336,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/fleet/fleet-set.xml b/fleet/fleet-set.xml index e8e3061..2cff47d 100644 --- a/fleet/fleet-set.xml +++ b/fleet/fleet-set.xml @@ -60,7 +60,7 @@ false - + pinto, onox, Nikolai V. Chr., Alexis Bory, Detlef Faber, T. Henkel Fleet @@ -143,6 +143,7 @@ false false false + @@ -150,23 +151,7 @@ true - - - - nasal - - - - - - enemies - - dialog-show - enemies - - + @@ -194,6 +179,36 @@ enemies/e12 + + Follow + lookat + false + + + true + rotors/main/blade[0]/flap-deg + rotors/main/blade[1]/flap-deg + rotors/main/blade[2]/position-deg + /position/latitude-deg + /position/longitude-deg + /position/altitude-ft + + /orientation/heading-deg + 0 + 0 + 50 + 50 + 25 + 120 + + false + + + + @@ -344,9 +359,33 @@ 50 - + + 20 + true + 200 + false + true + true + false + false + false + false + 2.25 + false + Aircraft/fleet/Models/Armament/Weapons/ + true + true + rotors/main/blade[0]/flap-deg + rotors/main/blade[1]/flap-deg + rotors/main/blade[2]/flap-deg + rotors/main/blade[2]/position-deg + true + true + 1 + controls/armament + station true true true @@ -373,19 +412,22 @@ - - Leto - swamp - pinto - fb237 - Wilco-1 - Kokos - J-Mav16 - S - Raider1 - Slobb - 5H1N0B1 - SNOWY1 + 0 + 1 + + + target1 + target2 + target3 + target4 + target5 + target6 + target7 + target8 + target9 + targetA + targetB + targetC @@ -413,8 +455,4 @@ - - - - diff --git a/gci-radar/Nasal/damage.nas b/gci-radar/Nasal/damage.nas index c4434f2..44b753a 100644 --- a/gci-radar/Nasal/damage.nas +++ b/gci-radar/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 10;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 80;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -66,7 +68,7 @@ var shells = { var warheads = { # [id,lbs,anti surface,cluster,(name)] - "AGM-65": [ 0, 126.00,1,0], + "AGM-65B": [ 0, 126.00,1,0], "AGM-84": [ 1, 488.00,1,0], "AGM-88": [ 2, 146.00,1,0], "MK-82SE": [ 3, 192.00,1,0],# snake eye @@ -75,12 +77,12 @@ var warheads = { "AGM-158": [ 6, 1000.00,1,0], "ALARM": [ 7, 450.00,1,0], "AM 39 Exocet": [ 8, 364.00,1,0], - "AS 37 Martel": [ 9, 330.00,1,0], + "AS 37 Martel": [ 9, 330.00,1,0],# Also : AJ 168 Martel "AS30L": [10, 529.00,1,0], "BL755": [11, 100.00,1,1],# 800lb bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-87": [12, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-105": [13, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. - "AJ 168 Martel": [14, 330.00,1,0], + "AS 37 Armat": [14, 330.00,1,0], "FAB-100": [15, 92.59,1,0], "FAB-250": [16, 202.85,1,0], "FAB-500": [17, 564.38,1,0], @@ -88,7 +90,7 @@ var warheads = { "GBU-24": [19, 945.00,1,0], "GBU-31": [20, 945.00,1,0], "GBU-54": [21, 190.00,1,0], - "GBU-10": [22, 2000.00,1,0], + "GBU-10": [22, 945.00,1,0], "GBU-16": [23, 450.00,1,0], "HVAR": [24, 7.50,1,0],#P51 "KAB-500": [25, 564.38,1,0], @@ -124,14 +126,14 @@ var warheads = { "AIM-7F": [55, 88.00,0,0], "AGM-62": [56, 2000.00,1,0], "AIM-9L": [57, 20.80,0,0], - "d-7": [58, 44.00,0,0],#deprecated + "AGM-65D": [58, 126.00,1,0], "AIM-132": [59, 22.05,0,0], - "d-8": [60, 20.80,0,0],#deprecated + "Apache AP": [60, 110.23,0,1],# Real mass of bomblet. (x 10). Anti runway. "KN-06": [61, 315.00,0,0], "9M317": [62, 145.00,0,0], - "d9": [63, 27.00,0,0],#deprecated + "GEM": [63, 185.00,0,0],#MIM-104D "R.550 Magic": [64, 26.45,0,0],# also called majic - "d-0": [65, 30.00,0,0],#deprecated + "5Ya23": [65, 414.00,0,0],#Volga-M "R.550 Magic 2": [66, 27.00,0,0], "R.530": [67, 55.00,0,0], "MK-82AIR": [68, 192.00,1,0], @@ -162,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -224,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -256,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -267,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -289,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -297,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -310,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -319,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -345,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -378,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -423,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -436,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -455,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -466,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -606,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -614,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -624,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -643,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -667,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -710,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -736,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -810,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -818,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -838,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -846,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -1005,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1039,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1053,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1078,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1137,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1150,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1176,16 +1300,17 @@ var code_ct = func () { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1203,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1282,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/gci-radar/Nasal/gci.nas b/gci-radar/Nasal/gci.nas index e76354b..ba6d5fa 100644 --- a/gci-radar/Nasal/gci.nas +++ b/gci-radar/Nasal/gci.nas @@ -26,6 +26,7 @@ var prop_watch = { "JA37Di-Viggen": [20,21,22], "AJ37-Viggen": [20,21,22], "AJS37-Viggen": [20,21,22], + "Jaguar-GR1": [20,21,22], }; var radar_stations = [ diff --git a/gci-radar/Nasal/vector.nas b/gci-radar/Nasal/vector.nas index 36ae539..621b0cb 100644 --- a/gci-radar/Nasal/vector.nas +++ b/gci-radar/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.93 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -13,6 +13,8 @@ var Math = { # When doing euler angles (from pilots point of view): yaw = yaw left, pitch = rotate up, roll = roll right. # FG rotations: heading = yaw right, pitch = rotate up, roll = roll right. # + # Cartesian is right-handed coord system. + # clamp: func(v, min, max) { v < min ? min : v > max ? max : v }, convertCoords: func (x,y,z) { @@ -37,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -55,9 +59,27 @@ var Math = { return R2D * math.acos(me.value); }, + # Rodrigues' rotation formula. Use unitVectorAxis as axis, and rotate around it. + rotateVectorAroundVector: func (a, unitVectorAxis, angle) { + return me.plus( + me.plus( + me.product(math.cos(angle*D2R),a), + me.product(math.sin(angle*D2R), me.crossProduct(unitVectorAxis,a)) + ), + me.product((1-math.cos(angle*D2R))*me.dotProduct(unitVectorAxis,a), unitVectorAxis) + ); + }, + + #Rotate a certain amound of degrees towards 'towardsMe'. + rotateVectorTowardsVector: func (a, towardsMe, angle) { + return me.rotateVectorAroundVector(a, me.normalize(me.crossProduct(a, towardsMe)), angle); + }, + # length of vector magnitudeVector: func (a) { - return math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2)); + me.mag = 1; + call(func {me.mag = math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2))},nil,nil,var err =[]);#strange bug in Nasal can sometimes mess up sqrt + return me.mag; }, # dot product of 2 vectors @@ -65,8 +87,20 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { + me.rollM = me.rollMatrix(roll); + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) + yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); @@ -74,15 +108,34 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) - yawPitchRollVector: func (yaw, pitch, roll, vector) { - me.rollM = me.rollMatrix(roll); + # rotate a vector. Order: yaw, pitch + yawPitchVector: func (yaw, pitch, vector) { me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.pitchM, me.yawM); return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: pitch, yaw + pitchYawVector: func (pitch, yaw, vector) { + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.pitchM); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + + # rotate a vector. Order: pitch + pitchVector: func (pitch, vector) { + me.pitchM = me.pitchMatrix(pitch); + return me.multiplyMatrixWithVector(me.pitchM, vector); + }, + # multiply 3x3 matrix with vector multiplyMatrixWithVector: func (matrix, vector) { return [matrix[0]*vector[0]+matrix[1]*vector[1]+matrix[2]*vector[2], @@ -107,10 +160,10 @@ var Math = { # matrix for pitching pitchMatrix: func (pitch) { - pitch = pitch * D2R; - return [math.cos(pitch),0,-math.sin(pitch), + pitch = -pitch * D2R; + return [math.cos(pitch),0,math.sin(pitch), 0,1,0, - math.sin(pitch),0,math.cos(pitch)]; + -math.sin(pitch),0,math.cos(pitch)]; }, # matrix for yawing @@ -126,7 +179,8 @@ var Math = { me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); if (me.horz != 0) { me.pitch = math.atan2(vector[2],me.horz)*R2D; - me.hdg = math.asin(-vector[1]/me.horz)*R2D; + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; if (vector[0] < 0) { # south @@ -144,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -242,8 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, + + # Project a onto ontoMe. + projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety + return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); + }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -268,7 +369,7 @@ var Math = { # print vector to console format: func (v) { - return sprintf("(%.1f, %.1f, %.1f)",v[0],v[1],v[2]); + return sprintf("(%.2f, %.2f, %.2f)",v[0],v[1],v[2]); }, # make vector length 1.0 @@ -288,6 +389,20 @@ var Math = { return me.magnitudeVector(me.crossProduct(me.minus(L2,L1), me.minus(L1,P)))/me.magnitudeVector(me.minus(L2,L1)); }, + + interpolateVector: func (start, end, fraction) { + me.xx = (start[0]*(1-fraction) +end[0]*fraction); + me.yy = (start[1]*(1-fraction) +end[1]*fraction); + me.zz = (start[2]*(1-fraction) +end[2]*fraction); + + return [me.xx, me.yy, me.zz]; + }, + + # move distance 'along' from start towards end. Total dist from start to end is dist. + alongVector: func (start, end, dist, along) { + me.fraction = along/dist; + return me.interpolateVector(start, end, me.fraction); + }, # Orthogonal projection of a vector `vec` onto another `ref` !!can throw an exception if the referential vector is null!!. orthogonalProjection: func(vec, ref){ @@ -319,9 +434,9 @@ var Math = { #| 0 cos(roll) -sin(roll) | #| 0 sin(roll) cos(roll) | # -#| cos(pitch) 0 -sin(pitch) | -#| 0 1 0 | -#| sin(pitch) 0 cos(pitch) | +#| cos(-pitch) 0 sin(-pitch) | +#| 0 1 0 | +#| -sin(-pitch) 0 cos(-pitch) | # #| cos(yaw) -sin(yaw) 0 | #| sin(yaw) cos(yaw) 0 | @@ -336,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/gci-radar/Sounds/sound.xml b/gci-radar/Sounds/sound.xml index f42ec74..f742ac9 100644 --- a/gci-radar/Sounds/sound.xml +++ b/gci-radar/Sounds/sound.xml @@ -1,21 +1,5 @@ - diff --git a/gci-radar/gci-set.xml b/gci-radar/gci-set.xml index 394307c..3dd9b25 100644 --- a/gci-radar/gci-set.xml +++ b/gci-radar/gci-set.xml @@ -1,11 +1,6 @@ - - - false - - pinto gci @@ -111,6 +106,7 @@ 0 0 + @@ -121,6 +117,20 @@ + + 10 + true + 80 + false + true + false + false + false + false + false + 2.25 + false + Aircraft/gci-radar/Models/emesary/ true diff --git a/groundtarget/Nasal/damage.nas b/groundtarget/Nasal/damage.nas index 6b270d8..44b753a 100644 --- a/groundtarget/Nasal/damage.nas +++ b/groundtarget/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 2;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 5;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -37,24 +39,30 @@ var shells = { # 0.20 means a direct hit will disable 20% of the failure modes on average. # or, 0.20 also means a direct hit can do 20 hitpoints damage. # - "M70 rocket": [0,0.250], #135mm - "S-5 rocket": [1,0.200], # 55mm - "M55 shell": [2,0.100], # 30mm - "KCA shell": [3,0.100], # 30mm - "GSh-30": [4,0.100], # 30mm mig29/su27 - "GAU-8/A": [5,0.100], # 30mm - "Mk3Z": [6,0.100], # 30mm Jaguar - "BK27": [7,0.070], # 27mm - "GSh-23": [8,0.065], # 23mm - "M61A1 shell": [9,0.050], # 20mm F14, F15, F16 - "50 BMG": [10,0.015], # 12.7mm (non-explosive) + # Damage roughly proportional to projectile weight. + # If weight isn't listed here, it was estimated from dimensions (proportional to diameter^2 * length). + # Approximate formulae for cannons: + # damage ~ weight / 3.6 (in g) + # or damage ~ diameter^2 * length / 1.6e6 (in mm) + # + "M70 rocket": [0,0.500], # 135mm, ~5kg warhead + "S-5 rocket": [1,0.200], # 55mm, ~1-2kg warhead + "M55 shell": [2,0.060], # 30x113mm, 220g + "KCA shell": [3,0.100], # 30x173mm, 360g + "GSh-30": [4,0.095], # 30x165mm mig29/su27 + "GAU-8/A": [5,0.100], # 30x173mm, 360g + "Mk3Z": [6,0.060], # 30x113mm Jaguar, 220g + "BK27": [7,0.070], # 27x145mm, 270g + "GSh-23": [8,0.040], # 23x115mm, + "M61A1 shell": [9,0.030], # 20x102mm F14, F15, F16, 100g + "50 BMG": [10,0.015], # 12.7mm (non-explosive) "7.62": [11,0.005], # 7.62mm (non-explosive) - "Hydra-70": [12,0.250], # F-16/A-6 LAU-68 and LAU-61 - "SNEB": [13,0.250], # Jaguar - "DEFA 554": [14,0.100], # 30mm Mirage - "20mm APDS": [15,0.050], # CIWS - "LAU-10": [16,0.225], # 127mm -}; + "Hydra-70": [12,0.500], # 70mm, F-16/A-6 LAU-68 and LAU-61, ~4-6kg warhead + "SNEB": [13,0.500], # 68mm, Jaguar + "DEFA 554": [14,0.060], # 30x113mm Mirage, 220g + "20mm APDS": [15,0.030], # CIWS + "LAU-10": [16,0.500], # 127mm, ~4-7kg warhead +}; # lbs of warheads is explosive+fragmentation+fuse, so total warhead mass. @@ -82,7 +90,7 @@ var warheads = { "GBU-24": [19, 945.00,1,0], "GBU-31": [20, 945.00,1,0], "GBU-54": [21, 190.00,1,0], - "GBU-10": [22, 2000.00,1,0], + "GBU-10": [22, 945.00,1,0], "GBU-16": [23, 450.00,1,0], "HVAR": [24, 7.50,1,0],#P51 "KAB-500": [25, 564.38,1,0], @@ -123,9 +131,9 @@ var warheads = { "Apache AP": [60, 110.23,0,1],# Real mass of bomblet. (x 10). Anti runway. "KN-06": [61, 315.00,0,0], "9M317": [62, 145.00,0,0], - "d9": [63, 27.00,0,0],#deprecated + "GEM": [63, 185.00,0,0],#MIM-104D "R.550 Magic": [64, 26.45,0,0],# also called majic - "d-0": [65, 30.00,0,0],#deprecated + "5Ya23": [65, 414.00,0,0],#Volga-M "R.550 Magic 2": [66, 27.00,0,0], "R.530": [67, 55.00,0,0], "MK-82AIR": [68, 192.00,1,0], @@ -156,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -218,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -250,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -261,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -283,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -291,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -304,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -313,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -339,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -372,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -417,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -430,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -449,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -460,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -600,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -608,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -618,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -637,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -661,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -704,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -730,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -804,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -812,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -832,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -840,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -999,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1033,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1047,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1072,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1131,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1144,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1170,16 +1300,17 @@ var code_ct = func () { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1197,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1276,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/groundtarget/Nasal/fdm.nas b/groundtarget/Nasal/fdm.nas index a6b927f..56665f7 100644 --- a/groundtarget/Nasal/fdm.nas +++ b/groundtarget/Nasal/fdm.nas @@ -23,20 +23,22 @@ print("loading fdm"); var frequency = 60.0; # Change in heading per second at full rudder deflection -var heading_ps = 0.5; +var heading_ps = 20; -time_last = 0; -sim_speed = 1; +var time_last = 0; +var sim_speed = 1; + +var distance = 0; var speed = 25; + +setprop("/carrier/sunk",0); +setprop("/orientation/pitch-deg", 0); + +var arrived = 0; + var last_type = 0; -setprop("/carrier/pitch-deg",0); -setprop("/carrier/pitch-offset",0); -setprop("/carrier/roll-deg",0); -setprop("/carrier/roll-offset",0); -setprop("/carrier/sunk",0); -var arrived = 0; var PositionUpdater = func () { settimer( PositionUpdater, 1/frequency ); @@ -47,7 +49,7 @@ var PositionUpdater = func () { setprop("/sim/multiplay/chat",getprop("sim/multiplay/callsign") ~ " has arrived safely!"); print("arrived"); arrived = 1; - #return; + return; } var time_now = getprop("/sim/time/elapsed-sec"); @@ -56,58 +58,70 @@ var PositionUpdater = func () { time_last = time_now; - #var heading = getprop("/orientation/heading-deg"); - #var speed = getprop("/velocities/groundspeed-kt"); - var rudder = getprop("/surface-positions/rudder-pos-norm"); if ( getprop("/carrier/sunk") == 0 and getprop("/autopilot/route-manager/active") == 1 ) { - #for event var cur_waypoint = getprop("/autopilot/route-manager/current-wp"); - var cur_wp_lon = getprop("/autopilot/route-manager/route/wp[" ~ cur_waypoint ~ "]/longitude-deg"); - var cur_wp_lat = getprop("/autopilot/route-manager/route/wp[" ~ cur_waypoint ~ "]/latitude-deg"); - var rm_destination = geo.Coord.new().set_latlon(cur_wp_lat,cur_wp_lon); - - - var heading = position.course_to(rm_destination); - - var distance = speed * globals.KT2MPS * dt; - position.apply_course_distance(heading, distance); - - # Set new position - setprop("/position/latitude-deg", position.lat()); - setprop("/position/longitude-deg", position.lon()); - var g_alt = getprop("/position/ground-elev-ft")+1; + if (cur_waypoint > -1) { + var cur_wp_lon = getprop("/autopilot/route-manager/route/wp[" ~ cur_waypoint ~ "]/longitude-deg"); + var cur_wp_lat = getprop("/autopilot/route-manager/route/wp[" ~ cur_waypoint ~ "]/latitude-deg"); + var rm_destination = geo.Coord.new().set_latlon(cur_wp_lat,cur_wp_lon); + + var heading = getprop("/orientation/heading-deg"); + + var course = position.course_to(rm_destination); + + distance = speed * KT2MPS * dt; + + var turn_max = heading_ps * dt; + + var turn = math.clamp(geo.normdeg180(course-heading),-turn_max,turn_max); + + heading += turn; + + heading = geo.normdeg(heading); + + position.apply_course_distance(heading, distance); + + + + # Set new position + setprop("/position/latitude-deg", position.lat()); + setprop("/position/longitude-deg", position.lon()); + + setprop("/orientation/heading-deg", heading); + setprop("velocities/groundspeed-kt",speed); + } else { + setprop("velocities/groundspeed-kt",0); + } + var g_alt = geo.elevation(position.lat(),position.lon());#getprop("/position/ground-elev-ft"); + if (g_alt == nil) g_alt = 0; + g_alt *= M2FT; + + var alt_diff = g_alt - getprop("/position/altitude-ft"); + + var pitch = math.atan2(alt_diff, distance*M2FT)*R2D; + + setprop("/orientation/pitch-deg", pitch); + setprop("/position/altitude-ft",g_alt); - - - - - # Update heading - var course = heading + rudder * heading_ps * dt; - setprop("/orientation/heading-deg", course); - setprop("velocities/groundspeed-kt",speed); } else { setprop("velocities/groundspeed-kt",0); } - - #set pitch - setprop("/orientation/pitch-deg",getprop("/carrier/pitch-deg") + getprop("/carrier/pitch-offset")); - + #set roll - setprop("/orientation/roll-deg",getprop("/carrier/roll-deg") + getprop("/carrier/roll-offset")); - + setprop("/orientation/roll-deg", 0); var type = getprop("sim/multiplay/generic/int[17]"); if (type != last_type) { if (damage.hp_max == damage.hp) { - if (type == 0) {damage.hp_max=5;damage.hp=5;speed=25;}#humvee - if (type == 1) {damage.hp_max=100;damage.hp=100;speed=15;}#tank - if (type == 2) {damage.hp_max=75;damage.hp=75;speed=10;}#rocket launcher - if (type == 3) {damage.hp_max=85;damage.hp=85;speed=20;}#small tank - if (type == 4) {damage.hp_max=80;damage.hp=80;speed=20;}#APC (mine proected) - if (type == 5) {damage.hp_max=75;damage.hp=75;speed=20;}#APC + if (type == 0) {damage.hp_max=5;damage.hp=damage.hp_max;speed=25;}#humvee + if (type == 1) {damage.hp_max=100;damage.hp=damage.hp_max;speed=15;}#tank + if (type == 2) {damage.hp_max=75;damage.hp=damage.hp_max;speed=10;}#rocket launcher + if (type == 3) {damage.hp_max=85;damage.hp=damage.hp_max;speed=20;}#small tank + if (type == 4) {damage.hp_max=80;damage.hp=damage.hp_max;speed=20;}#APC (mine proected) + if (type == 5) {damage.hp_max=75;damage.hp=damage.hp_max;speed=20;}#APC last_type = type; } else { print("Can only switch type when not damaged!!"); @@ -117,4 +131,7 @@ var PositionUpdater = func () { }; -PositionUpdater(); \ No newline at end of file +PositionUpdater(); + + + \ No newline at end of file diff --git a/groundtarget/Nasal/vector.nas b/groundtarget/Nasal/vector.nas index 5b995f8..621b0cb 100644 --- a/groundtarget/Nasal/vector.nas +++ b/groundtarget/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.94 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -39,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -57,6 +59,22 @@ var Math = { return R2D * math.acos(me.value); }, + # Rodrigues' rotation formula. Use unitVectorAxis as axis, and rotate around it. + rotateVectorAroundVector: func (a, unitVectorAxis, angle) { + return me.plus( + me.plus( + me.product(math.cos(angle*D2R),a), + me.product(math.sin(angle*D2R), me.crossProduct(unitVectorAxis,a)) + ), + me.product((1-math.cos(angle*D2R))*me.dotProduct(unitVectorAxis,a), unitVectorAxis) + ); + }, + + #Rotate a certain amound of degrees towards 'towardsMe'. + rotateVectorTowardsVector: func (a, towardsMe, angle) { + return me.rotateVectorAroundVector(a, me.normalize(me.crossProduct(a, towardsMe)), angle); + }, + # length of vector magnitudeVector: func (a) { me.mag = 1; @@ -69,21 +87,24 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.rollM, me.multiplyMatrices(me.pitchM, me.yawM)); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.rollM, me.multiplyMatrices(me.pitchM, me.yawM)); return me.multiplyMatrixWithVector(me.rotation, vector); }, @@ -95,6 +116,20 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: pitch, yaw + pitchYawVector: func (pitch, yaw, vector) { + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.pitchM); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + # rotate a vector. Order: pitch pitchVector: func (pitch, vector) { me.pitchM = me.pitchMatrix(pitch); @@ -163,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -261,8 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, + + # Project a onto ontoMe. + projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety + return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); + }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -369,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/groundtarget/truck-set.xml b/groundtarget/truck-set.xml index 9eec3d0..9654d48 100644 --- a/groundtarget/truck-set.xml +++ b/groundtarget/truck-set.xml @@ -46,6 +46,7 @@ 0 0 + @@ -75,6 +76,20 @@ + + 2 + true + 5 + false + true + false + false + false + false + false + 2.25 + false + Aircraft/groundtarget/Models/emesary/ true diff --git a/missile-frigate/Models/missile_frigate.xml b/missile-frigate/Models/missile_frigate.xml index f71a10a..b6ce742 100644 --- a/missile-frigate/Models/missile_frigate.xml +++ b/missile-frigate/Models/missile_frigate.xml @@ -2,8 +2,8 @@ USS SanAntonio - Naval Postgraduate School SAVAGE project, Vivian Meazza, Heiko Schulz - early-production + Naval Postgraduate School SAVAGE project, Vivian Meazza, Heiko Schulz + early-production LPD17.ac surface-positions/elevators-pos-norm -90.0
- 94.936 - 0.00 - 3.968 -
- - 0 - 1 - 0 - + 94.936 + 0.00 + 3.968 + + + 0 + 1 + 0 + @@ -109,15 +109,15 @@ surface-positions/elevators-pos-norm 90.0
- 93.243 - 0.00 - -6.464 -
- - 0 - 1 - 0 - + 93.243 + 0.00 + -6.464 + + + 0 + 1 + 0 +
diff --git a/missile-frigate/Nasal/damage.nas b/missile-frigate/Nasal/damage.nas index 032a8ed..44b753a 100644 --- a/missile-frigate/Nasal/damage.nas +++ b/missile-frigate/Nasal/damage.nas @@ -1,28 +1,30 @@ # # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) -# for damage to be recognised, the property /payload/armament/msg must be 1 +# if /payload/armament/spectator is 1 and damage off, missile trails, craters, flares, +# and missile warnings will be received, but not actual damage. # -# Authors: Nikolai V. Chr., Pinto and Richard (with improvement by Onox) +# Authors: Nikolai V. Chr., Pinto, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## -var full_damage_dist_m = 20;# Can vary from aircraft to aircraft depending on how many failure modes it has. +var full_damage_dist_m = getprop("payload/d-config/full_damage_dist_m");# Can vary from aircraft to aircraft depending on how many failure modes it has. # Many modes (like Viggen) ought to have lower number like zero. # Few modes (like F-14) ought to have larger number such as 3. # For assets this should be average radius of the asset. -var use_hitpoints_instead_of_failure_modes_bool = 1;# mainly used by assets that don't have failure modes. -var hp_max = 200;# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. -var hitable_by_air_munitions = 0; # if anti-air can do damage -var hitable_by_cannon = 1; # if cannon can do damage +var use_hitpoints_instead_of_failure_modes_bool = getprop("payload/d-config/use_hitpoints_instead_of_failure_modes_bool");# bool. mainly used by assets that don't have failure modes. +var hp_max = getprop("payload/d-config/hp_max");# given a direct hit, how much pounds of warhead is needed to kill. Only used if hitpoints is enabled. +var hitable_by_air_munitions = getprop("payload/d-config/hitable_by_air_munitions"); # if anti-air can do damage +var hitable_by_cannon = getprop("payload/d-config/hitable_by_cannon"); # if cannon can do damage #var hitable_by_ground_munitions = 1;# if anti-ground/marine can do damage -var is_fleet = 0; # Is really 7 ships, 3 of which has offensive missiles. -var rwr_to_screen=0; # for aircraft that do not yet have proper RWR -var tacview_supported=0; # For aircraft with tacview support -var m28_auto=0; # only used by automats -var mlw_max=2.25; # -var auto_flare_caller = 0; # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft +var is_fleet = getprop("payload/d-config/is_fleet"); # Is really 7 ships, 3 of which has offensive missiles. +var rwr_to_screen=getprop("payload/d-config/rwr_to_screen"); # for aircraft that do not yet have proper RWR +var rwr_audio_extended=getprop("payload/d-config/rwr_audio_extended"); # for aircraft that want seperate audio properties for different radar spikes. +var tacview_supported=getprop("payload/d-config/tacview_supported"); # For aircraft with tacview support +var m28_auto=getprop("payload/d-config/m28_auto"); # only used by automats +var mlw_max=getprop("payload/d-config/mlw_max"); # +var auto_flare_caller = getprop("payload/d-config/auto_flare_caller"); # If damage.nas should detect flare releases, or if function is called from somewhere in aircraft ############################################################################################################################ var TRUE = 1; @@ -66,7 +68,7 @@ var shells = { var warheads = { # [id,lbs,anti surface,cluster,(name)] - "AGM-65": [ 0, 126.00,1,0], + "AGM-65B": [ 0, 126.00,1,0], "AGM-84": [ 1, 488.00,1,0], "AGM-88": [ 2, 146.00,1,0], "MK-82SE": [ 3, 192.00,1,0],# snake eye @@ -75,12 +77,12 @@ var warheads = { "AGM-158": [ 6, 1000.00,1,0], "ALARM": [ 7, 450.00,1,0], "AM 39 Exocet": [ 8, 364.00,1,0], - "AS 37 Martel": [ 9, 330.00,1,0], + "AS 37 Martel": [ 9, 330.00,1,0],# Also : AJ 168 Martel "AS30L": [10, 529.00,1,0], "BL755": [11, 100.00,1,1],# 800lb bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-87": [12, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. "CBU-105": [13, 100.00,1,1],# bomblet warhead. Mix of armour piecing and HE. 100 due to need to be able to kill buk-m2. - "AJ 168 Martel": [14, 330.00,1,0], + "AS 37 Armat": [14, 330.00,1,0], "FAB-100": [15, 92.59,1,0], "FAB-250": [16, 202.85,1,0], "FAB-500": [17, 564.38,1,0], @@ -88,7 +90,7 @@ var warheads = { "GBU-24": [19, 945.00,1,0], "GBU-31": [20, 945.00,1,0], "GBU-54": [21, 190.00,1,0], - "GBU-10": [22, 2000.00,1,0], + "GBU-10": [22, 945.00,1,0], "GBU-16": [23, 450.00,1,0], "HVAR": [24, 7.50,1,0],#P51 "KAB-500": [25, 564.38,1,0], @@ -124,14 +126,14 @@ var warheads = { "AIM-7F": [55, 88.00,0,0], "AGM-62": [56, 2000.00,1,0], "AIM-9L": [57, 20.80,0,0], - "d-7": [58, 44.00,0,0],#deprecated + "AGM-65D": [58, 126.00,1,0], "AIM-132": [59, 22.05,0,0], - "d-8": [60, 20.80,0,0],#deprecated + "Apache AP": [60, 110.23,0,1],# Real mass of bomblet. (x 10). Anti runway. "KN-06": [61, 315.00,0,0], "9M317": [62, 145.00,0,0], - "d9": [63, 27.00,0,0],#deprecated + "GEM": [63, 185.00,0,0],#MIM-104D "R.550 Magic": [64, 26.45,0,0],# also called majic - "d-0": [65, 30.00,0,0],#deprecated + "5Ya23": [65, 414.00,0,0],#Volga-M "R.550 Magic 2": [66, 27.00,0,0], "R.530": [67, 55.00,0,0], "MK-82AIR": [68, 192.00,1,0], @@ -162,12 +164,46 @@ var warheads = { "pilot": [93, 0.00,1,0],# ejected pilot "BETAB-500ShP": [94, 1160.00,1,0], "Flare": [95, 0.00,0,0], + "3M9": [96, 125.00,0,0],# 3M9 Missile used with 2K12/SA-6 }; +var AIR_RADAR = "air"; + +var radar_signatures = { + "unknown-model": AIR_RADAR, + "f-14b": AIR_RADAR, + "F-14D": AIR_RADAR, + "F-15C": AIR_RADAR, + "F-15D": AIR_RADAR, + "F-16": AIR_RADAR, + "AJS37-Viggen": AIR_RADAR, + "JA37Di-Viggen": AIR_RADAR, + "m2000-5": AIR_RADAR, + "m2000-5B": AIR_RADAR, + "MiG-21bis": AIR_RADAR, + "MiG-21MF-75": AIR_RADAR, + "MiG-29": AIR_RADAR, + "SU-27": AIR_RADAR, + "EC-137R": AIR_RADAR, + "RC-137R": AIR_RADAR, + "E-8R": AIR_RADAR, + "EC-137D": AIR_RADAR, + "Mig-28": AIR_RADAR, + "SA-6": AIR_RADAR,#Air radar tone chosen so that there is at least some lock tone until asset-specific is created + "ZSU-23-4M": "gnd-23", + "S-75": "gnd-02", + "buk-m2": "gnd-11", + "s-300": "gnd-20", + "MIM104D": "gnd-p2", + "missile_frigate": "gnd-nk", + "fleet": "gnd-nk", +}; + + var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID -var heavy_smoke = [61,62,63,65,92]; +var heavy_smoke = [61,62,63,65,92,96]; var k = keys(warheads); @@ -224,12 +260,12 @@ var DamageRecipient = } # # -# This will be where movement and damage notifications are received. -# This can replace MP chat for damage notifications +# This will be where movement and damage notifications are received. +# This can replace MP chat for damage notifications # and allow missile visibility globally (i.e. all suitable equipped models) have the possibility # to receive notifications from all other suitably equipped models. if (notification.NotificationType == "ArmamentInFlightNotification" or notification.NotificationType == "ObjectInFlightNotification") { -# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, +# print("recv(d1): ",notification.NotificationType, " ", notification.Ident, # " UniqueIdentity=",notification.UniqueIdentity, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, @@ -256,10 +292,10 @@ var DamageRecipient = notification.Flags = 0; notification.RemoteCallsign = ""; } - if(getprop("payload/armament/msg") == 0 and notification.RemoteCallsign != notification.Callsign) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1 and notification.RemoteCallsign != notification.Callsign) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } - + var elapsed = getprop("sim/time/elapsed-sec"); var ownPos = geo.aircraft_position(); var bearing = ownPos.course_to(notification.Position); @@ -267,11 +303,11 @@ var DamageRecipient = var thrustOn = bits.test(notification.Flags, 1); var index = notification.SecondaryKind-21; var typ = id2warhead[index]; - + if (notification.Kind == MOVE) { if (thrustOn or index == 93 or index == 95) { # visualize missile smoke trail - + var smoke = 1; if (index == 93) { smoke = 0; @@ -289,7 +325,7 @@ var DamageRecipient = } } dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime(), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,smoke]; - + } else { # the +1.5 is the update time that missiles send notifications out in dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] @@ -297,8 +333,8 @@ var DamageRecipient = } elsif (notification.Kind == DESTROY) { dynamics["noti_"~notification.Callsign~"_"~notification.UniqueIdentity] = [systime()-(time_before_delete-1.6), notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.u_fps, notification.Heading, notification.Pitch,-1] } - - if (tacview_supported and getprop("sim/multiplay/txhost") == "mpserver.opredflag.com") { + + if (tacview_supported and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity),6); if (notification.Kind == DESTROY) { @@ -310,6 +346,7 @@ var DamageRecipient = var typp = typ[4]=="pilot"?"Parachutist":typ[4]; var extra = typp=="Parachutist"?"|0|0|0":""; var extra2 = typ[2]==0?",Type=Weapon+Missile":",Type=Weapon+Bomb"; + extra2 = typ[4]=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); @@ -319,15 +356,15 @@ var DamageRecipient = } } } - + if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } - + if (index == 95 or index == 93) { return emesary.Transmitter.ReceiptStatus_OK; } - + # Missile launch warning: if (thrustOn) { var launch = launched[notification.Callsign~notification.UniqueIdentity]; @@ -345,7 +382,7 @@ var DamageRecipient = } } } - + # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; @@ -378,6 +415,27 @@ var DamageRecipient = # debug.dump(notification); # # + if (tacview_supported and tacview.starttime and getprop("sim/multiplay/txhost") != "mpserver.opredflag.com") { + var node = getCallsign(notification.RemoteCallsign); + if (node != nil and notification.SecondaryKind > 20) { + # its a warhead + var wh = id2warhead[notification.SecondaryKind - 21]; + var lbs = wh[1]; + var hitCoord = geo.Coord.new(); + hitCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M+notification.RelativeAltitude); + if (notification.Distance > math.abs(notification.RelativeAltitude)) {#just a sanity check + hitCoord = hitCoord.apply_course_distance(notification.Bearing, math.sqrt(notification.Distance*notification.Distance-notification.RelativeAltitude*notification.RelativeAltitude)); + } + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(hitCoord.lat(),hitCoord.lon(),hitCoord.alt(), lbs*0.5); + thread.unlock(tacview.mutexWrite); + } elsif (node != nil and notification.SecondaryKind < 0) { + # its a cannon or rocket + thread.lock(tacview.mutexWrite); + tacview.writeExplosion(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), node.getNode("position/altitude-ft").getValue()*FT2M, 5); + thread.unlock(tacview.mutexWrite); + } + } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit @@ -423,10 +481,10 @@ var DamageRecipient = } var distance = math.max(dist-full_damage_dist_m, 0); - + var maxDist = 0;# distance where the explosion dont hurt us anymore var lbs = 0; - + if (wh[2] == 1) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs);#3*sqrt(lbs) @@ -436,15 +494,15 @@ var DamageRecipient = } else { return; } - + var diff = maxDist-distance; if (diff < 0) { diff = 0; } diff = diff * diff; - + var probability = diff / (maxDist*maxDist); - + if (use_hitpoints_instead_of_failure_modes_bool) { var hpDist = maxDamageDistFromWarhead(hp_max); probability = (maxDist/hpDist)*probability; @@ -455,7 +513,7 @@ var DamageRecipient = printf("Took %.1f%% damage from %s at %0.1f meters. %s systems was hit", percent,type,dist,failed); damageLog.push(sprintf("%s hit you with %s, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); - + #### # I don't remember all the considerations that went into our original warhead damage model. # But looking at the formula it looks like they all do 100% damage at 0 meter hit, @@ -466,27 +524,27 @@ var DamageRecipient = # Anyway, for hitpoint based assets, this is now the case. Maybe we should consider to also do something # similar for failure mode based aircraft. ~Nikolai #### - + ## example 1: ## # 300 lbs warhead, 50 meters distance # maxDist=52 # diff = 52-50 = 2 # diff^2 = 4 # prob = 4/2700 = 0.15% - + ## example 2: ## # 300 lbs warhead, 25 meters distance # maxDist=52 # diff = 52-25 = 27 # diff^2 = 729 # prob = 729/2700 = 27% - } + } } # } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "StaticNotification") { - if(getprop("payload/armament/msg") == 0) { + if(getprop("payload/armament/msg") == 0 and getprop("payload/armament/spectator") != 1) { return emesary.Transmitter.ReceiptStatus_NotProcessed; } if (notification.Kind == CREATE and getprop("payload/armament/enable-craters") == 1 and statics["obj_"~notification.UniqueIdentity] == nil) { @@ -606,7 +664,7 @@ var ModelManager = { } } m.model = n.getChild("model", i, 1); - + n = props.globals.getNode("sim/emesary-models", 1); for (i = 0; 1==1; i += 1) { if (n.getChild("dynamic", i, 0) == nil) { @@ -614,9 +672,9 @@ var ModelManager = { } } m.ai = n.getChild("dynamic", i, 1); - + m.model.getNode("path", 1).setValue(path); - + # Create the AI position and orientation properties. m.lat = m.ai.getNode("position/latitude-deg", 1); m.lon = m.ai.getNode("position/longitude-deg", 1); @@ -624,14 +682,14 @@ var ModelManager = { m.heading= m.ai.getNode("orientation/true-heading-deg", 1); m.pitch = m.ai.getNode("orientation/pitch-deg", 1); m.roll = m.ai.getNode("orientation/roll-deg", 1); - + m.lat.setDoubleValue(lat); m.lon.setDoubleValue(lon); m.alt_ft.setDoubleValue(alt_ft); m.heading.setDoubleValue(heading); m.pitch.setDoubleValue(para?0:pitch); m.roll.setDoubleValue(0); - + m.vLat = lat; m.vLon = lon; m.vAlt_ft = alt_ft; @@ -643,22 +701,22 @@ var ModelManager = { m.pAlt_ft = m.vAlt_ft; m.pHeading = m.vHeading; m.pPitch = m.vPitch; - - + + m.model.getNode("latitude-deg-prop", 1).setValue(m.lat.getPath()); m.model.getNode("longitude-deg-prop", 1).setValue(m.lon.getPath()); m.model.getNode("elevation-ft-prop", 1).setValue(m.alt_ft.getPath()); m.model.getNode("heading-deg-prop", 1).setValue(m.heading.getPath()); m.model.getNode("pitch-deg-prop", 1).setValue(m.pitch.getPath()); m.model.getNode("roll-deg-prop", 1).setValue(m.roll.getPath()); - + m.coord = geo.Coord.new(); m.uBody_fps = 0; m.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; m.past = m.last; m.frametime = 0; - m.delayTime = 0; - + m.delayTime = 0; + return m; }, moveRealtime: func (uBody_fps, dt, factor) { @@ -667,17 +725,17 @@ var ModelManager = { me.uBody_fps = uBody_fps; me.alt_dist = me.slant_ft*math.sin(me.vPitch*D2R); me.horiz_dist = me.slant_ft*math.cos(me.vPitch*D2R); - + me.coord.set_latlon(me.vLat, me.vLon, (me.vAlt_ft+me.alt_dist) * FT2M); - + me.coord = me.coord.apply_course_distance(me.vHeading, me.horiz_dist*FT2M); - + me.latlon = me.coord.latlon(); - + me.vLat = me.latlon[0]; me.vLon = me.latlon[1]; me.vAlt_ft = me.latlon[2]*M2FT; - + me.lat.setDoubleValue(me.vLat); me.lon.setDoubleValue(me.vLon); me.alt_ft.setDoubleValue(me.vAlt_ft); @@ -710,20 +768,20 @@ var ModelManager = { translateDelayed: func (lat,lon,alt_ft,heading,pitch, para) { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); - + me.pLat = me.vLat; me.pLon = me.vLon; me.pAlt_ft = me.vAlt_ft; me.pHeading = me.vHeading; me.pPitch = me.vPitch; - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; - + me.past = me.last; me.last = [geo.Coord.new().set_latlon(lat,lon,alt_ft*FT2M).xyz(),systime()]; me.delayTime = 0; @@ -736,7 +794,7 @@ var ModelManager = { me.heading.setDoubleValue(heading); me.pitch.setDoubleValue(para?0:pitch); #me.roll.setDoubleValue(0); - + me.vLat = lat; me.vLon = lon; me.vAlt_ft = alt_ft; @@ -810,7 +868,7 @@ var flare_sorter = func(a, b) { if(a[0] < b[0]){ return -1; # A should before b in the returned vector }elsif(a[0] == b[0]){ - return 0; # A is equivalent to b + return 0; # A is equivalent to b }else{ return 1; # A should after b in the returned vector } @@ -818,9 +876,6 @@ var flare_sorter = func(a, b) { var animate_flare = func { # Send out notifications about own flare positions every 0.4s - if (!getprop("payload/armament/msg")) { - return; - } var stime = systime(); # old flares var old_flares = []; @@ -838,6 +893,7 @@ var animate_flare = func { msg.Heading = 0; msg.u_fps = 0; notifications.objectBridgedTransmitter.NotifyAll(msg); + recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { @@ -846,7 +902,7 @@ var animate_flare = func { flare = [stime, flare[1], flare[2], flare[3], (flare[4] 60) { + if (getprop("payload/armament/enable-craters") == 1 and getprop("sim/multiplay/online") and (getprop("payload/armament/spectator") or getprop("payload/armament/msg")) and systime()-last_check > 60) { last_check = systime(); var msg = notifications.StaticNotification.new("stat", int(rand()*15000000), REQUEST_ALL, 0); msg.IsDistinct = 0; @@ -1005,7 +1087,7 @@ var fail_systems = func (probability, factor = 100) {#this factor needs tuning a } } } - + return failed; } }; @@ -1039,7 +1121,7 @@ setlistener("/sim/signals/reinit", repairYasim); hp_f = [hp_max,hp_max,hp_max,hp_max,hp_max,hp_max,hp_max]; var fail_fleet_systems = func (probability, factor) { - + var sinking_ships = (hp_f[0]<0) + (hp_f[1]<0) + (hp_f[2]<0) + (hp_f[3]<0) + (hp_f[4]<0) + (hp_f[5]<0) + (hp_f[6]<0); var hit_sinking = 0; if (sinking_ships == 0) { @@ -1053,9 +1135,9 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("You shot one of our already sinking ships, you are just mean."); return; } - + var no = 0; - + for (no=0; no < 7; no+=1) { if (hp_f[no] > 0) { break; @@ -1078,7 +1160,7 @@ var fail_fleet_systems = func (probability, factor) { armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); - } + } } return -1; }; @@ -1137,12 +1219,19 @@ var getCallsign = func (callsign) { var MAW_elapsed = 0; +var radarSpikes = {}; + +foreach (key ; keys(radar_signatures)) { + radarSpikes[radar_signatures[key]] = 0; +} + var processCallsigns = func () { callsign_struct = {}; var players = props.globals.getNode("ai/models").getChildren(); var myCallsign = getprop("sim/multiplay/callsign"); myCallsign = size(myCallsign) < 8 ? myCallsign : left(myCallsign,7); var painted = 0; + var paint_list = []; foreach (var player; players) { if(player.getChild("valid") != nil and player.getChild("valid").getValue() == TRUE and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); @@ -1150,14 +1239,49 @@ var processCallsigns = func () { var str6 = player.getNode("sim/multiplay/generic/string[6]"); if (str6 != nil and str6.getValue() != nil and str6.getValue() != "" and size(""~str6.getValue())==4 and left(md5(myCallsign),4) == str6.getValue()) { painted = 1; + if (rwr_audio_extended) { + append(paint_list, getModel(player.getNode("sim/model/path"))); + } } } } - setprop("payload/armament/spike", painted); if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning } + + # spike handling: + setprop("payload/armament/spike", painted); + if (!rwr_audio_extended) return; + var roundSpike = rand(); + foreach (var radarModel ; paint_list) { + var ref = radar_signatures[radarModel]; + if (ref != nil) { + radarSpikes[ref] = roundSpike; + } + } + foreach(key ; keys(radarSpikes)) { + if (radarSpikes[key] == roundSpike) { + setprop("payload/armament/spike-"~key, 1); + } else { + setprop("payload/armament/spike-"~key, 0); + } + } } +var remove_suffix = func(str, suffix) { + var len = size(suffix); + if (substr(str, -len) == suffix) return substr(str, 0, size(str) - len); + else return str; +}; +var getModel = func (node) { + if (node == nil) return "unknown-model"; + var value = node.getValue(); + if (value == nil or value == "") return ""; + var model = split(".", split("/", value)[-1])[0]; + model = remove_suffix(model, "-model"); + model = remove_suffix(model, "-anim"); + return model; +} + processCallsignsTimer = maketimer(1.5, processCallsigns); processCallsignsTimer.simulatedTime = 1; processCallsignsTimer.start(); @@ -1176,16 +1300,17 @@ var code_ct = func () { call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "WeightAndFuel"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "system-failures"}))},nil,var err2 = []); call(func{fgcommand('dialog-close', props.Node.new({"dialog-name": "instrument-failures"}))},nil,var err2 = []); - } + } setprop("sim/freeze/fuel",0); setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); + setprop("/sim/marker-pins/traffic", 0); setprop("/sim/gui/dialogs/map-canvas/draw-TFC", 0); #fgcommand("timeofday", props.Node.new({"timeofday": "real"})); #setprop("/sim/rendering/als-filters/use-filtering", 1); call(func{var interfaceController = fg1000.GenericInterfaceController.getOrCreateInstance(); - interfaceController.stop();},nil,var err2=[]); - } + interfaceController.stop();},nil,var err2=[]); + } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; @@ -1203,13 +1328,14 @@ code_ctTimer.start(); var re_init = func (node) { # repair the aircraft if (node.getValue() == 0) return; - + var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); foreach(var failure_mode_id; mode_list) { FailureMgr.set_failure_level(failure_mode_id, 0); } + stopLaunch(); damageLog.push("Aircraft was repaired due to re-init."); } @@ -1282,4 +1408,11 @@ setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MLW-bearing"); #screen.property_display.add("payload/armament/MLW-count"); #screen.property_display.add("payload/armament/MLW-launcher"); -#screen.property_display.add("payload/armament/spike"); \ No newline at end of file +#screen.property_display.add("payload/armament/spike"); +#screen.property_display.add("payload/armament/spike-air"); +#screen.property_display.add("payload/armament/spike-gnd-20"); +#screen.property_display.add("payload/armament/spike-gnd-02"); +#screen.property_display.add("payload/armament/spike-gnd-11"); +#screen.property_display.add("payload/armament/spike-gnd-23"); +#screen.property_display.add("payload/armament/spike-gnd-p2"); +#screen.property_display.add("payload/armament/spike-gnd-nk"); \ No newline at end of file diff --git a/missile-frigate/Nasal/vector.nas b/missile-frigate/Nasal/vector.nas index 36ae539..621b0cb 100644 --- a/missile-frigate/Nasal/vector.nas +++ b/missile-frigate/Nasal/vector.nas @@ -2,7 +2,7 @@ var Math = { # # Authors: Nikolai V. Chr, Axel Paccalin. # - # Version 1.93 + # Version 2.01 # # When doing euler coords. to cartesian: +x = forw, +y = left, +z = up. # FG struct. coords: +x = back, +y = right, +z = up. @@ -13,6 +13,8 @@ var Math = { # When doing euler angles (from pilots point of view): yaw = yaw left, pitch = rotate up, roll = roll right. # FG rotations: heading = yaw right, pitch = rotate up, roll = roll right. # + # Cartesian is right-handed coord system. + # clamp: func(v, min, max) { v < min ? min : v > max ? max : v }, convertCoords: func (x,y,z) { @@ -37,14 +39,16 @@ var Math = { me.upamount = -me.end_dist_m; } me.tgt_coord.set_alt(coord.alt()+me.upamount); - - return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z()}; + me.geoVeccy = me.product(me.magnitudeVector(a), me.normalize([me.tgt_coord.x()-coord.x(),me.tgt_coord.y()-coord.y(),me.tgt_coord.z()-coord.z()])); + return {"x":me.tgt_coord.x()-coord.x(), "y":me.tgt_coord.y()-coord.y(), "z":me.tgt_coord.z()-coord.z(), "vector": me.geoVeccy}; }, # When observing another MP aircraft the groundspeed velocity info is in body frame, this method will convert it to cartesian vector. + # + # Warning: If you input body velocities in fps the output will be in fps also. Likewise for mps. getCartesianVelocity: func (yaw_deg, pitch_deg, roll_deg, uBody_fps, vBody_fps, wBody_fps) { me.bodyVelocity = [uBody_fps, -vBody_fps, -wBody_fps]; - return me.yawPitchRollVector(yaw_deg, pitch_deg, roll_deg, me.bodyVelocity); + return me.rollPitchYawVector(roll_deg, pitch_deg, yaw_deg, me.bodyVelocity); }, # angle between 2 vectors. Returns 0-180 degrees. @@ -55,9 +59,27 @@ var Math = { return R2D * math.acos(me.value); }, + # Rodrigues' rotation formula. Use unitVectorAxis as axis, and rotate around it. + rotateVectorAroundVector: func (a, unitVectorAxis, angle) { + return me.plus( + me.plus( + me.product(math.cos(angle*D2R),a), + me.product(math.sin(angle*D2R), me.crossProduct(unitVectorAxis,a)) + ), + me.product((1-math.cos(angle*D2R))*me.dotProduct(unitVectorAxis,a), unitVectorAxis) + ); + }, + + #Rotate a certain amound of degrees towards 'towardsMe'. + rotateVectorTowardsVector: func (a, towardsMe, angle) { + return me.rotateVectorAroundVector(a, me.normalize(me.crossProduct(a, towardsMe)), angle); + }, + # length of vector magnitudeVector: func (a) { - return math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2)); + me.mag = 1; + call(func {me.mag = math.sqrt(math.pow(a[0],2)+math.pow(a[1],2)+math.pow(a[2],2))},nil,nil,var err =[]);#strange bug in Nasal can sometimes mess up sqrt + return me.mag; }, # dot product of 2 vectors @@ -65,8 +87,20 @@ var Math = { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }, - # rotate a vector. Order: roll, pitch, yaw + # rotate a vector. Order: roll, pitch, yaw (same as aircraft) + # + # The coordinate system is fixed during all the rolls. + # rollPitchYawVector: func (roll, pitch, yaw, vector) { + me.rollM = me.rollMatrix(roll); + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw, pitch, roll (reverse of aircraft) + yawPitchRollVector: func (yaw, pitch, roll, vector) { me.rollM = me.rollMatrix(roll); me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); @@ -74,15 +108,34 @@ var Math = { return me.multiplyMatrixWithVector(me.rotation, vector); }, - # rotate a vector. Order: yaw, pitch, roll (like an aircraft) - yawPitchRollVector: func (yaw, pitch, roll, vector) { - me.rollM = me.rollMatrix(roll); + # rotate a vector. Order: yaw, pitch + yawPitchVector: func (yaw, pitch, vector) { me.pitchM = me.pitchMatrix(pitch); me.yawM = me.yawMatrix(yaw); - me.rotation = me.multiplyMatrices(me.yawM, me.multiplyMatrices(me.pitchM, me.rollM)); + me.rotation = me.multiplyMatrices(me.pitchM, me.yawM); return me.multiplyMatrixWithVector(me.rotation, vector); }, + # rotate a vector. Order: pitch, yaw + pitchYawVector: func (pitch, yaw, vector) { + me.pitchM = me.pitchMatrix(pitch); + me.yawM = me.yawMatrix(yaw); + me.rotation = me.multiplyMatrices(me.yawM, me.pitchM); + return me.multiplyMatrixWithVector(me.rotation, vector); + }, + + # rotate a vector. Order: yaw + yawVector: func (yaw, vector) { + me.yawM = me.yawMatrix(yaw); + return me.multiplyMatrixWithVector(me.yawM, vector); + }, + + # rotate a vector. Order: pitch + pitchVector: func (pitch, vector) { + me.pitchM = me.pitchMatrix(pitch); + return me.multiplyMatrixWithVector(me.pitchM, vector); + }, + # multiply 3x3 matrix with vector multiplyMatrixWithVector: func (matrix, vector) { return [matrix[0]*vector[0]+matrix[1]*vector[1]+matrix[2]*vector[2], @@ -107,10 +160,10 @@ var Math = { # matrix for pitching pitchMatrix: func (pitch) { - pitch = pitch * D2R; - return [math.cos(pitch),0,-math.sin(pitch), + pitch = -pitch * D2R; + return [math.cos(pitch),0,math.sin(pitch), 0,1,0, - math.sin(pitch),0,math.cos(pitch)]; + -math.sin(pitch),0,math.cos(pitch)]; }, # matrix for yawing @@ -126,7 +179,8 @@ var Math = { me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); if (me.horz != 0) { me.pitch = math.atan2(vector[2],me.horz)*R2D; - me.hdg = math.asin(-vector[1]/me.horz)*R2D; + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; if (vector[0] < 0) { # south @@ -144,6 +198,39 @@ var Math = { return [me.hdg, me.pitch]; }, + # vector to pitch + cartesianToEulerPitch: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.pitch = math.atan2(vector[2],me.horz)*R2D; + } else { + me.pitch = vector[2]>=0?90:-90; + } + return me.pitch; + }, + + # vector to heading + cartesianToEulerHeading: func (vector) { + me.horz = math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); + if (me.horz != 0) { + me.div = math.clamp(-vector[1]/me.horz, -1, 1); + me.hdg = math.asin(me.div)*R2D; + + if (vector[0] < 0) { + # south + if (me.hdg >= 0) { + me.hdg = 180-me.hdg; + } else { + me.hdg = -180-me.hdg; + } + } + me.hdg = geo.normdeg(me.hdg); + } else { + me.hdg = 0; + } + return me.hdg; + }, + # gives an vector that points up from fuselage eulerToCartesian3Z: func (yaw_deg, pitch_deg, roll_deg) { me.yaw = yaw_deg * D2R; @@ -242,8 +329,22 @@ var Math = { # supply a normal to the plane, and a vector. The vector will be projected onto the plane, and that projection is returned as a vector. projVectorOnPlane: func (planeNormal, vector) { + if (me.magnitudeVector(planeNormal) == 0) return [0,0,0];#safety return me.minus(vector, me.product(me.dotProduct(vector,planeNormal)/math.pow(me.magnitudeVector(planeNormal),2), planeNormal)); }, + + # Project a onto ontoMe. + projVectorOnVector: func (a, ontoMe) { + if (me.magnitudeVector(ontoMe) == 0) return [0,0,0];#safety + return me.product(me.dotProduct(a,ontoMe)/me.dotProduct(ontoMe,ontoMe), ontoMe); + }, + + # Project a onto ontoMe and measure how long along ontoMe it goes, opposite will give negative number. + scalarProjVectorOnVector: func (a, ontoMe) { + me.ontoMeMag = me.magnitudeVector(ontoMe); + if (me.ontoMeMag == 0) return 0; + return me.dotProduct(a,ontoMe)/me.ontoMeMag; + }, # unary - vector opposite: func (v){ @@ -268,7 +369,7 @@ var Math = { # print vector to console format: func (v) { - return sprintf("(%.1f, %.1f, %.1f)",v[0],v[1],v[2]); + return sprintf("(%.2f, %.2f, %.2f)",v[0],v[1],v[2]); }, # make vector length 1.0 @@ -288,6 +389,20 @@ var Math = { return me.magnitudeVector(me.crossProduct(me.minus(L2,L1), me.minus(L1,P)))/me.magnitudeVector(me.minus(L2,L1)); }, + + interpolateVector: func (start, end, fraction) { + me.xx = (start[0]*(1-fraction) +end[0]*fraction); + me.yy = (start[1]*(1-fraction) +end[1]*fraction); + me.zz = (start[2]*(1-fraction) +end[2]*fraction); + + return [me.xx, me.yy, me.zz]; + }, + + # move distance 'along' from start towards end. Total dist from start to end is dist. + alongVector: func (start, end, dist, along) { + me.fraction = along/dist; + return me.interpolateVector(start, end, me.fraction); + }, # Orthogonal projection of a vector `vec` onto another `ref` !!can throw an exception if the referential vector is null!!. orthogonalProjection: func(vec, ref){ @@ -319,9 +434,9 @@ var Math = { #| 0 cos(roll) -sin(roll) | #| 0 sin(roll) cos(roll) | # -#| cos(pitch) 0 -sin(pitch) | -#| 0 1 0 | -#| sin(pitch) 0 cos(pitch) | +#| cos(-pitch) 0 sin(-pitch) | +#| 0 1 0 | +#| -sin(-pitch) 0 cos(-pitch) | # #| cos(yaw) -sin(yaw) 0 | #| sin(yaw) cos(yaw) 0 | @@ -336,3 +451,32 @@ var Math = { # }; + + +var unitTest = { + start: func { + # 1: Simple test of rotation matrices and getting the input back in another way. + var localDir = Math.pitchYawVector(-40, -75, [1,0,0]); + me.eulerDir = Math.cartesianToEuler(localDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 75 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 2: Revert part of test #1 + var forwardDir = Math.yawPitchVector(75, 0, localDir); + me.eulerDir = Math.cartesianToEuler(forwardDir); + me.eulerDir[0] = me.eulerDir[0]==nil?0:geo.normdeg(me.eulerDir[0]); + printf("Looking out of aircraft window at heading 0 and 40 degs down: %.4f heading %.4f pitch.",me.eulerDir[0],me.eulerDir[1]); + # 3: Tougher test, try it different places on earth since earth is not sphere. At KXTA runway it gives 0.03% error in pitch. + var someDir = Math.eulerToCartesian3X(-75, -40, 20); + var myCoord = geo.aircraft_position(); + me.thousandVectorGeo = Math.vectorToGeoVector(someDir, myCoord).vector;# does not return magnitude with meaning, only its direction matters. + me.thousandVectorGeo = Math.normalize(me.thousandVectorGeo); + me.thousandVectorGeo = Math.product(100000, me.thousandVectorGeo); + me.lookAt = geo.Coord.new().set_xyz(myCoord.x()+me.thousandVectorGeo[0], myCoord.y()+me.thousandVectorGeo[1], myCoord.z()+me.thousandVectorGeo[2]); + printf("Looking out of aircraft window 100000m away heading 75 and 40 degs down: %.4f heading %.4f pitch %.4f meter.", myCoord.course_to(me.lookAt), Math.getPitch(myCoord, me.lookAt), myCoord.direct_distance_to(me.lookAt)); + # 4: + var aircraft = Math.eulerToCartesian3X(-35, 72, 21); + var aircraft2 = Math.rollPitchYawVector(21, 72, -35, [1,0,0]); + printf("These two should be the same %s = %s",Math.format(aircraft),Math.format(aircraft2)); + }, +}; +#unitTest.start(); \ No newline at end of file diff --git a/missile-frigate/missile-frigate-set.xml b/missile-frigate/missile-frigate-set.xml index 4a4b86c..bf4aeb3 100644 --- a/missile-frigate/missile-frigate-set.xml +++ b/missile-frigate/missile-frigate-set.xml @@ -59,7 +59,7 @@ - pinto and onox + pinto, Nikolai V. Chr., onox Missile Frigate alpha 0.1 @@ -97,6 +97,36 @@ -500.0 + + Follow + lookat + false + + + true + rotors/main/blade[0]/flap-deg + rotors/main/blade[1]/flap-deg + rotors/main/blade[2]/position-deg + /position/latitude-deg + /position/longitude-deg + /position/altitude-ft + + /orientation/heading-deg + 0 + 0 + 50 + 50 + 25 + 70 + + false + + + + Aircraft/missile-frigate/Hud/hud.xml true @@ -116,15 +146,16 @@ Aircraft/missile-frigate/autopilot.xml - + 0 + @@ -132,23 +163,7 @@ true - - - - nasal - - - - - - enemies - - dialog-show - enemies - - + @@ -262,9 +277,33 @@ 50 - + + 20 + true + 200 + false + true + false + false + false + false + false + 2.25 + false + Aircraft/missile-frigate/Models/Armament/Weapons/ + true + true + rotors/main/blade[0]/flap-deg + rotors/main/blade[1]/flap-deg + rotors/main/blade[2]/flap-deg + rotors/main/blade[2]/position-deg + true + true + 1 + controls/armament + station true true true @@ -287,23 +326,27 @@ 0 + 0 - - Leto - swamp - pinto - fb237 - Wilco-1 - Kokos - J-Mav16 - S - Raider1 - Slobb - 5H1N0B1 - SNOWY1 + 0 + 1 + + + target1 + target2 + target3 + target4 + target5 + target6 + target7 + target8 + target9 + targetA + targetB + targetC @@ -353,8 +396,8 @@ - +