# # Install: Include this code into an aircraft to make it damagable. (remember to add it to the -set file) # 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, Colin Geniet and Richard (with improvement by Onox) # # ############################ Config ######################################################################################## 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 = 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 = 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 preAlphaKey = "ABC";# for hash keys that could start with number, which is not allowed. srand(); var hp = hp_max; setprop("sam/damage", math.max(0,100*hp/hp_max));#used in HUD var shells = { # [id,damage,(name)] # # 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. # # 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.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 # Max id is 39 }; # lbs of warheads is explosive+fragmentation+fuse, so total warhead mass. var warheads = { # [id,lbs,anti surface,cluster,(name)] "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 "AGM-119": [ 4, 264.50,1,0], "AGM-154A": [ 5, 493.00,1,0], "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],# 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. "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], "GBU-12": [18, 190.00,1,0], "GBU-24": [19, 945.00,1,0], "GBU-31": [20, 945.00,1,0], "GBU-54": [21, 190.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], "Kh-25MP": [26, 197.53,1,0], "Kh-66": [27, 244.71,1,0], "LAU-68": [28, 10.00,1,0], "M71": [29, 200.00,1,0], "M71R": [30, 200.00,1,0], "M90": [31, 10.00,1,1],# bomblet warhead. x3 of real mass. "MK-82": [32, 192.00,1,0], "MK-83": [33, 445.00,1,0], "MK-83HD": [34, 445.00,1,0], "MK-84": [35, 945.00,1,0], "OFAB-100": [36, 92.59,1,0], "RB-04E": [37, 661.00,1,0], "RB-15F": [38, 440.92,1,0], "RB-75": [39, 126.00,1,0], "RN-14T": [40, 800.00,1,0], #fictional, thermobaeric replacement for the RN-24 nuclear bomb "RN-18T": [41, 1200.00,1,0], #fictional, thermobaeric replacement for the RN-28 nuclear bomb "RS-2US": [42, 28.66,1,0], "S-21": [43, 245.00,1,0], "S-24": [44, 271.00,1,0], "SCALP EG": [45, 992.00,1,0],# aka. Storm Shadow "Sea Eagle": [46, 505.00,1,0], "MK-82HD": [47, 192.00,1,0], "MK-20": [48, 100.00,1,1],#aka CBU-100 # bomblet warhead. 247 x 0.4lb "ZB-250": [49, 236.99,1,0], "ZB-500": [50, 473.99,1,0], "AGM-45": [51, 149.00,1,0],#shrike "AIM-120B": [52, 44.00,0,0], "AIM-54": [53, 135.00,0,0], "AGM-78": [54, 215.00,1,0], "AIM-7F": [55, 88.00,0,0], "AGM-62": [56, 2000.00,1,0], "AIM-9L": [57, 20.80,0,0], "AGM-65D": [58, 126.00,1,0], "AIM-132": [59, 22.05,0,0], "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], "GEM": [63, 185.00,0,0],#MIM-104D "R.550 Magic": [64, 26.45,0,0],# also called majic "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], "AIM-9M": [69, 20.80,0,0], "R-73 RVV-MD": [70, 16.31,0,0],# automat su34 "Meteor": [71, 55.00,0,0], "MICA-EM": [72, 30.00,0,0], "MICA-IR": [73, 30.00,0,0], "R-13M": [74, 16.31,0,0], "R-27R1": [75, 85.98,0,0], "R-27T1": [76, 85.98,0,0], "R-3R": [77, 16.31,0,0], "R-3S": [78, 16.31,0,0], "R-55": [79, 20.06,0,0], "R-60": [80, 6.60,0,0], "R-60M": [81, 7.70,0,0], "R-73": [82, 16.31,0,0],# automat mig23/mig29/su-27 "R-77": [83, 49.60,0,0], "R74": [84, 16.00,0,0], "RB-05A": [85, 353.00,1,0], "RB-24": [86, 20.80,0,0], "RB-24J": [87, 20.80,0,0], "RB-71": [88, 88.00,0,0], "RB-74": [89, 20.80,0,0], "RB-99": [90, 44.00,0,0], "Super 530D": [91, 66.00,0,0], "48N6": [92, 330.00,0,0],# 48N6 from S-300pmu "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],# 3M9M3 Missile used with 2K12/SA-6 "5V28V": [97, 478.00,0,0],# Missile used with S-200D/SA-5 "AIM-9X": [98, 20.80,0,0], "R-23R": [99, 55.00,0,0],# mig23 fox 1 "5V27": [100, 150.00,0,0],# Missile used with SA-3/S-125 # Max id is 180 }; var AIR_RADAR = "air"; var radar_signatures = { # be careful these keys do not start with a number char "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-23MLD": AIR_RADAR, "MiG-29": AIR_RADAR, "SU-27": AIR_RADAR, "EC-137R": AIR_RADAR, "E-3R": AIR_RADAR, "E-3": AIR_RADAR, "RC-137R": AIR_RADAR, "E-8R": AIR_RADAR, "EC-137D": AIR_RADAR, "Mig-28": AIR_RADAR, "SA-6": "gnd-06", "s-200": "gnd-05", "ZSU-23-4M": "gnd-23", "S-75": "gnd-02", "buk-m2": "gnd-17", "s-300": "gnd-20", "MIM104D": "gnd-p2", "missile_frigate": "gnd-nk", "fleet": "gnd-nk", "SA-3": "gnd-p2", }; var id2warhead = []; var launched = {};# callsign: elapsed-sec var approached = {};# callsign: uniqueID var lastSeenTacObject = {};# tacID: last time seen weapon/flare var heavy_smoke = [61,62,63,65,92,96,97,100]; var k = keys(warheads); for(var myid = 0;myid 5) { # Don't bother to show flares further than 5 nm return emesary.Transmitter.ReceiptStatus_OK; } } else { foreach(var black;heavy_smoke) { if (index == black) { smoke = 2; break; } } } 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] } } 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" or m28_auto)) { # Record armament flightpath in tacview if (tacview.starttime) { var tacID = left(md5(notification.Callsign~notification.UniqueIdentity~typ[4]),6); var elapsed = getprop("sim/time/elapsed-sec"); lastSeenTacObject[preAlphaKey ~ tacID] = elapsed; if (notification.Kind == DESTROY) { thread.lock(tacview.mutexWrite); tacview.write("#" ~ (systime() - tacview.starttime)~"\n"); tacview.write("-"~tacID~"\n"); thread.unlock(tacview.mutexWrite); } else { 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 = typp=="Flare"?",Type=Flare":extra2; extra2 = typp=="Parachutist"?"":extra2; var color = radarOn or CWIOn?",Color=Red":",Color=Yellow"; thread.lock(tacview.mutexWrite); tacview.write("#" ~ (systime() - tacview.starttime)~"\n"); tacview.write(tacID~",T="~notification.Position.lon()~"|"~notification.Position.lat()~"|"~notification.Position.alt()~extra~",Name="~typp~color~extra2~"\n"); thread.unlock(tacview.mutexWrite); } } } if (notification.Kind == DESTROY) { return emesary.Transmitter.ReceiptStatus_OK; } if (index == warheads["Flare"][0] or index == warheads["pilot"][0]) { return emesary.Transmitter.ReceiptStatus_OK; } # Missile launch warning: if (thrustOn) { var launch = launched[preAlphaKey ~ notification.Callsign~notification.UniqueIdentity]; if (launch == nil or elapsed - launch > 300) { launch = elapsed; launched[preAlphaKey ~ notification.Callsign~notification.UniqueIdentity] = launch; if (notification.Position.direct_distance_to(ownPos)*M2NM < mlw_max) { setprop("payload/armament/MLW-bearing", bearing); setprop("payload/armament/MLW-launcher", notification.Callsign); setprop("payload/armament/MLW-count", getprop("payload/armament/MLW-count")+1); var out = sprintf("Missile Launch Warning from %03d degrees.", bearing); if (rwr_to_screen) screen.log.write(out, 1,0.5,0);# temporary till someone models a RWR in RIO seat print(out); damageLog.push(sprintf("Missile Launch Warning from %03d degrees from %s.", bearing, notification.Callsign)); if (m28_auto) mig28.missileLaunch(); } } } # Missile approach warning: var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (notification.RemoteCallsign != callsign) return emesary.Transmitter.ReceiptStatus_OK; if (!radarOn and !CWIOn) return emesary.Transmitter.ReceiptStatus_OK;# this should be little more complex later #var heading = getprop("orientation/heading-deg"); #var clock = geo.normdeg(bearing - heading); if (radarOn) { setprop("payload/armament/MAW-bearing", bearing); setprop("payload/armament/MAW-active", 1);# resets every 1 seconds } elsif (CWIOn) { setprop("payload/armament/MAW-semiactive", 1);# resets every 1 seconds if (notification.Callsign != nil) setprop("payload/armament/MAW-semiactive-callsign", notification.Callsign);# resets every 1 seconds } MAW_elapsed = elapsed; var appr = approached[preAlphaKey ~ notification.Callsign~notification.UniqueIdentity]; if (appr == nil or elapsed - appr > 450) { if (radarOn) { #printf("Missile Approach Warning from %03d degrees.", bearing); damageLog.push(sprintf("Missile Approach Warning from %03d degrees from %s.", bearing, notification.Callsign)); if (rwr_to_screen) screen.log.write(sprintf("Missile Approach Warning from %03d degrees.", bearing), 1,0.5,0);# temporary till someone models a RWR in RIO seat } elsif (CWIOn) { #printf("Missile Approach Warning (semi-active)."); damageLog.push(sprintf("Missile Approach Warning from %s.", notification.Callsign)); if (rwr_to_screen) screen.log.write(sprintf("Missile Approach Warning (semi-active)."), 1,0.5,0);# temporary till someone models a RWR in RIO seat } approached[preAlphaKey ~ notification.Callsign~notification.UniqueIdentity] = elapsed; if (m28_auto) mig28.engagedBy(notification.Callsign, 1); } return emesary.Transmitter.ReceiptStatus_OK; } if (notification.NotificationType == "ArmamentNotification") { # if (notification.FromIncomingBridge) { # print("recv(d2): ",notification.NotificationType, " ", notification.Ident, # " Kind=",notification.Kind, # " SecondaryKind=",notification.SecondaryKind, # " RelativeAltitude=",notification.RelativeAltitude, # " Distance=",notification.Distance, # " Bearing=",notification.Bearing, # " Inc-bridge=",notification.FromIncomingBridge, # " RemoteCallsign=",notification.RemoteCallsign); # debug.dump(notification); # # if(multiplayer.ignore[notification.Callsign] == 1) { damageLog.push("Ignored hit by "~notification.Callsign); return emesary.Transmitter.ReceiptStatus_NotProcessed; } var callsign = processCallsign(getprop("sim/multiplay/callsign")); if (tacview_supported and tacview.starttime and (getprop("sim/multiplay/txhost") != "mpserver.opredflag.com" or m28_auto or notification.RemoteCallsign == callsign)) { # Record hits in tacview var node = getCallsign(notification.RemoteCallsign); if (notification.RemoteCallsign == callsign) node = 1; if (node != nil and (notification.SecondaryKind > 20 or notification.SecondaryKind < -40)) { # its a warhead var wh = id2warhead[DamageRecipient.emesaryID2typeID(notification.SecondaryKind)]; var lbs = wh[1]; var hitCoord = geo.Coord.new(); if (notification.RemoteCallsign == callsign) { hitCoord = geo.aircraft_position(); } else { 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 == 1 and notification.SecondaryKind < 0) { # its a cannon or rocket var hitCoord = geo.aircraft_position(); thread.lock(tacview.mutexWrite); tacview.writeExplosion(hitCoord.lat(), hitCoord.lon(), hitCoord.alt(), 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); } } if (notification.RemoteCallsign == callsign and getprop("payload/armament/msg") == 1) { #damage enabled and were getting hit if (notification.SecondaryKind < 0 and notification.SecondaryKind >= -40 and hitable_by_cannon) { # cannon hit if (m28_auto) mig28.engagedBy(notification.Callsign, 0); var probability = id2shell[-1*notification.SecondaryKind-1][1]; var typ = id2shell[-1*notification.SecondaryKind-1][2]; var hit_count = notification.Distance; if (hit_count != nil) { var damaged_sys = 0; for (var i = 1; i <= hit_count; i = i + 1) { var failed = fail_systems(probability); damaged_sys = damaged_sys + failed; } printf("Took %.1f%% x %2d damage from %s! %s systems was hit.", probability*100, hit_count, typ, damaged_sys); damageLog.push(sprintf("%s hit you with %d %s.", notification.Callsign, hit_count, typ)); nearby_explosion(); } } elsif (notification.SecondaryKind > 20 or notification.SecondaryKind < -40) { # its a warhead if (m28_auto) mig28.engagedBy(notification.Callsign, 1); var dist = notification.Distance; var wh = id2warhead[DamageRecipient.emesaryID2typeID(notification.SecondaryKind)]; var type = wh[4];#test code if (wh[3] == 1) { # cluster munition var lbs = wh[1]; var maxDist = maxDamageDistFromWarhead(lbs); var distance = math.max(0,rand()*5-full_damage_dist_m);#being 0 to 5 meters from a bomblet on average. var diff = math.max(0, maxDist-distance); 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; } var failed = fail_systems(probability, hp_max); var percent = 100 * probability; printf("Took %.1f%% damage from %s clusterbomb at %0.1f meters from bomblet. %s systems was hit", percent,type,distance,failed); damageLog.push(sprintf("%s hit you with %s bomblet, %.1f meters distance.", notification.Callsign, type, dist)); nearby_explosion(); return; } 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) } elsif (hitable_by_air_munitions and wh[2] == 0) { lbs = wh[1]; maxDist = maxDamageDistFromWarhead(lbs); } 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; } var failed = fail_systems(probability, hp_max); var percent = 100 * probability; 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, # and warhead size is only used to determine the decrease of damage with distance increase. # It sorta gets the job done though, so I am hesitant to advocate that warheads above a certain # size should give 100% damage for some distance, and that warheads smaller than certain size should # not give 100% damage even on direct hit. # 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 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) { if (notification.SecondaryKind == 0) {# TODO: make a hash with all the models var static = geo.put_model(crater_model0, notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.Heading); if (static != nil) { statics["obj_"~notification.UniqueIdentity] = [static, notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.Heading, notification.SecondaryKind]; #static is a PropertyNode inside /models } } elsif (notification.SecondaryKind == 1) { var static = geo.put_model(crater_model1, notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.Heading); if (static != nil) { statics["obj_"~notification.UniqueIdentity] = [static, notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.Heading, notification.SecondaryKind]; } } elsif (notification.SecondaryKind == 2) { var static = geo.put_model(crater_model2, notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.Heading); if (static != nil) { statics["obj_"~notification.UniqueIdentity] = [static, notification.Position.lat(), notification.Position.lon(), notification.Position.alt(), notification.Heading, notification.SecondaryKind]; } } } elsif (notification.Kind == REQUEST_ALL and getprop("payload/armament/enable-craters") == 1) { # someone has requested all statics, lets send them out var kes = keys(statics); printf(notification.Callsign~" has requested all statics, sending %d to him/her.",size(kes)); foreach(ke;kes) { var static = statics[ke]; var msg = notifications.StaticNotification.new("stat", num(substr(ke,4)), CREATE, static[5]); msg.Position.set_latlon(static[1],static[2],static[3]); msg.IsDistinct = 0; msg.Heading = static[4]; notifications.hitBridgedTransmitter.NotifyAll(msg); } } return emesary.Transmitter.ReceiptStatus_OK; } return emesary.Transmitter.ReceiptStatus_NotProcessed; }; return new_class; }, typeID2emesaryID: func (typeID) { if (typeID <= 100) { return typeID + 21; } elsif (typeID <= 180) { return (typeID - 100) * -1 - 40; } else { print("Missile TypeID too large value, max is 180"); return 0; } }, emesaryID2typeID: func (emesaryID) { if (emesaryID > 20) { return emesaryID - 21; } elsif (emesaryID < -40) { return (emesaryID + 40) * -1 + 100; } else { print("Missile emesaryID not a warhead"); return 0; } }, }; damage_recipient = DamageRecipient.new("DamageRecipient"); emesary.GlobalTransmitter.Register(damage_recipient); #================================================================== # Notification Kinds #================================================================== # Static variables for notification.Kind: var CREATE = 1; var MOVE = 2; var DESTROY = 3; var IMPACT = 4; var REQUEST_ALL = 5; #================================================================== # Flying missiles over MP #================================================================== var statics = {}; var dynamics = {}; var dynamic3d = []; var deadreckon_updatetime = 0.1;# 1/15 of missile send rate var time_before_delete = 2.5;# time since last notification before deleting var dynamic_loop = func { # This keeps track of MP flying missiles/parachutes/flares and manages ModelManager. var new_dynamic3d = []; var stime = systime(); foreach (dynamic3d_entry ; dynamic3d) { var dyna = dynamics[dynamic3d_entry[0]]; if (dyna != nil and stime-dyna[0] > time_before_delete) { # OLD, DELETE ALL delete(dynamics, dynamic3d_entry[0]); reckon_delete(dynamic3d_entry); } elsif (dyna != nil and dynamic3d_entry[1] < dyna[0]) { # REFRESH INCOMING # update pos and attitude append(new_dynamic3d, reckon_update(dyna, dynamic3d_entry, stime)); delete(dynamics, dynamic3d_entry[0]); } elsif (dyna == nil and stime-dynamic3d_entry[1] < time_before_delete) { # BETWEEN UPDATES # deadreckon reckon_move(dynamic3d_entry, stime); append(new_dynamic3d, dynamic3d_entry); } else { # OLD, DELETE ALL reckon_delete(dynamic3d_entry); } } dynamic3d = new_dynamic3d; var kees = keys(dynamics); foreach(kee; kees) { var dyna = dynamics[kee]; if (stime-dyna[0] < time_before_delete) { var new_entry = reckon_create(kee, dyna, stime); if (new_entry !=nil) { append(dynamic3d, new_entry); } } delete(dynamics, kee); } settimer(dynamic_loop,deadreckon_updatetime); } var ModelManager = { # This shows missiles/parachutes/flares flying and their smoke trail. new: func (path,lat,lon,alt_ft,heading,pitch,para) { var m = {parents:[ModelManager]}; var n = props.globals.getNode("models", 1); var i = 0; for (i = 0; 1==1; i += 1) { if (n.getChild("model", i, 0) == nil) { break; } } 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) { break; } } 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); m.alt_ft= m.ai.getNode("position/altitude-ft", 1); 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; m.vHeading = heading; m.vPitch = pitch; #m.vRoll = 0; m.pLat = m.vLat; m.pLon = m.vLon; 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; return m; }, moveRealtime: func (uBody_fps, dt, factor) { if (me.uBody_fps == 0) me.uBody_fps = uBody_fps; me.slant_ft = (me.uBody_fps < uBody_fps?me.uBody_fps:uBody_fps) * dt * factor; 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); }, moveDelayed: func (dt) { if (me.frametime==0) return; me.place(); me.xyz = me.interpolate(me.past[0],me.last[0], me.delayTime/me.frametime); #print("% "~100*me.delayTime/me.frametime); me.coord.set_xyz(me.xyz[0],me.xyz[1],me.xyz[2]); me.latlon = me.coord.latlon(); me.lat.setDoubleValue(me.latlon[0]); me.lon.setDoubleValue(me.latlon[1]); me.alt_ft.setDoubleValue(me.latlon[2]*M2FT); me.delayTime += dt; }, interpolate: 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]; }, place: func { if (me["loadNode"] == nil) { me.loadNode = me.model.getNode("load", 1); me.loadNode.setBoolValue(1); me.loadNode.setBoolValue(0); } }, 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; me.frametime = me.last[1]-me.past[1]; }, translateRealtime: func (lat,lon,alt_ft,heading,pitch, para) { me.lat.setDoubleValue(lat); me.lon.setDoubleValue(lon); me.alt_ft.setDoubleValue(alt_ft); 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; me.vHeading = heading; me.vPitch = pitch; #me.vRoll = 0; }, del: func { me.model.remove(); me.ai.remove(); }, }; var reckon_create = func (kee, dyna, stime) { var path = getprop("payload/armament/models") ~ "parachutist.xml"; if (dyna[7]==1) { path = getprop("payload/armament/models") ~ "light_smoke.xml"; } elsif (dyna[7] ==2) { path = getprop("payload/armament/models") ~ "heavy_smoke.xml"; } elsif (dyna[7] ==3) { path = getprop("payload/armament/models") ~ "the-flare.xml"; } elsif (dyna[7] == -1) { return nil; } var static = ModelManager.new(path, dyna[1],dyna[2],dyna[3]*M2FT,dyna[5],dyna[6],dyna[7]==0);#path,lat,lon,alt_m,heading,pitch if (static != nil) { var entry = [kee, stime, static, dyna[4]]; return entry; } print("NOT FOUND (Emesary): "~path); return nil; } var reckon_update = func (dyna, entry, stime) { var static = entry[2]; var dynami2 = [entry[0], stime, static, dyna[4]]; # translate static.translateDelayed(dyna[1],dyna[2],dyna[3]*M2FT,dyna[5],dyna[6],dyna[7]==0); static.moveDelayed(deadreckon_updatetime); return dynami2; } var reckon_move = func (entry, stime) { var static = entry[2]; var time_then = entry[1]; var time_now = stime; # dead-reckon #static.moveRealtime(entry[3] , time_now-time_then, entry[4]?0.25:0.5); static.moveDelayed(deadreckon_updatetime);#time_now-time_then); } var reckon_delete = func (entry) { entry[2].del(); } dynamic_loop(); #================================================================== # Flares over MP #================================================================== var last_prop = 0; var last_release = 0; var flare_list = []; var flare_update_time = 0.4; var flare_duration = 8; var flare_terminal_speed = 50;#m/s var flares_max_process_per_loop = 4; var flare_sequencer = -120; 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 }else{ return 1; # A should after b in the returned vector } } var animate_flare = func { # Send out notifications about own flare positions every 0.4s var stime = systime(); # old flares var old_flares = []; var flares_sent = 0; flare_list = sort(flare_list, flare_sorter); foreach(flare; flare_list) { if (stime-flare[1] > flare_duration) { var msg = notifications.ObjectInFlightNotification.new("ffly", flare[6], DESTROY, DamageRecipient.typeID2emesaryID(warheads["Flare"][0])); msg.Position = flare[2]; msg.IsDistinct = 1; msg.UniqueIndex = flare[6]; notifications.objectBridgedTransmitter.NotifyAll(msg); recordOwnFlare(msg); continue; } if (flares_sent < flares_max_process_per_loop) { var flare_dt = stime-flare[0]; # update_t,start_t, coord, heading, speed_down_mps, , speed_horiz_mps, unique flare = [stime, flare[1], flare[2], flare[3], (flare[4] 1) { flare_released(); last_release = stime; } last_prop = prop; } var flare_released = func { # We released a flare. If you call this method manually, then make sure 'auto_flare_caller' is false. var stime = systime(); var flare =[stime, stime, geo.aircraft_position(), getprop("orientation/heading-deg"), FT2M*getprop("velocities/speed-down-fps"), FT2M*math.sqrt(getprop("velocities/speed-north-fps")*getprop("velocities/speed-north-fps")+getprop("velocities/speed-east-fps")*getprop("velocities/speed-east-fps")), flare_sequencer]; flare_sequencer += 1; if (flare_sequencer > 120) flare_sequencer = -120; append(flare_list, flare); var msg = notifications.ObjectInFlightNotification.new("ffly", flare[6], MOVE, DamageRecipient.typeID2emesaryID(warheads["Flare"][0])); msg.Position = flare[2]; msg.IsDistinct = 1; msg.UniqueIndex = flare[6]; notifications.objectBridgedTransmitter.NotifyAll(msg); recordOwnFlare(msg); } var recordOwnFlare = func (msg) { if (tacview_supported) { if (tacview.starttime) { var tacID = left(md5("ownShip"~msg.UniqueIndex),6); if (msg.Kind == DESTROY) { thread.lock(tacview.mutexWrite); tacview.write("#" ~ (systime() - tacview.starttime)~"\n"); tacview.write("-"~tacID~"\n"); thread.unlock(tacview.mutexWrite); } else { var typp = "Flare"; var extra = ""; var extra2 = ",Type=Flare"; var color = ",Color=Yellow"; thread.lock(tacview.mutexWrite); tacview.write("#" ~ (systime() - tacview.starttime)~"\n"); tacview.write(tacID~",T="~msg.Position.lon()~"|"~msg.Position.lat()~"|"~msg.Position.alt()~extra~",Name="~typp~color~extra2~"\n"); thread.unlock(tacview.mutexWrite); } } } } #================================================================== # Notification for getting craters #================================================================== setlistener("sim/multiplay/online", func { check_for_Request(); },0,0); setlistener("payload/armament/msg", func { check_for_Request(); },0,0); setlistener("payload/armament/spectator", func { check_for_Request(); },0,0); var last_check = -65; var check_for_Request = func { # This sends out a notification to ask other aircraft for all craters 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; msg.Heading = 0; notifications.hitBridgedTransmitter.NotifyAll(msg); } else { } } settimer(check_for_Request, 60);# for aircraft like mig21 that starts with damage enabled #================================================================== # Damage functions #================================================================== var maxDamageDistFromWarhead = func (lbs) { # Calc at what distance the warhead will do zero damage every time. var dist = 3*math.sqrt(lbs); return dist; } var fail_systems = func (probability, factor = 100) {#this factor needs tuning after all asset hitpoints have been evaluated. if (is_fleet) { return fail_fleet_systems(probability, factor); } elsif (use_hitpoints_instead_of_failure_modes_bool) { hp -= factor * probability*(0.75+rand()*0.25);# from 75 to 100% damage printf("HP: %d/%d", hp, hp_max); setprop("sam/damage", math.max(0,100*hp/hp_max));#used in HUD if ( hp < 0 ) { setprop("/carrier/sunk/",1);#we are dead setprop("/sim/multiplay/generic/int[2]",1);#radar off setprop("/sim/multiplay/generic/int[0]",1);#smoke on setprop("/sim/messages/copilot", getprop("sim/multiplay/callsign")~" dead."); } return -1; } else { var failure_modes = FailureMgr._failmgr.failure_modes; var mode_list = keys(failure_modes); var failed = 0; foreach(var failure_mode_id; mode_list) { #print(failure_mode_id); if (rand() < probability) { FailureMgr.set_failure_level(failure_mode_id, 1); failed += 1; if (getprop("sim/flight-model") == "yasim") { if (failure_mode_id == "Engines/engine" and yasim_list2 == nil) { # fail yasim: setprop("sim/model/uh1/state",0); setprop("controls/engines/engine/magnetos", 0); setprop("controls/engines/engine/cutoff", 1); setprop("controls/engines/engine/on-fire", 1); #set a listener so that if a restart is attempted, it'll fail. yasim_list = setlistener("sim/model/uh1/state",func {setprop("sim/model/uh1/state",0);}); yasim_list2 = setlistener("controls/engines/engine/cutoff",func {setprop("controls/engines/engine/cutoff",1);}); } if (failure_mode_id == "Engines/engine[1]" and yasim_list3 == nil) { # fail yasim: setprop("controls/engines/engine[1]/magnetos", 0); setprop("controls/engines/engine[1]/cutoff", 1); setprop("controls/engines/engine[1]/on-fire", 1); #set a listener so that if a restart is attempted, it'll fail. yasim_list3 = setlistener("controls/engines/engine[1]/cutoff",func {setprop("controls/engines/engine[1]/cutoff",1);}); } if (failure_mode_id == "Engines/engine[2]" and yasim_list4 == nil) { # fail yasim: setprop("controls/engines/engine[2]/magnetos", 0); setprop("controls/engines/engine[2]/cutoff", 1); setprop("controls/engines/engine[2]/on-fire", 1); #set a listener so that if a restart is attempted, it'll fail. yasim_list4 = setlistener("controls/engines/engine[2]/cutoff",func {setprop("controls/engines/engine[2]/cutoff",1);}); } if (failure_mode_id == "Engines/engine[3]" and yasim_list5 == nil) { # fail yasim: setprop("controls/engines/engine[3]/magnetos", 0); setprop("controls/engines/engine[3]/cutoff", 1); setprop("controls/engines/engine[3]/on-fire", 1); #set a listener so that if a restart is attempted, it'll fail. yasim_list5 = setlistener("controls/engines/engine[3]/cutoff",func {setprop("controls/engines/engine[3]/cutoff",1);}); } } } } if (rand() < probability and rand() < probability and getprop("sim/flight-model") == "yasim") { setprop("consumables/fuel/tank[0]/level-norm", 0); setprop("consumables/fuel/tank[1]/level-norm", 0); setprop("consumables/fuel/tank[2]/level-norm", 0); setprop("consumables/fuel/tank[3]/level-norm", 0); setprop("consumables/fuel/tank[4]/level-norm", 0); setprop("consumables/fuel/tank[5]/level-norm", 0); setprop("consumables/fuel/tank[6]/level-norm", 0); setprop("consumables/fuel/tank[7]/level-norm", 0); setprop("consumables/fuel/tank[8]/level-norm", 0); setprop("consumables/fuel/tank[9]/level-norm", 0); setprop("consumables/fuel/tank[10]/level-norm", 0); setprop("consumables/fuel/tank[11]/level-norm", 0); # Somtimes these needs to be set too: setprop("consumables/fuel/tank[0]/level-lbs", 0); setprop("consumables/fuel/tank[1]/level-lbs", 0); setprop("consumables/fuel/tank[2]/level-lbs", 0); setprop("consumables/fuel/tank[3]/level-lbs", 0); setprop("consumables/fuel/tank[4]/level-lbs", 0); setprop("consumables/fuel/tank[5]/level-lbs", 0); setprop("consumables/fuel/tank[6]/level-lbs", 0); setprop("consumables/fuel/tank[7]/level-lbs", 0); setprop("consumables/fuel/tank[8]/level-lbs", 0); setprop("consumables/fuel/tank[9]/level-lbs", 0); setprop("consumables/fuel/tank[10]/level-lbs", 0); setprop("consumables/fuel/tank[11]/level-lbs", 0); # these will make the fraction indicator in fuel dialog not work after relocation, but they are needed: setprop("consumables/fuel/tank[0]/capacity-m3", 0); setprop("consumables/fuel/tank[1]/capacity-m3", 0); setprop("consumables/fuel/tank[2]/capacity-m3", 0); setprop("consumables/fuel/tank[3]/capacity-m3", 0); setprop("consumables/fuel/tank[4]/capacity-m3", 0); setprop("consumables/fuel/tank[5]/capacity-m3", 0); setprop("consumables/fuel/tank[6]/capacity-m3", 0); setprop("consumables/fuel/tank[7]/capacity-m3", 0); setprop("consumables/fuel/tank[8]/capacity-m3", 0); setprop("consumables/fuel/tank[9]/capacity-m3", 0); setprop("consumables/fuel/tank[10]/capacity-m3", 0); setprop("consumables/fuel/tank[11]/capacity-m3", 0); } return failed; } }; var yasim_list = nil; var yasim_list2 = nil; var yasim_list3 = nil; var yasim_list4 = nil; var yasim_list5 = nil; var repairYasim = func { if (yasim_list != nil) {removelistener(yasim_list); yasim_list=nil;} if (yasim_list2 != nil) {removelistener(yasim_list2); yasim_list2=nil;} if (yasim_list3 != nil) {removelistener(yasim_list3); yasim_list3=nil;} if (yasim_list4 != nil) {removelistener(yasim_list4); yasim_list4=nil;} if (yasim_list5 != nil) {removelistener(yasim_list5); yasim_list5=nil;} setprop("controls/engines/engine[0]/on-fire", 0); setprop("controls/engines/engine[1]/on-fire", 0); setprop("controls/engines/engine[2]/on-fire", 0); setprop("controls/engines/engine[3]/on-fire", 0); setprop("sim/crashed", 0); 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); } } var setupYasimEngines = func { if (getprop("engines/engine/n1") != nil and !hasMode("engines/engine")) { var e0 = compat_failure_modes.fail_engine("engine"); FailureMgr.add_failure_mode("engines/engine", "Engine 1", e0); } if (getprop("engines/engine[1]/n1") != nil and !hasMode("engines/engine[1]")) { var e1 = compat_failure_modes.fail_engine("engine[1]"); FailureMgr.add_failure_mode("engines/engine[1]", "Engine 2", e1); } if (getprop("engines/engine[2]/n1") != nil and !hasMode("engines/engine[2]")) { var e2 = compat_failure_modes.fail_engine("engine[2]"); FailureMgr.add_failure_mode("engines/engine[2]", "Engine 3", e2); } if (getprop("engines/engine[3]/n1") != nil and !hasMode("engines/engine[3]")) { var e3 = compat_failure_modes.fail_engine("engine[3]"); FailureMgr.add_failure_mode("engines/engine[3]", "Engine 4", e3); } if (getprop("engines/engine[4]/n1") != nil and !hasMode("engines/engine[4]")) { var e4 = compat_failure_modes.fail_engine("engine[4]"); FailureMgr.add_failure_mode("engines/engine[4]", "Engine 5", e4); } if (getprop("engines/engine[5]/n1") != nil and !hasMode("engines/engine[5]")) { var e5 = compat_failure_modes.fail_engine("engine[5]"); FailureMgr.add_failure_mode("engines/engine[5]", "Engine 6", e5); } if (getprop("engines/engine[6]/n1") != nil and !hasMode("engines/engine[6]")) { var e6 = compat_failure_modes.fail_engine("engine[6]"); FailureMgr.add_failure_mode("engines/engine[6]", "Engine 7", e6); } if (getprop("engines/engine[7]/n1") != nil and !hasMode("engines/engine[7]")) { var e7 = compat_failure_modes.fail_engine("engine[7]"); FailureMgr.add_failure_mode("engines/engine[7]", "Engine 8", e7); } } settimer(setupYasimEngines, 150); var hasMode = func (test) { # Tests if a failure mode exist already var modes = FailureMgr.get_failure_modes(); foreach(mode;modes) { if (mode["id"]==test) return 1; } return 0; } 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) { hit_sinking = 0; } elsif (sinking_ships == 7) { hit_sinking = 1; } else { hit_sinking = rand()<0.10; } if (hit_sinking) { 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; } } hp_f[no] -= factor * probability*(0.75+rand()*0.25);# from 75 to 100% damage printf("HP[%d]: %d/%d", no, hp_f[no], hp_max); #setprop("sam/damage", math.max(0,100*hp/hp_max));#used in HUD if ( hp_f[no] < 0 ) { setprop("/sim/multiplay/generic/bool["~(no+40)~"]",1); armament.defeatSpamFilter("So you sank one of our ships, we will get you for that!"); if (!getprop("/carrier/disabled") and hp_f[0]<0 and hp_f[1]<0 and hp_f[2]<0) { setprop("/carrier/disabled",1); armament.defeatSpamFilter("Captain our offensive capability is crippled!"); } if (hp_f[0]<0 and hp_f[1]<0 and hp_f[2]<0 and hp_f[3]<0 and hp_f[4]<0 and hp_f[5]<0 and hp_f[6]<0) { setprop("/carrier/sunk",1); setprop("/sim/multiplay/generic/int[2]",1);#radar off setprop("/sim/messages/copilot", getprop("sim/multiplay/callsign")~" dead."); armament.defeatSpamFilter("S.O.S. Heeelp"); } else { armament.defeatSpamFilter("This is not over yet.."); } } return -1; }; setlistener("payload/armament/MLW-count", func { setLaunch(getprop("payload/armament/MLW-launcher"), 0);#TODO: figure out if that callsign is a SAM/ship. }); #================================================================== # RWR and sound functions #================================================================== var setLaunch = func (c,s) { setprop("sound/rwr-launch-sam", s); setprop("sound/rwr-launch", c); settimer(func {stopLaunch();},7); } var stopLaunch = func () { setprop("sound/rwr-launch", ""); setprop("sound/rwr-launch-sam", 0); } var playIncomingSound = func (clock) { setprop("sound/incoming"~clock, 1); settimer(func {stopIncomingSound(clock);},3); } var stopIncomingSound = func (clock) { setprop("sound/incoming"~clock, 0); } var nearby_explosion = func { setprop("damage/sounds/nearby-explode-on", 0); settimer(nearby_explosion_a, 0); } var nearby_explosion_a = func { setprop("damage/sounds/nearby-explode-on", 1); settimer(nearby_explosion_b, 0.5); } var nearby_explosion_b = func { setprop("damage/sounds/nearby-explode-on", 0); } #================================================================== # Helper functions #================================================================== var callsign_struct = {}; var getCallsign = func (callsign) { var node = callsign_struct[preAlphaKey ~ callsign]; return node; } 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() == 1 and player.getChild("callsign") != nil and player.getChild("callsign").getValue() != "" and player.getChild("callsign").getValue() != nil) { var callsign = player.getChild("callsign").getValue(); if(multiplayer.ignore[callsign] == 1) { continue; } callsign_struct[preAlphaKey ~ callsign] = player; 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"))); } } } } if (getprop("sim/time/elapsed-sec")-MAW_elapsed > 1.1) { setprop("payload/armament/MAW-active", 0);# resets every 1.1 seconds without warning setprop("payload/armament/MAW-semiactive", 0); setprop("payload/armament/MAW-semiactive-callsign", ""); } if (tacview_supported and tacview.starttime) { var keyss = keys(lastSeenTacObject); var elapsed = getprop("sim/time/elapsed-sec"); var new_lastSeenTacObject = {}; foreach (var key; keyss) { if (elapsed - lastSeenTacObject[key] > 30) { thread.lock(tacview.mutexWrite); tacview.write("#" ~ (systime() - tacview.starttime)~"\n"); tacview.write("-"~substr(key,size(preAlphaKey))~"\n"); thread.unlock(tacview.mutexWrite); } else { new_lastSeenTacObject[key] = lastSeenTacObject[key]; } } lastSeenTacObject = new_lastSeenTacObject; } # 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(); #================================================================== # Stuff #================================================================== var code_ct = func () { #ANTIC if (getprop("payload/armament/msg")) { setprop("sim/rendering/redout/enabled", 1); setprop("sim/rendering/redout/parameters/locked-by-airframe", 1); #call(func{fgcommand('dialog-close', multiplayer.dialog.dialog.prop())},nil,var err= []);# props.Node.new({"dialog-name": "location-in-air"})); if (!m28_auto) 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); if (!m28_auto) setprop("/sim/speed-up", 1); setprop("/gui/map/draw-traffic", 0); setprop("/sim/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=[]); } else { setprop("sim/rendering/redout/parameters/locked-by-airframe", 0); } } code_ctTimer = maketimer(1, code_ct); code_ctTimer.simulatedTime = 1; setprop("/sim/failure-manager/display-on-screen", 0); code_ctTimer.start(); #================================================================== # Relocation function #================================================================== 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."); # Remove all 3D craters and re-place them. Due to re-init can remove some of them. foreach (var thekey ; keys(statics)) { var sta = statics[thekey]; if (sta[0] != nil) { sta[0].remove(); if (sta[5] == 0) {# TODO: make a hash with all the models sta[0] = geo.put_model(crater_model0, sta[1], sta[2], sta[3], sta[4]); } elsif (sta[5] == 1) { sta[0] = geo.put_model(crater_model1, sta[1], sta[2], sta[3], sta[4]); } elsif (sta[5] == 2) { sta[0] = geo.put_model(crater_model2, sta[1], sta[2], sta[3], sta[4]); } } } } #================================================================== # Event log #================================================================== var damageLog = events.LogBuffer.new(echo: 0); damageLog.push("Flightgear "~getprop("sim/version/flightgear")~" was loaded up with "~getprop("sim/description")~" - "~getprop("sim/time/gmt")); setlistener("/sim/signals/reinit", re_init, 0, 0); setlistener("payload/armament/msg", func {damageLog.push("Damage is now "~(getprop("payload/armament/msg")?"ON.":"OFF."));}, 1, 0); setlistener("sim/multiplay/callsign", func {damageLog.push("Callsign is now "~getprop("sim/multiplay/callsign"));}, 1, 0); setlistener("sim/multiplay/online", func {damageLog.push(getprop("sim/multiplay/online")?("Connected to "~getprop("sim/multiplay/txhost")):"Disconnected from MP.");}, 1, 0); var printDamageLog = func { if (getprop("payload/armament/msg")) {print("disable damage to use this function");return;} var buffer = damageLog.get_buffer(); var str = ""; foreach(entry; buffer) { str = str~" "~entry.time~" "~entry.message~"\n"; } print(); print(str); 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 { var output_file = getprop("/sim/fg-home") ~ "/Export/combat-log.txt"; var buffer = damageLog.get_buffer(); var str = "\n"; foreach(entry; buffer) { str = str~" "~entry.time~" "~entry.message~"\n"; } str = str ~ "\n"; var file = nil; if (io.stat(output_file) == nil) { file = io.open(output_file, "w"); io.close(file); } file = io.open(output_file, "a"); io.write(file, str); io.close(file); } var unitTest = func { for (var i= 0; i<=180;i+=1) { var em = DamageRecipient.typeID2emesaryID(i); var b = DamageRecipient.emesaryID2typeID(em); if (b != i) { print("unit test failed for index "~i); return; } } print("unit test passed"); } #unitTest(); setlistener("sim/signals/exit", writeDamageLog, 0, 0); #screen.property_display.add("payload/armament/MAW-bearing"); #screen.property_display.add("payload/armament/MAW-active"); #screen.property_display.add("payload/armament/MAW-semiactive"); #screen.property_display.add("payload/armament/MAW-semiactive-callsign"); #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"); #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-05"); #screen.property_display.add("payload/armament/spike-gnd-06"); #screen.property_display.add("payload/armament/spike-gnd-17"); #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");