libsota - a helper library in lua for Shroud in lua.

Discussion in 'Lua Discussions' started by CatweazleX, Nov 14, 2019.

  1. Michael Vasq

    Michael Vasq Avatar

    Messages:
    21
    Likes Received:
    6
    Trophy Points:
    3
    Gender:
    Male
    0.3.8 version with 889 build failes on:
    ShroudUseLuaConsoleForPrint(true)
     
  2. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Thank you. I am on Build 890. Was sleeping and missed QA 889. So, no tests against 889.
    Trying to bring out another version as soon as possible that also run on 889.
     
  3. Michael Vasq

    Michael Vasq Avatar

    Messages:
    21
    Likes Received:
    6
    Trophy Points:
    3
    Gender:
    Male
    Hmm, i've just updated and didn't receive 890, where did you get it?:)
     
  4. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Have written my own updater. Runs in background, i do not want to wait on "updating..." before i can play. It also supports verifying and repairing an installation (without re-install)
    Sometimes the update is somewhat early. I call it proposed updates ;). (I don't know how to ask the server which version is current)
     
  5. Michael Vasq

    Michael Vasq Avatar

    Messages:
    21
    Likes Received:
    6
    Trophy Points:
    3
    Gender:
    Male
    Restarted launcher and still at 889 build, strange. nvm, if in newer version it works - fine.

    p.s.
    < Michael Vasq, i've seen ya at QA i think :)
     
  6. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    yes , i was to silly to get the lua script into shroud. at the time when the one and only file named "Lua\BootStrap.lua" (yes, with backslash in filename)
     
  7. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    preview
    Code:
    -- libsota.0.4.0 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        isHitching = false,
        isLoading = true,
        _frame = 0,
        _ts = os.time(),
        _isLoading = false,
        _statEnum = {},
        _statDescr = {},
        --_queue = {},
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        isAfk = nil,
        isMoving = false,
        isStill = false,
        lastMoved = os.time(),
        location = {},
        health = {},
        focus = {},
        xp = function() return { producer = ShroudGetPooledProducerExperience(), adventurer = ShroudGetPooledAdventurerExperience() } end,
        stat = function(index)
            local ret = {
                number = -1,
                name = "invalid",
                value = -999,
                description = "",
            }
            if tonumber(index) == nil then
                index = client._statEnum[index]
            else
                index = tonumber(index)
            end
            if index and index <= #client._statEnum then
                ret.number = index
                ret.name = client._statEnum[index]
                ret.value = ShroudGetStatValueByNumber(index)
                ret.description = client._statDescr[index]
            end
            return ret
        end,
    }
    ui = {
        timer = {
            _ts = os.time(),
            _granulaty = 0.100,
            list = {},
            add = function(timeout, once, callback, ...)
                local index = #ui.timer.list + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer.list[index] = {
                    _index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    callback = callback,
                    userdata = ...,
                }
                return index
            end,
            get = function(index)
                return ui.timer.list[index]
            end,
            remove = function(index)
                ui.timer.list[index] = nil
            end,
            enabled = function(index, enabled)
                if enabled ~= nil then
                    ui.timer.list[index].enabled = enabled
                end
                return ui.timer.list[index].enabled
            end,
            pause = function(index)
                ui.timer.list[index].enabled = false
            end,
            resume = function(index)
                ui.timer.list[index].enabled = true
            end,
            toggle = function(index)
                ui.timer.list[index].enabled = not ui.timer.list[index].enabled
            end,
        },
        setTimeout = function(timeout, callback) return ui.timer.add(timeout, true, callback) end,
        setInterval = function(interval, callback) return ui.timer.add(interval, false, callback) end,
    
       
        handler = {
            list = {},
            add = function(name, callback)
                local index = #ui.handler.list + 1
    
                ui.handler.list[index] = {
                    _index = index,
                    name = name,
                    callback = callback,
                }
                return index   
            end,
            remove = function(index)
                ui.handler.list[index] = nil
            end,
            invoke = function(name, ...)
                for _,h in next, ui.handler.list do
                    if h.name == name then
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.callback(...)
                    end
                end
            end,
        },
        onInit = function(callback) ui.handler.add("_init", callback) end,
        onStart = function(callback) ui.handler.add("_start", callback) end,
        onUpdate = function(callback) ui.handler.add("_update", callback) end,
        onConsoleInput = function(callback) ui.handler.add("_consoleInput", callback) end,
        onConsoleCommand = function(callback) ui.handler.add("_consoleCommand", callback) end,
        onSceneChanged = function(callback) ui.handler.add("_sceneChanged", callback) end,
        onPlayerChanged = function(callback) ui.handler.add("_playerChanged", callback) end,
        onPlayerMoveStart = function(callback) ui.handler.add("_playerMoveStart", callback) end,
        onPlayerMoveStop = function(callback) ui.handler.add("_playerMoveStop", callback) end,
        onPlayerIsStill = function(callback) ui.handler.add("_playerIsStill", callback) end,
        onPlayerDamage = function(callback) ui.handler.add("_playerDamage", callback) end,
        onClientIsHitching = function(callback) ui.handler.add("_clientIsHitching", callback) end,
        onClientIsLoading = function(callback) ui.handler.add("_clientIsLoading", callback) end,
    
    
        label = {
            list = {},
            add = function(left, top, width, height, caption)
                local index = #ui.label.list + 1
    
                ui.label.list[index] = {
                    _index = index,
                    left = left,
                    top = top,
                    width = width,
                    height = height,
                    caption = caption,
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                }
                setmetatable(ui.label.list[index], {__index = ui.label})
                return index
            end,
            get = function(index)
                return ui.label.list[index]
            end,
            remove = function(index)
                ui.label.list[index] = nil
            end,
            rect = function(index, rect)
                local l = ui.label.list[index]
                l.left = rect.left
                l.top = rect.top
                l.width = rect.width
                l.height = rect.height
            end,
            caption = function(index, caption)
                if caption then
                    ui.label.list[index].caption = caption
                end
                return ui.label.list[index].caption
            end,
            visible = function(index, visible)
                if visible ~= nil then
                    ui.label.list[index].visible = visible
                end
                return ui.label.list[index].visible
            end,
            toggle = function(index)
                ui.label.list[index].visible = not ui.label.list[index].visible
            end,
            moveTo = function(index, x, y)
                ui.label.list[index].left = x
                ui.label.list[index].top = y
            end,
            moveBy = function(index, x, y)
                ui.label.list[index].left = ui.label.list[index].left + x
                ui.label.list[index].top = ui.label.list[index].top + y
            end,
            resize = function(index, w, h)
                ui.label.list[index].width = w
                ui.label.list[index].height = h
            end,
        },
       
        keyStroke = {
            list = {},
            add = function(...)
                local keys = {...}
                local callback = keys[#keys]; keys[#keys] = nil
                local key = keys[#keys]; keys[#keys] = nil
                if not ui.keyStroke.list[tostring(key)] then
                    ui.keyStroke.list[tostring(key)] = {}
                end
                if type(callback) == "string" then
                    callback = ui.command.list[callback].callback
                end
                local id = #ui.keyStroke.list[key] + 1
                ui.keyStroke.list[key][id] = {
                    _index = { key = key, id = id },
                    key = key,
                    keysHeld = keys,
                    callback = callback,
                }
                if type(callback) == string then
                    ui.command.list[callback].keyStroke = { key = key, id = id }
                end
                return { key = key, id = id }
            end,
            remove = function(index)
                ui.keyStroke.list[index.key][index.id] = nil
                if #ui.keyStroke.list[index.key] == 0 then ui.keyStroke.list[index.key] = nil end
            end,
            invoke = function(index)
                ui.keyStroke.list[index.key][index.id].callback()
            end,
        },
       
        command = {
            list = {},
            add = function(command, callback)
                ui.command.list[tostring(command)] = {
                    _command = command,
                    callback = callback,
                    keyStroke = nil,
                }
                return command
            end,
            remove = function(command)
                if ui.command.list[command].keyStroke then
                    ui.keyStroke.remove(ui.command.list[command].keyStroke)
                end
                ui.command.list[command] = nil
            end,
            invoke = function(command, ...)
                ui.command.list[command].callback(...)
            end,
        },
       
        verbosity = 0,
        consoleLog = function(message, verbosity)
            if ui.verbosity >= verbosity then
                for l in message:gmatch("[^\n]+") do
                    ShroudConsoleLog(l)
                end
            end
        end,
    }
    setmetatable(ui.timer, {__index = ui.timer.list})
    setmetatable(ui.label, {__index = ui.label.list})
    
    
    -- todo
    function ui._getPlayerName()
        if player.caption ~= ShroudGetPlayerName() then
            player.caption = ShroudGetPlayerName()
            player.name = player.caption
            player.flag = ""
            player.isPvp = false
            if player.name:byte(#player.name) == 93 then
                player.name, player.flag = player.name:match("^(.-)(%[.-%])$")
                player.isPvp = player.flag and player.flag:find("PVP") ~= nil
                player.isAfk = player.flag and player.flag:find("AFK") ~= nil
            end
            ui.handler.invoke("_playerChanged")
        end
    end
    function ui._getSceneName()
        if scene.name ~= ShroudGetCurrentSceneName() then
            scene.name = ShroudGetCurrentSceneName()
            scene.maxPlayer = ShroudGetCurrentSceneMaxPlayerCount()
            scene.isPvp = ShroudGetCurrentSceneIsPVP()
            scene.isPot = ShroudGetCurrentSceneIsPOT()
            scene.timeStarted = os.time()
            ui.handler.invoke("_sceneChanged")
        end
    end
    
    
    -- shroud hooks
    
    function ShroudOnStart()
        -- need QA 890
        --ShroudUseLuaConsoleForPrint(true)
        ui.handler.invoke("_start")
    end
    
    function ShroudOnUpdate()
        local ts = os.time()
    
        -- init
        if not client.screen then
            client.timeStarted = ts
            client.screen = {
                width = ShroudGetScreenX(),
                height = ShroudGetScreenY(),
                isFullScreen = ShroudGetFullScreen(),
            }
            for i=0, ShroudGetStatCount()-1, 1 do
                local name = ShroudGetStatNameByNumber(i)
                client._statEnum[tostring(name)] = i
                client._statEnum[i] = name
                client._statDescr[i] = ShroudGetStatDescriptionByNumber(i)
            end
            ui._getSceneName()
            ui._getPlayerName()
            player.location = { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene }
            player.health = { max = ShroudGetStatValueByNumber(30), current = ShroudPlayerCurrentHealth, percentage = 0 }
            player.focus =  { max = ShroudGetStatValueByNumber(27), current = ShroudPlayerCurrentFocus, percentage = 0 }
    
            ui.handler.invoke("_init")
        end
    
        -- client stats
        client._frame = client._frame + 1;
        if ts - client._ts >= 1 then
            if client.accuracy > 10 then
                ui._getSceneName()
                ui._getPlayerName()
                client._isLoading = false
                scene.timeToLoad = client.accuracy
            end
            client.timeInGame = ShroudTime
            client.timeDelta =  ShroudDeltaTime
            client.fps = client._frame
            client._frame = 0
            client.accuracy = ts - client._ts - 1
            client._ts = ts
            scene.timeInScene = ts - scene.timeStarted
            ui.timer._granulaty = 0.100 - (client.accuracy*10)
        end
    
        -- queued callbacks
        --[[for i,f in next, client._queue do
            f()
            client._queue[i] = nil
        end]]
       
        -- ui timer
        if ts - client._ts >= 0.25 then
            local loc = { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene }
            local isMoving = loc.x ~= player.location.x or loc.y ~= player.location.y or loc.z ~= player.location.z
            if isMoving then player.lastMoved = ts end
            local isStill = ts - player.lastMoved > 5
            local invoke = isStill ~= player.isStill or isMoving ~= player.isMoving
            player.isMoving = isMoving
            player.isStill = isStill
            player.location = loc
            if invoke then
                if isMoving then
                    ui.handler.invoke("_playerMoveStart")
                    ui.handler.invoke("_playerChanged")
                elseif isStill then
                    ui.handler.invoke("_playerIsStill")
                    ui.handler.invoke("_playerChanged")
                else
                    ui.handler.invoke("_playerMoveStop")
                    ui.handler.invoke("_playerChanged")
                end
            end
            local health = { max = ShroudGetStatValueByNumber(30), current = ShroudPlayerCurrentHealth, percentage = 0 }
            local focus =  { max = ShroudGetStatValueByNumber(27), current = ShroudPlayerCurrentFocus, percentage = 0 }
            health.percentage = health.max / health.current * 100
            focus.percentage = focus.max / focus.current * 100
            invoke = player.health.current ~= health.current or player.focus.current ~= focus.current
            if invoke then
                ui.handler.invoke("_playerDamage", health, focus)
                ui.handler.invoke("_playerChanged")
            end
            player.health = health
            player.focus = focus
        end
       
        -- user timer
        if ts - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = ts
            for _,t in next, ui.timer.list do
                if t.enabled and ts >= t.time then
                    if t.interval then
                        t.time = ts + t.interval
                    else
                        ui.timer.list[t._index] = nil
                    end
                    if t.callback(t.userdata) then ui.timer.list[t._index] = nil end
                end
            end
        end
    
        -- check key strokes
        for ku,r in next, ui.keyStroke.list do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
       
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
        local ts = os.time()
        local isHitching = ts - client._ts >= 2
        local isLoading = ts - client._ts >= 5
       
        if isHitching and client.isHitching ~= isHitching then
            client.isHitching = isHitching
            ui.handler.invoke("_clientIsHitching")
        end
        if isLoading and client.isLoading ~= isLoading then
            client.isLoading = isLoading
            ui.handler.invoke("_clientIsLoading")
        end
           
        if not isHitching or isLoading then
            for _,l in next, ui.label.list do
                if l.visible then
                    if not isLoading and l.shownInScene then
                        ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                    elseif isLoading and l.shownInLoadScreen then
                        ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                    end
                end
                if os.time() - ts > 0.01 then break end
            end
        end   
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
       
        ui.timer.add(0, true, function(...)
    
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and message:find("PvP") then ui._getPlayerName() end
    
            -- parse message
            local src, dst, msg = message:match("^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if msg:byte() == 92 then
                local cmd, arg = msg:match("^\\(%w+)%s*(.*)$")
                if ui.command.list[cmd] then
                    ui.command.list[cmd].callback(arg:gmatch("[%S]-"))
                else
                    ui.handler.invoke("_consoleCommand", cmd, sender, dst, arg)
                end
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
    
        end, channel, sender, message)
    end
    
    
    Code:
    -- libsota.0.4.util-dev by Catweazle Waldschrath
    -- helper functions for global namespace to work with the ui.objects
    -- depends on libsota.0.4.x
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.add(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.add(interval, false, callback)
    end
    
    function getTimer(index)
        return ui.timer.list[index]
    end
    
    function cancelTimer(index)
        ui.timer.list[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer.list[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer.list[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        return ui.label.add(r.left, r.top, r.width, r.height, caption)
    end
    
    function getLabel(index)
        return ui.label.list[index]
    end
    
    function getLabelCaption(index)
        return ui.label.list[index].caption
    end
    getLabelText = getLabelCaption
    
    function setLabelCaption(index, caption)
        ui.label.list[index].caption = caption
    end
    setLabelText = setLabelCaption
    
    function removeLabel(index)
        ui.label.list[index] = nil
    end
    
    function showLabel(index)
        ui.label.list[index].visible = true
    end
    
    function hideLabel(index)
        ui.label.list[index].visible = false
    end
    
    function toggleLabel(index)
        ui.label.list[index].visible = not ui.label.list[index].visible
    end
    
    function isLabelVisible(index)
        return ui.label.list[index].visible
    end
    
    function setLabelVisible(index, visible)
        ui.label.list[index].visible = visible
    end
    
    function moveLabelBy(index, x, y)
        ui.label.list[index].left = ui.label.list[index].left + x
        ui.label.list[index].top = ui.label.list[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label.list[index].left = x
        ui.label.list[index].top = y
    end
    
    
    -- implement Shroud calls
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart() end
    
    
    function ui.registerKey(...)
        return ui.keyStroke.add(...)
    end
    
    
    
    string.style = function(string, style)
        if style.bold then string = "<b>"..string.."</b>" end
        if style.italic then string = "<i>"..string.."</i>" end
        if style.size and style.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
        if style.color and #style.color > 3 then string = "<color="..style.color..">"..string.."</color>" end
        return string
    end
    
    
    rect = {
        new = function(left, top, width, height)
            local r = {
                left = left,
                top = top,
                width = width,
                height = height,
            }
    
            if not width then
                r.width = client.screen.width / 3.3
            elseif tonumber(width) == nil then
                width = tonumber(string.match(width, "^%d+"))
                r.width = client.screen.width / 100 * math.abs(width)
            end
            if not height then
                r.height = client.screen.height / 3.6
            elseif tonumber(height) == nil then
                height = tonumber(string.match(height, "^%d+"))
                r.height = client.screen.height / 100 * math.abs(height)
            end
            if not left then
                r.left = (client.screen.width - r.width) / 2
            elseif tonumber(left) == nil then
                left = tonumber(string.match(left, "^%d+"))
                if left < 0 then
                    r.left = client.screen.width - (client.screen.width / 100 * -left) - r.width
                else
                    r.left = client.screen.width / 100 * left
                end
            end
            if not top then
                r.top = (client.screen.height - r.height) / 2
            elseif tonumber(top) == nil then
                top = tonumber(string.match(top, "^%d+"))
                if top < 0 then
                    r.top = client.screen.height - (client.screen.height / 100 * -top) - r.height
                else
                    r.top = client.screen.height / 100 * top
                end
            end
    
            return r
        end,
    
        fromString = function(string)
            local str = string:gsub("<[^>]*>", "")
            local s = string:match("<size=(%d-)")
            if not s then s = 14 end
            return { left = 0, top = 0, width = str:len() * (s/2), height = s*2 }
        end,
       
        moveTo = function(rect, x, y)
            if not x then x = (client.screen.width - rect.width) / 2 end
            if not y then y = (client.screen.height - rect.height) / 2 end
            rect.left = x
            rect.top = y
            return rect
        end,
       
        moveBy = function(rect, x, y)
            rect.left = rect.left + x
            rect.top = rect.top + y
            return rect
        end
    }
    setmetatable(rect, {__newindex = rect.new})
    
    

    needs QA 889
    (ShroudUseLuaConsoleForPrint(true) commented out)

    cleaned up libsota... and done some optimizations/polish
    libsota.util contains all functions apearing in the global namespace like labelCreate and such. They are comatible to libsota.0.3.x
    i am working on libsota.ui a simple window class, a container for labels

    i made the decision to move the "gui" parts to an extra file, because there are already lua libs for graphical ui out there. may they can be used, saves a lot of work. And keep libsota small.

    there are new events:
    onPlayerMoveStart - invoked when the player starts to move
    onPlayerMoveStop - invoked when the player stops moving
    onPlayerIsStill - invoked when player has stillness bonus. (may i rename it to PlayerStandStill ... sounds better)
    onPlayerDamage(health, focus) - invoked when currentHealth or currentFocus are changed. The parameter health and focus contains the _new_ values. player.health and player.focus the _old_ ones (may i turn it around)
    onPlayerChanged - invoked when name, flagging, moving, damage or stillness occur (sometimes it is simpler to only have one callback). name changed from onPlayerChange to onPlayerChanged. Because it occurs after change nit during
    onSceneChanged - name change, the same as onPlayerChanged

    player:
    isMoving = false, - true when player is moving
    isStill = false, - true when player has stillness bonus active
    lastMoved = os.time(), - timestamp when player stopped moving
    location = {}, - table containing: x, y, z and scene object
    health = {}, - table containing: current, max and percentage values of health (total was renamd to max to use the same words like the shroud-api. percentage can be used for a progress bar)
    focus = {}, - table containing: current, max and percentage values of health (total was renamd to max to use the same words like the shroud-api. percentage can be used for a progress bar)

    new objects:
    ui.keystrokes:
    table add - binds a keystroke to a callback/function. ([keyHeld],[keyHeld], ..., keyUp, {func callback | string command})
    remove - removes a keystrokes
    invoke - invoke the callback/function that belong to the keystroke

    ui.command:
    string add - bind a command to a callback/function. commands are typed into the chatwindow and starting with \.
    remove - removes a command
    invoke(command, ...) - invoke the callback/function with the parameters given of the command

    changed objects (added methods to them):
    ui.timer:
    int add - adds a timer. renamed from create
    table get - retrieves a timer object
    remove - removes a timer object
    enabled - sets or get the enabled state of a timer
    pause - pauses a timer
    resume - resume a timer
    toggle - toggle the enabled state of a timer (on/off)
    ui.setInterval - creates a interval timer
    ui.setTimeout - creates a timerout timer

    ui.label:
    int add - adds a label (renamed from create)
    table get - retrieves a label object
    remove - removes a label object
    (rect - sets the rect of a label)
    caption - sets or get the caption of a label
    visible - set or get visibility
    toggle - toggles visibility
    moveTo - move label to the position
    moveBy - moves label by given delta pos
    resize - set a new size for the label

    ui.verbosity - set verbosity level
    ui.consoleLog(message, verbosity) - send a multiline message to the console depending on verbosity level
    lua print - is connected to console (needs QA 890)

    object properties can be accessed by either using ui.<object>.get(index).property or ui.<object>[index].property

    there are may be bugs....
    it is planned to leave the object model of libsota like it is with 0.4 , only adding things (when there is something new at the Shroud-API)

    ---- libsota.util
    contains all the function from libsota.0.3.8 (createLabel, isLabelVisible, ...)

    --- libsota.ui
    hopefully when R73 is released an the last thunderday in month ... oh, wait....


    comments are welcome , also bug reports, or how to make things more usefull
     
  8. Drake Aedus

    Drake Aedus Avatar

    Messages:
    542
    Likes Received:
    891
    Trophy Points:
    75
    Gender:
    Male
    Have you noticed that "/lua reload" breaks ui.registerKey()?
     
  9. Lace

    Lace Avatar

    Messages:
    1,577
    Likes Received:
    5,239
    Trophy Points:
    113
    Gender:
    Female
    Location:
    Florida
    So I have been waiting for some GUI functionality to come in, and just been working on chat log parsing. My plans are to allow you to constrain queries to date span, character and down to location and other things as I build them.. I have only started this and just learning lua on the fly for it, but for those that have been talking with Chris more directly is there any word on when we will have a button, and a text box input. I will want folks to be able to save configurations etc for their queries

    Or @Chris if you can give word .. thanks


    To get yall excited here is some sample output from testing:

    Starting File Parsing, this may take some time depending on how many your are doing

    Counting all the carrots and what not you been picking up out in the wild, many files well then it will take a bit
    Harvestable Count(Animal Hide) 15
    Harvestable Count(Brown Cap Mushroom) 2
    Harvestable Count(Cinnamon Bark) 2
    Harvestable Count(Copper Ore) 3
    Harvestable Count(Death Bunny Head) 6
    Harvestable Count(Fabric Scrap) 9
    Harvestable Count(Gold Ore) 13
    Harvestable Count(Granite Block) 3
    Harvestable Count(Killer Rabbit Art Bundle) 2
    Harvestable Count(Maple Wood) 13
    Harvestable Count(Pecan Nuts) 4
    Harvestable Count(Pine Resin) 3
    Harvestable Count(Pine Tree Bark) 2
    Harvestable Count(Pine Wood) 12
    Harvestable Count(Rabbit Carcass) 6
    Harvestable Count(Rabbit Meat) 38
    Harvestable Count(Raw Cotton) 12
    Harvestable Count(Sheep Carcass) 10
    Harvestable Count(Sooty Bark) 2
    Harvestable Count(Wolf Carcass) 2
    Harvestable Count(Wolf Head) 1

    If you craft way to much like Lace, counting up all your craftables may take some time.
    (note that right now the items bought with crowns are showing up here as well)

    Crafting Count-(Bacon) 1023
    Crafting Count-(Bass Trophy) 2
    Crafting Count-(Beer Cask) 50
    Crafting Count-(Blue Stall Awning) 1
    Crafting Count-(Brittany Ale Ingredients) 39
    Crafting Count-(Buoy rope) 4
    Crafting Count-(Coil of Rope) 88
    Crafting Count-(Copper Ingot) 223
    Crafting Count-(Cotton Bow String) 7
    Crafting Count-(Crab trap) 6
    Crafting Count-(Dark Orange Dye) 76
    Crafting Count-(Doric Column Pedestal) 45
    Crafting Count-(Drach Bock Beer Ingredients) 37
    Crafting Count-(Dual Wax Cylinder Phonograph) 7
    Crafting Count-(Edelmann Beer Ingredients) 39
    Crafting Count-(Fabric Scrap) 57
    Crafting Count-(Gravestone) 5
    Crafting Count-(Green Stall Awning) 1
    Crafting Count-(Guillotine) 23
    Crafting Count-(Hanging Potted Azalea) 20
    Crafting Count-(Hanging Potted Ivy) 20
    Crafting Count-(Hanging Potted Sword Plant) 20
    Crafting Count-(Harvest Wheat Beer Ingredients) 41
    Crafting Count-(Headstone) 3
    Crafting Count-(Hospital Room Divider) 13
    Crafting Count-(IV Stand) 16
    Crafting Count-(Iron Two-handed Axe Blade) 23
    Crafting Count-(Leather Bow String) 8
    Crafting Count-(Leather Scrap) 5
    Crafting Count-(Light Orange Dye) 76
    Crafting Count-(Lobster pot) 6
    Crafting Count-(Maple Board) 592
    Crafting Count-(Maple Long Bowstave) 1
    Crafting Count-(Medium Treasure Pile) 1
    Crafting Count-(Metal Scrap) 2678
    Crafting Count-(Mounted Antlers) 20
    Crafting Count-(Mounted Bass) 20
    Crafting Count-(Mounted Elder Wolf) 3
    Crafting Count-(Mounted Obsidian Bear) 1
    Crafting Count-(Mounted Patriarch Bear) 1
    Crafting Count-(Mounted Salmon) 20
    Crafting Count-(Mounted Troll) 1
    Crafting Count-(Mounted Trout) 19
    Crafting Count-(Mounted Wild Boar) 6
    Crafting Count-(Operating Table) 5
    Crafting Count-(Orange Dye) 67
    Crafting Count-(Ornate Elven Wall Sconce) 3
    Crafting Count-(Ornate Tavern Counter with Gate) 1
    Crafting Count-(Ornate Tavern Counter) 1
    Crafting Count-(Ornate Trident) 1
    Crafting Count-(Ornate Water Platform) 1
    Crafting Count-(Paladis White Wine Ingredients) 38
    Crafting Count-(Pattern: Ancient Bastard Sword) 2
    Crafting Count-(Pattern: Battle Scythe) 2
    Crafting Count-(Pattern: Clan of the Bear Boots) 1
    Crafting Count-(Pattern: Elven Mage Staff) 1
    Crafting Count-(Pattern: Lich Axe) 4
    Crafting Count-(Pattern: Norgard Shirt) 1
    Crafting Count-(Perennial Red Wine Ingredients) 40
    Crafting Count-(Pillory) 6
    Crafting Count-(Pine Long Bowstave) 4
    Crafting Count-(Pirate Feathered Tricorn Hat) 1
    Crafting Count-(Pirate Hook Hand Gloves) 1
    Crafting Count-(Pirate Jacket with Strap) 1
    Crafting Count-(Pirate Pants with Boot and Pegleg) 1
    Crafting Count-(Prison Cage) 5
    Crafting Count-(Prison Shackle) 17
    Crafting Count-(Red Dye) 40
    Crafting Count-(Red Stall Awning) 1
    Crafting Count-(Salmon Trophy) 20
    Crafting Count-(Shogun Paper & Wood Room Divider) 55
    Crafting Count-(Spirits Cask) 80
    Crafting Count-(Trout Trophy) 20
    Crafting Count-(Trunk) 3
    Crafting Count-(Wall Shelf) 18
    Crafting Count-(Wine Cask) 50
    Crafting Count-(Wood Pulp) 728
    Crafting Count-(Wood Scrap) 3282
    Crafting Count-(Wooden Barrel) 181
    Crafting Count-(Wooden Phonograph) 18
    Crafting Count-(Yard of Leather) 1

    Counting Exceptionals, Fingers Crossed for Ya.

    Exceptional Count-2 Year Whiskey Ingredients 11
    Exceptional Count-25 Year Whiskey Ingredients 11
    Exceptional Count-7 Year Whiskey Ingredients 8
    Exceptional Count-Bacon 132
    Exceptional Count-Bass Trophy 1
    Exceptional Count-Beer Cask 12
    Exceptional Count-Blue Stall Awning 1
    Exceptional Count-Brittany Ale Ingredients 9
    Exceptional Count-Coil of Rope 24
    Exceptional Count-Copper Ingot from Metal Scraps 58
    Exceptional Count-Dark Orange Dye 20
    Exceptional Count-Doric Column Pedestal 13
    Exceptional Count-Drach Bock Beer Ingredients 8
    Exceptional Count-Dual Wax Cylinder Phonograph 2
    Exceptional Count-Edelmann Beer Ingredients 8
    Exceptional Count-Green Stall Awning 1
    Exceptional Count-Guillotine 5
    Exceptional Count-Hanging Potted Azalea 7
    Exceptional Count-Hanging Potted Ivy 4
    Exceptional Count-Hanging Potted Sword Plant 3
    Exceptional Count-Harvest Wheat Beer Ingredients 14
    Exceptional Count-Headstone 1
    Exceptional Count-Hospital Room Divider 2
    Exceptional Count-IV Stand 4
    Exceptional Count-Iron Two-handed Axe Blade 6
    Exceptional Count-Light Orange Dye 24
    Exceptional Count-Luminous Atavist Robe 3
    Exceptional Count-Maple Board 76
    Exceptional Count-Mounted Antlers 7
    Exceptional Count-Mounted Bass 3
    Exceptional Count-Mounted Elder Wolf 1
    Exceptional Count-Mounted Salmon 7
    Exceptional Count-Mounted Troll 1
    Exceptional Count-Mounted Trout 2
    Exceptional Count-Mounted Wild Boar 2
    Exceptional Count-Operating Table 2
    Exceptional Count-Orange Dye 14
    Exceptional Count-Paladis White Wine Ingredients 8
    Exceptional Count-Perennial Red Wine Ingredients 11
    Exceptional Count-Pillory 2
    Exceptional Count-Prison Cage 1
    Exceptional Count-Prison Shackle 4
    Exceptional Count-Red Dye 11
    Exceptional Count-Salmon Trophy 6
    Exceptional Count-Shogun Paper & Wood Room Divider 12
    Exceptional Count-Spirits Cask 24
    Exceptional Count-Tombstone 1
    Exceptional Count-Trout Trophy 3
    Exceptional Count-Wall Shelf 6
    Exceptional Count-Wine Cask 13
    Exceptional Count-Wood Pulp from Scrap 171
    Exceptional Count-Wooden Barrel 63
    Exceptional Count-Wooden Phonograph 8

    Counting the meh Non-Exceptionals.

    NonExceptional Count-2 Year Whiskey Ingredients 29
    NonExceptional Count-25 Year Whiskey Ingredients 27
    NonExceptional Count-7 Year Whiskey Ingredients 31
    NonExceptional Count-Bacon 2233
    NonExceptional Count-Bass Trophy 1
    NonExceptional Count-Beer Cask 38
    NonExceptional Count-Brittany Ale Ingredients 30
    NonExceptional Count-Coil of Rope 64
    NonExceptional Count-Copper Ingot from Metal Scraps 131
    NonExceptional Count-Dark Orange Dye 56
    NonExceptional Count-Doric Column Pedestal 32
    NonExceptional Count-Drach Bock Beer Ingredients 29
    NonExceptional Count-Dual Wax Cylinder Phonograph 5
    NonExceptional Count-Edelmann Beer Ingredients 31
    NonExceptional Count-Gravestone 5
    NonExceptional Count-Guillotine 18
    NonExceptional Count-Hanging Potted Azalea 13
    NonExceptional Count-Hanging Potted Ivy 16
    NonExceptional Count-Hanging Potted Sword Plant 17
    NonExceptional Count-Harvest Wheat Beer Ingredients 27
    NonExceptional Count-Headstone 2
    NonExceptional Count-Hospital Room Divider 11
    NonExceptional Count-IV Stand 12
    NonExceptional Count-Iron Two-handed Axe Blade 17
    NonExceptional Count-Light Orange Dye 52
    NonExceptional Count-Luminous Atavist Robe 5
    NonExceptional Count-Maple Board 220
    NonExceptional Count-Mounted Antlers 13
    NonExceptional Count-Mounted Bass 17
    NonExceptional Count-Mounted Elder Wolf 2
    NonExceptional Count-Mounted Obsidian Bear 1
    NonExceptional Count-Mounted Patriarch Bear 1
    NonExceptional Count-Mounted Salmon 13
    NonExceptional Count-Mounted Trout 17
    NonExceptional Count-Mounted Wild Boar 4
    NonExceptional Count-Operating Table 3
    NonExceptional Count-Orange Dye 53
    NonExceptional Count-Paladis White Wine Ingredients 30
    NonExceptional Count-Perennial Red Wine Ingredients 29
    NonExceptional Count-Pillory 4
    NonExceptional Count-Potion of Restoration, Imbued 831
    NonExceptional Count-Prison Cage 4
    NonExceptional Count-Prison Shackle 13
    NonExceptional Count-Red Dye 29
    NonExceptional Count-Red Stall Awning 1
    NonExceptional Count-Salmon Trophy 14
    NonExceptional Count-Shogun Paper & Wood Room Divider 43
    NonExceptional Count-Spirits Cask 56
    NonExceptional Count-Tombstone 7
    NonExceptional Count-Trout Trophy 17
    NonExceptional Count-Trunk 3
    NonExceptional Count-Wall Shelf 12
    NonExceptional Count-Wine Cask 37
    NonExceptional Count-Wood Pulp from Scrap 557
    NonExceptional Count-Wooden Barrel 118
    NonExceptional Count-Wooden Phonograph 10

    Counting Fish .. One Fish Two Fish Red Fish Blue Fish

    Fishing Count-Amberjack Mackerel 1
    Fishing Count-Bass 16
    Fishing Count-Catfish 19
    Fishing Count-Cloth Boots 4
    Fishing Count-Cloth Gloves 1
    Fishing Count-Cloth Helm 2
    Fishing Count-Cod 16
    Fishing Count-Herring 3
    Fishing Count-Leather Boots 1
    Fishing Count-Leather Helm 1
    Fishing Count-Mackerel 4
    Fishing Count-Mahi-Mahi 3
    Fishing Count-Minnow 5
    Fishing Count-Oscar Sunfish 3
    Fishing Count-Pacu 1
    Fishing Count-Peacock Bass 2
    Fishing Count-Pike 1
    Fishing Count-Salmon 17
    Fishing Count-Sardine 1
    Fishing Count-Small King Salmon 1
    Fishing Count-Spotted Bass 2
    Fishing Count-Sunfish 16
    Fishing Count-Trout 23

    Phat lewt coming...

    Loot Count-Amethyst Fragment 1
    Loot Count-Arrow 1311
    Loot Count-Bandit Blade 2
    Loot Count-Beaker 2
    Loot Count-Black Pearl 20
    Loot Count-Bottle 3
    Loot Count-Caltrops 2
    Loot Count-Candelabra 1
    Loot Count-Candle 3
    Loot Count-Ceramic Cup 1
    Loot Count-Ceramic Jar 3
    Loot Count-Ceramic Mug 1
    Loot Count-Corpse Wax 580
    Loot Count-Crown of the Obsidians 15
    Loot Count-Cure Plague Potion 6
    Loot Count-Cure Poison Potion 7
    Loot Count-Digging Shovel 1
    Loot Count-Dirty Cloth Boots 1
    Loot Count-Dirty Cloth Leggings 1
    Loot Count-Dirty Cutting Board 1
    Loot Count-Dirty Wooden Spoon 1
    Loot Count-Elf Essence 3
    Loot Count-Elven Mage Staff 4
    Loot Count-Explosive Potion 13
    Loot Count-Filled Boiler Glass 1
    Loot Count-Filled Test Tube Rack 1
    Loot Count-Filled Vial A 1
    Loot Count-Filled Vial B 1
    Loot Count-Flask 3
    Loot Count-Flat Shovel 2
    Loot Count-Garlic 5
    Loot Count-Haste Potion 3
    Loot Count-Large Pewter Bowl 1
    Loot Count-Lesser Staff 27
    Loot Count-Lesser Wand 19
    Loot Count-Lich Essence 102
    Loot Count-Long Bow 3
    Loot Count-Mandrake Root 9
    Loot Count-Nightshade 7
    Loot Count-Norgard Fur Hood 1
    Loot Count-Obsidian Chip 145
    Loot Count-Onyx Fragment 3
    Loot Count-Painting (Butcher Shop 1
    Loot Count-Painting (Ladies In Waiting 3
    Loot Count-Painting (Portrait of a Lady 2
    Loot Count-Pewter Jar 1
    Loot Count-Pewter Mug 1
    Loot Count-Picture 1
    Loot Count-Pitchfork 1
    Loot Count-Plow 2
    Loot Count-Poison Potion 1
    Loot Count-Potion of Deftness 1
    Loot Count-Potion of Focus 16
    Loot Count-Potion of Focus, Lesser 1
    Loot Count-Potion of Health 17
    Loot Count-Potion of Restoration, Imbued 1
    Loot Count-Potion of Wolf Speed 1
    Loot Count-Ragged Leather Boots 2
    Loot Count-Ragged Leather Chest Armor 1
    Loot Count-Ragged Leather Gloves 2
    Loot Count-Recipe: Poison Potion, Corpion 1
    Loot Count-Recipe: Poison Potion, Paralyzing Spider Venom 1
    Loot Count-Recipe: Potion of Acumen 1
    Loot Count-Recipe: Potion of Focus, Imbued 1
    Loot Count-Recipe: Potion of Health, Greater 1
    Loot Count-Recipe: Potion of Health, Imbued 1
    Loot Count-Recipe: Potion of Might 1
    Loot Count-Recipe: Potion of Restoration 1
    Loot Count-Recipe: Potion of Wolf Speed 1
    Loot Count-Repair Kit - Blacksmithing 2
    Loot Count-Repair Kit - Carpentry 4
    Loot Count-Repair Kit - Tool 4
    Loot Count-Rotting Flesh 83
    Loot Count-Rusty Axe 1
    Loot Count-Rusty Bowl 1
    Loot Count-Rusty Chainmail Gauntlets 1
    Loot Count-Rusty Chainmail Helm 1
    Loot Count-Rusty Cleaver 1
    Loot Count-Rusty Hammer 2
    Loot Count-Rusty Ladle 4
    Loot Count-Rusty Plate 1
    Loot Count-Rusty Spatula 2
    Loot Count-Rusty Spoon 1
    Loot Count-Rusty Sword 1
    Loot Count-Scythe 1
    Loot Count-Serpent Scales 8
    Loot Count-Shoddy Short Bow 6
    Loot Count-Skeleton Bone 8
    Loot Count-Small Round Flask 3
    Loot Count-Small Square Jar 3
    Loot Count-Smelling Salts 1
    Loot Count-Spider Silk 5
    Loot Count-Square Jar 3
    Loot Count-Square Shovel 1
    Loot Count-Sulfurous Ash 13
    Loot Count-Supply Bundle (Grey 7
    Loot Count-Teleport to Friend Scroll 6
    Loot Count-Torch 23
    Loot Count-Triangle Shield 1
    Loot Count-Vial A 2
    Loot Count-Vial C 1
    Loot Count-Wood Bowl 1
    Loot Count-Wood Serving Bowl 1
    Loot Count-Wood Serving Plate 3
    Loot Count-Wooden Mug 1
    Loot Count-Worn Cloak 1
    Loot Count-Yoke 1

    Ching Ching for the Bling Bling...

    Gold Received-2251


    How good are your dice in a group?

    LootRollsWon-371
    LootRollsLost-2445


    Distinct Trader Channel messages--Distinct because of the spam repetition and sometimes you just want the facts .....this was hand edited to only show a sample
    1
    Jett Blackheart (To Traders): all prices in crown shop need to be converted to beetles
    Cristiano Kimberlin (To Traders): BUYING: Daemon Hoof 1500, Phoenix Talons 20k Fourth Vendor Left Default Brit Alleys, Potion Vendor
    Zadien (To Traders): WTS Fleet Flute 3750
    livelifegood (To Traders): WTS 20 troll essence 500 per
    Shark Attack (To Traders): ого,круто ,а сколько примерно денег надо на 1 такое?
    Turandis Claudbourne (To Traders): whats the price per timber these days?
    Sheldon Cooper (To Traders): or people who watch my stream type thing
    Bilmar (To Traders): going rate for hoppers?
    Daxstalker (To Traders): WTS Legendary Ring for 150k only one for Type
    Ezekiel Cooper (To Traders): We got laws tht deal with drama and agitation
    Titania Xylia (To Traders): WTS Golden Kobold Four-Story Great Hall (City Home) - 9,999,000g - listed in Novia Market
    Azurion Dufresne (To Traders): selling all new Wyvern Colours for 24k each
    Jett Blackheart (To Traders): Blackheart's Blackmarket ☠ in DIamond FIelds has your stolen/dropped deco for finishing touches and COTOS for sale at ➊➐➎
    Vermasis Echo (To Traders): anyone looking to buy beetles?
    Malek Zandor (To Traders): WTS TF POT Village Lot Deed for 15.ooo COTO or 2.ooo.ooo Gold
    CICI (To Traders): CICI's Bazaar @ Novia Market (Castle Water lot) Turn around when you 1st zone in and walk to the end of the docks take the row boat on the right/near the sign( R70 Clearance Sale!)
    CICI (To Traders): CICI's Bazaar @ Novia Market (Castle Water lot) Turn around when you 1st zone in and walk to the end of the docks take the row boat on the right/near the sign ( R70 Clearance Sale!)
    Malek Zandor (To Traders): :)
    Reynald DeChatillon (To Traders): dang thing came outta nowhere. scared the piss outta me lol i was just farming bears
    Kynral (To Traders): Psst Over Here....Looking for dungeon recipes and blueprints? Check out Silent Ascent! New PoT nested in Soltown.
    CICI (To Traders): Duke's Crown 2,399,999 Gold - CICI's Bazaar @ Novia Market (Castle Water lot)
    Dark Tidings (To Traders): Wtb ring of the ringmaster
    Gestek zasada (To Traders): wts wood 120 per and carapace 2500 per in BloodMark
    Sheldon Cooper (To Traders): i could be wrong but cici buy 20,000 cotos at the vendor price ill have to ask him later lol
    Girlsname (To Traders): wts heart of vruuls //// wtb pheonix talons @20k ea
    Elrond (To Traders): Buying - Suet 75g each, Wolf and Bear Heads 20g each, Nickel 100 each, Beetles 1100 each -Buy orders listed in Crafters Town - Coto Vendor.
    CICI (To Traders): tons of beetles 10 for 24,999
    Cristiano Kimberlin (To Traders): BUYING: Scrap scrap scrap! Lots of it! Metal 3per Brit Alleys Default 5th Vendor on Left. Wood Scrap 2per 5th Vendor Right.
    CICI (To Traders): note must be sold by CICI other players use my vendors


    Its like Oz the people just come and go so fast.
    (This sample is partial of the 7k or so from parsing a few sample files - I think it has no real use, but was good practice for learning lua)
    1
    1 [3/31/2018 6:07:35 AM] Sun Wu is now online.
    2 [3/31/2018 6:08:17 AM] Daemend Hunter is now offline.
    3 [3/31/2018 6:10:37 AM] Hemswal Genis is now offline.
    4 [3/31/2018 6:12:48 AM] Sun Wu is now offline.
    5 [3/31/2018 6:15:36 AM] Yigg Endimion is now offline.
    6 [3/31/2018 6:17:04 AM] Johnkirk Bayard is now online.
    6782 [11/2/2019 10:17:38 PM] scroda is now offline.
    6783 [11/2/2019 10:18:13 PM] Hugh Wolfrider is now offline.
    6784 [11/2/2019 10:19:54 PM] Engel is now offline.
    6785 [11/2/2019 10:22:34 PM] Christopher is now online.

    Heres what used to happen with the old command /playlist, we got a list of our sheet music directory
    1
    1 song1.abc
    2 song2.abc

    We were only looking at data in these files: this is internal listing of what files were parsed
    1
    1 C:\CJ\LUA\SotAChatLog_Lace Delamorte_2018-03-31SomeGroupHunt.txt
    2 C:\CJ\LUA\SotAChatLog_Lace Delamorte_2019-03-08_fishing.txt
    3 C:\CJ\LUA\SotAChatLog_Lace Delamorte_2019-03-10_pacu.txt
    4 C:\CJ\LUA\SotAChatLog_Lace Delamorte_2019-04-13_UT.txt
    5 C:\CJ\LUA\SotAChatLog_Lace Delamorte_2019-05-19.txt
    6 C:\CJ\LUA\SotAChatLog_Lace Delamorte_2019-09-28.txt
    7 C:\CJ\LUA\SotAChatLog_Lace Delamorte_2019-11-02.txt

    Here come table dumps for testing
    (none right now)
    End Table Dump Testing
     
    Last edited: Nov 21, 2019
    ldykllr likes this.
  10. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    with libsota.ui i can add buttons. For now the chat windows is the only source for text input. Yes saving settings would be good.

    hm, shouldn't be the case but i will check it.
     
  11. Chris

    Chris Tech Lord Moderator SOTA Developer Ambassador

    Messages:
    2,454
    Likes Received:
    27,637
    Trophy Points:
    190
    Gender:
    Male
    I had originally slated this first version for the end of year and GUI for Q2 next year but obviously I'm making faster progress than expected. I'll probably have buttons in by end of year now but not sure on other things just yet.
     
    Sean Silverfoot, Lace, Jaesun and 3 others like this.
  12. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.4.0 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeToLoad = 0,
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        isHitching = false,
        isLoading = true,
        _frame = 0,
        _ts = os.time(),
        _isLoading = false,
        _statEnum = {},
        _statDescr = {},
        --_queue = {},
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        isAfk = nil,
        isMoving = false,
        isStill = false,
        lastMoved = os.time(),
        location = {},
        health = {},
        focus = {},
        xp = function() return { producer = ShroudGetPooledProducerExperience(), adventurer = ShroudGetPooledAdventurerExperience() } end,
        stat = function(index)
            local ret = {
                number = -1,
                name = "invalid",
                value = -999,
                description = "",
            }
            if tonumber(index) == nil then
                index = client._statEnum[index]
            else
                index = tonumber(index)
            end
            if index and index <= #client._statEnum then
                ret.number = index
                ret.name = client._statEnum[index]
                ret.value = ShroudGetStatValueByNumber(index)
                ret.description = client._statDescr[index]
            end
            return ret
        end,
    }
    ui = {
        timer = {
            _ts = os.time(),
            _granulaty = 0.100,
            list = {},
            add = function(timeout, once, callback, ...)
                local index = #ui.timer.list + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer.list[index] = {
                    _index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    callback = callback,
                    userdata = ...,
                }
                return index
            end,
            get = function(index)
                return ui.timer.list[index]
            end,
            remove = function(index)
                ui.timer.list[index] = nil
            end,
            enabled = function(index, enabled)
                if enabled ~= nil then
                    ui.timer.list[index].enabled = enabled
                end
                return ui.timer.list[index].enabled
            end,
            pause = function(index)
                ui.timer.list[index].enabled = false
            end,
            resume = function(index)
                ui.timer.list[index].enabled = true
            end,
            toggle = function(index)
                ui.timer.list[index].enabled = not ui.timer.list[index].enabled
            end,
        },
        setTimeout = function(timeout, callback) return ui.timer.add(timeout, true, callback) end,
        setInterval = function(interval, callback) return ui.timer.add(interval, false, callback) end,
    
     
        handler = {
            list = {},
            add = function(name, callback)
                local index = #ui.handler.list + 1
    
                ui.handler.list[index] = {
                    _index = index,
                    name = name,
                    callback = callback,
                }
                return index
            end,
            remove = function(index)
                ui.handler.list[index] = nil
            end,
            invoke = function(name, ...)
                for _,h in next, ui.handler.list do
                    if h.name == name then
                        --print(os.date("%X").." : invoke "..h.name)
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.callback(...)
                    end
                end
            end,
        },
        onInit = function(callback) ui.handler.add("_init", callback) end,
        onStart = function(callback) ui.handler.add("_start", callback) end,
        onUpdate = function(callback) ui.handler.add("_update", callback) end,
        onConsoleInput = function(callback) ui.handler.add("_consoleInput", callback) end,
        onConsoleCommand = function(callback) ui.handler.add("_consoleCommand", callback) end,
        onSceneChanged = function(callback) ui.handler.add("_sceneChanged", callback) end,
        onPlayerChanged = function(callback) ui.handler.add("_playerChanged", callback) end,
        onPlayerMoveStart = function(callback) ui.handler.add("_playerMoveStart", callback) end,
        onPlayerMoveStop = function(callback) ui.handler.add("_playerMoveStop", callback) end,
        onPlayerIsStill = function(callback) ui.handler.add("_playerIsStill", callback) end,
        onPlayerDamage = function(callback) ui.handler.add("_playerDamage", callback) end,
        onClientIsHitching = function(callback) ui.handler.add("_clientIsHitching", callback) end,
        onClientIsLoading = function(callback) ui.handler.add("_clientIsLoading", callback) end,
    
    
        label = {
            list = {},
            add = function(left, top, width, height, caption)
                local index = #ui.label.list + 1
    
                ui.label.list[index] = {
                    _index = index,
                    left = left,
                    top = top,
                    width = width,
                    height = height,
                    caption = caption,
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                }
                setmetatable(ui.label.list[index], {__index = ui.label})
                return index
            end,
            get = function(index)
                return ui.label.list[index]
            end,
            remove = function(index)
                ui.label.list[index] = nil
            end,
            rect = function(index, rect)
                local l = ui.label.list[index]
                l.left = rect.left
                l.top = rect.top
                l.width = rect.width
                l.height = rect.height
            end,
            caption = function(index, caption)
                if caption then
                    ui.label.list[index].caption = caption
                end
                return ui.label.list[index].caption
            end,
            visible = function(index, visible)
                if visible ~= nil then
                    ui.label.list[index].visible = visible
                end
                return ui.label.list[index].visible
            end,
            toggle = function(index)
                ui.label.list[index].visible = not ui.label.list[index].visible
            end,
            moveTo = function(index, x, y)
                ui.label.list[index].left = x
                ui.label.list[index].top = y
            end,
            moveBy = function(index, x, y)
                ui.label.list[index].left = ui.label.list[index].left + x
                ui.label.list[index].top = ui.label.list[index].top + y
            end,
            resizeTo = function(index, w, h)
                ui.label.list[index].width = w
                ui.label.list[index].height = h
            end,
            resizeBy = function(index, x, y)
                ui.label.list[index].width = ui.label.list[index].width + x
                ui.label.list[index].height = ui.label.list[index].height + y
            end,
        },
     
        shortcut = {
            list = { press = {}, watch = {} },
            add = function(action, ...)
                local keys = {...}
                local callback = keys[#keys]; keys[#keys] = nil
                local key = keys[#keys]; keys[#keys] = nil
                if not ui.shortcut.list[action][tostring(key)] then
                    ui.shortcut.list[action][tostring(key)] = {}
                end
                if type(callback) == "string" then
                    callback = ui.command.list[callback].callback
                end
                local id = #ui.shortcut.list[action][key] + 1
                local index = { action = action, key = key, id = id }
                ui.shortcut.list[action][key][id] = {
                    _index = index,
                    key = key,
                    keysHeld = keys,
                    callback = callback,
                }
                if type(callback) == string then
                    if ui.command.list[callback].shortcut then
                        ui.shortcut.remove(ui.command.list[callback].shortcut)
                    end
                    ui.command.list[callback].shortcut = index
                end
                return index
            end,
            remove = function(index)
                ui.shortcut.list[action][index.key][index.id] = nil
                if #ui.shortcut.list[action][index.key] == 0 then ui.shortcut.list[action][index.key] = nil end
            end,
            invoke = function(index)
                ui.shortcut.list[action][index.key][index.id].callback()
            end,
        },
     
        command = {
            list = {},
            add = function(command, callback)
                ui.command.list[tostring(command)] = {
                    _command = command,
                    callback = callback,
                    shortcut = nil,
                }
                return command
            end,
            remove = function(command)
                if ui.command.list[command].shortcut then
                    ui.shortcut.remove(ui.command.list[command].shortcut)
                end
                ui.command.list[command] = nil
            end,
            invoke = function(command, ...)
                ui.command.list[command].callback(unpack(...))
            end,
        },
     
        verbosity = 0,
        consoleLog = function(message, verbosity)
            if not verbosity then verbosity = 0 end
            if ui.verbosity >= verbosity then
                for l in message:gmatch("[^\n]+") do
                    ShroudConsoleLog(l)
                end
            end
        end,
    }
    setmetatable(ui.timer, {__index = ui.timer.list})
    setmetatable(ui.label, {__index = ui.label.list})
    
    
    -- internal functions
    local function ui_getPlayerName()
        if player.caption ~= ShroudGetPlayerName() then
            player.caption = ShroudGetPlayerName()
            player.name = player.caption
            player.flag = ""
            player.isPvp = false
            if player.name:byte(#player.name) == 93 then
                player.name, player.flag = player.name:match("^(.-)(%[.-%])$")
                player.isPvp = player.flag and player.flag:find("PVP") ~= nil
                player.isAfk = player.flag and player.flag:find("AFK") ~= nil
            end
            ui.handler.invoke("_playerChanged", "caption")
        end
    end
    local function ui_getPlayerChange()
        local loc = { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene }
        local isMoving = loc.x ~= player.location.x or loc.y ~= player.location.y or loc.z ~= player.location.z
        if isMoving then player.lastMoved = os.time() end
        local isStill = os.time() - player.lastMoved > 5
        local invoke = isStill ~= player.isStill or isMoving ~= player.isMoving
        player.isMoving = isMoving
        player.isStill = isStill
        player.location = loc
        if invoke then
            if isMoving then
                ui.handler.invoke("_playerMoveStart")
                ui.handler.invoke("_playerChanged", "moving")
            elseif isStill then
                ui.handler.invoke("_playerIsStill")
                ui.handler.invoke("_playerChanged", "stillness")
            else
                ui.handler.invoke("_playerMoveStop")
                ui.handler.invoke("_playerChanged", "standing")
            end
        end
        local health = { max = ShroudGetStatValueByNumber(30), current = ShroudPlayerCurrentHealth, percentage = 0 }
        local focus =  { max = ShroudGetStatValueByNumber(27), current = ShroudPlayerCurrentFocus, percentage = 0 }
        health.percentage = health.max / health.current
        focus.percentage = focus.max / focus.current
        invoke = player.health.current ~= health.current or player.focus.current ~= focus.current
        if invoke then
            ui.handler.invoke("_playerDamage", health, focus)
            ui.handler.invoke("_playerChanged", "damage", health, focus)
        end
        player.health = health
        player.focus = focus
    end
    local function ui_getSceneName()
        if scene.name ~= ShroudGetCurrentSceneName() then
            scene.name = ShroudGetCurrentSceneName()
            scene.maxPlayer = ShroudGetCurrentSceneMaxPlayerCount()
            scene.isPvp = ShroudGetCurrentSceneIsPVP()
            scene.isPot = ShroudGetCurrentSceneIsPOT()
            scene.timeStarted = os.time()
            ui.handler.invoke("_sceneChanged")
        end
    end
    
    
    -- shroud hooks
    
    function ShroudOnStart()
        ShroudUseLuaConsoleForPrint(true)
        ui.handler.invoke("_start")
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
     
        ui.timer.add(0, true, function(...)
    
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and message:find("PvP") then ui_getPlayerName() end
    
            -- parse message
            local src, dst, msg = message:match("^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if msg:byte() == 92 then
                local cmd, tail = msg:match("^\\(%w+)%s*(.*)$")
                if ui.command.list[cmd] then
                    local arg = {}
                    for a in tail:gmatch("%S+") do arg[#arg + 1] = a end
                    arg.n = #arg
                    ui.command.list[cmd].callback(unpack(arg))
                else
                    ui.handler.invoke("_consoleCommand", cmd, sender, dst, tail)
                end
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
    
        end, channel, sender, message)
    end
    
    function ShroudOnUpdate()
        local ts = os.time()
    
        -- init
        if not client.screen then
            client.timeToLoad = client._ts
            client.screen = {
                width = ShroudGetScreenX(),
                height = ShroudGetScreenY(),
                isFullScreen = ShroudGetFullScreen(),
            }
            for i=0, ShroudGetStatCount()-1, 1 do
                local name = ShroudGetStatNameByNumber(i)
                client._statEnum[tostring(name)] = i
                client._statEnum[i] = name
                client._statDescr[i] = ShroudGetStatDescriptionByNumber(i)
            end
            ui_getSceneName()
            ui_getPlayerName()
            player.location = { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene }
            player.health = { max = ShroudGetStatValueByNumber(30), current = ShroudPlayerCurrentHealth, percentage = 0 }
            player.focus =  { max = ShroudGetStatValueByNumber(27), current = ShroudPlayerCurrentFocus, percentage = 0 }
    
            ui.handler.invoke("_init")
        end
    
    
        -- queued callbacks
        --[[for i,f in next, client._queue do
            f()
            client._queue[i] = nil
        end]]
     
        -- ui timer
        if ts - client._ts >= 0.25 then
            ui_getPlayerChange()
        end
     
        -- user timer
        if ts - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = ts
            for _,t in next, ui.timer.list do
                if t.enabled and ts >= t.time then
                    if t.interval then
                        t.time = ts + t.interval
                    else
                        ui.timer.list[t._index] = nil
                    end
                    if t.callback(t.userdata) then ui.timer.list[t._index] = nil end
                end
            end
        end
    
        -- check key up
        for ku,r in next, ui.shortcut.list.press do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
        -- check key help/repeat
        for ku,r in next, ui.shortcut.list.watch do
            if ShroudGetOnKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeDown = os.time()
                        f._ts = os.time()
                        f.callback("down", ku, f.keysHeld)
                    end
                end
    
            elseif ShroudGetKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        if os.time() - f.timeDown >= 0.5 then
                            if os.time() - f._ts >= 0.1 then
                                f._ts = os.time()
                                f.callback("held", ku, f.keysHeld)
                            end
                        end
                    end
                end
    
            elseif ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeUp = os.time()
                        f.callback("up", ku, f.keysHeld)
                        if os.time() - f.timeDown <= 0.5 then
                            f.callback("pressed", ku, f.keysHeld)
                        end
                    end
                end
            end
        end
     
     
        ui.handler.invoke("_update")
    
        -- client stats
        client._frame = client._frame + 1;
        if ts - client._ts >= 1 then
            if client.accuracy > 10 then
                ui_getSceneName()
                ui_getPlayerName()
                client._isLoading = false
                scene.timeToLoad = client.accuracy
            end
            client.timeInGame = ShroudTime
            client.timeDelta =  ShroudDeltaTime
            client.fps = client._frame
            client._frame = 0
            client.accuracy = ts - client._ts - 1
            client._ts = ts
            scene.timeInScene = ts - scene.timeStarted
            ui.timer._granulaty = 0.100 - (client.accuracy*10)
        end
    end
    
    
    
    local ui_debug = false
    if not ui_debug then
    
        ShroudOnGUI = function()
            local ts = os.time()
            local isHitching = ts - client._ts >= 2
            local isLoading = ts - client._ts >= 5
    
            if isHitching and client.isHitching ~= isHitching then
                client.isHitching = isHitching
                ui.handler.invoke("_clientIsHitching")
            end
            if isLoading and client.isLoading ~= isLoading then
                client.isLoading = isLoading
                ui.handler.invoke("_clientIsLoading")
            end
           
            if not isHitching or isLoading then
                for _,l in next, ui.label.list do
                    if l.visible then
                        if not isLoading and l.shownInScene then
                            ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                        elseif isLoading and l.shownInLoadScreen then
                            ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                        end
                    end
                    if os.time() - ts > 0.01 then break end
                end
            end
        end
    
    -- libsota end
    
    
    
    else -- debug start
    
        gui = {
            _ts = os.time(),
            fps = 0,
            _frame = 0,
            _ts2 = 0,
            _loopTime = 0,
        }
        ShroudOnGUI = function()
            gui._ts2 = os.time()
    
            local ts = os.time()
            local isHitching = ts - client._ts >= 2
            local isLoading = ts - client._ts >= 5
       
            if isHitching and client.isHitching ~= isHitching then
                client.isHitching = isHitching
                ui.handler.invoke("_clientIsHitching")
            end
            if isLoading and client.isLoading ~= isLoading then
                client.isLoading = isLoading
                ui.handler.invoke("_clientIsLoading")
            end
       
            if isHitching then
                if not client.screen then
                    ShroudGUILabel(30,24,1024,24, "Erster Start: "..(os.time() - client._ts).." Sekunden")
                elseif isLoading then
                    ShroudGUILabel(client.screen.width / 2 - 240, client.screen.height / 2 - 24, 512, 24, "Lade Szene: "..(os.time() - client._ts).." Sekunden")
                else
                    ShroudGUILabel(client.screen.width / 2 - 240, client.screen.height / 2 - 24, 512, 24, "Hitching: "..(os.time() - client._ts).." Sekunden")
                end
            end
       
            if not isHitching or isLoading then
                for _,l in next, ui.label.list do
                    if l.visible then
                        if not isLoading and l.shownInScene then
                            ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                        elseif isLoading and l.shownInLoadScreen then
                            ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                        end
                    end
                    if os.time() - ts > 0.01 then break end
                end
            end
       
            gui._frame = gui._frame + 1
            if os.time() - gui._ts >= 1 then
                gui.fps = gui._frame
                gui._frame = 0
                gui._ts = os.time()
            end
            gui._loopTime = (os.time() - gui._ts2) * 1000
            ShroudGUILabel(30,40,512,24, string.format("update calls: %d, gui calls: %d, sota Fps: %f, gui time: %f", client.fps, gui.fps, 1 / client.timeDelta, gui._loopTime));
        end
    
    end -- debug end
    
    
    Code:
    -- libsota.0.4.util-dev by Catweazle Waldschrath
    -- helper functions for global namespace to work with the ui.objects
    -- depends on libsota.0.4.x
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.add(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.add(interval, false, callback)
    end
    
    function getTimer(index)
        return ui.timer.list[index]
    end
    
    function cancelTimer(index)
        ui.timer.list[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer.list[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer.list[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        return ui.label.add(r.left, r.top, r.width, r.height, caption)
    end
    
    function getLabel(index)
        return ui.label.list[index]
    end
    
    function getLabelCaption(index)
        return ui.label.list[index].caption
    end
    getLabelText = getLabelCaption
    
    function setLabelCaption(index, caption)
        ui.label.list[index].caption = caption
    end
    setLabelText = setLabelCaption
    
    function removeLabel(index)
        ui.label.list[index] = nil
    end
    
    function showLabel(index)
        ui.label.list[index].visible = true
    end
    
    function hideLabel(index)
        ui.label.list[index].visible = false
    end
    
    function toggleLabel(index)
        ui.label.list[index].visible = not ui.label.list[index].visible
    end
    
    function isLabelVisible(index)
        return ui.label.list[index].visible
    end
    
    function setLabelVisible(index, visible)
        ui.label.list[index].visible = visible
    end
    
    function moveLabelBy(index, x, y)
        ui.label.list[index].left = ui.label.list[index].left + x
        ui.label.list[index].top = ui.label.list[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label.list[index].left = x
        ui.label.list[index].top = y
    end
    
    function resizeLabelBy(index, w, h)
        ui.label.list[index].width = ui.label.list[index].width + w
        ui.label.list[index].height = ui.label.list[index].height + h
    end
    
    function resizeLabelTo(index, w, h)
        ui.label.list[index].width = w
        ui.label.list[index].height = h
    end
    
    
    -- implement Shroud calls
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart() end
    
    
    
    
    
    function ui.onShortcutPressed(...)
        return ui.shortcut.add("press", ...)
    end
    function ui.onShortcut(...)
        return ui.shortcut.add("watch", ...)
    end
    ui.registerKey = ui.onShortcutPress -- depricated: ui.registerKey is about to be removed]]
    
    
    
    string.style = function(string, style)
        if style.bold then string = "<b>"..string.."</b>" end
        if style.italic then string = "<i>"..string.."</i>" end
        if style.size and style.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
        if style.color and #style.color > 3 then string = "<color="..style.color..">"..string.."</color>" end
        return string
    end
    
    
    rect = {
        _new = function(self, left, top, width, height)
            return self.new(left, top, width, height)
        end,
        new = function(left, top, width, height)
            local r = {
                left = left,
                top = top,
                width = width,
                height = height,
            }
    
            if not width then
                r.width = client.screen.width / 3.3
            elseif tonumber(width) == nil then
                width = tonumber(string.match(width, "^%d+"))
                r.width = client.screen.width / 100 * math.abs(width)
            end
            if not height then
                r.height = client.screen.height / 3.6
            elseif tonumber(height) == nil then
                height = tonumber(string.match(height, "^%d+"))
                r.height = client.screen.height / 100 * math.abs(height)
            end
            if not left then
                r.left = (client.screen.width - r.width) / 2
            elseif tonumber(left) == nil then
                left = tonumber(string.match(left, "^%d+"))
                if left < 0 then
                    r.left = client.screen.width - (client.screen.width / 100 * -left) - r.width
                else
                    r.left = client.screen.width / 100 * left
                end
            end
            if not top then
                r.top = (client.screen.height - r.height) / 2
            elseif tonumber(top) == nil then
                top = tonumber(string.match(top, "^%d+"))
                if top < 0 then
                    r.top = client.screen.height - (client.screen.height / 100 * -top) - r.height
                else
                    r.top = client.screen.height / 100 * top
                end
            end
    
            return r
        end,
    
        fromString = function(string)
            local str = string:gsub("<[^>]*>", "")
            local s = string:match("<size=(%d-)")
            if not s then s = 14 end
            return { left = 0, top = 0, width = str:len() * (s/2), height = s*2 }
        end,
     
        moveTo = function(rect, x, y)
            if not x then x = (client.screen.width - rect.width) / 2 end
            if not y then y = (client.screen.height - rect.height) / 2 end
            rect.left = x
            rect.top = y
            return rect
        end,
     
        moveBy = function(rect, x, y)
            rect.left = rect.left + x
            rect.top = rect.top + y
            return rect
        end
    }
    setmetatable(rect, {__call = rect._new})
    
    

    needs Live 1077

    i tested libsota.0.4.0 / libsota.util.0.4 and it is time to release it

    client.timeToLoad - tells how long it tooks to load into the first scene
    ui.label.size was removed
    ui.label.resizeTo - resizes a label to the given size
    ui.label.resizeBy - resizes a label to the given delta size

    ui.keyStrokes was renamed to ui.shortcut
    ui.shortcut.add("press", key,[key],...,callback) - adds a shortcut and invoke the callback when the shortcut was pressed
    ui.shortcut.add("watch", key,[key],...,callback) - add a shortcut and invoke the callback on shortcut down,up,held and pressed
    callback shortcut(string state, string key, table modifiers) - when "watch" was used
    callback shortcut() - when "press" was used.
    "pressed" is not send when the shortcut was held. the sequence is as follows:
    user pressed a shortcut: down, up, pressed
    user held a shortcut: down, held, [held], [held], ..., [held], up

    callback playerChanged(what) - the invoked function tells in its argument what, what has changed: moving, standing, stillness, name or damage. When playerChanged is invoked because of damage two arguments are added: health and focus
    ui.onPlayerDamage(health, focus) and ui.onPlayerChanged("damage", health, focus) are called when either current focus or current health about to change. (so, not only on damage)

    player.health and player.focus - the percentage now needs to be multiplied by 100 to get a percent value. this is easier when it is used for a progress bar:
    max_dots * player.health.percentage for "on" dots and 100 * player.health.percentage for the percentage value to show

    ----- libsota.utl
    added:
    resizeLabelBy
    resizeLabelTo
    ui.onShortcutPressed(key,[key],...,callback) - invoked when a shourcut was used
    ui.onShortcut(key,[key],...,callback) - invoked on shortcut down, up, held or pressed
     
    Last edited: Nov 22, 2019
    Browncoat Jayson likes this.
  13. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    you can test it with 0.4 i do not have had problems with it. May there is another script that has not implmented all ShroudOn.. functions.
     
  14. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.4.1 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeToLoad = 0,
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        isHitching = false,
        isLoading = true,
        _frame = 0,
        _ts = os.time(),
        _isLoading = false,
        _statEnum = {},
        _statDescr = {},
        --_queue = {},
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        isAfk = nil,
        isMoving = false,
        isStill = false,
        lastMoved = os.time(),
        location = {},
        health = {},
        focus = {},
        xp = function() return { producer = ShroudGetPooledProducerExperience(), adventurer = ShroudGetPooledAdventurerExperience() } end,
        stat = function(index)
            local ret = {
                number = -1,
                name = "invalid",
                value = -999,
                description = "",
            }
            if tonumber(index) == nil then
                index = client._statEnum[index]
            else
                index = tonumber(index)
            end
            if index and index <= #client._statEnum then
                ret.number = index
                ret.name = client._statEnum[index]
                ret.value = ShroudGetStatValueByNumber(index)
                ret.description = client._statDescr[index]
            end
            return ret
        end,
    }
    ui = {
        timer = {
            _ts = os.time(),
            _granulaty = 0.100,
            list = {},
            add = function(timeout, once, callback, ...)
                local index = #ui.timer.list + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer.list[index] = {
                    _index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    callback = callback,
                    userdata = ...,
                }
                return index
            end,
            get = function(index)
                return ui.timer.list[index]
            end,
            remove = function(index)
                ui.timer.list[index] = nil
            end,
            enabled = function(index, enabled)
                if enabled ~= nil then
                    ui.timer.list[index].enabled = enabled
                end
                return ui.timer.list[index].enabled
            end,
            pause = function(index)
                ui.timer.list[index].enabled = false
            end,
            resume = function(index)
                ui.timer.list[index].enabled = true
            end,
            toggle = function(index)
                ui.timer.list[index].enabled = not ui.timer.list[index].enabled
            end,
        },
        setTimeout = function(timeout, callback) return ui.timer.add(timeout, true, callback) end,
        setInterval = function(interval, callback) return ui.timer.add(interval, false, callback) end,
    
        
        handler = {
            list = {},
            add = function(name, callback)
                local index = #ui.handler.list + 1
    
                ui.handler.list[index] = {
                    _index = index,
                    name = name,
                    callback = callback,
                }
                return index    
            end,
            remove = function(index)
                ui.handler.list[index] = nil
            end,
            invoke = function(name, ...)
                for _,h in next, ui.handler.list do
                    if h.name == name then
                        --print(os.date("%X").." : invoke "..h.name)
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.callback(...)
                    end
                end
            end,
        },
        onInit = function(callback) return ui.handler.add("_init", callback) end,
        onStart = function(callback) return ui.handler.add("_start", callback) end,
        onUpdate = function(callback) return ui.handler.add("_update", callback) end,
        onConsoleInput = function(callback) return ui.handler.add("_consoleInput", callback) end,
        onConsoleCommand = function(callback) return ui.handler.add("_consoleCommand", callback) end,
        onSceneChanged = function(callback) return ui.handler.add("_sceneChanged", callback) end,
        onPlayerChanged = function(callback) return ui.handler.add("_playerChanged", callback) end,
        onPlayerMoveStart = function(callback) return ui.handler.add("_playerMoveStart", callback) end,
        onPlayerMoveStop = function(callback) return ui.handler.add("_playerMoveStop", callback) end,
        onPlayerIsStill = function(callback) return ui.handler.add("_playerIsStill", callback) end,
        onPlayerDamage = function(callback) return ui.handler.add("_playerDamage", callback) end,
        onClientIsHitching = function(callback) return ui.handler.add("_clientIsHitching", callback) end,
        onClientIsLoading = function(callback) return ui.handler.add("_clientIsLoading", callback) end,
    
    
        label = {
            list = {},
            add = function(left, top, width, height, caption)
                local index = #ui.label.list + 1
    
                ui.label.list[index] = {
                    _index = index,
                    left = left,
                    top = top,
                    width = width,
                    height = height,
                    caption = caption,
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                }
                setmetatable(ui.label.list[index], {__index = ui.label})
                return index
            end,
            get = function(index)
                return ui.label.list[index]
            end,
            remove = function(index)
                ui.label.list[index] = nil
            end,
            rect = function(index, rect)
                local l = ui.label.list[index]
                l.left = rect.left
                l.top = rect.top
                l.width = rect.width
                l.height = rect.height
            end,
            caption = function(index, caption)
                if caption then
                    ui.label.list[index].caption = caption
                end
                return ui.label.list[index].caption
            end,
            visible = function(index, visible)
                if visible ~= nil then
                    ui.label.list[index].visible = visible
                end
                return ui.label.list[index].visible
            end,
            toggle = function(index)
                ui.label.list[index].visible = not ui.label.list[index].visible
            end,
            moveTo = function(index, x, y)
                ui.label.list[index].left = x
                ui.label.list[index].top = y
            end,
            moveBy = function(index, x, y)
                ui.label.list[index].left = ui.label.list[index].left + x
                ui.label.list[index].top = ui.label.list[index].top + y
            end,
            resizeTo = function(index, w, h)
                ui.label.list[index].width = w
                ui.label.list[index].height = h
            end,
            resizeBy = function(index, x, y)
                ui.label.list[index].width = ui.label.list[index].width + x
                ui.label.list[index].height = ui.label.list[index].height + y
            end,
        },
        
        shortcut = {
            list = { pressed = {}, watch = {} },
            add = function(action, ...)
                local keys = {...}
                local callback = keys[#keys]; keys[#keys] = nil
                local key = keys[#keys]; keys[#keys] = nil
                if not ui.shortcut.list[action][tostring(key)] then
                    ui.shortcut.list[action][tostring(key)] = {}
                end
                if type(callback) == "string" then
                    callback = ui.command.list[callback].callback
                end
                local id = #ui.shortcut.list[action][key] + 1
                local index = { action = action, key = key, id = id }
                ui.shortcut.list[action][key][id] = {
                    _index = index,
                    key = key,
                    keysHeld = keys,
                    callback = callback,
                }
                if type(callback) == string then
                    if ui.command.list[callback].shortcut then
                        ui.shortcut.remove(ui.command.list[callback].shortcut)
                    end
                    ui.command.list[callback].shortcut = index
                end
                return index
            end,
            remove = function(index)
                ui.shortcut.list[index.action][index.key][index.id] = nil
                if #ui.shortcut.list[index.action][index.key] == 0 then ui.shortcut.list[index.action][index.key] = nil end
            end,
            invoke = function(index)
                ui.shortcut.list[index.action][index.key][index.id].callback()
            end,
        },
        
        command = {
            list = {},
            add = function(command, callback)
                ui.command.list[tostring(command)] = {
                    _command = command,
                    callback = callback,
                    shortcut = nil,
                }
                return command
            end,
            remove = function(command)
                if ui.command.list[command].shortcut then
                    ui.shortcut.remove(ui.command.list[command].shortcut)
                end
                ui.command.list[command] = nil
            end,
            invoke = function(command, ...)
                if ... then
                    ui.command.list[command].callback(unpack(...))
                else
                    ui.command.list[command].callback()
                end
            end,
        },
        
        verbosity = 0,
        consoleLog = function(message, verbosity)
            if not verbosity then verbosity = 0 end
            if ui.verbosity >= verbosity then
                for l in message:gmatch("[^\n]+") do
                    ShroudConsoleLog(l)
                end
            end
        end,
    }
    setmetatable(ui.timer, {__index = ui.timer.list})
    setmetatable(ui.label, {__index = ui.label.list})
    
    
    -- internal functions
    local function ui_getPlayerName()
        if player.caption ~= ShroudGetPlayerName() then
            player.caption = ShroudGetPlayerName()
            player.name = player.caption
            player.flag = ""
            player.isPvp = false
            if player.name:byte(#player.name) == 93 then
                player.name, player.flag = player.name:match("^(.-)(%[.-%])$")
                player.isPvp = player.flag and player.flag:find("PVP") ~= nil
                player.isAfk = player.flag and player.flag:find("AFK") ~= nil
            end
            ui.handler.invoke("_playerChanged", "caption")
        end
    end
    local function ui_getPlayerChange()
        local loc = { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene }
        local isMoving = loc.x ~= player.location.x or loc.y ~= player.location.y or loc.z ~= player.location.z
        if isMoving then player.lastMoved = os.time() end
        local isStill = os.time() - player.lastMoved > 5
        local invoke = isStill ~= player.isStill or isMoving ~= player.isMoving
        player.isMoving = isMoving
        player.isStill = isStill
        player.location = loc
        if invoke then
            if isMoving then
                ui.handler.invoke("_playerMoveStart")
                ui.handler.invoke("_playerChanged", "moving")
            elseif isStill then
                ui.handler.invoke("_playerIsStill")
                ui.handler.invoke("_playerChanged", "stillness")
            else
                ui.handler.invoke("_playerMoveStop")
                ui.handler.invoke("_playerChanged", "standing")
            end
        end
        local health = { max = ShroudGetStatValueByNumber(30), current = ShroudPlayerCurrentHealth, percentage = 0 }
        local focus =  { max = ShroudGetStatValueByNumber(27), current = ShroudPlayerCurrentFocus, percentage = 0 }
        health.percentage = health.max / health.current
        focus.percentage = focus.max / focus.current
        invoke = player.health.current ~= health.current or player.focus.current ~= focus.current
        if invoke then
            ui.handler.invoke("_playerDamage", health, focus)
            ui.handler.invoke("_playerChanged", "damage", health, focus)
        end
        player.health = health
        player.focus = focus
    end
    local function ui_getSceneName()
        if scene.name ~= ShroudGetCurrentSceneName() then
            scene.name = ShroudGetCurrentSceneName()
            scene.maxPlayer = ShroudGetCurrentSceneMaxPlayerCount()
            scene.isPvp = ShroudGetCurrentSceneIsPVP()
            scene.isPot = ShroudGetCurrentSceneIsPOT()
            scene.timeStarted = os.time()
            ui.handler.invoke("_sceneChanged")
        end
    end
    
    
    -- shroud hooks
    
    function ShroudOnStart()
        ShroudUseLuaConsoleForPrint(true)
        ui.handler.invoke("_start")
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
        
        ui.timer.add(0, true, function(...)
    
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and message:find("PvP") then ui_getPlayerName() end
    
            -- parse message
            local src, dst, msg = message:match("^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if msg:byte() == 92 then
                local cmd, tail = msg:match("^\\(%w+)%s*(.*)$")
                if ui.command.list[cmd] then
                    local arg = {}
                    for a in tail:gmatch("%S+") do arg[#arg + 1] = a end
                    arg.n = #arg
                    ui.command.list[cmd].callback(sender, dst, unpack(arg))
                else
                    ui.handler.invoke("_consoleCommand", cmd, sender, dst, tail)
                end
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
    
        end, channel, sender, message)
    end
    
    function ShroudOnUpdate()
        local ts = os.time()
    
        -- init
        if not client.screen then
            client.timeToLoad = client._ts
            client.screen = {
                width = ShroudGetScreenX(),
                height = ShroudGetScreenY(),
                isFullScreen = ShroudGetFullScreen(),
            }
            for i=0, ShroudGetStatCount()-1, 1 do
                local name = ShroudGetStatNameByNumber(i)
                client._statEnum[tostring(name)] = i
                client._statEnum[i] = name
                client._statDescr[i] = ShroudGetStatDescriptionByNumber(i)
            end
            ui_getSceneName()
            ui_getPlayerName()
            player.location = { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene }
            player.health = { max = ShroudGetStatValueByNumber(30), current = ShroudPlayerCurrentHealth, percentage = 0 }
            player.focus =  { max = ShroudGetStatValueByNumber(27), current = ShroudPlayerCurrentFocus, percentage = 0 }
    
            ui.handler.invoke("_init")
        end
    
    
        -- queued callbacks
        --[[for i,f in next, client._queue do
            f()
            client._queue[i] = nil
        end]]
        
        -- ui timer
        if ts - client._ts >= 0.25 then
            ui_getPlayerChange()
        end
        
        -- user timer
        if ts - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = ts
            for _,t in next, ui.timer.list do
                if t.enabled and ts >= t.time then
                    if t.interval then
                        t.time = ts + t.interval
                    else
                        ui.timer.list[t._index] = nil
                    end
                    if t.callback(t.userdata) then ui.timer.list[t._index] = nil end
                end
            end
        end
    
        -- check key up
        for ku,r in next, ui.shortcut.list.pressed do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
        -- check key help/repeat
        for ku,r in next, ui.shortcut.list.watch do
            if ShroudGetOnKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeDown = os.time()
                        f._ts = os.time()
                        f.callback("down", ku, f.keysHeld)
                    end
                end
    
            elseif ShroudGetKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        if os.time() - f.timeDown >= 0.5 then
                            if os.time() - f._ts >= 0.0001 then
                                f._ts = os.time()
                                f.callback("held", ku, f.keysHeld)
                            end
                        end
                    end
                end
    
            elseif ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeUp = os.time()
                        f.callback("up", ku, f.keysHeld)
                        if os.time() - f.timeDown <= 0.5 then
                            f.callback("pressed", ku, f.keysHeld)
                        end
                    end
                end
            end
        end
        
        
        ui.handler.invoke("_update")
    
        -- client stats
        client._frame = client._frame + 1;
        if ts - client._ts >= 1 then
            if client.accuracy > 10 then
                ui_getSceneName()
                ui_getPlayerName()
                client._isLoading = false
                scene.timeToLoad = client.accuracy
            end
            client.timeInGame = ShroudTime
            client.timeDelta =  ShroudDeltaTime
            client.fps = client._frame
            client._frame = 0
            client.accuracy = ts - client._ts - 1
            client._ts = ts
            scene.timeInScene = ts - scene.timeStarted
            ui.timer._granulaty = 0.100 - (client.accuracy*10)
        end
    end
    
    
    
    local ui_debug = false
    if not ui_debug then
    
        ShroudOnGUI = function()
            local ts = os.time()
            local isHitching = ts - client._ts >= 2
            local isLoading = ts - client._ts >= 5
    
            if isHitching and client.isHitching ~= isHitching then
                client.isHitching = isHitching
                ui.handler.invoke("_clientIsHitching")
            end
            if isLoading and client.isLoading ~= isLoading then
                client.isLoading = isLoading
                ui.handler.invoke("_clientIsLoading")
            end
                
            if not isHitching or isLoading then
                for _,l in next, ui.label.list do
                    if l.visible then
                        if not isLoading and l.shownInScene then
                            ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                        elseif isLoading and l.shownInLoadScreen then
                            ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                        end
                    end
                    if os.time() - ts > 0.01 then break end
                end
            end
        end
    
    -- libsota end
    
    
    
    else -- debug start
    
        gui = {
            _ts = os.time(),
            fps = 0,
            _frame = 0,
            _ts2 = 0,
            _loopTime = 0,
        }
        ShroudOnGUI = function()
            gui._ts2 = os.time()
    
            local ts = os.time()
            local isHitching = ts - client._ts >= 2
            local isLoading = ts - client._ts >= 5
            
            if isHitching and client.isHitching ~= isHitching then
                client.isHitching = isHitching
                ui.handler.invoke("_clientIsHitching")
            end
            if isLoading and client.isLoading ~= isLoading then
                client.isLoading = isLoading
                ui.handler.invoke("_clientIsLoading")
            end
            
            if isHitching then
                if not client.screen then
                    ShroudGUILabel(30,24,1024,24, "Erster Start: "..(os.time() - client._ts).." Sekunden")
                elseif isLoading then
                    ShroudGUILabel(client.screen.width / 2 - 240, client.screen.height / 2 - 24, 512, 24, "Lade Szene: "..(os.time() - client._ts).." Sekunden")
                else
                    ShroudGUILabel(client.screen.width / 2 - 240, client.screen.height / 2 - 24, 512, 24, "Hitching: "..(os.time() - client._ts).." Sekunden")
                end
            end
            
            if not isHitching or isLoading then
                for _,l in next, ui.label.list do
                    if l.visible then
                        if not isLoading and l.shownInScene then
                            ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                        elseif isLoading and l.shownInLoadScreen then
                            ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                        end
                    end
                    if os.time() - ts > 0.01 then break end
                end
            end
            
            gui._frame = gui._frame + 1
            if os.time() - gui._ts >= 1 then
                gui.fps = gui._frame
                gui._frame = 0
                gui._ts = os.time()
            end
            gui._loopTime = (os.time() - gui._ts2) * 1000
            ShroudGUILabel(30,40,512,24, string.format("update calls: %d, gui calls: %d, sota Fps: %f, gui time: %f", client.fps, gui.fps, 1 / client.timeDelta, gui._loopTime));
        end
    
    end -- debug end
    
    
    Code:
    -- libsota.0.4.util-dev by Catweazle Waldschrath
    -- helper functions for global namespace to work with the ui.objects
    -- depends on libsota.0.4.x
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.add(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.add(interval, false, callback)
    end
    
    function getTimer(index)
        return ui.timer.list[index]
    end
    
    function cancelTimer(index)
        ui.timer.list[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer.list[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer.list[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        return ui.label.add(r.left, r.top, r.width, r.height, caption)
    end
    
    function getLabel(index)
        return ui.label.list[index]
    end
    
    function getLabelCaption(index)
        return ui.label.list[index].caption
    end
    getLabelText = getLabelCaption
    
    function setLabelCaption(index, caption)
        ui.label.list[index].caption = caption
    end
    setLabelText = setLabelCaption
    
    function removeLabel(index)
        ui.label.list[index] = nil
    end
    
    function showLabel(index)
        ui.label.list[index].visible = true
    end
    
    function hideLabel(index)
        ui.label.list[index].visible = false
    end
    
    function toggleLabel(index)
        ui.label.list[index].visible = not ui.label.list[index].visible
    end
    
    function isLabelVisible(index)
        return ui.label.list[index].visible
    end
    
    function setLabelVisible(index, visible)
        ui.label.list[index].visible = visible
    end
    
    function moveLabelBy(index, x, y)
        ui.label.list[index].left = ui.label.list[index].left + x
        ui.label.list[index].top = ui.label.list[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label.list[index].left = x
        ui.label.list[index].top = y
    end
    
    function resizeLabelBy(index, w, h)
        ui.label.list[index].width = ui.label.list[index].width + w
        ui.label.list[index].height = ui.label.list[index].height + h
    end
    
    function resizeLabelTo(index, w, h)
        ui.label.list[index].width = w
        ui.label.list[index].height = h
    end
    
    
    -- implement Shroud calls
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart() end
    
    
    
    
    
    function ui.onShortcutPressed(...)
        return ui.shortcut.add("pressed", ...)
    end
    function ui.onShortcut(...)
        return ui.shortcut.add("watch", ...)
    end
    ui.registerKey = ui.onShortcutPress -- depricated: ui.registerKey is about to be removed]]
    
    
    
    string.style = function(string, style)
        if style.bold then string = "<b>"..string.."</b>" end
        if style.italic then string = "<i>"..string.."</i>" end
        if style.size and style.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
        if style.color and #style.color > 3 then string = "<color="..style.color..">"..string.."</color>" end
        return string
    end
    
    
    rect = {
        _new = function(self, left, top, width, height)
            return self.new(left, top, width, height)
        end,
        new = function(left, top, width, height)
            local r = {
                left = left,
                top = top,
                width = width,
                height = height,
            }
    
            if not width then
                r.width = client.screen.width / 3.3
            elseif tonumber(width) == nil then
                width = tonumber(string.match(width, "^%d+"))
                r.width = client.screen.width / 100 * math.abs(width)
            end
            if not height then
                r.height = client.screen.height / 3.6
            elseif tonumber(height) == nil then
                height = tonumber(string.match(height, "^%d+"))
                r.height = client.screen.height / 100 * math.abs(height)
            end
            if not left then
                r.left = (client.screen.width - r.width) / 2
            elseif tonumber(left) == nil then
                left = tonumber(string.match(left, "^%d+"))
                if left < 0 then
                    r.left = client.screen.width - (client.screen.width / 100 * -left) - r.width
                else
                    r.left = client.screen.width / 100 * left
                end
            end
            if not top then
                r.top = (client.screen.height - r.height) / 2
            elseif tonumber(top) == nil then
                top = tonumber(string.match(top, "^%d+"))
                if top < 0 then
                    r.top = client.screen.height - (client.screen.height / 100 * -top) - r.height
                else
                    r.top = client.screen.height / 100 * top
                end
            end
    
            return r
        end,
    
        fromString = function(string)
            local str = string:gsub("<[^>]*>", "")
            local s = string:match("<size=(%d-)")
            if not s then s = 14 end
            return { left = 0, top = 0, width = str:len() * (s/2), height = s*2 }
        end,
        
        moveTo = function(rect, x, y)
            if not x then x = (client.screen.width - rect.width) / 2 end
            if not y then y = (client.screen.height - rect.height) / 2 end
            rect.left = x
            rect.top = y
            return rect
        end,
        
        moveBy = function(rect, x, y)
            rect.left = rect.left + x
            rect.top = rect.top + y
            return rect
        end
    }
    setmetatable(rect, {__call = rect._new})
    
    

    bug fixes
    callback for commands are now called with sender and receiver - callback command(sender, reciever, ...)
     
    Jaesun and Browncoat Jayson like this.
  15. Drake Aedus

    Drake Aedus Avatar

    Messages:
    542
    Likes Received:
    891
    Trophy Points:
    75
    Gender:
    Male
    @CatweazleX If I wanted to overwrite the command character used in the library from "\" to something else, what would be the best way to do that from my script?
     
    Last edited: Nov 23, 2019
  16. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    It is hardcoded in the library. It is also a shared library that is, may, be used by other mods on the same client. I have seen that people use _ and ! as command prefix. What do you want to use?
    Can change it so, that commands are need to be registered including the prefix. That would allow each mod (and each command) to use its own prefix (first char), but it also leads to some inconsistency for the player (/sota command, \lua command).
    What do you think allowing define own prefix, or stay with one? We can also select another one.

    In the current version you need to use ui.onConsoleInput and check for commands on your own. You can still use ui.command.add and then from ui.onConsoleInput use/call ui.command.invoke("<commandname>"). In this case your command would use both \ and your prefix. And you can still bind a shortcut to the command.
    Code:
    -- somewhere around line 376
            if msg:byte() == 92 then
                local cmd, tail = msg:match("^\\(%w+)%s*(.*)$")
    
    That is what i use, replace 92 with the ascii value of your prefix and \\ with your prefix (in your code). And then ui.command.invoke(cmd, sender, receiver, tail)
     
    Last edited: Nov 23, 2019
    Jaesun and Drake Aedus like this.
  17. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.4.2 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeToLoad = 0,
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        isHitching = false,
        isLoading = false,
        _statEnum = {},
        _statDescr = {},
        api = {
            luaVersion = "",
            list = {},
        },
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        isAfk = nil,
        isMoving = false,
        isStill = false,
        lastMoved = os.time(),
        location = {},
        health = {},
        focus = {},
        xp = function() return { producer = ShroudGetPooledProducerExperience(), adventurer = ShroudGetPooledAdventurerExperience() } end,
        stat = function(index)
            local ret = {
                number = -1,
                name = "invalid",
                value = -999,
                description = "",
            }
            if tonumber(index) == nil then
                index = client._statEnum[index]
            else
                index = tonumber(index)
            end
            if index and index <= #client._statEnum then
                ret.number = index
                ret.name = client._statEnum[index]
                ret.value = ShroudGetStatValueByNumber(index)
                ret.description = client._statDescr[index]
            end
            return ret
        end,
    }
    ui = {
        timer = {
            list = {},
            add = function(timeout, once, callback, ...)
                local index = #ui.timer.list + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer.list[index] = {
                    _index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    callback = callback,
                    userdata = ...,
                }
                return index
            end,
            get = function(index)
                return ui.timer.list[index]
            end,
            remove = function(index)
                ui.timer.list[index] = nil
            end,
            enabled = function(index, enabled)
                if enabled ~= nil then
                    ui.timer.list[index].enabled = enabled
                end
                return ui.timer.list[index].enabled
            end,
            pause = function(index)
                ui.timer.list[index].enabled = false
            end,
            resume = function(index)
                ui.timer.list[index].enabled = true
            end,
            toggle = function(index)
                ui.timer.list[index].enabled = not ui.timer.list[index].enabled
            end,
        },
        setTimeout = function(timeout, callback) return ui.timer.add(timeout, true, callback) end,
        setInterval = function(interval, callback) return ui.timer.add(interval, false, callback) end,
    
        
        handler = {
            list = {},
            add = function(name, callback)
                local index = #ui.handler.list + 1
    
                ui.handler.list[index] = {
                    _index = index,
                    name = name,
                    callback = callback,
                }
                return index    
            end,
            remove = function(index)
                ui.handler.list[index] = nil
            end,
            invoke = function(name, ...)
                if not ui_initialized then return end
                for _,h in next, ui.handler.list do
                    if h.name == name then
                        --print(os.date("%X").." : invoke "..h.name)
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.callback(...)
                    end
                end
            end,
        },
        onInit = function(callback) return ui.handler.add("_init", callback) end,
        onStart = function(callback) return ui.handler.add("_start", callback) end,
        onUpdate = function(callback) return ui.handler.add("_update", callback) end,
        onConsoleInput = function(callback) return ui.handler.add("_consoleInput", callback) end,
        onConsoleCommand = function(callback) return ui.handler.add("_consoleCommand", callback) end,
        onSceneChanged = function(callback) return ui.handler.add("_sceneChanged", callback) end,
        onPlayerChanged = function(callback) return ui.handler.add("_playerChanged", callback) end,
        onPlayerMoveStart = function(callback) return ui.handler.add("_playerMoveStart", callback) end,
        onPlayerMoveStop = function(callback) return ui.handler.add("_playerMoveStop", callback) end,
        onPlayerIsStill = function(callback) return ui.handler.add("_playerIsStill", callback) end,
        onPlayerDamage = function(callback) return ui.handler.add("_playerDamage", callback) end,
        onClientIsHitching = function(callback) return ui.handler.add("_clientIsHitching", callback) end,
        onClientIsLoading = function(callback) return ui.handler.add("_clientIsLoading", callback) end,
    
    
        label = {
            list = {},
            add = function(left, top, width, height, caption)
                local index = #ui.label.list + 1
    
                ui.label.list[index] = {
                    _index = index,
                    left = left,
                    top = top,
                    width = width,
                    height = height,
                    caption = caption,
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                }
                setmetatable(ui.label.list[index], {__index = ui.label})
                return index
            end,
            get = function(index)
                return ui.label.list[index]
            end,
            remove = function(index)
                ui.label.list[index] = nil
            end,
            rect = function(index, rect)
                local l = ui.label.list[index]
                l.left = rect.left
                l.top = rect.top
                l.width = rect.width
                l.height = rect.height
            end,
            caption = function(index, caption)
                if caption then
                    ui.label.list[index].caption = caption
                end
                return ui.label.list[index].caption
            end,
            visible = function(index, visible)
                if visible ~= nil then
                    ui.label.list[index].visible = visible
                end
                return ui.label.list[index].visible
            end,
            toggle = function(index)
                ui.label.list[index].visible = not ui.label.list[index].visible
            end,
            moveTo = function(index, x, y)
                ui.label.list[index].left = x
                ui.label.list[index].top = y
            end,
            moveBy = function(index, x, y)
                ui.label.list[index].left = ui.label.list[index].left + x
                ui.label.list[index].top = ui.label.list[index].top + y
            end,
            resizeTo = function(index, w, h)
                ui.label.list[index].width = w
                ui.label.list[index].height = h
            end,
            resizeBy = function(index, x, y)
                ui.label.list[index].width = ui.label.list[index].width + x
                ui.label.list[index].height = ui.label.list[index].height + y
            end,
        },
        
        shortcut = {
            list = { pressed = {}, watch = {} },
            add = function(action, ...)
                local keys = {...}
                local callback = keys[#keys]; keys[#keys] = nil
                local key = keys[#keys]; keys[#keys] = nil
                if not ui.shortcut.list[action][tostring(key)] then
                    ui.shortcut.list[action][tostring(key)] = {}
                end
                if type(callback) == "string" then
                    callback = ui.command.list[callback].callback
                end
                local id = #ui.shortcut.list[action][key] + 1
                local index = { action = action, key = key, id = id }
                ui.shortcut.list[action][key][id] = {
                    _index = index,
                    key = key,
                    keysHeld = keys,
                    callback = callback,
                }
                if type(callback) == string then
                    if ui.command.list[callback].shortcut then
                        ui.shortcut.remove(ui.command.list[callback].shortcut)
                    end
                    ui.command.list[callback].shortcut = index
                end
                return index
            end,
            remove = function(index)
                ui.shortcut.list[index.action][index.key][index.id] = nil
                if #ui.shortcut.list[index.action][index.key] == 0 then ui.shortcut.list[index.action][index.key] = nil end
            end,
            invoke = function(index)
                ui.shortcut.list[index.action][index.key][index.id].callback()
            end,
        },
        
        command = {
            list = {},
            add = function(command, callback)
                ui.command.list[tostring(command)] = {
                    _command = command,
                    callback = callback,
                    shortcut = nil,
                    channel = nil,
                    sender = player.name,
                    receiver = nil,
                }
                return command
            end,
            remove = function(command)
                if ui.command.list[command].shortcut then
                    ui.shortcut.remove(ui.command.list[command].shortcut)
                end
                ui.command.list[command] = nil
            end,
            invoke = function(command, ...)
                if ... then
                    ui.command.list[command].callback(nil, unpack(...))
                else
                    ui.command.list[command].callback()
                end
            end,
            restrict = function(command, channel, sender, receiver)
                if ui.command[command] then
                    ui.command[command].channel = channel
                    ui.command[command].sender = sender
                    ui.command[command].receiver = receiver
                end
            end
        },
        
        verbosity = 0,
        consoleLog = function(message, verbosity)
            if not verbosity then verbosity = 0 end
            if ui.verbosity >= verbosity then
                for l in message:gmatch("[^\n]+") do
                    ShroudConsoleLog(l)
                end
            end
        end,
    }
    setmetatable(ui.timer, {__index = ui.timer.list})
    setmetatable(ui.label, {__index = ui.label.list})
    
    
    
    -- internal
    ui_initialized = false
    ui_client_ts = os.time()
    ui_client_frame = 0
    
    local function ui_getPlayerName()
        if player.caption ~= ShroudGetPlayerName() then
            player.caption = ShroudGetPlayerName()
            player.name = player.caption
            player.flag = ""
            player.isPvp = false
            if player.name:byte(#player.name) == 93 then
                player.name, player.flag = player.name:match("^(.-)(%[.-%])$")
                player.isPvp = player.flag and player.flag:find("PVP") ~= nil
                player.isAfk = player.flag and player.flag:find("AFK") ~= nil
            end
            ui.handler.invoke("_playerChanged", "caption")
        end
    end
    local function ui_getPlayerChange()
        local loc = { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene }
        local isMoving = loc.x ~= player.location.x or loc.y ~= player.location.y or loc.z ~= player.location.z
        if isMoving then player.lastMoved = os.time() end
        local isStill = os.time() - player.lastMoved > 5
        local invoke = isStill ~= player.isStill or isMoving ~= player.isMoving
        player.isMoving = isMoving
        player.isStill = isStill
        player.location = loc
        if invoke then
            if isMoving then
                ui.handler.invoke("_playerMoveStart")
                ui.handler.invoke("_playerChanged", "moving")
            elseif isStill then
                ui.handler.invoke("_playerIsStill")
                ui.handler.invoke("_playerChanged", "stillness")
            else
                ui.handler.invoke("_playerMoveStop")
                ui.handler.invoke("_playerChanged", "standing")
            end
        end
        local health = { max = ShroudGetStatValueByNumber(30), current = ShroudPlayerCurrentHealth, percentage = 0 }
        local focus =  { max = ShroudGetStatValueByNumber(27), current = ShroudPlayerCurrentFocus, percentage = 0 }
        if not health.current or not health.max or not focus.current or not focus.max then
            print(health.current, health.max, focus.current, focus.max) -- stats sind noch nicht da....
        end
        health.percentage = health.max / health.current
        focus.percentage = focus.max / focus.current
        invoke = player.health.current ~= health.current or player.focus.current ~= focus.current
        if invoke then
            ui.handler.invoke("_playerDamage", health, focus)
            ui.handler.invoke("_playerChanged", "damage", health, focus)
        end
        player.health = health
        player.focus = focus
    end
    local function ui_getSceneName()
        if scene.name ~= ShroudGetCurrentSceneName() then
            scene.name = ShroudGetCurrentSceneName()
            scene.maxPlayer = ShroudGetCurrentSceneMaxPlayerCount()
            scene.isPvp = ShroudGetCurrentSceneIsPVP()
            scene.isPot = ShroudGetCurrentSceneIsPOT()
            scene.timeStarted = os.time()
            ui.handler.invoke("_sceneChanged")
        end
    end
    local function ui_initialize()
        client.timeToLoad = ui_client_ts
        client.screen = {
            width = ShroudGetScreenX(),
            height = ShroudGetScreenY(),
            isFullScreen = ShroudGetFullScreen(),
        }
        for i=0, ShroudGetStatCount()-1, 1 do
            local name = ShroudGetStatNameByNumber(i)
            client._statEnum[tostring(name)] = i
            client._statEnum[i] = name
            client._statDescr[i] = ShroudGetStatDescriptionByNumber(i)
        end
        ui_getSceneName()
        ui_getPlayerName()
        ui_getPlayerChange()
    
        ShroudRegisterPeriodic("ui_timer_internal", "ui_timer_internal", 0.240, true)
        ShroudRegisterPeriodic("ui_timer_user", "ui_timer_user", 0.120, true)
    end
    
    
    
    -- shroud hooks
    
    
    -- shroud timers
    
    function ui_timer_internal()
        ui_getPlayerChange()
    end
    function ui_timer_user()
        local ts = os.time()
        
        for _,t in next, ui.timer.list do
            if t.enabled and ts >= t.time then
                if t.interval then
                    t.time = ts + t.interval
                else
                    ui.timer.list[t._index] = nil
                end
                if t.callback(t.userdata) then ui.timer.list[t._index] = nil end
            end
        end
    end
    
    
    -- shroud callbacks
    
    function ShroudOnStart()
        ShroudUseLuaConsoleForPrint(true)
        ShroudConsoleLog(_G._MOONSHARP.banner)
        ShroudConsoleLog("LUA Version: ".._G._MOONSHARP.luacompat)
    
        client.api.luaVersion = _G._MOONSHARP.luacompat
        for i,v in next, _G do
            if i:find("^Shroud") then client.api.list[tostring(i)] = type(v) end
        end
        
        ui.handler.invoke("_start")
    end
    
    function ShroudOnUpdate()
        local ts = os.time()
    
        -- init
        if not ui_initialized then
            if not ShroudServerTime then return end -- shroud Api not ready, yet
            ShroudConsoleLog("Shroud Api Ready. ServerTime: "..ShroudServerTime)
    
            ui_initialize()
            ui_initialized = true
    
            ui.handler.invoke("_init")
        end
    
        -- client stats
        ui_client_frame = ui_client_frame + 1;
        if ts - ui_client_ts >= 1 then
            if client.accuracy > 10 then
                ui_getSceneName()
                ui_getPlayerName()
                client.isLoading = false
                scene.timeToLoad = client.accuracy
            end
            client.timeInGame = ShroudTime
            client.timeDelta =  ShroudDeltaTime
            client.fps = ui_client_frame
            ui_client_frame = 0
            client.accuracy = ts - ui_client_ts - 1
            ui_client_ts = ts
            scene.timeInScene = ts - scene.timeStarted
        end
    
        -- check key up
        for ku,r in next, ui.shortcut.list.pressed do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
        -- check key help/repeat
        for ku,r in next, ui.shortcut.list.watch do
            if ShroudGetOnKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeDown = os.time()
                        f.callback("down", ku, f.keysHeld)
                    end
                end
    
            elseif ShroudGetKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        if os.time() - f.timeDown >= 0.25 then
                            f.callback("held", ku, f.keysHeld)
                        end
                    end
                end
    
            elseif ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeUp = os.time()
                        f.callback("up", ku, f.keysHeld)
                        if os.time() - f.timeDown <= 0.25 then
                            f.callback("pressed", ku, f.keysHeld)
                        end
                    end
                end
            end
        end
        
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
        local ts = os.time()
        local isHitching = ts - ui_client_ts >= 2
        local isLoading = ts - ui_client_ts >= 5
    
        if isHitching and client.isHitching ~= isHitching then
            client.isHitching = isHitching
            ui.handler.invoke("_clientIsHitching")
        end
        if isLoading and client.isLoading ~= isLoading then
            client.isLoading = isLoading
            ui.handler.invoke("_clientIsLoading")
        end
            
        if not isHitching or isLoading then
            for _,l in next, ui.label.list do
                if l.visible then
                    if not isLoading and l.shownInScene then
                        ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                    elseif isLoading and l.shownInLoadScreen then
                        ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                    end
                end
                if os.time() - ts > 0.01 then break end
            end
        end
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
        
        ui.timer.add(0, true, function(...)
    
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and message:find("PvP") then ui_getPlayerName() end
    
            -- parse message
            local src, dst, msg = message:match("^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if msg:byte() == 92 or msg:byte() == 95 or msg:byte() == 33 then
                local cmd, tail = msg:match("^[\\_!](%w+)%s*(.*)$")
                if ui.command.list[cmd] then
                    if not ui.command.list[cmd].sender or ui.command.list[cmd].sender == sender
                    and not ui.command.list[cmd].channel or ui.command.list[cmd].channel == channel
                    and not ui.command.list[cmd].receiver or ui.command.list[cmd].receiver == dst
                    then
                        local info = {
                            channel = channel,
                            sender = sender,
                            receiver = dst,
                        }
                        local arg = {}
                        for a in tail:gmatch("%S+") do arg[#arg + 1] = a end
                        arg.n = #arg
                        ui.command.list[cmd].callback(info, unpack(arg))
                    end
                else
                    ui.handler.invoke("_consoleCommand", cmd, sender, dst, tail)
                end
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
        end, channel, sender, message)
    end
    
    
    Code:
    -- libsota.0.4.util-dev by Catweazle Waldschrath
    -- helper functions for global namespace to work with the ui.objects
    -- depends on libsota.0.4.x
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.add(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.add(interval, false, callback)
    end
    
    function getTimer(index)
        return ui.timer.list[index]
    end
    
    function cancelTimer(index)
        ui.timer.list[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer.list[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer.list[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        return ui.label.add(r.left, r.top, r.width, r.height, caption)
    end
    
    function getLabel(index)
        return ui.label.list[index]
    end
    
    function getLabelCaption(index)
        return ui.label.list[index].caption
    end
    getLabelText = getLabelCaption
    
    function setLabelCaption(index, caption)
        ui.label.list[index].caption = caption
    end
    setLabelText = setLabelCaption
    
    function removeLabel(index)
        ui.label.list[index] = nil
    end
    
    function showLabel(index)
        ui.label.list[index].visible = true
    end
    
    function hideLabel(index)
        ui.label.list[index].visible = false
    end
    
    function toggleLabel(index)
        ui.label.list[index].visible = not ui.label.list[index].visible
    end
    
    function isLabelVisible(index)
        return ui.label.list[index].visible
    end
    
    function setLabelVisible(index, visible)
        ui.label.list[index].visible = visible
    end
    
    function moveLabelBy(index, x, y)
        ui.label.list[index].left = ui.label.list[index].left + x
        ui.label.list[index].top = ui.label.list[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label.list[index].left = x
        ui.label.list[index].top = y
    end
    
    function resizeLabelBy(index, w, h)
        ui.label.list[index].width = ui.label.list[index].width + w
        ui.label.list[index].height = ui.label.list[index].height + h
    end
    
    function resizeLabelTo(index, w, h)
        ui.label.list[index].width = w
        ui.label.list[index].height = h
    end
    
    
    -- implement Shroud calls
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart()
        ui.onInit(function()
            ui.command.add("lua", function(source, action)
                if action == "api" then
                    for f,t in next, client.api.list do
                        ui.consoleLog(t..": "..f)
                    end
                elseif action == "lua" or action == "path" or action == "version" then
                    ui.consoleLog("LUA Version: "..client.api.luaVersion)
                    ui.consoleLog("LUA Path: "..ShroudLuaPath)
                elseif action == "reload" or action == "unload" then
                    ui.consoleLog("type: /lua "..action.." in the chat window instead")
                end
            end)
            ui.command.add("info", function(source, action, param)
                if action == "xp" then
                    local xp = player.xp()
                    ui.consoleLog("Adventurer pooled XP: "..xp.adventurer.."\nProducer pooled XP: "..xp.producer)
                elseif action == "stat" and param then
                    local stat = player.stat(param)
                    ui.consoleLog(string.format("Stat %d: %s = %s (%s)", stat.number, stat.name, stat.value, stat.description))
                elseif action == "client" or action == "player" or action == "scene" then
                    for n,v in next, _G[action] do
                        if n:byte() ~= 95 then
                            if type(v) == "table" then
                                for n1,v1 in next, v do
                                    ui.consoleLog(n.."."..n1.." = "..tostring(v1))
                                end
                            else
                                    ui.consoleLog(n.." = "..tostring(v))
                            end
                        end
                    end
                elseif action == "lib" then
                    ui.consoleLog(string.format("timer: %d\nhandler: %d\nlabel: %d\n", #ui.timer.list, #ui.handler.list, #ui.label.list))
                    for n in next, ui.command.list do
                        ui.consoleLog("command: "..n)
                    end
                end
            end)
        end)
    end
    
    
    function ui.onShortcutPressed(...)
        return ui.shortcut.add("pressed", ...)
    end
    function ui.onShortcut(...)
        return ui.shortcut.add("watch", ...)
    end
    ui.registerKey = ui.onShortcutPress -- depricated: ui.registerKey is about to be removed]]
    
    
    
    string.style = function(string, style)
        if style.bold then string = "<b>"..string.."</b>" end
        if style.italic then string = "<i>"..string.."</i>" end
        if style.size and style.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
        if style.color and #style.color > 3 then string = "<color="..style.color..">"..string.."</color>" end
        return string
    end
    
    
    rect = {
        _new = function(self, left, top, width, height)
            return self.new(left, top, width, height)
        end,
        new = function(left, top, width, height)
            local r = {
                left = left,
                top = top,
                width = width,
                height = height,
            }
    
            if not width then
                r.width = client.screen.width / 3.3
            elseif tonumber(width) == nil then
                width = tonumber(string.match(width, "^%d+"))
                r.width = client.screen.width / 100 * math.abs(width)
            end
            if not height then
                r.height = client.screen.height / 3.6
            elseif tonumber(height) == nil then
                height = tonumber(string.match(height, "^%d+"))
                r.height = client.screen.height / 100 * math.abs(height)
            end
            if not left then
                r.left = (client.screen.width - r.width) / 2
            elseif tonumber(left) == nil then
                left = tonumber(string.match(left, "^%d+"))
                if left < 0 then
                    r.left = client.screen.width - (client.screen.width / 100 * -left) - r.width
                else
                    r.left = client.screen.width / 100 * left
                end
            end
            if not top then
                r.top = (client.screen.height - r.height) / 2
            elseif tonumber(top) == nil then
                top = tonumber(string.match(top, "^%d+"))
                if top < 0 then
                    r.top = client.screen.height - (client.screen.height / 100 * -top) - r.height
                else
                    r.top = client.screen.height / 100 * top
                end
            end
    
            return r
        end,
    
        fromString = function(string)
            local str = string:gsub("<[^>]*>", "")
            local s = string:match("<size=(%d-)")
            if not s then s = 14 end
            return { left = 0, top = 0, width = str:len() * (s/2), height = s*2 }
        end,
       
        moveTo = function(rect, x, y)
            if not x then x = (client.screen.width - rect.width) / 2 end
            if not y then y = (client.screen.height - rect.height) / 2 end
            rect.left = x
            rect.top = y
            return rect
        end,
       
        moveBy = function(rect, x, y)
            rect.left = rect.left + x
            rect.top = rect.top + y
            return rect
        end
    }
    setmetatable(rect, {__call = rect._new})
    
    

    Improved the init squence. A /lua reload should not be needed anymore

    Changed the callback for command to callback(source, ...). source is a table containing channel, sender and receiver. The second argument is now the first argument for your command. (old was: callback(sender, receiver, ...)). The source argument can be used to check who is allowed to use the command.

    command.restrict(channel, sender, receiver) - this allows to restrict when the command is issued. Default: sender = player.name, channel = nil, receiver = nil. The default means the callback is only invoked when the player has typed the command regardless of channel or receiver. Nil or false means do not check. The restriction is only used for regsitered commands. Not regsitered command will incoke ui.onConsoleCommand without checking.

    Added _ and ! as command prefix. Commands can know start with \, _, or !

    Know using ShroudPeriodic for timing. That is less fps dependend.
    Improved key handling somewhat

    string client.api.luaversion - tells used lua version
    table client.api.list - a table with available functions and globals from shroud api. That can be used to check if a function is already available in the client version the player is using.

    libsota.util
    added command:
    \lua lua - shows lua version and path
    \lua api - shows list of available functions and globals from shroud api
    \info xp -- shows players pooled xp
    \info stat <number or name> -- shows stat info and value
    \info client -- shows client info and variable names
    \info player -- shows player info and variable names
    \info scene -- shows scene info and variable names
    \info lib -- shows other info from libsota. incl. list of registered / added commands
     
    Browncoat Jayson likes this.
  18. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.4.2 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeToLoad = 0,
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        isHitching = false,
        isLoading = false,
        _statEnum = {},
        _statDescr = {},
        api = {
            luaVersion = "",
            list = {},
        },
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        isAfk = nil,
        isMoving = false,
        isStill = false,
        lastMoved = os.time(),
        location = {},
        health = {},
        focus = {},
        xp = function() return { producer = ShroudGetPooledProducerExperience(), adventurer = ShroudGetPooledAdventurerExperience() } end,
        stat = function(index)
            local ret = {
                number = -1,
                name = "invalid",
                value = -999,
                description = "",
            }
            if tonumber(index) == nil then
                index = client._statEnum[index]
            else
                index = tonumber(index)
            end
            if index and index <= #client._statEnum then
                ret.number = index
                ret.name = client._statEnum[index]
                ret.value = ShroudGetStatValueByNumber(index)
                ret.description = client._statDescr[index]
            end
            return ret
        end,
    }
    ui = {
        timer = {
            list = {},
            add = function(timeout, once, callback, ...)
                local index = #ui.timer.list + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer.list[index] = {
                    _index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    callback = callback,
                    userdata = ...,
                }
                return index
            end,
            get = function(index)
                return ui.timer.list[index]
            end,
            remove = function(index)
                ui.timer.list[index] = nil
            end,
            enabled = function(index, enabled)
                if enabled ~= nil then
                    ui.timer.list[index].enabled = enabled
                end
                return ui.timer.list[index].enabled
            end,
            pause = function(index)
                ui.timer.list[index].enabled = false
            end,
            resume = function(index)
                ui.timer.list[index].enabled = true
            end,
            toggle = function(index)
                ui.timer.list[index].enabled = not ui.timer.list[index].enabled
            end,
        },
        setTimeout = function(timeout, callback) return ui.timer.add(timeout, true, callback) end,
        setInterval = function(interval, callback) return ui.timer.add(interval, false, callback) end,
    
     
        handler = {
            list = {},
            add = function(name, callback)
                local index = #ui.handler.list + 1
    
                ui.handler.list[index] = {
                    _index = index,
                    name = name,
                    callback = callback,
                }
                return index
            end,
            remove = function(index)
                ui.handler.list[index] = nil
            end,
            invoke = function(name, ...)
                if not ui_initialized then return end
                for _,h in next, ui.handler.list do
                    if h.name == name then
                        --print(os.date("%X").." : invoke "..h.name)
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.callback(...)
                    end
                end
            end,
        },
        onInit = function(callback) return ui.handler.add("_init", callback) end,
        onStart = function(callback) return ui.handler.add("_start", callback) end,
        onUpdate = function(callback) return ui.handler.add("_update", callback) end,
        onConsoleInput = function(callback) return ui.handler.add("_consoleInput", callback) end,
        onConsoleCommand = function(callback) return ui.handler.add("_consoleCommand", callback) end,
        onSceneChanged = function(callback) return ui.handler.add("_sceneChanged", callback) end,
        onPlayerChanged = function(callback) return ui.handler.add("_playerChanged", callback) end,
        onPlayerMoveStart = function(callback) return ui.handler.add("_playerMoveStart", callback) end,
        onPlayerMoveStop = function(callback) return ui.handler.add("_playerMoveStop", callback) end,
        onPlayerIsStill = function(callback) return ui.handler.add("_playerIsStill", callback) end,
        onPlayerDamage = function(callback) return ui.handler.add("_playerDamage", callback) end,
        onClientIsHitching = function(callback) return ui.handler.add("_clientIsHitching", callback) end,
        onClientIsLoading = function(callback) return ui.handler.add("_clientIsLoading", callback) end,
    
    
        label = {
            list = {},
            add = function(left, top, width, height, caption)
                local index = #ui.label.list + 1
    
                ui.label.list[index] = {
                    _index = index,
                    left = left,
                    top = top,
                    width = width,
                    height = height,
                    caption = caption,
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                }
                setmetatable(ui.label.list[index], {__index = ui.label})
                return index
            end,
            get = function(index)
                return ui.label.list[index]
            end,
            remove = function(index)
                ui.label.list[index] = nil
            end,
            rect = function(index, rect)
                local l = ui.label.list[index]
                l.left = rect.left
                l.top = rect.top
                l.width = rect.width
                l.height = rect.height
            end,
            caption = function(index, caption)
                if caption then
                    ui.label.list[index].caption = caption
                end
                return ui.label.list[index].caption
            end,
            visible = function(index, visible)
                if visible ~= nil then
                    ui.label.list[index].visible = visible
                end
                return ui.label.list[index].visible
            end,
            toggle = function(index)
                ui.label.list[index].visible = not ui.label.list[index].visible
            end,
            moveTo = function(index, x, y)
                ui.label.list[index].left = x
                ui.label.list[index].top = y
            end,
            moveBy = function(index, x, y)
                ui.label.list[index].left = ui.label.list[index].left + x
                ui.label.list[index].top = ui.label.list[index].top + y
            end,
            resizeTo = function(index, w, h)
                ui.label.list[index].width = w
                ui.label.list[index].height = h
            end,
            resizeBy = function(index, x, y)
                ui.label.list[index].width = ui.label.list[index].width + x
                ui.label.list[index].height = ui.label.list[index].height + y
            end,
        },
     
        shortcut = {
            list = { pressed = {}, watch = {} },
            add = function(action, ...)
                local keys = {...}
                local callback = keys[#keys]; keys[#keys] = nil
                local key = keys[#keys]; keys[#keys] = nil
                if not ui.shortcut.list[action][tostring(key)] then
                    ui.shortcut.list[action][tostring(key)] = {}
                end
                if type(callback) == "string" then
                    callback = ui.command.list[callback].callback
                end
                local id = #ui.shortcut.list[action][key] + 1
                local index = { action = action, key = key, id = id }
                ui.shortcut.list[action][key][id] = {
                    _index = index,
                    key = key,
                    keysHeld = keys,
                    callback = callback,
                }
                if type(callback) == string then
                    if ui.command.list[callback].shortcut then
                        ui.shortcut.remove(ui.command.list[callback].shortcut)
                    end
                    ui.command.list[callback].shortcut = index
                end
                return index
            end,
            remove = function(index)
                ui.shortcut.list[index.action][index.key][index.id] = nil
                if #ui.shortcut.list[index.action][index.key] == 0 then ui.shortcut.list[index.action][index.key] = nil end
            end,
            invoke = function(index)
                ui.shortcut.list[index.action][index.key][index.id].callback()
            end,
        },
     
        command = {
            list = {},
            add = function(command, callback)
                ui.command.list[tostring(command)] = {
                    _command = command,
                    callback = callback,
                    shortcut = nil,
                    channel = nil,
                    sender = player.name,
                    receiver = nil,
                }
                return command
            end,
            remove = function(command)
                if ui.command.list[command].shortcut then
                    ui.shortcut.remove(ui.command.list[command].shortcut)
                end
                ui.command.list[command] = nil
            end,
            invoke = function(command, ...)
                if ... then
                    ui.command.list[command].callback(nil, unpack(...))
                else
                    ui.command.list[command].callback()
                end
            end,
            restrict = function(command, channel, sender, receiver)
                if ui.command[command] then
                    ui.command[command].channel = channel
                    ui.command[command].sender = sender
                    ui.command[command].receiver = receiver
                end
            end
        },
     
        verbosity = 0,
        consoleLog = function(message, verbosity)
            if not verbosity then verbosity = 0 end
            if ui.verbosity >= verbosity then
                for l in message:gmatch("[^\n]+") do
                    ShroudConsoleLog(l)
                end
            end
        end,
    }
    setmetatable(ui.timer, {__index = ui.timer.list})
    setmetatable(ui.label, {__index = ui.label.list})
    
    
    
    -- internal
    ui_initialized = false
    ui_client_ts = os.time()
    ui_client_frame = 0
    
    local function ui_getPlayerName()
        if player.caption ~= ShroudGetPlayerName() then
            player.caption = ShroudGetPlayerName()
            player.name = player.caption
            player.flag = ""
            player.isPvp = false
            if player.name:byte(#player.name) == 93 then
                player.name, player.flag = player.name:match("^(.-)(%[.-%])$")
                player.isPvp = player.flag and player.flag:find("PVP") ~= nil
                player.isAfk = player.flag and player.flag:find("AFK") ~= nil
            end
            ui.handler.invoke("_playerChanged", "caption")
        end
    end
    local function ui_getPlayerChange()
        local loc = { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene }
        local isMoving = loc.x ~= player.location.x or loc.y ~= player.location.y or loc.z ~= player.location.z
        if isMoving then player.lastMoved = os.time() end
        local isStill = os.time() - player.lastMoved > 5
        local invoke = isStill ~= player.isStill or isMoving ~= player.isMoving
        player.isMoving = isMoving
        player.isStill = isStill
        player.location = loc
        if invoke then
            if isMoving then
                ui.handler.invoke("_playerMoveStart")
                ui.handler.invoke("_playerChanged", "moving")
            elseif isStill then
                ui.handler.invoke("_playerIsStill")
                ui.handler.invoke("_playerChanged", "stillness")
            else
                ui.handler.invoke("_playerMoveStop")
                ui.handler.invoke("_playerChanged", "standing")
            end
        end
        local health = { max = ShroudGetStatValueByNumber(30), current = ShroudPlayerCurrentHealth, percentage = 0 }
        local focus =  { max = ShroudGetStatValueByNumber(27), current = ShroudPlayerCurrentFocus, percentage = 0 }
        if not health.current or not health.max or not focus.current or not focus.max then
            print(health.current, health.max, focus.current, focus.max) -- stats sind noch nicht da....
        end
        health.percentage = health.max / health.current
        focus.percentage = focus.max / focus.current
        invoke = player.health.current ~= health.current or player.focus.current ~= focus.current
        if invoke then
            ui.handler.invoke("_playerDamage", health, focus)
            ui.handler.invoke("_playerChanged", "damage", health, focus)
        end
        player.health = health
        player.focus = focus
    end
    local function ui_getSceneName()
        if scene.name ~= ShroudGetCurrentSceneName() then
            scene.name = ShroudGetCurrentSceneName()
            scene.maxPlayer = ShroudGetCurrentSceneMaxPlayerCount()
            scene.isPvp = ShroudGetCurrentSceneIsPVP()
            scene.isPot = ShroudGetCurrentSceneIsPOT()
            scene.timeStarted = os.time()
            ui.handler.invoke("_sceneChanged")
        end
    end
    local function ui_initialize()
        client.timeToLoad = ui_client_ts
        client.screen = {
            width = ShroudGetScreenX(),
            height = ShroudGetScreenY(),
            isFullScreen = ShroudGetFullScreen(),
        }
        for i=0, ShroudGetStatCount()-1, 1 do
            local name = ShroudGetStatNameByNumber(i)
            client._statEnum[tostring(name)] = i
            client._statEnum[i] = name
            client._statDescr[i] = ShroudGetStatDescriptionByNumber(i)
        end
        ui_getSceneName()
        ui_getPlayerName()
        ui_getPlayerChange()
    
        ShroudRegisterPeriodic("ui_timer_internal", "ui_timer_internal", 0.240, true)
        ShroudRegisterPeriodic("ui_timer_user", "ui_timer_user", 0.120, true)
    end
    
    
    
    -- shroud hooks
    
    
    -- shroud timers
    
    function ui_timer_internal()
        ui_getPlayerChange()
    end
    function ui_timer_user()
        local ts = os.time()
     
        for _,t in next, ui.timer.list do
            if t.enabled and ts >= t.time then
                if t.interval then
                    t.time = ts + t.interval
                else
                    ui.timer.list[t._index] = nil
                end
                if t.callback(t.userdata) then ui.timer.list[t._index] = nil end
            end
        end
    end
    
    
    -- shroud callbacks
    
    function ShroudOnStart()
        ShroudUseLuaConsoleForPrint(true)
        ShroudConsoleLog(_G._MOONSHARP.banner)
        ShroudConsoleLog("LUA Version: ".._G._MOONSHARP.luacompat)
    
        client.api.luaVersion = _G._MOONSHARP.luacompat
        for i,v in next, _G do
            if i:find("^Shroud") then client.api.list[tostring(i)] = type(v) end
        end
     
        ui.handler.invoke("_start")
    end
    
    function ShroudOnUpdate()
        local ts = os.time()
    
        -- init
        if not ui_initialized then
            if not ShroudServerTime then return end -- shroud Api not ready, yet
            ShroudConsoleLog("Shroud Api Ready. ServerTime: "..ShroudServerTime)
    
            ui_initialize()
            ui_initialized = true
    
            ui.handler.invoke("_init")
        end
    
        -- client stats
        ui_client_frame = ui_client_frame + 1;
        if ts - ui_client_ts >= 1 then
            if client.accuracy > 10 then
                ui_getSceneName()
                ui_getPlayerName()
                client.isLoading = false
                scene.timeToLoad = client.accuracy
            end
            client.timeInGame = ShroudTime
            client.timeDelta =  ShroudDeltaTime
            client.fps = ui_client_frame
            ui_client_frame = 0
            client.accuracy = ts - ui_client_ts - 1
            ui_client_ts = ts
            scene.timeInScene = ts - scene.timeStarted
        end
    
        -- check key up
        for ku,r in next, ui.shortcut.list.pressed do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
        -- check key help/repeat
        for ku,r in next, ui.shortcut.list.watch do
            if ShroudGetOnKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeDown = os.time()
                        f.callback("down", ku, f.keysHeld)
                    end
                end
    
            elseif ShroudGetKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        if os.time() - f.timeDown >= 0.25 then
                            f.callback("held", ku, f.keysHeld)
                        end
                    end
                end
    
            elseif ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeUp = os.time()
                        f.callback("up", ku, f.keysHeld)
                        if os.time() - f.timeDown <= 0.25 then
                            f.callback("pressed", ku, f.keysHeld)
                        end
                    end
                end
            end
        end
     
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
        local ts = os.time()
        local isHitching = ts - ui_client_ts >= 2
        local isLoading = ts - ui_client_ts >= 5
    
        if isHitching and client.isHitching ~= isHitching then
            client.isHitching = isHitching
            ui.handler.invoke("_clientIsHitching")
        end
        if isLoading and client.isLoading ~= isLoading then
            client.isLoading = isLoading
            ui.handler.invoke("_clientIsLoading")
        end
     
        if not isHitching or isLoading then
            for _,l in next, ui.label.list do
                if l.visible then
                    if not isLoading and l.shownInScene then
                        ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                    elseif isLoading and l.shownInLoadScreen then
                        ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                    end
                end
                if os.time() - ts > 0.01 then break end
            end
        end
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
     
        ui.timer.add(0, true, function(...)
    
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and message:find("PvP") then ui_getPlayerName() end
    
            -- parse message
            local src, dst, msg = message:match("^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if msg:byte() == 92 or msg:byte() == 95 or msg:byte() == 33 then
                local cmd, tail = msg:match("^[\\_!](%w+)%s*(.*)$")
                if ui.command.list[cmd] then
                    if not ui.command.list[cmd].sender or ui.command.list[cmd].sender == sender
                    and not ui.command.list[cmd].channel or ui.command.list[cmd].channel == channel
                    and not ui.command.list[cmd].receiver or ui.command.list[cmd].receiver == dst
                    then
                        local info = {
                            channel = channel,
                            sender = sender,
                            receiver = dst,
                        }
                        local arg = {}
                        for a in tail:gmatch("%S+") do arg[#arg + 1] = a end
                        arg.n = #arg
                        ui.command.list[cmd].callback(info, unpack(arg))
                    end
                else
                    ui.handler.invoke("_consoleCommand", cmd, sender, dst, tail)
                end
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
        end, channel, sender, message)
    end
    
    
    Code:
    -- libsota.0.4.util-dev by Catweazle Waldschrath
    -- helper functions for global namespace to work with the ui.objects
    -- depends on libsota.0.4.x
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.add(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.add(interval, false, callback)
    end
    
    function getTimer(index)
        return ui.timer.list[index]
    end
    
    function cancelTimer(index)
        ui.timer.list[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer.list[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer.list[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        return ui.label.add(r.left, r.top, r.width, r.height, caption)
    end
    
    function getLabel(index)
        return ui.label.list[index]
    end
    
    function getLabelCaption(index)
        return ui.label.list[index].caption
    end
    getLabelText = getLabelCaption
    
    function setLabelCaption(index, caption)
        ui.label.list[index].caption = caption
    end
    setLabelText = setLabelCaption
    
    function removeLabel(index)
        ui.label.list[index] = nil
    end
    
    function showLabel(index)
        ui.label.list[index].visible = true
    end
    
    function hideLabel(index)
        ui.label.list[index].visible = false
    end
    
    function toggleLabel(index)
        ui.label.list[index].visible = not ui.label.list[index].visible
    end
    
    function isLabelVisible(index)
        return ui.label.list[index].visible
    end
    
    function setLabelVisible(index, visible)
        ui.label.list[index].visible = visible
    end
    
    function moveLabelBy(index, x, y)
        ui.label.list[index].left = ui.label.list[index].left + x
        ui.label.list[index].top = ui.label.list[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label.list[index].left = x
        ui.label.list[index].top = y
    end
    
    function resizeLabelBy(index, w, h)
        ui.label.list[index].width = ui.label.list[index].width + w
        ui.label.list[index].height = ui.label.list[index].height + h
    end
    
    function resizeLabelTo(index, w, h)
        ui.label.list[index].width = w
        ui.label.list[index].height = h
    end
    
    
    -- implement Shroud calls
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart()
        ui.onInit(function()
            ui.command.add("lua", function(source, action)
                if action == "api" then
                    for f,t in next, client.api.list do
                        ui.consoleLog(t..": "..f)
                    end
                elseif action == "lua" or action == "path" or action == "version" then
                    ui.consoleLog("LUA Version: "..client.api.luaVersion)
                    ui.consoleLog("LUA Path: "..ShroudLuaPath)
                elseif action == "reload" or action == "unload" then
                    ui.consoleLog("type: /lua "..action.." in the chat window instead")
                end
            end)
            ui.command.add("info", function(source, action, param)
                if action == "xp" then
                    local xp = player.xp()
                    ui.consoleLog("Adventurer pooled XP: "..xp.adventurer.."\nProducer pooled XP: "..xp.producer)
                elseif action == "stat" and param then
                    local stat = player.stat(param)
                    ui.consoleLog(string.format("Stat %d: %s = %s (%s)", stat.number, stat.name, stat.value, stat.description))
                elseif action == "client" or action == "player" or action == "scene" or action == "ui" then
                    for n,v in next, _G[action] do
                        if n:byte() ~= 95 then
                            if type(v) == "table" then
                                for n1,v1 in next, v do
                                    ui.consoleLog(action.."."..n.."."..n1.." = "..tostring(v1))
                                end
                            else
                                    ui.consoleLog(action.."."..n.." = "..tostring(v))
                            end
                        end
                    end
                elseif action == "lib" then
                    ui.consoleLog(string.format("timer: %d\nhandler: %d\nlabel: %d\n", #ui.timer.list, #ui.handler.list, #ui.label.list))
                    for n in next, ui.command.list do
                        ui.consoleLog("command: "..n)
                    end
                end
            end)
        end)
    end
    
    
    function ui.onShortcutPressed(...)
        return ui.shortcut.add("pressed", ...)
    end
    function ui.onShortcut(...)
        return ui.shortcut.add("watch", ...)
    end
    ui.registerKey = ui.onShortcutPress -- depricated: ui.registerKey is about to be removed]]
    
    
    
    string.style = function(string, style)
        if style.bold then string = "<b>"..string.."</b>" end
        if style.italic then string = "<i>"..string.."</i>" end
        if style.size and style.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
        if style.color and #style.color > 3 then string = "<color="..style.color..">"..string.."</color>" end
        return string
    end
    
    
    rect = {
        _new = function(self, left, top, width, height)
            return self.new(left, top, width, height)
        end,
        new = function(left, top, width, height)
            local r = {
                left = left,
                top = top,
                width = width,
                height = height,
            }
    
            if not width then
                r.width = client.screen.width / 3.3
            elseif tonumber(width) == nil then
                width = tonumber(string.match(width, "^%d+"))
                r.width = client.screen.width / 100 * math.abs(width)
            end
            if not height then
                r.height = client.screen.height / 3.6
            elseif tonumber(height) == nil then
                height = tonumber(string.match(height, "^%d+"))
                r.height = client.screen.height / 100 * math.abs(height)
            end
            if not left then
                r.left = (client.screen.width - r.width) / 2
            elseif tonumber(left) == nil then
                left = tonumber(string.match(left, "^%d+"))
                if left < 0 then
                    r.left = client.screen.width - (client.screen.width / 100 * -left) - r.width
                else
                    r.left = client.screen.width / 100 * left
                end
            end
            if not top then
                r.top = (client.screen.height - r.height) / 2
            elseif tonumber(top) == nil then
                top = tonumber(string.match(top, "^%d+"))
                if top < 0 then
                    r.top = client.screen.height - (client.screen.height / 100 * -top) - r.height
                else
                    r.top = client.screen.height / 100 * top
                end
            end
    
            return r
        end,
    
        fromString = function(string)
            local str = string:gsub("<[^>]*>", "")
            local s = string:match("<size=(%d-)")
            if not s then s = 14 end
            return { left = 0, top = 0, width = str:len() * (s/2), height = s*2 }
        end,
     
        moveTo = function(rect, x, y)
            if not x then x = (client.screen.width - rect.width) / 2 end
            if not y then y = (client.screen.height - rect.height) / 2 end
            rect.left = x
            rect.top = y
            return rect
        end,
     
        moveBy = function(rect, x, y)
            rect.left = rect.left + x
            rect.top = rect.top + y
            return rect
        end
    }
    setmetatable(rect, {__call = rect._new})
    
    

    Improved the init squence. A /lua reload should not be needed anymore

    Changed the callback for command to callback(source, ...). source is a table containing channel, sender and receiver. The second argument is now the first argument for your command. (old was: callback(sender, receiver, ...)). The source argument can be used to check who is allowed to use the command.

    ui.command.restrict(command, channel, sender, receiver) - this allows to restrict when the command is issued. Default: sender = player.name, channel = nil, receiver = nil. The default means the callback is only invoked when the player has typed the command regardless of channel or receiver. Nil or false means do not check. The restriction is only used for regsitered commands. Not regsitered command will incoke ui.onConsoleCommand without checking.

    Added _ and ! as command prefix. Commands can know start with \, _, or !
    Means it is now the players choice if the command is typed \command, _command or !command. In all three cases the regsitered command will be invoked.

    Know using ShroudPeriodic for timing. That is less fps dependend.
    Improved key handling somewhat

    string client.api.luaversion - tells used lua version
    table client.api.list - a table with available functions and globals from shroud api. That can be used to check if a function is already available in the client version the player is using.

    libsota.util
    added command:
    \lua lua - shows lua version and path
    \lua api - shows list of available functions and globals from shroud api
    \info xp -- shows players pooled xp
    \info stat <number or name> -- shows stat info and value
    \info client -- shows client info and variable names (client object from libsota)
    \info player -- shows player info and variable names (player object from libsota)
    \info scene -- shows scene info and variable names (scene object from libsota)
    \info ui -- shows the functions you can use (api) from libsota (ui object from libsota)
    \info lib -- shows other info from libsota. incl. list of registered / added commands
     
    Last edited: Nov 24, 2019
    Browncoat Jayson likes this.
  19. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    
    -- libsota.0.4.3 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeToLoad = 0,
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        isHitching = false,
        isLoading = false,
        _statEnum = {},
        _statDescr = {},
        api = {
            luaVersion = "",
            luaPath = "",
            list = {},
            isImplemented = function(name)
                return client.api.list[name] ~= nil
            end
        },
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        isAfk = false,
        isGod = false,
        isMoving = false,
        isStill = false,
        lastMoved = os.time(),
        location = {},
        health = {},
        focus = {},
        xp = function() return { producer = ShroudGetPooledProducerExperience(), adventurer = ShroudGetPooledAdventurerExperience() } end,
        stat = function(index)
            local ret = {
                number = -1,
                name = "invalid",
                value = -999,
                description = "",
            }
            if tonumber(index) == nil then
                index = client._statEnum[index]
            else
                index = tonumber(index)
            end
            if index and index <= #client._statEnum then
                ret.number = index
                ret.name = client._statEnum[index]
                ret.value = ShroudGetStatValueByNumber(index)
                ret.description = client._statDescr[index]
            end
            return ret
        end,
    }
    ui = {
        version = "0.4.3",
    
        timer = {
            list = {},
            add = function(timeout, once, callback, ...)
                local index = #ui.timer.list + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer.list[index] = {
                    _index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    callback = callback,
                    userdata = ...,
                }
                return index
            end,
            get = function(index)
                return ui.timer.list[index]
            end,
            remove = function(index)
                ui.timer.list[index] = nil
            end,
            enabled = function(index, enabled)
                if enabled ~= nil then
                    ui.timer.list[index].enabled = enabled
                end
                return ui.timer.list[index].enabled
            end,
            pause = function(index)
                ui.timer.list[index].enabled = false
            end,
            resume = function(index)
                ui.timer.list[index].enabled = true
            end,
            toggle = function(index)
                ui.timer.list[index].enabled = not ui.timer.list[index].enabled
            end,
        },
        setTimeout = function(timeout, callback) return ui.timer.add(timeout, true, callback) end,
        setInterval = function(interval, callback) return ui.timer.add(interval, false, callback) end,
    
       
        handler = {
            list = {},
            add = function(name, callback)
                local index = #ui.handler.list + 1
    
                ui.handler.list[index] = {
                    _index = index,
                    name = name,
                    callback = callback,
                }
                return index   
            end,
            remove = function(index)
                ui.handler.list[index] = nil
            end,
            invoke = function(name, ...)
                if not ui_initialized then return end
                for _,h in next, ui.handler.list do
                    if h.name == name then
                        --print(os.date("%X").." : invoke "..h.name)
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.callback(...)
                    end
                end
            end,
        },
        onInit = function(callback) return ui.handler.add("_init", callback) end,
        onStart = function(callback) return ui.handler.add("_start", callback) end,
        onUpdate = function(callback) return ui.handler.add("_update", callback) end,
        onConsoleInput = function(callback) return ui.handler.add("_consoleInput", callback) end,
        onConsoleCommand = function(callback) return ui.handler.add("_consoleCommand", callback) end,
        onSceneChanged = function(callback) return ui.handler.add("_sceneChanged", callback) end,
        onPlayerChanged = function(callback) return ui.handler.add("_playerChanged", callback) end,
        onPlayerMoveStart = function(callback) return ui.handler.add("_playerMoveStart", callback) end,
        onPlayerMoveStop = function(callback) return ui.handler.add("_playerMoveStop", callback) end,
        onPlayerIsStill = function(callback) return ui.handler.add("_playerIsStill", callback) end,
        onPlayerDamage = function(callback) return ui.handler.add("_playerDamage", callback) end,
        onClientIsHitching = function(callback) return ui.handler.add("_clientIsHitching", callback) end,
        onClientIsLoading = function(callback) return ui.handler.add("_clientIsLoading", callback) end,
    
    
        label = {
            list = {},
            add = function(left, top, width, height, caption)
                local index = #ui.label.list + 1
    
                ui.label.list[index] = {
                    _index = index,
                    left = left or 0,
                    top = top or 0,
                    width = width or 0,
                    height = height or 0,
                    caption = caption or "",
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                }
                --setmetatable(ui.label.list[index], {__index = ui.label})
                return index
            end,
            get = function(index)
                return ui.label.list[index]
            end,
            remove = function(index)
                ui.label.list[index] = nil
            end,
            rect = function(index, rect)
                local l = ui.label.list[index]
                l.left = rect.left
                l.top = rect.top
                l.width = rect.width
                l.height = rect.height
            end,
            caption = function(index, caption)
                if caption then
                    ui.label.list[index].caption = caption
                end
                return ui.label.list[index].caption
            end,
            visible = function(index, visible)
                if visible ~= nil then
                    ui.label.list[index].visible = visible
                end
                return ui.label.list[index].visible
            end,
            toggle = function(index)
                ui.label.list[index].visible = not ui.label.list[index].visible
            end,
            moveTo = function(index, x, y)
                ui.label.list[index].left = x
                ui.label.list[index].top = y
            end,
            moveBy = function(index, x, y)
                ui.label.list[index].left = ui.label.list[index].left + x
                ui.label.list[index].top = ui.label.list[index].top + y
            end,
            resizeTo = function(index, w, h)
                ui.label.list[index].width = w
                ui.label.list[index].height = h
            end,
            resizeBy = function(index, x, y)
                ui.label.list[index].width = ui.label.list[index].width + x
                ui.label.list[index].height = ui.label.list[index].height + y
            end,
        },
       
        shortcut = {
            list = { pressed = {}, watch = {} },
            add = function(action, ...)
                local keys = {...}
                local callback = keys[#keys]; keys[#keys] = nil
                local key = keys[#keys]; keys[#keys] = nil
                if not ui.shortcut.list[action][tostring(key)] then
                    ui.shortcut.list[action][tostring(key)] = {}
                end
                if type(callback) == "string" then
                    callback = ui.command.list[callback].callback
                end
                local id = #ui.shortcut.list[action][key] + 1
                local index = { action = action, key = key, id = id }
                ui.shortcut.list[action][key][id] = {
                    _index = index,
                    key = key,
                    keysHeld = keys,
                    callback = callback,
                }
                if type(callback) == string then
                    if ui.command.list[callback].shortcut then
                        ui.shortcut.remove(ui.command.list[callback].shortcut)
                    end
                    ui.command.list[callback].shortcut = index
                end
                return index
            end,
            remove = function(index)
                ui.shortcut.list[index.action][index.key][index.id] = nil
                if #ui.shortcut.list[index.action][index.key] == 0 then ui.shortcut.list[index.action][index.key] = nil end
            end,
            invoke = function(index)
                ui.shortcut.list[index.action][index.key][index.id].callback()
            end,
        },
       
        command = {
            list = {},
            add = function(command, callback)
                ui.command.list[tostring(command)] = {
                    _command = command,
                    callback = callback,
                    shortcut = nil,
                    channel = nil,
                    sender = player.name,
                    receiver = nil,
                }
                return command
            end,
            remove = function(command)
                if ui.command.list[command].shortcut then
                    ui.shortcut.remove(ui.command.list[command].shortcut)
                end
                ui.command.list[command] = nil
            end,
            invoke = function(command, ...)
                if ... then
                    ui.command.list[command].callback(nil, unpack(...))
                else
                    ui.command.list[command].callback()
                end
            end,
            restrict = function(command, channel, sender, receiver)
                if ui.command[command] then
                    ui.command[command].channel = channel
                    ui.command[command].sender = sender
                    ui.command[command].receiver = receiver
                end
            end
        },
       
        verbosity = 0,
        consoleLog = function(message, verbosity)
            if not verbosity then verbosity = 0 end
            if ui.verbosity >= verbosity then
                for l in message:gmatch("[^\n]+") do
                    ShroudConsoleLog(l)
                end
            end
        end,
    }
    --setmetatable(ui.timer, {__index = ui.timer.list})
    setmetatable(ui.label, {__index = ui.label.list})
    
    
    
    -- internal
    ui_initialized = false
    ui_client_ts = os.time()
    ui_client_frame = 0
    
    local function ui_getPlayerName()
        if player.caption ~= ShroudGetPlayerName() then
            player.caption = ShroudGetPlayerName()
            player.name = player.caption
            player.flag = ""
            player.isPvp = false
            if player.name:byte(#player.name) == 93 then
                player.name, player.flag = player.name:match("^(.-)(%[.-%])$")
                player.isPvp = player.flag and player.flag:find("PVP") ~= nil
                player.isAfk = player.flag and player.flag:find("AFK") ~= nil
                player.isGod = player.flag and player.flag:find("God") ~= nil
            end
            ui.handler.invoke("_playerChanged", "caption")
        end
    end
    local function ui_getPlayerChange()
        local loc = { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene }
        local isMoving = loc.x ~= player.location.x or loc.y ~= player.location.y or loc.z ~= player.location.z
        if isMoving then player.lastMoved = os.time() end
        local isStill = os.time() - player.lastMoved > 5
        local invoke = isStill ~= player.isStill or isMoving ~= player.isMoving
        player.isMoving = isMoving
        player.isStill = isStill
        player.location = loc
        if invoke then
            if isMoving then
                ui.handler.invoke("_playerMoveStart")
                ui.handler.invoke("_playerChanged", "moving")
            elseif isStill then
                ui.handler.invoke("_playerIsStill")
                ui.handler.invoke("_playerChanged", "stillness")
            else
                ui.handler.invoke("_playerMoveStop")
                ui.handler.invoke("_playerChanged", "standing")
            end
        end
        local health = { max = ShroudGetStatValueByNumber(30), current = ShroudPlayerCurrentHealth, percentage = 0 }
        local focus =  { max = ShroudGetStatValueByNumber(27), current = ShroudPlayerCurrentFocus, percentage = 0 }
        health.percentage = health.current / health.max
        focus.percentage = focus.current / focus.max
        invoke = player.health.current ~= health.current or player.focus.current ~= focus.current
        if invoke then
            ui.handler.invoke("_playerDamage", health, focus)
            ui.handler.invoke("_playerChanged", "damage", health, focus)
        end
        player.health = health
        player.focus = focus
    end
    local function ui_getSceneName()
        if scene.name ~= ShroudGetCurrentSceneName() then
            scene.name = ShroudGetCurrentSceneName()
            scene.maxPlayer = ShroudGetCurrentSceneMaxPlayerCount()
            scene.isPvp = ShroudGetCurrentSceneIsPVP()
            scene.isPot = ShroudGetCurrentSceneIsPOT()
            scene.timeStarted = os.time()
            ui.handler.invoke("_sceneChanged")
        end
    end
    local function ui_initialize()
    
        client.api.luaPath = ShroudLuaPath
        client.api.luaVersion = _G._MOONSHARP.luacompat
        for i,v in next, _G do
            if i:find("^Shroud") then client.api.list[tostring(i)] = type(v) end
        end
    
        client.timeToLoad = ui_client_ts - client.timeStarted
        client.screen = {
            width = ShroudGetScreenX(),
            height = ShroudGetScreenY(),
            isFullScreen = ShroudGetFullScreen(),
        }
        for i=0, ShroudGetStatCount()-1, 1 do
            local name = ShroudGetStatNameByNumber(i)
            client._statEnum[tostring(name)] = i
            client._statEnum[i] = name
            client._statDescr[i] = ShroudGetStatDescriptionByNumber(i)
        end
        ui_getSceneName()
        ui_getPlayerName()
        ui_getPlayerChange()
    
        ShroudRegisterPeriodic("ui_timer_internal", "ui_timer_internal", 0.240, true)
        ShroudRegisterPeriodic("ui_timer_user", "ui_timer_user", 0.120, true)
    end
    
    
    
    -- shroud hooks
    
    
    -- shroud timers
    
    function ui_timer_internal()
        ui_getPlayerChange()
    end
    function ui_timer_user()
        local ts = os.time()
       
        for _,t in next, ui.timer.list do
            if t.enabled and ts >= t.time then
                if t.interval then
                    t.time = ts + t.interval
                else
                    ui.timer.list[t._index] = nil
                end
                if t.callback(t.userdata) then ui.timer.list[t._index] = nil end
            end
        end
    end
    
    
    -- shroud callbacks
    
    function ShroudOnStart()
        ShroudUseLuaConsoleForPrint(true)
        ShroudConsoleLog(_G._MOONSHARP.banner)
        ShroudConsoleLog("LUA Version: ".._G._MOONSHARP.luacompat)
    end
    
    function ShroudOnUpdate()
        local ts = os.time()
    
        -- init
        if not ui_initialized then
            if not ShroudServerTime then return end -- shroud Api not ready, yet
            ShroudConsoleLog("Shroud Api Ready. ServerTime: "..ShroudServerTime)
    
            ui_initialize()
            ui_initialized = true
    
            ui.handler.invoke("_init")
            ui.handler.invoke("_start")
        end
    
        -- client stats
        ui_client_frame = ui_client_frame + 1;
        if ts - ui_client_ts >= 1 then
            if client.accuracy > 10 then
                ui_getSceneName()
                ui_getPlayerName()
                client.isLoading = false
                scene.timeToLoad = client.accuracy
            end
            client.timeInGame = ShroudTime
            client.timeDelta =  ShroudDeltaTime
            client.fps = ui_client_frame
            ui_client_frame = 0
            client.accuracy = ts - ui_client_ts - 1
            ui_client_ts = ts
            scene.timeInScene = ts - scene.timeStarted
        end
    
        -- check key up
        for ku,r in next, ui.shortcut.list.pressed do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
        -- check key held/repeat
        for ku,r in next, ui.shortcut.list.watch do
            if ShroudGetOnKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeDown = os.time()
                        f.callback("down", ku, f.keysHeld)
                    end
                end
    
            elseif ShroudGetKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        if os.time() - f.timeDown >= 0.25 then
                            f.callback("held", ku, f.keysHeld)
                        end
                    end
                end
    
            elseif ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeUp = os.time()
                        f.callback("up", ku, f.keysHeld)
                        if os.time() - f.timeDown <= 0.25 then
                            f.callback("pressed", ku, f.keysHeld)
                        end
                    end
                end
            end
        end
       
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
        local ts = os.time()
        local isHitching = ts - ui_client_ts >= 2
        local isLoading = ts - ui_client_ts >= 5
    
        if isHitching and client.isHitching ~= isHitching then
            client.isHitching = isHitching
            ui.handler.invoke("_clientIsHitching")
        end
        if isLoading and client.isLoading ~= isLoading then
            client.isLoading = isLoading
            ui.handler.invoke("_clientIsLoading")
        end
           
        if not isHitching or isLoading then
            for _,l in next, ui.label.list do
                if l.visible then
                    if not isLoading and l.shownInScene then
                        ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                    elseif isLoading and l.shownInLoadScreen then
                        ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                    end
                end
                if os.time() - ts > 0.01 then break end
            end
        end
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
       
        ui.timer.add(0, true, function(...)
    
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and message:find("PvP") then ui_getPlayerName() end
    
            -- parse message
            local src, dst, msg = message:match("^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if msg:byte() == 92 or msg:byte() == 95 or msg:byte() == 33 then
                local cmd, tail = msg:match("^[\\_!](%w+)%s*(.*)$")
                local source = {
                    channel = channel,
                    sender = sender,
                    receiver = dst,
                }
                if ui.command.list[cmd] then
                    if not ui.command.list[cmd].sender or ui.command.list[cmd].sender == sender
                    and not ui.command.list[cmd].channel or ui.command.list[cmd].channel == channel
                    and not ui.command.list[cmd].receiver or ui.command.list[cmd].receiver == dst
                    then
                        local arg = {}
                        for a in tail:gmatch("%S+") do arg[#arg + 1] = a end
                        arg.n = #arg
                        ui.command.list[cmd].callback(source, unpack(arg))
                    end
                else
                    ui.handler.invoke("_consoleCommand", source, cmd, tail)
                end
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
        end, channel, sender, message)
    end
    
    
    Code:
    
    -- libsota.0.4.util-dev by Catweazle Waldschrath
    -- helper functions for global namespace to work with the ui.objects
    -- depends on libsota.0.4.x
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.add(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.add(interval, false, callback)
    end
    
    function getTimer(index)
        return ui.timer.list[index]
    end
    
    function cancelTimer(index)
        ui.timer.list[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer.list[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer.list[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        return ui.label.add(r.left, r.top, r.width, r.height, caption)
    end
    
    function createLabelWithShadow(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        local s = ui.label.add(r.left+1, r.top+1, r.width, r.height, caption)
        local l = ui.label.add(r.left, r.top, r.width, r.height, caption)
        ui.label.list[l].shadow = s
    end
    
    function getLabel(index)
        return ui.label.list[index]
    end
    
    function getLabelCaption(index)
        return ui.label.list[index].caption
    end
    getLabelText = getLabelCaption
    
    function setLabelCaption(index, caption)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].caption = "<color=black>"..caption.."</color>"
        end
        ui.label.list[index].caption = caption
    end
    setLabelText = setLabelCaption
    
    function removeLabel(index)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow] = nil
        end
        ui.label.list[index] = nil
    end
    
    function showLabel(index)
        setLabelVisible(index, true)
    end
    
    function hideLabel(index)
        setLabelVisible(index, false)
    end
    
    function toggleLabel(index)
        setLabelVisible(index, not ui.label.list[index].visible)
    end
    
    function isLabelVisible(index)
        return ui.label.list[index].visible
    end
    
    function setLabelVisible(index, visible)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].visible = visible
        end
        ui.label.list[index].visible = visible
    end
    
    function moveLabelBy(index, x, y)
        moveLabelTo(index, ui.label.list[index].left + x, ui.label.list[index].top + y)
    end
    
    function moveLabelTo(index, x, y)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].left = x + 1
            ui.label.list[ui.label.list[index].shadow].top = y + 1
        end
        ui.label.list[index].left = x
        ui.label.list[index].top = y
    end
    
    function resizeLabelBy(index, w, h)
        resizeLabelTo(index, ui.label.list[index].width + w, ui.label.list[index].height + h)
    end
    
    function resizeLabelTo(index, w, h)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].width = width
            ui.label.list[ui.label.list[index].shadow].height = height
        end
        ui.label.list[index].width = w
        ui.label.list[index].height = h
    end
    
    
    
    
    -- implement Shroud calls
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart()
        ui.onInit(function()
            ui.command.add("lua", function(source, action)
                if action == "api" then
                    for f,t in next, client.api.list do
                        ui.consoleLog(t..": "..f)
                    end
                elseif action == "lua" or action == "path" or action == "version" then
                    ui.consoleLog("LUA Version: "..client.api.luaVersion)
                    ui.consoleLog("LUA Path: "..client.api.luaPath)
                elseif action == "reload" or action == "unload" then
                    ui.consoleLog("type: /lua "..action.." in the chat window instead")
                end
            end)
            ui.command.add("info", function(source, action, param)
                if action == "xp" then
                    local xp = player.xp()
                    ui.consoleLog("Adventurer pooled XP: "..xp.adventurer.."\nProducer pooled XP: "..xp.producer)
                elseif action == "stat" and param then
                    local stat = player.stat(param)
                    ui.consoleLog(string.format("Stat %d: %s = %s (%s)", stat.number, stat.name, stat.value, stat.description))
                elseif action == "client" or action == "player" or action == "scene" or action == "ui" then
                    for n,v in next, _G[action] do
                        if n:byte() ~= 95 then
                            if type(v) == "table" then
                                for n1,v1 in next, v do
                                    ui.consoleLog(action.."."..n.."."..n1.." = "..tostring(v1))
                                end
                            else
                                ui.consoleLog(action.."."..n.." = "..tostring(v))
                        end
                    end
                end
                elseif action == "lib" then
                    ui.consoleLog(string.format("timer: %d\nhandler: %d\nlabel: %d\n", #ui.timer.list, #ui.handler.list, #ui.label.list))
                    for n in next, ui.command.list do
                        ui.consoleLog("command: "..n)
                    end
                    for k,r in next, ui.shortcut.list.pressed do
                        local s = k
                        for _,t in next, r do
                            for _,p in next, t.keysHeld do
                                s = p.." + "..s
                            end
                        end
                        ui.consoleLog("shortcut: "..s)
                    end
                end
            end)
        end)
    end
    
    
    --- all functions and objects below this line are subject to be changed and/or removed
    
    
    function ui.onShortcutPressed(...)
        return ui.shortcut.add("pressed", ...)
    end
    function ui.onShortcut(...)
        return ui.shortcut.add("watch", ...)
    end
    ui.registerKey = ui.onShortcutPress -- depricated: ui.registerKey is about to be removed]]
    
    
    
    function moveLabelOffsetCenter(index, x, y)
        local r = ui.label.list[index].caption:rect()
        r.moveTo(nil, nil):moveBy(x, y)
        ui.label.rect(index, r)
    end
    
    
    --- the objects below will move to libsota.ui and may change
    --- this is espacially true when we get a graphical ui
    
    rect = {
        _new = function(self, left, top, width, height)
            return self.new(left, top, width, height)
        end,
        new = function(left, top, width, height)
            local r = {
                left = left,
                top = top,
                width = width,
                height = height,
            }
    
            if not width then
                r.width = client.screen.width / 3.3
            elseif tonumber(width) == nil then
                width = tonumber(string.match(width, "^%d+"))
                r.width = client.screen.width / 100 * math.abs(width)
            end
            if not height then
                r.height = client.screen.height / 3.6
            elseif tonumber(height) == nil then
                height = tonumber(string.match(height, "^%d+"))
                r.height = client.screen.height / 100 * math.abs(height)
            end
            if not left then
                r.left = (client.screen.width - r.width) / 2
            elseif tonumber(left) == nil then
                left = tonumber(string.match(left, "^%d+"))
                if left < 0 then
                    r.left = client.screen.width - (client.screen.width / 100 * -left) - r.width
                else
                    r.left = client.screen.width / 100 * left
                end
            end
            if not top then
                r.top = (client.screen.height - r.height) / 2
            elseif tonumber(top) == nil then
                top = tonumber(string.match(top, "^%d+"))
                if top < 0 then
                    r.top = client.screen.height - (client.screen.height / 100 * -top) - r.height
                else
                    r.top = client.screen.height / 100 * top
                end
            end
           
            setmetatable(r, {__index = rect})
            return r
        end,
    
        fromString = function(string)
            local str = string:gsub("<[^>]*>", "")
            local s = tonumber(string:match("<size=(%d-)>"))
            if not s then s = 12 end
            return rect.new(0, 0, str:len() * (s/2), s*1.78)
        end,
       
        moveTo = function(rect, x, y)
            if not x then x = (client.screen.width - rect.width) / 2 end
            if not y then y = (client.screen.height - rect.height) / 2 end
            rect.left = x
            rect.top = y
            return rect
        end,
       
        moveBy = function(rect, x, y)
            rect.left = rect.left + x
            rect.top = rect.top + y
            return rect
        end,
       
        resizeTo = function(rect, w, h)
            rect.width = w
            rect.height = h
            return rect
        end,
       
        resizeBy = function(rect, w, h)
            rect.width = rect.width + w
            rect.height = rect.height + h
            return rect
        end,
    }
    setmetatable(rect, {__call = rect._new})
    
    
    string.style = function(string, style)
        if style.bold then string = "<b>"..string.."</b>" end
        if style.italic then string = "<i>"..string.."</i>" end
        if style.size and style.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
        if style.color and #style.color > 3 then string = "<color="..style.color..">"..string.."</color>" end
        return string
    end
    string.rect = rect.fromString
    
    
    
    --[[
    ui.window = {
        _new = function(self, top, width, height, title)
            return self.new(top, width, height, title)
        end,
        new = function(left, top, width, height, title)
            local w = {
                _label = {},
                rect = rect(left, top, width, height),
                title = title,
                background = true,
            }
            setmetatable(w, {__index = window})
            return w
        end,
        createLabel = function(window, name, left, top, caption)
            local r = caption:rect()
            r = rect(left, top, r.width, r.height):moveBy(window.rect.left, window.rect.top)
            window._label[tostring(name)] = ui.label.add(r.left, r.top. r.width, r.height, caption)
        end,
        createTextbox = function(window, name, left, top, width, height)
            local id = ui.label.add()
            ui.label.rect(id, rect(left, top, width, height):moveBy(window.rect.left, window.rect.top))
            window._label[tostring(name)] = id
        end,
        visible = function(window, visible)
            for _,l in next, window._label do
                ui.label.list[index].visible = visible
            end
        end,
        move = function(window, rect)
            local dx = window.rect.left - rect.left
            local dy = window.rect.top - rect.top
            for _,l in next, window._label do
                ui.label.moveBy(l, dx, dy)
            end
            window.rect = rect
        end,
        visualMove = function(window)
        end,
    }
    setmetatable(window, {__call = window._new})
    ]]
    
    

    client.api.list contains know also the globals from the shroud api
    client.api.isImplemented("<shroudfunction>") - returns true if the functions is available , false otherwise
    client.api.luaPath - added

    ui.onStart - is know invoked after all mods that are using the lib are initialized. ShroudOnStart must be used to ensure all scripts are loaded
    ui.onConsoleCommand is know invoked with source argument: callback(source, command, tail). source is a table containing sender, receiver and channel

    add player.isGod - true when player has [God] flag set

    the keycode still need a clean up.

    ----libsota.util

    createLabelWithShadow - this will create a label with shadow. The functions from util (moveLabel, showLabel, .. and so on) have to be used, to deal with a label that has shadow

    @that_shawn_guy asked (here) to have OffsetCenterLabel added..
    That is now in: rect.fromString(caption):moveTo(nil, nil):moveBy(offsetX, offsetY) - get size of caption : move to center : offset from center
    moveLabelOffsetCenter(index, x, y) - can also be used to have the above in one function. caption is taken from the label

    rect object:

    rect(left, top, width, height) - creates a rect object for use in other rect functions (left or top = nil means center, width or height = nil means default size. for every argument a percent value can be given "5%")
    rect.fromString(string) - returns a rect that matches the size of the string. <size=nn></size> is supported. rect.left = 0, rect.top = 0, rect.with = width on screen of given string, rect.height = height on screen of given string
    rect.moveBy , rect.moveTo - moves rect to the given position (by = delta, to = absolute)
    rect.resizeBy, rect.resizeTo - resizes rect to the given size (by = delta, to = absolute)

    a rect can be applied to a label bei using ui.label.rect(index, rect)

    string object (added)
    string.style(string, style) or string:style(style) - applies a style to the string
    string.rect(string) or string:rect() - returns the rect from the string (alias for rect.fromString)

    style is a table that may contain: { size = <size>, color = <color>, bold = <true|false>, italic = <true|false> }
    Eg: string:style({ size = 16, bold = true })

    --- libsota.ui
    i was about to hack a window object into libsota.util quick and dirty. but that would need to much objects.
    so i decided to move on with libsota.ui
    to many players are asking... can i move the window, can i resize the font
    i try to bring this basic features into libsota.ui

    implenting the party interface is delayed in favor of libsota.ui

    Example using rect and style:
    Code:
    -- locTracker by Catweazle Waldschrath
    -- depends on libsota.0.4.x
    
    
    function ShroudOnStart()
    
    -- apis are ready, time to compose our mod. (initialize)
    ui.onInit(function()
        local styleStillness = { size=14, color="#c4a000", bold=true }
        local styleMoving = { size=14, color="yellow", bold=true }
        local styleStanding = { size=14, color="#edd400", bold=true }
        local styleShadow = { size=14, color="black", bold=true }
    
        loctrack = {
            enabled = true,
            shadow = ui.label.add(),
            label = ui.label.add(),
            timer = ui.setInterval(0.25, function()
                local loc = player.location
                local caption = string.format("%s (x: %.2f, y: %.2f, z: %.2f)", loc.scene.name, loc.x, loc.y, loc.z)
                local style = styleStanding
               
                if player.isMoving then
                    style = styleMoving
                elseif player.isStill then
                    style = styleStillness
                end
    
                local r = rect.fromString(caption:style(style)):moveTo(nil, 90)
                ui.label.rect(loctrack.label, r)
                ui.label.rect(loctrack.shadow, r:moveBy(1,1))
                ui.label.caption(loctrack.label, caption:style(style))
                ui.label.caption(loctrack.shadow, caption:style(styleShadow))           
            end),
        }
           
        ui.command.add("loctrack", function()
            loctrack.enabled = not loctrack.enabled
    
            ui.timer.enabled(loctrack.timer, loctrack.enabled)
            ui.label.visible(loctrack.label, loctrack.enabled)
            ui.label.visible(loctrack.shadow, loctrack.enabled)
           
            local msg = createLabel(nil, nil, 180, 120)
            if ui.timer.enabled(loctrack.timer) then
                ui.label.caption(msg, "<size=16><color=white>locTracker activated</color></size>")
            else
                ui.label.caption(msg, "<size=16><color=white>locTracker deactivated</color></size>")
            end
            ui.label.visible(msg, true)
            ui.setTimeout(3, function() ui.label.remove(msg) end)
        end)
        ui.shortcut.add("pressed", "RightAlt", "L", "loctrack")
    
    
        -- all mods are initialized, time to start/run
        ui.onStart(function()
            ui.label.visible(loctrack.label, true)
            ui.label.visible(loctrack.shadow, true)
        end)
       
    end)
    
    
    
    
    end -- ShroudOnStart
    
    -- implement other ShroudOn... to allow other scripts
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    
    
     
  20. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Quick and dirty windows and window manager (libsota.util):
    Code:
    -- libsota.0.4.util-dev by Catweazle Waldschrath
    -- helper functions for global namespace to work with the ui.objects
    -- depends on libsota.0.4.x
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.add(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.add(interval, false, callback)
    end
    
    function getTimer(index)
        return ui.timer.list[index]
    end
    
    function cancelTimer(index)
        ui.timer.list[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer.list[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer.list[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        return ui.label.add(r.left, r.top, r.width, r.height, caption)
    end
    
    function createLabelWithShadow(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        local s = ui.label.add(r.left+1, r.top+1, r.width, r.height, caption)
        local l = ui.label.add(r.left, r.top, r.width, r.height, caption)
        ui.label.list[l].shadow = s
    end
    
    function getLabel(index)
        return ui.label.list[index]
    end
    
    function getLabelCaption(index)
        return ui.label.list[index].caption
    end
    getLabelText = getLabelCaption
    
    function setLabelCaption(index, caption)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].caption = "<color=black>"..caption.."</color>"
        end
        ui.label.list[index].caption = caption
    end
    setLabelText = setLabelCaption
    
    function removeLabel(index)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow] = nil
        end
        ui.label.list[index] = nil
    end
    
    function showLabel(index)
        setLabelVisible(index, true)
    end
    
    function hideLabel(index)
        setLabelVisible(index, false)
    end
    
    function toggleLabel(index)
        setLabelVisible(index, not ui.label.list[index].visible)
    end
    
    function isLabelVisible(index)
        return ui.label.list[index].visible
    end
    
    function setLabelVisible(index, visible)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].visible = visible
        end
        ui.label.list[index].visible = visible
    end
    
    function moveLabelBy(index, x, y)
        moveLabelTo(index, ui.label.list[index].left + x, ui.label.list[index].top + y)
    end
    
    function moveLabelTo(index, x, y)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].left = x + 1
            ui.label.list[ui.label.list[index].shadow].top = y + 1
        end
        ui.label.list[index].left = x
        ui.label.list[index].top = y
    end
    
    function resizeLabelBy(index, w, h)
        resizeLabelTo(index, ui.label.list[index].width + w, ui.label.list[index].height + h)
    end
    
    function resizeLabelTo(index, w, h)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].width = width
            ui.label.list[ui.label.list[index].shadow].height = height
        end
        ui.label.list[index].width = w
        ui.label.list[index].height = h
    end
    
    
    
    
    -- implement Shroud calls
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart()
        ui.onInit(function()
            ui.command.add("lua", function(source, action)
                if action == "api" then
                    for f,t in next, client.api.list do
                        ui.consoleLog(t..": "..f)
                    end
                elseif action == "lua" or action == "path" or action == "version" then
                    ui.consoleLog("LUA Version: "..client.api.luaVersion)
                    ui.consoleLog("LUA Path: "..client.api.luaPath)
                elseif action == "reload" or action == "unload" then
                    ui.consoleLog("type: /lua "..action.." in the chat window instead")
                end
            end)
            ui.command.add("info", function(source, action, param)
                if action == "xp" then
                    local xp = player.xp()
                    ui.consoleLog("Adventurer pooled XP: "..xp.adventurer.."\nProducer pooled XP: "..xp.producer)
                elseif action == "stat" and param then
                    local stat = player.stat(param)
                    ui.consoleLog(string.format("Stat %d: %s = %s (%s)", stat.number, stat.name, stat.value, stat.description))
                elseif action == "client" or action == "player" or action == "scene" or action == "ui" then
                    for n,v in next, _G[action] do
                        if n:byte() ~= 95 then
                            if type(v) == "table" then
                                for n1,v1 in next, v do
                                    ui.consoleLog(action.."."..n.."."..n1.." = "..tostring(v1))
                                end
                            else
                                ui.consoleLog(action.."."..n.." = "..tostring(v))
                        end
                    end
                end
                elseif action == "lib" then
                    ui.consoleLog(string.format("timer: %d\nhandler: %d\nlabel: %d\n", #ui.timer.list, #ui.handler.list, #ui.label.list))
                    for n in next, ui.command.list do
                        ui.consoleLog("command: "..n)
                    end
                    for k,r in next, ui.shortcut.list.pressed do
                        local s = k
                        for _,t in next, r do
                            for _,p in next, t.keysHeld do
                                s = p.." + "..s
                            end
                        end
                        ui.consoleLog("shortcut: "..s)
                    end
                end
            end)
        end)
    end
    
    
    --- all functions and objects below this line are subject to be changed and/or removed
    
    
    function ui.onShortcutPressed(...)
        return ui.shortcut.add("pressed", ...)
    end
    function ui.onShortcut(...)
        return ui.shortcut.add("watch", ...)
    end
    ui.registerKey = ui.onShortcutPress -- depricated: ui.registerKey is about to be removed]]
    
    ui.onCommand = ui.command.add
    
    
    function moveLabelOffsetCenter(index, x, y)
        local r = ui.label.list[index].caption:rect()
        r.moveTo(nil, nil):moveBy(x, y)
        ui.label.rect(index, r)
    end
    
    
    --- the objects below will move to libsota.ui and may change
    --- this is espacially true when we get a graphical ui
    
    rect = {
        _new = function(self, left, top, width, height)
            return self.new(left, top, width, height)
        end,
        new = function(left, top, width, height)
            local r = {
                left = left,
                top = top,
                width = width,
                height = height,
            }
    
            if not width then
                r.width = client.screen.width / 3.3
            elseif tonumber(width) == nil then
                width = tonumber(string.match(width, "^%d+"))
                r.width = client.screen.width / 100 * math.abs(width)
            end
            if not height then
                r.height = client.screen.height / 3.6
            elseif tonumber(height) == nil then
                height = tonumber(string.match(height, "^%d+"))
                r.height = client.screen.height / 100 * math.abs(height)
            end
            if not left then
                r.left = (client.screen.width - r.width) / 2
            elseif tonumber(left) == nil then
                left = tonumber(string.match(left, "^%d+"))
                if left < 0 then
                    r.left = client.screen.width - (client.screen.width / 100 * -left) - r.width
                else
                    r.left = client.screen.width / 100 * left
                end
            end
            if not top then
                r.top = (client.screen.height - r.height) / 2
            elseif tonumber(top) == nil then
                top = tonumber(string.match(top, "^%d+"))
                if top < 0 then
                    r.top = client.screen.height - (client.screen.height / 100 * -top) - r.height
                else
                    r.top = client.screen.height / 100 * top
                end
            end
           
            setmetatable(r, {__index = rect})
            return r
        end,
    
        fromString = function(string)
            local str = string:gsub("<[^>]*>", "")
            local s = tonumber(string:match("<size=(%d-)>"))
            if not s then s = 12 end
            return rect.new(0, 0, str:len() * (s/1.78), s*1.78)
        end,
       
        moveTo = function(rect, x, y)
            if not x then x = (client.screen.width - rect.width) / 2 end
            if not y then y = (client.screen.height - rect.height) / 2 end
            rect.left = x
            rect.top = y
            return rect
        end,
       
        moveBy = function(rect, x, y)
            rect.left = rect.left + x
            rect.top = rect.top + y
            return rect
        end,
       
        resizeTo = function(rect, w, h)
            rect.width = w
            rect.height = h
            return rect
        end,
       
        resizeBy = function(rect, w, h)
            rect.width = rect.width + w
            rect.height = rect.height + h
            return rect
        end,
    }
    setmetatable(rect, {__call = rect._new})
    
    
    string.style = function(string, style)
        if style.bold then string = "<b>"..string.."</b>" end
        if style.italic then string = "<i>"..string.."</i>" end
        if style.size and style.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
        if style.color and #style.color > 3 then string = "<color="..style.color..">"..string.."</color>" end
        return string
    end
    string.rect = rect.fromString
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    window = {
        _new = function(self, left, top, width, height, title)
            return self.new(left, top, width, height, title)
        end,
        new = function(left, top, width, height, title)
            local w = {
                _label = {},
                _rect = rect(left, top, width, height),
                _background = ui.label.add(left, top, width, height),
                _title = ui.label.add(left + 10, top + 10, width - 20, 24, "<b>"..(title or "").."</b>"),
                _borderStyle = {},
                _visible = false,
            }
            setmetatable(w, {__index = window})
            w:resizeTo(width, height)
            return w
        end,
        _clearShortcuts = function(window)
            for i,s in next, window._shortcut do
                ui.shortcut.remove(s)
                window._shortcut[i] = nil
            end
        end,
        createLabel = function(window, name, left, top, caption)
            local r = caption:rect()
            r = rect(left, top, r.width, r.height):moveBy(window._rect.left, window._rect.top)
            window._label[tostring(name)] = ui.label.add(r.left, r.top, r.width, r.height, caption)
        end,
        createTextbox = function(window, name, left, top, width, height)
            local id = ui.label.add()
            ui.label.rect(id, rect(left, top, width, height):moveBy(window._rect.left, window._rect.top))
            window._label[tostring(name)] = id
        end,
        visible = function(window, visible)
            for _,l in next, window._label do
                ui.label.list[l].visible = visible
            end
            ui.label.list[window._background].visible = visible
            ui.label.list[window._title].visible = visible
            window._visible = visible
        end,
        moveTo = function(window, x, y)
            local dx = x - window._rect.left
            local dy = y - window._rect.top
            for _,l in next, window._label do
                ui.label.moveBy(l, dx, dy)
            end
            window._rect = window._rect:moveTo(x,y)
            ui.label.rect(window._background, window._rect)
            ui.label.moveTo(window._title, window._rect.left + 10, window._rect.top + 10)
        end,
        moveBy = function(window, x, y)
            window:moveTo(window._rect.left + x, window._rect.top + y)
        end,
        resizeTo = function(window, w, h)
            window._rect.width = w
            window._rect.height = h
            ui.label.rect(window._background, window._rect)
    
            local w = window._rect
            local cw = w.width / 9.5
            local ch = w.height / 16.67
            local border = "╔" .. string.rep("═", cw) .. "╗" .. string.rep("\n║".."<color=#000000bb>"..string.rep("█", cw).."</color>".."║", ch) .. "\n╚" .. string.rep("═", cw) .. "╝";
            ui.label.list[window._background].caption = border:style(window._borderStyle)
        end,
        setText = function(window, name, text)
            ui.label.list[window._label[name]].caption = text
        end,
        setCaption = function(window, name, caption)
            ui.label.list[window._label[name]].caption = text
        end,
        getLabel = function(window, name)
            return ui.label.list[window._label[name]]
        end,
    }
    setmetatable(window, {__call = window._new})
    
    ui.onInit(function()
    
    
    wdm = {
        _window = {},
        visible = false,
        _focused = 0,
        _focusNext = function()
            if wdm._window[wdm._focused] then
                wdm._window[wdm._focused]._borderStyle = { }
                wdm._window[wdm._focused]:resizeTo(wdm._window[wdm._focused]._rect.width, wdm._window[wdm._focused]._rect.height)
                if wdm._window[wdm._focused]._wasInvisible then
                    wdm._window[wdm._focused]._wasInvisible = nil
                    wdm._window[wdm._focused]:visible(false)
                end
            end       
            if not wdm._window[wdm._focused + 1] then wdm._focused = 0 end
            wdm._focused = wdm._focused + 1
            if wdm._window[wdm._focused] then
                if not wdm._window[wdm._focused]._visible then
                    wdm._window[wdm._focused]._wasInvisible = true
                    wdm._window[wdm._focused]:visible(true)
                end
                if wdm._window[wdm._focused]._wasInvisible then
                    wdm._window[wdm._focused]._borderStyle =  { color = "#edd40099" }
                else
                    wdm._window[wdm._focused]._borderStyle =  { color = "#edd400" }               
                end
                wdm._window[wdm._focused]:resizeTo(wdm._window[wdm._focused]._rect.width, wdm._window[wdm._focused]._rect.height)
            end
        end,
        _moveFocused = function(x, y)
            if wdm._window[wdm._focused] then
                wdm._window[wdm._focused]:moveBy(x, y)
            end
        end,
        _toggleFocused = function()
            if wdm._window[wdm._focused] then
                if wdm._window[wdm._focused]._wasInvisible then
                    wdm._window[wdm._focused]:visible(true)
                    wdm._window[wdm._focused]._wasInvisible = nil
                else
                    wdm._window[wdm._focused]._wasInvisible = true
                end
                if wdm._window[wdm._focused]._wasInvisible then
                    wdm._window[wdm._focused]._borderStyle =  { color = "#edd40099" }
                else
                    wdm._window[wdm._focused]._borderStyle =  { color = "#edd400" }               
                end
                wdm._window[wdm._focused]:resizeTo(wdm._window[wdm._focused]._rect.width, wdm._window[wdm._focused]._rect.height)
            end
        end,
        add = function(window)
            local index = #wdm._window + 1
            wdm._window[index] = window
            return index
        end,
       
       
    }
    ui.shortcut.add("pressed", "LeftControl", "Tab", wdm._focusNext)
    ui.shortcut.add("watch", "LeftControl", "UpArrow", function() wdm._moveFocused(0, -5) end)
    ui.shortcut.add("watch", "LeftControl", "DownArrow", function() wdm._moveFocused(0, 5) end)
    ui.shortcut.add("watch", "LeftControl", "LeftArrow", function() wdm._moveFocused(-7, 0) end)
    ui.shortcut.add("watch", "LeftControl", "RightArrow", function() wdm._moveFocused(7, 0) end)
    ui.shortcut.add("pressed", "LeftControl", "F4", wdm._toggleFocused)
    
    end)
    
    


    You can switch through the windows using LeftControl + Tab. Focused windows can be moved using LeftControl and the ArrowKeys. LeftControl + F4 hides or unhides the focused window.
    You can play a little around with that. Good luck.

    Here is an example script how to use:
    Code:
    function ShroudOnStart()
        ui.onInit(function()
           
            w1 = window(100, 100, 600, 300, "testfenster")
            w1:createLabel("test", 10, 30, "Hallo")
            w1:visible(true)
            w1:moveTo(250, 300)
            wdm.add(w1)
    
            w2 = window(150, 150, 600, 300, "testfenster")
            w2:createLabel("test", 10, 30, "Welt")
            w2:visible(true)
            wdm.add(w2)
           
            w3 = window(350, 450, 600, 300, "testfenster")
            w3:createLabel("test2", 10, 30, "Hallo")
            w3:createLabel("test1", 40, 30, "Welt")
            w3:visible(true)
            wdm.add(w3)
    
        end)
    end
    
     
    StarLord, Anpu, devilcult and 2 others like this.
  21. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.4.4 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeToLoad = 0,
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        isHitching = false,
        isLoading = false,
        _statEnum = {},
        _statDescr = {},
        api = {
            luaVersion = "",
            luaPath = "",
            list = {},
            isImplemented = function(name)
                return client.api.list[name] ~= nil
            end
        },
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        isAfk = false,
        isGod = false,
        isMoving = false,
        isStill = false,
        lastMoved = os.time(),
        location = {},
        health = {},
        focus = {},
        xp = function() return { producer = ShroudGetPooledProducerExperience(), adventurer = ShroudGetPooledAdventurerExperience() } end,
        stat = function(index)
            local ret = {
                number = -1,
                name = "invalid",
                value = -999,
                description = "",
            }
            if tonumber(index) == nil then
                index = client._statEnum[index]
            else
                index = tonumber(index)
            end
            if index and index <= #client._statEnum then
                ret.number = index
                ret.name = client._statEnum[index]
                ret.value = ShroudGetStatValueByNumber(index)
                ret.description = client._statDescr[index]
            end
            return ret
        end,
    }
    ui = {
        version = "0.4.4",
    
        timer = {
            list = {},
            add = function(timeout, once, callback, ...)
                local index = #ui.timer.list + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer.list[index] = {
                    _index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    callback = callback,
                    userdata = ...,
                }
                return index
            end,
            get = function(index)
                return ui.timer.list[index]
            end,
            remove = function(index)
                ui.timer.list[index] = nil
            end,
            enabled = function(index, enabled)
                if enabled ~= nil then
                    ui.timer.list[index].enabled = enabled
                end
                return ui.timer.list[index].enabled
            end,
            pause = function(index)
                ui.timer.list[index].enabled = false
            end,
            resume = function(index)
                ui.timer.list[index].enabled = true
            end,
            toggle = function(index)
                ui.timer.list[index].enabled = not ui.timer.list[index].enabled
            end,
        },
        setTimeout = function(timeout, callback) return ui.timer.add(timeout, true, callback) end,
        setInterval = function(interval, callback) return ui.timer.add(interval, false, callback) end,
    
     
        handler = {
            list = {},
            add = function(name, callback)
                local index = #ui.handler.list + 1
    
                ui.handler.list[index] = {
                    _index = index,
                    name = name,
                    callback = callback,
                }
                return index 
            end,
            remove = function(index)
                ui.handler.list[index] = nil
            end,
            invoke = function(name, ...)
                if not ui_initialized then return end
                for _,h in next, ui.handler.list do
                    if h.name == name then
                        --print(os.date("%X").." : invoke "..h.name)
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.callback(...)
                    end
                end
            end,
        },
        onInit = function(callback) return ui.handler.add("_init", callback) end,
        onStart = function(callback) return ui.handler.add("_start", callback) end,
        onUpdate = function(callback) return ui.handler.add("_update", callback) end,
        onConsoleInput = function(callback) return ui.handler.add("_consoleInput", callback) end,
        onConsoleCommand = function(callback) return ui.handler.add("_consoleCommand", callback) end,
        onSceneChanged = function(callback) return ui.handler.add("_sceneChanged", callback) end,
        onPlayerChanged = function(callback) return ui.handler.add("_playerChanged", callback) end,
        onPlayerMoveStart = function(callback) return ui.handler.add("_playerMoveStart", callback) end,
        onPlayerMoveStop = function(callback) return ui.handler.add("_playerMoveStop", callback) end,
        onPlayerIsStill = function(callback) return ui.handler.add("_playerIsStill", callback) end,
        onPlayerDamage = function(callback) return ui.handler.add("_playerDamage", callback) end,
        onClientIsHitching = function(callback) return ui.handler.add("_clientIsHitching", callback) end,
        onClientIsLoading = function(callback) return ui.handler.add("_clientIsLoading", callback) end,
    
    
        label = {
            _drawInScene = {},
            _drawInLoadScreen = {},
            list = {},
            add = function(left, top, width, height, caption)
                local index = #ui.label.list + 1
    
                ui.label.list[index] = {
                    _index = index,
                    left = left or 0,
                    top = top or 0,
                    width = width or 0,
                    height = height or 0,
                    caption = caption or "",
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                    zIndex = 0,
                }
                --setmetatable(ui.label.list[index], {__index = ui.label})
                return index
            end,
            get = function(index)
                return ui.label.list[index]
            end,
            remove = function(index)
                ui.label.list[index] = nil
            end,
            rect = function(index, rect)
                local l = ui.label.list[index]
                l.left = rect.left
                l.top = rect.top
                l.width = rect.width
                l.height = rect.height
            end,
            caption = function(index, caption)
                if caption then
                    ui.label.list[index].caption = caption
                end
                return ui.label.list[index].caption
            end,
            visible = function(index, visible)
                if visible ~= nil then
                    ui.label.list[index].visible = visible
                end
                return ui.label.list[index].visible
            end,
            toggle = function(index)
                ui.label.list[index].visible = not ui.label.list[index].visible
            end,
            moveTo = function(index, x, y)
                ui.label.list[index].left = x
                ui.label.list[index].top = y
            end,
            moveBy = function(index, x, y)
                ui.label.list[index].left = ui.label.list[index].left + x
                ui.label.list[index].top = ui.label.list[index].top + y
            end,
            resizeTo = function(index, w, h)
                ui.label.list[index].width = w
                ui.label.list[index].height = h
            end,
            resizeBy = function(index, x, y)
                ui.label.list[index].width = ui.label.list[index].width + x
                ui.label.list[index].height = ui.label.list[index].height + y
            end,
        },
     
        shortcut = {
            list = { pressed = {}, watch = {} },
            add = function(action, ...)
                local keys = {...}
                local callback = keys[#keys]; keys[#keys] = nil
                local key = keys[#keys]; keys[#keys] = nil
                if not ui.shortcut.list[action][tostring(key)] then
                    ui.shortcut.list[action][tostring(key)] = {}
                end
                if type(callback) == "string" then
                    callback = ui.command.list[callback].callback
                end
                local id = #ui.shortcut.list[action][key] + 1
                local index = { action = action, key = key, id = id }
                ui.shortcut.list[action][key][id] = {
                    _index = index,
                    key = key,
                    keysHeld = keys,
                    callback = callback,
                }
                if type(callback) == string then
                    if ui.command.list[callback].shortcut then
                        ui.shortcut.remove(ui.command.list[callback].shortcut)
                    end
                    ui.command.list[callback].shortcut = index
                end
                return index
            end,
            remove = function(index)
                ui.shortcut.list[index.action][index.key][index.id] = nil
                if #ui.shortcut.list[index.action][index.key] == 0 then ui.shortcut.list[index.action][index.key] = nil end
            end,
            invoke = function(index)
                ui.shortcut.list[index.action][index.key][index.id].callback()
            end,
        },
     
        command = {
            list = {},
            add = function(command, callback)
                ui.command.list[tostring(command)] = {
                    _command = command,
                    callback = callback,
                    shortcut = nil,
                    channel = nil,
                    sender = player.name,
                    receiver = nil,
                }
                return command
            end,
            remove = function(command)
                if ui.command.list[command].shortcut then
                    ui.shortcut.remove(ui.command.list[command].shortcut)
                end
                ui.command.list[command] = nil
            end,
            invoke = function(command, ...)
                if ... then
                    ui.command.list[command].callback(nil, unpack(...))
                else
                    ui.command.list[command].callback()
                end
            end,
            restrict = function(command, channel, sender, receiver)
                if ui.command[command] then
                    ui.command[command].channel = channel
                    ui.command[command].sender = sender
                    ui.command[command].receiver = receiver
                end
            end
        },
     
        verbosity = 0,
        consoleLog = function(message, verbosity)
            if not verbosity then verbosity = 0 end
            if ui.verbosity >= verbosity then
                for l in message:gmatch("[^\n]+") do
                    ShroudConsoleLog(l)
                end
            end
        end,
    }
    --setmetatable(ui.timer, {__index = ui.timer.list})
    setmetatable(ui.label, {__index = ui.label.list})
    
    
    
    -- internal
    ui_initialized = false
    ui_client_ts = os.time()
    ui_client_frame = 0
    
    local function ui_getPlayerName()
        if player.caption ~= ShroudGetPlayerName() then
            player.caption = ShroudGetPlayerName()
            player.name = player.caption
            player.flag = ""
            player.isPvp = false
            if player.name:byte(#player.name) == 93 then
                player.name, player.flag = player.name:match("^(.-)(%[.-%])$")
                player.isPvp = player.flag and player.flag:find("PVP") ~= nil
                player.isAfk = player.flag and player.flag:find("AFK") ~= nil
                player.isGod = player.flag and player.flag:find("God") ~= nil
            end
            ui.handler.invoke("_playerChanged", "caption")
        end
    end
    local function ui_getPlayerChange()
        local loc = { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene }
        local isMoving = loc.x ~= player.location.x or loc.y ~= player.location.y or loc.z ~= player.location.z
        if isMoving then player.lastMoved = os.time() end
        local isStill = os.time() - player.lastMoved > 5
        local invoke = isStill ~= player.isStill or isMoving ~= player.isMoving
        player.isMoving = isMoving
        player.isStill = isStill
        player.location = loc
        if invoke then
            if isMoving then
                ui.handler.invoke("_playerMoveStart")
                ui.handler.invoke("_playerChanged", "moving")
            elseif isStill then
                ui.handler.invoke("_playerIsStill")
                ui.handler.invoke("_playerChanged", "stillness")
            else
                ui.handler.invoke("_playerMoveStop")
                ui.handler.invoke("_playerChanged", "standing")
            end
        end
        local health = { max = ShroudGetStatValueByNumber(30), current = ShroudPlayerCurrentHealth, percentage = 0 }
        local focus =  { max = ShroudGetStatValueByNumber(27), current = ShroudPlayerCurrentFocus, percentage = 0 }
        health.percentage = health.current / health.max
        focus.percentage = focus.current / focus.max
        invoke = player.health.current ~= health.current or player.focus.current ~= focus.current
        if invoke then
            ui.handler.invoke("_playerDamage", health, focus)
            ui.handler.invoke("_playerChanged", "damage", health, focus)
        end
        player.health = health
        player.focus = focus
    end
    local function ui_getSceneName()
        if scene.name ~= ShroudGetCurrentSceneName() then
            scene.name = ShroudGetCurrentSceneName()
            scene.maxPlayer = ShroudGetCurrentSceneMaxPlayerCount()
            scene.isPvp = ShroudGetCurrentSceneIsPVP()
            scene.isPot = ShroudGetCurrentSceneIsPOT()
            scene.timeStarted = os.time()
            ui.handler.invoke("_sceneChanged")
        end
    end
    local function ui_initialize()
    
        client.api.luaPath = ShroudLuaPath
        client.api.luaVersion = _G._MOONSHARP.luacompat
        for i,v in next, _G do
            if i:find("^Shroud") then client.api.list[tostring(i)] = type(v) end
        end
    
        client.timeToLoad = ui_client_ts - client.timeStarted
        client.screen = {
            width = ShroudGetScreenX(),
            height = ShroudGetScreenY(),
            isFullScreen = ShroudGetFullScreen(),
        }
        for i=0, ShroudGetStatCount()-1, 1 do
            local name = ShroudGetStatNameByNumber(i)
            client._statEnum[tostring(name)] = i
            client._statEnum[i] = name
            client._statDescr[i] = ShroudGetStatDescriptionByNumber(i)
        end
        ui_getSceneName()
        ui_getPlayerName()
        ui_getPlayerChange()
    
        ShroudRegisterPeriodic("ui_timer_internal", "ui_timer_internal", 0.240, true)
        ShroudRegisterPeriodic("ui_timer_user", "ui_timer_user", 0.120, true)
    end
    local function ui_buildLabelDrawList()
        local ll = ui.label.list
        local l1 = {}
        local l2 = {}
     
        for _,l in next, ll do
            if l.visible and l.shownInScene then
                l1[#l1 + 1] = l
            elseif l.visible and l.shownInLoadScreen then
                l2[#l2 + 1] = l
            end
        end
        table.sort(l1, function(a, b) return a.zIndex < b.zIndex end)
        table.sort(l2, function(a, b) return a.zIndex < b.zIndex end)
    
        ui.label._drawInScene = l1
        ui.label._drawInLoadScreen = l2
    end
    
    
    
    -- shroud hooks
    
    
    -- shroud timers
    
    function ui_timer_internal()
        ui_getPlayerChange()
    end
    function ui_timer_user()
        local ts = os.time()
     
        for _,t in next, ui.timer.list do
            if t.enabled and ts >= t.time then
                if t.interval then
                    t.time = ts + t.interval
                else
                    ui.timer.list[t._index] = nil
                end
                if t.callback(t.userdata) then ui.timer.list[t._index] = nil end
            end
        end
    end
    
    
    -- shroud callbacks
    
    function ShroudOnStart()
        ShroudUseLuaConsoleForPrint(true)
        ShroudConsoleLog(_G._MOONSHARP.banner)
        ShroudConsoleLog("LUA Version: ".._G._MOONSHARP.luacompat)
    end
    
    function ShroudOnUpdate()
        local ts = os.time()
    
        -- init
        if not ui_initialized then
            if not ShroudServerTime then return end -- shroud Api not ready, yet
            ShroudConsoleLog("Shroud Api Ready. ServerTime: "..ShroudServerTime)
    
            ui_initialize()
            ui_initialized = true
    
            ui.handler.invoke("_init")
            ui.handler.invoke("_start")
        end
    
        -- client stats
        ui_client_frame = ui_client_frame + 1;
        if ts - ui_client_ts >= 1 then
            if client.accuracy > 10 then
                ui_getSceneName()
                ui_getPlayerName()
                client.isLoading = false
                scene.timeToLoad = client.accuracy
            end
            client.timeInGame = ShroudTime
            client.timeDelta =  ShroudDeltaTime
            client.fps = ui_client_frame
            ui_client_frame = 0
            client.accuracy = ts - ui_client_ts - 1
            ui_client_ts = ts
            scene.timeInScene = ts - scene.timeStarted
        end
    
        -- check key up
        for ku,r in next, ui.shortcut.list.pressed do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
        -- check key held/repeat
        for ku,r in next, ui.shortcut.list.watch do
            if ShroudGetOnKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeDown = os.time()
                        f.callback("down", ku, f.keysHeld)
                    end
                end
    
            elseif ShroudGetKeyDown(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        if os.time() - f.timeDown >= 0.25 then
                            f.callback("held", ku, f.keysHeld)
                        end
                    end
                end
    
            elseif ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.keysHeld do
                        invoke = invoke and ShroudGetKeyDown(kd)
                    end
                    if invoke then
                        f.timeUp = os.time()
                        f.callback("up", ku, f.keysHeld)
                        if os.time() - f.timeDown <= 0.25 then
                            f.callback("pressed", ku, f.keysHeld)
                        end
                    end
                end
            end
        end
     
        ui.handler.invoke("_update")
     
        ui_buildLabelDrawList() -- sollte bei label sein, wenn eigenschaften gesetzt werden
    end
    
    function ShroudOnGUI()
        local ts = os.time()
        local isHitching = ts - ui_client_ts >= 2
        local isLoading = ts - ui_client_ts >= 5
    
        if isHitching and client.isHitching ~= isHitching then
            client.isHitching = isHitching
            ui.handler.invoke("_clientIsHitching")
        end
        if isLoading and client.isLoading ~= isLoading then
            client.isLoading = isLoading
            ui.handler.invoke("_clientIsLoading")
        end
         
        if not isHitching then
            local ll = ui.label._drawInScene
            if isLoading then ll = ui.label._drawInLoadScreen end
         
            for _,l in next, ll do
                ShroudGUILabel(l.left, l.top, l.width, l.height, l.caption)
                if os.time() - ts > 0.01 then break end
            end
        end
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
    
        ui.timer.add(0, true, function(...)
    
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and message:find("PvP") then ui_getPlayerName() end
    
            -- parse message
            local src, dst, msg = message:match("^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if msg:byte() == 92 or msg:byte() == 95 or msg:byte() == 33 then
                local cmd, tail = msg:match("^[\\_!](%w+)%s*(.*)$")
                local source = {
                    channel = channel,
                    sender = sender,
                    receiver = dst,
                }
                if ui.command.list[cmd] then
                    if not ui.command.list[cmd].sender or ui.command.list[cmd].sender == sender
                    and not ui.command.list[cmd].channel or ui.command.list[cmd].channel == channel
                    and not ui.command.list[cmd].receiver or ui.command.list[cmd].receiver == dst
                    then
                        local arg = {}
                        for a in tail:gmatch("%S+") do arg[#arg + 1] = a end
                        arg.n = #arg
                        ui.command.list[cmd].callback(source, unpack(arg))
                    end
                else
                    ui.handler.invoke("_consoleCommand", source, cmd, tail)
                end
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
        end, channel, sender, message)
    end
    
    
    Code:
    -- libsota.0.4.util-dev by Catweazle Waldschrath
    -- helper functions for global namespace to work with the ui.objects
    -- depends on libsota.0.4.x
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.add(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.add(interval, false, callback)
    end
    
    function getTimer(index)
        return ui.timer.list[index]
    end
    
    function cancelTimer(index)
        ui.timer.list[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer.list[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer.list[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        return ui.label.add(r.left, r.top, r.width, r.height, caption)
    end
    
    function createLabelWithShadow(left, top, width, height, caption)
        local r = rect(left, top, width, height)
        local s = ui.label.add(r.left+1, r.top+1, r.width, r.height, caption)
        local l = ui.label.add(r.left, r.top, r.width, r.height, caption)
        ui.label.list[l].shadow = s
    end
    
    function getLabel(index)
        return ui.label.list[index]
    end
    
    function getLabelCaption(index)
        return ui.label.list[index].caption
    end
    getLabelText = getLabelCaption
    
    function setLabelCaption(index, caption)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].caption = "<color=black>"..caption.."</color>"
        end
        ui.label.list[index].caption = caption
    end
    setLabelText = setLabelCaption
    
    function removeLabel(index)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow] = nil
        end
        ui.label.list[index] = nil
    end
    
    function showLabel(index)
        setLabelVisible(index, true)
    end
    
    function hideLabel(index)
        setLabelVisible(index, false)
    end
    
    function toggleLabel(index)
        setLabelVisible(index, not ui.label.list[index].visible)
    end
    
    function isLabelVisible(index)
        return ui.label.list[index].visible
    end
    
    function setLabelVisible(index, visible)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].visible = visible
        end
        ui.label.list[index].visible = visible
    end
    
    function moveLabelBy(index, x, y)
        moveLabelTo(index, ui.label.list[index].left + x, ui.label.list[index].top + y)
    end
    
    function moveLabelTo(index, x, y)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].left = x + 1
            ui.label.list[ui.label.list[index].shadow].top = y + 1
        end
        ui.label.list[index].left = x
        ui.label.list[index].top = y
    end
    
    function resizeLabelBy(index, w, h)
        resizeLabelTo(index, ui.label.list[index].width + w, ui.label.list[index].height + h)
    end
    
    function resizeLabelTo(index, w, h)
        if ui.label.list[index].shadow then
            ui.label.list[ui.label.list[index].shadow].width = width
            ui.label.list[ui.label.list[index].shadow].height = height
        end
        ui.label.list[index].width = w
        ui.label.list[index].height = h
    end
    
    
    
    
    -- implement Shroud calls
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart()
        ui.onInit(function()
            ui.command.add("lua", function(source, action)
                if action == "api" then
                    for f,t in next, client.api.list do
                        ui.consoleLog(t..": "..f)
                    end
                elseif action == "lua" or action == "path" or action == "version" then
                    ui.consoleLog("LUA Version: "..client.api.luaVersion)
                    ui.consoleLog("LUA Path: "..client.api.luaPath)
                elseif action == "reload" or action == "unload" then
                    ui.consoleLog("type: /lua "..action.." in the chat window instead")
                end
            end)
            ui.command.add("info", function(source, action, param)
                if action == "xp" then
                    local xp = player.xp()
                    ui.consoleLog("Adventurer pooled XP: "..xp.adventurer.."\nProducer pooled XP: "..xp.producer)
                elseif action == "stat" and param then
                    local stat = player.stat(param)
                    ui.consoleLog(string.format("Stat %d: %s = %s (%s)", stat.number, stat.name, stat.value, stat.description))
                elseif action == "client" or action == "player" or action == "scene" or action == "ui" then
                    for n,v in next, _G[action] do
                        if n:byte() ~= 95 then
                            if type(v) == "table" then
                                for n1,v1 in next, v do
                                    ui.consoleLog(action.."."..n.."."..n1.." = "..tostring(v1))
                                end
                            else
                                ui.consoleLog(action.."."..n.." = "..tostring(v))
                        end
                    end
                end
                elseif action == "lib" then
                    ui.consoleLog(string.format("timer: %d\nhandler: %d\nlabel: %d\n", #ui.timer.list, #ui.handler.list, #ui.label.list))
                    for n in next, ui.command.list do
                        ui.consoleLog("command: "..n)
                    end
                    for k,r in next, ui.shortcut.list.pressed do
                        local s = k
                        for _,t in next, r do
                            for _,p in next, t.keysHeld do
                                s = p.." + "..s
                            end
                        end
                        ui.consoleLog("shortcut: "..s)
                    end
                end
            end)
        end)
    end
    
    
    --- all functions and objects below this line are subject to be changed and/or removed
    
    
    function ui.onShortcutPressed(...)
        return ui.shortcut.add("pressed", ...)
    end
    function ui.onShortcut(...)
        return ui.shortcut.add("watch", ...)
    end
    ui.registerKey = ui.onShortcutPress -- depricated: ui.registerKey is about to be removed]]
    
    ui.onCommand = ui.command.add
    
    
    function moveLabelOffsetCenter(index, x, y)
        local r = ui.label.list[index].caption:rect()
        r.moveTo(nil, nil):moveBy(x, y)
        ui.label.rect(index, r)
    end
    
    
    --- the objects below will move to libsota.ui and may change
    --- this is espacially true when we get a graphical ui
    
    rect = {
        _new = function(self, left, top, width, height)
            return self.new(left, top, width, height)
        end,
        new = function(left, top, width, height)
            local r = {
                left = left,
                top = top,
                width = width,
                height = height,
            }
    
            if not width then
                r.width = client.screen.width / 3.3
            elseif tonumber(width) == nil then
                width = tonumber(string.match(width, "^%d+"))
                r.width = client.screen.width / 100 * math.abs(width)
            end
            if not height then
                r.height = client.screen.height / 3.6
            elseif tonumber(height) == nil then
                height = tonumber(string.match(height, "^%d+"))
                r.height = client.screen.height / 100 * math.abs(height)
            end
            if not left then
                r.left = (client.screen.width - r.width) / 2
            elseif tonumber(left) == nil then
                left = tonumber(string.match(left, "^%d+"))
                if left < 0 then
                    r.left = client.screen.width - (client.screen.width / 100 * -left) - r.width
                else
                    r.left = client.screen.width / 100 * left
                end
            end
            if not top then
                r.top = (client.screen.height - r.height) / 2
            elseif tonumber(top) == nil then
                top = tonumber(string.match(top, "^%d+"))
                if top < 0 then
                    r.top = client.screen.height - (client.screen.height / 100 * -top) - r.height
                else
                    r.top = client.screen.height / 100 * top
                end
            end
         
            setmetatable(r, {__index = rect})
            return r
        end,
    
        fromString = function(string)
            local str = string:gsub("<[^>]*>", "")
            local s = tonumber(string:match("<size=(%d-)>"))
            if not s then s = 12 end
            return rect.new(0, 0, str:len() * (s/1.78), s*1.78)
        end,
     
        moveTo = function(rect, x, y)
            if not x then x = (client.screen.width - rect.width) / 2 end
            if not y then y = (client.screen.height - rect.height) / 2 end
            rect.left = x
            rect.top = y
            return rect
        end,
     
        moveBy = function(rect, x, y)
            rect.left = rect.left + x
            rect.top = rect.top + y
            return rect
        end,
     
        resizeTo = function(rect, w, h)
            rect.width = w
            rect.height = h
            return rect
        end,
     
        resizeBy = function(rect, w, h)
            rect.width = rect.width + w
            rect.height = rect.height + h
            return rect
        end,
    }
    setmetatable(rect, {__call = rect._new})
    
    
    string.style = function(string, style)
        if style.bold then string = "<b>"..string.."</b>" end
        if style.italic then string = "<i>"..string.."</i>" end
        if style.size and style.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
        if style.color and #style.color > 3 then string = "<color="..style.color..">"..string.."</color>" end
        return string
    end
    string.rect = rect.fromString
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    window = {
        _new = function(self, left, top, width, height, title)
            return self.new(left, top, width, height, title)
        end,
        new = function(left, top, width, height, title)
            local w = {
                _label = {},
                _rect = rect(left, top, width, height),
                _background = ui.label.add(left, top, width, height),
                _title = ui.label.add(left + 10, top + 10, width - 20, 24, "<b>"..(title or "").."</b>"),
                _borderStyle = {},
                _visible = false,
                _zIndex = 0,
                resizeAble = true,
                moveAble = true,
            }
            setmetatable(w, {__index = window})
            w:resizeTo(width, height)
            return w
        end,
        createLabel = function(window, name, left, top, caption)
            local r = caption:rect()
            r = rect(left, top, r.width, r.height):moveBy(window._rect.left, window._rect.top)
            window._label[tostring(name)] = ui.label.add(r.left, r.top, r.width, r.height, caption)
        end,
        createTextbox = function(window, name, left, top, width, height)
            local id = ui.label.add()
            ui.label.rect(id, rect(left, top, width, height):moveBy(window._rect.left, window._rect.top))
            window._label[tostring(name)] = id
        end,
        visible = function(window, visible)
            for _,l in next, window._label do
                ui.label.list[l].visible = visible
            end
            ui.label.list[window._background].visible = visible
            ui.label.list[window._title].visible = visible
            window._visible = visible
        end,
        moveTo = function(window, x, y)
            local dx = x - window._rect.left
            local dy = y - window._rect.top
            for _,l in next, window._label do
                ui.label.moveBy(l, dx, dy)
            end
            window._rect = window._rect:moveTo(x,y)
            ui.label.rect(window._background, window._rect)
            ui.label.moveTo(window._title, window._rect.left + 10, window._rect.top + 10)
        end,
        moveBy = function(window, x, y)
            window:moveTo(window._rect.left + x, window._rect.top + y)
        end,
        resizeTo = function(window, w, h)
            window._rect.width = w
            window._rect.height = h
            ui.label.rect(window._background, window._rect)
    
            for _,l in next, window._label do
                ui.label.list[l].width = window._rect.width - 50
            end
    
            local w = window._rect
            local cw = w.width / 9.5
            local ch = w.height / 16.67
            local border = "╔" .. string.rep("═", cw) .. "╗" .. string.rep("\n║".."<color=#000000bb>"..string.rep("█", cw).."</color>".."║", ch) .. "\n╚" .. string.rep("═", cw) .. "╝";
            ui.label.list[window._background].caption = border:style(window._borderStyle)
        end,
        resizeBy = function(window, w, h)
            window:resizeTo(window._rect.width + w, window._rect.height + h)
        end,
        setText = function(window, name, text)
            ui.label.list[window._label[name]].caption = text
        end,
        setCaption = function(window, name, caption)
            ui.label.list[window._label[name]].caption = text
        end,
        getLabel = function(window, name)
            return ui.label.list[window._label[name]]
        end,
        zIndex = function(window, zIndex)
            for _,l in next, window._label do
                ui.label.list[l].zIndex = zIndex
            end
            ui.label.list[window._title].zIndex = zIndex
            ui.label.list[window._background].zIndex = zIndex - 1
            window._zIndex = zIndex
        end,
    }
    setmetatable(window, {__call = window._new})
    
    ui.onInit(function()
    
    
    wdm = {
        _window = {},
        _focused = 0,
        _focusNext = function()
            if wdm._window[wdm._focused] then
                wdm._window[wdm._focused]._borderStyle = { }
                wdm._window[wdm._focused]:resizeTo(wdm._window[wdm._focused]._rect.width, wdm._window[wdm._focused]._rect.height)         
                if wdm._window[wdm._focused]._wasInvisible then
                    wdm._window[wdm._focused]._wasInvisible = nil
                    wdm._window[wdm._focused]:visible(false)
                end
                wdm._window[wdm._focused]:zIndex(wdm._window[wdm._focused]._zIndex - 100)
            end     
            if not wdm._window[wdm._focused + 1] then wdm._focused = -1 end
            wdm._focused = wdm._focused + 1
            if wdm._window[wdm._focused] then
                if not wdm._window[wdm._focused]._visible then
                    wdm._window[wdm._focused]._wasInvisible = true
                    wdm._window[wdm._focused]:visible(true)
                end
                if wdm._window[wdm._focused]._wasInvisible then
                    wdm._window[wdm._focused]._borderStyle =  { color = "#edd40099" }
                else
                    wdm._window[wdm._focused]._borderStyle =  { color = "#edd400" }
                    wdm._window[wdm._focused]:zIndex(wdm._window[wdm._focused]._zIndex + 100)
                end
                wdm._window[wdm._focused]:resizeTo(wdm._window[wdm._focused]._rect.width, wdm._window[wdm._focused]._rect.height)
            end
        end,
        _moveFocused = function(x, y)
            if wdm._window[wdm._focused] and wdm._window[wdm._focused].moveAble then
                wdm._window[wdm._focused]:moveBy(x, y)
            end
        end,
        _resizeFocused = function(w, h)
            if wdm._window[wdm._focused] and wdm._window[wdm._focused].resizeAble then
                wdm._window[wdm._focused]:resizeBy(w, h)
            end
        end,
        _toggleFocused = function()
            if wdm._window[wdm._focused] then
                if wdm._window[wdm._focused]._wasInvisible then
                    wdm._window[wdm._focused]:visible(true)
                    wdm._window[wdm._focused]._wasInvisible = nil
                else
                    wdm._window[wdm._focused]._wasInvisible = true
                end
                if wdm._window[wdm._focused]._wasInvisible then
                    wdm._window[wdm._focused]._borderStyle =  { color = "#edd40099" }
                else
                    wdm._window[wdm._focused]._borderStyle =  { color = "#edd400" }             
                end
                wdm._window[wdm._focused]:resizeTo(wdm._window[wdm._focused]._rect.width, wdm._window[wdm._focused]._rect.height)
            end
        end,
        add = function(window)
            local index = #wdm._window + 1
            wdm._window[index] = window
            window:zIndex(index)
            return index
        end,
     
     
    }
    ui.shortcut.add("pressed", "LeftControl", "Tab", wdm._focusNext)
    ui.shortcut.add("watch", "LeftControl", "UpArrow", function(keyState) if keyState == "held" or keyState == "pressed" then wdm._moveFocused(0, -7) end end)
    ui.shortcut.add("watch", "LeftControl", "DownArrow", function(keyState) if keyState == "held" or keyState == "pressed" then wdm._moveFocused(0, 7) end end)
    ui.shortcut.add("watch", "LeftControl", "LeftArrow", function(keyState) if keyState == "held" or keyState == "pressed" then wdm._moveFocused(-7, 0) end end)
    ui.shortcut.add("watch", "LeftControl", "RightArrow", function(keyState) if keyState == "held" or keyState == "pressed" then wdm._moveFocused(7, 0) end end)
    ui.shortcut.add("pressed", "LeftControl", "F4", wdm._toggleFocused)
    ui.shortcut.add("watch", "LeftAlt", "UpArrow", function(keyState) if keyState == "held" or keyState == "pressed" then wdm._resizeFocused(0, -7) end end)
    ui.shortcut.add("watch", "LeftAlt", "DownArrow", function(keyState) if keyState == "held" or keyState == "pressed" then wdm._resizeFocused(0, 7) end end)
    ui.shortcut.add("watch", "LeftAlt", "LeftArrow", function(keyState) if keyState == "held" or keyState == "pressed" then wdm._resizeFocused(-7, 0) end end)
    ui.shortcut.add("watch", "LeftAlt", "RightArrow", function(keyState) if keyState == "held" or keyState == "pressed" then wdm._resizeFocused(7, 0) end end)
    
    end)
    
    

    Minor update. There are new things on the QA i need to check out


    ui.label can now have a zIndex.

    ----
    libsota.util
    windows are now resizeable using LeftAlt + ArrowKeys
    Windows are brought to front when they get the focus.

    window.moveAble - allows window to be moved by the "window manger" or not
    window.resizeAble - allows window to be resized by the "window manager" or not
    window:moveBy, window.moveTo - moves window (by = delta, to = absolute)
    window:resizeBy, window.resizeTo - resizes window (by = delta, to = absolute)
    window:visible(bool) - set window visible or not
    window:zIndex(zIndex) - set the z-index of the window
    window:createLabel(name, x, y, caption) - creates a label as child for that window.
    window:createTextbox(name, x, y, w, h) - creates a label as child for that window
    window:setCaption(name, caption) - set te caption of label that is child of that window
    window:setText(name, caption) - set the text of label that is a child of that window
    window:getLabel(name) - returns the index of the ui.label used


    Code:
    function ShroudOnStart()
        ui.onInit(function()
         
            w1 = window(100, 100, 600, 300, "testfenster")
            w1:createLabel("test", 10, 30, "Hallo")
            w1:visible(true)
            w1:moveTo(250, 300)
            wdm.add(w1)
    
            w2 = window(150, 150, 600, 300, "testfenster")
            w2:createLabel("test", 10, 30, "Welt")
            w2:visible(true)
            wdm.add(w2)
         
            w3 = window(350, 450, 600, 300, "testfenster")
            w3:createLabel("test2", 10, 30, "Fast alles über Lorem ipsum")
            w3:createTextbox("test1", 10, 50, 580, 200)
            w3:setText("test1", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\nAt vero eos et accusam et justo duo dolores et ea rebum.")
            w3:visible(true)
            wdm.add(w3)
        end)
    end
    
     
    Last edited: Nov 27, 2019
    Anpu and Browncoat Jayson like this.