UEVR: Lua API

UEVR provides a Lua API that can be used to create plugins.

Scripts can be loaded under LuaLoader, and are automatically loaded from <game config folder>/scripts/*.lua.

The Lua API can also be loaded in different environments, like UE4SS. UEVR comes with a LuaVR.dll for this purpose.

Example

print("Initializing hello_world.lua")

UEVR_UObjectHook.activate()

local api = uevr.api;
local uobjects = uevr.types.FUObjectArray.get()

print("Printing first 5 UObjects")
for i=0, 5 do
    local uobject = uobjects:get_object(i)

    if uobject ~= nil then
        print(uobject:get_full_name())
    end
end

local once = true
local last_world = nil
local last_level = nil

uevr.sdk.callbacks.on_post_engine_tick(function(engine, delta)

end)

local spawn_once = true

uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta)
    --[[if spawn_once then
        local cheat_manager_c = api:find_uobject("Class /Script/Engine.CheatManager")
        local cheat_manager = UEVR_UObjectHook.get_first_object_by_class(cheat_manager_c)

        print(tostring(cheat_manager_c))

        cheat_manager:Summon("Something_C")

        spawn_once = false
    end]]

    local game_engine_class = api:find_uobject("Class /Script/Engine.GameEngine")
    local game_engine = UEVR_UObjectHook.get_first_object_by_class(game_engine_class)

    local viewport = game_engine.GameViewport
    if viewport == nil then
        print("Viewport is nil")
        return
    end
    local world = viewport.World

    if world == nil then
        print("World is nil")
        return
    end

    if world ~= last_world then
        print("World changed")
    end

    last_world = world

    local level = world.PersistentLevel

    if level == nil then
        print("Level is nil")
        return
    end

    if level ~= last_level then
        print("Level changed")
        print("Level name: " .. level:get_full_name())

        local game_instance = game_engine.GameInstance

        if game_instance == nil then
            print("GameInstance is nil")
            return
        end

        local local_players = game_instance.LocalPlayers

        for i in ipairs(local_players) do
            local player = local_players[i]
            local player_controller = player.PlayerController
            local pawn = player_controller.Pawn
    
            if pawn ~= nil then
                print("Pawn: " .. pawn:get_full_name())
                --pawn.BaseEyeHeight = 0.0
                --pawn.bActorEnableCollision = not pawn.bActorEnableCollision

                local actor_component_c = api:find_uobject("Class /Script/Engine.ActorComponent");
                print("actor_component_c class: " .. tostring(actor_component_c))
                local test_component = pawn:GetComponentByClass(actor_component_c)

                print("TestComponent: " .. tostring(test_component))

                local controller = pawn.Controller

                if controller ~= nil then
                    print("Controller: " .. controller:get_full_name())

                    local velocity = controller:GetVelocity()
                    print("Velocity: " .. tostring(velocity.x) .. ", " .. tostring(velocity.y) .. ", " .. tostring(velocity.z))

                    local test = Vector3d.new(1.337, 1.0, 1.0)
                    print("Test: " .. tostring(test.x) .. ", " .. tostring(test.y) .. ", " .. tostring(test.z))

                    controller:SetActorScale3D(Vector3d.new(1.337, 1.0, 1.0))

                    local actor_scale_3d = controller:GetActorScale3D()
                    print("ActorScale3D: " .. tostring(actor_scale_3d.x) .. ", " .. tostring(actor_scale_3d.y) .. ", " .. tostring(actor_scale_3d.z))

                    
                    local control_rotation = controller:GetControlRotation()

                    print("ControlRotation: " .. tostring(control_rotation.Pitch) .. ", " .. tostring(control_rotation.Yaw) .. ", " .. tostring(control_rotation.Roll))
                    control_rotation.Pitch = 1.337

                    controller:SetControlRotation(control_rotation)
                    control_rotation = controller:GetControlRotation()

                    print("New ControlRotation: " .. tostring(control_rotation.Pitch) .. ", " .. tostring(control_rotation.Yaw) .. ", " .. tostring(control_rotation.Roll))
                end

                local primary_actor_tick = pawn.PrimaryActorTick

                if primary_actor_tick ~= nil then
                    print("PrimaryActorTick: " .. tostring(primary_actor_tick))

                    -- Print various properties, this is testing of StructProperty as PrimaryActorTick is a struct
                    local tick_interval = primary_actor_tick.TickInterval
                    print("TickInterval: " .. tostring(tick_interval))

                    print("bAllowTickOnDedicatedServer: " .. tostring(primary_actor_tick.bAllowTickOnDedicatedServer))
                    print("bCanEverTick: " .. tostring(primary_actor_tick.bCanEverTick))
                    print("bStartWithTickEnabled: " .. tostring(primary_actor_tick.bStartWithTickEnabled))
                    print("bTickEvenWhenPaused: " .. tostring(primary_actor_tick.bTickEvenWhenPaused))
                else
                    print("PrimaryActorTick is nil")
                end

                local control_input_vector = pawn.ControlInputVector
                pawn.ControlInputVector.x = 1.337

                print("ControlInputVector: " .. tostring(control_input_vector.x) .. ", " .. tostring(control_input_vector.y) .. ", " .. tostring(control_input_vector.z))

                local is_actor_tick_enabled = pawn:IsActorTickEnabled()
                print("IsActorTickEnabled: " .. tostring(is_actor_tick_enabled))

                pawn:SetActorTickEnabled(not is_actor_tick_enabled)
                is_actor_tick_enabled = pawn:IsActorTickEnabled()
                print("New IsActorTickEnabled: " .. tostring(is_actor_tick_enabled))

                pawn:SetActorTickEnabled(not is_actor_tick_enabled) -- resets it back to default

                local life_span = pawn:GetLifeSpan()
                local og_life_span = life_span
                print("LifeSpan: " .. tostring(life_span))

                pawn:SetLifeSpan(10.0)
                life_span = pawn:GetLifeSpan()

                print("New LifeSpan: " .. tostring(life_span))
                pawn:SetLifeSpan(og_life_span) -- resets it back to default

                local net_driver_name = pawn.NetDriverName:to_string()

                print("NetDriverName: " .. net_driver_name)
            end
    
            if player_controller ~= nil then
                print("PlayerController: " .. player_controller:get_full_name())
            end
        end

        print("Local players: " .. tostring(local_players))
    end

    last_level = level

    if once then
        print("executing stat fps")
        uevr.api:execute_command("stat fps")
        once = false

        print("executing stat unit")
        uevr.api:execute_command("stat unit")

        print("GameEngine class: " .. game_engine_class:get_full_name())
        print("GameEngine object: " .. game_engine:get_full_name())
    end
end)

uevr.sdk.callbacks.on_script_reset(function()
    print("Resetting hello_world.lua")
end)

Example (when integrated into an environment like UE4SS)

local LuaVR = require("LuaVR")

local function vr_print(text)
    print("[LuaVR Script] " .. text .. "\n")
end

local params = LuaVR.params
local callbacks = params.sdk.callbacks

local total_t = 0.0

-- Example usage of callbacks
callbacks.on_pre_engine_tick(function(engine, delta)
    total_t = total_t + delta
end)

-- Modifies the camera position
callbacks.on_post_calculate_stereo_view_offset(function(device, view_index, world_to_meters, position, rotation, is_double)
    position.z = position.z + 100.0
    position.y = position.y - 100.0
end)



-- UEVR_PluginInitializeParam

-- UEVR_PluginVersion
vr_print("Major: " .. tostring(params.version.major))
vr_print("Minor: " .. tostring(params.version.minor))
vr_print("Patch: " .. tostring(params.version.patch))

-- UEVR_PluginFunctions
vr_print("Is drawing ui: " .. tostring(params.functions.is_drawing_ui()))
params.functions.log_info("Hello from LuaVR!")
params.functions.log_warn("Hello from LuaVR!")
params.functions.log_error("Hello from LuaVR!")

-- UEVR_VRData
vr_print("Runtime ready state: " .. tostring(params.vr.is_runtime_ready()))
vr_print("Is OpenVR: " .. tostring(params.vr.is_openvr()))
vr_print("Is OpenXR: " .. tostring(params.vr.is_openxr()))
vr_print("Is HMD Active: " .. tostring(params.vr.is_hmd_active()))

local standing_origin = UEVR_Vector3f.new()
params.vr.get_standing_origin(standing_origin)
vr_print("Standing Origin: " .. tostring(standing_origin.x) .. ", " .. tostring(standing_origin.y) .. ", " .. tostring(standing_origin.z))

local rotation_offset = UEVR_Vector3f.new()
params.vr.get_rotation_offset(rotation_offset)
vr_print("Rotation Offset: " .. tostring(rotation_offset.x) .. ", " .. tostring(rotation_offset.y) .. ", " .. tostring(rotation_offset.z))

local hmd_index = params.vr.get_hmd_index()
vr_print("HMD Index: " .. tostring(hmd_index))

local left_controller_index = params.vr.get_left_controller_index()
vr_print("Left Controller Index: " .. tostring(left_controller_index))

local right_controller_index = params.vr.get_right_controller_index()
vr_print("Right Controller Index: " .. tostring(right_controller_index))

local hmd_position = UEVR_Vector3f.new()
local hmd_rotation = UEVR_Quaternionf.new()
params.vr.get_pose(hmd_index, hmd_position, hmd_rotation)

vr_print("HMD Position: " .. tostring(hmd_position.x) .. ", " .. tostring(hmd_position.y) .. ", " .. tostring(hmd_position.z))
vr_print("HMD Rotation: " .. tostring(hmd_rotation.x) .. ", " .. tostring(hmd_rotation.y) .. ", " .. tostring(hmd_rotation.z) .. ", " .. tostring(hmd_rotation.w))

if left_controller_index ~= -1 then
    local left_controller_position = UEVR_Vector3f.new()
    local left_controller_rotation = UEVR_Quaternionf.new()
    params.vr.get_pose(left_controller_index, left_controller_position, left_controller_rotation)

    vr_print("Left Controller Position: " .. tostring(left_controller_position.x) .. ", " .. tostring(left_controller_position.y) .. ", " .. tostring(left_controller_position.z))
    vr_print("Left Controller Rotation: " .. tostring(left_controller_rotation.x) .. ", " .. tostring(left_controller_rotation.y) .. ", " .. tostring(left_controller_rotation.z) .. ", " .. tostring(left_controller_rotation.w))        
end

if right_controller_index ~= -1 then
    local right_controller_position = UEVR_Vector3f.new()
    local right_controller_rotation = UEVR_Quaternionf.new()
    params.vr.get_pose(right_controller_index, right_controller_position, right_controller_rotation)

    vr_print("Right Controller Position: " .. tostring(right_controller_position.x) .. ", " .. tostring(right_controller_position.y) .. ", " .. tostring(right_controller_position.z))
    vr_print("Right Controller Rotation: " .. tostring(right_controller_rotation.x) .. ", " .. tostring(right_controller_rotation.y) .. ", " .. tostring(right_controller_rotation.z) .. ", " .. tostring(right_controller_rotation.w))        
end

local left_eye_offset = UEVR_Vector3f.new()
local right_eye_offset = UEVR_Vector3f.new()

params.vr.get_eye_offset(0, left_eye_offset)
params.vr.get_eye_offset(1, right_eye_offset)

vr_print("Left Eye Offset: " .. tostring(left_eye_offset.x) .. ", " .. tostring(left_eye_offset.y) .. ", " .. tostring(left_eye_offset.z))
vr_print("Right Eye Offset: " .. tostring(right_eye_offset.x) .. ", " .. tostring(right_eye_offset.y) .. ", " .. tostring(right_eye_offset.z))

local is_using_controllers = params.vr.is_using_controllers()
vr_print("Is Using Controllers: " .. tostring(is_using_controllers))