Search code examples
luaunreal-engine5

How to make a function repeat in lua, UE4SS


So there is this code that I downloaded from nexus mods:

local IDA, IDB
local LightComponent

function HookNewLight(ContextParam)
    local Context = ContextParam:get()

    LightComponent = Context:GetPropertyValue("PointLight")
    LightComponent:SetHiddenInGame(true, true)  
end

RegisterHook("/Script/Engine.PlayerController:ClientRestart", function(Context, NewPawn)
    if IDA == nil then
        IDA, IDB =     RegisterHook("/Game/Core/Characters/Player/AnathemaPlayerCharacter_BP.AnathemaPlayerCharacter_BP_C:Re    ceiveBeginPlay", HookNewLight)
    end
end)

function ToggleVisibility()
    if(LightComponent and LightComponent:IsValid()) then
        local NewState = LightComponent:IsVisible()
        LightComponent:SetHiddenInGame(NewState, true)
    end
end

RegisterKeyBind(Key.F3, ToggleVisibility)

It's in lua, getting loaded into an unreal engine 5 game using UE4SS, I was trying to make the ToggleVisibility() function repeat for the script to work properly, using timermanager:

function CheckVisibility()
if (LightComponent and LightComponent:IsValid() and LightComponent:IsVisible()) then
    ToggleVisibility()
end
-- Schedule the function to be called again after 0.1 seconds
TimerManager:SetTimer(function() CheckVisibility() end, 0.1, false)
end

hooking into the actor ticks:

RegisterHook("/Script/Engine.Actor:Tick", CheckVisibility)

,etc but none of them seems to work, usually it gives "attempt to index a nil value" error. I wonder if anyone knows how to make it repeat and could help.


Solution

  • I have no experience with Unreal Engine, but I see a couple things.

    1. "attempt to index a nil value" should be interpreted as, in simplified form, you tried to do this: nil[1] or nil:foo(). The wording tends to trip some people up. In this case, it makes the most sense that TimerManager does not actually exist in your script because I see no declaration or require, and from the docs on UE4SS, it does not appear under the global-functions section of the API; but it's hard to tell without more details.
    2. As I was reading through for ways to get a create Unreal classes, I came across the LoopAsync global function for setting up asynchronous loops. This should resolve your issue by foregoing the TimerManager completely, but if you still want to use that class specifically, you can look through to see if there's any global function that will create an Unreal class. From my read-through, it seems like you can only access ones already existing in memory, though.

    https://github.com/UE4SS-RE/RE-UE4SS/tree/main/docs/lua-api/global-functions

    local IDA, IDB
    local LightComponent
    
    function HookNewLight(ContextParam)
        local Context = ContextParam:get()
    
        LightComponent = Context:GetPropertyValue("PointLight")
        LightComponent:SetHiddenInGame(true, true)  
    end
    
    RegisterHook("/Script/Engine.PlayerController:ClientRestart", function(Context, NewPawn)
        if IDA == nil then
            IDA, IDB =     RegisterHook("/Game/Core/Characters/Player/AnathemaPlayerCharacter_BP.AnathemaPlayerCharacter_BP_C:Re    ceiveBeginPlay", HookNewLight)
        end
    end)
    
    function ToggleVisibility()
        if(LightComponent and LightComponent:IsValid()) then
            local NewState = LightComponent:IsVisible()
            LightComponent:SetHiddenInGame(NewState, true)
        end
    end
    
    RegisterKeyBind(Key.F3, function()
        -- call every 100 milliseconds / 0.1 seconds
        LoopAsync(100, function()
            ToggleVisibility()
    
            -- if you need to disable it, you can set up a flag or condition for
            -- this function to return true; otherwise it will loop forever
            return false
        end)
    end)