FlyWithLua

repository·master·Indexed 41 days ago

https://github.com/x-friese/flywithlua

A plugin that enables Lua scripting within X-Plane to extend and customize the flight simulator. It supports X-Plane 9, 10, 11, and 12 across Windows, Linux, and macOS. The project includes specialized editions such as FlyWithLua NG for X-Plane 11 and FlyWithLua NG Plus for X-Plane 12.

Tokens
18.6K
Snippets
65
Records
90
Agent score
52%

What's inside FlyWithLua

  1. Overview of FlyWithLua for X-Plane 12

    master

    FlyWithLua is a plugin designed specifically for X-Plane 12 (64-bit only). It allows users to extend the simulator's functionality using Lua scripting.

    For detailed instructions on how to use the plugin, refer to the Documentation subfolder within the repository.

  2. Overview of X-Plane Plugin SDK files

    master

    The X-Plane Plugin SDK provides the necessary files to build plugins for X-Plane. The SDK structure includes:

    • CHeaders: Header files for compiling C/C++ plugins.
    • Delphi: Interfaces for compiling Pascal plugins.
    • Libraries: Import libraries for Windows (linking) and frameworks for Mac (linking).

    Note for Linux developers: There are no import/link-time libraries for Linux. On Linux, plugins leave SDK symbols undefined and they are discovered at runtime.

  3. What is Sol2 and how is it used in FlyWithLua

    master
    Sol2 is a C++ library used within FlyWithLua to provide C++-friendly bindings for Lua. It facilitates interaction between C++ and Lua, including function binding and direct stack interaction. In this repository, Sol2 is maintained as a single-header version located in the ./upstream/ directory.
  4. Map Arcaze USB pins to X-Plane DataRefs

    master

    To make hardware actions affect the flight simulator, map the pin states to X-Plane DataRefs using the dataref() function.

    Example workflow:

    1. Initialize the Arcaze device.
    2. Define a writable DataRef using dataref(name, path, "writable").
    3. Create a function that reads the pins via arcaze.read_pins() and assigns the result to the DataRef variable.
    4. Execute that function every frame using do_every_frame().
    local my_arcaze = arcaze.open_first_device()
    
    if my_arcaze ~= nil then
        -- 1. Define the DataRef
        dataref("battery", "sim/cockpit/electrical/battery_on", "writable")
    
        -- 2. Create the update function
        function arcaze_battery_switch()
            -- 3. Map pin state directly to the DataRef variable
            battery = arcaze.read_pins(my_arcaze)
        end
    
        -- 4. Run every frame
        do_every_frame("arcaze_battery_switch()")
    end
  5. Understand and use custom commands

    master

    Commands are procedures provided by X-Plane or third-party addons. X-Plane's native commands always start with the sim/ prefix. Any command that does not start with sim/ is considered a custom command (e.g., from a specific aircraft or addon).

    To find command names in X-Plane:

    1. Go to Settings -> Joysticks & Equipment.
    2. Select the Buttons: Adv tab.
    3. Press an unassigned button to see its current assignment (e.g., sim/none/none).
    4. Use the textbox in the top right to copy the command name.
  6. How LuaXML maps XML to Lua tables

    master

    LuaXML provides a natural mapping between XML data and Lua tables. This allows you to parse and manipulate XML using standard Lua table access and iteration methods:

    • Substatements and text content: Represented as array data using numerical keys (e.g., x[1]).
    • Attributes and tags: Represented using string keys (e.g., x.id or x["id"]).
    • Tags: The tag name itself is stored at index 0 (or the symbolic name xml.TAG).

    This structure ensures that the XML hierarchy is preserved exactly during a read/write cycle.

    -- Example of the mapping logic
    local xfile = xml.load("test.xml")
    local xscene = xfile:find("scene")
    if xscene ~= nil then
      print(xscene.id)    -- Accessing an attribute via string key
      print(xscene[1])    -- Accessing the first substatement via numerical key
      xscene["id"] = "newId" -- Modifying an attribute
    end
  7. Implementation differences in GLUT for Win32 vs X11

    master

    When porting code from X11 to GLUT for Win32, be aware of the following behavioral differences:

    Command Line Options

    The following glutInit options are ignored in Win32:

    • -display
    • -indirect
    • -direct
    • -sync

    Window Positioning

    • glutPositionWindow(0, 0): Places the window client area at 0,0. This often results in window decorations (title bar/edges) being off-screen.
    • glutInitWindowPosition(0, 0): Places the window relative to the entire window space (including decorations), typically opening the window in the upper-left corner.

    Titles and Cursors

    • Titles: There is no distinction between Icon title and Window title in Win32; glutSetIconTitle has no effect.
    • Cursors: While most cursors match by meaning, GLUT_CURSOR_INFO and GLUT_CURSOR_SPRAY use the crosshair, and GLUT_CURSOR_CYCLE uses the 'no' or 'destroy' cursor.

    Visibility and Focus

    • Visibility: glutVisibilityFunc only reflects the Iconic, Hidden, or Shown state. A window is considered "visible" even if it is fully obscured by another window.
    • Focus: Win32 uses a "click to focus" policy. To change this to "focus follows mouse", you can use the Microsoft utility Tweak UI.
    • Menus: Menú operations are modal. The glutIdleFunc will not be called while a menu is active. However, timer functions will still fire.
    • Mouse Buttons: glutAttachMenu works best with left and right mouse buttons. If attached to the middle mouse button, the left button must be used to select items.
  8. Set up the Xbtn2cmd cross-compiling environment

    master

    Use the scripts in the docker/ directory to build Xbtn2cmd binaries for Windows, Linux, and OS X in a reproducible environment. This allows you to compile for all supported platforms without manually setting up independent build environments on your host system.

    Prerequisites

    1. Docker: Provides containers for a reproducible build environment.
    2. GNU Make: Used to build container images and launch build processes.
    # Ensure Docker and GNU Make are installed on your host system.
  9. Cross-compile FlyWithLua on Ubuntu for Windows and OS X

    master

    While not recommended over native builds or GitHub Actions, you can cross-compile from Ubuntu.

    1. Install Toolchains

    Install MinGW for Windows and Clang/osxcross for OS X:

    # Windows toolchain
    sudo apt-get install -y --no-install-recommends mingw-w64 g++-mingw-w64
    
    # OS X toolchain
    apt-get install -y --no-install-recommends clang curl
    # (Follow the detailed osxcross build steps in the source to set up /usr/osxcross)

    2. Build for Windows

    Use the provided MinGW toolchain file:

    cmake -S ./src -B ./build-win -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_TOOLCHAIN_FILE=src/Toolchain-mingw-w64-x86-64.cmake
    cmake --build ./build-win

    3. Build for OS X

    Use the provided osxcross toolchain file:

    cmake -S ./src -B ./build-mac -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_TOOLCHAIN_FILE=src/Toolchain-ubuntu-osxcross-10.11.cmake -DCMAKE_FIND_ROOT_PATH=/usr/osxcross/SDK/MacOSX10.11.sdk/
    cmake --build ./build-mac
    # Example: Building for Windows from Ubuntu
    cmake -S ./src -B ./build-win -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_TOOLCHAIN_FILE=src/Toolchain-mingw-w64-x86-64.cmake
    cmake --build ./build-win
  10. Integrate Arcaze USB hardware with FlyWithLua

    master

    To use Arcaze USB hardware for custom flight simulator controls (like radios, switches, or displays), you must first include the arcaze module. The workflow involves:

    1. Loading the module: Use require "arcaze".
    2. Opening the device: Use arcaze.open_first_device() to find and connect to the hardware. This function returns nil if no device is found, so always check for nil to prevent script crashes.
    3. Initializing displays: Use arcaze.init_display(device, connector, brightness) to set up 7-segment displays. Brightness is typically mapped from X-Plane DataRefs (0.0-1.0) to an integer range (e.g., 0-15).
    4. Reading inputs: Use arcaze.read_encoders(device) for rotary encoders and arcaze.read_pins(device) for digital buttons/switches.
    5. Updating displays: Use arcaze.show(device, connector, mode, string) to send text to the displays.

    Always wrap your logic in a function and register it with do_every_frame("function_name()") to ensure hardware state is polled and displays are updated continuously.

    require "arcaze"
    
    local my_arcaze = arcaze.open_first_device()
    
    if my_arcaze ~= nil then
        -- Initialize displays
        arcaze.init_display(my_arcaze, "1b", 15)
        
        function my_hardware_loop()
            -- Read inputs
            local big_wheel, small_wheel = arcaze.read_encoders(my_arcaze)
            local _, _, _, _, button = arcaze.read_pins(my_arcaze)
            
            -- Update displays
            arcaze.show(my_arcaze, "1b", 0xff, "123.456")
        end
        
        do_every_frame("my_hardware_loop()")
    end
  11. Implement continuous movement in custom commands

    master

    To create a command that increases a value continuously while a button is held (rather than just once per press), use the on_hold argument (the 4th argument) combined with os.clock() to calculate elapsed time.

    Because the on_hold code runs every frame, you should store the initial state (like the starting time and value) in the on_start argument (the 3rd argument) to avoid resetting the calculation every frame. Use \n to separate multiple lines of code within the string arguments.

    require("radio")
    local obs_up_time = 1
    local ops_up_value = 1
    
    create_command("FlyWithLua/testing/increase_OBS",
                   "This command increases the OBS value by one degree.",
                   "obs_up_time = os.clock() + 2\n
    	    obs_up_value = OBS1\n
    	    OBS1 = obs_up_value + 1",
                   "if os.clock() > obs_up_time then\n
    	      OBS1 = 10 * (os.clock() - obs_up_time) + obs_up_value\n
    	end",
                   "OBS1 = OBS1 % 360")