Crash and stress damage system: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
m (cat)
Line 10: Line 10:


== How to install the current system on an aircraft ==
== How to install the current system on an aircraft ==
Copy this code into your aircrafts Nasal folder as jsbsim-crash.nas file:
Copy this code into your aircrafts Nasal folder as crash-and-stress.nas file:
<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
#
#
Line 20: Line 20:
#
#
#
#
# Version 0.1
# Version 0.11
#
#
# License:
# License:
Line 29: Line 29:
var FALSE = 0;
var FALSE = 0;


var JsbsimCrash = {
var CrashAndStress = {
# pattern singleton
# pattern singleton
_instance: nil,
_instance: nil,
Line 38: Line 38:
if(me._instance == nil) {
if(me._instance == nil) {
me._instance = {};
me._instance = {};
me._instance["parents"] = [JsbsimCrash];
me._instance["parents"] = [CrashAndStress];


m = me._instance;
m = me._instance;
Line 48: Line 48:


m.wingsAttached = TRUE;
m.wingsAttached = TRUE;
m.loopRunning = FALSE;
m.wingLoadLimitUpper = nil;
m.wingLoadLimitUpper = nil;
m.wingLoadLimitLower = nil;
m.wingLoadLimitLower = nil;
m.wingFailureModes = nil;
m._looptimer = maketimer(0, m, m._loop);


m.input = {
m.input = {
# trembleOn:  "damage/g-tremble-on",
# trembleMax: "damage/g-tremble-max",
replay:    "sim/replay/replay-state",
replay:    "sim/replay/replay-state",
Nz:        "fdm/jsbsim/accelerations/Nz",
lat:        "position/latitude-deg",
lat:        "position/latitude-deg",
lon:        "position/longitude-deg",
lon:        "position/longitude-deg",
Line 61: Line 61:
altAgl:    "position/altitude-agl-ft",
altAgl:    "position/altitude-agl-ft",
elev:      "position/ground-elev-ft",
elev:      "position/ground-elev-ft",
g3d:        "velocities/groundspeed-3D-kt",
simTime:    "fdm/jsbsim/simulation/sim-time-sec",
vgFps:      "fdm/jsbsim/velocities/vg-fps",
  downFps:    "velocities/down-relground-fps",
  crackOn:    "damage/sounds/crack-on",
  crackOn:    "damage/sounds/crack-on",
creakOn:    "damage/sounds/creaking-on",
creakOn:    "damage/sounds/creaking-on",
# trembleOn:  "damage/g-tremble-on",
crackVol:  "damage/sounds/crack-volume",
crackVol:  "damage/sounds/crack-volume",
creakVol:  "damage/sounds/creaking-volume",
creakVol:  "damage/sounds/creaking-volume",
# trembleMax: "damage/g-tremble-max",
wCrashOn:  "damage/sounds/water-crash-on",
wCrashOn:  "damage/sounds/water-crash-on",
crashOn:    "damage/sounds/crash-on",
crashOn:    "damage/sounds/crash-on",
detachOn:  "damage/sounds/detach-on",
detachOn:  "damage/sounds/detach-on",
explodeOn:  "damage/sounds/explode-on",
explodeOn:  "damage/sounds/explode-on",
weight:    "fdm/jsbsim/inertia/weight-lbs",
fuel:      "fdm/jsbsim/propulsion/total-fuel-lbs",
};
};
foreach(var ident; keys(m.input)) {
foreach(var ident; keys(m.input)) {
Line 82: Line 74:
}
}


m.fdm = nil;
if(getprop("sim/flight-model") == "jsb") {
m.fdm = jsbSimProp;
} elsif(getprop("sim/flight-model") == "yasim") {
m.fdm = yaSimProp;
} else {
return nil;
}
m.fdm.convert();
m.wowStructure = [];
m.wowStructure = [];
m.wowGear = [];
m.wowGear = [];
Line 123: Line 126:
me.lastMessageTime = 0;
me.lastMessageTime = 0;
me.repairing = TRUE;
me.repairing = TRUE;
settimer(func {call(me._finishRepair, nil, me)}, 10);
var timer = maketimer(10, me, me._finishRepair);
timer.start();
},
},
# accepts a vector with failure mode IDs, they will fail when wings break off.
# accepts a vector with failure mode IDs, they will fail when wings break off.
setWingsFailureModes: func (modes) {
setWingsFailureModes: func (modes) {
me.wingFailureModes = modes;
if(modes == nil) {
modes = [];
}
 
##
    # Returns an actuator object that will set the serviceable property at
    # the given node to zero when the level of failure is > 0.
    # it will also fail additionally failure modes.
 
    var set_unserviceable_cascading = func(path, casc_paths) {
 
        var prop = path ~ "/serviceable";
 
        if (props.globals.getNode(prop) == nil) {
            props.globals.initNode(prop, TRUE, "BOOL");
        } else {
        props.globals.getNode(prop).setValue(TRUE);#in case this gets initialized empty from a recorder signal or MP alias.
        }
 
        return {
            parents: [FailureMgr.FailureActuator],
            mode_paths: casc_paths,
            set_failure_level: func(level) {
                setprop(prop, level > 0 ? 0 : 1);
                foreach(var mode_path ; me.mode_paths) {
                    FailureMgr.set_failure_level(mode_path, level);
                }
            },
            get_failure_level: func { getprop(prop) ? 0 : 1 }
        }
    }
 
    var prop = me.fdm.wingsFailureID;
    var actuator_wings = set_unserviceable_cascading(prop, modes);
    FailureMgr.add_failure_mode(prop, "Main wings", actuator_wings);
},
},
# set the stresslimit for the main wings
# set the stresslimit for the main wings
setStressLimit: func (stressLimit = nil) {
setStressLimit: func (stressLimit = nil) {
if (stressLimit != nil) {
if (stressLimit != nil) {
var wingloadMax = stressLimit['wingloadMax'];
var wingloadMax = stressLimit['wingloadMaxLbs'];
var wingloadMin = stressLimit['wingloadMin'];
var wingloadMin = stressLimit['wingloadMinLbs'];
var maxG = stressLimit['maxG'];
var maxG = stressLimit['maxG'];
var minG = stressLimit['minG'];
var minG = stressLimit['minG'];
var weight = stressLimit['weight'];
var weight = stressLimit['weightLbs'];
if(wingloadMax != nil) {
if(wingloadMax != nil) {
me.wingLoadLimitUpper = wingloadMax;
me.wingLoadLimitUpper = wingloadMax;
Line 150: Line 188:
me.wingLoadLimitLower = -me.wingLoadLimitUpper * 0.4;#estimate for when lower is not specified
me.wingLoadLimitLower = -me.wingLoadLimitUpper * 0.4;#estimate for when lower is not specified
}
}
me.loopRunning = TRUE;
me._looptimer.start();
me._loop();
} else {
} else {
me.loopRunning = FALSE;
me._looptimer.stop();
}
}
},
},
Line 198: Line 235:
},
},
_startImpactListeners: func () {
_startImpactListeners: func () {
ImpactStructureListener.crash = me;
foreach(var structure; me.wowStructure) {
foreach(var structure; me.wowStructure) {
setlistener(structure, func {call(me._impactStructureListener, nil, me)},0,0);
setlistener(structure, func {call(ImpactStructureListener.run, nil, ImpactStructureListener, ImpactStructureListener)},0,0);
}
}
},
},
_impactGearListener: func () {
_isRunning: func () {
# TODO: damage gear failure mode at high speed impact
if (me.inService == FALSE or me.input.replay.getBoolValue() == TRUE or me.repairing == TRUE) {
},
return FALSE;
_impactStructureListener: func () {
#print("tst: "~me.inService~" "~me.input.replay.getBoolValue()~" "~me._isRunning()~" "~me.repairing);
if (me.inService == TRUE and me.input.replay.getBoolValue() == FALSE and me._isRunning() == TRUE and me.repairing == FALSE) {
#print("testing");
var wow = me._isStructureInContact();
if (wow == TRUE) {
me._impactDamage();
}
}
}
},
var time = me.fdm.input.simTime.getValue();
_isRunning: func () {
var time = me.input.simTime.getValue();
if (time != nil and time > 1) {
if (time != nil and time > 1) {
return TRUE;
return TRUE;
Line 223: Line 251:
},
},
_calcGroundSpeed: func () {
_calcGroundSpeed: func () {
var horzSpeed = me.input.vgFps.getValue();
var horzSpeed = me.fdm.input.vgFps.getValue();
   var vertSpeed = me.input.downFps.getValue();
   var vertSpeed = me.fdm.input.downFps.getValue();
   var realSpeed = math.sqrt((horzSpeed * horzSpeed) + (vertSpeed * vertSpeed));
   var realSpeed = math.sqrt((horzSpeed * horzSpeed) + (vertSpeed * vertSpeed));
   realSpeed = realSpeed * 0.5924838;#ft/s to kt
   realSpeed = me.fdm.fps2kt(realSpeed);
   return realSpeed;
   return realSpeed;
},
},
Line 239: Line 267:
var failure_modes = FailureMgr._failmgr.failure_modes;
var failure_modes = FailureMgr._failmgr.failure_modes;
    var mode_list = keys(failure_modes);
    var mode_list = keys(failure_modes);
    var probability = speed / 200.0;#200kt will fail everything, 0kt will fail nothing.
    var probability = speed / 200.0;# 200kt will fail everything, 0kt will fail nothing.


    #test for explosion
    # test for explosion
    if(probability > 1.0 and me.input.fuel.getValue() > 2500) {
    if(probability > 1.0 and me.fdm.input.fuel.getValue() > 2500) {
    #200kt+ and fuel intanks will explode the aircraft on impact.
    # 200kt+ and fuel in tanks will explode the aircraft on impact.
    me._explodeBegin();
    me._explodeBegin();
    return;
    return;
Line 270: Line 298:
if (speed > 5) {#check if sound already running?
if (speed > 5) {#check if sound already running?
me.input.wCrashOn.setValue(1);
me.input.wCrashOn.setValue(1);
settimer(func {call(me._impactSoundWaterEnd, nil, me)}, 3);
var timer = maketimer(3, me, me._impactSoundWaterEnd);
timer.start();
}
}
},
},
Line 279: Line 308:
if (speed > 5) {
if (speed > 5) {
me.input.crashOn.setValue(1);
me.input.crashOn.setValue(1);
settimer(func {call(me._impactSoundEnd, nil, me)}, 3);
var timer = maketimer(3, me, me._impactSoundEnd);
timer.start();
}
}
},
},
Line 297: Line 327:
    me._output("Aircraft exploded.", TRUE);
    me._output("Aircraft exploded.", TRUE);


settimer(func {call(me._explodeEnd, nil, me)}, 3);
var timer = maketimer(3, me, me._explodeEnd);
timer.start();
},
},
_explodeEnd: func () {
_explodeEnd: func () {
Line 305: Line 336:
me._output("Aircraft damaged: Wings broke off, due to "~str~" G forces.");
me._output("Aircraft damaged: Wings broke off, due to "~str~" G forces.");
me.input.detachOn.setValue(1);
me.input.detachOn.setValue(1);
if (me.wingFailureModes != nil) {
foreach(var failureModeId; me.wingFailureModes) {
  FailureMgr.set_failure_level(me.fdm.wingsFailureID, 1);
      FailureMgr.set_failure_level(failureModeId, 1);
    }
}


me.wingsAttached = FALSE;
me.wingsAttached = FALSE;
settimer(func {call(me._stressDamageEnd, nil, me)}, 3);
var timer = maketimer(3, me, me._stressDamageEnd);
timer.start();
},
},
_stressDamageEnd: func () {
_stressDamageEnd: func () {
Line 318: Line 347:
},
},
_output: func (str, override = FALSE) {
_output: func (str, override = FALSE) {
var time = me.input.simTime.getValue();
var time = me.fdm.input.simTime.getValue();
if (override == TRUE or (time - me.lastMessageTime) > 3) {
if (override == TRUE or (time - me.lastMessageTime) > 3) {
me.lastMessageTime = time;
me.lastMessageTime = time;
Line 328: Line 357:
me._testStress();
me._testStress();
me._testWaterImpact();
me._testWaterImpact();
if(me.loopRunning == TRUE) {
settimer(func {call(me._loop, nil, me)}, 0);
}
},
},
_testWaterImpact: func () {
_testWaterImpact: func () {
Line 344: Line 370:
},
},
_testStress: func () {
_testStress: func () {
if (me.inService == TRUE and me.input.replay.getBoolValue() == FALSE and me._isRunning() == TRUE and me.repairing == FALSE and me.wingsAttached == TRUE) {
if (me._isRunning() == TRUE and me.wingsAttached == TRUE) {
var gForce = me.input.Nz.getValue() == nil?1:me.input.Nz.getValue();
var gForce = me.fdm.input.Nz.getValue() == nil?1:me.fdm.input.Nz.getValue();
var weight = me.input.weight.getValue();
var weight = me.fdm.input.weight.getValue();
var wingload = gForce * weight;
var wingload = gForce * weight;


Line 404: Line 430:
},
},
};
};
var ImpactStructureListener = {
crash: nil,
run: func () {
if (crash._isRunning() == TRUE) {
var wow = crash._isStructureInContact();
if (wow == TRUE) {
crash._impactDamage();
}
}
},
};
# static class
var fdmProperties = {
input: {},
convert: func () {
foreach(var ident; keys(me.input)) {
    me.input[ident] = props.globals.getNode(me.input[ident], 1);
}
},
fps2kt: func (fps) {
return fps * 0.5924838;
},
wingsFailureID: nil,
};
var jsbSimProp = {
parents: [fdmProperties],
input: {
weight:    "fdm/jsbsim/inertia/weight-lbs",
fuel:      "fdm/jsbsim/propulsion/total-fuel-lbs",
simTime:    "fdm/jsbsim/simulation/sim-time-sec",
vgFps:      "fdm/jsbsim/velocities/vg-fps",
downFps:    "velocities/down-relground-fps",
Nz:        "fdm/jsbsim/accelerations/Nz",
},
wingsFailureID: "fdm/jsbsim/structural/wings",
};
var yaSimProp = {
parents: [fdmProperties],
input: {
weight:    "yasim/gross-weight-lbs",
fuel:      "consumables/fuel/total-fuel-lbs",
simTime:    "sim/time/elapsed-sec",
vgFps:      "velocities/groundspeed-kt",
Nz:        "accelerations/n-z-cg-fps_sec",
},
convert: func () {
call(fdmProperties.convert, [], me);
me.input.downFps = props.Node.new().setValue(0);
},
fps2kt: func (fps) {
return fps;
},
wingsFailureID: "structural/wings",
};


# TODO:
# TODO:
Line 412: Line 499:
# Explosion depending on bumpiness and speed when sliding?
# Explosion depending on bumpiness and speed when sliding?
# Tie in with damage from Bombable?
# Tie in with damage from Bombable?
# Use galvedro's UpdateLoop framework when it gets merged




# example uses:
# example uses:
#
#
# var crashCode = JsbsimCrash.new([0,1,2];  
# var crashCode = CrashAndStress.new([0,1,2];  
#
#
# var crashCode = JsbsimCrash.new([0,1,2], {"weight":30000, "maxG": 12}, ["fdm/jsbsim/fcs/wings"]);
# var crashCode = CrashAndStress.new([0,1,2], {"weightLbs":30000, "maxG": 12});
#
#
# var crashCode = JsbsimCrash.new([0,1,2,3], {"weight":20000, "maxG": 11, "minG": -5});
# var crashCode = CrashAndStress.new([0,1,2,3], {"weightLbs":20000, "maxG": 11, "minG": -5});
#
#
# var crashCode = JsbsimCrash.new([0,1,2], {"wingloadMax": 90000, "wingloadMin": -45000}, ["fdm/jsbsim/fcs/wings"]);
# var crashCode = CrashAndStress.new([0,1,2], {"wingloadMaxLbs": 90000, "wingloadMinLbs": -45000}, ["controls/flight/aileron", "controls/flight/elevator", "controls/flight/flaps"]);
#
#
# var crashCode = JsbsimCrash.new([0,1,2], {"wingloadMax":90000}, ["controls/flight/aileron", "controls/flight/elevator", "controls/flight/flaps"]);
# var crashCode = CrashAndStress.new([0,1,2], {"wingloadMaxLbs":90000}, ["controls/flight/aileron", "controls/flight/elevator", "controls/flight/flaps"]);
#
#
# Gears parameter must be defined.
# Gears parameter must be defined.
Line 435: Line 523:


# use:
# use:
var crashCode = JsbsimCrash.new([0,1,2], {"weight":30000, "maxG": 12}, ["fdm/jsbsim/fcs/wings"]);
var crashCode = CrashAndStress.new([0,1,2], {"weightLbs":30000, "maxG": 12}, ["controls/gear1", "controls/gear2", "controls/flight/aileron", "controls/flight/elevator", "consumables/fuel/wing-tanks"]);
crashCode.start();
crashCode.start();


Line 450: Line 538:
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
<crash>
<crash>
   <file>Aircraft/[aircraft name here]/Nasal/jsbsim-crash.nas</file>
   <file>Aircraft/[aircraft name here]/Nasal/crash-and-stress.nas</file>
</crash>
</crash>
</syntaxhighlight>
</syntaxhighlight>
Line 575: Line 663:
           <type>float</type>
           <type>float</type>
           <property type="string">damage/sounds/creaking-volume</property>
           <property type="string">damage/sounds/creaking-volume</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">fdm/jsbsim/structural/wings/serviceable</property>
        </signal>
</syntaxhighlight>
Notice that the last signal is jsbsim specific, the corresponding yasim signal is:
<syntaxhighlight lan="nasal">
        <signal>
          <type>bool</type>
          <property type="string">structural/wings/serviceable</property>
         </signal>
         </signal>
</syntaxhighlight>
</syntaxhighlight>
That property is also what you would use to animate wings being detached and/or changes in the aerodynamics.


== Performance ==
== Performance ==

Revision as of 18:46, 26 December 2014

Inspired by the crash system in the Mig-15, this system is meant to become generic and usable for all Jsbsim aircraft by adding just a few lines. For now you have to add a file with code though.

Features so far

  • Impact detection and tied into the new failure manager.
  • Over-G detection for wing stress.
  • Sounds.

Planned features

Feel free to suggest some in the forum topic linked to at the end of this article.

How to install the current system on an aircraft

Copy this code into your aircrafts Nasal folder as crash-and-stress.nas file:

#
# A Flightgear JSBSim crash and stress damage system.
#
# Inspired by the crash system in Mig15 by Slavutinsky Victor. And by Hvengel's formula for wingload stress.
#
# Authors: Slavutinsky Victor, Nikolai V. Chr. (Necolatis)
#
#
# Version 0.11
#
# License:
#   GPL 2.0


var TRUE = 1;
var FALSE = 0;

var CrashAndStress = {
	# pattern singleton
	_instance: nil,
	# Get the instance
	new: func (gears, stressLimit = nil, wingsFailureModes = nil) {

		var m = nil;
		if(me._instance == nil) {
			me._instance = {};
			me._instance["parents"] = [CrashAndStress];

			m = me._instance;

			m.inService = FALSE;
			m.repairing = FALSE;

			m.exploded = FALSE;

			m.wingsAttached = TRUE;
			m.wingLoadLimitUpper = nil;
			m.wingLoadLimitLower = nil;
			m._looptimer = maketimer(0, m, m._loop);

			m.input = {
			#	trembleOn:  "damage/g-tremble-on",
			#	trembleMax: "damage/g-tremble-max",				
				replay:     "sim/replay/replay-state",
				lat:        "position/latitude-deg",
				lon:        "position/longitude-deg",
				alt:        "position/altitude-ft",
				altAgl:     "position/altitude-agl-ft",
				elev:       "position/ground-elev-ft",
	  			crackOn:    "damage/sounds/crack-on",
				creakOn:    "damage/sounds/creaking-on",
				crackVol:   "damage/sounds/crack-volume",
				creakVol:   "damage/sounds/creaking-volume",
				wCrashOn:   "damage/sounds/water-crash-on",
				crashOn:    "damage/sounds/crash-on",
				detachOn:   "damage/sounds/detach-on",
				explodeOn:  "damage/sounds/explode-on",
			};
			foreach(var ident; keys(m.input)) {
			    m.input[ident] = props.globals.getNode(m.input[ident], 1);
			}

			m.fdm = nil;

			if(getprop("sim/flight-model") == "jsb") {
				m.fdm = jsbSimProp;
			} elsif(getprop("sim/flight-model") == "yasim") {
				m.fdm = yaSimProp;
			} else {
				return nil;
			}
			m.fdm.convert();
			
			m.wowStructure = [];
			m.wowGear = [];

			m.lastMessageTime = 0;


			m._identifyGears(gears);
			m.setStressLimit(stressLimit);
			m.setWingsFailureModes(wingsFailureModes);

			m._startImpactListeners();
		} else {
			m = me._instance;
		}

		return m;
	},
	# start the system
	start: func () {
		me.inService = TRUE;
	},
	# stop the system
	stop: func () {
		me.inService = FALSE;
	},
	# return TRUE if in progress
	isStarted: func () {
		return me.inService;
	},
	# repair the aircaft
	repair: func () {
		var failure_modes = FailureMgr._failmgr.failure_modes;
		var mode_list = keys(failure_modes);

		foreach(var failure_mode_id; mode_list) {
			FailureMgr.set_failure_level(failure_mode_id, 0);
		}
		me.wingsAttached = TRUE;
		me.exploded = FALSE;
		me.lastMessageTime = 0;
		me.repairing = TRUE;
		var timer = maketimer(10, me, me._finishRepair);
		timer.start();
	},
	# accepts a vector with failure mode IDs, they will fail when wings break off.
	setWingsFailureModes: func (modes) {
		if(modes == nil) {
			modes = [];
		}

		##
	    # Returns an actuator object that will set the serviceable property at
	    # the given node to zero when the level of failure is > 0.
	    # it will also fail additionally failure modes.

	    var set_unserviceable_cascading = func(path, casc_paths) {

	        var prop = path ~ "/serviceable";

	        if (props.globals.getNode(prop) == nil) {
	            props.globals.initNode(prop, TRUE, "BOOL");
	        } else {
	        	props.globals.getNode(prop).setValue(TRUE);#in case this gets initialized empty from a recorder signal or MP alias.
	        }

	        return {
	            parents: [FailureMgr.FailureActuator],
	            mode_paths: casc_paths,
	            set_failure_level: func(level) {
	                setprop(prop, level > 0 ? 0 : 1);
	                foreach(var mode_path ; me.mode_paths) {
	                    FailureMgr.set_failure_level(mode_path, level);
	                }
	            },
	            get_failure_level: func { getprop(prop) ? 0 : 1 }
	        }
	    }

	    var prop = me.fdm.wingsFailureID;
	    var actuator_wings = set_unserviceable_cascading(prop, modes);
	    FailureMgr.add_failure_mode(prop, "Main wings", actuator_wings);
	},
	# set the stresslimit for the main wings
	setStressLimit: func (stressLimit = nil) {
		if (stressLimit != nil) {
			var wingloadMax = stressLimit['wingloadMaxLbs'];
			var wingloadMin = stressLimit['wingloadMinLbs'];
			var maxG = stressLimit['maxG'];
			var minG = stressLimit['minG'];
			var weight = stressLimit['weightLbs'];
			if(wingloadMax != nil) {
				me.wingLoadLimitUpper = wingloadMax;
			} elsif (maxG != nil and weight != nil) {
				me.wingLoadLimitUpper = maxG * weight;
			}

			if(wingloadMin != nil) {
				me.wingLoadLimitLower = wingloadMin;
			} elsif (minG != nil and weight != nil) {
				me.wingLoadLimitLower = minG * weight;
			} elsif (me.wingLoadLimitUpper != nil) {
				me.wingLoadLimitLower = -me.wingLoadLimitUpper * 0.4;#estimate for when lower is not specified
			}
			me._looptimer.start();
		} else {
			me._looptimer.stop();
		}
	},
	_identifyGears: func (gears) {
		var contacts = props.globals.getNode("/gear").getChildren("gear");

		foreach(var contact; contacts) {
			var index = contact.getIndex();
			var isGear = me._contains(gears, index);
			var wow = contact.getChild("wow");
			if (isGear == TRUE) {
				append(me.wowGear, wow);
			} else {
				append(me.wowStructure, wow);
			}
		}
	},	
	_finishRepair: func () {
		me.repairing = FALSE;
	},
	_isStructureInContact: func () {
		foreach(var structure; me.wowStructure) {
			if (structure.getBoolValue() == TRUE) {
				return TRUE;
			}
		}
		return FALSE;
	},
	_isGearInContact: func () {
		foreach(var gear; me.wowGear) {
			if (gear.getBoolValue() == TRUE) {
				return TRUE;
			}
		}
		return FALSE;
	},
	_contains: func (vector, content) {
		foreach(var vari; vector) {
			if (vari == content) {
				return TRUE;
			}
		}
		return FALSE;
	},
	_startImpactListeners: func () {
		ImpactStructureListener.crash = me;
		foreach(var structure; me.wowStructure) {
			setlistener(structure, func {call(ImpactStructureListener.run, nil, ImpactStructureListener, ImpactStructureListener)},0,0);
		}
	},
	_isRunning: func () {
		if (me.inService == FALSE or me.input.replay.getBoolValue() == TRUE or me.repairing == TRUE) {
			return FALSE;
		}
		var time = me.fdm.input.simTime.getValue();
		if (time != nil and time > 1) {
			return TRUE;
		}
		return FALSE;
	},
	_calcGroundSpeed: func () {
		var horzSpeed = me.fdm.input.vgFps.getValue();
  		var vertSpeed = me.fdm.input.downFps.getValue();
  		var realSpeed = math.sqrt((horzSpeed * horzSpeed) + (vertSpeed * vertSpeed));
  		realSpeed = me.fdm.fps2kt(realSpeed);
  		return realSpeed;
	},
	_impactDamage: func () {
	    var lat = me.input.lat.getValue();
		var lon = me.input.lon.getValue();
		var info = geodinfo(lat, lon);
		var solid = info[1] == nil?TRUE:info[1].solid;
		var speed = me._calcGroundSpeed();

		if (me.exploded == FALSE) {
			var failure_modes = FailureMgr._failmgr.failure_modes;
		    var mode_list = keys(failure_modes);
		    var probability = speed / 200.0;# 200kt will fail everything, 0kt will fail nothing.

		    # test for explosion
		    if(probability > 1.0 and me.fdm.input.fuel.getValue() > 2500) {
		    	# 200kt+ and fuel in tanks will explode the aircraft on impact.
		    	me._explodeBegin();
		    	return;
		    }

		    foreach(var failure_mode_id; mode_list) {
		    	if(rand() < probability) {
		      		FailureMgr.set_failure_level(failure_mode_id, 1);
		      	}
		    }
			var str = "Aircraft hit "~info[1].names[size(info[1].names)-1]~".";
			me._output(str);
		} elsif (solid == TRUE) {
			var pos= geo.Coord.new().set_latlon(lat, lon);
			wildfire.ignite(pos, 1);
		}
		if(solid == TRUE) {
			#print("solid");
			me._impactSoundBegin(speed);
		} else {
			#print("water");
			me._impactSoundWaterBegin(speed);
		}
	},
	_impactSoundWaterBegin: func (speed) {
		if (speed > 5) {#check if sound already running?
			me.input.wCrashOn.setValue(1);
			var timer = maketimer(3, me, me._impactSoundWaterEnd);
			timer.start();
		}
	},
	_impactSoundWaterEnd: func	() {
		me.input.wCrashOn.setValue(0);
	},
	_impactSoundBegin: func (speed) {
		if (speed > 5) {
			me.input.crashOn.setValue(1);
			var timer = maketimer(3, me, me._impactSoundEnd);
			timer.start();
		}
	},
	_impactSoundEnd: func () {
		me.input.crashOn.setValue(0);
	},
	_explodeBegin: func() {
		me.input.explodeOn.setValue(1);
		me.exploded = TRUE;
		var failure_modes = FailureMgr._failmgr.failure_modes;
	    var mode_list = keys(failure_modes);

	    foreach(var failure_mode_id; mode_list) {
      		FailureMgr.set_failure_level(failure_mode_id, 1);
	    }

	    me._output("Aircraft exploded.", TRUE);

		var timer = maketimer(3, me, me._explodeEnd);
		timer.start();
	},
	_explodeEnd: func () {
		me.input.explodeOn.setValue(0);
	},
	_stressDamage: func (str) {
		me._output("Aircraft damaged: Wings broke off, due to "~str~" G forces.");
		me.input.detachOn.setValue(1);
		
  		FailureMgr.set_failure_level(me.fdm.wingsFailureID, 1);

		me.wingsAttached = FALSE;
		var timer = maketimer(3, me, me._stressDamageEnd);
		timer.start();
	},
	_stressDamageEnd: func () {
		me.input.detachOn.setValue(0);
	},
	_output: func (str, override = FALSE) {
		var time = me.fdm.input.simTime.getValue();
		if (override == TRUE or (time - me.lastMessageTime) > 3) {
			me.lastMessageTime = time;
			print(str);
			screen.log.write(str, 0.7098, 0.5372, 0.0);# solarized yellow
		}
	},
	_loop: func () {
		me._testStress();
		me._testWaterImpact();
	},
	_testWaterImpact: func () {
		if(me.input.altAgl.getValue() < 0) {
			var lat = me.input.lat.getValue();
			var lon = me.input.lon.getValue();
			var info = geodinfo(lat, lon);
			var solid = info[1] == nil?TRUE:info[1].solid;
			if(solid == FALSE) {
				me._impactDamage();
			}
		}
	},
	_testStress: func () {
		if (me._isRunning() == TRUE and me.wingsAttached == TRUE) {
			var gForce = me.fdm.input.Nz.getValue() == nil?1:me.fdm.input.Nz.getValue();
			var weight = me.fdm.input.weight.getValue();
			var wingload = gForce * weight;

			#print("wingload: "~wingload~" max: "~me.wingLoadLimitUpper);
			var broken = FALSE;

			if(wingload < 0) {
				broken = me._testWingload(-wingload, -me.wingLoadLimitLower);
				if(broken == TRUE) {
					me._stressDamage("negative");
				}
			} else {
				broken = me._testWingload(wingload, me.wingLoadLimitUpper);
				if(broken == TRUE) {
					me._stressDamage("positive");
				}
			}
		} else {
			me.input.crackOn.setValue(0);
			me.input.creakOn.setValue(0);
			#me.input.trembleOn.setValue(0);
		}
	},
	_testWingload: func (wingload, wingLoadLimit) {
		if (wingload > (wingLoadLimit * 0.5)) {
			#me.input.trembleOn.setValue(1);
			var tremble_max = math.sqrt((wingload - (wingLoadLimit * 0.5)) / (wingLoadLimit * 0.5));
			#me.input.trembleMax.setValue(1);

			if (wingload > (wingLoadLimit * 0.75)) {

				#tremble_max = math.sqrt((wingload - (wingLoadLimit * 0.5)) / (wingLoadLimit * 0.5));
				me.input.creakVol.setValue(tremble_max);
				me.input.creakOn.setValue(1);

				if (wingload > (wingLoadLimit * 0.90)) {
					me.input.crackOn.setValue(1);
					me.input.crackVol.setValue(tremble_max);
					if (wingload > wingLoadLimit) {
						me.input.crackVol.setValue(1);
						me.input.creakVol.setValue(1);
						#me.input.trembleMax.setValue(1);
						return TRUE;
					}
				} else {
					me.input.crackOn.setValue(0);
				}
			} else {
				me.input.creakOn.setValue(0);
			}
		} else {
			me.input.crackOn.setValue(0);
			me.input.creakOn.setValue(0);
			#me.input.trembleOn.setValue(0);
		}
		return FALSE;
	},
};


var ImpactStructureListener = {
	crash: nil,
	run: func () {
		if (crash._isRunning() == TRUE) {
			var wow = crash._isStructureInContact();
			if (wow == TRUE) {
				crash._impactDamage();
			}
		}
	},
};


# static class
var fdmProperties = {
	input: {},
	convert: func () {
		foreach(var ident; keys(me.input)) {
		    me.input[ident] = props.globals.getNode(me.input[ident], 1);
		}
	},
	fps2kt: func (fps) {
		return fps * 0.5924838;
	},
	wingsFailureID: nil,
};

var jsbSimProp = {
	parents: [fdmProperties],
	input: {
				weight:     "fdm/jsbsim/inertia/weight-lbs",
				fuel:       "fdm/jsbsim/propulsion/total-fuel-lbs",
				simTime:    "fdm/jsbsim/simulation/sim-time-sec",
				vgFps:      "fdm/jsbsim/velocities/vg-fps",
				downFps:    "velocities/down-relground-fps",
				Nz:         "fdm/jsbsim/accelerations/Nz",
	},
	wingsFailureID: "fdm/jsbsim/structural/wings",
};

var yaSimProp = {
	parents: [fdmProperties],
	input: {
				weight:     "yasim/gross-weight-lbs",
				fuel:       "consumables/fuel/total-fuel-lbs",
				simTime:    "sim/time/elapsed-sec",
				vgFps:      "velocities/groundspeed-kt",
				Nz:         "accelerations/n-z-cg-fps_sec",
	},
	convert: func () {
		call(fdmProperties.convert, [], me);
		me.input.downFps = props.Node.new().setValue(0);
	},
	fps2kt: func (fps) {
		return fps;
	},
	wingsFailureID: "structural/wings",
};


# TODO:
#
# Loss of inertia if impacting/sliding? Or should the jsb groundcontacts take care of that alone?
# If gears hit something at too high speed the gears should be damaged?
# Make property to control if system active, or method enough?
# Explosion depending on bumpiness and speed when sliding?
# Tie in with damage from Bombable?
# Use galvedro's UpdateLoop framework when it gets merged


# example uses:
#
# var crashCode = CrashAndStress.new([0,1,2]; 
#
# var crashCode = CrashAndStress.new([0,1,2], {"weightLbs":30000, "maxG": 12});
#
# var crashCode = CrashAndStress.new([0,1,2,3], {"weightLbs":20000, "maxG": 11, "minG": -5});
#
# var crashCode = CrashAndStress.new([0,1,2], {"wingloadMaxLbs": 90000, "wingloadMinLbs": -45000}, ["controls/flight/aileron", "controls/flight/elevator", "controls/flight/flaps"]);
#
# var crashCode = CrashAndStress.new([0,1,2], {"wingloadMaxLbs":90000}, ["controls/flight/aileron", "controls/flight/elevator", "controls/flight/flaps"]);
#
# Gears parameter must be defined.
# Stress parameter is optional. If minimum wing stress is not defined it will be set to -40% of max wingload stress if that is defined.
# The last optional parameter is a list of failure mode IDs that shall fail when wings detach. They must be defined in the FailureMgr.
#
#
# Remember to add sounds and to add the sound properties as custom signals to the replay recorder.


# use:
var crashCode = CrashAndStress.new([0,1,2], {"weightLbs":30000, "maxG": 12}, ["controls/gear1", "controls/gear2", "controls/flight/aileron", "controls/flight/elevator", "consumables/fuel/wing-tanks"]);
crashCode.start();

# test:
var repair = func {
	crashCode.repair();
};

Notice that you should edit the line underneath # use to fit your aircraft requirements.

In your -set.xml file under nasal tags add

<crash>
  <file>Aircraft/[aircraft name here]/Nasal/crash-and-stress.nas</file>
</crash>

Add this to your sounds file:

        <aircraft-explode>
  		<name>aircraft-explode</name>
  		<path>Sounds/aircraft-explode.wav</path>
  		<mode>once</mode>
  		<condition>
                      <or>
      		        <equals>
      			  <property>damage/sounds/explode-on</property>
      		          <value>1</value>
    			</equals>
                        <equals>
                            <property>damage/sounds/detach-on</property>
                            <value>1</value>
                          </equals>
                        </or>
  		</condition>
  		<volume>
  			<factor>1</factor>
  		</volume>
  	</aircraft-explode>

  	<aircraft-crash>
  		<name>aircraft-crash</name>
  		<path>Sounds/aircraft-crash.wav</path>
  		<mode>once</mode>
  		<condition>
    			<equals>
    				<property>damage/sounds/crash-on</property>
    				<value>1</value>
    			</equals>
  		</condition>
  		<volume>
  			<factor>1</factor>
  		</volume>
  	</aircraft-crash>

  	<aircraft-water-crash>
  		<name>aircraft-water-crash</name>
  		<path>Sounds/aircraft-water-crash.wav</path>
  		<mode>once</mode>
  		<condition>
  			<equals>
  				<property>damage/sounds/water-crash-on</property>
  				<value>1</value>
  			</equals>
  		</condition>
  		<volume>
  			<factor>1</factor>
  		</volume>
  	</aircraft-water-crash>

  	<aircraft-crack>
  		<name>aircraft-crack</name>
  		<path>Sounds/aircraft-crack.wav</path>
  		<mode>once</mode>
  		<condition>
  		<property>sim/current-view/internal</property>
  			<equals>
  				<property>damage/sounds/crack-on</property>
  				<value>1</value>
  			</equals>
  		</condition>
  		<volume>
  			<property>damage/sounds/crack-volume</property>
  			<factor>1</factor>
  		</volume>
  	</aircraft-crack>

  	<aircraft-creaking>
  		<name>aircraft-creaking</name>
  		<path>Sounds/aircraft-creaking.wav</path>
  		<mode>looped</mode>
  		<condition>
  		  <property>sim/current-view/internal</property>
  			<equals>
  				<property>damage/sounds/creaking-on</property>
  				<value>1</value>
  			</equals>
  		</condition>
  		<volume>
  			<property>damage/sounds/creaking-volume</property>
  			<factor>1</factor>
  		</volume>
  	</aircraft-creaking>

Add some sounds to the aircraft sound folder that corresponds to the file names used in the above. (you can use those in the Mig15 or Ja-37 folders)

Optionally add these signals to the replay recorder:

        <signal>
          <type>bool</type>
          <property type="string">damage/sounds/explode-on</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">damage/sounds/crash-on</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">damage/sounds/water-crash-on</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">damage/sounds/crack-on</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">damage/sounds/creaking-on</property>
        </signal>
        <signal>
          <type>float</type>
          <property type="string">damage/sounds/crack-volume</property>
        </signal>
        <signal>
          <type>float</type>
          <property type="string">damage/sounds/creaking-volume</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">fdm/jsbsim/structural/wings/serviceable</property>
        </signal>

Notice that the last signal is jsbsim specific, the corresponding yasim signal is:

        <signal>
          <type>bool</type>
          <property type="string">structural/wings/serviceable</property>
        </signal>

That property is also what you would use to animate wings being detached and/or changes in the aerodynamics.

Performance

As it is now you should feel no performance degradation at all from using it.

People volunteering to work on this

  • Bomber
  • Necolatis

Aircrafts that use this system

Saab JA-37 Viggen (version 2.83+)

Related content