Commands

Using XLua, you can:

It is quite common to execute default X-Plane commands from Lua code. Indeed it is preferable to leverage X-Plane's default command strings wherever possible so that users will not have to map their hardware or keyboard to new commands just for your aircraft. Even though X-Plane saves per aircraft hardware/keyboard configurations, many users do not like to have to remap their hardware/keyboard.

For example, do not create a custom "gear_up" command when X-Plane already has one you can hook into. Prefer to replace, augment or filter X-Plane's commands first, and only create a custom command if no similiar X-Plane command string is available.


Phases and Duration

Every command you fetch/create in XLua requires associated callback functions that X-Plane calls whenever the command is being executed. X-Plane passes two parameters to these callback functions when called, to tell it what phase the command is in and also how long it has been running. The phase parameter (1 of 2) is an integer with the three possible values shown below.

Phase Value Description
Button Down / Command Start 0 A one-time, instantaneous event
Button hold / Command Hold 1 A recurring event, until the Button Up event
Button Up / Command Stop 2 A one-time, instantaneous event

Execution Methods

X-Plane Commands are typically executed through the keyboard or attached hardware; however, you can execute X-Plane commands from Lua also, via virtual button presses. These 'virtual presses' are accomplished using "class methods" on your command handle names. The following methods are available to execute commands in XLua. The examples that follow further below will illustrate these methods in use.

Method Description
:once( ) executes the command callback exactly two times, with phase = 0 and phase = 2 respectively
:start( ) executes the command callback one time with phase = 0, and then executes continuously with phase = 1
:stop( ) executes the command callback one time only with phase = 2

Executing Default

In order to execute default X-Plane commands from within XLua, you'll need to first fetch (find) the X-Plane command using XLua's find_command function to create what we call a command handle variable. Once you have done this, you can invoke the command by appending one of the above methods to the command handle name like so:


--fetch/find the XP command by string name and assign it to a command handle variable.
xcmd_pitch_trim_up  =   find_command("sim/flight_controls/pitch_trim_up")

--  execute the command in various ways by appending an execution method to the command handle name.
xcmd_pitch_trim_up:once()       --  Executes the XP command one time, as if you pressed a button (down/up, phase 0/2) to execute it
xcmd_pitch_trim_up:start()      --  Starts the XP command and then runs it continously until you stop it. (phase 0/1)
xcmd_pitch_trim_up:stop()       --  Stops the XP command (phase 2)

CAUTION

Every :start() call needs to be balanced with a :stop() call. You do not want any commands running indefinitnely!


Augmenting Default

Augmenting (wrapping) X-Plane Commands means wrapping additional functionality around X-Plane commands such that both you and X-Plane do something when the command is invoked. Default X-Plane commands are augmented by using the XLua wrap_command function and supplying TWO callback functions. One callback runs before X-Plane does and the other runs after. Though both callbacks have to exist, neither has to do anything and most times, you will only use one of them for your functionality. The example code block below shows how an X-Plane command may be augmented.


--  This example creates a custom dataref and alternates its value between 0/1 whenever a default X-Plane command is 
--  executed.  An FMOD sound COULD be linked to this dataref and played every time the dataref changes and since the
--  dataref changes every time the command is invoked, then the sound will play every time the XP command is invoked.
--  We have AUGMENTED a default X-Plane command to alter our custom dataref every time the command is executed, while
--  still letting X-Plane use the command for its original purpose. (to initiate raising the landing gear)
------------------------------------------------------------------------------------------------------------------------
-- Create a custom dataref and initialize it

cdr_play_gearup_sound       =   create_dataref("myairplane/command_wrap/sound/gear_up_sound",  "number")
cdr_play_gearup_sound       =   0   -- defaults to zero on creation FYI, but initializing is good practice.

------------------------------------------------------------------------------------------------------------------------
--  Create the TWO callbacks required with XLua's 'wrap_command' function.  
--  See 'best practices' for symbol naming conventions

function    xccb_gearup_sound_bhandler()    -- the "before handler" function
    --  empty function because there is nothing 
    --  to do before X-Plane's command code runs.
end


function    xccb_gearup_sound_ahandler()    --  the "after handler" function        
    --  alternate custom dataref value between 0/1 each time this function runs
    cdr_play_gearup_sound =  (1 - cdr_play_gearup_sound)        
end
------------------------------------------------------------------------------------------------------------------------
--  Create the command handle variable (object) using 'wrap_command'

--handle var name--                     --XP command string name--             --before handler name--      --after handler name--
xcmd_gear_up_sound  =   wrap_command("sim/flight_controls/landing_gear_up,  xccb_gearup_sound_bhandler,  xccb_gearup_sound_ahandler)  



Replacing Default

Replacing default X-Plane Commands means using (stealing) X-Plane's existing command string names, but replacing X-Plane's behaviors with your own whenever the command is executed. X-Plane does nothing when a default command that you have replaced here in XLua is executed. Your callback function will be called instead. In this way, a user does not have to remap their hardware to some custom command string just for your one customized aircraft. They can use standard X-Plane command names, but you can give them custom functionality if you need to.

Default X-Plane commands are replaced using XLua's replace_command function and supplying ONE callback function. The example code block below shows how an X-Plane command may be replaced.


--  Lets say we want to simulate a 'canopy safety latch', where you have to click on the latch (manipulator) in 3D to
--  release it so you can close a sliding canopy.  X-Plane's default command "sim/cockpit2/switches/canopy_close"
--  has no concept of such a latch and executing that command will close your canopy every time.  In order to achieve
--  this result, we're going to REPLACE X-Plane's command functionality with our own callback function and in that
--  function, we're going to check the state of this release latch before we allow the canopy to be closed via the XP
--  dataref, "sim/cockpit2/switches/canopy_open".  The release latch click spot in 3D is implemented via a 'button'
--  manipulator custom dataref and the latch animation itself is driven by a separate custom dataref.

--  NOTE:  A command manipulator is another way (and better) to implement this type of simulation.
--  See 'Best Practices > Button Presses' section of this manual. 
--====================================================================================================================
--  Fetch the XP datarefs relevant to the canopy.  Both the 'switch' and the 'open ratio' itself.
--  We will 'set/write' the switch dataref to close the canopy
--  We will 'test' the 'canopy_ratio' dataref to see if the canopy is open or closed

xdr_canopy_switch   =   find_dataref("sim/cockpit2/switches/canopy_open")
xdr_canopy_ratio    =   find_dataref("sim/flightmodel/controls/canopy_ratio")


------------------------------------------------------------------------------------------------------------------------
--  Create custom datarefs (and handler) for the manip click spot and the latch animation.  Callback functions
--  must be defined first, then "create" the custom datarefs below.

cdr_canopy_latch_manip_handler  =   function()

    --  this custom write DR callback runs every time the manipulator value changes.  For a 'button type', this means
    --  it changes for 'mouse down' AND 'mouse up' events, so it runs twice each time you "click" on the manipulator.
    --  In Blender we set the "down" value to 1 and the "up" value to 0.  We don't care about the 'up' value, only the
    --  down value, which when 1, means the user has "clicked/mouse_down" on the manipulator....that's when we do 
    --  our thing.  For this example  0 = 'canopy not restrained (latch open)'  and 1 = 'canopy restrained' 
    --  (latch closed)

    --  IF the canopy is open, then the latch can be "toggled" back and forth when clicking on the manipulator.  If the
    --  canopy is closed, then the latch cannot be toggled 'closed' at all, the canopy would block its movement.

    if  cdr_canopy_latch_manip  ==  1   then    --  user has clicked (mouse down) on the "button" type manipulator
        if  xdr_canopy_ratio > 0.95     then    --  canopy is 'open enough' to toggle the latch open/closed
            cdr_canopy_latch_anim = (1 - cdr_canopy_latch_anim)     --  toggle latch open/closed each manip click.
        end
    end
end

--  Create two custom datarefs.  One to process the manipulator click and another to animate the latch itself.  In many
--  cases, you can animate the 3D using the manip dataref also; however, when the animation is "dependent" upon
--  some criteria being satisfied first, then you will typically need "dataref pairs", one for the manip and another for 
--  the animation itself.  The manip callback will "determine" if the animation can proceed.  This is a relatively
--  common scenario.

cdr_canopy_latch_manip  =   create_dataref("myairplane/misc/canopy_latch_manip",    "number",   cdr_canopy_latch_manip_handler)
cdr_canopy_latch_anim   =   create_dataref("mhairplane/misc/canopy_latch_anim") 

----------------------------------------------------------------------------------------------------------------------
--  REPLACE the default XP command with your own callback and test for the canopy latch position.  If 'unlocked'
--  then set the XP Dataref for the canopy switch to 0.  This will cause XP to 'close the canopy'. 

function    xcmd_close_canopy_handler(phase,    duration)

    if  phase   ==  0   then -- command start
        if  cdr_canopy_latch_anim   ==  0   then -- latch is 'unlocked' and canopy can close
            xdr_canopy_switch = 0   --  set XP canopy switch to 0, causing XP to close the canopy (if its open)
        end
    else -- phase 'hold' or 'stop'
        -- do nothing on command hold/stop.  We only take action on 'command start'.  Very common.
    end
end

--  Tell XP you are 'replacing' its command for canopy close.
xcmd_close_canopy   =   replace_command("sim/flight_controls/canopy_close",     xcmd_close_canopy_handler)



Filtering Default

There are a few default XP commands, like sim/flightcontrols/drop_tank where no dataref exists that you can set from a callback to initiate some action, yet you need to specify some additional criteria before you want the command to do what it does. So for this drop_tank example, lets say you want to check the position of an "arming" switch, before you actually drop the fuel tank. Without any other way to drop a tank via dataref, then replacing the command would not work since you could not set any dataref based on your custom criteria checks in code.

For this reason, XLua has a filter_command function. When using filter_command, you specify the default X-Plane command you want to "filter" and provide ONE callback. Your callback function should return true if you want the command to execute (or continue to execute if running), or return false if you do not want the command to execute.

The filter_command function is more the 'exception' than the 'rule'. Most default X-Plane commands simply set dataref values, which means you can add custom criteria in a replace_command callback as shown above and set those same datarefs yourself, but every now and then, you come across some commands like this 'drop tank' example, where filter_command is your only way to add additional criteria checks before letting a command execute its functionality.


--  Define the filter callback function with your filtering criteria.
xcmd_drop_tanks_handler =   function()
    if  cdr_arm_switch_anim ==  1   then    --  drop tank switch is armed
        return true     --  return TRUE to let X-Plane go ahead with its command functionality
    else
        return false    --  return FALSE to stop x-Plane from executing its command functionality
    end
end

--  declare the command to be filtered, provide filter callback
xcmd_drop_tank  =   filter_command("sim/flightcontrols/droptank",   xcmd_drop_tanks_handler)


Creating Custom

To create a custom command in XLua, use the create_command function. Pass in three parameters and define ONE callback function.



--  Define the command callback. See 'Best Practices > Custom Command Names' section
function    ccb_myCustomCommand_handler(phase,  duration)

    if      phase   ==  0   then        --  command start
        -- maybe do stuff, maybe not
    elseif  phase   ==  1   then        --  command 'hold'
        -- maybe do stuff, maybe not
    elseif  phase   ==  2   then        --  command 'stop
        -- maybe do stuff, maybe not
    end

    if  duration > 1.2  then
        -- do something AFTER the command has been 'held' for more than 1.2 seconds.  NOTE that this block would run
        -- continuously after 1.2 seconds as long as the command is being 'held'.  you could do something like:
    end

end

--  Declare the custom command

-- command handle--                           --command string name--              --description--        --command handler name--
ccmd_myCustomCommand    =   create_command("myairplane/blahblah/myCustomCommand", "Does cool stuff",    ccb_myCustomCommand_handler)