Interaction Procedures
To compliment Interaction Configurations you can create Interaction Procedures. These are essentially Input Events which are not necessarily linked to a specific interaction, and using them can be a good way to have an “Interaction Manager” when several interactions can potentially affect each other, or when the state of multiple things need to be checked.
To give an example, say your landing lights depend on the state of 2 different switches - the “normal” way to do it would be to have an input event for each switch, and every time the position of one of these switches is changed, they need to verify the state of the other switch to figure out if the light needs to be on or not. An alternative for this using a procedure is to have these switches only control their own state which is tracked by a variable for each switch. We then have a procedure that is watching these variables so that it updates when either is changed, with the update deciding on the state of the light based on these two variables. This means that you isolate the whole logic within the procedure rather than having two separate switches with two different logic setups regarding the state of the lights.
Another thing that procedures facilitates is binding an input - like a keyboard press - such that instead of having to bind two or three interactions with complex logic in each, you only need 1. We can also use procedures for things that go beyond a single interaction, for example, overriding the normal auto-start behavior. If your aircraft has some specific logic which means that the normal ⌃ Ctrl + E behavior would not start it, you can use a procedure to intercept that ⌃ Ctrl + E input and add some extra interactions to it.
Creating A Procedure
To create a procedure we will use the model behavior template ASOBO_Procedure_Template. When using this template the first thing we need to do is give it a unique identifier, like this:
<UseTemplate Name="ASOBO_Procedure_Template">
<PROCEDURE_ID>Wipers_Simulation</PROCEDURE_ID>
</UseTemplate>With this setup, the <UseTemplate> element will look for a Parameter Function helper named after your procedure following this pattern:
Get_Procedure_#PROCEDURE_ID#_ParametersSo - with the example code above and following the naming pattern - we would have to define a parameter function, which we’ll call Get_Procedure_Wipers_Simulation_Parameters:
<ParametersFn Name="Get_Procedure_Wipers_Simulation_Parameters">
<!-- (to be added) -->
</ParametersFn>To define the code that should be executed by the procedure we do not use ASOBO_Append_Init_Code_Helper (as we would have for interaction configurations), but instead we simply return the parameter ON_VALUE_CHANGED with, as content, the code you wish to execute when a watched variable changes value:
<ParametersFn Name="Get_Procedure_Test_Parameters">
<ReturnParameters>
<UseParametersFn Name="ASOBO_Watch_Simvar_Helper">
<SIMVAR>CIRCUIT SWITCH ON:1</SIMVAR>
</UseParametersFn>
<ON_VALUE_CHANGED>(A:CIRCUIT SWITCH ON:1, Bool) (O:_IsOn) != if{ (O:_IsOn) ! (>O:_IsOn) }</ON_VALUE_CHANGED>
</ReturnParameters>
</ParametersFn>Ticking
If your procedure relies on a lot of variables or needs to act on a system over time, you’ll want your code to execute every frame to watch for any relevant changes. To achieve this we use the parameter TICK_EVERY_FRAME, eg:
<TICK_EVERY_FRAME>True</TICK_EVERY_FRAME>This will configure the procedure for continuous updates, once per frame. You don’t need to (and shouldn’t) have any watched variables in this case. Note that delta time since last update is stored inside the macro variable @TICK_Get_Delta_Time. You can use this delta to apply changes to the system based on the elapsed simulation time. Since the ticking is based on simulation time, you can use the macro variable @E_SimulationTime to get the current time.
Helper Functions
Setting up procedures can be prone to errors, and so to help limit their occurrence some helper functions have been created in the model behavior templates:
ASOBO_Procedure_Next_Safe_Jump_Flag_Helper- Whenever your code needs to jump to a given flag you need to make sure not to reuse the same number twice or you’ll end up jumping to the wrong place. To fix that inconvenience jump flags should always be defined using using this helper function:<UseParametersFn Name="ASOBO_Procedure_Next_Safe_Jump_Flag_Helper"> <OUT_PARAM_NAME>AFTER_POS_CHANGED</OUT_PARAM_NAME> </UseParametersFn> <!-- AFTER_POS_CHANGED = 0 --> <UseParametersFn Name="ASOBO_Procedure_Next_Safe_Jump_Flag_Helper"> <OUT_PARAM_NAME>AFTER_POS_TICK</OUT_PARAM_NAME> </UseParametersFn> <!-- AFTER_POS_TICK = 1 --> <!-- (...) --> <ON_VALUE_CHANGED> (A:CIRCUIT SWITCH ON:1, Bool) (O:_IsOn) != if{ (O:_IsOn) ! (>O:_IsOn) } els{ g#AFTER_POS_CHANGED# } (* Position just changed *) :#AFTER_POS_CHANGED# (* Position tick *) :#AFTER_POS_TICK# </ON_VALUE_CHANGED>Every time you call this function it will give you a new flag you can use, different to the previous one. By default the value will be output in the parameter
NEXT_SAFE_JUMP_FLAG, but you should always name the output parameter differently if you want to call this function several times in a row.ASOBO_Procedure_Get_Next_Safe_Var_Helper- In some instances you may want to ensure every stack variable is unique with it’s own unique index. To do so we use helper function. Every time you call this function, it will give you a new flag you can use, different from the one before:<UseParametersFn Name="ASOBO_Procedure_Get_Next_Safe_Var_Helper"> <OUT_PARAM_NAME>VAR_FROM</OUT_PARAM_NAME> </UseParametersFn> <!-- VAR_FROM = 0 --> <UseParametersFn Name="ASOBO_Procedure_Get_Next_Safe_Var_Helper"> <OUT_PARAM_NAME>VAR_TO</OUT_PARAM_NAME> </UseParametersFn> <!-- VAR_TO = 1 --> <!-- (...) --> <ON_VALUE_CHANGED> (A:CIRCUIT SWITCH ON:1, Bool) if{ 0 sp#VAR_FROM# 1 sp#VAR_TO# } els{ 1 sp#VAR_FROM# 0 sp#VAR_TO# } (O:_Var) != l#VAR_TO# if{ (* interpolate *) } </ON_VALUE_CHANGED>By default the value will be output in the parameter
NEXT_SAFE_VARbut you should always name the output parameter differently as you want to call this function several times in a row.ASOBO_Move_Interpolation_Helper- Interpolation between 2 values can be simplified by using this helper function, which takes 5 parameters:START_VALUE- Where to interpolate from.END_VALUE- Where to interpolate to.SET_NEW_VALUE- Setter for the new computed value.COEFFICIENT- Alpha coefficient (0 to 1) of the desired value.OUT_PARAM_NAME- Name of the parameter containing the generated code snippet.
Note that this is is usually a calculation you want to do over time to get a smooth interpolation. Here is a concrete example which updates the cabin altitude at a given rate:
<Parameters Type="Override"> <UseParametersFn Name="ASOBO_Aircraft_Sim_Parameters"/> </Parameters> <Parameters Type="Override"> <!-- altitude is converted back to pressure externally --> <UseParametersFn Name="ASOBO_Move_Interpolation_Helper"> <START_VALUE>(O:_CurrentPlaneAltitude)</START_VALUE> <SET_NEW_VALUE>(>O:_CurrentPlaneAltitude)</SET_NEW_VALUE> <END_VALUE>#GET_PLANE_ALTITUDE#</END_VALUE> <COEFFICIENT>(O:_CabinAltRate) @TICK_Get_Delta_Time *</COEFFICIENT> <OUT_PARAM_NAME>LERP_CABIN_ALTITUDE</OUT_PARAM_NAME> </UseParametersFn> </Parameters> <ReturnParameters> <TICK_EVERY_FRAME>True</TICK_EVERY_FRAME> <ON_VALUE_CHANGED> (O:_CurrentPlaneAltitude) (O:_TargetPlaneAltitude) != if{ (* Check if the target altitude is attainable using outside air pressure *) (O:_CurrentPlaneAltitude) (O:_TargetPlaneAltitude) < (O:_CurrentPlaneAltitude) #GET_PLANE_ALTITUDE# < == if{ #LERP_CABIN_ALTITUDE# } els{ (* Need to use an other mean to attain the target altitude *) } } </ON_VALUE_CHANGED> </ReturnParameters>
Creating Custom Input Variables (B: Vars)
Input variables are tied to a specific aircraft but are accessible everywhere. Behind the scenes they are essentially input events (B: vars in RPN), defined using some of the model behavior templates (which one you use depend son the variable type). You can create the following two kinds of custom input variables:
Boolean - To create a bool variable use the template
ASOBO_Bool_Variable_Template. This template takes aVARIABLE_IDand exposes an interface to interact with the variable, for example:<!-- Declare the variable --> <UseTemplate Name="ASOBO_Bool_Variable_Template"> <VARIABLE_ID>Pitot_Damage_Visible</VARIABLE_ID> </UseTemplate> <!-- Check the variable value --> (B:VARIABLE_Pitot_Damage_Visible, Bool) <!-- Toggle the variable --> (>B:VARIABLE_Pitot_Damage_Visible_Toggle) <!-- Set the variable --> (O:_IsDamageVisible) (>B:VARIABLE_Pitot_Damage_Visible_Set)Number - To create a variable with a value other than a boolean we use the template
ASOBO_Number_Variable_Template. This template takes aVARIABLE_IDand - optionally - aVALUE_UNITSstring (Number, by default) and also exposes an interface to interact with the variable, for example:<!-- Declare the variable --> <UseTemplate Name="ASOBO_Number_Variable_Template"> <VARIABLE_ID>Fuel_Temperature</VARIABLE_ID> <VALUE_UNITS>celsius</VALUE_UNITS> </UseTemplate> <!-- Check the variable value --> (B:VARIABLE_Fuel_Temperature) <!-- Check the variable value in a different unit --> (B:VARIABLE_Fuel_Temperature, farenheit) <!-- Set the variable --> (O:_ComputedFuelTemperature, celsius) (>B:VARIABLE_Fuel_Temperature_Set) <!-- Add or remove a value using Inc and Dec --> 15 (>B:VARIABLE_Fuel_Temperature_Inc) (* +15°C *) 12.3 (>B:VARIABLE_Fuel_Temperature_Dec) (* -12.3°C *)
Variable Configuration
If you wish to define your variable completely, you can use the template ASOBO_Variable_Template. By default it will read the variable configuration in the function Get_Variable_#VARIABLE_ID#_Parameters but you can provide a different name to your function using the parameter GET_VARIABLE_PARAMETERS.
Events
By default every event will simply forward the first argument sent alongside the expression:
<SaveParameters ID="Event_Parameters_Config" Append="Default">
<INC_PARAM_0>p0</INC_PARAM_0>
<INC_PARAM_0_TYPE>Float</INC_PARAM_0_TYPE>
<INC_PARAM_0_IS_DYNAMIC>True</INC_PARAM_0_IS_DYNAMIC>
<DEC_PARAM_0>p0</DEC_PARAM_0>
<DEC_PARAM_0_TYPE>Float</DEC_PARAM_0_TYPE>
<DEC_PARAM_0_IS_DYNAMIC>True</DEC_PARAM_0_IS_DYNAMIC>
<SET_PARAM_0>p0</SET_PARAM_0>
<SET_PARAM_0_TYPE>Float</SET_PARAM_0_TYPE>
<SET_PARAM_0_IS_DYNAMIC>True</SET_PARAM_0_IS_DYNAMIC>
</SaveParameters>The value is configured as Float but you can redefine everything by saving your own configuration in Event_Parameters_Config. If your events need to take more parameters you can specify how many using the following parameters, here are the defaults:
<IE_INC_ARG_COUNT>1</IE_INC_ARG_COUNT>
<IE_DEC_ARG_COUNT>1</IE_DEC_ARG_COUNT>
<IE_SET_ARG_COUNT>1</IE_SET_ARG_COUNT>To define what happens on those events you can return one or more INC_CODE, DEC_CODE, and SET_CODE.
Values
The value code will be evaluated by the RPN snippet contained in the VALUE_CODE parameter. If your value needs to react to WatchVars you can add an init code by returning the parameter ON_VALUE_CHANGED:
<VALUE_CODE>(O:_Myvar)</VALUE_CODE>
<ON_VALUE_CHANGED>(A:CIRCUIT ON:1, Bool) (>O:_Myvar)</ON_VALUE_CHANGED>
<UseParametersFn Name="ASOBO_Watch_Simvar_Helper">
<SIMVAR>CIRCUIT ON:1</SIMVAR>
</UseParametersFn>Intercepting Key Events
To create a procedure that intercepts key events, you’ll need to use the template ASOBO_Procedure_Template with the PROCEDURE_ID parameter set to Intercept_Key_Events, for example:
<Component ID="Procedures">
<UseTemplate Name="ASOBO_Procedure_Template">
<PROCEDURE_ID>Intercept_Key_Events</PROCEDURE_ID>
</UseTemplate>
</Component>When doing things this way you will also need to create some <SaveParameters> using the name GET_KEY_EVENTS_TO_INTERCEPT as the input, where each entry is a ParametersFn which need to return a list of <KEY_ID>CODE_TO_EXECUTE</KEY_ID>.
Intercepting Throttle Events
A common use key interception is to create throttle control procedures, so we’ll use that as an example, since the model behaviour templates provide a ParametersFn called ASOBO_Get_Throttle_Key_Events_To_Intercepts which does all the heavy lifting for us. It would be used like this:
<SaveParameters ID="GET_KEY_EVENTS_TO_INTERCEPT">
<ASOBO_Get_Throttle_Key_Events_To_Intercepts/>
</SaveParameters>The ASOBO_Get_Throttle_Key_Events_To_Intercepts function expects a parameters save called Throttles which will contain the usual <key>param</key> where - for each entry - the param is the throttle index and the key refers to the input event name associated with it:
<SaveParameters ID="Throttles">
<ENGINE_POWER_LEFT>1</ENGINE_POWER_LEFT>
<ENGINE_POWER_RIGHT>2</ENGINE_POWER_RIGHT>
</SaveParameters>The input event name will be used to set and get the throttle position at different steps, and - by default - we assume the throttle cut position is equal to 0 and the full position is equal to 1. To change this behavior you can either specify an alias for the positions, eg:
<THROTTLE_(CUT|FULL)_ALIAS_ID>ALIAS_FOR_THIS_POSITION</THROTTLE_(CUT|FULL)_ALIAS_ID>or a value (usually between 0 and 1 depending on how your input event is setup), eg:
<THROTTLE_(CUT|FULL)_VALUE>VALUE_FOR_THIS_POSITION</THROTTLE_(CUT|FULL)_VALUE>Putting this together we have:
Detents
In addition to the above, you can intercept the detent events by defining all the positions we want the throttle to teleport to (including full backward and forward). This requires some SaveParameters called Throttles which will contain a list of <dentent_index>detent_value</dentent_index>, eg:
<SaveParameters ID="Input_Detents">
<DETENT_0>0.0</DETENT_0>
<DETENT_1 Process="Float">@Ground_Idle 100 /</DETENT_1>
<DETENT_2 Process="Float">@Flight_Idle 100 /</DETENT_2>
<DETENT_3>1.0</DETENT_3>
</SaveParameters>Additional Information
The sections below cover some additional situations and workarounds when using procedures.
Running Normal Updates Alongside A Procedure
Proceedures can run alongside the normal updates simply by using the ASOBO_Add_Update_Helper template. This is very similar to setting up animation interactions etc… as explained here:
Assigning A Value To A Procedure
Under some circumstances you may wish a procedure to hold a value. This can be done using the ASOBO_Append_Value_Code_Helper parameter function template and giving it a VALUE_CODE and a VALUE_UNITS as follows:
<UseParametersFn Name="ASOBO_Append_Value_Code_Helper">
<VALUE_CODE>(O:_MyTrackedValue, psi)</VALUE_CODE>
<VALUE_UNITS>psi</VALUE_UNITS>
</UseParametersFn>Configuring A Procedure To Handle Events
To add event handling you have to define the content of the event you want to use (ie: the Inc, Dec and Setparts) and then you can event create bindings for your event, for example:
<UseParametersFn Name="ASOBO_Append_Set_Code_Helper">
<SET_CODE>(>O:XMLVAR_CAUTION_MASTER_ACTIVE)</SET_CODE>
</UseParametersFn>
<UseParametersFn Name="ASOBO_Append_Value_Code_Helper">
<VALUE_CODE>(O:XMLVAR_CAUTION_MASTER_ACTIVE)</VALUE_CODE>
<VALUE_UNITS>Bool</VALUE_UNITS>
</UseParametersFn>
<UseParametersFn Name="ASOBO_Add_Set_Binding_Helper">
<ALIAS_ID>Acknowledge</ALIAS_ID>
<PARAM_0>0</PARAM_0>
<PARAM_0_IS_DYNAMIC>False</PARAM_0_IS_DYNAMIC>
</UseParametersFn>Configuring Procedure Event Parameters
There is no built-in way to easily configure event parameters, and for now you have to manually add the IE (input event) parameters to the IE_PARAMS collection using the following (where [EV] is either INC, DEC or SET):
IE_[EV]_ARG_COUNT: Number of parameters this event expects.EV_PARAM_#: Param definition (where#is a number). Either a literal or an RPN snippet resulting in a literal.EV_PARAM_#_TYPE: Literal value type (where#is the param number).EV_PARAM_#_IS_DYNAMIC: Iftrue, the param (number#) has to be evaluated as RPN.
For example:
<SaveParameters ID="#IE_PARAMS#" Append="Override">
<IE_SET_ARG_COUNT>1</IE_SET_ARG_COUNT>
<SET_PARAM_0>p0</SET_PARAM_0>
<SET_PARAM_0_TYPE>String</SET_PARAM_0_TYPE>
<SET_PARAM_0_IS_DYNAMIC>True</SET_PARAM_0_IS_DYNAMIC>
</SaveParameters>