##################################################### #This module implements three different but interrelated functions #that can be used by, for example, AI objects and scenery objects: # # 1. BOMBABLE: Makes objects bombable. They will detect hits, change livery according to damage, and finally start on fire and smoke when sufficiently damaged. There is also a function to change the livery colors # # 2. GROUND: Makes objects stay at ground level, adjusting pitch to match any slope they are on. So, for instance, cars, trucks, or tanks can move and always stay on the ground, drive up slopes somewhat realistically, etc. Ships can be placed in any lake and will automatically find their correct altitude, etc. # # 3. LOCATE: Usually AI objects return to their initial start positions when FG re-inits (ie, file/reset). This function saves and maintains their previous position prior to the reset # #TYPICAL USAGE--ADDING BOMBABILITY TO AN AI OR SCENERY MODEL # # Required: # 1. The Fire-Particles subdirectory (included in this package) # must be installed in the FG/data/AI/Aircraft/Fire-Particles subdirectory # 2. This file, bombable.nas, must be installed in the FG/data/Nasal # subdirectory # # To make any particular object "bombable", simply include code similar to that # included in the AI aircraft XML files in this distribution. # # This approach generally should work with any AI objects or scenery objects. # # You then typically create an AI scenario that includes these "bombable # objects" and then, to see and bomb the objects, load the scenario using # the command line or fgrun when you start FlightGear. # # Notes: # - The object's node name can be found using cmdarg().getPath() # - You can make slight damage & high damage livery quite easily by modifying # any existing livery a model may have. Note, however, that many objects # do not use liveries, but simply include color in the model itself. You # won't be able to change liveries on such models unless you alter the # model (.ac file) to use external textures. # # # See file bombable-modding-aircraft-for-dogfighting.txt included in this # package for more details about adding bombable to aircraft or other objects. # # See m1-abrams/m1.xml and other AI object XML files in this package for # sample implementations. # # #AUTHORS # Base code for M1 Abrams tank by Emmanuel BARANGER - David BASTIEN # Modded heavily by Brent HUGH to add re-location and ground altitude # functionality, crashes for ships and aircraft, evasive manuevers when # under attack, multiplayer functionality, other functionality, # and to abstract the code to a unit that can be included in most # any AI or scenery object. # # Many snippets of code and examples of implemention were borrowed from other # FlightGear projects--thanks to all those contributors! # ################################################################################ #put_tied_model places a new model that is tied to another AI model # (given by myNodeName) and will move with it in lon, lat, & alt var put_tied_model = func(myNodeName, path="AI/Aircraft/Fire-Particles/Fire-Particles.xml ") { fgcommand("add-model", fireNode=props.Node.new({ "path": path, "latitude-deg-prop": myNodeName ~ "/position/latitude-deg", "longitude-deg-prop":myNodeName ~ "/position/longitude-deg", "elevation-ft-prop": myNodeName ~ "/position/altitude-ft", "heading-deg-prop": myNodeName ~ "/orientation/true-heading-deg", "pitch-deg-prop": myNodeName ~ "/orientation/pitch-deg", "roll-deg-prop": myNodeName ~ "/orientation/roll-deg", })); return props.globals.getNode(fireNode.getNode("property").getValue()); } #################################################### #Delete a fire object (model) created earlier, turn off the fire triggers #and unlink the fire from the parent object. #This sets the object up so it can actually start on fire again if #wanted (or hit again by ballistics . . . though damage is already to max if #it has been on fire for a while, and damage is not re-set) var deleteFire = func (myNodeName,fireNode="") { if (fireNode=="") { fireNodeName=getprop(""~myNodeName~"/bombable/fire-particles/fire-particles-model"); fireNode = props.globals.getNode(fireNodeName); } #remove the fire node/model altogether if (fireNode!= nil) fireNode.remove(); #turn off the object's fire trigger & unlink it from its fire model setprop(""~myNodeName~"/bombable/fire-particles/fire-trigger", 0); setprop(""~myNodeName~"/bombable/fire-particles/fire-particles-model", ""); } #################################################### #start a fire in a given location & associated with a given object # # #A fire is different than the smoke, contrails, and flares below because #when the fire is burning it adds damage to the object and eventually #destroys it. # #object is given by "myNodeName" and directory path to the model in "model" #Also sets the fire trigger on the object itself so it knows it is on fire #and saves the name of the fire (model) node so the object can find #the fire (model) it is associated with to update it etc. #Returns name of the node with the newly started fire object (model) var startFire = func (myNodeName, model="") { #if there is already a fire going/associated with this object # then we don't want to start another var currFire= getprop(""~myNodeName~"/bombable/fire-particles/fire-particles-model"); if ((currFire != nil) and (currFire != "")) return currFire; if (model==nil or model=="") model="AI/Aircraft/Fire-Particles/fire-particles.xml"; var fireNode=put_tied_model(myNodeName, model); if (myNodeName!="") type=props.globals.getNode(myNodeName).getName(); else type=""; if (type=="multiplayer") mp_send_damage(myNodeName, 0); #var fire_node=geo.put_model("Models/Effects/Wildfire/wildfire.xml", lat, lon, alt*.3048); #print ("started fire! ", myNodeName); #turn off the fire after user-set amount of time (default 1800 seconds) var burnTime=getprop ("/environment/bombable/fire-particles/fire-burn-time"); if (burnTime==0 or burnTime==nil) burnTime=1800; settimer (func {deleteFire(myNodeName,fireNode)}, burnTime); #name of this prop is "/models" + getname() + [ getindex() ] fireNodeName="/models/" ~ fireNode.getName() ~ "[" ~ fireNode.getIndex() ~ "]"; setprop(""~myNodeName~"/bombable/fire-particles/fire-trigger", 1); setprop(""~myNodeName~"/bombable/fire-particles/fire-particles-model", fireNodeName); return fireNodeName; #we usually start with the name & then use props.globals.getNode(nodeName) to get the node object if necessary. } #################################################### #Delete any of the various smoke, contrail, flare, etc. objects #and unlink the fire from the smoke object. # var deleteSmoke = func (smokeType, myNodeName,fireNode="") { if (fireNode=="") { fireNodeName=getprop(""~myNodeName~"/bombable/fire-particles/"~smokeType~"-particles-model"); fireNode = props.globals.getNode(fireNodeName); } #remove the fire node/model altogether if (fireNode != nil) fireNode.remove(); #turn off the object's fire trigger & unlink it from its fire model setprop(""~myNodeName~"/bombable/fire-particles/"~smokeType~"-trigger", 0); setprop(""~myNodeName~"/bombable/fire-particles/"~smokeType~"-particles-model", ""); if (myNodeName!="") type=props.globals.getNode(myNodeName).getName(); else type=""; if (type=="multiplayer") mp_send_damage(myNodeName, 0); } #################################################### # Smoke is like a fire, but doesn't cause damage & can use one of # several different models to create different effects. # # smokeTypes are flare, smoketrail, pistonexhaust, contrail, damagedengine # # This func starts a flare in a given location & associated with a given object #object is given by "myNodeName" and directory path to the model in "model" #Also sets the fire trigger on the object itself so it knows it is on fire #and saves the name of the fire (model) node so the object can find #the fire (model) it is associated with to update it etc. #Returns name of the node with the newly started fire object (model) var startSmoke = func (smokeType, myNodeName, model="") { #if there is already smoke of this type going/associated with this object # then we don't want to start another var currFire= getprop(""~myNodeName~"/bombable/fire-particles/"~smokeType~"-particles-model"); if ((currFire != nil) and (currFire != "")) return currFire; if (model==nil or model=="") model="AI/Aircraft/Fire-Particles/"~smokeType~"-particles.xml"; var fireNode=put_tied_model(myNodeName, model); #var fire_node=geo.put_model("Models/bombable/Wildfire/wildfire.xml", lat, lon, alt*.3048); #print ("started fire! ", myNodeName); #turn off the flare after user-set amount of time (default 1800 seconds) var burnTime=getprop (burntime1_pp~smokeType~burntime2_pp); if (burnTime==0 or burnTime==nil) burnTime=1800; #burnTime=-1 means leave it on indefinitely if (burnTime >= 0) settimer (func {deleteSmoke(smokeType, myNodeName,fireNode)}, burnTime); #name of this prop is "/models" + getname() + [ getindex() ] fireNodeName="/models/" ~ fireNode.getName() ~ "[" ~ fireNode.getIndex() ~ "]"; if (myNodeName!="") type=props.globals.getNode(myNodeName).getName(); else type=""; if (type=="multiplayer") mp_send_damage(myNodeName, 0); setprop(""~myNodeName~"/bombable/fire-particles/"~smokeType~"-trigger", 1); setprop(""~myNodeName~"/bombable/fire-particles/"~smokeType~"-particles-model", fireNodeName); return fireNodeName; #we usually pass around the name & then use props.globals.getNode(nodeName) to get the node object if necessary. } ############################################################ #################################################### #Add a new menu item to turn smoke on/off #todo: need to integrate this into the menus rather than just #arbitrarily adding it to menu[97] # #This function adds the dialog object to an actual GUI menubar item var init_fire_particles_dialog = func () { #todo: figure out how to position it in the center of the screen or somewhere better dialog.init(0,0); #make the GUI menu props.globals.getNode ("/sim/menubar/default/menu[97]/enabled", 1).setBoolValue(1); props.globals.getNode ("/sim/menubar/default/menu[97]/label", 1).setValue("Bombable"); props.globals.getNode ("/sim/menubar/default/menu[97]/item/enabled", 1).setBoolValue(1); #Note: the label must be distinct from all other labels in the menubar #or you will get duplicate functionality with the other menu item #sharing the same label props.globals.getNode ("/sim/menubar/default/menu[97]/item/label", 1).setValue("Bombable Options"); #must be unique name from all others in the menubar or they both pop up together props.globals.getNode ("/sim/menubar/default/menu[97]/item/binding/command", 1).setValue("nasal"); props.globals.getNode ("/sim/menubar/default/menu[97]/item/binding/script", 1).setValue("bombable.dialog.create()"); #reinit makes the property changes to both the GUI & input become active fgcommand("reinit"); } var targetStatusPopupTip = func (label, delay = 5, override = nil) { var tmpl = props.Node.new({ name : "PopTipTarget", modal : 0, layout : "hbox", y: 70, text : { label : label, padding : 6 } }); if (override != nil) tmpl.setValues(override); popdown(tipArgTarget); fgcommand("dialog-new", tmpl); fgcommand("dialog-show", tipArgTarget); currTimerTarget += 1; var thisTimerTarget = currTimerTarget; # Final argument is a flag to use "real" time, not simulated time settimer(func { if(currTimerTarget == thisTimerTarget) { popdown(tipArgTarget) } }, delay, 1); } var selfStatusPopupTip = func (label, delay = nil, override = nil) { var tmpl = props.Node.new({ name : "PopTipSelf", modal : 0, layout : "hbox", y: 140, text : { label : label, padding : 6 } }); if (override != nil) tmpl.setValues(override); if (override != nil) tmpl.setValues(override); popdown(tipArgSelf); fgcommand("dialog-new", tmpl); fgcommand("dialog-show", tipArgSelf); currTimerSelf += 1; var thisTimerSelf = currTimerSelf; # Final argument is a flag to use "real" time, not simulated time settimer(func { if(currTimerSelf == thisTimerSelf) { popdown(tipArgSelf) } }, delay, 1); } var popdown = func ( tipArg ) { fgcommand("dialog-close", tipArg); } ############################################################################### ## Set up Bombable Menu to turn on/off contrails etc. ## Based on the WildFire configuration dialog, ## which is partly based on Till Bush's multiplayer dialog ## to start, do dialog.init(30,30); dialog.create(); var CONFIG_DLG = 0; var dialog = { ################################################################# init : func (x = nil, y = nil) { me.x = x; me.y = y; me.bg = [0, 0, 0, 0.3]; # background color me.fg = [[1.0, 1.0, 1.0, 1.0]]; # # "private" me.title = "Bombable"; me.basenode = props.globals.getNode("/environment/bombable/fire-particles"); me.dialog = nil; me.namenode = props.Node.new({"dialog-name" : me.title }); me.listeners = []; }, ################################################################# create : func { if (me.dialog != nil) me.close(); me.dialog = gui.Widget.new(); me.dialog.set("name", me.title); if (me.x != nil) me.dialog.set("x", me.x); if (me.y != nil) me.dialog.set("y", me.y); me.dialog.set("layout", "vbox"); me.dialog.set("default-padding", 0); var titlebar = me.dialog.addChild("group"); titlebar.set("layout", "hbox"); titlebar.addChild("empty").set("stretch", 1); titlebar.addChild("text").set("label", "Bombable Objects Settings"); var w = titlebar.addChild("button"); w.set("pref-width", 16); w.set("pref-height", 16); w.set("legend", ""); w.set("default", 0); w.set("key", "esc"); w.setBinding("nasal", "bombable.dialog.destroy(); "); w.setBinding("dialog-close"); me.dialog.addChild("hrule"); var content = me.dialog.addChild("group"); content.set("layout", "vbox"); content.set("halign", "center"); content.set("default-padding", 5); foreach (var b; [["Multiplayer enabled", MP_share_pp, "checkbox"], ["Fires/Explosions enabled", trigger1_pp~"fire"~trigger2_pp, "checkbox"], ["Jet Contrails enabled", trigger1_pp~"jetcontrail"~trigger2_pp, "checkbox"], ["Smoke Trails enabled", trigger1_pp~"smoketrail"~trigger2_pp, "checkbox"], ["Piston engine exhaust enabled", trigger1_pp~"pistonexhaust"~trigger2_pp, "checkbox"], ["Damaged engine smoke enabled", trigger1_pp~"damagedengine"~trigger2_pp, "checkbox"], ["Flares enabled", trigger1_pp~"flare"~trigger2_pp, "checkbox"], ["Easy mode enabled (damage doubled)", bomb_pp~"easy-mode", "checkbox"], ["Super Easy Mode (tripled)", bomb_pp~"super-easy-mode", "checkbox"]] ) { var w = content.addChild(b[2]); w.node.setValues({"label" : b[0], "halign" : "left", "property" : b[1]}); w.setBinding("nasal", "setprop(\"" ~ b[1] ~ "\"," ~ "!getprop(\"" ~ b[1] ~ "\"))"); } me.dialog.addChild("hrule"); # Load button. #var load = me.dialog.addChild("button"); #load.node.setValues({"legend" : "Load Wildfire log", # "halign" : "center"}); #load.setBinding("nasal", # "wildfire.dialog.select_and_load()"); fgcommand("dialog-new", me.dialog.prop()); fgcommand("dialog-show", me.namenode); }, ################################################################# close : func { fgcommand("dialog-close", me.namenode); }, ################################################################# destroy : func { CONFIG_DLG = 0; me.close(); foreach(var l; me.listeners) removelistener(l); delete(gui.dialog, "\"" ~ me.title ~ "\""); }, ################################################################# show : func { if (!CONFIG_DLG) { CONFIG_DLG = 1; me.init(); me.create(); } }, ################################################################# select_and_load : func { var selector = gui.FileSelector.new (func (n) { CAFire.load_event_log(n.getValue()); }, "Load Wildfire log", # dialog title "Load", # button text ["*.xml"], # pattern for files SAVEDIR, # start dir "fire_log.xml"); # default file name selector.open(); } }; #oh yeah, that final ; is REALLy needed ############################################################################### #################################################### #return altitude (in feet) of given lat/lon var elev = func (lat, lon) { var info = geodinfo(lat, lon); if (info != nil) { var alt=info[0]; if (alt==nil) alt=0; return alt/0.3048; } else return 0; } ############################################################################### # MP messages # directly based on similar functions in wildfire.nas # var damage_msg = func (callsign, damageAdd, damageTotal, smoke=0, fire=0) { if (!getprop(MP_share_pp)) return; if (!getprop ("/environment/bombable/mp_broadcast_exists")) return; n=0; #callsign="flug"; # for testing #bits.switch(n,1, checkRange(smoke,0,1,0 )); # !! makes sure it's a boolean value #bits.switch(n,2, checkRange(fire,0,1,0 )); #can send up to 7 bits in a byte this way return sprintf ("%6s", callsign) ~ Binary.encodeByte(1) ~ Binary.encodeDouble(damageAdd) ~ Binary.encodeDouble(damageTotal) ~ Binary.encodeByte(smoke) ~ Binary.encodeByte(fire); } var water_drop_msg = func (pos, radius, volume) { seq += 1; return Binary.encodeInt(seq) ~ Binary.encodeByte(2) ~ Binary.encodeCoord(pos) ~ Binary.encodeDouble(radius); } var retardant_drop_msg = func (pos, radius, volume) { seq += 1; return Binary.encodeInt(seq) ~ Binary.encodeByte(3) ~ Binary.encodeCoord(pos) ~ Binary.encodeDouble(radius); } var foam_drop_msg = func (pos, radius, volume) { seq += 1; return Binary.encodeInt(seq) ~ Binary.encodeByte(4) ~ Binary.encodeCoord(pos) ~ Binary.encodeDouble(radius); } var parse_msg = func (source, msg) { if (!getprop(MP_share_pp)) return; if (!getprop ("/environment/bombable/mp_broadcast_exists")) return; #print ("source: ",source, " msg: ", msg); var ourcallsign=getprop ("/sim/multiplay/callsign"); var p = 0; var msgcallsign = substr(msg, 0, 6); #not our callsign, we ignore it & return if (sprintf ("%6s", msgcallsign) != sprintf ("%6s", ourcallsign)) return; p = 6; var type = Binary.decodeByte(substr(msg, p)); p += Binary.sizeOf["byte"]; #print ("msgcallsign:", msgcallsign," type:", type); if (type == 1) { var damageAdd = Binary.decodeDouble(substr(msg, p)); p += Binary.sizeOf["double"]; var damageTotal = Binary.decodeDouble(substr(msg, p)); p += Binary.sizeOf["double"]; var smokeStart = Binary.decodeByte(substr(msg, p)); p += Binary.sizeOf["byte"]; var fireStart = Binary.decodeByte(substr(msg, p)); p += Binary.sizeOf["byte"]; #print ("damageAdd:",damageAdd," damageTotal:",damageTotal," smoke:",smokeStart," fire:", fireStart); mp_add_damage (damageAdd, damageTotal, smokeStart, fireStart ); } elsif (type == 2) { var pos = Binary.decodeCoord(substr(msg, 6)); var radius = Binary.decodeDouble(substr(msg, 36)); resolve_water_drop(pos, radius, 0, 0); } elsif (type == 3) { var pos = Binary.decodeCoord(substr(msg, 6)); var radius = Binary.decodeDouble(substr(msg, 36)); resolve_retardant_drop(pos, radius, 0, 0); } elsif (type == 4) { var pos = Binary.decodeCoord(substr(msg, 6)); var radius = Binary.decodeDouble(substr(msg, 36)); resolve_foam_drop(pos, radius, 0, 0); } } #################################################### #timer function, every 1.5 to 2.5 seconds, adds damage if on fire var fire_loop = func(id, myNodeName) { var loopid = getprop(""~myNodeName~"/bombable/bomb-loopid"); id == loopid or return; var fireLoopUpdateTime_sec=2; if(getprop(""~myNodeName~"/bombable/fire-particles/fire-trigger")) { var myFireNodeName = getprop(""~myNodeName~"/bombable/fire-particles/fire-particles-model"); #we are stuck with one single property to control the startsize & endsize #of ALL fire-particles active at one time. The idea here is to change # the values of the start/endsize randomly and fairly quickly so the # various smoke columns don't all look like clones of each other # each smoke column only puts out particles 2X per second so # if the sizes are changed more often than that they can affect only # some of the smoke columns independently. var smokeEndsize = rand()*100+50; setprop ("/environment/bombable/fire-particles/smoke-endsize", smokeEndsize); var smokeEndsize = rand()*125+60; setprop ("/environment/bombable/fire-particles/smoke-endsize-large", smokeEndsize); var smokeEndsize = rand()*75+33; setprop ("/environment/bombable/fire-particles/smoke-endsize-small", smokeEndsize); var smokeStartsize=rand()*10 + 5; #occasionally make a really BIG explosion if (rand()<.02/fireLoopUpdateTime_sec) { settimer (func {setprop ("/environment/bombable/fire-particles/smoke-startsize", smokeStartsize); }, 0.1);#turn the big explosion off quickly so it only affects a few of the fires for a moment--they put out smoke particles 4X/second smokeStartsize = smokeStartsize * rand() * 15 + 100; #make the occasional really big explosion } setprop ("/environment/bombable/fire-particles/smoke-startsize", smokeStartsize); setprop ("/environment/bombable/fire-particles/smoke-startsize-small", smokeStartsize * (rand()/2 + 0.5)); setprop ("/environment/bombable/fire-particles/smoke-startsize-large", smokeStartsize* (rand()*4 + 1)); damageRate_percentpersecond = getprop (""~myNodeName~"/bombable/attributes/vulnerabilities/fireDamageRate_percentpersecond"); # The object is burning, so we regularly add damage. add_damage( damageRate_percentpersecond/100 * fireLoopUpdateTime_sec , myNodeName ); } # add rand() so that all objects dont do this function simultaneously settimer(func { fire_loop(id, myNodeName); }, fireLoopUpdateTime_sec - 0.5 + rand()); } ########################################################## #Puts myNodeName right at ground level, explodes, sets up #for full damage & on-ground trigger to make it stop real fast now # var hitground_stop_explode = func (myNodeName, alt) { var b = props.globals.getNode (""~myNodeName~"/bombable/attributes"); var vuls = b.getNode("vulnerabilities").getValues(); startFire( myNodeName ); #if it wasn't on fire before it is now setprop (""~myNodeName~"/position/altitude-ft", alt ); setprop (""~myNodeName~"/bombable/on-ground", 1 ); #this affects the slow-down system which is handled by add-damage, and will stop any forward movement very quickly add_damage(1, myNodeName); #and once we have buried ourselves in the ground we are surely dead; this also will stop any & all forward movement #check if this object has exploded already exploded= getprop (""~myNodeName~"/bombable/exploded" ); #if not, explode for ~3 seconds if ( exploded==nil or !exploded ){ #and we cover our tracks by making a really big explosion momentarily #if it hit the ground that hard it's justified, right? if (vuls.explosiveMass_kg<0) vuls.explosiveMass_kg=1; lnexpl= math.ln (vuls.explosiveMass_kg/10); var smokeStartsize = rand()*lnexpl*20 + 30; setprop ("/environment/bombable/fire-particles/smoke-startsize", smokeStartsize); setprop ("/environment/bombable/fire-particles/smoke-startsize-small", smokeStartsize * (rand()/2 + 0.5)); setprop ("/environment/bombable/fire-particles/smoke-startsize-large", smokeStartsize * (rand()*4 + 1)); #explode for, say, 3 seconds but then we're done for this object settimer ( func {setprop(""~myNodeName~"/bombable/exploded" , 1 ); }, 3 + rand() ); } } ################################################### #ground_loop #timer function, every (0.5 to 1.5 * updateTime_s) seconds, to keep object at # ground level # or other specified altitude above/below ground level, and at a # reasonable-looking pitch. length_m & width_m are distances (in meters) # needed to clear the object and find open earth on either side and front/back. # damagealtadd is the total amount to subtract from the normal the altitude above ground level (in meters) as # the object becomes damaged--say a sinking ship or tires flattening on a # vehicle. # damageAltMaxRate is the max rate to allow the object to rise or sink # as it becomes disabled var ground_loop = func( id, myNodeName ) { var loopid = getprop(""~myNodeName~"/bombable/ground-loopid"); id == loopid or return; var b = props.globals.getNode (""~myNodeName~"/bombable/attributes"); var alts = b.getNode("altitudes").getValues(); var dims= b.getNode("dimensions").getValues(); var vels= b.getNode("velocities").getValues(); var updateTime_s=b.getNode("updateTime_s").getValue(); node= props.globals.getNode(myNodeName); type=node.getName(); #bhugh, update altitude to keep moving objects at ground level the ground var currAlt_ft= getprop(""~myNodeName~"/position/altitude-ft"); #where the object is, in feet var lat = getprop(""~myNodeName~"/position/latitude-deg"); var lon = getprop(""~myNodeName~"/position/longitude-deg"); var heading = getprop(""~myNodeName~"/orientation/true-heading-deg"); var speed_kt = getprop(""~myNodeName~"/velocities/true-airspeed-kt"); var damageValue = getprop(""~myNodeName~"/bombable/attributes/damage"); var damageAltAddPrev_ft = getprop(""~myNodeName~"/bombable/attributes/damageAltAddCurrent_ft"); if (damageAltAddPrev_ft == nil) damageAltAddPrev_ft=0; var damageAltAddCumulative_ft = getprop(""~myNodeName~"/bombable/attributes/damageAltAddCumulative_ft"); if (damageAltAddCumulative_ft == nil) damageAltAddCumulative_ft=0; #calculate the altitude behind & ahead of the object, this determines the pitch angle and helps determine the overall ground level at this spot var GeoCoord = geo.Coord.new(); GeoCoord.set_latlon(lat, lon); GeoCoord.apply_course_distance(heading, dims.length_m/2); #frontreardist in meters toFrontAlt_ft=elev (GeoCoord.lat(), GeoCoord.lon() ); #in feet GeoCoord.apply_course_distance(heading+180, dims.length_m); toRearAlt_ft=elev (GeoCoord.lat(), GeoCoord.lon() ); #in feet #print ("oFront:", toFrontAlt_ft); if (type=="aircraft") { #poor man's look-ahead radar GeoCoord.apply_course_distance(heading, dims.length_m + speed_kt * 0.5144444 * 10 ); var radarAheadAlt_ft=elev (GeoCoord.lat(), GeoCoord.lon() ); #in feet #print ("result: ", radarAheadAlt_ft); # our target altitude (for aircraft purposes) is the greater of the # altitude immediately in front and the altitude from our # poor man's lookahead radar. (ie, up to 2 min out at current # speed). If the terrain is rising we add 300 to our taret # alt just to be on the safe side. # But if we're crashing, we don't care about # what is ahead. lookingAheadAlt_ft=toFrontAlt_ft; if ( radarAheadAlt_ft > toFrontAlt_ft and ! (damageValue > 0.8 ) ) lookingAheadAlt_ft = radarAheadAlt_ft+300; } else { lookingAheadAlt_ft =toFrontAlt_ft; } pitchangle1_deg = rad2degrees * math.atan2(toFrontAlt_ft - toRearAlt_ft, dims.length_ft ); #must convert this from radians to degrees, thus the 180/pi pitchangle_deg=pitchangle1_deg; #figure altitude of ground to left & right of object to determine roll & #to help in determining altitude var GeoCoord2 = geo.Coord.new(); GeoCoord2.set_latlon(lat, lon); GeoCoord2.apply_course_distance(heading+90, dims.width_m/2); #sidedist in meters toRightAlt_ft=elev (GeoCoord2.lat(), GeoCoord2.lon() ); #in feet GeoCoord2.apply_course_distance(heading-90, dims.width_m); toLeftAlt_ft=elev (GeoCoord2.lat(), GeoCoord2.lon() ); #in feet rollangle_deg = 90 - rad2degrees * math.atan2(dims.width_ft, toLeftAlt_ft - toRightAlt_ft ); #must convert this from radians to degrees, thus the 180/pi #in CVS, taking the alt of an object's position actually finds the top #of that particular object. So to find the alt of the actual landscape # we do ahead, behind, to left, to right of object & take the average. #luckily this also helps us calculate the pitch of the slope, #which we need to set pitch & roll, so little is #lost alt_ft = (toFrontAlt_ft + toRearAlt_ft + toLeftAlt_ft + toRightAlt_ft) / 4; #in feet #The first time this is called just initialize all the altitudes and exit if ( alts.initialized != 1 ) { var initial_altitude_ft= getprop (""~myNodeName~"/position/altitude-ft"); if (initial_altitude_ftalt_ft + alts.wheelsOnGroundAGL_ft + alts.maximumAGL_ft) { initial_altitude_ft = alt_ft + alts.wheelsOnGroundAGL_ft + alts.maximumAGL_ft; } target_alt_ft=initial_altitude_ft - alt_ft - alts.wheelsOnGroundAGL_ft; print ("Initial Altitude: ", initial_altitude_ft, " target altitude: ",target_alt_ft, " object=", myNodeName); setprop (""~myNodeName~"/position/altitude-ft", initial_altitude_ft ); setprop (""~myNodeName~"/controls/flight/target-alt", initial_altitude_ft); #set target AGL here. This way the aircraft file can simply set altitude # limits for the craft while the scenario files sets the specific altitude # target for a specific plane in a specific scenario b.getNode("altitudes/targetAGL_ft", 1).setDoubleValue(target_alt_ft); b.getNode("altitudes/targetAGL_m", 1).setDoubleValue(target_alt_ft*.3048); b.getNode("altitudes/initialized", 1).setBoolValue(1); #set the timer & exit settimer(func { ground_loop(id, myNodeName)}, (0.5 + rand())*updateTime_s ); return; } var objectsLowestAllowedAlt_ft =alt_ft + alts.wheelsOnGroundAGL_ft + alts.crashedAGL_ft; #print (" objectsLowestAllowedAlt_ft=", objectsLowestAllowedAlt_ft); onGround= getprop (""~myNodeName~"/bombable/on-ground"); if (onGround==nil) onGround=0; if (onGround){ #go to object's resting altitude setprop (""~myNodeName~"/position/altitude-ft", objectsLowestAllowedAlt_ft ); setprop (""~myNodeName~"/controls/flight/target-alt", objectsLowestAllowedAlt_ft); #all to a complete stop setprop(""~myNodeName~"/controls/tgt-speed-kt", 0); setprop(""~myNodeName~"/controls/flight/target-spd", 0); setprop(""~myNodeName~"/velocities/true-airspeed-kt", 0); #we don't even really need the timer any more, since this object #is now exploded to heck & stopped also. But just in case . . . settimer(func { ground_loop(id, myNodeName)}, (0.5 + rand())*updateTime_s ); #and that's it return; } #our target altitude for normal/undamaged forward movement #this isn't based on our current altitude but the results of our # "lookahead radar" to provide the base altitude # However as the craft is more damaged it loses its ability to do this # (see above: lookingAheadAlt just becomes the same as toFrontAlt) targetAlt_ft = lookingAheadAlt_ft + alts.targetAGL_ft + alts.wheelsOnGroundAGL_ft; fullDamageAltAdd_ft = (alt_ft+alts.crashedAGL_ft +alts.wheelsOnGroundAGL_ft) - currAlt_ft; #amount we should add to our current altitude when fully crashed. This is to get the object to "full crashed position", ie, on the ground for an aircraft, fully sunk for a ship, etc. #now calculate how far to force the thing down if it is crashing/damaged if ( damageValue > 0.8 ) { damageAltAddMax_ft= (damageValue) * fullDamageAltAdd_ft; #max altitude amount to add to altitude to this object based on its current damage. # #Like fullDamageAltAdd & damageAltAddPrev this should always be zero #or negative as everything on earth falls or sinks when it loses #power. And assuming that simplifies calculations immensely. #The altitude the object should be at, based on damagealtAddMax & the #ground level: shouldBeAlt=currAlt_ft + damageAltAddMax_ft; #print ("shouldBeAlt ", shouldBeAlt); #print ( "alt=", alt_ft, " currAlt_ft=",currAlt_ft, " fulldamagealtadd", fullDamageAltAdd_ft," damagealtaddmax", damageAltAddMax_ft, " damagevalue", damageValue," ", myNodeName ); #print ("shouldBeAlt=oldalt+ alts.wheelsOnGroundAGL_ft + damageAltAddMax; ", # shouldBeAlt, " ", oldalt, " ", alts.wheelsOnGroundAGL_ft, " ", damageAltAddMax ); #limit amount of sinkage to damageAltMaxRate in one hit/loop--otherwise it just goes down too fast, not realistic. This is basically like the terminal # velocity for this type of object. damageAltMaxPerCycle_ft = -abs(vels.damagedAltitudeChangeMaxRate_meterspersecond*updateTime_s/.3048); #move 10% more than previous or if no previous, start at 1% the max rate #making sure to move in the right direction! (using sgn of damageAltAdd) if (damageAltAddPrev_ft != 0) damageAltAddCurrent_ft = -abs((1 + 0.1*updateTime_s) * damageAltAddPrev_ft); else damageAltAddCurrent_ft=- abs(0.01*damageAltMaxPerCycle_ft); # make sure this is not bigger than the max rate, if so only change #it by the max amount allowed per cycle if ( abs( damageAltAddCurrent_ft ) > abs(damageAltMaxPerCycle_ft ) ) damageAltAddCurrent_ft = damageAltMaxPerCycle_ft; #Make sure we're not above the max allowed altitude change for this damage level; if so, cut it off if ( abs(damageAltAddCurrent_ft) > abs(damageAltAddMax_ft) ) { damageAltAddCurrent_ft = damageAltAddMax_ft; } #print ( " damageAltAddMax=", damageAltAddMax, " damageAltMaxRate=", #print ("damageAltAddCurrent_ft ", damageAltAddCurrent_ft); } else { damageAltAddCurrent_ft=0; } #if the thing is basically as low as allowed by crashedAGL_m # we consider it "on the ground" (for an airplane) or # completely sunk (for a ship) etc. # If it is going there at any speed we consider it crashed # into the ground. When this # property is set to true then the speed will slow quite dramatically. # This allows for example airplanes to continue forward movement # in the air but skid to a sudden halt when hitting the ground. # # alts.wheelsOnGroundAGL_ft + damageAltAdd = the altitude (AGL) the object should be at when # finished crashing, sinking, etc. # It's not that easy to determine if an object crashes--if an airplane # hits the ground it crashes but tanks etc are always on the ground noPitch=0; if ( (damageValue > 0.8 and ( currAlt_ft <= objectsLowestAllowedAlt_ft and speed_kt > 20 ) or ( currAlt_ft <= objectsLowestAllowedAlt_ft-5)) or (damageValue == 1 and currAlt_ft <= objectsLowestAllowedAlt_ft) ) hitground_stop_explode(myNodeName, alt_ft); #if we are dropping faster than the current slope (typically because # we are an aircraft diving to the ground because of damage) we # make the pitch match that angle, even if it more acute than the # regular slope of the underlying ground if ( (damageValue > 0.8 ) ) { #this goes off every updateTime_s seconds approximately so the horizontal motion in one second is: (1.68780986 converts knots to ft per second) horizontalDistance_ft=speed_kt * 1.68780986 * updateTime_s; pitchangle2_deg = rad2degrees * math.atan2(damageAltAddCurrent_ft, horizontalDistance_ft ); if (damageAltAddCurrent_ft ==0 and horizontalDistance_ft >0) pitchangle2_deg=0; #forward if (horizontalDistance_ft == 0 and damageAltAddCurrent_ft<0 ) pitchangle2_deg=-90; #straight down #Straight up won't happen here because we are (on purpose) forcing #the object down as we crash. So we ignore the case. #if (horizontalDistance==0 and deltaAlt>0 ) pitchangle2=90; straight up #if no movement at all then we leave the pitch alone #if movement is less than 0.4 feet for pitch purposes we consider it #no movement at all--just a bit of wiggling noPitch= ( (abs(damageAltAddCurrent_ft)< 0.5) and ( abs(horizontalDistance_ft) < 0.5)); if (noPitch) pitchangle2_deg=0; if (abs(pitchangle2_deg) > abs(pitchangle1_deg)) pitchangle_deg=pitchangle2_deg; setprop (""~myNodeName~"/velocities/vertical-speed-fps",damageAltAddCurrent_ft * updateTime_s ); #since we do this updateTime_ss per second the vertical speed in FPS (ideally) exactly equals damageAltAddCurrent*updateTime_s #print ("speed-based pitchangle=", pitchangle2_deg, " hor=", horizontalDistance_ft, " dalt=", deltaAlt_ft); } #don't set pitch/roll for aircraft if (type != "aircraft") { setprop (""~myNodeName~"/orientation/roll-deg", rollangle_deg ); setprop (""~myNodeName~"/controls/flight/target-roll", rollangle_deg); if (!noPitch) { setprop (""~myNodeName~"/orientation/pitch-deg", pitchangle_deg ); setprop (""~myNodeName~"/controls/flight/target-pitch", pitchangle_deg); } } #if (crashing) print ("Crashing! damageAltAdd_deg and damageAltAddCurrent_deg, #damageAltAddPrev & damageAltAddMax, damageAltMaxRate, damageAltMaxPerCycle ", #damageAltAdd_ft, " ",damageAltAddCurrent," ", damageAltAddPrev_ft, " ", #damageAltAddMax_ft, " ", myNodeName, " ", damageAltMaxRate, " ", #damageAltMaxPerCycle_ft, " ", updateTime_s ); #setprop (""~myNodeName~"/velocities/vertical-speed-fps", verticalspeed); #set the target alt. This mainly works for aircraft. #when crashing, only do this if the new target alt is less then the current #target al newTgtAlt_ft = targetAlt_ft + damageAltAddCurrent_ft; currTgtAlt_ft = getprop (""~myNodeName~"/controls/flight/target-alt");#in ft if (currTgtAlt_ft==nil) currTgtAlt_ft=0; if (! (damageValue > 0.8 ) or newTgtAlt_ft < currTgtAlt_ft ) setprop (""~myNodeName~"/controls/flight/target-alt", (newTgtAlt_ft )); #target altitude--this is 10 feet or so in front of us for a ship or up to 1 minute in front for an aircraft #if going uphill base the altitude on the front of the vehicle (targetAlt). #This keeps the vehicle from sinking into the #hillside when climbing. This is a bit of a kludge that is simple/fast #because we have already calculated targetAlt in calculating the pitch. #To make this precise, calculate the correct position forward #based on the current speed of the current object and updateTime_s #and find the altitude of that spot. #For aircraft the targetAlt is the altitude 1 minute out IF that is higher #than the ground level. if (lookingAheadAlt_ft > alt_ft ) useAlt_ft = lookingAheadAlt_ft; else useAlt_ft=alt_ft; calcAlt_ft = (useAlt_ft + alts.wheelsOnGroundAGL_ft + alts.targetAGL_ft + damageAltAddCumulative_ft + damageAltAddCurrent_ft); # calcAlt_ft=where the object should be, in feet #if it is an aircraft we try to control strictly via setting the target # altitude etc. (above). If a ship etc. we just have to force it to that altitude (below). However if an aircraft gets too close to the ground #the AI aircraft controls just won't react quickly enough so we "rescue" #it by simply moving it up a bit (see below). #print ("type=", type); if (type != "aircraft") { setprop (""~myNodeName~"/position/altitude-ft", (calcAlt_ft) ); # feet } # for an aircraft, if it is within feet of the ground (and not forced # there because of damage etc.) then we "rescue" it be putting it 25 feet # above ground again. elsif ( currAlt_ft < toFrontAlt_ft + 75 and !(damageValue > 0.8 ) ) { #print ("correcting!", myNodeName, " ", toFrontAlt_ft, " ", currAlt_ft, " ", currAlt_ft-toFrontAlt_ft, " ", toFrontAlt_ft+40, " ", currAlt_ft+20 ); #set the pitch to try to make it look like we're climbing real #fast here, not just making an emergency correction . . . #for some reason the pitch is always aiming down when we #need to make a correction up, using pitchangle1. #Kluge, we just always put pitch @30 degrees setprop (""~myNodeName~"/orientation/pitch-deg", 30 ); setprop (""~myNodeName~"/controls/flight/target-pitch", 30); if (currAlt_ft < toFrontAlt_ft + 25 ) { #dramatic correction print ("Avoiding ground collision, ", myNodeName); setprop (""~myNodeName~"/position/altitude-ft", toFrontAlt_ft + 40 ); setprop (""~myNodeName~"/controls/flight/target-alt", toFrontAlt_ft + 40); } else { #more minor correction print ("Correcting course to avoid ground, ", myNodeName); setprop (""~myNodeName~"/position/altitude-ft", currAlt_ft + 20 ); setprop (""~myNodeName~"/controls/flight/target-alt", currAlt_ft + 20); } } if ( type == "aircraft" and (damageValue > 0.8 )) { #print ("Crashing! damageAltAdd & damageAltAddCurrent, damageAltAddPrev & damageAltAddMax, damageAltMaxRate, damageAltMaxPerCycle ",damageAltAdd, " ",damageAltAddCurrent," ", damageAltAddPrev, " ", damageAltAddMax, " ", myNodeName, " ", damageAltMaxRate, " ", damageAltMaxPerCycle, " ", updateTime_s ); #if crashing we just force it to the right altitude, even if an aircraft #but we move it a maximum of damageAlMaxRate #if it's an airplane & it's crashing, we take it down as far as #needed OR by the maximum allowed rate. #when it hits this altitude it is (or most very soon become) #completely kaput #For many objects, depending on how the model is set up, this #may be somewhat higher or lower than actual ground level if ( damageAltMaxPerCycle_ft < damageAltAddCurrent_ft ) { #setprop (""~myNodeName~"/position/altitude-ft", (objectsLowestAllowedAlt_ft + alts.wheelsOnGroundAGL_ft + damageAltAddCurrent) ); # feet #setprop (""~myNodeName~"/position/altitude-ft", (currAlt_ft + damageAltAddCurrent - updateTime_s) ); # feet #nice #print ("damageAltAddCurrent=", damageAltAddCurrent); setprop (""~myNodeName~"/controls/flight/target-alt", currAlt_ft -500); setprop (""~myNodeName~"/controls/flight/target-pitch", -45); var orientPitch=getprop (""~myNodeName~"/orientation/pitch-deg"); if ( orientPitch > -10) setprop (""~myNodeName~"/orientation/pitch-deg", orientPitch-1); } elsif (currAlt_ft + damageAltMaxPerCycle_ft > objectsLowestAllowedAlt_ft ) { #put it down by the max allowed rate #setprop (""~myNodeName~"/position/altitude-ft", (currAlt_ft + damageAltMaxPerCycle_ft ) ); #setprop (""~myNodeName~"/position/altitude-ft", (currAlt_ft + damageAltAddCurrent_ft - updateTime_s*2 ) ); #print ("damageAltAddCurrent=", damageAltAddCurrent); #not that nice setprop (""~myNodeName~"/controls/flight/target-alt", currAlt_ft -10000); setprop (""~myNodeName~"/controls/flight/target-pitch", -70); var orientPitch_deg=getprop (""~myNodeName~"/orientation/pitch-deg"); if (orientPitch_deg > -20) setprop (""~myNodeName~"/orientation/pitch-deg", orientPitch_deg - 1 ); dodge (myNodeName); #it will roll/dodge as though under fire #a vibration setprop (""~myNodeName~"/orientation/roll-deg", getprop(""~myNodeName~"/orientation/roll-deg") + rand() * 8 - 4 ); } else { #closer to the ground than MaxPerCycle so, just put it right on the ground. Oh yeah, also explode etc. hitground_stop_explode(myNodeName, objectsLowestAllowedAlt_ft); } #somehow the aircraft are getting below ground sometimes #sometimes it's just because they hit into a mountain or something #else in the way. #kludgy fix, just check for it & put them back on the surface #if necessary. And expldode & stuff. aircraftAlt_ft = getprop (""~myNodeName~"/position/altitude-ft" ); if ( aircraftAlt_ft < alt_ft ) { hitground_stop_explode(myNodeName, objectsLowestAllowedAlt_ft ); } } #whatever else, we don't let objects go below their lowest allowed altitude #Maybe they are skidding along on teh ground, but they are not allowed # to skid along UNDER the ground . . . if (currAlt_ft < objectsLowestAllowedAlt_ft) setprop(""~myNodeName~"/position/altitude-ft", objectsLowestAllowedAlt_ft); #where the object is, in feet setprop(""~myNodeName~"/bombable/attributes/damageAltAddCurrent_ft", damageAltAddCurrent_ft); setprop(""~myNodeName~"/bombable/attributes/damageAltAddCumulative_ft", damageAltAddCumulative_ft + damageAltAddCurrent_ft); #print ("alt = ", alt, " currAlt_ft = ", currAlt_ft, " deltaAlt= ", deltaAlt, " altAdjust= ", alts.wheelsOnGroundAGL_ft, " calcAlt_ft=", calcAlt_ft, "damageAltAddCurrent=", damageAltAddCurrent, " ", myNodeName); # add rand() so that all objects don't do this function simultaneously settimer(func { ground_loop(id, myNodeName)}, (0.5 + rand())*updateTime_s ); } ####################################################### #location-check loop, a timer function, every 15-16 seconds to check if the object has been relocated (this will happen if the object is set up as an AI ship or aircraft and FG is reset). If so it restores the object to its position before the reset. #This solves an annoying problem in FG, where using file/reset (which #you might do if you crash the aircraft, but also if you run out of ammo #and need to re-load or for other reasons) will also reset the objects to #their original positions. #With moving objects (set up as AI ships or aircraft with velocities, #rudders, and/or flight plans) the objects abre often just getting to #interesting/difficult positions, so we want to preserve those positions # rather than letting them reset back to where they started. #TODO: Some of this could be done better using a listener on /sim/signals/reinit var location_loop = func(id, myNodeName) { var loopid = getprop(""~myNodeName~"/bombable/location-loopid"); id == loopid or return; var node = props.globals.getNode(myNodeName); var started = getprop (""~myNodeName~"/position/previous/initialized"); var lat = getprop(""~myNodeName~"/position/latitude-deg"); var lon = getprop(""~myNodeName~"/position/longitude-deg"); var alt_ft = getprop(""~myNodeName~"/position/altitude-ft"); #getting the global_x,y,z seems to stop strange behavior from the smoke #when we do a relocate of the objects var global_x = getprop(""~myNodeName~"/position/global-x"); var global_y = getprop(""~myNodeName~"/position/global-y"); var global_z = getprop(""~myNodeName~"/position/global-z"); prev_distance=0; directDistance=200; # this will be set as previous/distance if we are initializing # if we have previously recorded the position we check if it has moved too far # if it has moved too far it is because FG has reset and we # then restore the object's position to where it was before the reset if (started ) { var prevlat = getprop(""~myNodeName~"/position/previous/latitude-deg"); var prevlon = getprop(""~myNodeName~"/position/previous/longitude-deg"); var prevalt_ft = getprop(""~myNodeName~"/position/previous/altitude-ft"); var prev_global_x = getprop(""~myNodeName~"/position/previous/global-x"); var prev_global_y = getprop(""~myNodeName~"/position/previous/global-y"); var prev_global_z = getprop(""~myNodeName~"/position/previous/global-z"); var prev_distance = getprop(""~myNodeName~"/position/previous/distance"); var GeoCoord = geo.Coord.new(); GeoCoord.set_latlon(lat, lon, alt_ft * 0.3048); var GeoCoordprev = geo.Coord.new(); GeoCoordprev.set_latlon(prevlat, prevlon, prevalt_ft * 0.3048); var directDistance = GeoCoord.distance_to(GeoCoordprev); #print ("Object ", myNodeName ", distance: ", directDistance); #4X the previously traveled distance is our cutoff #so if our object is moving faster/further than this we assume it has #been reset by FG and put it back where it was before the reset. #Luckily, this same scheme works in the case this subroutine has moved the #object--then the previous distance exactly equals the distance traveled-- #so even though that is a much larger than usual distance (which would #usually trigger this subroutine to think an init had happened) since #the object moved that large distance on the **previous step** (due to the #reset) the move back is less than 4X the previous move and so it is OK. #A bit kludgy . . . but it works. if ( directDistance > 5 and directDistance > 4 * prev_distance ) { node.getNode("position/latitude-deg", 1).setDoubleValue(prevlat); node.getNode("position/longitude-deg", 1).setDoubleValue(prevlon); node.getNode("position/altitude-ft", 1).setDoubleValue(prevalt_ft); #now we want to show the previous location as this newly relocated position and distance traveled = 0; lat=prevlat; lon=prevlon; alt_ft=prevalt_ft; print ("Repositioned object ", myNodeName, " to lat: ", prevlat, " long: ", prevlon, " altitude: ", prevalt_ft," ft."); } } #now we save the current position node.getNode("position/previous/initialized", 1).setBoolValue(1); node.getNode("position/previous/latitude-deg", 1).setDoubleValue(lat); node.getNode("position/previous/longitude-deg", 1).setDoubleValue(lon); node.getNode("position/previous/altitude-ft", 1).setDoubleValue(alt_ft); node.getNode("position/previous/global-x", 1).setDoubleValue(global_x); node.getNode("position/previous/global-y", 1).setDoubleValue(global_y); node.getNode("position/previous/global-z", 1).setDoubleValue(global_z); node.getNode("position/previous/distance", 1).setDoubleValue(directDistance); # reset the timer so we will check this again in 15 seconds +/- # add rand() so that all objects don't do this function simultaneously # when 15-20 objects are all doing this simultaneously it can lead to jerkiness in FG settimer(func {location_loop(id, myNodeName); }, 15 + rand() ); } ################################################ #listener function on ballistic impacts #checks if the impact has hit our object and if so, adds the damage # damageMult can be set high (for easy to damage things) or low (for # hard to damage things). Default/normal value (M1 tank) should be 1. var test_impact = func(changedNode, myNodeName) { var impactNodeName = changedNode.getValue(); var impactNode = props.globals.getNode(impactNodeName); var node = props.globals.getNode(myNodeName); var oLat=node.getNode("position/latitude-deg").getValue(); var iLat=impactNode.getNode("impact/latitude-deg").getValue(); var maxLat = getprop (""~myNodeName~"/bombable/attributes/dimensions/maxLat"); var maxLon = getprop (""~myNodeName~"/bombable/attributes/dimensions/maxLon"); #quick-n-dirty way to tell if an impact is close to our object at all #without processor-intensive calculations #we do this first and then exit if not close, to reduce impact #of impacts on processing time deltaLat=abs(oLat-iLat); if (deltaLat > maxLat ) { #print ("Not close in lat."); return; } oLon= node.getNode("position/longitude-deg").getValue(); iLon= impactNode.getNode("impact/longitude-deg").getValue(); deltaLon=abs(oLon-iLon); if (deltaLon > maxLon ) { #print ("Not close in lon."); return; } oAlt_m= node.getNode("position/altitude-ft").getValue()*0.3048; iAlt_m= impactNode.getNode("impact/elevation-m").getValue(); #print ("Impactor: ", impactNodeName, ", Object: ", myNodeName); if (impactNodeName=="" or impactNodeName==nil or impactNode==nil) return; var ballisticMass_lb = impactNode.getNode("mass-slug").getValue() * 32.174049; #weight/mass of the ballistic object, in lbs #print ("Testing impact of ", impactNodeName, " on ", myNodeName, " mass= ", ballisticMass_lb); # figure how close the impact and terrain it's on var objectGeoCoord = geo.Coord.new(); objectGeoCoord.set_latlon(oLat,oLon,oAlt_m ); var impactGeoCoord = geo.Coord.new(); impactGeoCoord.set_latlon(iLat, iLon, iAlt_m); var impactDistance_m = objectGeoCoord.direct_distance_to(impactGeoCoord); var impactSurfaceDistance_m = objectGeoCoord.distance_to(impactGeoCoord); var heightDifference_m=math.abs(impactNode.getNode("impact/elevation-m").getValue() - node.getNode("position/altitude-ft").getValue()*0.3048); #any impacts somewhat close to us, we start dodging if (impactDistance_m < 500 ) dodge (myNodeName); #print ("Distance= ", impactDistance); #ignore anything more than 200 meters distant if (impactDistance_m > 200 ) return; #print (impactDistance_m); #return; var b = props.globals.getNode (""~myNodeName~"/bombable/attributes"); vuls= b.getNode("vulnerabilities").getValues(); var impactTerrain = impactNode.getNode("impact/type").getValue(); #If the terrain is ship, aircraft, carrier, etc. and the distance is #close to our object, we assume it is our object and this an actual #direct hit on it that definitely will do damage even if just a projectile #rather than an explosive. # #Todo: Make the distances here match the actual dimensions of our object. #ie, a ship will be far different in size from a jeep. Even just #using max dimension of the object/2 rather than 6.0 would be a #big improvement. # #Todo: Make sure the impact is actually on **this** object, not just another #one that happens to be immediately adjacent to this one. # #upping impact distance to 6.0 from 4.0, because it seems to ignore #legitimate hits at times. added surface/height distance to make it more #forgiving if the impact registers close to the object horizontally but above #or below, which seems to happen a lot. bhugh 8/2009. #How many shots does it take to down an object? Supposedly the Red Baron #at times put in as many as 500 machine-gun rounds into a target to *maek #sure* it really went down. #Potential for adding serious damage increases the closer we are to the center #of the object. We'll say more than damageRadius meters away, no potential for increased damage var damageRadius_m= getprop (""~myNodeName~"/bombable/attributes/dimensions/damageRadius_m"); var easyMode=1; #Easy Mode if (getprop(""~bomb_pp~"easy-mode")) { easyMode*=2; damageRadius_m*=2; } if (getprop(""~bomb_pp~"supereasy-mode")){ easyMode*=3; damageRadius_m*=3; } if (impactDistance_m > damageRadius_m ) damagePotential=0; else damagePotential = (damageRadius_m - impactDistance_m)/damageRadius_m; #FG seems to miss hits more often when they are too high/too low. #Thus the expanded 3*height_m parameter height_m=getprop (""~myNodeName~"/bombable/attributes/dimensions/height_m"); if((impactDistance_m <= damageRadius_m) and (impactTerrain != "terrain")) { print ("Bombable: Direct hit, ", impactNodeName, " on ", myNodeName, " Distance= ", impactDistance_m, " heightDiff= ", heightDifference_m, " terrain=", impactTerrain, " radius=", damageRadius_m); #print ("Bombable: Direct hit. ballisticMass_lb=", ballisticMass_lb," Distance= ", impactDistance_m, " heightDiff= ", heightDifference, " terrain= " , impactTerrain); # print ("Bombable: Direct hit. Distance= ", impactDistance_m, " meters on ", myNodeName ); #didnt they use feet back in the Sopwith Camel Days? In {} targetStatusPopupTip (sprintf( "Direct hit, distance %d feet ", impactDistance_m/.3048), 20); #targetStatusPopupTip ("Direct hit, distance "~impactDistance_m~" meters "); # TODO: damage depend on mass and speed of the # ballistic # We can't do that yet as FG doesn't seem to expose the velocity of the object #our ballisticMass_lb variable is basically slugs * 32 or the "mass" of #the object expressed in pounds. So ballisticMass_lb=0.8 would # be a 0.8 pound bullet or cannon round. # if (ballisticMass_lb < 0.1){ #small chance of doing a decent amount of damage if ( rand()< damagePotential * vuls.damageVulnerability * easyMode/200 ) damageCaused=rand()/3; else damageCaused=0; #plus a very small chance of doing total damage if ( rand()< vuls.damageVulnerability*easyMode*ballisticMass_lb*damagePotential/20 ) damageCaused+=rand(); #always add just a bit of damage, ballisticMass_lb for a Vickers is about #0.0033, meaning it takes about 45 direct hits to definitely down a #Sopwith Camel--less if you are lucky. add_damage(ballisticMass_lb * vuls.damageVulnerability*easyMode/5 + damageCaused, myNodeName ); #we're going to say these heavier rounds always cause some damage #but they also have a slight change of doing full damage. } elsif (ballisticMass_lb < 0.3) { # heavier round if ( rand()< damagePotential*vuls.damageVulnerability*easyMode/10 ) damageCaused=rand()/2 + 0.2; else damageCaused=0; add_damage(vuls.damageVulnerability * easyMode * ballisticMass_lb / 5 + damageCaused, myNodeName ); } elsif (ballisticMass_lb < 0.8) { # heavier round if ( rand()< damagePotential*vuls.damageVulnerability*easyMode/5 ) damageCaused=rand()/2 + 0.5; else damageCaused=0; add_damage(vuls.damageVulnerability * easyMode * ballisticMass_lb /5 + damageCaused, myNodeName); } elsif(ballisticMass_lb < 1.2) { if ( rand()< damagePotential*vuls.damageVulnerability*easyMode * ballisticMass_lb / 5 ) damageCaused=rand() + 1; else damageCaused=0; # must be a big gun, like the GAU-8 gatling gun. add_damage(0.8 * vuls.damageVulnerability * ballisticMass_lb * easyMode + damageCaused, myNodeName ); } else { # large bombs etc have potential to do huge damage, if they hit the right spot if ( rand()< damagePotential*vuls.damageVulnerability*easyMode * ballisticMass_lb / 5 ) damageCaused=rand() * ballisticMass_lb + 1; else damageCaused=0; # and they always do quite a bit of damage add_damage( vuls.damageVulnerability * ballisticMass_lb * easyMode + damageCaused, myNodeName ); } } else { # a near hit, on terrain if (ballisticMass_lb >= 200) print ("Bombable: Close hit by heavy bomb, ", impactNodeName, " on ", myNodeName, " Distance= ", impactDistance_m, " heightDiff= ", heightDifference_m, " terrain=", impactTerrain, " radius=", damageRadius_m); # check submodel blast effect distance. if((ballisticMass_lb >= 200) and (ballisticMass_lb < 350)) { # Mk-81 class if(impactDistance_m <= 10 + damageRadius_m) add_damage(1.0 * vuls.damageVulnerability * ballisticMass_lb * easyMode, myNodeName); elsif((impactDistance_m > 10 + damageRadius_m) and (impactDistance_m < 30 + damageRadius_m)) add_damage(0.2 * vuls.damageVulnerability * ballisticMass_lb* easyMode, myNodeName); elsif((impactDistance_m > 30 + damageRadius_m) and (impactDistance_m < 60 + damageRadius_m)) add_damage(0.05 * vuls.damageVulnerability * ballisticMass_lb* easyMode, myNodeName); } elsif((ballisticMass_lb >= 350) and (ballisticMass_lb < 750)) { # Mk-82 class if(impactDistance_m <= 33 + damageRadius_m ) add_damage(1.0 * vuls.damageVulnerability * ballisticMass_lb * easyMode, myNodeName); elsif((impactDistance_m > 33 + damageRadius_m) and (impactDistance_m < 50 + damageRadius_m)) add_damage(0.25 * vuls.damageVulnerability * ballisticMass_lb * easyMode, myNodeName); } elsif(ballisticMass_lb >= 750) { # Mk-83 class and upper if(impactDistance_m <= 70 + damageRadius_m ) add_damage(1.0 * vuls.damageVulnerability * ballisticMass_lb * easyMode, myNodeName); elsif((impactDistance_m > 70 + damageRadius_m ) and (impactDistance_m < 200 + damageRadius_m)) add_damage(0.25 * vuls.damageVulnerability * ballisticMass_lb * easyMode, myNodeName); } } } ######################################################### #rudder_roll_climb - sets the rudder position/roll degrees #roll degrees controls aircraft & rudder position #and for aircraft, sets an amount of climb #controls ships, so we just change both, to be safe var rudder_roll_climb = func (myNodeName, degrees=15, alt_ft=-20 ){ var b = props.globals.getNode (""~myNodeName~"/bombable/attributes"); alts= b.getNode("altitudes").getValues(); #rudder/roll currRudder=getprop(""~myNodeName~"/surface-positions/rudder-pos-deg"); if (currRudder==nil) currRudder=0; currRoll=getprop(""~myNodeName~"/controls/flight/target-roll"); if (currRoll==nil) currRoll=0; #add our amount to any existing roll/rudder & set the new roll/rudder position setprop(""~myNodeName~"/surface-positions/rudder-pos-deg", currRudder + degrees); setprop(""~myNodeName~"/controls/flight/target-roll", currRoll + degrees); #altitude #This only works for aircraft but that's OK because it's not sensible #for a ground vehicle or ship to dive or climb above or below ground/sea #level anyway (submarines excepted . . . but under current the FG AI system # it would have to be operated as an "aircraft", not a "ship", if it # wants to be able to climb & dive). var currAlt_ft= getprop(""~myNodeName~"/position/altitude-ft"); #where the object is, in ft if (currAlt_ft + alt_ft < alts.minimumAGL_ft ) alt_ft = alts.minimumAGL_ft; if (currAlt_ft + alt_ft > alts.maximumAGL_ft ) alt_ft = alts.maximumAGL_ft; setprop (""~myNodeName~"/controls/flight/target-alt", alt_ft); #print (myNodeName, " dodging ", degrees, " degrees ", alt_ft, " feet"); } ################################################################ #function makes an object dodge # var dodge = func(myNodeName) { var b = props.globals.getNode (""~myNodeName~"/bombable/attributes"); if ( getprop ( ""~myNodeName~"/bombable/attributes/evasions/dont-dodge" ) )return; evas= b.getNode("evasions").getValues(); #amount to dodge, up to dodgeMax_deg in either direction dodgeAmount_deg=(evas.dodgeMax_deg-evas.dodgeMin_deg)*rand()+evas.dodgeMin_deg; if (rand() > evas.dodgeROverLPreference_percent/100) dodgeAmount_deg = -dodgeAmount_deg; #taret amount to climb or drop dodgeAltAmount_ft=(evas.dodgeAltMin_ft - evas.dodgeAltMax_ft) * rand()+evas.dodgeAltMin_ft; #set rudder or roll degrees to that amount rudder_roll_climb (myNodeName, dodgeAmount_deg, dodgeAltAmount_ft); var dodgeDelay=(evas.dodgeDelayMax_sec-evas.dodgeDelayMin_sec)*rand()+evas.dodgeDelayMin_sec; #Don't change rudder/roll again until the delay setprop ( ""~myNodeName~"/bombable/attributes/evasions/dont-dodge" , 1); #After this delay, reset the rudder/roll to where it was before #and allow further dodging to occur. Just leave the target alt # where it is. settimer ( func {setprop(""~myNodeName~"/bombable/attributes/evasions/dont-dodge", 0); rudder_roll_climb (myNodeName, -dodgeAmount_deg, 0 );}, dodgeDelay ); } ################################################################ # function adds damage to the main aircraft when a msg # is received over MP #damageRise is the increase in damage sent by the remote MP aircraft #damageTotal is the remote MP aircraft's current total of damage # (This should always be <= our damage total, so it is a failsafe # in case of some packet loss) var mp_add_damage = func (damageRise, damageTotal=0, smokeStart=0, fireStart=0 ) { var damageValue = getprop("/environment/bombable/attributes/damage"); if (damageValue == nil ) damageValue=0; if(damageValue < 1.0) damageValue += damageRise; if (damageValue 1.0) damageValue = 1.0; elsif(damageValue < 0.0) damageValue = 0.0; setprop("/environment/bombable/attributes/damage", damageValue); selfStatusPopupTip (sprintf( "You've been hit! Damage added %d%% - Total damage %d%%", damageRise*100, damageValue*100), 30); print (sprintf( "You've been hit! Damage added %d - damage total %d", damageRise*100, damageValue*100)); if (smokeStart) startSmoke ("damagedengine", ""); else deleteSmoke("damagedengine", ""); if (fireStart) startFire (""); else deleteFire(""); if (damageValue >= 1) { #turn off all engines setprop("/controls/engines/engines[0]/magnetos",0); setprop("/controls/engines/engines[0]/throttle",0); setprop("/controls/engines/engines[1]/magnetos",0); setprop("/controls/engines/engines[1]/throttle",0); setprop("/controls/engines/engines[2]/magnetos",0); setprop("/controls/engines/engines[2]/throttle",0); setprop("/controls/engines/engines[3]/magnetos",0); setprop("/controls/engines/engines[3]/throttle",0); var smokeStartsize=rand()*10 + 5; settimer (func {setprop ("/environment/bombable/fire-particles/smoke-startsize", smokeStartsize); }, 2.5);#turn the big explosion off sorta quickly smokeStartsize = smokeStartsize * rand() * 100 + 100; #make a really big explosion, so they know they are dead. } } #send the damage message via multiplayer var mp_send_damage = func (myNodeName, damageRise=0 ) { if (! getprop ("/environment/bombable/mp_broadcast_exists")) return; var damageValue = getprop(""~myNodeName~"/bombable/attributes/damage"); if (damageValue==nil) damageValue=0; callsign=getprop (""~myNodeName~"/callsign"); var fireStarted=getprop(""~myNodeName~"/bombable/fire-particles/fire-trigger"); if (fireStarted == nil ) fireStarted=0; var damageEngineSmokeStarted=getprop(""~myNodeName~"/bombable/fire-particles/damagedengine-trigger"); if (damageEngineSmokeStarted == nil ) damageEngineSmokeStarted = 0; var msg=damage_msg (callsign, damageRise, damageValue, damageEngineSmokeStarted, fireStarted,); #print ("MP sending: ", msg); broadcast.send(msg); } ################################################################ #function adds damage to an AI aircraft #(called by the fire loop and ballistic impact #listener function, typically) # Also slows down the vehicle whenever damage increases. # vuls.damageVulnerability multiplies the damage, with an M1 tank = 1. vuls.damageVulnerability=2 # means 2X the damage. # maxSpeedReduce is a percentage, the maximum percentage to reduce speed # in one step. An airplane might keep moving close to the same speed # even if the engine dies completely. A tank might stop forward motion almost # instantly. var add_damage = func(damageRise, myNodeName ) { node = props.globals.getNode(myNodeName); var b = props.globals.getNode (""~myNodeName~"/bombable/attributes"); var vuls= b.getNode("vulnerabilities").getValues(); var spds= b.getNode("velocities").getValues(); var livs= b.getNode ("damageLiveries").getValues(); var liveriesCount= b.getNode ("damageLiveries/count").getValue(); var type=node.getName(); var damageValue = getprop(""~myNodeName~"/bombable/attributes/damage"); # update bombable/attributes/damage: 0.0 mean no damage, 1.0 mean full damage prevDamageValue=damageValue; if(damageValue < 1.0) damageValue += damageRise; #make sure it's in range 0-1.0 if(damageValue > 1.0) damageValue = 1.0; elsif(damageValue < 0.0) damageValue = 0.0; setprop(""~myNodeName~"/bombable/attributes/damage", damageValue); #print ("damageValue=",damageValue); callsign=node.getNode("callsign", 1).getValue(); if (callsign==nil or callsign=="") callsign=node.getName() ~ "[" ~ node.getIndex() ~ "]"; if (int(damageValue * 20 )!=int(prevDamageValue * 20 )) { var msg= "Damage added: " ~ int( damageRise*100) ~ "% - Total damage: " ~ int( damageValue * 100) ~ "% for " ~ callsign; print ( msg ); targetStatusPopupTip (msg, 20); } onGround= getprop (""~myNodeName~"/bombable/on-ground"); if (onGround==nil) onGround=0; if (onGround) { #all to a complete stop setprop(""~myNodeName~"/controls/tgt-speed-kts", 0); setprop(""~myNodeName~"/controls/flight/target-spd", 0); setprop(""~myNodeName~"/velocities/true-airspeed-kt", 0); #we hit the ground, now we are 100% dead setprop(""~myNodeName~"/bombable/attributes/damage", 1); if (liveriesCount > 0 and liveriesCount != nil ) { livery = livs.damageLivery [ int ( damageValue * ( liveriesCount - 1 ) ) ]; setprop(""~myNodeName~"/bombable/texture-corps-path", livery ); } if (type=="multiplayer") mp_send_damage(myNodeName, 1); return; } #print (damageRise, " ", myNodeName); #max speed reduction due to damage, in % minSpeed = spds.minSpeed_kt; #if the object is "on the ground" or "completely sunk" due to damage # then we make it come to a stop much more dramatically if ( onGround){ if (spds.maxSpeedReduce_percent<20) spds.maxSpeedReduce_percent=20; minSpeed=0; onGround=1; } else onGround=0; var damageValue = getprop(""~myNodeName~"/bombable/attributes/damage"); if ( damageValue==nil ) damageValue=0; #for moving objects (ships & aircraft), reduce velocity each time damage added #eventually stopping when damage = 1. #But don't reduce speed below minSpeed. #we put it here outside the "if" statement so that burning #objects continue to slow/stop even if their damage is already at 1 # (this happens when file/reset is chosen in FG) var tgt_spd_kts=getprop (""~myNodeName~"/controls/tgt-speed-kts"); if (tgt_spd_kts == nil ) tgt_spd_kts=0; var flight_tgt_spd=getprop (""~myNodeName~"/controls/flight/target-spd"); if (flight_tgt_spd == nil ) flight_tgt_spd=0; var true_spd=getprop (""~myNodeName~"/velocities/true-airspeed-kt"); if (true_spd == nil ) true_spd=0; maxSpeedReduceProp=1-spds.maxSpeedReduce_percent/100; #spds.maxSpeedReduce_percent is a percentage speedReduce= 1-damageValue; if (speedReduce < maxSpeedReduceProp) speedReduce=maxSpeedReduceProp; pitch=getprop (""~myNodeName~"/orientation/pitch-deg"); var node = props.globals.getNode(myNodeName); #print ("type=", type); if (type=="aircraft") { if (pitch > - 90) setprop (""~myNodeName~"/orientation/pitch-deg", pitch-1); if ( pitch < 0 and damageValue >= -0.8 and !onGround) { #nothing for now! } else { if (flight_tgt_spd > minSpeed) setprop(""~myNodeName~"/controls/flight/target-spd", flight_tgt_spd * speedReduce); else setprop(""~myNodeName~"/controls/flight/target-spd", minSpeed); } #ships etc we control all these ways, making sure the speed decreases but #not below the minimum allowed } else { if (tgt_spd_kts > minSpeed) setprop(""~myNodeName~"/controls/tgt-speed-kts", tgt_spd_kts * speedReduce); if (flight_tgt_spd > minSpeed) setprop(""~myNodeName~"/controls/flight/target-spd", flight_tgt_spd * speedReduce); if (true_spd > minSpeed) setprop(""~myNodeName~"/velocities/true-airspeed-kt", true_spd * speedReduce); } var fireStarted=getprop(""~myNodeName~"/bombable/fire-particles/fire-trigger"); if (fireStarted == nil ) fireStarted=0; var damageEngineSmokeStarted=getprop(""~myNodeName~"/bombable/fire-particles/damagedengine-trigger"); if (damageEngineSmokeStarted == nil ) damageEngineSmokeStarted = 0; #don't print this for every fire damage rise, but otherwise . . . #if (!fireStarted or damageRise > vuls.fireDamageRate_percentpersecond * 2.5 ) print ("Damage added: ", damageRise, ", Total damage: ", damageValue); #Start damaged engine smoke but only sometimes; great chance when hitting an aircraft if (!damageEngineSmokeStarted and !fireStarted and rand()*100 < vuls.engineDamageVulnerability_percent ) startSmoke("damagedengine",myNodeName); # start fire if there is enough damages. if(damageValue >= 1 - vuls.fireVulnerability_percent/100 and !fireStarted ) { print ("Starting fire"); #use small, medium, large smoke column depending on vuls.damageVulnerability #(high vuls.damageVulnerability means small/light/easily damaged while # low vuls.damageVulnerability means a difficult, hardened target that should burn # more vigorously once finally on fire) var fp=""; if (vuls.damageVulnerability > 2.5 ) { fp="AI/Aircraft/Fire-Particles/fire-particles-small.xml"; } elsif (vuls.damageVulnerability < 0.5 ) { fp="AI/Aircraft/Fire-Particles/fire-particles-large.xml"; } else {fp="AI/Aircraft/Fire-Particles/fire-particles.xml";} startFire(myNodeName, fp); #only one damage smoke at a time . . . deleteSmoke("damagedengine",myNodeName); #fire can be extinguished up to MaxTime_seconds in the future, #if it is extinguished we set up the damagedengine smoke so #the smoke doesn't entirely go away, but no more damage added if ( rand() * 100 < vuls.fireExtinguishSuccess_percentage ) { settimer (func { deleteFire (myNodeName); startSmoke("damagedengine",myNodeName); } , rand() * vuls.fireExtinguishMaxTime_seconds + 15 ) ; } ; # print ("started fire"); #Set livery to the one corresponding to this amount of damage if (liveriesCount > 0 and liveriesCount != nil ) { livery = livs.damageLivery [ int ( damageValue * ( liveriesCount - 1 ) ) ]; setprop(""~myNodeName~"/bombable/texture-corps-path", livery ); } } if (type=="multiplayer") mp_send_damage(myNodeName, damageRise); } #################################################### #functions to increment loopids #these are called on init and destruct (which should be called #when the object loads/unloads) #When the loopid increments it will kill any timer functions #using that loopid for that object. (Otherwise they will just #continue to run indefinitely even though the object itself is unloaded) var inc_bomb_loopid = func (nodeName) { var loopid = getprop(""~nodeName~"/bombable/bomb-loopid"); if ( loopid == nil ) loopid=0; loopid += 1; setprop(""~nodeName~"/bombable/bomb-loopid", loopid); return loopid; } var inc_ground_loopid = func (nodeName) { var loopid = getprop(""~nodeName~"/bombable/ground-loopid"); if ( loopid == nil ) loopid=0; loopid += 1; setprop(""~nodeName~"/bombable/ground-loopid", loopid); return loopid; } var inc_location_loopid = func (nodeName) { var loopid = getprop(""~nodeName~"/bombable/location-loopid"); if ( loopid == nil ) loopid=0; loopid += 1; setprop(""~nodeName~"/bombable/location-loopid", loopid); return loopid; } ##################################################### # Set livery color (including normal through # slightly and then completely damaged) # # Example: # # liveries = [ # "Models/livery_nodamage.png", # "Models/livery_slightdamage.png", # "Models/livery_highdamage.png" # ]; # bombable.set_livery (cmdarg().getPath(), liveries); var set_livery = func (nodeName, liveries) { var node = props.globals.getNode(nodeName); var livs = node.getNode ("/bombable/attributes/damageLiveries",1).getValues(); #set new liveries, also set the count to the number #of liveries installed if (liveries == nil or size ( liveries) == 0 ) { livs.removeChildren(); node.getNode("bombable/attributes/damageLiveries/count", 1).setValue( 0 ); } else { node.getNode("bombable/attributes/damageLiveries/count", 1).setValue( size (liveries) ); node.getNode ("/bombable/attributes/damageLiveries",1).setValues( { damageLivery : liveries } ); #current color (we'll set it to the undamaged color; #if the object is on fire/damage damaged this will soon be updated) #by the timer function #To actually work, the aircraft's xml file must be set up with an #animation to change the texture, keyed to the bombable/texture-corps-path #property node.getNode("bombable/texture-corps-path", 1).setValue(liveries[0]); } } var checkRange = func (v, low=nil, high=nil, default=1) { if ( v == nil ) v=default; if ( low != nil and v < low ) v = low; if ( high != nil and v > high ) v = high; return v; } ##################################################### # initialize: Do sanity checking, then # slurp the pertinent properties into # the object's node tree under sub-node "bombable" # so that they can be access by all the different # subroutines # # Now if you just need a certain property or two you can simply read it # with getprops. # # But for those routines that use many/all we can just grab them all with # var b = props.globals.getNode (""~myNodeName~"/bombable/attributes"); # bomb= b.getValues(); #all under the "bombable/attributes" branch # Then use values like bomb.dimensions.width_m etc. # Normally don't do this as it slurps in MANY values # # But (better if you only need one sub-branch) # dims= b.getNode("dimensions").getValues(); # Gets values from subbranch 'dimensions'. # Then your values are dims.width_m etc. # # var initialize = func ( b ){ #do sanity checking on input #also calculate a few values that will be useful later on & #add them to the object #initialize damage level of this object b.damage = 0; b.updateTime_s = checkRange ( b.updateTime_s, 0, 10, 1); ##altitudes sanity checking b.altitudes.wheelsOnGroundAGL_m = checkRange ( b.altitudes.wheelsOnGroundAGL_m, -1000000, 1000000, 0 ); b.altitudes.minimumAGL_m = checkRange ( b.altitudes.minimumAGL_m, -1000000, 1000000, 0 ); b.altitudes.maximumAGL_m = checkRange ( b.altitudes.maximumAGL_m, -1000000, 1000000, 0 ); #keep this one negative or zero: b.altitudes.crashedAGL_m = checkRange ( b.altitudes.crashedAGL_m, -1000000, 0, -0.001 ); if (b.altitudes.crashedAGL_m == 0 )b.altitudes.crashedAGL_m = -0.001; b.altitudes.initialized=0; #this is how ground_loop knows to initialize the alititude on its first call b.altitudes.wheelsOnGroundAGL_ft=b.altitudes.wheelsOnGroundAGL_m/.3048; b.altitudes.minimumAGL_ft=b.altitudes.minimumAGL_m/.3048; b.altitudes.maximumAGL_ft=b.altitudes.maximumAGL_m/.3048; b.altitudes.crashedAGL_ft=b.altitudes.crashedAGL_m/.3048; #crashedAGL must be at least a bit lower than minimumAGL if (b.altitudes.crashedAGL_m > b.altitudes.minimumAGL_m ) b.altitudes.crashedAGL_m = b.altitudes.minimumAGL_m - 0.001; #evasions sanity checking b.evasions.dodgeDelayMax_sec = checkRange ( b.evasions.dodgeDelayMax_sec, 0, 600, 30 ); b.evasions.dodgeDelayMin_sec = checkRange ( b.evasions.dodgeDelayMin_sec, 0, 600, 5 ); if (b.evasions.dodgeDelayMax_sec< b.evasions.dodgeDelayMin_sec) b.evasions.dodgeDelayMax_sec=b.evasions.dodgeDelayMin_sec; b.evasions.dodgeMax_deg = checkRange ( b.evasions.dodgeMax_deg, 0, 180, 90 ); b.evasions.dodgeMin_deg = checkRange ( b.evasions.dodgeMin_deg, 0, 180, 30 ); if (b.evasions.dodgeMax_deg< b.evasions.dodgeMin_deg) b.evasions.dodgeMax_deg=b.evasions.dodgeMax_deg; b.evasions.dodgeROverLPreference_percent = checkRange ( b.evasions.dodgeROverLPreference_percent, 0, 100, 50 ); b.evasions.dodgeAltMax_m = checkRange ( b.evasions.dodgeAltMax_m, -100000, 100000, 20 ); b.evasions.dodgeAltMin_m = checkRange ( b.evasions.dodgeAltMin_m, -100000, 100000, -20 ); if (b.evasions.dodgeAltMax_m < b.evasions.dodgeAltMin_m) b.evasions.dodgeAltMax_m = b.evasions.dodgeAltMin_m; b.evasions.dodgeAltMin_ft = b.evasions.dodgeAltMin_m/.3048; b.evasions.dodgeAltMax_ft = b.evasions.dodgeAltMax_m/.3048; ##dimensions sanity checking b.dimensions.width_m = checkRange ( b.dimensions.width_m, 0, nil , 30 ); b.dimensions.length_m = checkRange ( b.dimensions.length_m, 0, nil, 30 ); b.dimensions.height_m = checkRange ( b.dimensions.height_m, 0, nil, 30 ); b.dimensions.damageRadius_m =checkRange ( b.dimensions.damageRadius_m, 0, nil, 6 ); #add some helpful new: # b.dimensions.width_ft = b.dimensions.width_m/.3048; b.dimensions.length_ft = b.dimensions.length_m/.3048; b.dimensions.height_ft = b.dimensions.height_m/.3048; b.dimensions.damageRadius_ft = b.dimensions.damageRadius_m/.3048; ## velocities sanity checking b.velocities.maxSpeedReduce_percent = checkRange ( b.velocities.maxSpeedReduce_percent, 0, 100, 1 ); b.velocities.minSpeed_kt = checkRange (b.velocities.minSpeed_kt, 0, nil, 0 ); b.velocities.damagedAltitudeChangeMaxRate_meterspersecond = checkRange (b.velocities.damagedAltitudeChangeMaxRate_meterspersecond, 0, nil, 0.5 ); ##damage sanity checking if (b.vulnerabilities.damageVulnerability<=0) b.vulnerabilities.damageVulnerability = 1; b.vulnerabilities.engineDamageVulnerability_percent = checkRange (b.vulnerabilities.engineDamageVulnerability_percent, 0, 100, 1 ); b.vulnerabilities.fireVulnerability_percent = checkRange (b.vulnerabilities.fireVulnerability_percent, -1, 100, 20 ); b.vulnerabilities.fireDamageRate_percentpersecond = checkRange (b.vulnerabilities.fireDamageRate_percentpersecond, 0, 100, 1 ); b.vulnerabilities.fireExtinguishMaxTime_seconds = checkRange (b.vulnerabilities.fireExtinguishMaxTime_seconds, 0, nil, 3600 ); b.vulnerabilities.fireExtinguishSuccess_percentage = checkRange ( b.vulnerabilities.fireExtinguishSuccess_percentage, 0, 100, 10 ); b.vulnerabilities.explosiveMass_kg = checkRange ( b.vulnerabilities.explosiveMass_kg, 0, 10000000, 1000 ); b.damageLiveries.count = size (b.damageLiveries.damageLivery) ; #the object has stored the node telling where to store itself on the # property tree . . . b.objectNode.getNode("bombable/attributes",1).setValues( b ); } ##################################################### #call to make an object bombable # # features/parameters are set by a bombableObject and # a previous call to initialize (above) var bombable_init = func(myNodeName) { var node = props.globals.getNode (""~myNodeName); var b = props.globals.getNode (""~myNodeName~"/bombable/attributes"); dims= b.getNode("dimensions").getValues(); #we increment this each time we are inited or de-inited #when the loopid is changed it kills the timer loops that have that id loopid=inc_bomb_loopid(myNodeName); foreach (var smokeType; [ ["fire",88, 3600], ["jetcontrail", 77, -1], ["smoketrail", 55, -1], ["pistonexhaust", 15, -1], ["damagedengine", 55, -1], ["flare",66,3600] ] ) { props.globals.getNode(""~trigger1_pp~smokeType[0]~trigger2_pp, 1).setBoolValue(1); props.globals.getNode(""~life1_pp~smokeType[0]~life2_pp, 1).setDoubleValue(smokeType[1]); props.globals.getNode(""~burntime1_pp~smokeType[0]~burntime2_pp, 1).setDoubleValue(smokeType[2]); } # Add some useful nodes setprop ("/environment/bombable/fire-particles/smoke-startsize", 11.0); setprop ("/environment/bombable/fire-particles/smoke-endsize", 50.0); setprop ("/environment/bombable/fire-particles/smoke-startsize-small", 6.5); setprop ("/environment/bombable/fire-particles/smoke-endsize-small", 40); setprop ("/environment/bombable/fire-particles/smoke-startsize-large", 26.0); setprop ("/environment/bombable/fire-particles/smoke-endsize-large", 150.0); node.getNode("bombable/fire-particles/fire-trigger", 1).setBoolValue(0); node.getNode("bombable/attributes/damage", 1).setDoubleValue(0.0); var objectGeoCoord = geo.Coord.new(); var objectGeoCoordMove = geo.Coord.new(); objectGeoCoord.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), (node.getNode("position/altitude-ft").getValue()*0.3048)); objectGeoCoordMove.set_latlon(node.getNode("position/latitude-deg").getValue(), node.getNode("position/longitude-deg").getValue(), (node.getNode("position/altitude-ft").getValue()*0.3048)); objectGeoCoordMove.apply_course_distance(0, dims.damageRadius_m + 200); #we're assuming 200 meters is the max effective distance of our largest bombs objectGeoCoordMove.apply_course_distance(90, dims.damageRadius_m+200); maxLat=math.abs( objectGeoCoordMove.lat() - objectGeoCoord.lat()) ; maxLon=math.abs( objectGeoCoordMove.lon() - objectGeoCoord.lon()) ; #this is a kludge/optimization and obviously relationship between distance & # lat/long changes depending on your location on earth. Particularly it will # malfunction badly near the north/south pole. In those locations we'll # set maxLon to a safe value. # todo:make these values update every 30 seconds or so, to solve that issue # but still allow the optimizations. (Not an issue except near north/south # poles) if (abs(node.getNode("position/latitude-deg").getValue()) > 80 ) maxLon=180; #print ("Bombable: maxLat = ", maxLat, " maxLon = ", maxLon); #put these in nodes also so they can be easily updated by an external #routine or timer props.globals.getNode(""~myNodeName~"/bombable/attributes/dimensions/maxLat",1).setDoubleValue( maxLat); props.globals.getNode(""~myNodeName~"/bombable/attributes/dimensions/maxLon",1).setDoubleValue( maxLon); #Set the listener to check for impact damage. #FG aircraft use a wide variety of nodes to report impact of armament #So we try to check them all. There is no real overhead to this as #only the one(s) active with a particular aircraft will ever get any activity. #This should make all aircraft in the CVS version of FG (as of Aug 2009), #which have armament that reports an impact, work with bombable.nas AI #objects. # impactReporters= [ "ai/models/model-impact", #this is the FG default reporter "sim/armament/weapons/impact", "sim/ai/aircraft/impact/bullet", "sim/ai/aircraft/impact/gun", "sim/ai/aircraft/impact/cannon", "sim/model/bo105/weapons/impact/MG", "sim/model/bo105/weapons/impact/HOT", "sim/ai/aircraft/impact/droptank", "sim/ai/aircraft/impact/bomb" ]; var listenerids=[]; foreach (i; impactReporters) { #print ("i: " , i); listenerid=setlistener(i, func ( changedImpactReporterNode ) { test_impact( changedImpactReporterNode, myNodeName, ); }); append(listenerids, listenerid); } #start the loop to check for fire damage settimer(func{fire_loop(loopid,myNodeName);},5.0 + rand()); print ("Effect *bombable* loaded for ", myNodeName, " loopid=", loopid); #what to do when re-set is selected setlistener("/sim/signals/reinit", func { deleteFire(myNodeName); deleteSmoke("damagedengine", myNodeName); node.getNode("bombable/attributes/damage", 1).setDoubleValue(0); setprop (""~myNodeName~"/bombable/on-ground", 0); if (myNodeName!="") type=props.globals.getNode(myNodeName).getName(); else type=""; if (type=="multiplayer") mp_send_damage(myNodeName, 0); print ("Damage level and smoke reset for ", myNodeName); }); return listenerids; } ##################################################### # Call to make your object stay on the ground, or at a constant # distance above ground level--like a jeep or tank that drives along # the ground, or an aircraft that moves along at, say, 500 ft AGL. # The altitide will be continually readjusted # as the object (set up as, say, and AI ship or aircraft moves. # In addition, for "ships" the pitch will change to (roughly) match # when going up or downhill. # var ground_init = func( myNodeName ) { var b = props.globals.getNode (""~myNodeName~"/bombable/attributes"); alts= b.getNode("altitudes").getValues(); #we increment this each time we are inited or de-inited #when the loopid is changed it kills the timer loops that have that id var loopid=inc_ground_loopid(myNodeName); var node = props.globals.getNode(myNodeName); type=node.getName(); #don't even try to do this to multiplayer aircraft if (type == "multiplayer") return; # Add some useful nodes #get the object's initial altitude var lat = getprop(""~myNodeName~"/position/latitude-deg"); var lon = getprop(""~myNodeName~"/position/longitude-deg"); var alt=elev (lat, lon); #Do some checking for the ground_loop function so we don't always have #to check this in that function #damageAltAdd is the (maximum) amount the object will descend #when it is damaged. settimer(func { ground_loop(loopid, myNodeName); }, 4.0 + rand()); print ("Effect *maintain altitude above ground level* loaded for ", myNodeName); # altitude adjustment=", alts.wheelsOnGroundAGL_ft, " max drop/fall when damaged=", # damageAltAdd, " loopid=", loopid); } ##################################################### # Call to make your object keep its location even after a re-init # (file/reset). For instance a fleet of tanks, cars, or ships # will keep its position after the reset rather than returning # to their initial position.' # # Put this nasal code in your object's load: # bombable.location_init (cmdarg().getPath()) var location_init = func(myNodeName) { #we increment this each time we are inited or de-inited #when the loopid is changed it kills the timer loops that have that id var loopid=inc_location_loopid(myNodeName); var node = props.globals.getNode(myNodeName); type=node.getName(); #don't even try to do this to multiplayer aircraft if (type == "multiplayer") return; settimer(func { location_loop(loopid, myNodeName); }, 15.0 + rand()); print ("Effect *relocate after reset* loaded for ", myNodeName, " loopid=", loopid); } ##################################################### #unload function (delete/destructor) for bombable_init # #typical usage: # #... # #... # # bombable.bombable_del (cmdarg().getPath(), id); # # var bombable_del = func(myNodeName, listenerids) { #we increment this each time we are inited or de-inited #when the loopid is changed it kills the timer loops that have that id var loopid=inc_bomb_loopid(myNodeName); #remove the listener to check for impact damage foreach (id;listenerids) { removelistener(id); } #this loop will be killed when we increment loopid as well #settimer(func { fire_loop(loopid, myNodeName); }, 5.0+rand()); print ("Effect *bombable* unloaded for ", myNodeName, " loopid=", loopid); } ##################################################### # del/destructor function for ground_init # Put this nasal code in your object's unload: # bombable.bombable_del (cmdarg().getPath()); var ground_del = func(myNodeName) { #we increment this each time we are inited or de-inited #when the loopid is changed it kills the timer loops that have that id var loopid=inc_ground_loopid(myNodeName); print ("Effect *drive on ground* unloaded for ", myNodeName, " loopid=", loopid); } ##################################################### # del/destructor for location_init # Put this nasal code in your object's unload: # bombable.location_del (cmdarg().getPath()); var location_del = func(myNodeName) { #we increment this each time we are inited or de-inited #when the loopid is changed it kills the timer loops that have that id var loopid=inc_location_loopid(myNodeName); print ("Effect *relocate after reset* unloaded for ", myNodeName, " loopid=", loopid); } var countmsg=0; var test_msg = func { if (getprop ("/environment/bombable/mp_broadcast_exists")) damage_msg ("flug", 0.01, countmsg/100, 1, 0); countmsg+=1; settimer (test_msg, 5); } ########################################################### #initializers # #Turn fire/smoke on globally for the fire-particles system. #As soon as a fire-particle model is placed it will #start burning. To stop it from burning, simply remove the model. #You can turn off all smoke/fires globally by setting the trigger to false var broadcast = nil; Binary = nil; var seq = 0; var rad2degrees=180/math.pi; var trigger1_pp= "/environment/bombable/fire-particles/"; var trigger2_pp= "-trigger"; var life1_pp= "/environment/bombable/fire-particles/"; var life2_pp= "-life-sec"; var burntime1_pp= "/environment/bombable/fire-particles/"; var burntime2_pp= "-burn-time"; var bomb_pp="/environment/bombable/"; var MP_share_pp = "environment/bombable/share-events"; var MP_broadcast_exists_pp = "/environment/bombable/mp_broadcast_exists"; var screenHProp = nil; var tipArgTarget=nil; var tipArgSelf=nil; var currTimerTarget = 0; var currTimerSelf = 0; _setlistener("/sim/signals/nasal-dir-initialized", func { screenHProp = props.globals.getNode("/sim/startup/ysize"); tipArgTarget = props.Node.new({ "dialog-name" : "PopTipTarget" }); tipArgSelf = props.Node.new({ "dialog-name" : "PopTipSelf" }); var msg_channel_mpp = "sim/multiplay/generic/string[9]"; #multiplayer mode enabled by default props.globals.getNode(MP_share_pp, 1).setBoolValue(1); #make easy mode disabled by default props.globals.getNode(""~bomb_pp~"easy-mode", 1).setIntValue(0); props.globals.getNode(""~bomb_pp~"super-easy-mode", 1).setIntValue(0); #these are for the "mothership" not the AI or MP objects setprop("/bombable/fire-particles/fire-trigger", 0); setprop("environment/bombable/attributes/damage", 0); init_fire_particles_dialog(); #what to do when re-set is selected setlistener("/sim/signals/reinit", func { deleteFire(""); deleteSmoke("damagedengine", ""); setprop("bombable/attributes/damage", 0); setprop ("/bombable/on-ground", 1 ); print ("Damage level & smoke reset for main object"); }); print ("bombable & fire particle effects loaded"); #we save this for last because mp_broadcast doesn't exist for some people, # so runtime error & exit at this point for them. props.globals.getNode(MP_broadcast_exists_pp, 1).setBoolValue(0); Binary = mp_broadcast.Binary; props.globals.getNode(MP_broadcast_exists_pp, 1).setBoolValue(1); broadcast = mp_broadcast.BroadcastChannel.new(msg_channel_mpp, parse_msg, 1); test_msg(); } ); #How to copy a selected value(s) over to an MP situations: #setlistener("/controls/engines/engine/mixture", func(n) { # setprop("/sim/multiplay/generic/float[0]", n.getValue()); # });