Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
8744 lines (6679 sloc) 388 KB
#####################################################
## Bombable
## Brent Hugh, brent@brenthugh.com
var bombableVersion = "4.6";
##
## Copyright (C) 2009 - 2011 Brent Hugh (brent@brenthugh.com)
## This file is licensed under the GPL license version 2 or later.
#
# The Bombable module implements several different but interrelated functions
# that can be used by, for example, AI objects and scenery objects. The
# functions allow shooting and bombing of AI and multiplayer objects, explosions
# and disabling of objects that are hit, and even multiplayer communications to
# allow dogfighting:
#
# 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 according to damage level.
#
# Bombable also works for multiplayer objects and allows multiplayer dogfighting.
#
# In addition, it creates an explosion whenever the main aircraft hits the ground and crashes.
#
# 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
#
# 4. ATTACK: Makes AI Aircraft (and conceivable, other AI objects as well) swarm and attack the main aircraft
#
# 5. WEAPONS: Makes AI Aircraft shoot at the main aircraft
#
#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.
#
# Or two (or more) players can choose aircraft set up for dogfighting (see readme in accompanying documentation) and dogfight via multiplayer. Damage, smoke, fire, and explosions are all transmitted via multiplayer channels.
#
# 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 maneuvers 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!
#
#################################
# prints values to console only if 'debug' flag is set in props
var debprint = func {
setprop ("/sim/startup/terminal-ansi-colors",0);
if (getprop(""~bomb_menu_pp~"debug")) {
outputs="";
foreach (var elem;arg) {
if (elem != nil) {
if (typeof(elem)=="scalar") outputs = string.trim(outputs) ~ " " ~ elem;
else debug.dump(elem);
}
};
outputs= outputs ~ " (Line #";
var call1=caller();
var call2=caller("2");
var call3=caller ("3");
var call4=caller ("4");
if (typeof(call1)=="vector") outputs = outputs ~ call1["3"] ~ " ";
if (typeof(call2)=="vector") outputs = outputs ~ call2["3"] ~ " ";
if (typeof(call3)=="vector") outputs = outputs ~ call3["3"] ~ " ";
if (typeof(call4)=="vector") outputs = outputs ~ call4["3"] ~ " ";
outputs=outputs ~ ")";
print (outputs);
}
}
###############################
# returns round to nearest whole number
var round = func (a ) return int (a+0.5);
###############################
# normalize degree to -180 < angle <= 180
# (for checking aim)
#
var normdeg180 = func(angle) {
while (angle <= - 180)
angle += 360;
while (angle > 180)
angle -= 360;
return angle;
}
###########################################################
# Checks whether nodeName has been overall-initialized yet
# if so, returns 1
# if not, returns 0 and sets nodeName ~ /bombable/overall-initialized to true
#
var check_overall_initialized = func(nodeName) {
nodeName=cmdarg().getPath();
#only allow initialization for ai & multiplayer objects
# in FG 2.4.0 we're having trouble with strange(!?) init requests from
# joysticks & the like
var init_allowed=0;
if (find ("/ai/models/", nodeName ) != -1 ) init_allowed=1;
if (find ("/multiplayer/", nodeName ) != -1 ) init_allowed=1;
if (init_allowed!=1) {
bombable.debprint ("Bombable: Attempt to initialize a Bombable subroutine on an object that is not AI or Multiplayer; aborting initialization. ", nodeName);
return 1; #1 means abort; it's initialized already or can't/shouldn't be initialized,
}
# set to 1 if initialized and 0 when de-inited. Nil if never before inited.
# if it 1 and we're trying to initialize, something has gone wrong and we abort with a message.
var inited= getprop(""~nodeName~"/bombable/overall-initialized");
if (inited==1) {
bombable.debprint ("Bombable: Attempt to re-initialize AI aircraft when it has not been de-initialized; aborting re-initialization. ", nodeName);
return 1; #1 means abort; it's initialized already or can't/shouldn't be initialized,
}
# set to 1 if initialized and 0 when de-inited. Nil if never before inited.
setprop(""~nodeName~"/bombable/overall-initialized", 1);
return 0;
}
var de_overall_initialize = func(nodeName) {
setprop(""~nodeName~"/bombable/overall-initialized", 0);
}
var mpprocesssendqueue = func {
#we do the settimer first so any runtime errors, etc., below, don't stop the
# future instances of the timer from being started
settimer (func {mpprocesssendqueue()}, mpTimeDelaySend ); # This was experimental: mpTimeDelaySend * (97.48 + rand()/20 )
if (!getprop(MP_share_pp)) return "";
if (!getprop (MP_broadcast_exists_pp)) return "";
if (!getprop(bomb_menu_pp~"bombable-enabled") ) return;
if (size(mpsendqueue) > 0) {
setprop (MP_message_pp, mpsendqueue[0]);
mpsendqueue=subvec(mpsendqueue,1);
}
}
var mpsend = func (msg) {
#adding systime to the end of the message ensures that each message is unique--otherwise messages that happen to be the same twice in a row will
# be ignored. The system() at the end is ignored by the parser.
#
if (!getprop(MP_share_pp)) return "";
if (!getprop (MP_broadcast_exists_pp)) return "";
if (!getprop(bomb_menu_pp~"bombable-enabled") ) return;
append(mpsendqueue, msg~systime());
}
var mpreceive = func (mpMessageNode) {
if (!getprop(MP_share_pp)) return "";
if (!getprop (MP_broadcast_exists_pp)) return "";
if (!getprop(bomb_menu_pp~"bombable-enabled") ) return;
msg=mpMessageNode.getValue();
mpMessageNodeName=mpMessageNode.getPath();
mpNodeName=string.replace (mpMessageNodeName, MP_message_pp, "");
if (msg!=nil and msg !="") {
debprint("Bombable: Message received from ", mpNodeName,": ", msg);
parse_msg (mpNodeName, msg);
}
}
################################################################################
#put_ballistic_model places a new model that is starts atanother AI model's
# position but moves independently, like a bullet, bomb, etc
# this is still not working/experimental
# #update: The best way to do this appears to be to include a weapons/tracer
# submodel in the main aircraft. Then have Bombable place
# it in the right location, speed, direction, and trigger it.
# Somewhat similar to: http://wiki.flightgear.org/Howto:_Add_contrails#Persistent_Contrails
var put_ballistic_model = func(myNodeName="/ai/models/aircraft", path="AI/Aircraft/Fire-Particles/fast-particles.xml") {
#debprint ("Bombable: setprop 149");
# "environment" means the main aircraft
#if (myNodeName=="/environment" or myNodeName=="environment") myNodeName="";
fgcommand("add-model", ballisticNode=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",
}));
print (ballisticNode.getName());
print (ballisticNode.getName().getNode("property").getName());
print (props.globals.getNode(ballisticNode.getNode("property").getValue()));
print (ballisticNode.getNode("property").getValue());
return props.globals.getNode(ballisticNode.getNode("property").getValue());
}
################################################################################
#put_remove_model places a new model at the location specified and then removes
# it time_sec later
#it puts out 12 models/sec so normally time_sec=.4 or thereabouts it plenty of time to let it run
# If time_sec is too short then no particles will be emitted. Typical problem is
# many rounds from a gun slow FG's framerate to a crawl just as it is time to emit the
# particles. If time_sec is slower than the frame length then you get zero particle.
# Smallest safe value for time_sec is maybe .3 .4 or .5 seconds.
#
var put_remove_model = func(lat_deg=nil, lon_deg=nil, elev_m=nil, time_sec=nil, startSize_m=nil, endSize_m=1, path="AI/Aircraft/Fire-Particles/flack-impact.xml" ) {
if (lat_deg==nil or lon_deg==nil or elev_m==nil) { return; }
var delay_sec=0.1; #particles/models seem to cause FG crash *sometimes* when appearing within a model
#we try to reduce this by making the smoke appear a fraction of a second later, after
# the a/c model has moved out of the way. (possibly moved, anyway--depending on it's speed)
debprint ("Bombable: Placing flack");
settimer ( func {
#start & end size in particle system appear to be in feet
if (startSize_m!=nil) setprop ("/bombable/fire-particles/flack-startsize", startSize_m);
if (endSize_m!=nil) setprop ("/bombable/fire-particles/flack-endsize", endSize_m);
fgcommand("add-model", flackNode=props.Node.new({
"path": path,
"latitude-deg": lat_deg,
"longitude-deg":lon_deg,
"elevation-ft": elev_m/feet2meters,
"heading-deg" : 0,
"pitch-deg" : 0,
"roll-deg" : 0,
"enable-hot" : 0,
}));
var flackModelNodeName= flackNode.getNode("property").getValue();
#add the -prop property in /models/model[X] for each of lat, long, elev, etc
foreach (name; ["latitude-deg","longitude-deg","elevation-ft", "heading-deg", "pitch-deg", "roll-deg"]){
setprop( flackModelNodeName ~"/"~ name ~ "-prop",flackModelNodeName ~ "/" ~ name );
}
debprint ("Bombable: Placed flack, ", flackModelNodeName);
settimer ( func { props.globals.getNode(flackModelNodeName).remove();}, time_sec);
}, delay_sec);
}
##############################################################
#Start a fire on terrain, size depending on ballisticMass_lb
#location at lat/lon
#
var start_terrain_fire = func ( lat_deg, lon_deg, alt_m=0, ballisticMass_lb=1.2 ) {
var info = geodinfo(lat_deg, lon_deg);
debprint ("Bombable: Starting terrain fire at ", lat_deg, " ", lon_deg, " ", alt_m, " ", ballisticMass_lb);
#get the altitude of the terrain
if (info != nil) {
#debprint ("Bombable: Starting terrain fire at ", lat_deg, " ", lon_deg, " ", info[0]," ", info[1].solid );
#if it's water we don't set a fire . . . TODO make a different explosion or fire effect for water
if (typeof(info[1])=="hash" and contains(info[1], "solid") and info[1].solid==0) return;
else debprint (info);
#we go with explosion point if possible, otherwise the height of terrain at this point
if (alt_m==nil) alt_m=info[0];
if (alt_m==nil) alt_m=0;
}
if (ballisticMass_lb==nil or ballisticMass_lb<0) ballisticMass_lb=1.2;
if (ballisticMass_lb < 3 ) { time_sec=20; fp="AI/Aircraft/Fire-Particles/fire-particles-very-very-small.xml"; }
elsif (ballisticMass_lb < 20 ) { time_sec=60; fp="AI/Aircraft/Fire-Particles/fire-particles-very-very-small.xml"; }
elsif (ballisticMass_lb < 50 ) { time_sec=120; fp="AI/Aircraft/Fire-Particles/fire-particles-very-small.xml"; }
elsif (ballisticMass_lb > 450 ) {time_sec=600; fp="AI/Aircraft/Fire-Particles/fire-particles.xml"; }
elsif (ballisticMass_lb > 1000 ) { time_sec=900; fp="AI/Aircraft/Fire-Particles/fire-particles-large.xml"; }
else {time_sec=300; fp="AI/Aircraft/Fire-Particles/fire-particles-small.xml";}
debprint ({lat_deg:lat_deg, lon_deg:lon_deg, elev_m:alt_m, time_sec:time_sec, startSize_m: nil, endSize_m:nil, path:fp });
put_remove_model(lat_deg:lat_deg, lon_deg:lon_deg, elev_m:alt_m, time_sec:time_sec, startSize_m: nil, endSize_m:nil, path:fp );
#making the fire bigger for bigger bombs
if (ballisticMass_lb >= 1000 ) put_remove_model(lat_deg:lat_deg, lon_deg:lon_deg, elev_m:alt_m+1, time_sec:time_sec*.9, startSize_m: nil, endSize_m:nil, path:fp );
if (ballisticMass_lb >= 1500 ) put_remove_model(lat_deg:lat_deg, lon_deg:lon_deg, elev_m:alt_m+2, time_sec:time_sec*.8, startSize_m: nil, endSize_m:nil, path:fp );
if (ballisticMass_lb >= 2000 ) put_remove_model(lat_deg:lat_deg, lon_deg:lon_deg, elev_m:alt_m+3, time_sec:time_sec*.7, startSize_m: nil, endSize_m:nil, path:fp );
##put it out, but slowly, for large impacts
if (ballisticMass_lb>50) {
time_sec2=120; fp2="AI/Aircraft/Fire-Particles/fire-particles-very-small.xml";
settimer (func { put_remove_model(lat_deg:lat_deg, lon_deg:lon_deg, elev_m:alt_m, time_sec:time_sec2, startSize_m: nil, endSize_m:nil, path:fp2 )} , time_sec);
time_sec3=120; fp3="AI/Aircraft/Fire-Particles/fire-particles-very-very-small.xml";
settimer (func { put_remove_model(lat_deg:lat_deg, lon_deg:lon_deg, elev_m:alt_m, time_sec:time_sec3, startSize_m: nil, endSize_m:nil, path:fp3 )} , time_sec+time_sec2);
time_sec4=120; fp4="AI/Aircraft/Fire-Particles/fire-particles-very-very-very-small.xml";
settimer (func { put_remove_model(lat_deg:lat_deg, lon_deg:lon_deg, elev_m:alt_m, time_sec:time_sec4, startSize_m: nil, endSize_m:nil, path:fp4 )} , time_sec+time_sec2+time_sec3);
}
}
################################################################################
#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 ") {
#debprint ("Bombable: setprop 174");
# "environment" means the main aircraft
#if (myNodeName=="/environment" or myNodeName=="environment") myNodeName="";
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());
}
################################################################################
#put_tied_weapon places a new model that is tied to another AI model
# (given by myNodeName) and will move with it in lon, lat, & alt
# and have the delta heading, pitch, lat, long, alt, as specified in weapons_init
#
var put_tied_weapon = func(myNodeName="", elem="", startSize_m=.07, endSize_m=.05, path="AI/Aircraft/Fire-Particles/Fire-Particles.xml ") {
#debprint ("Bombable: setprop 174");
# "environment" means the main aircraft
#if (myNodeName=="/environment" or myNodeName=="environment") myNodeName="";
if (startSize_m!=nil) setprop ("/bombable/fire-particles/projectile-startsize", startSize_m);
if (endSize_m!=nil) setprop ("/bombable/fire-particles/projectile-endsize", endSize_m);
fgcommand("add-model", fireNode=props.Node.new({
"path": path,
"latitude-deg-prop": myNodeName ~ "/" ~ elem ~ "/position/latitude-deg",
"longitude-deg-prop":myNodeName ~ "/" ~ elem ~ "/position/longitude-deg",
"elevation-ft-prop": myNodeName ~ "/" ~ elem ~ "/position/altitude-ft",
"heading-deg-prop": myNodeName ~ "/" ~ elem ~ "/orientation/true-heading-deg",
"pitch-deg-prop": myNodeName ~ "/" ~ elem ~ "/orientation/pitch-deg",
"roll-deg-prop": myNodeName ~ "/" ~ elem ~"/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 (myNodeName=="") myNodeName="/environment";
if (fireNode=="") {
fireNodeName=getprop(""~myNodeName~"/bombable/fire-particles/fire-particles-model");
if (fireNodeName==nil) return;
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-burning", 0);
setprop(""~myNodeName~"/bombable/fire-particles/fire-particles-model", "");
}
####################################################
#Check current speed & add damage due to excessive speed
#
var speedDamage = func {
if (!getprop(bomb_menu_pp~"bombable-enabled") ) return;
var damage_enabled=getprop (""~GF_damage_menu_pp~"damage_enabled");
var warning_enabled=getprop (""~GF_damage_menu_pp~"warning_enabled");
if (! damage_enabled and ! warning_enabled ) return;
var currSpeed_kt=getprop("/velocities/airspeed-kt");
if (currSpeed_kt==0 or currSpeed_kt == nil) return;
var speedDamageThreshold_kt = getprop(""~vulnerabilities_pp~"airspeed_damage/damage_threshold_kt/");
var speedWarningThreshold_kt = getprop(""~vulnerabilities_pp~"airspeed_damage/warning_threshold_kt/");
if (speedDamageThreshold_kt==0 or speedDamageThreshold_kt==nil) speedDamageThreshold_kt=7000;
if (speedWarningThreshold_kt==0 or speedWarningThreshold_kt==nil) speedWarningThreshold_kt=7000;
var speedDamageMultiplier_PercentPerSecond = getprop(""~vulnerabilities_pp~"airspeed_damage/damage_multiplier_percentpersecond/");
if (speedDamageMultiplier_PercentPerSecond==nil) speedDamageMultiplier_PercentPerSecond=1;
#debprint ("Bombable: Speed checking ", currSpeed_kt, " ", speedDamageThreshold_kt, " ", speedWarningThreshold_kt," ", speedDamageMultiplier_PercentPerSecond);
if (warning_enabled and currSpeed_kt > speedWarningThreshold_kt ) {
var msg="Overspeed warning: "~ round ( currSpeed_kt ) ~" kts";
debprint(msg);
#only put the message on the screen if damage is less than 100%
# after that there is little point AND it will overwrite
# any "you're out of commission" message
if ( getprop("/bombable/attributes/damage") <1)
selfStatusPopupTip (msg, 5 );
}
if (damage_enabled and currSpeed_kt > speedDamageThreshold_kt ) {
mainAC_add_damage( speedDamageMultiplier_PercentPerSecond * (currSpeed_kt -speedDamageThreshold_kt)/100,
0, "speed", "" ~ round( currSpeed_kt ) ~ " kts (overspeed) damaged airframe!" );
}
}
####################################################
#Check current accelerations & add damage due to excessive acceleration
#
var accelerationDamage = func {
if (!getprop(bomb_menu_pp~"bombable-enabled") ) return;
var damage_enabled=getprop (""~GF_damage_menu_pp~"damage_enabled");
var warning_enabled=getprop (""~GF_damage_menu_pp~"warning_enabled");
if (! damage_enabled and ! warning_enabled ) return;
if (!getprop(bomb_menu_pp~"bombable-enabled") ) return;
#debprint ("Bombable: Checking acceleration");
#The acceleration nodes are updated once per second
var currAccel_g=getprop("/accelerations/pilot-gdamped");
if (currAccel_g==0 or currAccel_g == nil) return;
if (currAccel_g>0 ) a="positive";
else a="negative";
currAccel_fg=math.abs(currAccel_g);
var accelDamageThreshold_g = getprop(""~GF_damage_pp~"damage_threshold_g/"~a);
var accelWarningThreshold_g = getprop(""~GF_damage_pp~"warning_threshold_g/"~a);
if (accelDamageThreshold_g==0 or accelDamageThreshold_g==nil) accelDamageThreshold_g=50;
if (accelWarningThreshold_g==0 or accelWarningThreshold_g==nil) accelWarningThreshold_g=10;
var accelDamageMultiplier_PercentPerSecond = getprop(""~GF_damage_pp~"damage_multiplier_percentpersecond/"~a);
if (accelDamageMultiplier_PercentPerSecond==nil) accelDamageMultiplier_PercentPerSecond=8;
# debprint ("Bombable: Accel checking ", a, " ", currAccel_g, " ", accelDamageThreshold_g, " ", accelWarningThreshold_g," ", accelDamageMultiplier_PercentPerSecond);
if (warning_enabled and currAccel_g > accelWarningThreshold_g ) {
var msg="G-force warning: "~ round( currAccel_g ) ~"g";
debprint(msg);
#only put the message on the screen if damage is less than 100%
# after that there is little point AND it will overwrite
# any "you're out of commission" message
if ( getprop("/bombable/attributes/damage") <1)
selfStatusPopupTip (msg, 5 );
}
if (damage_enabled and currAccel_g > accelDamageThreshold_g ) {
mainAC_add_damage( accelDamageMultiplier_PercentPerSecond * (currAccel_g -accelDamageThreshold_g)/100,
0, "gforce", "" ~ sprintf("%1.2f", currAccel_g ) ~ "g force damaged airframe!" );
}
}
#########################################################################
# timer for accel & speed damage checks
#
var damageCheck = func () {
settimer (func {damageCheck (); }, damageCheckTime);
if (!getprop(bomb_menu_pp~"bombable-enabled") ) return;
#debprint ("Bombable: Checking damage.");
accelerationDamage();
speedDamage();
}
#Notes on g-force:
# Max G tolerated by a person for periods of c. 1 sec or more
# is about 30-50g even in fairly ideal circumstances. So even if the aircraft
# survives your 30+g maneuver--you won't!
# F22 has max g of 9.5 and Su-47 has max g of 9, so those numbers
# are probably pretty typical for modern fighter jets.
# See http://www.thespacereview.com/article/410/1
# http://www.airforce-technology.com/projects/s37/
# http://www.airforce-technology.com/projects/s37/
# Max G tolerated routinely by fighter or acrobatic
# pilots etc seems to be about 9-12g
# 12-17g is tolerated long term, depending on the direction of the
# acceleration See http://en.wikipedia.org/wiki/G-force
# #max g for WWI era aircraft was 4-5g (best guess). Repeat high gs do
# weaken the structure.
# In modern aircraft, F-16 has a maximum G of 9, F-18: 9.6, Mirage M.III/V: 7, A-4:6.
# http://www.ww2aircraft.net/forum/technical/strongest-aircraft-7494-3.html :
# WW2 aircraft sometimes has higher max G, but it is interesting because pilot did not have G-suit, and trained pilots could not resist 5g for more than some seconds without G-suit.
# the strongest aircraft of WWII were the Italian monoplane fighters. They were built to withstand 8g normal load with 12g failure load. The same spec for German aircraft was 6g - 8.33g. For the late war P51s it was 5.33g
# Spitfire VIII can pull about 9 and dive to about 570 mph before ripping apart while the F4U will only dive to about 560 mph and pull a similar load.
# at normal weight the designed limit load was 7.5 g positive and 3.5 g negative for the Corsair.
# FIAT G.50 had an ultimate factor of 14 g. According to Dottore Eng. Gianni Cattaneo´s Profile booklet on the Macchi C.202, it had an ultimate factor of no less than 15.8 g! That would make it virtually indestructible. Also the Hawker Tempest was strong with its 14+ G strength.
# http://www.aviastar.org/air/japan/mitsubishi_a6m.php :
# Most Japanese fighters were designed to withstand a force of 7g. From 1932 all Japanese warplanes were required to meet a safety load factor of 1.8 so the limit for the A6M had to be 12.6g (1.8x7g).
#
# One flaw with FG's blackout/redout is that it instantaneous rather than
# delayed. IE, even a WW1 pilot could probably withstand a short period of
# 8-10 Gs and blackout would happen gradually rather than instantly.
########################################################
#Set attributes for main aircraft
# You can set vulnerabilities for any aircraft by
# simply creating a file 'vulnerabilities.nas',
# defining vulsObject as below, and including the line
# bombable.setAttributes (attsObject);
#
var setAttributes = func (attsObject=nil) {
debprint ("Bombable: Loading main aircraft vulnerability settings.");
if (attsObject==nil) {
attsObject = {
# TODO: Update all below to be actual dimensions of that aircraft
#########################################
# DIMENSION DEFINITIONS
#
# All dimensions are in meters
# source: http://en.wikipedia.org/wiki/Fairchild_Republic_A-10_Thunderbolt_II
#
dimensions : {
width_m : 17.53, #width of your object, ie, for aircraft, wingspan
length_m : 16.26, #length of your object, ie, for aircraft, distance nose to tail
height_m : 4.47, #height of your object, ie, for aircraft ground to highest point when sitting on runway
damageRadius_m : 8, #typically 1/2 the longest dimension of the object. Hits within this distance of the
#center of object have some possibility of damage
vitalDamageRadius_m : 2, #typically the radius of the fuselage or cockpit or other most
# vital area at the center of the object. Always smaller than damageRadius_m
crashRadius_m : 6, #It's a crash if the main aircraft hits in this area.
},
vulnerabilities: {
engineDamageVulnerability_percent : 3,
fireVulnerability_percent:15,
damageVulnerability:20,
fireExtinguishSuccess_percentage:10,
fireExtinguishMaxTime_seconds:100,
fireDamageRate_percentpersecond : .4,
explosiveMass_kg : 20000,
airspeed_damage:{
#If we don't know the aircraft it could be anything, even the UFO.
damage_threshold_kt: 200000,
warning_threshold_kt: 8500,
#1kt over the threshold for 1 second will add 1% damage:
damage_multiplier_percentpersecond: 0.07
},
gforce_damage: {
damage_enabled: 0,
warning_enabled: 1,
damage_threshold_g: {positive:30, negative:20},
warning_threshold_g: {positive:12, negative:7},
#1g over the threshold for 1 second will add 8% damage:
damage_multiplier_percentpersecond: {positive:8, negative:8 }
},
redout: {
enabled: 0,
parameters: {
blackout_onset_g: 4,
blackout_complete_g: 7,
redout_onset_g: -2,
redout_complete_g: -5
}
}
}
}
};
#predefined values for a few aircraft we have set up for
# dogfighting
var aircraftname=getprop("sim/aircraft");
if (string.match(aircraftname,"A6M2*" )){
debprint ("Bombable: Loading A6M2 main aircraft vulnerabilities");
attsObject = {
#########################################
# DIMENSION DEFINITIONS
#
# All dimensions are in meters
# source: http://en.wikipedia.org/wiki/Fairchild_Republic_A-10_Thunderbolt_II
#
dimensions : {
width_m : 17.53, #width of your object, ie, for aircraft, wingspan
length_m : 16.26, #length of your object, ie, for aircraft, distance nose to tail
height_m : 4.47, #height of your object, ie, for aircraft ground to highest point when sitting on runway
damageRadius_m : 8, #typically 1/2 the longest dimension of the object. Hits within this distance of the
#center of object have some possibility of damage
vitalDamageRadius_m : 2, #typically the radius of the fuselage or cockpit or other most
# vital area at the center of the object. Always smaller than damageRadius_m
crashRadius_m : 6, #It's a crash if the main aircraft hits in this area.
},
vulnerabilities: {
engineDamageVulnerability_percent : 6,
fireVulnerability_percent:34,
fireDamageRate_percentpersecond : .4,
damageVulnerability:90,
fireExtinguishSuccess_percentage:50,
fireExtinguishMaxTime_seconds:50,
explosiveMass_kg : 27772,
airspeed_damage:{
damage_threshold_kt: 356, #http://en.wikipedia.org/wiki/A6M_Zero
warning_threshold_kt: 325,
#1 kt over the threshold for 1 second will add 1% damage:
damage_multiplier_percentpersecond: 0.07
},
gforce_damage: {
damage_enabled: 1, #boolean yes/no
warning_enabled: 1, #boolean yes/no
damage_threshold_g: {positive:12.6, negative:9},
warning_threshold_g: {positive:7, negative:6},
damage_multiplier_percentpersecond: {positive:12, negative:12 }
},
redout: {
enabled: 1,
parameters: {
blackout_onset_g: 5, #no g-suit in WWI so really 6gs is pushing it
blackout_complete_g: 9,
redout_onset_g: -2.5,
redout_complete_g: -5
}
}
}
}
} elsif ( string.match(aircraftname,"A-10*" ) ) {
debprint ("Bombable: Loading A-10 main aircraft vulnerabilities");
attsObject = {
#########################################
# DIMENSION DEFINITIONS
#
# All dimensions are in meters
# source: http://en.wikipedia.org/wiki/Fairchild_Republic_A-10_Thunderbolt_II
#
dimensions : {
width_m : 17.53, #width of your object, ie, for aircraft, wingspan
length_m : 16.26, #length of your object, ie, for aircraft, distance nose to tail
height_m : 4.47, #height of your object, ie, for aircraft ground to highest point when sitting on runway
damageRadius_m : 8, #typically 1/2 the longest dimension of the object. Hits within this distance of the
#center of object have some possibility of damage
vitalDamageRadius_m : 2, #typically the radius of the fuselage or cockpit or other most
# vital area at the center of the object. Always smaller than damageRadius_m
crashRadius_m : 6, #It's a crash if the main aircraft hits in this area.
},
vulnerabilities: {
engineDamageVulnerability_percent : 6,
fireVulnerability_percent:7,
fireDamageRate_percentpersecond : .1,
damageVulnerability:6,
fireExtinguishSuccess_percentage:65,
fireExtinguishMaxTime_seconds:80,
explosiveMass_kg : 27772,
airspeed_damage:{
damage_threshold_kt: 480, # Never exceed speed, http://en.wikipedia.org/wiki/Fairchild_Republic_A-10_Thunderbolt_II
warning_threshold_kt: 450,
#1 kt over the threshold for 1 second will add 1% damage:
damage_multiplier_percentpersecond: 0.5
},
gforce_damage: {
damage_enabled: 1,
warning_enabled: 1,
damage_threshold_g: {positive:9, negative:9},
warning_threshold_g: {positive:8, negative:8},
damage_multiplier_percentpersecond: {positive:3, negative:3 } # higher = weaker aircraft
},
redout: {
enabled: 1,
parameters: {
blackout_onset_g: 7, #g-suit allows up to 9Gs, http://en.wikipedia.org/wiki/G-LOC
blackout_complete_g: 12, #or even 10-12. Maybe. http://forum.acewings.com/pop_printer_friendly.asp?ARCHIVE=true&TOPIC_ID=3588
redout_onset_g: -2, #however, g-suit doesn't help with red-out. Source: http://en.wikipedia.org/wiki/Greyout_(medical)
redout_complete_g: -5
}
}
}
}
} elsif ( string.match(aircraftname,"f6f*" ) ) {
debprint ("Bombable: Loading F6F Hellcat main aircraft vulnerabilities");
attsObject = {
#########################################
# DIMENSION DEFINITIONS
#
# All dimensions are in meters
# source: http://en.wikipedia.org/wiki/Fairchild_Republic_A-10_Thunderbolt_II
#
dimensions : {
width_m : 17.53, #width of your object, ie, for aircraft, wingspan
length_m : 16.26, #length of your object, ie, for aircraft, distance nose to tail
height_m : 4.47, #height of your object, ie, for aircraft ground to highest point when sitting on runway
damageRadius_m : 8, #typically 1/2 the longest dimension of the object. Hits within this distance of the
#center of object have some possibility of damage
vitalDamageRadius_m : 2, #typically the radius of the fuselage or cockpit or other most
# vital area at the center of the object. Always smaller than damageRadius_m
crashRadius_m : 6, #It's a crash if the main aircraft hits in this area.
},
vulnerabilities: {
engineDamageVulnerability_percent : 7,
fireVulnerability_percent:15,
fireDamageRate_percentpersecond : .5,
damageVulnerability:3.5,
fireExtinguishSuccess_percentage:23,
fireExtinguishMaxTime_seconds:30,
explosiveMass_kg : 735,
airspeed_damage:{
damage_threshold_kt: 450, #VNE, http://forums.ubi.com/eve/forums/a/tpc/f/23110283/m/46710245
warning_threshold_kt: 420,
#1 kt over the threshold for 1 second will add 1% damage:
damage_multiplier_percentpersecond: 0.5
},
gforce_damage: {
#data: http://www.amazon.com/Grumman-Hellcat-Pilots-Operating-Instructions/dp/1935327291/ref=sr_1_1?s=books&ie=UTF8&qid=1319249394&sr=1-1
#see particularly p. 59
#accel 'never exceed' limits are +7 and -3 Gs in all situations, and less in some situations
damage_enabled: 1, #boolean yes/no
warning_enabled: 1, #boolean yes/no
damage_threshold_g: {positive:15.6, negative:10}, # it's somewhat stronger built than the A6M2
warning_threshold_g: {positive:12, negative:8},
damage_multiplier_percentpersecond: {positive:12, negative:12 }
},
redout: {
enabled: 1,
parameters: {
blackout_onset_g: 5, #no g-suit in WWI so really 6gs is pushing it
blackout_complete_g: 9,
redout_onset_g: -2.5,
redout_complete_g: -5
}
}
}
}
} elsif ( string.match(aircraftname,"*sopwithCamel*" ) ) {
debprint ("Bombable: Loading SopwithCamel main aircraft vulnerabilities");
attsObject = {
#########################################
# DIMENSION DEFINITIONS
#
# All dimensions are in meters
# source: http://en.wikipedia.org/wiki/Fairchild_Republic_A-10_Thunderbolt_II
#
dimensions : {
width_m : 17.53, #width of your object, ie, for aircraft, wingspan
length_m : 16.26, #length of your object, ie, for aircraft, distance nose to tail
height_m : 4.47, #height of your object, ie, for aircraft ground to highest point when sitting on runway
damageRadius_m : 8, #typically 1/2 the longest dimension of the object. Hits within this distance of the
#center of object have some possibility of damage
vitalDamageRadius_m : 2, #typically the radius of the fuselage or cockpit or other most
# vital area at the center of the object. Always smaller than damageRadius_m
crashRadius_m : 6, #It's a crash if the main aircraft hits in this area.
},
vulnerabilities: {
engineDamageVulnerability_percent : 7,
fireVulnerability_percent:15,
fireDamageRate_percentpersecond : .5,
damageVulnerability:3.5,
fireExtinguishSuccess_percentage:23,
fireExtinguishMaxTime_seconds:30,
explosiveMass_kg : 735,
airspeed_damage:{
damage_threshold_kt: 240, #max speed, level flight is 100 kt, so this is a guess
warning_threshold_kt: 210,
#1 kt over the threshold for 1 second will add 1% damage:
damage_multiplier_percentpersecond: 0.5
},
gforce_damage: {
damage_enabled: 1,
warning_enabled: 1,
damage_threshold_g: {positive:4, negative:3},
warning_threshold_g: {positive:3, negative:2.5},
damage_multiplier_percentpersecond: {positive:12, negative:12 }
},
redout: {
enabled: 1,
parameters: {
blackout_onset_g: 3,
blackout_complete_g: 7,
redout_onset_g: -2,
redout_complete_g: -5
}
}
}
}
} elsif ( string.match(aircraftname, "*spadvii*" ) ) {
debprint ("Bombable: Loading SPAD VII main aircraft vulnerabilities");
attsObject = {
#########################################
# DIMENSION DEFINITIONS
#
# All dimensions are in meters
# source: http://en.wikipedia.org/wiki/Fairchild_Republic_A-10_Thunderbolt_II
#
dimensions : {
width_m : 17.53, #width of your object, ie, for aircraft, wingspan
length_m : 16.26, #length of your object, ie, for aircraft, distance nose to tail
height_m : 4.47, #height of your object, ie, for aircraft ground to highest point when sitting on runway
damageRadius_m : 8, #typically 1/2 the longest dimension of the object. Hits within this distance of the
#center of object have some possibility of damage
vitalDamageRadius_m : 2, #typically the radius of the fuselage or cockpit or other most
# vital area at the center of the object. Always smaller than damageRadius_m
crashRadius_m : 6, #It's a crash if the main aircraft hits in this area.
},
vulnerabilities: {
engineDamageVulnerability_percent : 3,
fireVulnerability_percent:20,
fireDamageRate_percentpersecond : .2,
damageVulnerability:4,
fireExtinguishSuccess_percentage:10,
fireExtinguishMaxTime_seconds:100,
explosiveMass_kg : 735,
airspeed_damage:{
#The Spads and SE5's were quoted to dive "well in excess of 200mph" (I'can try to dig up the reference if you like) The Alb's and Nieuports were notorious for shedding lower wings in anything other than a normal dive. http://www.theaerodrome.com/forum/2000/8286-maximum-dive-speed.html
damage_threshold_kt: 290, #max speed, level flight is 103 kt, so this is a guess based on that plus Spad's rep as able to hold together in "swift dives" better than most
warning_threshold_kt: 260,
#1 kt over the threshold for 1 second will add 1% damage:
damage_multiplier_percentpersecond: 0.4
},
gforce_damage: {
damage_enabled: 1,
warning_enabled: 1,
#"swift dive" capability must mean it is a bit more structurally
# sound than camel/DR1
damage_threshold_g: {positive:4.5, negative:3},
warning_threshold_g: {positive:3, negative:2.5},
damage_multiplier_percentpersecond: {positive:9, negative:9 }
},
redout: {
enabled: 1,
parameters: {
blackout_onset_g: 3,
blackout_complete_g: 7,
redout_onset_g: -2,
redout_complete_g:-5
}
}
}
}
} elsif ( string.match(aircraftname,"*fkdr*" ) ) {
debprint ("Bombable: Loading Fokker DR.1 main aircraft vulnerabilities");
attsObject = {
#########################################
# DIMENSION DEFINITIONS
#
# All dimensions are in meters
# source: http://en.wikipedia.org/wiki/Fairchild_Republic_A-10_Thunderbolt_II
#
dimensions : {
width_m : 17.53, #width of your object, ie, for aircraft, wingspan
length_m : 16.26, #length of your object, ie, for aircraft, distance nose to tail
height_m : 4.47, #height of your object, ie, for aircraft ground to highest point when sitting on runway
damageRadius_m : 8, #typically 1/2 the longest dimension of the object. Hits within this distance of the
#center of object have some possibility of damage
vitalDamageRadius_m : 2, #typically the radius of the fuselage or cockpit or other most
# vital area at the center of the object. Always smaller than damageRadius_m
crashRadius_m : 6, #It's a crash if the main aircraft hits in this area.
},
vulnerabilities: {
engineDamageVulnerability_percent : 3,
fireVulnerability_percent:20,
fireDamageRate_percentpersecond : .2,
damageVulnerability:4,
fireExtinguishSuccess_percentage:10,
fireExtinguishMaxTime_seconds:100,
explosiveMass_kg : 735,
airspeed_damage:{
damage_threshold_kt: 170, #max speed, level flight is 100 kt, so this is a guess based on that plus the DR1's reputation for wing damage at high speeds
warning_threshold_kt: 155,
#1 kt over the threshold for 1 second will add 1% damage:
damage_multiplier_percentpersecond: 0.8
},
gforce_damage: {
damage_enabled: 1,
warning_enabled: 1,
#wing breakage problems indicate weaker construction
# than SPAD VII, Sopwith Camel
damage_threshold_g: {positive:3.8, negative:2.8},
warning_threshold_g: {positive:3, negative:2.2},
damage_multiplier_percentpersecond: {positive:14, negative:14 }
},
redout: {
enabled: 1,
parameters: {
blackout_onset_g: 3,
blackout_complete_g: 7,
redout_onset_g: -2,
redout_complete_g:-5
}
}
}
}
}
props.globals.getNode(""~attributes_pp, 1).setValues(attsObject);
attributes[""]= attsObject;
# We setmaxlatlon here so that it is re-done on reinit--otherwise we
#get errors about maxlat being nil
settimer (func { setMaxLatLon("", 500);}, 6.2398471);
#put the redout properties in place, too; wait a couple of
# seconds so we aren't overwritten by the redout.nas subroutine:
settimer ( func {
props.globals.getNode("/sim/rendering/redout/enabled", 1).setValue(attsObject.vulnerabilities.redout.enabled);
props.globals.getNode("/sim/rendering/redout/parameters/blackout-onset-g", 1).setValue(attsObject.vulnerabilities.redout.parameters.blackout_onset_g);
props.globals.getNode("/sim/rendering/redout/parameters/blackout-complete-g", 1).setValue(attsObject.vulnerabilities.redout.parameters.blackout_complete_g);
props.globals.getNode("/sim/rendering/redout/parameters/redout-onset-g", 1).setValue(attsObject.vulnerabilities.redout.parameters.redout_onset_g);
props.globals.getNode("/sim/rendering/redout/parameters/redout-complete-g", 1).setValue(attsObject.vulnerabilities.redout.parameters.redout_complete_g);
}, 3);
#reset the vulnerabilities for the main object whenever FG
# reinits.
# Important especially for setting redout/blackout, which otherwise
# reverts to FG's defaults on reset.
# We need to do it here so that if some outside aircraft
# calls setVulnerabilities with its own attsObject
# we will be able to use that here & reinit with that attsObject
#
attsSet=getprop (""~attributes_pp~"/attributes-set");
if (attsSet==nil) attsSet=0;
if (attsSet==0) { setlistener("/sim/signals/reinit", func {
setAttributes(attsObject)} );
#also set the default gforce/speed damage/warning enabled/disabled
# but only on initial startup, not on reset
if (getprop (GF_damage_menu_pp ~"/damage_enabled")==nil)
props.globals.getNode(GF_damage_menu_pp ~"/damage_enabled", 1).setValue(attsObject.vulnerabilities.gforce_damage.damage_enabled);
if (getprop (GF_damage_menu_pp ~"/warning_enabled")==nil)
props.globals.getNode(GF_damage_menu_pp ~"/warning_enabled", 1).setValue(attsObject.vulnerabilities.gforce_damage.warning_enabled);
}
props.globals.getNode(""~attributes_pp~"/attributes-set", 1).setValue(1);
}
####################################################
#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 (myNodeName=="") myNodeName="/environment";
#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 != "")) {
setprop(""~myNodeName~"/bombable/fire-particles/fire-burning", 1);
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*feet2meters);
#print ("started fire! ", myNodeName);
#turn off the fire after user-set amount of time (default 1800 seconds)
var burnTime=getprop ("/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-burning", 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.
#you can also use cmdarg().getPath() to get the full path from the node
}
####################################################
#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 (myNodeName=="") myNodeName="/environment";
if (fireNode=="") {
fireNodeName=getprop(""~myNodeName~"/bombable/fire-particles/"~smokeType~"-particles-model");
if (fireNodeName==nil) return;
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~"-burning", 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 burning flag 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 (myNodeName=="") myNodeName="";
#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*feet2meters);
#debprint ("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~"-burning", 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.
}
####################################################
#reset damage & fires for main object
#
#
var reset_damage_fires = func {
deleteFire("");
deleteSmoke("damagedengine", "");
setprop("/bombable/attributes/damage", 0);
setprop ("/bombable/on-ground", 0 );
#blow away the locks for MP communication--shouldn't really
# be needed--but just a little belt & suspendors things here
# to make sure that no old damage (prior to the reset) is sent
# to other aircraft again after the reset, and that none of the
# locks are stuck.
props.globals.getNode("/bombable").removeChild("locks",0);
var msg_add="";
var msg=reset_msg();
if (msg != "" and getprop(MP_share_pp) and getprop (MP_broadcast_exists_pp) ) {
debprint ("Bombable RESET: MP sending: "~msg);
mpsend(msg);
msg_add=" and broadcast via multi-player";
}
debprint ("Bombable: Damage level & smoke reset for main object"~msg_add);
var msg= "Your damage reset to 0%";
selfStatusPopupTip (msg, 30);
}
####################################################
# resetAllAIDamage
# reset the damage, smoke & fires from all AI object with bombable operative
# TODO if an aircraft is crashing, it stays crashing despite this.
#
var revitalizeAllAIObjects = func (revitType="aircraft", preservePosSpeed=0) {
ai = props.globals.getNode ("/ai/models").getChildren();
#var m_per_deg_lat=getprop ("/bombable/sharedconstants/m_per_deg_lat");
#var m_per_deg_lon=getprop ("/bombable/sharedconstants/m_per_deg_lon");
# This will put the AI objects on a circle with 5000 meters radius
# from the main a/c, at angle relocAngle_deg from the main a/c
var relocAngle_deg=rand()*360;
var latPlusMinus=math.sin (relocAngle_deg /rad2degrees) * (5000)/m_per_deg_lat;
var lonPlusMinus=math.cos (relocAngle_deg /rad2degrees) * (5000)/m_per_deg_lat;
var min_dist_km=getprop(bomb_menu_pp~"dispersal-dist-min_km");
if (typeof(min_dist_km)=="nil" or min_dist_km=="" or min_dist_km==0) min_dist_km=1;
var max_dist_km=getprop(bomb_menu_pp~"dispersal-dist-max_km");
if (typeof(max_dist_km)=="nil" or max_dist_km=="" or max_dist_km==0 or max_dist_km<min_dist_km) max_dist_km=16;
setprop(bomb_menu_pp~"dispersal-dist-min_km",min_dist_km);
setprop(bomb_menu_pp~"dispersal-dist-max_km",max_dist_km);
var min_dist_m=1000*min_dist_km;
var max_dist_m=1000*max_dist_km;
#var latPlusMinus=1; if (rand()>.5) latPlusMinus=-1;
#var lonPlusMinus=1; if (rand()>.5) lonPlusMinus=-1;
var referenceLat=0;
var referenceLon=0;
var heading_deg = rand() * 360; #it's helpful to have them all going in the same
#direction, in case AI piloting is turned off (they stay together rather than dispersing)
var waitTime_sec=0;
var numRespawned=0;
foreach (elem;ai) {
#only do this for the type named in the function call
type=elem.getName();
if (type != revitType) continue;
aiName=type ~ "[" ~ elem.getIndex() ~ "]";
#Disperse within a given radius
if (preservePosSpeed==2) {
# This will put the AI objects within a circle with 15000 meters radius
# from the main a/c
var dist = math.sqrt(rand())*(max_dist_m - min_dist_m) + min_dist_m;
var relocAngle_deg=rand()*360;
var latPlusMinus=math.sin (relocAngle_deg /rad2degrees) * (dist)/m_per_deg_lat;
var lonPlusMinus=math.cos (relocAngle_deg /rad2degrees) * (dist)/m_per_deg_lat;
var heading_deg = rand() * 360;
}
#only if bombable initialized
#experimental: disable the next line to do this for ALL aircraft/objects regardless of bombable status.
#OK, scenarios are only initialized (and the bombable routines started) if they are within a certain distance of the main a/c. So we DO need to remark
#out the following line--otherwise distant scenarios are not respawned near
#to the main a/c because they have not had bombable initialized yet
#if (props.globals.getNode ( "/ai/models/"~aiName~"/bombable" ) == nil) continue;
numRespawned+=1;
#reset damage, smoke, fires for all objects that have bombable initialized
#even does it for multiplayer objects, which is not completely proper (the MP bombable
#keeps their 'real' damage total remotely), but might help in case of MP malfunction of some sort, and doesn't hurt in the meanwhile
resetBombableDamageFuelWeapons ("/ai/models/" ~ aiName);
setprop ("ai/models/"~aiName~"/controls/flight/target-pitch", 0);
setprop ("ai/models/"~aiName~"/controls/flight/target-roll", 0);
setprop ("ai/models/"~aiName~"/orientation/roll-deg", 0);
#settimer & increased waittime helps avoid segfault that seems to happen
#to FG too often when many models appear all at once
#settimer ( func {
newlat_deg = getprop ("/position/latitude-deg") + latPlusMinus;
newlon_deg = getprop ("/position/longitude-deg") + lonPlusMinus;
if (preservePosSpeed==1){
var currLat = getprop ("ai/models/"~aiName~"/position/latitude-deg");
var currLon = getprop ("ai/models/"~aiName~"/position/longitude-deg");
var old_elev_ft = elev (currLat,currLon);
if (referenceLat==0 and referenceLon==0) {
referenceLat=currLat;
referenceLon=currLon;
}
newlat_deg = newlat_deg + currLat-referenceLat ;
newlon_deg = newlon_deg + currLon-referenceLon;
} else {
newlat_deg = newlat_deg + (rand() - .5)*500/m_per_deg_lat ;
newlon_deg = newlon_deg + (rand() - .5)*500/m_per_deg_lon;
}
setprop ("ai/models/"~aiName~"/position/latitude-deg", newlat_deg );
setprop ("ai/models/"~aiName~"/position/longitude-deg", newlon_deg );
var elev_ft = elev (newlat_deg,newlon_deg);
var currAlt_ft = getprop ("ai/models/"~aiName~"/position/altitude-ft");
if (type=="aircraft") {
if (preservePosSpeed==1) {
alt_ft=currAlt_ft-old_elev_ft + elev_ft;
if (alt_ft-500<elev_ft) alt_ft=elev_ft+500;
} else if (preservePosSpeed==2) {
var min_alt_ft=elev_ft+500;
var main_alt_ft=getprop ("/position/altitude-ft");
var max_alt_ft=main_alt_ft*2;
if (max_alt_ft < 2*min_alt_ft) max_alt_ft = 2*min_alt_ft;
if (max_alt_ft < 10000) max_alt_ft=16000;
if (max_alt_ft > 45000) max_alt_ft=45000;
alt_ft= rand()* (max_alt_ft-min_alt_ft) + min_alt_ft;
} else {
alt_ft=getprop ("/position/altitude-ft")+100;
if (alt_ft-500<elev_ft) alt_ft=elev_ft+500;
}
} else {
alt_ft= elev_ft;
}
setprop ("ai/models/"~aiName~"/position/altitude-ft", alt_ft);
setprop ("ai/models/"~aiName~"/controls/flight/target-alt", alt_ft);
if (preservePosSpeed==0 or preservePosSpeed==2) {
setprop ("ai/models/"~aiName~"/controls/flight/target-hdg", heading_deg);
setprop ("ai/models/"~aiName~"/orientation/true-heading-deg", heading_deg);
}
#setting these stops the relocate function from relocating them back
setprop("ai/models/"~aiName~"/position/previous/latitude-deg", newlat_deg);
setprop("ai/models/"~aiName~"/position/previous/longitude-deg", newlon_deg);
setprop("ai/models/"~aiName~"/position/previous/altitude-ft", alt_ft);
var cart = geodtocart(newlat_deg, newlon_deg, alt_ft*feet2meters); # lat/lon/alt(m)
setprop("ai/models/"~aiName~"/position/previous/global-x", cart[0]);
setprop("ai/models/"~aiName~"/position/previous/global-y", cart[1]);
setprop("ai/models/"~aiName~"/position/previous/global-z", cart[2]);
#}, waitTime_sec );
#waitTime_sec+=4;
# set the speed--if not preserving speed/position OR if speed is 0 (due to crashing etc)
var currSpeed_kt= getprop ("ai/models/"~aiName~"/velocities/true-airspeed-kt");
if (preservePosSpeed==0 or currSpeed_kt==0 ) {
var min_vel_kt=getprop( "ai/models/"~aiName~"/bombable/attributes/velocities/minSpeed_kt");
var cruise_vel_kt=getprop( "ai/models/"~aiName~"/bombable/attributes/velocities/cruiseSpeed_kt");
var attack_vel_kt=getprop( "ai/models/"~aiName~"/bombable/attributes/velocities/attackSpeed_kt");
var max_vel_kt=getprop( "ai/models/"~aiName~"/bombable/attributes/velocities/maxSpeed_kt");
#defaults
if (type=="aircraft") {
if (min_vel_kt==nil or min_vel_kt<1) min_vel_kt=50;
if (cruise_vel_kt==nil or cruise_vel_kt<1) {
cruise_vel_kt=2*min_vel_kt;
#they're at 82% to 102% of your current airspeed
var vel=getprop ("/velocities/airspeed-kt") * (.82 + rand()*.2);
} else { var vel=0; }
if (attack_vel_kt==nil or attack_vel_kt<=cruise_vel_kt) attack_vel_kt=1.5*cruise_vel_kt;
if (max_vel_kt==nil or max_vel_kt<=attack_vel_kt) max_vel_kt=1.5*attack_vel_kt;
} else {
if (min_vel_kt==nil or min_vel_kt<1) min_vel_kt=10;
if (cruise_vel_kt==nil or cruise_vel_kt<1) {
cruise_vel_kt=2*min_vel_kt;
var vel=15;
} else { var vel=0;}
if (attack_vel_kt==nil or attack_vel_kt<=cruise_vel_kt) attack_vel_kt=1.5*cruise_vel_kt;
if (max_vel_kt==nil or max_vel_kt<=attack_vel_kt) max_vel_kt=1.5*attack_vel_kt;
}
debprint ("vel1:", vel);
if (vel<min_vel_kt or vel==0) vel=(attack_vel_kt-cruise_vel_kt)*rand() + cruise_vel_kt;
if (vel>max_vel_kt) vel=max_vel_kt;
debprint ("vel2:", vel);
setprop ("ai/models/"~aiName~"/velocities/true-airspeed-kt", vel);
setprop ("ai/models/"~aiName~"/controls/flight/target-spd", vel);
}
}
if ( preservePosSpeed==1) {
if (revitType=="aircraft") {
var msg = numRespawned ~ " AI Aircraft have damage reset and are about 5000 meters off, with their existing speed, direction, and altitude above ground level preserved";
} else {
var msg = numRespawned ~ " AI ground/water craft have damage reset and are about 5000 meters off";
}
} else if ( preservePosSpeed==2) {
if (revitType=="aircraft") {
var msg = numRespawned ~ " AI Aircraft have damage reset and are at various locations and altitudes within about 15,000 meters";
} else {
var msg = numRespawned ~ " AI ground/water craft have damage reset and are at various locations within about 15,000 meters";
}
} else {
if (revitType=="aircraft") {
var msg = numRespawned ~ " AI Aircraft have damage reset and are at your altitude about 5000 meters off";
} else {
var msg = numRespawned ~ " AI ground/water craft have damage reset and are about 5000 meters off";
}
}
#many times when the objects are relocated they initialize and
# in doing so call reinit GUI. This can cause a segfault if
# we are in the middle of popping up our message. So best to wait a while
# before doing it . . .
settimer ( func { targetStatusPopupTip (msg, 2);}, 13);
debprint ("Bombable: " ~ msg);
}
####################################################
# resetBombableDamageFuelWeapons
# reset the damage, smoke & fires from an AI aircraft, or the main aircraft
# myNodeName = the AI node to reset, or set myNodeName="" for the main
# #aircraft.
var resetBombableDamageFuelWeapons = func (myNodeName) {
#if (myNodeName=="" or myNodeName=="environment") myNodeName="/environment";
debprint ("Bombable: Resetting damage level and fires for ", myNodeName);
#don't do this for objects that don't even have bombable initialized
if (props.globals.getNode ( ""~myNodeName~"/bombable" ) == nil) return;
if (myNodeName=="") {
#main aircraft
reset_damage_fires();
} else {
#ai objects
#refill fuel & weapons
stores.fillFuel(myNodeName, 1);
stores.fillWeapons (myNodeName, 1);
deleteFire(myNodeName);
deleteSmoke("damagedengine", myNodeName);
if (props.globals.getNode ( ""~myNodeName~"/bombable/attributes/damage" ) != nil) {
setprop(""~myNodeName~"/bombable/attributes/damage", 0);
setprop(""~myNodeName~"/bombable/exploded", 0);
setprop(""~myNodeName~"/bombable/on-ground", 0);
setprop(""~myNodeName~"/bombable/attributes/damageAltAddCurrent_ft", 0);
setprop(""~myNodeName~"/bombable/attributes/damageAltAddCumulative_ft",0);
#take the opportunity to reset the pilot's abilities, giving them
# a new personality when they come back alive
pilotAbility = math.pow (rand(), 1.5) ;
if (rand()>.5) pilotAbility=-pilotAbility;
setprop(""~myNodeName~"/bombable/attack-pilot-ability", pilotAbility);
# Set an individual pilot weapons ability, -1 to 1, with 0 being average
pilotAbility = math.pow (rand(), 1.5) ;
if (rand()>.5) pilotAbility=-pilotAbility;
setprop(""~myNodeName~"/bombable/weapons-pilot-ability", pilotAbility);
if (myNodeName != "") {
msg = "Damage reset to 0 for " ~ myNodeName;
targetStatusPopupTip (msg, 2);
}
}
}
}
####################################################
# resetAllAIDamage
# reset the damage, smoke & fires from all AI object with bombable operative
var resetAllAIDamage = func {
ai = props.globals.getNode ("/ai/models").getChildren();
foreach (elem;ai) {
aiName=elem.getName() ~ "[" ~ elem.getIndex() ~ "]";
#reset damage, smoke, fires for all objects that have bombable initialized
#even does it for multiplayer objects, which is not completely proper (the MP bombable
#keeps their 'real' damage total remotely), but might help in case of MP malfunction of some sort, and doesn't hurt in the meanwhile
if (props.globals.getNode ( "/ai/models/"~aiName~"/bombable" ) != nil) {
resetBombableDamageFuelWeapons ("/ai/models/" ~ aiName);
}
}
msg = "Damage reset to 0 for all AI objects";
targetStatusPopupTip (msg, 2);
debprint ("Bombable: "~msg);
}
####################################################
# resetMainAircraftDamage
# reset the damage, smoke & fires from the main aircraft/object
var resetMainAircraftDamage = func {
resetBombableDamageFuelWeapons ("");
msg = "Damage reset to 0 for main aircraft - you'll need to turn on your magnetos/restart your engines";
selfStatusPopupTip (msg, 2);
debprint ("Bombable: "~msg);
}
############################################################
####################################################
#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_bombable_dialog = func () {
#return; #gui prob
#we set bomb_menuNum to -1 at initialization time.
#On reinit & some other times, this routine will be called again
#so if bomb_menuNum != -1 we know not to seek out another new menu number
#Without this check, we'd get a new Bombable menu added each time FG reinits
#or re-positions.
if (bomb_menuNum==nil or bomb_menuNum==-1) {
#find the next open menu number/kludge
bomb_menuNum=97; #the default
for (var i=0;i<300;i+=1) {
p=props.globals.getNode("/sim/menubar/default/menu["~i~"]");
if ( typeof(p) == "nil" ) {
bomb_menuNum=i;
print ("Bombable: Found empty menu: " ~ i);
break;
} else {
# var l = props.globals.getNode("/sim/menubar/default/menu["~i~"]/name");
var n = p.getChild("name");
if (typeof(n) != "nil" ) var l = n.getValue();
print ("Bombable: Looking at menu found a " ~ typeof(l));
#p = records.create_printable_summary(l);
if (typeof(l) != "nil") mss= l else mss= "nothing at " ~ i;
print ("Bombable: Looking @ menu found: " ~ mss);
if ( typeof(l) != "nil" and l == "Bombable") { # aha, we've already set up the menu once before. So just re-use it. This happens in FG 2016.x etc when the user re-inits.
bomb_menuNum=i;
print ("Bombable: Found existing Bombable menu; re-initing: " ~ i);
break;
}
}
}
}
#init the main bombable options menu
#todo: figure out how to position it in the center of the screen or somewhere better
dialog.init(0,0);
#make the GUI menubar item to select the options menu
props.globals.getNode ("/sim/menubar/default/menu["~bomb_menuNum~"]/enabled", 1).setBoolValue(1);
props.globals.getNode ("/sim/menubar/default/menu["~bomb_menuNum~"]/name", 1).setValue("Bombable");
props.globals.getNode ("/sim/menubar/default/menu["~bomb_menuNum~"]/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["~bomb_menuNum~"]/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["~bomb_menuNum~"]/item/binding/command", 1).setValue("nasal");
props.globals.getNode ("/sim/menubar/default/menu["~bomb_menuNum~"]/item/binding/script", 1).setValue("bombable.dialog.create()");
props.globals.getNode ("/sim/menubar/default/menu["~bomb_menuNum~"]/item[1]/label", 1).setValue("Bombable Statistics"); #must be unique name from all others in the menubar or they both pop up together
props.globals.getNode ("/sim/menubar/default/menu["~bomb_menuNum~"]/item[1]/binding/command", 1).setValue("nasal");
props.globals.getNode ("/sim/menubar/default/menu["~bomb_menuNum~"]/item[1]/binding/script", 1).setValue("bombable.records.display_results()");
#reinit makes the property changes to both the GUI & input become active
#the delay is to avoid a segfault under dev version of FlightGear, 2010/09/07
#This just a workaround, a real fix would like:
# overwriting preferences.xml with a new one including a line like <menubar include="Dialogs/bombable.xml"/>'
#Thx goes to user Citronnier for tracking this down
#settimer (func {fgcommand("reinit")}, 15);
#As of FG 2.4.0, a straight "reinit" leads to FG crash or the dreaded NAN issue
#at least with some aircraft. Reinit/gui (as below) gets around this problem.
#fgcommand("reinit", props.Node.new({subsystem : "gui"}));
#OK . . . per gui.nas line 63, this appears to be the right way to do this:
fgcommand ("gui-redraw");
}
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 = 10, override = nil) {
#return; #gui prob
var tmpl = props.Node.new({
name : "PopTipSelf", modal : 0, layout : "hbox",
y: 140,
text : { label : label, padding : 6 }
});
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 ) {
#return; #gui prob
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("/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();
#return; #gui prob
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 buttonBar1 = me.dialog.addChild("group");
buttonBar1.set("layout", "hbox");
buttonBar1.set("default-padding", 10);
lresetSelf = buttonBar1.addChild("button");
lresetSelf.set("legend", "Reset Main Aircraft Damage");
lresetSelf.set("equal", 1);
lresetSelf.prop().getNode("binding[0]/command", 1).setValue("nasal");
lresetSelf.prop().getNode("binding[0]/script", 1).setValue("bombable.resetMainAircraftDamage();");
lresetSelf.prop().getNode("binding[1]/command", 1).setValue("nasal");
lresetSelf.prop().getNode("binding[1]/script", 1).setValue("bombable.bombable_dialog_save();");
lresetSelf.prop().getNode("binding[2]/command", 1).setValue("dialog-apply");
lresetSelf.prop().getNode("binding[3]/command", 1).setValue("dialog-close");
lresetAI = buttonBar1.addChild("button");
lresetAI.set("legend", "Reset AI Objects Damage");
lresetAI.prop().getNode("binding[0]/command", 1).setValue("nasal");
lresetAI.prop().getNode("binding[0]/script", 1).setValue("bombable.resetAllAIDamage();");
lresetAI.prop().getNode("binding[1]/command", 1).setValue("nasal");
lresetAI.prop().getNode("binding[1]/script", 1).setValue("bombable.bombable_dialog_save();");
lresetAI.prop().getNode("binding[2]/command", 1).setValue("dialog-apply");
lresetAI.prop().getNode("binding[3]/command", 1).setValue("dialog-close");
var buttonBar2 = me.dialog.addChild("group");
buttonBar2.set("layout", "hbox");
buttonBar2.set("default-padding", 10);
#respawning often makes AI objects init or reinit, which sometimes
# includes GUI reinit. So we need to save/close the dialogue first
# thing; otherwise segfault is likely
lrevitAIAir = buttonBar2.addChild("button");
lrevitAIAir.set("legend", "Respawn AI Aircraft Grouped Near You");
lrevitAIAir.set("tooltip", "Place all AI Aircraft in a group near your location.");
lrevitAIAir.prop().getNode("binding[0]/command", 1).setValue("nasal");
lrevitAIAir.prop().getNode("binding[0]/script", 1).setValue("bombable.revitalizeAllAIObjects(\"aircraft\",0);");
lrevitAIAir.prop().getNode("binding[1]/command", 1).setValue("nasal");
lrevitAIAir.prop().getNode("binding[1]/script", 1).setValue("bombable.bombable_dialog_save();");
lrevitAIAir.prop().getNode("binding[2]/command", 1).setValue("dialog-apply");
lrevitAIAir.prop().getNode("binding[3]/command", 1).setValue("dialog-close");
lrevitAIObj = buttonBar2.addChild("button");
lrevitAIObj.prop().getNode("binding[0]/command", 1).setValue("nasal");
lrevitAIObj.prop().getNode("binding[0]/script", 1).setValue("bombable.revitalizeAllAIObjects(\"ship\", 0);");
lrevitAIObj.prop().getNode("binding[1]/command", 1).setValue("nasal");
lrevitAIObj.prop().getNode("binding[1]/script", 1).setValue("bombable.bombable_dialog_save();");
lrevitAIObj.set("legend", "Respawn AI Ground/Water Craft Grouped Near You");
lrevitAIObj.prop().getNode("binding[2]/command", 1).setValue("dialog-apply");
lrevitAIObj.prop().getNode("binding[3]/command", 1).setValue("dialog-close");
var buttonBar3 = me.dialog.addChild("group");
buttonBar3.set("layout", "hbox");
buttonBar3.set("default-padding", 10);
#respawning often makes AI objects init or reinit, which sometimes
# includes GUI reinit. So we need to save/close the dialogue first
# thing; otherwise segfault is likely
lrevitPAIAir = buttonBar3.addChild("button");
lrevitPAIAir.set("legend", "Respawn AI Aircraft Near You, Preserve Relative Position");
lrevitPAIAir.prop().getNode("binding[0]/command", 1).setValue("nasal");
lrevitPAIAir.prop().getNode("binding[0]/script", 1).setValue("bombable.revitalizeAllAIObjects(\"aircraft\",1);");
lrevitPAIAir.prop().getNode("binding[1]/command", 1).setValue("nasal");
lrevitPAIAir.prop().getNode("binding[1]/script", 1).setValue("bombable.bombable_dialog_save();");
lrevitPAIAir.prop().getNode("binding[2]/command", 1).setValue("dialog-apply");
lrevitPAIAir.prop().getNode("binding[3]/command", 1).setValue("dialog-close");
lrevitPAIObj = buttonBar3.addChild("button");
lrevitPAIObj.prop().getNode("binding[0]/command", 1).setValue("nasal");
lrevitPAIObj.prop().getNode("binding[0]/script", 1).setValue("bombable.revitalizeAllAIObjects(\"ship\", 1);");
lrevitPAIObj.prop().getNode("binding[1]/command", 1).setValue("nasal");
lrevitPAIObj.prop().getNode("binding[1]/script", 1).setValue("bombable.bombable_dialog_save();");
lrevitPAIObj.set("legend", "Respawn AI Ground/Water Craft Near You, Preserve Relative Position");
lrevitPAIObj.set("tooltip", "Respawn AI Ground/Water Craft Preserving Relative Position");
lrevitPAIObj.prop().getNode("binding[2]/command", 1).setValue("dialog-apply");
lrevitPAIObj.prop().getNode("binding[3]/command", 1).setValue("dialog-close");
var buttonBar4 = me.dialog.addChild("group");
buttonBar4.set("layout", "hbox");
buttonBar4.set("default-padding", 10);
lrevitDISAir = buttonBar4.addChild("button");
lrevitDISAir.set("legend", "Respawn AI Aircraft Near You, Dispersed");
lrevitDISAir.prop().getNode("binding[0]/command", 1).setValue("nasal");
lrevitDISAir.prop().getNode("binding[0]/script", 1).setValue("settimer ( func {bombable.revitalizeAllAIObjects(\"aircraft\",2); }, 1);"); #settimer so there is time for the distance values to be put on the prop. tree
lrevitDISAir.prop().getNode("binding[1]/command", 1).setValue("nasal");
lrevitDISAir.prop().getNode("binding[1]/script", 1).setValue("bombable.bombable_dialog_save();");
lrevitDISAir.prop().getNode("binding[2]/command", 1).setValue("dialog-apply");
lrevitDISAir.prop().getNode("binding[3]/command", 1).setValue("dialog-close");
lrevitDISObj = buttonBar4.addChild("button");
lrevitDISObj.prop().getNode("binding[0]/command", 1).setValue("nasal");
lrevitDISObj.prop().getNode("binding[0]/script", 1).setValue("settimer ( func {bombable.revitalizeAllAIObjects(\"ship\",2); }, 1);");
lrevitDISObj.prop().getNode("binding[1]/command", 1).setValue("nasal");
lrevitDISObj.prop().getNode("binding[1]/script", 1).setValue("bombable.bombable_dialog_save();");
lrevitDISObj.set("legend", "Respawn AI Ground/Water Craft Near You, Dispersed");
lrevitDISObj.set("tooltip", "Respawn AI Ground/Water Craft Preserving Relative Position");
lrevitDISObj.prop().getNode("binding[2]/command", 1).setValue("dialog-apply");
lrevitDISObj.prop().getNode("binding[3]/command", 1).setValue("dialog-close");
var buttonBar5 = me.dialog.addChild("group");
buttonBar5.set("layout", "hbox");
buttonBar5.set("default-padding", 10);
lrevitDISmin = buttonBar5.addChild("input");
lrevitDISmin.set("label", "Min dispersal distance (km)");
lrevitDISmin.set("property", bomb_menu_pp~"dispersal-dist-min_km");
lrevitDISmin.set("default", "1");
lrevitDISmax = buttonBar5.addChild("input");
lrevitDISmax.set("label", "Max dispersal distance (km)");
lrevitDISmax.set("property", bomb_menu_pp~"dispersal-dist-max_km");
lrevitDISmax.set("default", "16");
# lresetAI = buttonBar1.addChild("button");
# lresetAI.set("legend", "Reset All Damage (Main & AI)");
# lresetAI.prop().getNode("binding[0]/command", 1).setValue("nasal");
# lresetAI.prop().getNode("binding[0]/script", 1).setValue("bombable.resetAllAIDamage();bombable.resetMainAircraftDamage();");
me.dialog.addChild("hrule");
var content = me.dialog.addChild("group");
content.set("layout", "vbox");
content.set("halign", "center");
content.set("default-padding", 5);
#triggers (-trigger) are the overall on/off flag for that type of fire/smoke globally in Bombable
# burning (-burning) is the local flag telling whether the type of
# fire/smoke is burning on that particle node/aircraft
#
foreach (var b; [["Bombable module enabled", bomb_menu_pp~"bombable-enabled", "checkbox"],
["", "", "hrule"],
["Weapon realism (your weapons)", bomb_menu_pp~"main-weapon-realism-combo", "combo", 300, ["Ultra-realistic", "Normal", "Easier", "Dead easy"]],
#["AI aircraft can shoot at you", bomb_menu_pp~"ai-aircraft-weapons-enabled", "checkbox"],
["AI Weapon effectiveness (AI aircraft's weapons)", bomb_menu_pp~"ai-weapon-power-combo", "combo", 300, ["Much more effective", "More effective", "Normal", "Less effective", "Much less effective", "Disabled (they can't shoot at you)"]],
#["AI fighter aircraft maneuver and attack", bomb_menu_pp~"ai-aircraft-attack-enabled", "checkbox"],
["AI aircraft flying/dogfighting skill", bomb_menu_pp~"ai-aircraft-skill-combo", "combo", 300, ["Very skilled", "Above average", "Normal", "Below average", "Unskilled", "Disabled (AI aircraft can't maneuver)"]],
["Bombable-via-multiplayer enabled", MP_share_pp, "checkbox"],
["Excessive acceleration/speed warnings", GF_damage_menu_pp~"warning_enabled", "checkbox"],
["Excessive acceleration/speed damages aircraft", GF_damage_menu_pp~"damage_enabled", "checkbox"],
["Weapon impact flack enabled", trigger1_pp~"flack"~trigger2_pp, "checkbox"],
["AI weapon fire visual effect", trigger1_pp~"ai-weapon-fire-visual"~trigger2_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 (twice as easy to hit targets; AI aircraft do easier manuevers; may combine w/Super Easy)", bomb_menu_pp~"easy-mode", "checkbox"],
#["Super Easy Mode (3X as easy to hit targets; damaged tripled; AI aircraft do yet easier manuevers)", bomb_menu_pp~"super-easy-mode", "checkbox"],
["AI ground detection: Can be disabled to improve framerate when your AI scenarios are far above the ground", bomb_menu_pp~"ai-ground-loop-enabled", "checkbox"],
#["AI Weapon Effectiveness", bomb_menu_pp~"ai-weapon-power", "slider", 200, 0, 100 ],
["Print debug messages to console", bomb_menu_pp~"debug", "checkbox"]
]
) {
var w = content.addChild(b[2]);
w.node.setValues({"label" : b[0],
"halign" : "left",
"property" : b[1],
# "width" : "200",
});
if (b[2]=="select" or b[2]=="combo" or b[2]=="list" ){
w.node.setValues({"pref-width" : b[3],
});
foreach (var r; b[4]) {
var newentry = w.addChild("value");
newentry.node.setValue(r);
}
}
if (b[2]=="slider"){
w.node.setValues({"pref-width" : b[3],
"min" : b[4],
"max" : b[5],
});
}
}
me.dialog.addChild("hrule");
var buttonBar = me.dialog.addChild("group");
buttonBar.set("layout", "hbox");
buttonBar.set("default-padding", 10);
lsave = buttonBar.addChild("button");
lsave.set("legend", "Save");
lsave.set("default", 1);
lsave.set("equal", 1);
lsave.prop().getNode("binding[0]/command", 1).setValue("dialog-apply");
lsave.prop().getNode("binding[1]/command", 1).setValue("nasal");
lsave.prop().getNode("binding[1]/script", 1).setValue("bombable.bombable_dialog_save();");
lsave.prop().getNode("binding[2]/command", 1).setValue("dialog-close");
lcancel = buttonBar.addChild("button");
lcancel.set("legend", "Cancel");
lcancel.set("equal", 1);
lcancel.prop().getNode("binding[0]/command", 1).setValue("dialog-close");
# 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 {
#return; #gui prob
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 {
#return; #gui prob
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
###############################################################################
var bombable_dialog_save = func {
#return; #gui prob
debprint ("Bombable: iowriting, writing . . . ");
io.write_properties(bombable_settings_file, ""~bomb_menu_pp);
}
var init_bombable_dialog_listeners = func {
#return; #gui prob
#We replaced this scheme for writing the menu selections whenever they
#are changed, to just using the 'save' button
#what to do when any bombable setting is changed
#setlistener(""~bomb_menu_pp, func {
#the lock prevents the file from being written if we are setting/
# changing menu values internally or setting menu defaults
# We only want to save the menu properties when the **user**
# makes changes.
# debprint ("Bombable: iowriting, checking lock . . . ");
# if (!getprop(bomb_menu_save_lock)) {
# debprint ("Bombable: iowriting, writing . . . ");
# io.write_properties(bombable_settings_file, ""~bomb_menu_pp);
# }
#},0,2);#0,0 means (0) don't do on initial startup and (2) call listener func
# on change of any child value
#set listener function for main weapon power menu item
setlistener(""~bomb_menu_pp~"main-weapon-realism-combo", func {
var weap_pow=""~bomb_menu_pp~"main-weapon-realism-combo";
var val = getprop(weap_pow);
debprint ("Updating main weapon power combo . . . ");
#"Realistic", "Easy", "Super Easy", "Super-Duper Easy"
if (val=="Ultra-realistic") {
setprop (bomb_menu_pp~"easy-mode", 0);
setprop (bomb_menu_pp~"super-easy-mode", 0);
} elsif (val=="Normal") {
setprop (bomb_menu_pp~"easy-mode", 1);
setprop (bomb_menu_pp~"super-easy-mode", 0);
} elsif (val=="Dead easy") {
setprop (bomb_menu_pp~"easy-mode", 1);
setprop (bomb_menu_pp~"super-easy-mode", 1);
} else { #value "Easier" is the default
setprop (bomb_menu_pp~"easy-mode", 0);
setprop (bomb_menu_pp~"super-easy-mode", 1);
}
},1,1);#0,0 means (1) do on initial startup and (1) call listener func only when value is changed
#set listener function for main weapon power menu item
setlistener(""~bomb_menu_pp~"ai-weapon-power-combo", func {
debprint ("Updating ai weapon power combo . . . ");
var weap_pow=""~bomb_menu_pp~"ai-weapon-power-combo";
var val = getprop(weap_pow);
if (val=="More effective") {
setprop (bomb_menu_pp~"ai-weapon-power", 15);
setprop (bomb_menu_pp~"ai-aircraft-weapons-enabled", 1);
} elsif (val=="Much more effective") {
setprop (bomb_menu_pp~"ai-weapon-power", 22.5);
setprop (bomb_menu_pp~"ai-aircraft-weapons-enabled", 1);
} elsif (val=="Less effective") {
setprop (bomb_menu_pp~"ai-weapon-power", 7.5);
setprop (bomb_menu_pp~"ai-aircraft-weapons-enabled", 1);
} elsif (val=="Normal") {
setprop (bomb_menu_pp~"ai-weapon-power", 11);
setprop (bomb_menu_pp~"ai-aircraft-weapons-enabled", 1);
} elsif (val=="Disabled (they can't shoot at you)") {
setprop (bomb_menu_pp~"ai-weapon-power", 0);
setprop (bomb_menu_pp~"ai-aircraft-weapons-enabled", 0);
} else { #value "Much less effective" is the default
setprop (bomb_menu_pp~"ai-weapon-power", 5);
setprop (bomb_menu_pp~"ai-aircraft-weapons-enabled", 1);
}
},1,1);#0,0 means (1) do on initial startup and (1) call listener func only when value is changed
#set listener function for AI aircraft fighting skill menu item
setlistener(""~bomb_menu_pp~"ai-aircraft-skill-combo", func {
debprint ("Updating ai aircraft skill combo . . . ");
var maneuv=""~bomb_menu_pp~"ai-aircraft-skill-combo";
var val = getprop(maneuv);
#"Realistic", "Easy", "Super Easy", "Super-Duper Easy"
if (val=="Very skilled") {
setprop (bomb_menu_pp~"ai-aircraft-skill-level", 5);
setprop (bomb_menu_pp~"ai-aircraft-attack-enabled", 1);
} elsif (val=="Above average") {
setprop (bomb_menu_pp~"ai-aircraft-skill-level", 4);
setprop (bomb_menu_pp~"ai-aircraft-attack-enabled", 1);
} elsif (val=="Below average") {
setprop (bomb_menu_pp~"ai-aircraft-skill-level", 2);
setprop (bomb_menu_pp~"ai-aircraft-attack-enabled", 1);
} elsif (val=="Normal") {
setprop (bomb_menu_pp~"ai-aircraft-skill-level", 3);
setprop (bomb_menu_pp~"ai-aircraft-attack-enabled", 1);
} elsif (val=="Disabled (AI aircraft can't maneuver)") {
setprop (bomb_menu_pp~"ai-aircraft-skill-level", 0);
setprop (bomb_menu_pp~"ai-aircraft-attack-enabled", 0);
} else { #value "Unskilled" is the default
setprop (bomb_menu_pp~"ai-aircraft-skill-level", 1);
setprop (bomb_menu_pp~"ai-aircraft-attack-enabled", 1);
}
},1,1);#0,0 means (1) do on initial startup and (1) call listener func only when value is changed
}
var setupBombableMenu = func {
init_bombable_dialog_listeners ();
#main bombable module is enabled by default
if (getprop (bomb_menu_pp~"bombable-enabled") == nil )
props.globals.getNode(bomb_menu_pp~"bombable-enabled", 1).setBoolValue(1);
#multiplayer mode enabled by default
if (getprop (MP_share_pp) == nil )
props.globals.getNode(MP_share_pp, 1).setBoolValue(1);
#fighter attack turned on by default
if (getprop (""~bomb_menu_pp~"ai-aircraft-attack-enabled") == nil )
props.globals.getNode(""~bomb_menu_pp~"ai-aircraft-attack-enabled", 1).setIntValue(1);
if (getprop (""~bomb_menu_pp~"ai-ground-loop-enabled") == nil )
props.globals.getNode(""~bomb_menu_pp~"ai-ground-loop-enabled", 1).setIntValue(1);
#set these defaults
if (getprop (""~bomb_menu_pp~"main-weapon-realism-combo") == nil )
props.globals.getNode(""~bomb_menu_pp~"main-weapon-realism-combo", 1).setValue("Much easier");
if (getprop (""~bomb_menu_pp~"ai-weapon-power-combo") == nil )
props.globals.getNode(""~bomb_menu_pp~"ai-weapon-power-combo", 1).setValue("Less effective");
if (getprop (""~bomb_menu_pp~"ai-aircraft-skill-combo") == nil )
props.globals.getNode(""~bomb_menu_pp~"ai-aircraft-skill-combo", 1).setValue("Unskilled");
#debug default
if (getprop (bomb_menu_pp~"debug") == nil )
props.globals.getNode(bomb_menu_pp~"debug", 1).setIntValue(0);
#flack is default off because it seems to sometimes cause FG crashes
#Update now default on because it seems fine
if (getprop (""~trigger1_pp~"flack"~trigger2_pp) == nil )
props.globals.getNode(""~trigger1_pp~"flack"~trigger2_pp, 1).setBoolValue(1);
if (getprop (""~trigger1_pp~"ai-weapon-fire-visual"~trigger2_pp) == nil )
props.globals.getNode(""~trigger1_pp~"ai-weapon-fire-visual"~trigger2_pp, 1).setBoolValue(1);
foreach (var smokeType; [
["fire",88, 3600],
["jetcontrail", 77, -1],
["smoketrail", 55, -1],
["pistonexhaust", 15, -1],
["damagedengine", 55, -1],
["flare",66,3600],
] ) {
#trigger is the overall flag for that type of smoke/fire
# for Bombable as a whole
# burning is the flag as to whether that effect is turned on
# for the main aircraft
props.globals.getNode(""~life1_pp~smokeType[0]~burning_pp, 1).setBoolValue(0);
if (getprop (""~trigger1_pp~smokeType[0]~trigger2_pp) == nil )
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]);
}
init_bombable_dialog();
#the previously attempted "==nil" trick doesn't work because this io.read routine
# leaves unchecked values as 'nil'
# so we set our defaults first & then load the file. Anything that wasn't set by
# the file just remains as our default.
#
# Now, read the menu default file:
debprint ("Bombable: ioreading . . . ");
var target = props.globals.getNode(""~bomb_menu_pp);
io.read_properties(bombable_settings_file, target);
}
#####################################################
# FUNCTION calcPilotSkill
# returns the skill level of the AI pilot
# adjusted for the pilot individual skill level AND
# the current level of damage
#
var calcPilotSkill = func ( myNodeName ) {
#skill ranges 0-5; 0=disabled, so 1-5;
var skill=getprop (bomb_menu_pp~"ai-aircraft-skill-level");
if (skill==nil) skill=0;
var skillMult=1;
#pilotSkill is a rand +/-1 in skill level per individual pilot
# so now skill ranges 0-6
var pilotSkill = getprop(""~myNodeName~"/bombable/attack-pilot-ability");
if (pilotSkill==nil) pilotSkill=0;
skill+=pilotSkill;
#ability to maneuever goes down as attack fuel reserves are depleted
var fuelLevel=stores.fuelLevel (myNodeName);
if (fuelLevel<.2) skill *= fuelLevel/ 0.2;
var damage = getprop(""~myNodeName~"/bombable/attributes/damage");
#skill goes down to 0 as damage goes from 80% to 100%
if (damage > 0.8) skill *= (1 - damage)/ 0.2;
return skill;
}
##########################################################
# FUNCTION trueAirspeed2indicatedAirspeed
# Give a node name & true airspeed, returns the indicated airspeed
# (using the elevation of the AI object for the calculation)
#
# The formula IAS=TAS* (1 + .02 * alt/1000) is a rule-of-thumb
# approximation for IAS but about the best we can do in simple terms
# since we don't have the temperature or pressure of the AI aircraft
# current altitude easily available.
#
# TODO: We should really use IAS for more of the AI aircraft speed limits
# & calculations, but stall speed is likely most crucial. For instance,
# VNE (max allowed speed) seems more related to TAS for most AC.
var trueAirspeed2indicatedAirspeed = func (myNodeName="", trueAirspeed_kt=0 ) {
currAlt_ft = getprop(""~myNodeName~"/position/altitude-ft");
return trueAirspeed_kt * ( 1 + .02 * currAlt_ft/1000);
}
####################################################
#return altitude (in feet) of given lat/lon
var elev = func (lat, lon) {
var info = geodinfo(lat, lon);
if (info != nil) {
var alt_m=info[0];
if (alt_m==nil) alt_m=0;
return alt_m/feet2meters; #return the altitude in feet
} else return 0;
}
###############################################################################
# MP messages
# directly based on similar functions in wildfire.nas
#
var damage_msg = func (callsign, damageAdd, damageTotal, smoke=0, fire=0, messageType=1) {
if (!getprop(MP_share_pp)) return;
if (!getprop (MP_broadcast_exists_pp)) return;
if (!getprop(bomb_menu_pp~"bombable-enabled") ) return;
n=0;
#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
msg=sprintf ("%6s", callsign) ~
Binary.encodeByte(messageType) ~
Binary.encodeDouble(damageAdd) ~
Binary.encodeDouble(damageTotal) ~
Binary.encodeByte(smoke) ~
Binary.encodeByte(fire);
#too many messages overwhelm the system. So we set a lock & only send messages
# every 5 seconds or so (lockWaitTime); at the end we send the final message
# (which has the final damage percentage)
# of any that were skipped in the meanwhile
lockName=""~callsign~messageType;
lock=props.globals.getNode("/bombable/locks/"~lockName~"/lock", 1).getValue();
if (lock==nil or lock=="") lock=0;
masterLock=props.globals.getNode("/bombable/locks/masterLock", 1).getValue();
if (masterLock==nil or masterLock=="") masterLock=0;
currTime=systime();
#We can send 1 message per callsign & per message type, per lockWaitTime
# seconds. It sets a lock to prevent messages being sent in the meanwhile.
# It sets a timer to send a cumulative damage message at the end of the
# lock time to give a single update for damage in the meanwhile.
# As a failsafe it also saves system time in the lock & any new
# damage messages coming through after that lockWaitTime seconds are
# allowed to go forward.
# For vitally important messages (like master reset) we can set a masterLock
# and no other messages can go out during that time.
# This is abit of a kludge. For real we should queue up messages &
# send them out at a rate no faster than say 1/2 as fast as the rate
# mpreceive checks for new messages.
if ((currTime - masterLock > masterLockWaitTime) and (lock==nil or lock=="" or lock==0 or currTime - lock > lockWaitTime)) {
lockNum=lockNum+1;
props.globals.getNode("/bombable/locks/"~lockName~"/lock", 1).setDoubleValue(currTime);
settimer (func {
lock=getprop ("/bombable/locks/"~lockName~"/lock");
msg2=getprop ("/bombable/locks/"~lockName~"/msg");
setprop ("/bombable/locks/"~lockName~"/lock", 0);
setprop ("/bombable/locks/masterLock", 0);
setprop ("/bombable/locks/"~lockName~"/msg", "");
if (msg2!=nil and msg2 != ""){
mpsend(msg2);
debprint ("Bombable: Sending delayed message "~msg);
}
}, lockWaitTime);
return msg
} else {
setprop ("/bombable/locks/"~lockName~"/msg", msg);
return nil;
}
}
###############################################################################
# reset_msg - part of MP messages
#
var reset_msg = func () {
if (!getprop(MP_share_pp)) return "";
if (!getprop (MP_broadcast_exists_pp)) return "";
if (!getprop(bomb_menu_pp~"bombable-enabled") ) return;
n=0;
#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
callsign=getprop ("/sim/multiplay/callsign");
props.globals.getNode("/bombable/locks/masterLock", 1).setDoubleValue(systime());
#messageType=2 is the reset message
return sprintf ("%6s", callsign) ~
Binary.encodeByte(2);
}
var parse_msg = func (source, msg) {
if (!getprop(MP_share_pp)) return;
if (!getprop (MP_broadcast_exists_pp)) return;
if (!getprop(bomb_menu_pp~"bombable-enabled") ) return;
debprint("Bombable: typeof source: ", typeof(source));
debprint ("Bombable: source: ", source, " msg: ",msg);
var ourcallsign=getprop ("/sim/multiplay/callsign");
var p = 0;
var msgcallsign = substr(msg, 0, 6);
p = 6;
var type = Binary.decodeByte(substr(msg, p));
p += Binary.sizeOf["byte"];
#debprint ("msgcallsign:"~ msgcallsign," type:"~ type);
#not our callsign and type !=2, we ignore it & return (type=2 broadcasts to
#*everyone* that their callsign is re-setting, so we always listen to that)
if ((sprintf ("%6s", msgcallsign) != sprintf ("%6s", ourcallsign)) and
type != 2 and type != 3 ) return;
#damage message
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"];
debprint ("damageAdd:",damageAdd," damageTotal:",damageTotal," smoke:",smokeStart," fire:", fireStart);
mainAC_add_damage (damageAdd, damageTotal, "weapons", "Hit by weapons!" );
}
#reset message for callsign
elsif (type == 2) {
#ai_loc="/ai/models";
#var mp_aircraft = props.globals.getNode(ai_loc).getChildren("multiplayer");
#foreach (mp;mp_aircraft) { #mp is the node of a multiplayer AI aircraft
# mp_callsign=mp.getNode("callsign").getValue();
# mp_childname=mp.getName();
# mp_index=mp.getIndex();
# mp_name=ai_loc~"/"~mp_childname~"["~mp_index~"]";
# mp_path=cmdarg().getPath(mp);
# debprint ("Bombable: mp_path=" ~mp_path);
mp_name=source;
debprint ("Bombable: Resetting fire/damage for - name: ", source, " callsign: "~string.trim(msgcallsign) );
# if (sprintf ("%6s", mp_callsign) == sprintf ("%6s", msgcallsign)) {
#blow away the locks for MP communication--shouldn't really
# be needed--just a little belt & suspendors things here
# to make sure that no old damage (prior to the reset) is sent
# to the aircraft again after the reset, and that none of the
# locks are stuck.
props.globals.getNode("/bombable").removeChild("locks",0);
resetBombableDamageFuelWeapons(source);
msg= string.trim(msgcallsign)~" is resetting; damage reset to 0% for "~string.trim(msgcallsign);
debprint ("Bombable: "~msg);
targetStatusPopupTip (msg, 30);
# }
#}
}
#update of callsign's current damage, smoke, fire situation
elsif (type == 3) {
# ai_loc="/ai/models";
#var mp_aircraft = props.globals.getNode(ai_loc).getChildren("multiplayer");
#foreach (mp;mp_aircraft) { #mp is the node of a multiplayer AI aircraft
# mp_callsign=mp.getNode("callsign").getValue();
# mp_childname=mp.getName();
# mp_index=mp.getIndex();
# mp_name=ai_loc~"/"~mp_childname~"["~mp_index~"]";
# mp_path=cmdarg().getPath(mp);
# if (sprintf ("%6s", mp_callsign) == sprintf ("%6s", msgcallsign)) {
debprint ("Bombable: Updating fire/damage from - name: ", source ," callsign: "~string.trim(msgcallsign) );
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"];
mp_update_damage (source, damageAdd, damageTotal, smokeStart, fireStart, msgcallsign );
#}
# }
}
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
# TODO: This seems to be causing stutters. We can separate out a separate
# loop to update the fire sizes and probably do some simplification of the
# add_damage routines.
#
var fire_loop = func(id, myNodeName="") {
if (myNodeName=="") myNodeName="";
var loopid = getprop(""~myNodeName~"/bombable/loopids/fire-loopid");
id == loopid or return;
#Set the timer function here at the top
# so if there is some runtime error in the code
# below the timer function still continues to run
var fireLoopUpdateTime_sec=3;
# add rand() so that all objects dont do this function simultaneously
#debprint ("fire_loop starting");
settimer(func { fire_loop(id, myNodeName); }, fireLoopUpdateTime_sec - 0.5 + rand());
node= props.globals.getNode(myNodeName);
type=node.getName();
if(getprop(""~myNodeName~"/bombable/fire-particles/fire-burning")) {
var myFireNodeName = getprop(""~myNodeName~"/bombable/fire-particles/fire-particles-model");
#we have one single property to control the startsize & endsize
#of ALL fire-particles active at one time. This is a bit fakey but saves on processor 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 ("/bombable/fire-particles/smoke-endsize", smokeEndsize);
var smokeEndsize = rand()*125+60;
setprop ("/bombable/fire-particles/smoke-endsize-large", smokeEndsize);
var smokeEndsize = rand()*75+33;
setprop ("/bombable/fire-particles/smoke-endsize-small", smokeEndsize);
var smokeEndsize = rand()*25+9;
setprop ("/bombable/fire-particles/smoke-endsize-very-small", smokeEndsize);
var smokeStartsize=rand()*10 + 5;
#occasionally make a really BIG explosion
if (rand()<.02/fireLoopUpdateTime_sec) {
settimer (func {setprop ("/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 ("/bombable/fire-particles/smoke-startsize", smokeStartsize);
setprop ("/bombable/fire-particles/smoke-startsize-small", smokeStartsize * (rand()/2 + 0.5));
setprop ("/bombable/fire-particles/smoke-startsize-very-small", smokeStartsize * (rand()/8 + 0.2));
setprop ("/bombable/fire-particles/smoke-startsize-large", smokeStartsize* (rand()*4 + 1));
#damageRate_percentpersecond = getprop (""~myNodeName~"/bombable/attributes/vulnerabilities/fireDamageRate_percentpersecond");
damageRate_percentpersecond = attributes[myNodeName].vulnerabilities.fireDamageRate_percentpersecond;
if (damageRate_percentpersecond==nil) damageRate_percentpersecond=0;
if (damageRate_percentpersecond==0) damageRate_percentpersecond=0.1;
# The object is burning, so we regularly add damage.
# Have to do it differently if it is the main aircraft ("")
if (myNodeName=="") {
mainAC_add_damage( damageRate_percentpersecond/100 * fireLoopUpdateTime_sec,0, "fire", "Fire damage!" );
}
#we don't add damage to multiplayer--we let the remote object do it & send
# it back to us
else {
if (type != "multiplayer") add_damage( damageRate_percentpersecond/100 * fireLoopUpdateTime_sec , myNodeName,"nonweapon" );
}
}
}
##########################################################
#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 = attributes[myNodeName].vulnerabilities;
startFire( myNodeName ); #if it wasn't on fire before it is now
#debprint ("Bombable: setprop 4256");
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, "nonweapon"); #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 ("/bombable/fire-particles/smoke-startsize", smokeStartsize);
setprop ("/bombable/fire-particles/smoke-startsize-small", smokeStartsize * (rand()/2 + 0.5));
setprop ("/bombable/fire-particles/smoke-startsize-very-small", smokeStartsize * (rand()/8 + 0.2));
setprop ("/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() );
}
}
var addAltitude_ft = func (myNodeName, altAdd_ft=40 , time=1 ) {
var loopTime=0.033;
elapsed = getprop(""~myNodeName~"/position/addAltitude_elapsed");
if (elapsed==nil) elapsed=0;
elapsed+= loopTime;
#debprint ("Bombable: setprop 4257");
setprop(""~myNodeName~"/position/addAltitude_elapsed", elapsed);
currAlt_ft = getprop (""~myNodeName~"/position/altitude-ft");
#if (elapsed==0) setprop (""~myNodeName~"/position/addAltitude_starting_alt_ft", currAlt_ft )
#else var startAlt_ft=getprop (""~myNodeName~"/position/addAltitude_starting_alt_ft");
#debprint ("Bombable: setprop 1284");
setprop (""~myNodeName~"/position/altitude-ft", currAlt_ft+altAdd_ft*loopTime/time);
#debprint ("Bombable: setprop 1287");
if (elapsed < time) settimer (func { addAltitude_ft (myNodeName,altAdd_ft,time)}, loopTime);
else setprop(""~myNodeName~"/position/addAltitude_elapsed", 0 );
}
######################################
# FUNCTION setVerticalSpeed
# Changes to the new target vert speed but gradually over a few steps
# using settimer
#
var setVerticalSpeed = func (myNodeName, targetVertSpeed_fps=70, maxChange_fps=25, iterations=4, time=.05, targetAirSpeed_kt=0, maxChangeAirSpeed_kt=0) {
#give the vertical speed a boost
var curr_vertical_speed_fps=getprop (""~myNodeName~"/velocities/vertical-speed-fps");
var new_vertical_speed_fps=checkRange (targetVertSpeed_fps, curr_vertical_speed_fps-maxChange_fps, curr_vertical_speed_fps+maxChange_fps, targetVertSpeed_fps);
setprop (""~myNodeName~"/velocities/vertical-speed-fps", new_vertical_speed_fps);
# now do the same to the airspeed
if (targetAirSpeed_kt > 0 ) {
var curr_airspeed_kt=getprop (""~myNodeName~"/velocities/true-airspeed-kt");
var new_airspeed_kt=checkRange (targetAirSpeed_kt, curr_airspeed_kt, curr_airspeed_kt+maxChangeAirSpeed_kt, targetAirSpeed_kt);
setprop (""~myNodeName~"/velocities/true-airspeed-kt", new_airspeed_kt);
}
iterations -=1;
if (iterations>-0) {
settimer (func {
setVerticalSpeed (myNodeName, targetVertSpeed_fps, maxChange_fps, iterations, time);
} , time);
}
}
###################################################
#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
# TODO: This is one of the biggest framerate sucks in Bombable. It can probably
# be optimized in many ways.
var ground_loop = func( id, myNodeName ) {
var loopid = getprop(""~myNodeName~"/bombable/loopids/ground-loopid");
id == loopid or return;
#var b = props.globals.getNode (""~myNodeName~"/bombable/attributes");
var updateTime_s=attributes[myNodeName].updateTime_s;
#reset the timer loop first so we don't lose it entirely in case of a runtime
# error or such
# add rand() so that all objects don't do this function simultaneously
settimer(func { ground_loop(id, myNodeName)}, (0.5 + rand())*updateTime_s );
#Allow this function to be disabled via menu/it can kill framerate at times
if (! getprop ( bomb_menu_pp~"ai-ground-loop-enabled") or ! getprop(bomb_menu_pp~"bombable-enabled") ) return;
#debprint ("ground_loop starting");
node= props.globals.getNode(myNodeName);
type=node.getName();
#var alts = b.getNode("altitudes").getValues();
#var dims = b.getNode("dimensions").getValues();
#var vels = b.getNode("velocities").getValues();
var alts = attributes[myNodeName].altitudes;
var dims = attributes[myNodeName].dimensions;
var vels = attributes[myNodeName].velocities;
var onGround= getprop (""~myNodeName~"/bombable/on-ground");
if (onGround==nil) onGround=0;
# If you get too close in to the object, FG detects the elevation of the top of the object itself
# rather than the underlying ground elevation. So we go an extra FGAltObjectPerimeterBuffer_m
#meters out from the object
# just to be safe. Otherwise objects climb indefinitely, always trying to get on top of themselves
# Sometimes needed in _m, sometimes _ft, so we need both . . .
var FGAltObjectPerimeterBuffer_m = 2.5;
var FGAltObjectPerimeterBuffer_ft = FGAltObjectPerimeterBuffer_m/feet2meters;
var thorough = rand()<1/5; # to save FR we only do it thoroughly sometimes
if (onGround) thorough=0; #never need thorough when crashed
#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;
if (lat==nil) {
lat=0;
debprint ("Bombable: Lat=NIL, ground_loop ", myNodeName);
}
if (lon==nil) {
lon=0;
debprint ("Bombable: Lon=NIL, ground_loop ", myNodeName);
}
#calculate the altitude behind & ahead of the object, this determines the pitch angle and helps determine the overall ground level at this spot
#Go that extra amount, FGAltObjectPerimeterBuffer_m, out from the actual length to keep FG from detecting the top of the
#object as the altitude. We need ground altitude here.
# You can't just ask for elev at the object's current position or you'll get
# the elev at the top of the object itself, not the ground . . .
var GeoCoord = geo.Coord.new();
GeoCoord.set_latlon(lat, lon);
#debprint ("Bombable: GeoCoord.apply_course_distance(heading, dims.length_m/2); ",heading, " ", dims.length_m/2 );
GeoCoord.apply_course_distance(heading, dims.length_m/2 + FGAltObjectPerimeterBuffer_m); #frontreardist in meters
toFrontAlt_ft=elev (GeoCoord.lat(), GeoCoord.lon() ); #in feet
#This loop is one of our biggest framerate sucks and so if we're an undamaged
# aircraft way above our minimum AGL we're just going to skip it entirely.
if (type=="aircraft" and damageValue < 0.95 and (currAlt_ft - toFrontAlt_ft) > 3* alts.minimumAGL_ft) return;
if (thorough) {
GeoCoord.apply_course_distance(heading+180, dims.length_m + 2 * FGAltObjectPerimeterBuffer_m );
toRearAlt_ft=elev (GeoCoord.lat(), GeoCoord.lon() ); #in feet
} else {
toRearAlt_ft=toFrontAlt_ft;
}
#debprint ("oFront:", toFrontAlt_ft);
if (type=="aircraft" and ! onGround ) {
#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
#debprint ("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 target
# 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;
#debprint ("tofrontalt ft: ", toFrontAlt_ft, " radaraheadalt ", radarAheadAlt_ft);
# Use the radar lookahead altitude if
# 1. higher than elevation of current location
# 2. not damaged
# 3. we'll end up below our minimumAGL if we continue at
# 4. current altitude
if ( radarAheadAlt_ft > toFrontAlt_ft and (damageValue < 0.8 )
and (radarAheadAlt_ft + alts.minimumAGL_ft > currAlt_ft ) )
lookingAheadAlt_ft = radarAheadAlt_ft;
#if we're low to the ground we add this extra 500 ft just to be safe
if (currAlt_ft-radarAheadAlt_ft < 500)
lookingAheadAlt_ft +=500;
} else {
lookingAheadAlt_ft =toFrontAlt_ft;
}
# if it's damaged we always get the pitch angle etc as that is how we force it down.
# but if it's on the ground, we don't care and all these geo.Coords & elevs really kill FR.
if (thorough or ( damageValue > 0.8 and ! onGround ) ) {
pitchangle1_deg = rad2degrees * math.atan2(toFrontAlt_ft - toRearAlt_ft, dims.length_ft + 2* FGAltObjectPerimeterBuffer_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);
#go that extra amount out from the actual width to keep FG from detecting the top of the
#object as the altitude. We need ground altitude here. FGAltObjectPerimeterBuffer_m
GeoCoord2.apply_course_distance(heading+90, dims.width_m/2 + FGAltObjectPerimeterBuffer_m); #sidedist in meters
toRightAlt_ft=elev (GeoCoord2.lat(), GeoCoord2.lon() ); #in feet
GeoCoord2.apply_course_distance(heading-90, dims.width_m + 2*FGAltObjectPerimeterBuffer_m );
toLeftAlt_ft=elev (GeoCoord2.lat(), GeoCoord2.lon() ); #in feet
rollangle_deg = 90 - rad2degrees * math.atan2(dims.width_ft + 2 * FGAltObjectPerimeterBuffer_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
} else {
alt_ft = toFrontAlt_ft;
toLeftAlt_ft = toFrontAlt_ft;
toRightAlt_ft = toFrontAlt_ft;
}
#The first time this is called just initializes all the altitudes and exit
if ( alts.initialized != 1 ) {
var initial_altitude_ft= getprop (""~myNodeName~"/position/altitude-ft");
if (initial_altitude_ft<alt_ft + alts.wheelsOnGroundAGL_ft + alts.minimumAGL_ft) {
initial_altitude_ft = alt_ft + alts.wheelsOnGroundAGL_ft + alts.minimumAGL_ft;
}
if (initial_altitude_ft>alt_ft + alts.wheelsOnGroundAGL_ft + alts.maximumAGL_ft) {
initial_altitude_ft = alt_ft + alts.wheelsOnGroundAGL_ft + alts.maximumAGL_ft;
}
target_alt_AGL_ft=initial_altitude_ft - alt_ft - alts.wheelsOnGroundAGL_ft;
debprint ("Bombable: Initial Altitude: "~ initial_altitude_ft~ " target AGL: "~target_alt_AGL_ft~ " object="~ myNodeName);
debprint ("Bombable: ", alt_ft, " ", toRightAlt_ft, " ",toLeftAlt_ft, " ",toFrontAlt_ft," ", toLeftAlt_ft, " ", alts.wheelsOnGroundAGL_ft);
#debprint ("Bombable: setprop 1430");
setprop (""~myNodeName~"/position/altitude-ft", initial_altitude_ft );
setprop (""~myNodeName~"/controls/flight/target-alt", initial_altitude_ft);
#debprint ("1349 ", getprop (""~myNodeName~"/controls/flight/target-alt")) ;
#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
#var b = props.globals.getNode (""~myNodeName~"/bombable/attributes");
#b.getNode("altitudes/targetAGL_ft", 1).setDoubleValue(target_alt_AGL_ft);
#b.getNode("altitudes/targetAGL_m", 1).setDoubleValue(target_alt_AGL_ft*feet2meters);
#b.getNode("altitudes/initialized", 1).setBoolValue(1);
alts.targetAGL_ft=target_alt_AGL_ft;
alts.targetAGL_m=target_alt_AGL_ft*feet2meters;
alts.initialized=1;
return;
}
var objectsLowestAllowedAlt_ft =alt_ft + alts.wheelsOnGroundAGL_ft + alts.crashedAGL_ft;
#debprint (" objectsLowestAllowedAlt_ft=", objectsLowestAllowedAlt_ft);
if (onGround){
#go to object's resting altitude
#debprint ("Bombable: setprop 1457");
setprop (""~myNodeName~"/position/altitude-ft", objectsLowestAllowedAlt_ft );
setprop (""~myNodeName~"/controls/flight/target-alt", objectsLowestAllowedAlt_ft);
#debprint ("1373 ", getprop (""~myNodeName~"/controls/flight/target-alt")) ;
#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 . . .
#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;
#debprint ("laa ", lookingAheadAlt_ft, " tagl ", alts.targetAGL_ft, " awog ", 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;
#debprint ("shouldBeAlt ", shouldBeAlt);
#debprint ( "alt=", alt_ft, " currAlt_ft=",currAlt_ft, " fulldamagealtadd", fullDamageAltAdd_ft," damagealtaddmax", damageAltAddMax_ft, " damagevalue", damageValue," ", myNodeName );
#debprint ("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/feet2meters);
#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;
}
#debprint ( " damageAltAddMax=", damageAltAddMax, " damageAltMaxRate=",
#debprint ("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 (type=="aircraft" and (
(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 * knots2fps * 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;
#vert-speed prob
if ( type != "aircraft" ) 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
#debprint ("speed-based pitchangle=", pitchangle2_deg, " hor=", horizontalDistance_ft, " dalt=", deltaAlt_ft);
}
#don't set pitch/roll for aircraft
#debprint ("Bombable: setprop 4261");
if (type != "aircraft" and thorough ) {
#debprint ("Bombable: Setting roll-deg for ", myNodeName , " to ", rollangle_deg, " 1610");
setprop (""~myNodeName~"/orientation/roll-deg", rollangle_deg );
setprop (""~myNodeName~"/controls/flight/target-roll", rollangle_deg);
#if (!noPitch) { #noPitch only applies to aircraft, not sure how it ever got here . . .
# As of FG 2.4.0 FG doesn't let us change AI object pitch so all this code is a bit useless . . .
setprop (""~myNodeName~"/orientation/pitch-deg", pitchangle_deg );
setprop (""~myNodeName~"/controls/flight/target-pitch", pitchangle_deg);
#}
}
#if (crashing) debprint ("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;
newTgtAlt_ft = targetAlt_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 ) {
#debprint ("Bombable: setprop 1625");
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
#debprint ("1536 ", newTgtAlt_ft);
#debprint ("1536 ", getprop (""~myNodeName~"/controls/flight/target-alt")) ;
#debprint ("Bombable: ", alt_ft, " ", toRightAlt_ft, " ",toLeftAlt_ft, " ",toFrontAlt_ft," ", toLeftAlt_ft, " ", alts.wheelsOnGroundAGL_ft);
}
#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);
if (calcAlt_ft< objectsLowestAllowedAlt_ft) calcAlt_ft=objectsLowestAllowedAlt_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).
#debprint ("type=", type);
if (type != "aircraft") {
#debprint ("Bombable: setprop 1652");
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 ) ) {
#debprint ("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
#vert-speed prob
setprop (""~myNodeName~"/orientation/pitch-deg", 30 );
#debprint ("Bombable: setprop 1668");
setprop (""~myNodeName~"/controls/flight/target-pitch", 30);
if (currAlt_ft < toFrontAlt_ft + 25 ) { #dramatic correction
debprint ("Bombable: Avoiding ground collision, "~ myNodeName);
#addAltitude_ft is experimental/not quite working yet
#addAltitude_ft (myNodeName, toFrontAlt_ft + 40-currAlt_ft, updateTime_s );
#debprint ("Bombable: setprop 1676");
setprop (""~myNodeName~"/position/altitude-ft", toFrontAlt_ft + 40 );
setprop (""~myNodeName~"/controls/flight/target-alt", toFrontAlt_ft + 40);
#debprint ("1749 ", getprop (""~myNodeName~"/controls/flight/target-alt")) ;
#vert-speed prob
# 250 fps is achieved by a Zero in a normal barrel roll, so 300 fps is
# a pretty extreme/edge of reality maneuver for most aircraft
#
# We are trying to set the vert spd to 300 fps but do it in
# increments of 70 fps at most to try to maintain realism
setVerticalSpeed (myNodeName, 300, 75, 4, .1, 80, 35);
#debprint ("1557 vertspeed ", getprop (""~myNodeName~"/controls/flight/target-alt")) ;
} else { #more minor correction
#debprint ("Correcting course to avoid ground, ", myNodeName);
#setprop (""~myNodeName~"/position/altitude-ft", currAlt_ft + 20 );
#addAltitude_ft is experimental/not quite working yet
#addAltitude_ft (myNodeName, toFrontAlt_ft + 20 - currAlt_ft, updateTime_s );
#debprint ("Bombable: setprop 1691");
setprop (""~myNodeName~"/controls/flight/target-alt", currAlt_ft + 20);
#debprint ("1767 ", getprop (""~myNodeName~"/controls/flight/target-alt")) ;
#vert-speed prob
# 250 fps is achieved by a Zero in a normal barrel roll, so 70 fps is
# a very hard pull back on the stick in most aircraft, but not utterly
# impossible-looking.
#
setVerticalSpeed (myNodeName, 100, 45, 4, .2, 70, 35);
}
}
if ( type == "aircraft" and (damageValue > 0.8 )) {
#debprint ("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
#debprint ("damageAltAddCurrent=", damageAltAddCurrent);
#debprint ("Bombable: setprop 1720");
setprop (""~myNodeName~"/controls/flight/target-alt", currAlt_ft -500);
#debprint ("1610 ", getprop (""~myNodeName~"/controls/flight/target-alt")) ;
setprop (""~myNodeName~"/controls/flight/target-pitch", -45);
#vert-speed prob
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 ) );
#debprint ("damageAltAddCurrent=", damageAltAddCurrent);
#not that nice
#debprint ("Bombable: setprop 1737");
setprop (""~myNodeName~"/controls/flight/target-alt", currAlt_ft -10000);
#debprint ("1625 ", getprop (""~myNodeName~"/controls/flight/target-alt")) ;
setprop (""~myNodeName~"/controls/flight/target-pitch", -70);
var orientPitch_deg=getprop (""~myNodeName~"/orientation/pitch-deg");
#vert-speed prob
if (orientPitch_deg > -20) setprop (""~myNodeName~"/orientation/pitch-deg", orientPitch_deg - 1 );
dodge (myNodeName); #it will roll/dodge as though under fire
} 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);
debprint ("Bombable: Aircraft hit ground, it's dead. 1851.");
}
#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 explode & stuff.
aircraftAlt_ft = getprop (""~myNodeName~"/position/altitude-ft" );
if ( aircraftAlt_ft < alt_ft - 5 ) {
debprint ("Bombable: Aircraft hit ground, it's dead. 1863.");
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 the ground, but they are not allowed
# to skid along UNDER the ground . . .
if (currAlt_ft < objectsLowestAllowedAlt_ft)
{
#debprint ("Bombable: setprop 1775");
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);
#debprint ("alt = ", alt, " currAlt_ft = ", currAlt_ft, " deltaAlt= ", deltaAlt, " altAdjust= ", alts.wheelsOnGroundAGL_ft, " calcAlt_ft=", calcAlt_ft, "damageAltAddCurrent=", damageAltAddCurrent, " ", myNodeName);
}
#######################################################
#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/loopids/location-loopid");
id == loopid or return;
#debprint ("location_loop starting");
# 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() );
#get out of here if Bombable is disabled
if (! getprop(bomb_menu_pp~"bombable-enabled") ) 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");
if (lat==nil) {
lat=0;
debprint ("Bombable: Lat=NIL, location_loop", myNodeName);
}
if (lon==nil) {
lon=0;
debprint ("Bombable: Lon=NIL, location_loop", myNodeName);
}
#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 * feet2meters);
var GeoCoordprev = geo.Coord.new();
GeoCoordprev.set_latlon(prevlat, prevlon, prevalt_ft * feet2meters);
var directDistance = GeoCoord.distance_to(GeoCoordprev);
#debprint ("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;
debprint ("Bombable: 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);
}
#################################################################
# This is the old way of calculating the closest impact distance
# This approach uses more of the geo.Coord functions from geo.nas
# The other approach is more vector based and uses a local XYZ
# coordinate system based on lat/lon/altitude.
# I'm not sure which is the most accurate but I believe this one is slower,
# with multiple geo.Coord calls plus some trig.
var altClosestApproachCalc = func {
# figure how close the impact and terrain it's on
var objectGeoCoord = geo.Coord.new();
objectGeoCoord.set_latlon(oLat_deg,oLon_deg,oAlt_m );
var impactGeoCoord = geo.Coord.new();
impactGeoCoord.set_latlon(iLat_deg, iLon_deg, iAlt_m);
#impact point as though at the same altitude as the object - for figuring impact distance on the XY plane
var impactSameAltGeoCoord = geo.Coord.new();
impactSameAltGeoCoord.set_latlon(iLat_deg, iLon_deg, oAlt_m);
var impactDistanceXY_m = objectGeoCoord.direct_distance_to(impactSameAltGeoCoord);
if (impactDistanceXY_m >200 ) {
#debprint ("Not close in surface distance. ", impactDistanceXY_m);
#return;
}
var impactDistance_m = objectGeoCoord.direct_distance_to(impactGeoCoord);
#debprint ("impactDistance ", impactDistance_m);
var impactHeadingDelta_deg=math.abs ( impactGeoCoord.course_to(objectGeoCoord) - impactorHeading_deg );
#the pitch angle from the impactor to the main object
var impact2ObjectPitch_deg = rad2degrees * math.asin ( deltaAlt_m/impactDistance_m);
var impactPitchDelta_deg = impactorPitch_deg - impact2ObjectPitch_deg;
#Closest approach of the impactor to the center of the object along the direction of pitch
var closestApproachPitch_m = impactDistance_m * math.sin (impactPitchDelta_deg /rad2degrees);
# This formula calcs the closest distance the object would have passed from the exact center of the target object, where 0 = a direct hit through the center of the object; on the XY plane
var closestApproachXY_m = math.sin (impactHeadingDelta_deg/rad2degrees) * impactDistanceXY_m * math.cos (impactPitchDelta_deg /rad2degrees);;
#combine closest approach in XY and closest approach along the pitch angle to get the
# overall point of closest approach
var closestApproachOLDWAY_m = math.sqrt (
closestApproachXY_m * closestApproachXY_m +
closestApproachPitch_m * closestApproachPitch_m);
#debprint ("Bombable: Projected closest impact distance : ", closestApproachOLDWAY_m, "FG Impact Detection Point: ", impactDistance_m, " XY: ", closestApproachXY_m, " Pitch: ", closestApproachPitch_m, " impactDistance_m=",impactDistance_m, " impactDistanceXY_m=",impactDistanceXY_m, " ballisticMass_lb=", ballisticMass_lb);
if (impactDistance_m<closestApproach_m) debprint ("#########CLOSEST APPROACH CALC ERROR########");
}
########################################
# put_splash puts the impact splash from test_impact
#
var put_splash = func (nodeName, iLat_deg,iLon_deg, iAlt_m, ballisticMass_lb, impactTerrain="terrain", refinedSplash=0, myNodeName="" ){
#This check to avoid duplicate splashes is not quite working in some cases
# perhaps because the lat is repeating exactly for different impacts, or
# because some weapon impacts and collisions are reported a little differently?
var impactSplashPlaced = getprop (""~nodeName~"/impact/bombable-impact-splash-placed");
var impactObjectLat_deg= getprop (""~nodeName~"/impact/latitude-deg");
if ((impactSplashPlaced==nil or impactSplashPlaced!=impactObjectLat_deg) and iLat_deg!=nil and iLon_deg!=nil and iAlt_m!=nil){
records.record_impact ( myNodeName: myNodeName, damageRise:0, damageIncrease:0, damageValue:0, impactNodeName: nodeName, ballisticMass_lb: ballisticMass_lb, lat_deg: iLat_deg, lon_deg: iLon_deg, alt_m: iAlt_m );
if (ballisticMass_lb<1.2) {
var startSize_m=0.25 + ballisticMass_lb/3;
var endSize_m= 1 + ballisticMass_lb;
} else {
var startSize_m=0.25 + ballisticMass_lb/1000;
var endSize_m= 2 + ballisticMass_lb/4;
}
impLength_sec=0.75+ ballisticMass_lb/1.2;
if (impLength_sec>20) impLength_sec=20;
#The idea is that if the impact hits earth it throws up a bunch of
#dirt & dust & stuff for a longer time. But only for smaller/projectile
#weapons where the dirt/dust is the main visual.
# Based on observing actual weapons impacts on Youtube etc.
#
if (impactTerrain=="terrain" and ballisticMass_lb<=1.2) {
endSize_m *= 5;
impLength_sec *= 5;
}
#debprint ("Bombable: Drawing impact, ", nodeName, " ", iLat_deg, " ", iLon_deg, " ", iAlt_m, " refined:", refinedSplash );
put_remove_model(iLat_deg,iLon_deg, iAlt_m, impLength_sec, startSize_m, endSize_m);
#for larger explosives (or a slight chance with smaller rounds, which
# all have some incindiary content) start a fire
if (ballisticMass_lb>1.2 or
(ballisticMass_lb <=1.2 and rand()<ballisticMass_lb/10) ) settimer ( func {start_terrain_fire( iLat_deg,iLon_deg,iAlt_m, ballisticMass_lb )}, impLength_sec/1.5);
setprop (""~nodeName~"/impact/bombable-impact-splash-placed", impactObjectLat_deg);
}
if (refinedSplash)
setprop (""~nodeName~"/impact/bombable-impact-refined-splash-placed", impactObjectLat_deg);
}
########################################
# exit_test_impact(nodeName)
# draws the impact splash for the nodeName
#
var exit_test_impact= func(nodeName, myNodeName){
#if impact on a ship etc we're assuming that one of the other test_impact
# instances will pick it up & we don't need to worry about it.
var impactTerrain = getprop(""~nodeName~"/impact/type");
if (impactTerrain!="terrain") {
#debprint ("Bombable: Not drawing impact; object impact");
return;
}
var iLat_deg=getprop(""~nodeName~"/impact/latitude-deg");
var iLon_deg=getprop(""~nodeName~"/impact/longitude-deg");
var iAlt_m=getprop(""~nodeName~"/impact/elevation-m");
var ballisticMass_lb= getBallisticMass_lb(nodeName);
#debprint ("Bombable: Exiting test_impact with a splash, ", nodeName, " ", ballisticMass_lb, " ", impactTerrain," ", iLat_deg, " ", iLon_deg, " ", iAlt_m);
put_splash (nodeName, iLat_deg, iLon_deg, iAlt_m, ballisticMass_lb, impactTerrain, 0, myNodeName );
}
var getBallisticMass_lb = func (impactNodeName) {
#weight/mass of the ballistic object, in lbs
#var ballisticMass_lb = impactNode.getNode("mass-slug").getValue() * 32.174049;
var ballisticMass_lb=0;
var ballisticMass_slug = getprop (""~impactNodeName~"/mass-slug");
#ok, FG 2.4.0 leaves out the /mass-slug property, so we have to improvise.
# We basically need to list or guess the mass of each & every type of ordinance
# that might exist or be used. Not good.
if (ballisticMass_slug != nil ) ballisticMass_lb = ballisticMass_slug * 32.174049
else {
ballisticMass_lb=.25;
var impactType = getprop (""~impactNodeName~"/name");
#debprint ("Bombable: ImpactNodeType = ", impactType);
if (impactType==nil) impactType="bullet";
#we start with specific & end with generic, so the specific info will take
# precedence (if we have it)
if (find ("MK-81", impactType ) != -1 ) ballisticMass_lb=250;
elsif (find ("MK-82", impactType ) != -1 ) ballisticMass_lb=500;
elsif (find ("MK82", impactType ) != -1 ) ballisticMass_lb=500;
elsif (find ("MK-83", impactType ) != -1 ) ballisticMass_lb=1000;
elsif (find ("MK-84", impactType ) != -1 ) ballisticMass_lb=2000;
elsif (find ("25 pound", impactType ) != -1 ) ballisticMass_lb=25;
elsif (find ("5 pound", impactType ) != -1 ) ballisticMass_lb=5;
elsif (find ("100 pound", impactType ) != -1 ) ballisticMass_lb=100;
elsif (find ("150 pound", impactType ) != -1 ) ballisticMass_lb=150;
elsif (find ("250 pound", impactType ) != -1 ) ballisticMass_lb=250;
elsif (find ("500 pound", impactType ) != -1 ) ballisticMass_lb=500;
elsif (find ("1000 pound", impactType ) != -1 ) ballisticMass_lb=1000;
elsif (find ("2000 pound", impactType ) != -1 ) ballisticMass_lb=2000;
elsif (find ("aim-9", impactType ) != -1 ) ballisticMass_lb=20.8;
elsif (find ("AIM", impactType ) != -1 ) ballisticMass_lb=20.8;
elsif (find ("WP-1", impactType ) != -1 ) ballisticMass_lb=23.9;
elsif (find ("GAU-8", impactType ) != -1 ) ballisticMass_lb=0.9369635;
elsif (find ("M-61", impactType ) != -1 ) ballisticMass_lb=0.2249;
elsif (find ("M61", impactType ) != -1 ) ballisticMass_lb=0.2249;
elsif (find ("LAU", impactType ) != -1 ) ballisticMass_lb=86; #http://www.dtic.mil/dticasd/sbir/sbir041/srch/af276.pdf
elsif (find ("smoke", impactType ) != -1 ) ballisticMass_lb=0.0;
elsif (find (".50 BMG", impactType ) != -1 ) ballisticMass_lb=0.130072735;
elsif (find (".50", impactType ) != -1 ) ballisticMass_lb=0.130072735;
elsif (find ("303", impactType ) != -1 ) ballisticMass_lb=0.0264554715; #http://en.wikipedia.org/wiki/Vickers_machine_gun
elsif (find ("gun", impactType ) != -1 ) ballisticMass_lb=.025;
elsif (find ("bullet", impactType) != -1 ) ballisticMass_lb=0.0249122356;
elsif (find ("tracer", impactType) != -1 ) ballisticMass_lb=0.0249122356;
elsif (find ("round", impactType) != -1 ) ballisticMass_lb=0.9369635;
elsif (find ("cannon", impactType ) != -1 ) ballisticMass_lb=0.282191696;
elsif (find ("bomb", impactType ) != -1 ) ballisticMass_lb=250;
elsif (find ("heavy-bomb", impactType ) != -1 ) ballisticMass_lb=750;
elsif (find ("rocket", impactType ) != -1 ) ballisticMass_lb=50;
elsif (find ("missile", impactType ) != -1 ) ballisticMass_lb=185;
}
return ballisticMass_lb;
}
var getImpactVelocity_mps = func (impactNodeName=nil,ballisticMass_lb=.25) {
var impactVelocity_mps = getprop (""~impactNodeName~"/impact/speed-mps");
#if perchance impact velocity isn't available we'll estimate it from
# projectile size
# These are rough approximations/guesses based on http://en.wikipedia.org/wiki/Muzzle_velocity
if (impactVelocity_mps == nil or impactVelocity_mps== 0) {
if (ballisticMass_lb < 0.1) impactVelocity_mps= 1200;
elsif (ballisticMass_lb < 0.5) impactVelocity_mps= 900;
elsif (ballisticMass_lb < 2) impactVelocity_mps= 500;
elsif (ballisticMass_lb < 50) impactVelocity_mps= 250;
elsif (ballisticMass_lb < 500) impactVelocity_mps= 150;
elsif (ballisticMass_lb < 2000) impactVelocity_mps= 125;
else impactVelocity_mps= 100;
}
return impactVelocity_mps;
}
########################################
# cartesianDistance (x,y,z, . . . )
# returns the cartesian distance of any number of elements
var cartesianDistance = func (elem...){
var dist=0;
foreach (e; elem ) dist+=e*e;
return math.sqrt(dist);
}
################################################
#FUNCTION test_impact
#
#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.
# FG uses a very basic collision detection algorithm that assumes a standard
# height and length for each type of AI object. These are actually 'radius'
# type measurements--ie for the 2nd object, if the ballistic obj strikes 50 ft
#above OR 50 ft below, and within a circle of radius 100 ft of the lat/lon,
#then we get a hit. From the C code:
# // we specify tgt extent (ft) according to the AIObject type
# double tgt_ht[] = {0, 50, 100, 250, 0, 100, 0, 0, 50, 50, 20, 100, 50};
# double tgt_length[] = {0, 100, 200, 750, 0, 50, 0, 0, 200, 100, 40, 200, 100};
# http://gitorious.org/fg/flightgear/blobs/next/src/AIModel/AIManager.cxx
# In order, those are:
# enum object_type { otNull = 0, otAircraft, otShip, otCarrier, otBallistic,
# otRocket, otStorm, otThermal, otStatic, otWingman, otGroundVehicle,
# otEscort, otMultiplayer,
# MAX_OBJECTS };
# http://gitorious.org/fg/flightgear/blobs/next/src/AIModel/AIBase.hxx
#So Aircraft is assumed to be 50 feet high, 100 ft long; multiplayer the same, etc.
#That is where any ballistic objects are detected and stopped by FG.
#
# A main point of the function below is to improve on this impact detection
# by projecting the point of closest approach of the impactor, then assigning
# a damage value based on that.
#
var test_impact = func(changedNode, myNodeName) {
#Allow this function to be disabled via bombable menu
if ( ! getprop(bomb_menu_pp~"bombable-enabled") ) return;
var impactNodeName = changedNode.getValue();
#var impactNode = props.globals.getNode(impactNodeName);
debprint ("Bombable: test_impact, ", myNodeName," ", impactNodeName);
var oLat_deg=getprop (""~myNodeName~"/position/latitude-deg");
var iLat_deg=getprop (""~impactNodeName~"/impact/latitude-deg");
debprint ("Bombable: test_impact oLat, iLat: ", oLat_deg, " ", iLat_deg );
# bhugh, 3/28/2013, not sure why this error is happening sometimes in 2.10:
# Nasal runtime error: No such member: maxLat
# at E:/FlightGear 2.10.0/FlightGear/data/Nasal/bombable.nas, line 3405
# called from: E:/FlightGear 2.10.0/FlightGear/data/Nasal/bombable.nas, line 8350
# called from: E:/FlightGear 2.10.0/FlightGear/data/Nasal/globals.nas, line 100
#debug.dump (attributes[myNodeName].dimensions);
var maxLat_deg = attributes[myNodeName].dimensions['maxLat'];
var maxLon_deg = attributes[myNodeName].dimensions['maxLon'];
#attributes[myNodeName].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
#
#
var deltaLat_deg=(oLat_deg-iLat_deg);
if (abs(deltaLat_deg) > maxLat_deg * 1.5 ) {
#debprint ("Not close in lat. ", deltaLat_deg);
exit_test_impact(impactNodeName, myNodeName);
return;
}
var oLon_deg= getprop (""~myNodeName~"/position/longitude-deg");
var iLon_deg= getprop (""~impactNodeName~"/impact/longitude-deg");
var deltaLon_deg=(oLon_deg-iLon_deg);
if (abs(deltaLon_deg) > maxLon_deg * 1.5 ) {
#debprint ("Not close in lon. ", deltaLon_deg);
exit_test_impact(impactNodeName, myNodeName);
return;
}
var oAlt_m= getprop (""~myNodeName~"/position/altitude-ft")*feet2meters;
var iAlt_m= getprop (""~impactNodeName~"/impact/elevation-m");
var deltaAlt_m = (oAlt_m-iAlt_m);
if (abs(deltaAlt_m) > 300 ) {
#debprint ("Not close in Alt. ", deltaAlt);
exit_test_impact(impactNodeName, myNodeName);
return;
}
#debprint ("Impactor: ", impactNodeName, ", Object: ", myNodeName);
if (impactNodeName=="" or impactNodeName==nil) {
#debprint ("impactNode doesn't seem to exist, exiting");
return;
}
#Since FG kindly intercepts collisions along a fairly large target cylinder surrounding the
# object, we simply project the last known heading of the ballistic object along
# its path to determine how close to the center of the object it would have struck,
# if it continued along its present heading in a straight line.
# we do this for both terrain & ship/aircraft hits, because if the aircraft or ship is on
# or very close to the ground, FG often lets the ai submodel go 'right through' the main
# object and the only impact detected is with the ground. This gets worse as the framerate
# gets slow, because FG can only check for impacts at each frame - so with a projectile
# going 1000 MPS and framerate of 10, that is only once every hundred meters.
# Formula here:
# http://mathforum.org/library/drmath/view/54731.html (a more vector-based
# approach).
#
# ft_per_deg_lat = 366468.96 - 3717.12 * cos(pos.getLatitudeRad());
# ft_per_deg_lon = 365228.16 * cos(pos.getLatitudeRad());
# per FG c code, http://gitorious.org/fg/flightgear/blobs/next/src/AIModel/AIBase.cxx line 178
# We could speed this up by leaving out the cos term in deg_lat and/or calculating these
# occasionally as the main A/C flies around and storing them (they dont change that)
# much from one mile to the next)
#var iLat_rad=iLat_deg/rad2degrees;
#m_per_deg_lat= 111699.7 - 1132.978 * math.cos (iLat_rad);
#m_per_deg_lon= 111321.5 * math.cos (iLat_rad);
#m_per_deg_lat=getprop ("/bombable/sharedconstants/m_per_deg_lat");
#m_per_deg_lon=getprop ("/bombable/sharedconstants/m_per_deg_lon");
#the following plus deltaAlt_m make a <vector> where impactor is at <0,0,0>
# and target object is at <deltaX,deltaY,deltaAlt> in relation to it.
var deltaY_m=deltaLat_deg*m_per_deg_lat;
var deltaX_m=deltaLon_deg*m_per_deg_lon;
#calculate point & distance of closest approach.
# if the main aircraft (myNodeName=="") then we just
# use FG's impact detection point. If an AI or MP
# aircraft, we project it into actual point of closest approach.
if (myNodeName=="") {
closestApproach_m= cartesianDistance(deltaX_m,deltaY_m,deltaAlt_m );
} else {
#debprint ("MPDL:", m_per_deg_lat, " MPDLon: ", m_per_deg_lon, " dL:", deltaLat_deg, " dLon:", deltaLon_deg);
impactorHeading_deg = getprop (""~impactNodeName~"/impact/heading-deg");
#if perchance this doesn't exist we'll just randomize it; it must be -90 to 90 or it wouldn't have hit.
if (impactorHeading_deg==nil ) impactorHeading_deg=rand() * 180 - 90;
impactorPitch_deg = getprop (""~impactNodeName~"/impact/pitch-deg");
#if perchance this doesn't exist we'll just randomize it; it must be -90 to 90 or it wouldn't have hit.
if (impactorPitch_deg==nil ) impactorPitch_deg=rand() * 180 - 90;
# the following make a unit vector in the direction the impactor is moving
# this could all be saved in the prop tree so as to avoid re-calcing in
# case of repeated AI objects checking the same impactor
var impactorPitch_rad=impactorPitch_deg/rad2degrees;
var impactorHeading_rad=impactorHeading_deg/rad2degrees;
var impactordirectionZcos=math.cos(impactorPitch_rad);
var impactorDirectionX=math.sin(impactorHeading_rad) * impactordirectionZcos; #heading
var impactorDirectionY=math.cos(impactorHeading_rad) * impactordirectionZcos; #heading
var impactorDirectionZ=math.sin(impactorPitch_rad); #pitch
#now we have a simple vector algebra problem: the impactor is at <0,0,0> moving
# in the direction of the <impactorDirection> vector and the object is
# at point <deltaX,deltaY,deltaAlt>.
# So the closest approach of the line through <0,0,0> in the direction of <impactorDirection>
# to point <deltaX,deltaY,deltaAlt> is the length of the cross product vector
# <impactorDirection> X <deltaX,deltaY,deltaAlt> divided by the length of
# <impactorDirection>. We have cleverly chosen <impactDirection> so as to always
# have length one (unit vector), so we can skip that calculation.
# So the cross product vector:
var crossProdX_m=impactorDirectionY*deltaAlt_m - impactorDirectionZ*deltaY_m;
var crossProdY_m=impactorDirectionZ*deltaX_m - impactorDirectionX*deltaAlt_m;
var crossProdZ_m=impactorDirectionX*deltaY_m - impactorDirectionY*deltaX_m;
#the length of the cross-product vector divided by the length of the line/direction
# vector is the distance we want (and the line/direction vector = 1 in our
# setup:
closestApproach_m= cartesianDistance(crossProdX_m,crossProdY_m,crossProdZ_m );
#debprint( "closestApproach_m=", closestApproach_m, " impactorDirectionX=", impactorDirectionX,
#" impactorDirectionY=", impactorDirectionY,
#" impactorDirectionZ=", impactorDirectionZ,
#" crossProdX_m=", crossProdX_m,
#" crossProdY_m=", crossProdY_m,
#" crossProdZ_m=", crossProdZ_m,
#" deltaX_m=", deltaX_m,
#" deltaY_m=", deltaY_m,
#" deltaAlt_m=", deltaAlt_m,
#" impactDist (lat/long) ", cartesianDistance(deltaX_m,deltaY_m,deltaAlt_m),
#" shouldbeOne: ", cartesianDistance(impactorDirectionX,impactorDirectionY,impactorDirectionZ),
#);
#var impactSurfaceDistance_m = objectGeoCoord.distance_to(impactGeoCoord);
#var heightDifference_m=math.abs(getprop (""~impactNodeName~"/impact/elevation-m") - getprop (""~nodeName~"/impact/altitude-ft")*feet2meters);
}
var damAdd=0; #total amoung of damage actually added as the result of the impact
var impactTerrain = getprop (""~impactNodeName~"/impact/type");
#debprint ("Bombable: Possible hit - calculating . . . ", impactTerrain);
#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 = attributes[myNodeName].dimensions.damageRadius_m;
var vitalDamageRadius_m = attributes[myNodeName].dimensions.vitalDamageRadius_m;
# if it doesn't exist we assume it is 1/3 the damage radius
if (!vitalDamageRadius_m) vitalDamageRadius_m = damageRadius_m/3;
#var b = props.globals.getNode (""~myNodeName~"/bombable/attributes");
var vuls= attributes[myNodeName].vulnerabilities;
ballisticMass_lb= getBallisticMass_lb(impactNodeName);
var ballisticMass_kg=ballisticMass_lb/2.2;
#Only worry about small arms/small cannon fire if it is a direct hit on the object;
# if it hits terrain, then no damage.
if (impactTerrain == "terrain" and ballisticMass_lb<=1.2) {
#debprint ("hit on terrain & mass < 1.2 lbs, exiting ");
exit_test_impact(impactNodeName, myNodeName);
return;
}
var impactVelocity_mps = getImpactVelocity_mps (impactNodeName, ballisticMass_lb);
#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 *make
#sure* it really went down.
var easyMode=1;
var easyModeProbability=1;
if (myNodeName!="" ) {
#Easy Mode increases the damage radius (2X), making it easier to score hits,
#but doesn't increase the damage done by armament
if (getprop(""~bomb_menu_pp~"easy-mode")) {
#easyMode*=2;
damageRadius_m*=2;
vitalDamageRadius_m*=2;
}
#Super Easy mode increases both the damage radius AND the damage done
#by 3X
if (getprop(""~bomb_menu_pp~"super-easy-mode")){
easyMode*=3;
easyModeProbability*=3;
damageRadius_m*=3;
vitalDamageRadius_m*=3;
}
}
# debprint ("Bombable: Projected closest impact distance delta : ", closestApproachOLDWAY_m-closestApproach_m, "FG Impact Detection Point delta: ", impactDistance_m - cartesianDistance(deltaX_m,deltaY_m,deltaAlt_m), " ballisticMass_lb=", ballisticMass_lb);
#var tgt_ht_m=50/.3042 + 5; # AIManager.cxx it is 50 ft for aircraft & multiplayer;extra 5 m is fudge factor
#var tgt_length_m=100/.3024 + 5; # AIManager.cxx it is 100 ft for aircraft & multiplayer; extra 5 m is fudge factor
#if impactterrain is aircraft or MP and the impact is within the tgt_alt and tgt_height, we're going to assume it is a direct impact on this object.
# it would be much easier if FG would just pass us the node name of the object that has been hit,
# but lacking that vital bit of info, we do it the hard way . . .
#if(abs(iAlt_m-oAlt_m) < tgt_ht_m and impactDistanceXY_m < tgt_length_m and impactTerrain != "terrain") {
#OK, it's within the damage radius - direct hit
if (closestApproach_m < damageRadius_m) {
damagePotential=0;
outsideIDdamagePotential=0;
#Kinetic energy ranges from about 1500 joules (Vickers machine gun round) to
# 200,000 joules (GAU-8 gatling gun round .8 lbs at 1000 MPS typical impact speed)
# to 220,000 joules (GAU-8 at muzzle velocity)
# to 330,000 joules (1.2 lb projectile at 1500 MPS muzzle velocity)
# GAU-8 can penetrate an M-1 tank at impact. But even there it would take a number of rounds,
# perhaps a large number, to disable a tank reliably. So let's say 20 rounds, and
# our 100% damage amount is 20 GAU hits.
#
# Kinetic Energy (joules) = 1/2* mass * velocity^2 (mass in kg, velocity in mps)
# See http://en.wikipedia.org/wiki/Kinetic_energy
#var kineticEnergy_joules= ballisticMass_kg * impactVelocity_mps * impactVelocity_mps /2;
#According to this, weapon effectiveness isn't well correlated to kinetic energy, but
# is better estimated in proportion to momentum
# plus a factor for the chemical explosiveness of the round:
# http://eaw.wikispaces.com/Technical+Tools--Gun+Power
#
# We don't have a good way to estimate the chemical energy of particular rounds
# (though it can be looked up) but momentum is easy: mass X velocity.
#
# Momentum ranges from 500 kg * m/s for a typical Vickers machine gun round
# to 180 for a GAU-8 round at impact, 360 for GAU-8 round at muzzle, 800 for
# at 1.2 lb slug at 1500 mps
#
momentum_kgmps = ballisticMass_kg * impactVelocity_mps;
weaponDamageCapability=momentum_kgmps/(60*360);
#debprint ("mass= ", ballisticMass_lb, " vel=", impactVelocity_mps, " Ek=", kineticEnergy_joules, " damageCapability=", weaponDamageCapability);
#likelihood of damage goes up the closer we are to the center; it becomes 1 at vitalDamageRadius
if (closestApproach_m <= vitalDamageRadius_m )impactLikelihood=1;
else impactLikelihood=(damageRadius_m - closestApproach_m)/(damageRadius_m -vitalDamageRadius_m);
#It's within vitalDamageRadius, this is the core of the object--engines pilot, fuel tanks,
# etc. #So, some chance of doing high damage and near certainty of doing some damage
if (closestApproach_m <= vitalDamageRadius_m ) {
#damagePotential = (damageRadius_m - closestApproach_m)/damageRadius_m;
damagePotential = impactLikelihood * vuls.damageVulnerability / 200; #possibility of causing a high amount of damage
outsideIDdamagePotential=impactLikelihood; #possibility of causing a routine amount of damage
# debprint ("Bombable: Direct hit, "~ impactNodeName~ " on ", myNodeName, " Distance= ", closestApproach_m, " heightDiff= ", deltaAlt_m, " terrain=", impactTerrain, " radius=", damageRadius_m, " dP:", damagePotential, " oIdP:", outsideIDdamagePotential, " bM:", ballisticMass_lb);
} else {
#It's within damage radius but not vital damage Radius: VERY slim chance
# of doing serious damage, like hitting a wing fuel tank or destroying a wing strut, and
#some chance of doing routine damage
damagePotential= impactLikelihood * vuls.damageVulnerability / 2000;
#Think of a typical aircraft projected onto the 2D plane with damage radius &
# vital damage radius superimposed over them. For vital damage radius, it's right near
# the center and most of the area enclosed would be a hit.
# But for the area between vital damage radius & damage
# radius, there is much empty space--and the more so, the more outwards we go
# towards the damage radius. Squaring the oIdP takes this geometrical fact into
# account--there is more and more area the further you go out, but more and more of
# it is empty. So there is less chance (approximately proportionate to
# square of distance from center) of hitting something vital the further
# you go out.
#
outsideIDdamagePotential= math.pow (impactLikelihood, 1.5) ;# ^2 makes it a bit too difficult to get a hit/let's try ^1.5 instead
# debprint ("Bombable: Near hit, "~ impactNodeName~ " on ", myNodeName, " Distance= ", closestApproach_m, " heightDiff= ", deltaAlt_m, " terrain=", impactTerrain, " radius=", damageRadius_m, " dP ", damagePotential, " OIdP ", outsideIDdamagePotential, " vitalHitchance% ", damagePotential*vuls.damageVulnerability*easyModeProbability * ballisticMass_lb / 5);
}
var damageCaused=0;
if (ballisticMass_lb < 1.2) {
# gun/small ammo
#Guarantee of some damage, maybe big damage if it hits some vital parts
# (the 'if' is a model for the percentage chance of it hitting some vital part,
# which should happen only occasionally--and less occasionally for well-armored targets)
# it always does at least 100% of weaponDamageCapability and up to 300%
if ( rand()< damagePotential*easyModeProbability) {
damageCaused=(weaponDamageCapability + rand()*weaponDamageCapability*2)*vuls.damageVulnerability*easyMode;
#debprint ("Bombable: Direct Hit/Vital hit. ballisticMass: ", ballisticMass_lb," damPotent: ", damagePotential, " weaponDamageCapab:", weaponDamageCapability);
debprint ("Bombable: Small weapons, direct hit, very damaging");
#Otherwise the possibility of damage
} elsif (rand() < outsideIDdamagePotential) {
damageCaused=rand () * weaponDamageCapability * vuls.damageVulnerability*easyMode * outsideIDdamagePotential;
#debprint ("Bombable: Direct Hit/Nonvital hit. ballisticMass: ", ballisticMass_lb," outsideIDDamPotent: ", outsideIDdamagePotential, " weaponDamageCapab:", weaponDamageCapability );
debprint ("Bombable: Small weapons, direct hit, damaging");
}
} else {
# anything larger than 1.2 lbs making a direct hit. It's some kind of bomb or
# exploding ordinance, presumably
#debprint ("larger than 1.2 lbs, making direct hit");
var damagePoss= .6 + ballisticMass_lb/250;
if (damagePoss>1) damagePoss=1;
# if it hits a vital spot (which becomes more likely, the larger the bomb)
if ( rand()< damagePotential*vuls.damageVulnerability*easyModeProbability * ballisticMass_lb / 5 ) damageCaused=damagePoss*vuls.damageVulnerability*ballisticMass_lb*easyMode/2;
else #if it hits a regular or less vital spot
damageCaused=rand () * ballisticMass_lb * vuls.damageVulnerability*easyMode * outsideIDdamagePotential;
debprint ("Bombable: Heavy weapon or bomb, direct hit, damaging");
}
#debprint ("Bombable: Damaging hit, "~ " Distance= ", closestApproach_m, "by ", impactNodeName~ " on ", myNodeName," terrain=", impactTerrain, " damageRadius=", damageRadius_m," weaponDamageCapability ", weaponDamageCapability, " damagePotential ", damagePotential, " OIdP ", outsideIDdamagePotential, " Par damage: ", weaponDamageCapability * vuls.damageVulnerability);
damAdd=add_damage( damageCaused, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m );
#checking/setting this prevents the same splash from being repeatedly re-drawn
# as we check the impact from different AI objects
if (damageCaused>0 ) {
#places a gun flack at the hit location
if (myNodeName=="") {
#case of MainAC, we just draw the impact where FG has detected it
exit_test_impact(impactNodeName, myNodeName);
} else {
#case of AI or MP Aircraft, we draw it at point of closest impact
#Code below calculates <crossProdObj_Imp>, the vector from the
# object location to the closest approach/impact point
# Vector <crossProd> has the right magnitude but is perpendicular to the plane
# containing the impact detection point, the closest approach point, and the
# object location. Doing the cross product of <crossProd> with <impactorDirection> (which
# is a unit vector in the direction of impactor travel) gives the vector
# in the direction from object location to impact closest approach point, and (since <impactorDirection> is the unit vector and <crossProd>'s magnitude is the distance from
# the object location to the closest approach point, that vector's magnitude is the
# distance from object location to closest approach point.
#
# Between this and <impactorDirection> we have the exact location and the direction
# of the closest impact point. These two items together could be used to calculate specific damage,
# systems affected, etc., by damage coming at a specific angle in a specific area.
var crossProdObj_ImpX_m=impactorDirectionY*crossProdZ_m - impactorDirectionZ*crossProdY_m;
var crossProdObj_ImpY_m=impactorDirectionZ*crossProdX_m - impactorDirectionX*crossProdZ_m;
var crossProdObj_ImpZ_m=impactorDirectionX*crossProdY_m - impactorDirectionY*crossProdX_m;
debprint ("Bombable: Put splash direct hit");
put_splash (impactNodeName, oLat_deg+crossProdObj_ImpY_m/m_per_deg_lat, oLon_deg+crossProdObj_ImpX_m/m_per_deg_lon, oAlt_m+crossProdObj_ImpZ_m,ballisticMass_lb, impactTerrain, 1, myNodeName);
}
}
# end, case of direct hit
} else {
# case of a near hit, on terrain, if it's a bomb we'll add damage
# Some of the below is a bit forward thinking--it includes some damage elements to 1000 m
# or even more distance for very large bombs. But up above via the quick lat/long calc
# (for performance reasons) we're exiting immediately for impacts > 300 meters or so away.
#debprint ("near hit, not direct");
if (myNodeName=="") {
#case of MainAC, we just draw the impact where FG has detected it,
# not calculating any refinements, which just case problems in case of the
# mainAC, anyway.
exit_test_impact(impactNodeName, myNodeName);
} else {
#case of AI or MP aircraft, we draw the impact at point of closest approach
var impactSplashPlaced=getprop (""~impactNodeName~"/impact/bombable-impact-splash-placed");
var impactRefinedSplashPlaced=getprop (""~impactNodeName~"/impact/bombable-impact-refined-splash-placed");
#debprint("iSP=",impactSplashPlaced, " iLat=", iLat_deg);
if ( (impactSplashPlaced==nil or impactSplashPlaced!=iLat_deg)
and (impactRefinedSplashPlaced==nil or impactRefinedSplashPlaced!=iLat_deg)
and ballisticMass_lb>1.2) {
var crossProdObj_ImpX_m=impactorDirectionY*crossProdZ_m - impactorDirectionZ*crossProdY_m;
var crossProdObj_ImpY_m=impactorDirectionZ*crossProdX_m - impactorDirectionX*crossProdZ_m;
var crossProdObj_ImpZ_m=impactorDirectionX*crossProdY_m - impactorDirectionY*crossProdX_m;
debprint ("Bombable: Put splash near hit > 1.2 ", ballisticMass_lb, " ", impactNodeName);
put_splash (impactNodeName,
oLat_deg+crossProdObj_ImpY_m/m_per_deg_lat,
oLon_deg+crossProdObj_ImpX_m/m_per_deg_lon,
oAlt_m+crossProdObj_ImpZ_m, ballisticMass_lb,
impactTerrain, 1, myNodeName );
}
}
if (ballisticMass_lb>1.2) {
debprint ("Bombable: Close hit by bomb, "~ impactNodeName~ " on "~ myNodeName~ " Distance= "~ closestApproach_m ~ " terrain="~ impactTerrain~ " radius="~ damageRadius_m~" mass="~ballisticMass_lb);
}
# check submodel blast effect distance.
# different cases for each size of ordnance and distance of hit
if (ballisticMass_lb < 1.2 ) {
#do nothing, just a small round hitting on terrain nearby
} elsif (ballisticMass_lb < 10 and ballisticMass_lb >= 1.2 ) {
if(closestApproach_m <= 10 + damageRadius_m)
damAdd=add_damage(.1 * vuls.damageVulnerability * ballisticMass_lb / 10 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 10 + damageRadius_m) and (closestApproach_m < 30 + damageRadius_m)){
var damFactor= (30-closestApproach_m)/30;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0002 * vuls.damageVulnerability * ballisticMass_lb/10* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
} elsif (ballisticMass_lb < 50 and ballisticMass_lb >= 10 ) {
if(closestApproach_m <= .75 + damageRadius_m)
damAdd=add_damage(.3 * vuls.damageVulnerability * ballisticMass_lb /50 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > .75 + damageRadius_m) and (closestApproach_m <= 10 + damageRadius_m))
damAdd=add_damage(.0001 * vuls.damageVulnerability * ballisticMass_lb /50 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 10 + damageRadius_m) and (closestApproach_m < 30 + damageRadius_m))
damAdd=add_damage(0.00005 * vuls.damageVulnerability * ballisticMass_lb /50 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 30 + damageRadius_m) and (closestApproach_m < 60 + damageRadius_m)){
var damFactor= (60-closestApproach_m)/60;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0002 * vuls.damageVulnerability * ballisticMass_lb/50* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
else{
var damFactor= (100-closestApproach_m)/100;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0001 * vuls.damageVulnerability * ballisticMass_lb/350* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
} elsif (ballisticMass_lb < 200 and ballisticMass_lb >= 50 ) {
if(closestApproach_m <= 1.5 + damageRadius_m)
damAdd=add_damage(1 * vuls.damageVulnerability * ballisticMass_lb/200 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 1.5 + damageRadius_m) and (closestApproach_m <= 10 + damageRadius_m))
damAdd=add_damage(.01 * vuls.damageVulnerability * ballisticMass_lb /200 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 10 + damageRadius_m) and (closestApproach_m < 30 + damageRadius_m))
damAdd=add_damage(0.0001 * vuls.damageVulnerability * ballisticMass_lb/200* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 30 + damageRadius_m) and (closestApproach_m < 60 + damageRadius_m)){
var damFactor= (75-closestApproach_m)/75;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0002 * vuls.damageVulnerability * ballisticMass_lb/200 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
else{
var damFactor= (100-closestApproach_m)/100;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0001 * vuls.damageVulnerability * ballisticMass_lb/350* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
} elsif ((ballisticMass_lb >= 200) and (ballisticMass_lb < 350)) {
# Mk-81 class
# Source: http://en.wikipedia.org/wiki/General-purpose_bomb
# Estimated: crater = 2 m, lethal blast=12 m, casualty radius (50%)=25 m, blast shrapnel ~70m, fragmentation ~= 250 m
# All bombs adjusted downwards outside of crater/lethal blast distance,
# based on flight testing plus:
# http://www.f-16.net/f-16_forum_viewtopic-t-10801.html
if(closestApproach_m <= 2 + damageRadius_m)
damAdd=add_damage(2 * vuls.damageVulnerability * ballisticMass_lb/350 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 2 + damageRadius_m) and (closestApproach_m <= 12 + damageRadius_m))
damAdd=add_damage(.015 * vuls.damageVulnerability * ballisticMass_lb /350 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 12 + damageRadius_m) and (closestApproach_m < 25 + damageRadius_m))
damAdd=add_damage(0.0005 * vuls.damageVulnerability * ballisticMass_lb/350* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 25 + damageRadius_m) and (closestApproach_m < 70 + damageRadius_m)) {
var damFactor= (90-closestApproach_m)/90;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0002 * vuls.damageVulnerability * ballisticMass_lb/350* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
else{
var damFactor= (250-closestApproach_m)/250;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0001 * vuls.damageVulnerability * ballisticMass_lb/350* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
} elsif((ballisticMass_lb >= 350) and (ballisticMass_lb < 750)) {
# Mk-82 class (500 lb)
# crater = 4 m, lethal blast=20 m, casualty radius (50%)=60 m, blast shrapnel ~100m, fragmentation ~= 500 m
# http://www.khyber.org/publications/006-010/usbombing.shtml
if(closestApproach_m <= 4 + damageRadius_m )
damAdd=add_damage(4 * vuls.damageVulnerability * ballisticMass_lb /750 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 4 + damageRadius_m) and (closestApproach_m <= 20 + damageRadius_m))
damAdd=add_damage(.02 * vuls.damageVulnerability * ballisticMass_lb /750 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 20 + damageRadius_m) and (closestApproach_m <= 60 + damageRadius_m))
damAdd=add_damage(0.001 * vuls.damageVulnerability * ballisticMass_lb /750 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 60 + damageRadius_m) and (closestApproach_m <= 100 + damageRadius_m)) {
var damFactor= (120-closestApproach_m)/120;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0002 * vuls.damageVulnerability * ballisticMass_lb/350* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
else{
var damFactor= (500-closestApproach_m)/500;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0001 * vuls.damageVulnerability * ballisticMass_lb/350* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
} elsif((ballisticMass_lb >= 750) and (ballisticMass_lb < 1500)) {
# Mk-83 class (1000 lb)
# crater = 11 m, lethal blast~=27 m, casualty radius (50%)~=230 m, blast shrapnel 190m, fragmentation 1000 m
# http://www.khyber.org/publications/006-010/usbombing.shtml
if(closestApproach_m <= 11 + damageRadius_m )
damAdd=add_damage(8 * vuls.damageVulnerability * ballisticMass_lb/1500 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 11 + damageRadius_m) and (closestApproach_m <= 27 + damageRadius_m))
damAdd=add_damage(.02 * vuls.damageVulnerability * ballisticMass_lb /1500 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 27 + damageRadius_m) and (closestApproach_m <= 190 + damageRadius_m))
damAdd=add_damage(0.001 * vuls.damageVulnerability * ballisticMass_lb/1500 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 190 + damageRadius_m) and (closestApproach_m <= 230 + damageRadius_m)){
var damFactor= (230-closestApproach_m)/230;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0002 * vuls.damageVulnerability * ballisticMass_lb/350* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
else {
var damFactor= (1000-closestApproach_m)/1000;
if (damFactor<0) damFactor=0;
if (rand()<damFactor) damAdd=add_damage(0.0001 * vuls.damageVulnerability * ballisticMass_lb/350* easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
}
} elsif(ballisticMass_lb >= 1500 ) {
# Mk-84 class (2000 lb) and upper
# crater = 18 m, lethal blast=34 m, casualty radius (50%)=400 m, blast shrapnel 380m, fragmentation = 1000 m
# http://www.khyber.org/publications/006-010/usbombing.shtml
if(closestApproach_m <= 18 + damageRadius_m )
damAdd=add_damage(16 * vuls.damageVulnerability * ballisticMass_lb/3000 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);
elsif((closestApproach_m > 18 + damageRadius_m) and (closestApproach_m <= 34 + damageRadius_m))
damAdd=add_damage(.02 * vuls.damageVulnerability * ballisticMass_lb /3000 * easyMode, myNodeName, "weapon", impactNodeName, ballisticMass_lb, iLat_deg, iLon_deg, iAlt_m);