Docs menu · Luau API+

    You are reading the docs of SDK 1.10.1, an older release. See the latest (1.11.0).

    Reference · SDK 1.10.1

    The Luau API

    What a script mod can use: the openrac table. Scripts run in a sandbox with a 2 ms budget per call

    The openrac table

    A script mod sees one global, openrac, and the safe parts of Luau's library. For autocomplete, point your editor's Luau language server at the SDK's types/rac1.d.luau.

    openrac.game

    Since 1.1
    Type
    openrac.game: string

    openrac.game, .version, .platform, .api: "rac1", "pal" or "ntsc", "macos-arm64" ..., the game's interface version ("1.1")

    openrac.version

    Since 1.1
    Type
    openrac.version: "pal" | "ntsc"

    openrac.game, .version, .platform, .api: "rac1", "pal" or "ntsc", "macos-arm64" ..., the game's interface version ("1.1")

    openrac.platform

    Since 1.1
    Type
    openrac.platform: string

    openrac.game, .version, .platform, .api: "rac1", "pal" or "ntsc", "macos-arm64" ..., the game's interface version ("1.1")

    openrac.api

    Since 1.1
    Type
    openrac.api: string

    openrac.game, .version, .platform, .api: "rac1", "pal" or "ntsc", "macos-arm64" ..., the game's interface version ("1.1")

    openrac.log()

    Since 1.1
    Type
    openrac.log: (...any) -> ()

    openrac.log(...), openrac.warn(...): a line in the game's log, after your mod's id (print does the same as log)

    From the guide “Hooks”
    openrac.hook("rac1.player.set_planar_velocity", function(original, angle)
        original(angle)   -- the game's own, or the mod before yours
        openrac.log(`moving along {angle}`)
    end)
    
    openrac.after("rac1.player.advance_animation", function()
        -- runs after the game has moved the animation on
    end)
    +Example (1)
    From sdk/template-script/main.luau
    -- After each game tick (50 a second on PAL, 60 on NTSC-U).
    openrac.on_frame(function()
    	ticks += 1
    	if ticks % (every * 50) == 0 then
    		openrac.log(`still here after {ticks} ticks`)
    	end
    end)

    openrac.warn()

    Since 1.1
    Type
    openrac.warn: (...any) -> ()

    openrac.log(...), openrac.warn(...): a line in the game's log, after your mod's id (print does the same as log)

    openrac.setting()

    Since 1.1
    Type
    openrac.setting: (key: string) -> any

    openrac.setting(key): one of your settings

    From the guide “Script mods (Luau)”
    --!strict
    local swap = openrac.setting("pair") :: string
    
    openrac.on_pad(function(port: number, report: buffer)
        local buttons = buffer.readu16(report, 0)   -- a bit is clear while its button is held
        -- ... change buttons ...
        buffer.writeu16(report, 0, buttons)
    end)
    
    openrac.on_frame(function()
        -- after each game tick: 50 a second on PAL, 60 on NTSC-U
    end)
    +Example (1)
    From sdk/template-script/main.luau
    local every = openrac.setting("every") :: number -- the manifest's setting, as the launcher set it
    local ticks = 0

    openrac.hook()

    Since 1.1
    Type
    openrac.hook: (name: HookPoint, fn: (original: (...number) -> ...number, ...number) -> ...number) -> ()

    openrac.hook(id, fn): wrap or replace a hook point: fn(original, ...) gets its arguments; original(...) goes on down the chain and returns its result; original() with no arguments passes the call's own; return nothing to keep the original's result

    From the guide “Hooks”
    openrac.hook("rac1.player.set_planar_velocity", function(original, angle)
        original(angle)   -- the game's own, or the mod before yours
        openrac.log(`moving along {angle}`)
    end)
    
    openrac.after("rac1.player.advance_animation", function()
        -- runs after the game has moved the animation on
    end)
    +Example (1)
    From sdk/template-script/main.luau
    -- A hook point by name, wrapped: the game's own call goes on through `original`.
    -- openrac.hook("Hero_SetPlanarVelocity", function(original, angle: number)
    -- 	original(angle)
    -- end)

    openrac.before()

    Since 1.1
    Type
    openrac.before: (name: HookPoint, fn: (...number) -> ()) -> ()

    openrac.before(id, fn), openrac.after(id, fn): fn(...) before or after the rest of the chain, which runs whatever fn does

    openrac.after()

    Since 1.1
    Type
    openrac.after: (name: HookPoint, fn: (...number) -> ()) -> ()

    openrac.before(id, fn), openrac.after(id, fn): fn(...) before or after the rest of the chain, which runs whatever fn does

    From the guide “Hooks”
    openrac.hook("rac1.player.set_planar_velocity", function(original, angle)
        original(angle)   -- the game's own, or the mod before yours
        openrac.log(`moving along {angle}`)
    end)
    
    openrac.after("rac1.player.advance_animation", function()
        -- runs after the game has moved the animation on
    end)

    openrac.on_frame()

    Since 1.1
    Type
    openrac.on_frame: (fn: () -> ()) -> ()

    openrac.on_frame(fn): fn() after each game tick

    From the guide “Script mods (Luau)”
    --!strict
    local swap = openrac.setting("pair") :: string
    
    openrac.on_pad(function(port: number, report: buffer)
        local buttons = buffer.readu16(report, 0)   -- a bit is clear while its button is held
        -- ... change buttons ...
        buffer.writeu16(report, 0, buttons)
    end)
    
    openrac.on_frame(function()
        -- after each game tick: 50 a second on PAL, 60 on NTSC-U
    end)
    +Example (1)
    From sdk/template-script/main.luau
    -- After each game tick (50 a second on PAL, 60 on NTSC-U).
    openrac.on_frame(function()
    	ticks += 1
    	if ticks % (every * 50) == 0 then
    		openrac.log(`still here after {ticks} ticks`)
    	end
    end)

    openrac.on_pad()

    Since 1.1
    Type
    openrac.on_pad: (fn: (port: number, report: buffer) -> ()) -> ()

    openrac.on_pad(fn): fn(port, report) for each pad report before the game reads it; report is a buffer you may change

    From the guide “Script mods (Luau)”
    --!strict
    local swap = openrac.setting("pair") :: string
    
    openrac.on_pad(function(port: number, report: buffer)
        local buttons = buffer.readu16(report, 0)   -- a bit is clear while its button is held
        -- ... change buttons ...
        buffer.writeu16(report, 0, buttons)
    end)
    
    openrac.on_frame(function()
        -- after each game tick: 50 a second on PAL, 60 on NTSC-U
    end)
    +Example (1)
    From sdk/template-script/main.luau
    -- Each pad report, before the game reads it: `report` is a buffer the script may change (rac1-mods'
    -- button-swap shows its layout). Uncomment to see the buttons.
    -- openrac.on_pad(function(port: number, report: buffer)
    -- 	openrac.log(`port {port}: buttons {buffer.readu16(report, 0)}`) -- a bit clear for a button held
    -- end)

    openrac.read()

    Since 1.1
    Type
    openrac.read: (kind: MemoryType, address: number) -> number

    openrac.read(type, address), openrac.write(type, address, value): game memory; type is u8 i8 u16 i16 u32 i32 f32; addresses in game RAM only (below 32 MB)

    openrac.write()

    Since 1.1
    Type
    openrac.write: (kind: MemoryType, address: number, value: number) -> ()

    openrac.read(type, address), openrac.write(type, address, value): game memory; type is u8 i8 u16 i16 u32 i32 f32; addresses in game RAM only (below 32 MB)

    openrac.fields

    Since 1.1Experimental
    Type
    openrac.fields: Fields

    openrac.fields: game state by name, read and written as plain values: openrac.fields.bolts, openrac.fields.hero.position.x (experimental: see the reference)

    openrac.key()

    Since 1.2
    Type
    openrac.key: (bind: string) -> boolean

    openrac.key(bind), openrac.pressed(bind) (1.2): whether a key, mouse button or wheel direction is held, or went down since the last tick, by its name: openrac.key("KeyW"), openrac.pressed("MouseLeft"), openrac.pressed("WheelUp") (settings)

    openrac.pressed()

    Since 1.2
    Type
    openrac.pressed: (bind: string) -> boolean

    openrac.key(bind), openrac.pressed(bind) (1.2): whether a key, mouse button or wheel direction is held, or went down since the last tick, by its name: openrac.key("KeyW"), openrac.pressed("MouseLeft"), openrac.pressed("WheelUp") (settings)

    openrac.mouse()

    Since 1.2
    Type
    openrac.mouse: () -> (number, number, number, number)

    openrac.mouse() (1.2): dx, dy, wheel_x, wheel_y: the mouse's motion since the last tick and its wheel in notches

    openrac.window()

    Since 1.2
    Type
    openrac.window: () -> (boolean, boolean, boolean)

    openrac.window() (1.2): focused, captured, overlay: the window has the keyboard; the cursor is captured; OpenRAC's overlay has the input

    openrac.pointer()

    Since 1.3
    Type
    openrac.pointer: () -> (number, number, boolean)

    openrac.pointer() (1.3): x, y, inside: the cursor over the game's picture, 0..1 each way (x * 512, y * 416 is the game's 2D pixel), and whether it is over it

    openrac.take_input()

    Since 1.2
    Type
    openrac.take_input: (...("keyboard" | "mouse")) -> ()

    openrac.take_input("keyboard") (1.2): while your scripts first run: the port's own keyboard table stops pressing pad 0's buttons, so your on_pad presses them

    openrac.capture_mouse()

    Since 1.2
    Type
    openrac.capture_mouse: (capture: boolean) -> ()

    openrac.capture_mouse(on) (1.2): hide the cursor and keep it in the window (relative motion), or let it go

    openrac.pvars()

    Since 1.1Experimental
    Type
    openrac.pvars: (moby: number) -> { [string]: number }?

    openrac.pvars(moby): with the behaviour catalogue: the per-class variables of the moby at that address, by name (experimental)

    require()

    Since 1.1
    Type
    require: (name: string) -> any

    require(name): another .luau file of your mod's own folder: require("util"), require("lib/util")

    Types

    MemoryType

    Luau
    type MemoryType = "u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "f32"

    Fields

    Luau
    type Fields = {
        hero: {
            position: {
                x: number, -- Where the hero is (gHeroPos).
                y: number,
                z: number,
            },
            facing: number, -- The way he faces (rot.z), in radians.
            move_yaw: number, -- Where the stick moves him: the stick's angle plus the camera's yaw.
            state: number, -- read-only; His state: 0 standing, 2 running, 7 jumping, ...
        },
        bolts: number, -- The player's bolts.
    }

    Pad_ProcessReportFn

    rac1.pad.process_report (Pad_ProcessReport) void(u32,u32,i32): One pad report into the pad's state (PadState, at pad), each poll, before the tick reads it.

    Luau
    type Pad_ProcessReportFn = (pad: number, report: number, size: number) -> ()

    Hero_UpdateMotionInputFn

    rac1.player.update_motion_input (Hero_UpdateMotionInput) void(): The stick read into the hero, early in his update each tick.

    Luau
    type Hero_UpdateMotionInputFn = () -> ()

    Hero_TurnToHeadingFn

    rac1.player.turn_to_heading (Hero_TurnToHeading) void(i32,f32,f32,f32): Eases the hero's facing toward his move yaw.

    Luau
    type Hero_TurnToHeadingFn = (mode: number, accel: number, damping: number, limit: number) -> ()

    Hero_SetPlanarVelocityFn

    rac1.player.set_planar_velocity (Hero_SetPlanarVelocity) void(f32): The hero's velocity at his ground speed along an angle; an angle above pi means his facing.

    Luau
    type Hero_SetPlanarVelocityFn = (angle: number) -> ()

    FollowCamera_ApplyOverridesFn

    rac1.camera.follow.apply_overrides (FollowCamera_ApplyOverrides) void(u32): The follow camera's per-tick requests from the hero's state; the camera moby's update calls it before it places the eye.

    Luau
    type FollowCamera_ApplyOverridesFn = (camera: number) -> ()

    Hero_AdvanceAnimationFn

    rac1.player.advance_animation (Hero_AdvanceAnimation) void(): Ratchet's animation moved on by his animation speed, once a tick after his state's update.

    Luau
    type Hero_AdvanceAnimationFn = () -> ()

    Item_GiveFn

    rac1.item.give (Item_Give) void(i32,i32): Gives the player item id (GiveItem): sets its owned and obtained bytes, shows its banner, gives its starting ammo, marks it bought in the vendor's stock and adds it to the quick select; equip non-zero also puts it in hand. Every item the game gives goes through it but the Persuader's.

    Luau
    type Item_GiveFn = (id: number, equip: number) -> ()

    Planet_UnlockFn

    rac1.planet.unlock (Planet_Unlock) void(i32): Unlocks a planet's coordinates (UnlockPlanet): its flag (one byte per level), the galaxy list, and its banner unless it is the planet the player is on.

    Luau
    type Planet_UnlockFn = (planet: number) -> ()

    Vendor_InitLevelStockFn

    rac1.vendor.init_level_stock (Vendor_InitLevelStock) void(): The vendor's stock at each level start (VendorStockLevelInit): gives the Bomb Glove while the player has none, keeps the stock's known items and adds the level's new weapon.

    Luau
    type Vendor_InitLevelStockFn = () -> ()

    Vendor_BuildItemListFn

    rac1.vendor.build_item_list (Vendor_BuildItemList) void(i32): The vendor menu's list (VendorBuildItemList), from the stock and the items owned: each time the menu opens and after each purchase. skip_unflagged leaves out what is for sale (the menu opened from the pause screen).

    Luau
    type Vendor_BuildItemListFn = (skip_unflagged: number) -> ()

    GoldBolts_CountCollectedFn

    rac1.gold_bolt.count_collected (GoldBolts_CountCollected) i32(): The gold bolts collected, at most 40 (count_nonzero_entries_up_to_40): what the pause screen shows; the unspent ones are this less 4 per gold weapon.

    Luau
    type GoldBolts_CountCollectedFn = () -> number

    Hero_TakeDamageFn

    rac1.player.take_damage (Hero_TakeDamage) void(i32): Takes hit points off the hero's health (Hero_TakeDamage): at most one a call, none for 0, then the HUD's health bank. Every hit the hero takes goes through it but the instant kills (falls, crushers), which set his health to 0.

    Luau
    type Hero_TakeDamageFn = (amount: number) -> ()

    Moby_GetHitMessageFn

    rac1.combat.get_hit (Moby_GetHitMessage) u32(u32,i32,i32): The hit pending on moby (MobyGetHitMessage): the record of the last hit posted to it (+0x20 the attacker, +0x24 the flags, +0x2C the damage, an f32) when its flags match mask, else 0, dropping the hit unless keep. How the hero and every enemy read the hits they take, each tick, so a hit may be read more than once.

    Luau
    type Moby_GetHitMessageFn = (moby: number, mask: number, keep: number) -> number

    Weapon_TakeAmmoFn

    rac1.weapon.take_ammo (Weapon_TakeAmmo) i32(i32,i32): Takes amount of ammo from item for a shot (TakeAmmo; -1 is the item in hand): 1 when taken, 0 when there is too little. An item with no ammo limit takes none and gives 1.

    Luau
    type Weapon_TakeAmmoFn = (item: number, amount: number) -> number

    Moby_RecordDeathFn

    rac1.moby.record_death (Moby_RecordDeath) void(u32,i32,i32): Records a placed moby's death or a crate's break (SetDeathBits): its save id's killed bits (none for a save id below 0), its mission, then drops its bolts: about the s16 at +0xB4 (or what a tallied source has left of +0xB6), which it then clears.

    Luau
    type Moby_RecordDeathFn = (moby: number, flags: number, index: number) -> ()

    Level_LoadGameplayFn

    rac1.level.load_gameplay (Level_LoadGameplay) u32(i32): Loads a level's gameplay file into the level heap (LoadLevelGameplay), once per level start: the settings, the placed mobys and their pvars, the spare moby pool, the paths and the rest; returns the heap cursor. After it, every placed moby exists and none has run.

    Luau
    type Level_LoadGameplayFn = (full: number) -> number

    Hero_ApproachSpeedFn

    rac1.player.approach_speed (Hero_ApproachSpeed) void(f32,f32): Eases the hero's ground speed (+0x194, the speed Hero_SetPlanarVelocity moves him at) toward his target speed (+0x190) by up when below it and by down when above, each tick he moves on foot, in every level.

    Luau
    type Hero_ApproachSpeedFn = (up: number, down: number) -> ()

    Ship_TravelToFn

    rac1.ship.travel_to (Ship_TravelTo) void(i32): The ship's travel from the planet select (ShipTravelTo), once its menu closes: to the planet it is on (dest the level now), the landing again, as backing out of the select does; to another, the game saved to the card and the ship's flight out of the level, whose loop then ends and flies to dest. After the take-off; a level's only way to another planet by ship.

    Luau
    type Ship_TravelToFn = (dest: number) -> ()

    Moby_CollectBoltFn

    rac1.moby.collect_bolt (Moby_CollectBolt) void(u32): Collects a bolt (collect_bolt), called by the bolt update (classes 13 to 16) once the bolt reaches the hero: adds its value (1, or 5 / 20 / 50 for classes 14 / 15 / 16) to the bolt count, the level's total and its source's stats, shows the bolt counter and makes two sparkles at it. The bolt update then deletes the bolt (a placed one marked taken) whether or not this ran.

    Luau
    type Moby_CollectBoltFn = (bolt: number) -> ()

    MobyUpdateFn

    rac1.moby.<class>.update (experimental): (moby: number) -> (), or () -> () where it gets no moby.

    Luau
    type MobyUpdateFn = (moby: number) -> ()

    Generated from sdk/types/rac1.d.luau at sdk-v1.10.1. Edit this page or report a problem.