Skip to content

Lua apps

leBluem edited this page Jul 29, 2024 · 12 revisions

Since CSP 0.1.76, it’s possible to create apps using Lua v5.1, LuaJIT 2.1.0-beta3.

Hello World app

To create an app, make a new folder “MyFirstApp” (or anything else) in “assettocorsa/apps/lua” and add “manifest.ini” in there:

[ABOUT]
NAME = Hello World App
AUTHOR = …
VERSION = 1.0
DESCRIPTION = My first Lua app for Assetto Corsa
REQUIRED_VERSION = 0
URL = your favorite url

[CORE]
LAZY = FULL
; • NONE (or 0, default value): load script when Assetto Corsa is loading, run it until it’s closed
; • PARTIAL (or 1): load script only when app is first opened, after that keep it running until Assetto Corsa is closed.
; • FULL (or 2): load script when app is opened, when all windows are closed, unload an app completely.
; Note: when app unloads, all of its internal state (apart from stored with things like `ac.connect()`, 
; `ac.storage()` or `ac.store()`) is completely lost. That’s why sometimes it might make more sense to
; use partial laziness and unload app manually on closing (for example, if your app has manual saving
; and a user closed or hid window without saving).

[WINDOW_...]
ID = main
NAME = Hello World
ICON = icon.png
FUNCTION_MAIN = windowMain
FUNCTION_SETTINGS = windowSettings ; optional
SIZE = 400, 200
FLAGS = SETTINGS

; optional
[RENDER_CALLBACKS]
TRANSPARENT = Draw3D

After that, create “MyFirstApp.lua” in the same folder and add:

function script.windowMain(dt)
  ui.text('Hello world!')
end

-- optional
function script.windowSettings(dt)
  -- draw settings ui
end

-- optional 
function script.Draw3D(dt)
  -- draw something with the render. functions
end

-- optional, standard available function 
function script.update(dt)
  -- called each frame!!
end

More about manifest format

Manifest can define multiple windows, each of windows gets its own icon on AC apps taskbar. By default first “WINDOW_…” section acts as main window, although you can explicitly set main window with a window flag (more on that later).

When window is visible, function mentioned in “FUNCTION_MAIN” will be called each frame to draw its contents. Note: for UI CSP uses Dear ImGui library which has a different approach to building UI. Instead of creating buttons and text fields and such, arranging them in a certain way and keeping track of their state, it works in immediate mode. Write ui.button('Label') and it would create a button in the current cursor position and move that cursor. If button is clicked, function would return true. You can read more about its paradigm here.

Full window section format:

[WINDOW_...]
ID = WINDOW_ID          ; defaults to section name
NAME = Window Name      ; shown it title bar of a window and in taskbar
SIZE = width, height    ; default window size in pixels (can be scaled based on global UI scaling parameter)
MIN_SIZE = 40, 20       ; minimum window size
MAX_SIZE = ∞, ∞         ; maximum window size
PADDING = X, Y          ; if set, overwrites default window padding
FLAGS = …               ; window flags separated by comma:
  ; • AUTO_RESIZE: automatically resizes window to fit its content
  ; • DARK_HEADER: black font and symbols in titlebar
  ; • FADING: makes window fade when inactive, similar to chat app
  ; • FIXED_SIZE: prevents window from being resized
  ; • FLOATING_TITLE_BAR: text and symbols floating/stacked
  ; • HIDDEN_OFFLINE: makes the window hidden when in a singleplayer (offline) session (useful if the app use is not intended for offline)
  ; • HIDDEN_ONLINE: makes the window hidden when connected to an online server (useful if the app uses functions not available online)
  ; • HIDDEN_RENDER_CUSTOM: hide in custom screen setup
  ; • HIDDEN_RENDER_SINGLE: hide in single screen mode
  ; • HIDDEN_RENDER_TRIPLE: hide in triple screen
  ; • HIDDEN_RENDER_VR: hide in VR
  ; • MAIN: makes window act like main window (if not set, first window gets that role)
  ; • NO_BACKGROUND: makes background transparent
  ; • NO_COLLAPSE: hides collapse button
  ; • NO_SCROLL_WITH_MOUSE: stops mouse wheel from scrolling
  ; • NO_SCROLLBAR: hides scrollbar 
  ; • NO_TITLE_BAR: hides title bar
  ; • SETTINGS: adds settings button next to collapse and close buttons in title bar, opening settings window
  ; • SETUP: show in setup screen 
  ; • SETUP_HIDDEN: show on setup menu but not in app list
FUNCTION_MAIN = fn      ; function to be called each frame to draw window content
FUNCTION_SETTINGS = fn  ; function to be called to draw content of corresponding settings window (only with “SETTINGS” flag)
FUNCTION_ON_SHOW = fn   ; function to be called once when window opens
FUNCTION_ON_HIDE = fn   ; function to be called once when window closes
ICON = icon.png         ; name of window icon (icon is searched in app folder)
FUNCTION_ICON = fn      ; optional function to be called instead to draw a window icon, for dynamic icons

Additional app functions

script.update(dt)

Called each frame after world matrix traversal ends for each app, even if none of its windows are active. Please make sure to not do anything too computationally expensive there (unless app needs it for some reason).

Sim callbacks

Optional callbacks triggered at different points of AC loop, in case app would need to get actual data or do some work at a very specific point. Can be set in manifest like so:

[SIM_CALLBACKS]
FRAME_BEGIN = fn   ; if set, will be called right before scene has started rendering (can be used to move camera around)
UPDATE = fn        ; if set, function `script.fn()` will be called after a whole simulation update

Render callbacks

Optional render callbacks are triggered in certain points of main scene rendering process. Could be used to draw some extra shapes in world space: for example, if you are working on an app for positioning additional audio events in the world, those callbacks can be used to render some outlines for those audio events, as well as a moving helper.

[RENDER_CALLBACKS]
OPAQUE = fn       ; called when opaque geometry (objects without transparent flag) has finished rendering
TRANSPARENT = fn  ; called when transparent objects are finished rendering

At least at the moment it’s not meant to draw additional geometry, debug only shapes. To load and render additional models, use ac.SceneReference functions to find parent node, load extra KN5 in there and manipulate it.

UI callback

Optional UI callback is meant for creating fullscreen UIs coming together in a predesigned layout to achieve a certain visual style (for example, to recreate HUD of another racing game). Such HUDs can replace certain original elements: virtual mirrors, damage display and low fuel indicator (more coming a bit later). To stop original elements from working, use ac.redirect… functions. Function ui.drawVirtualMirror() can be used to draw virtual mirror.

[UI_CALLBACKS]
IN_GAME = fn  ; called before rendering ImGui apps to draw things on screen

Note: unlike window rendering functions, this one does not run from a window, so to draw things, you first would need to create a window using function like ui.transparentWindow() or ui.toolWindow().

Example app

For an example, here is a Lua HelloWorld app (it includes some extra media to test video-rendering functions).

Features:

  • Example of a simple fullscreen UI;
  • Example of a custom camera motion;
  • Example of Real Mirrors editor;
  • Example of OS integration: running new processes, use of file dialogs;
  • Console integration: adds new command eval, prints out things to console.

Other Resources

https://github.com/CheesyManiac/cheesy-lua/wiki/Getting-Started-with-CSP-Lua-Scripting