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

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

  1. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    latest version (0.4.8) | "Docu"



    added the examples from @Tiina Onir and @Drake Aedus to show a little how the lib is used. Feedback and recommandations are welcome.

    Code:
    -- libsota start
    -- libsota.0.3 by Catweazle Waldschrath
    client = {
        started = os.time(),
        fps = 0,
        accuracy = 0,
        _frame = 0,
        _ts = os.time(),
        screen = nil,
    }
    player = {
        name = nil,
        x = function() return ShroudPlayerX end,
        y = function() return ShroudPlayerY end,
        z = function() return ShroudPlayerZ end,
        health = function() return ShroudPlayerCurrentHealth end,
        focus = function() return ShroudPlayerCurrentFocus end,
        stat = function(index)
            local ret = {
                number = nil,
                name = nil,
                value = nil,
                description = nil,
            }
            index = tonumber(index)
            ret.number = index
            ret.name = ShroudGetStatNameByNumber(index)
            ret.value = ShroudGetStatValueByNumber(index)
            --ret.description = ShroudGetStatDescriptionByNumber(index)
            return ret
        end,
    }
    ui = {
        timer = {
            _ts = os.time(),
            _granulaty = 0.120,
            create = function(timeout, once, callback)
                local index = #ui.timer + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer[index] = {
                    index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    func = callback,
                }
                return index
            end,
        },
    
        text = {
            create = function(string)
                local index = #ui.text + 1
    
                ui.text = {
                    value = string,
                    format = "%s",
                    bold = false,
                    italic = false,
                    color = "",
                    size = 0,
                }
                return index
            end,
            get = function(index)
                local t = ui.text[index]
    
                t.format = "%s"
                if t.bold then
                    t.format = "<b>"..t.format.."</b>"
                end
                if t.italic then
                    t.format = "<i>"..t.format.."</i>"
                end
                if t.size > 0 then
                    t.format = "<size="..t.size..">"..t.format.."</size>"
                end
                if t.color != "" then
                    t.format = "<color="..t.color..">"..t.format.."</color>"
                end
    
                return string.format(t.format, t.value)
            end
        },
    
        label = {
            create = function(left, top, width, height, string)
                local index = #ui.label + 1
    
                if left == "c" then left = (client.screen.width - width) / 2 end
                if top == "c" then top = (client.screen.height - height) / 2 end
    
                ui.label[index] = {
                    left = left,
                    top = top,
                    width = width,
                    height = height,
                    text = string,
                    visible = false,
                }
                return index
            end
        },
    }
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.create(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.create(interval, false, callback)
    end
    
    function cancelTimer(index)
        ui.timer[index] = nil;
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, text)
        return ui.label.create(left, top, width, height, text)
    end
    
    function getLabel(index)
        return ui.label[index]
    end
    
    --[[function getLabelText(index)
        return ui.text[ui.label[index].text]
    end
    
    function setLabelString(index, string)
        ui.text[ui.label[index].text].value = string
    end]]
    function getLabelText(index)
        return ui.label[index].text
    end
    function setLabelText(index, text)
        ui.label[index].text = text
    end
    
    function removeLabel(index)
        ui.label[index] = nil
    end
    
    function showLabel(index)
        ui.label[index].visible = true
    end
    
    function hideLabel(index)
        ui.label[index].visible = false
    end
    
    function moveLabelBy(index, x, y)
        ui.label[index].left = ui.label[index].left + x
        ui.label[index].top = ui.label[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label[index].left = x
        ui.label[index].top = y
    end
    
    
    
    -- shroud hooks
    
    function ShroudOnUpdate()
    
        -- init
        if not client.screen then
            client.screen = {
                width = ShroudGetScreenX(),
                height = ShroudGetScreenY(),
                isFullScreen = ShroudGetFullScreen(),
            }
            if ui.onInit then ui.onInit() end
        end
    
        -- client stats
        client._frame = client._frame + 1;
        if os.time() - client._ts >= 1 then
            client.fps = client._frame;
            client._frame = 0;
            client.accuracy = (os.time() - client._ts - 1) * 100
            client._ts = os.time();
            ui.timer._granulaty = 0.120 - (client.accuracy / 100)
        end
    
        -- timers
        if os.time() - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = os.time()
            for _,t in next, ui.timer do
                if type(t) == "table" and t.enabled and os.time() >= t.time then
                    if t.interval then
                        t.time = os.time() + t.interval
                    else
                        ui.timer[t.index] = nil;
                    end
                    t.func();
                end
            end
        end
    
        if ui.onUpdate then ui.onUpdate() end
    end
    
    function ShroudOnGUI()
        for _,l in next, ui.label do
            if type(l) == "table" and l.visible then
                ShroudGUILabel(l.left, l.top, l.width, l.height, l.text);
            end
        end
        --ShroudGUILabel(30,40,512,24, string.format("FPS: %d, Genauigkeit: %f ms", client.fps, client.accuracy));
    end
    
    function ShroudOnConsoleInput(type, src, msg)
        --ConsoleLog(type .. "|" .. src .. "|" .. msg)
        local s, dst, msg = string.match(msg, "^(.-) to (.-) %[.-:%s*(.*)")
        local cmd, arg = string.match(msg, "^\\(%w+)%s*(%w*)");
        if not player.name and dst == "everyone" then player.name = src end
        if src == "" then src = s end
        if cmd and player.name and ui.onConsoleCommand then
            ui.onConsoleCommand(cmd, src, dst, arg)
        elseif ui.onConsoleInput then
            ui.onConsoleInput(type, src, dst, msg)
        end
    end
    -- libsota end
    
    
    
    -- Mudmenu by Drake Aedus
    -- @see https://www.shroudoftheavatar.com/forum/index.php?threads/testing-text-info-window-qa-win-64-885.160145/#post-1266059
    
    -- initialize our variables
    ui.onInit = function()
           x = ShroudGetStatValueByNumber(14)
           y = ShroudGetStatValueByNumber(30)
           str = ShroudGetStatValueByNumber(46)
           dex = ShroudGetStatValueByNumber(22)
           int = ShroudGetStatValueByNumber(32)
           ea_pw = ShroudGetStatValueByNumber(159)
           ea_rs = ShroudGetStatValueByNumber(331)
           ai_pw = ShroudGetStatValueByNumber(160)
           ai_rs = ShroudGetStatValueByNumber(332)
           fi_pw = ShroudGetStatValueByNumber(157)
           fi_rs = ShroudGetStatValueByNumber(329)
           wa_pw = ShroudGetStatValueByNumber(158)
           wa_rs = ShroudGetStatValueByNumber(330)
           su_pw = ShroudGetStatValueByNumber(161)
           su_rs = ShroudGetStatValueByNumber(333)
           mo_pw = ShroudGetStatValueByNumber(162)
           mo_rs = ShroudGetStatValueByNumber(334)
           li_pw = ShroudGetStatValueByNumber(155)
           li_rs = ShroudGetStatValueByNumber(327)
           de_pw = ShroudGetStatValueByNumber(156)
           de_rs = ShroudGetStatValueByNumber(328)
           ch_pw = ShroudGetStatValueByNumber(163)
           ch_rs = ShroudGetStatValueByNumber(335)
           ma_pw = ShroudGetStatValueByNumber(439)
           ma_rs = ShroudGetStatValueByNumber(336)
    
        -- MUD style menu :P
        mudmenu = {
            lblBorder1 = createLabel(250,40,240,600,"╔════════════════════╗\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n╚════════════════════╝"),
            lblBorder2 = createLabel(439,55,240,600,"║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║"),
    
            lblHealthbar = createLabel(265,55,180,580,""),
            lblStatPower = createLabel(335,190,50,300,""),
            lblStatResist = createLabel(395,190,50,300,""),
        }
    end
    
    
    -- lets update our healthbar about once a second...
    setInterval(1, function()
        x = ShroudGetStatValueByNumber(14)   -- CurrentHealth
        y = ShroudGetStatValueByNumber(30)   -- Health
        z = math.floor((x/y) * 20)       -- how many bubbles
    
        ui.label[lblHealthbar].text = string.format("Health: %d/%d\n"..string.rep("■",z)..string.rep("□",20-z).."\n\nDate: "..os.date().."\n\nStr: %d   Dex: %d   Int: %d\n\n               -Power-   -Resist-\nEarth \nAir \nFire \nWater \nSun \nMoon \nLife \nDeath \nChaos \nMagic ",x,y,str,dex,int)
    
    end)
    
    
    -- lets update our stats less frequently
    setInterval(10, function()
           str = ShroudGetStatValueByNumber(46)
           dex = ShroudGetStatValueByNumber(22)
           int = ShroudGetStatValueByNumber(32)
           ea_pw = ShroudGetStatValueByNumber(159)
           ea_rs = ShroudGetStatValueByNumber(331)
           ai_pw = ShroudGetStatValueByNumber(160)
           ai_rs = ShroudGetStatValueByNumber(332)
           fi_pw = ShroudGetStatValueByNumber(157)
           fi_rs = ShroudGetStatValueByNumber(329)
           wa_pw = ShroudGetStatValueByNumber(158)
           wa_rs = ShroudGetStatValueByNumber(330)
           su_pw = ShroudGetStatValueByNumber(161)
           su_rs = ShroudGetStatValueByNumber(333)
           mo_pw = ShroudGetStatValueByNumber(162)
           mo_rs = ShroudGetStatValueByNumber(334)
           li_pw = ShroudGetStatValueByNumber(155)
           li_rs = ShroudGetStatValueByNumber(327)
           de_pw = ShroudGetStatValueByNumber(156)
           de_rs = ShroudGetStatValueByNumber(328)
           ch_pw = ShroudGetStatValueByNumber(163)
           ch_rs = ShroudGetStatValueByNumber(335)
           ma_pw = ShroudGetStatValueByNumber(439)
           ma_rs = ShroudGetStatValueByNumber(336)
    
           ui.label[lblStatPower].text = string.format("%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f",ea_pw,ai_pw,fi_pw,wa_pw,su_pw,mo_pw,li_pw,de_pw,ch_pw,ma_pw)
           ui.label[lblStatResist].text = string.format("%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f",ea_rs,ai_rs,fi_rs,wa_rs,su_rs,mo_rs,li_rs,de_rs,ch_rs,ma_rs)
    
    end)
    
    
    
    -- the command thingy by Tiina Onir
    -- @see https://www.shroudoftheavatar.com/forum/index.php?threads/run-lua-functions-from-the-command-line.160176/#post-1266186
    
    command = { };
    
    function command.ShowStats()
      ConsoleLog(string.format("total stats: %d", ShroudGetStatCount()));
      for i=0, ShroudGetStatCount(), 2 do
        ConsoleLog(string.format('%d: %s - %d: %s', i, ShroudGetStatNameByNumber(i), i+1, ShroudGetStatNameByNumber(i+1)));
      end
    end
    
    function command.ShowStat(si)
      i = tonumber(si);
      ConsoleLog(string.format('(%d)%s: %f', i, ShroudGetStatNameByNumber(i), ShroudGetStatValueByNumber(i)));
    end
    
    function command.findStat(args)
      for i=0, ShroudGetStatCount(), 1 do
        if string.match(string.lower(ShroudGetStatNameByNumber(i)), string.lower(args)) then
          ConsoleLog(string.format('(%d) %s', i, ShroudGetStatNameByNumber(i)));
        end
      end
    end
    
    
    ui.onConsoleCommand = function(cmd, src, dst, arg)
        if cmd == "togglemud" then
            for _,l in next, mudmenu do
                ui.label[l].visible = not ui.label[l].visible
            end
     
        elseif cmd == "help" then
            ConsoleLog("List of known commands:")
            ConsoleLog("togglemud")
            for c in next, command do
                ConsoleLog(c)
            end
    
        elseif not pcall(command[cmd], arg) then
            ConsoleLog("Command Not Found");
    
        end
    end
    

    another example (testing)
    Code:
    -- require "libsota.0.3.lua"
    -- please include/paste code from libsota here
    
    check=os.time()
    ui.onInit = function()
        local id = ui.label.create(30,24,256,24, "Init")
        ui.label[id].visible = true
        setInterval(1, function() ui.label[id].text = os.time() - check; check=os.time(); end)
    end
    
    ui.onConsoleInput = function(type, src, dst, msg)
        ConsoleLog(type .. "|" .. src .. "|" .. dst .. "|" .. msg)
        setTimeout(5, function() local id = ui.label.create("c", "c", 240, 240, msg); showLabel(id); setTimeout(5, function() removeLabel(id); end); end)
    end
    
    ui.onConsoleCommand = function(cmd, src, dst, arg)
            ConsoleLog(cmd .. "|" .. src .. "|" .. dst .. "|" .. arg)
            ConsoleLog("Playername: " .. player.name)
        local stat = player.stat(tonumber(arg))
        ConsoleLog(string.format("Stat: %d, %s, %s", stat.number, stat.name, stat.value))
    end
    
     
    Last edited: Dec 6, 2019
  2. Drake Aedus

    Drake Aedus Avatar

    Messages:
    542
    Likes Received:
    890
    Trophy Points:
    75
    Gender:
    Male
    Very nice library.

    player.foucs could use a typo correction.

    I like that you have examples to follow along. For instance, mudmenu "x" should be easily replaced with player.health which would mean I'd change the comment on "y" to be -- TotalHealth

    Great work!
     
    Last edited: Nov 14, 2019
    Rentier likes this.
  3. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    version 0.3

    Callbacks:
    ui.onInit() -- called on the first update. used to initialize variable and such things
    ui.onConsoleInput(type, src, dst, msg) -- called when text is entered or arrived in the chat window an it is not a command. type = message type, src = message from name, dst = message to name, msg = message
    ui.onConsoleCommand(cmd, src, dst, arg) -- called when a command (message starts with\) is entered or arrived. cmd = commad, src = message from name, dst = message to name, arg = first argument
    ui.onUpdate() -- called on each frame. please use setInterval or setTimeout instead.

    Timers:
    int id = setInterval(interval, callback) -- executes callback each interval seconds
    int id = setTimeout(timeout, callback) -- executes callback when timeout is reached
    cancelTimer(id) -- cancel a timer

    Label:
    int id = createLabel(left, top, width, height, text) -- creates a new label. labels are not shown, so you can create your ui without distortion. left and top can be "c", that means center
    table label = getLabel(index) -- returns the label
    table text = getLabelText(index) -- returns the text object of the label. (current returns string -- text not working, yet)
    setLabelString(index, string) -- sets the caption of the labels text object (currently use setLabelText(index, text))
    removeLabel(index) -- removes a label
    showLabel(index) -- set labels visibility to true
    hideLabel(index) -- set labels visibility to false
    moveLabelBy(index, x, y) -- move label relative to its current position. x and y are deltas
    moveLabelTo(index, x, y) -- move label to the position given by x and y

    ui.label, ui.text and ui.timer can also be accessed directly.

    client.started -- time when the client was started
    client.fps -- shows current fps
    client.accuracy --
    client.screen.width -- width of client window
    client.screen.heigh -- height of client window
    client.screen.isFullScreen -- tells if client is used in fullscreen mode

    player.name -- Name of the logged in avatar
    player.x() -- returns x position of the avatar
    player.y() -- returns y position of the avatar
    player.z() -- returns z position of the avatar
    player.health() -- returns current health of the avatar
    player.focus() -- returns current focus of the avatar
    player.stat(index) -- return stat info. stat.name = name of stat, stat.number = number of stat, stat.value = value of stat, stat.description = description of stat (not working in 885)

    player.name is set when the player send a message from the console/chat window. it may be used to check if the sender of a message is the player
    As long no player.name is set, ui.onConsoleCommand is not invoked.
    Usually typing \command in the console will set the player.name. so ui.onConsoleCommand should work without problems.

    Things todo:
    getting text object to work
    windows containing objects -> labels, progressbar, statusbar, table and giving the window a z-index
    notify object
    caching stat name/number for name / number lookup
    client.language
    ...
     
    Last edited: Nov 14, 2019
    StarLord, Jaesun, Rentier and 2 others like this.
  4. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Thx.

    player.focus is fixed

    I want to add a progress object, that takes a percentage value and a text. May also a utility function to get the percent value from current and max value.
    In your example there are many possible uses of player.stat() feel free to play a little with it.
    I can add player.currentHealth and player.TotalHealth to the player object (same for focus). May also player.onHealthChange / player.onStatChange.
     
    Drake Aedus likes this.
  5. Drake Aedus

    Drake Aedus Avatar

    Messages:
    542
    Likes Received:
    890
    Trophy Points:
    75
    Gender:
    Male
    So, for instance?

    str = player.stat(46)

    str.number -- 46
    str.name -- Strength
    str.value
    -- str.description

    Like this?
     
  6. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    str = player.stat(46).value -- if only the value is needed

    str = player.stat(46) returns the table/object and results in:
    str.name -- the name of the stat
    str.number -- the number of the stat
    str.value -- the value of the stat
     
    StarLord, Jaesun and Drake Aedus like this.
  7. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota start
    -- libsota.0.3.1 by Catweazle Waldschrath
    client = {
        started = os.time(),
        fps = 0,
        accuracy = 0,
        _frame = 0,
        _ts = os.time(),
        screen = nil,
        _statEnum = {},
    }
    player = {
        name = nil,
        x = function() return ShroudPlayerX end,
        y = function() return ShroudPlayerY end,
        z = function() return ShroudPlayerZ end,
        health = function() return { total = player.stat(30).value, current = player.stat(14).value, percentage = 0 } end,
        focus = function() return { total = player.stat(27).value, current = player.stat(13).value, percentage = 0 } 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 = ShroudGetStatDescriptionByNumber(index)
            end
            return ret
        end,
    }
    ui = {
        timer = {
            _ts = os.time(),
            _granulaty = 0.120,
            create = function(timeout, once, callback)
                local index = #ui.timer + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer[index] = {
                    index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    func = callback,
                }
                return index
            end,
        },
    
        text = {
            create = function(string)
                local index = #ui.text + 1
    
                ui.text = {
                    value = string,
                    format = "%s",
                    bold = false,
                    italic = false,
                    color = "",
                    size = 0,
                }
                return index
            end,
            get = function(index)
                local t = ui.text[index]
    
                t.format = "%s"
                if t.bold then
                    t.format = "<b>"..t.format.."</b>"
                end
                if t.italic then
                    t.format = "<i>"..t.format.."</i>"
                end
                if t.size > 0 then
                    t.format = "<size="..t.size..">"..t.format.."</size>"
                end
                if t.color != "" then
                    t.format = "<color="..t.color..">"..t.format.."</color>"
                end
    
                return string.format(t.format, t.value)
            end
        },
    
        label = {
            create = function(left, top, width, height, string)
                local index = #ui.label + 1
    
                if left == "c" then left = (client.screen.width - width) / 2 end
                if top == "c" then top = (client.screen.height - height) / 2 end
    
                ui.label[index] = {
                    left = left,
                    top = top,
                    width = width,
                    height = height,
                    text = string,
                    visible = false,
                }
                return index
            end
        },
    }
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.create(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.create(interval, false, callback)
    end
    
    function cancelTimer(index)
        ui.timer[index] = nil;
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, text)
        return ui.label.create(left, top, width, height, text)
    end
    
    function getLabel(index)
        return ui.label[index]
    end
    
    --[[function getLabelText(index)
        return ui.text[ui.label[index].text]
    end
    
    function setLabelString(index, string)
        ui.text[ui.label[index].text].value = string
    end]]
    function getLabelText(index)
        return ui.label[index].text
    end
    function setLabelText(index, text)
        ui.label[index].text = text
    end
    
    function removeLabel(index)
        ui.label[index] = nil
    end
    
    function showLabel(index)
        ui.label[index].visible = true
    end
    
    function hideLabel(index)
        ui.label[index].visible = false
    end
    
    function moveLabelBy(index, x, y)
        ui.label[index].left = ui.label[index].left + x
        ui.label[index].top = ui.label[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label[index].left = x
        ui.label[index].top = y
    end
    
    
    
    -- shroud hooks
    
    function ShroudOnUpdate()
    
        -- init
        if not client.screen then
            client.screen = {
                width = ShroudGetScreenX(),
                height = ShroudGetScreenY(),
                isFullScreen = ShroudGetFullScreen(),
            }
            local i
            for i=0, ShroudGetStatCount()-1, 1 do
                local name = ShroudGetStatNameByNumber(i)
                client._statEnum[tostring(name)] = i
                client._statEnum[i] = name
            end
            if ui.onInit then ui.onInit() end
        end
    
        -- client stats
        client._frame = client._frame + 1;
        if os.time() - client._ts >= 1 then
            client.fps = client._frame;
            client._frame = 0;
            client.accuracy = (os.time() - client._ts - 1) * 100
            client._ts = os.time();
            ui.timer._granulaty = 0.120 - (client.accuracy / 100)
        end
    
        -- timers
        if os.time() - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = os.time()
            for _,t in next, ui.timer do
                if type(t) == "table" and os.time() >= t.time then
                    if t.interval then
                        t.time = os.time() + t.interval
                    else
                        ui.timer[t.index] = nil;
                    end
                    t.func();
                end
            end
        end
    
        if ui.onUpdate then ui.onUpdate() end
    end
    
    function ShroudOnGUI()
        for _,l in next, ui.label do
            if type(l) == "table" and l.visible then
                ShroudGUILabel(l.left, l.top, l.width, l.height, l.text);
            end
        end
        --ShroudGUILabel(30,40,512,24, string.format("FPS: %d, Ungenauigkeit: %f ms", client.fps, client.accuracy));
    end
    
    function ShroudOnConsoleInput(type, src, msg)
        --ConsoleLog(type .. "|" .. src .. "|" .. msg)
        local s, dst, msg = string.match(msg, "^(.-) to (.-) %[.-:%s*(.*)")
        local cmd, arg = string.match(msg, "^\\(%w+)%s*(%w*)");
        if not player.name and dst == "everyone" then player.name = src end
        if src == "" then src = s end
        if cmd and player.name and ui.onConsoleCommand then
            ui.onConsoleCommand(cmd, src, dst, arg)
        elseif ui.onConsoleInput then
            ui.onConsoleInput(type, src, dst, msg)
        end
    end
    -- libsota end
    

    player.stat -- can now be retrieved by either using name or number
    health = player.health()
    health.current -- current health
    health.total -- health when healthy
    focus = player.focus()
    focus.current -- current focus
    focus.total -- focus when full
     
    StarLord, Jaesun and Rentier like this.
  8. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.3.2 by Catweazle Waldschrath
    
    client = {
        started = os.time(),
        fps = 0,
        accuracy = 0,
        _frame = 0,
        _ts = os.time(),
        screen = nil,
        _statEnum = {},
    }
    scene = {
        name = function() return ShroudGetCurrentSceneName() end,
        maxPlayer = function() return ShroudGetCurrentSceneMaxPlayerCount() end,
        isPvp = function() return ShroudGetCurrentSceneIsPVP() end,
        isPot = function() return ShroudGetCurrentSceneIsPOT() end,
    }
    player = {
        name = nil,
        x = function() return ShroudPlayerX end,
        y = function() return ShroudPlayerY end,
        z = function() return ShroudPlayerZ end,
        health = function() return { total = player.stat(30).value, current = player.stat(14).value, percentage = 0 } end,
        focus = function() return { total = player.stat(27).value, current = player.stat(13).value, percentage = 0 } end,
        --xp = function() return { producer = ShroudGetPooledProducerExperience(), adventurer = ShroudGetPooledAdventurerExperience() } end,
        xp = function() return { producer = 10, adventurer = 10 } 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 = ShroudGetStatDescriptionByNumber(index)
            end
            return ret
        end,
    }
    ui = {
        timer = {
            _ts = os.time(),
            _granulaty = 0.120,
            create = function(timeout, once, callback)
                local index = #ui.timer + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer[index] = {
                    index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    func = callback,
                }
                return index
            end,
        },
     
        handler = {
            add = function(name, callback)
                local index = #ui.handler + 1
    
                ui.handler[index] = {
                    index = index,
                    name = name,
                    func = callback,
                }
                return index
            end,
            remove = function(index)
                ui.handler[index] = nil
            end,
            invoke = function(name, ...)
                for _,h in next, ui.handler do
                    if type(h) == "table" and h.name == name then
                        h.func(...)
                    end
                end
            end,
        },
        onInit = function(callback) ui.handler.add("_init", 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,
     
        text = {
            style = function(string, styles)
                if styles.bold then string = "<b>"..string.."</b>" end
                if styles.italic then string = "<i>"..string.."</i>" end
                if styles.size and styles.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
                if styles.color and #styles.color == 7 then string = "<color="..style.color..">"..string.."</color>" end
                return string
            end
        },
     
        rect = {
            create = 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
        },
    
        label = {
            create = function(left, top, width, height, caption)
                local index = #ui.label + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.label[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    text = caption,
                    visible = false,
                }
                return index
            end
        },
     
        window = {
            create = function(left, top, width, height, title)
                local index = #ui.window + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.window[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    title = title,
                    visible = false,
                }
                return index
            end,
     
            _draw = function(w)
                local cw = w.width / 9.5
                local ch = w.height / 16.67
                local border = "╔" .. string.rep("═", cw) .. "╗" .. string.rep("\n║".."<color=#000000>"..string.rep("▒", cw).."</color>".."║", ch) .. "\n╚" .. string.rep("═", cw) .. "╝";
                --ShroudGUILabel(w.left, w.top, w.width, w.height, "<color=#2e3436>"..string.rep("▒", 1600).."</color>");
                ShroudGUILabel(w.left-3, w.top-5, w.width, w.height, "<color=#edd400>"..border.."</color>");
                ShroudGUILabel(w.left+10, w.top+10, w.width, w.height, "<b>"..w.title.."</b>");
            end,
        },
    }
    
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.create(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.create(interval, false, callback)
    end
    
    function cancelTimer(index)
        ui.timer[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, text)
        return ui.label.create(left, top, width, height, text)
    end
    
    function getLabel(index)
        return ui.label[index]
    end
    
    function getLabelText(index)
        return ui.label[index].text
    end
    
    function setLabelText(index, caption)
        ui.label[index].text = string
    end
    
    function removeLabel(index)
        ui.label[index] = nil
    end
    
    function showLabel(index)
        ui.label[index].visible = true
    end
    
    function hideLabel(index)
        ui.label[index].visible = false
    end
    
    function moveLabelBy(index, x, y)
        ui.label[index].left = ui.label[index].left + x
        ui.label[index].top = ui.label[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label[index].left = x
        ui.label[index].top = y
    end
    
    
    -- other utility functions
    function log(message)
        for l in string.gmatch(message, "[^\n]+") do
            ShroudConsoleLog(l)
        end
    end
    
    
    
    -- shroud hooks
    
    function ShroudOnStart()
    
        -- init
        client.screen = {
            width = ShroudGetScreenX(),
            height = ShroudGetScreenY(),
            isFullScreen = ShroudGetFullScreen(),
        }
        local i
        for i=0, ShroudGetStatCount()-1, 1 do
            local name = ShroudGetStatNameByNumber(i)
            client._statEnum[tostring(name)] = i
            client._statEnum[i] = name
        end
        --player.name = ShroudGetPlayerName()
     
        ui.handler.invoke("_init")
    end
    
    function ShroudOnUpdate()
    
        -- client stats
        client._frame = client._frame + 1;
        if os.time() - client._ts >= 1 then
            client.fps = client._frame;
            client._frame = 0;
            client.accuracy = (os.time() - client._ts - 1) * 100
            client._ts = os.time();
            ui.timer._granulaty = 0.120 - (client.accuracy / 100)
        end
    
        -- timers
        if os.time() - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = os.time()
            for _,t in next, ui.timer do
                if type(t) == "table" and t.enabled and os.time() >= t.time then
                    if t.interval then
                        t.time = os.time() + t.interval
                    else
                        ui.timer[t.index] = nil
                    end
                    if t.func() then ui.timer[t.index] = nil end
                end
            end
        end
    
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
        for _,l in next, ui.label do
            if type(l) == "table" and l.visible then
                ShroudGUILabel(l.left, l.top, l.width, l.height, l.text);
            end
        end
        for _,w in next, ui.window do
            if type(w) == "table" and w.visible then
                ui.window._draw(w);
            end
        end
        --ShroudGUILabel(30,40,512,24, string.format("FPS: %d, Ungenauigkeit: %f ms", client.fps, client.accuracy));
    end
    
    function ShroudOnConsoleInput(type, src, msg)
        --ConsoleLog(type .. "|" .. src .. "|" .. msg)
        local s, dst, msg = string.match(msg, "^(.-) to (.-) %[.-:%s*(.*)$")
        local cmd, arg = string.match(msg, "^\\(%w+)%s*(.*)$");
        if not player.name and dst == "everyone" then player.name = src end
        if src == "" then src = s end
        if cmd and player.name then
            ui.handler.invoke("_consoleCommand", cmd, src, dst, arg)
        else
            ui.handler.invoke("_consoleInput", type, src, dst, msg)
        end
    end
    [/SPOILER]

    needs QA build 886

    There is a problem with QA 886 regarding ShroudOn.. and ShroudRegisterPeriodic. The handlers and periodics are called more then one time. see
    Please add the following lines to each script that is loaded by the shroud lua interpreter to avoid strange problems/behaviors and don't use ShroudRegisterPeriodic:
    Code:
    -- kill hooks
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart() end
    
    You can use the handlers and timers from libsota instead, they do not have that problem.


    Handlers are now used like the timers. This allows that more then one script can use the handlers and timers.
    Aka. functions are subscribing to a handler.
    Code:
    -- ui.onInit(function() <function body> end)
    --
    -- setInterval(interval, function() <function body> end)
    --
    -- or
    --
    -- function myInit() <function body> end
    -- ui.onInit(myInit)
    --
    -- function myPeriodic() <function body> end
    -- setInterval(interval, myPeriodic)
    --
    
    
    ui.onInit(function()
    -- do the init stuff
    end)
    
    ui.onConsoleCommand(function(command, sender, receiver, tail)
    -- handle console input starting with \
    -- tail is the remainder after the command
    end)
    
    ui.onConsoleInput(function(channel, sender, receiver, message)
    -- handle all other console input
    end)
    
    ui.onUpdate(function()
    -- called on every frame. Please use setInterval or setTimeout instead.
    end)
    
    setInterval(interval, function()
    -- handle peridioc things
    end)
    
    setTimeout(timeout, function()
    -- handle something after timeout is reached
    end)
    

    You can also create your own handlers if you need
    Code:
    --
    -- int ui.handler.add(name, callback)
    --      add a callback to a named event. Returns the index of the callback
    -- ui.handler.remove(index)
    --      removes a callback
    -- ui.handler.invoke(name, ...)
    --      invokes all callbacks that has subscribed to the named event
    --
    
    -- Creating a handler for the "local" chat where other scripts may subscribe to
    -- The handler is called: onLocalChat
    
    -- helper function
    onLocalChat(callback) ui.handler.add("onLocalChat", callback) end
    
    -- catch console input and invoke the user created handler when necessary
    ui.onConsoleInput(function(channel, sender, receiver, message)
         if channel == "Local" then
              ui.handler.invoke("onLocalChat", sender, receiver, message)
         end
    end)
    
    -- the handler can be used in the same script or in any other script
    -- subscribe and function in one step
    onLocalChat(function(sender, receiver, message)
       -- do stuff here
    end)
    
    
    ---- in another script
    
    -- function to handle local chat events
    function myLocalChatInput(sender, receiver, message)
       -- do other stuff here
    end
    
    -- subscripe to the handler
    onLocalChat(myLocalChatInput)
    
    
    -- When the "onLocalChat" event is invoked the function of both handlers are executed.
    
    
    

    Timers can now be paused and resumed. This can be used when the function(s) are not used at the moment because the window/label is hidden. But needed again when the window/label is visible again.
    Timers can now be canceled when the return value is true (or any other value then nil, false or nothing).

    pauseTimer(index) -- pauses a timer
    resumeTimer(index) -- resumes a timer

    player.stat includes the stat description since QA 886

    A scene object was added:
    scene.name() -- returns the name of the current scene
    scene.maxPlayer() -- gives the max. number of players allowed in this instance
    scene.isPvp() -- yes
    scene.isPot() -- says if the player is in a POT or not

    ui.label.create -- left, top, width and height accepting also the values nil and a percantage value (given as string with a percante sign at the end, eg: "50%" ).
    nil for left and top means center, for width and height it means standard values
    percentage values for width and height means as much percent from screen.height / screen.width
    for left and top positive values means as many percent away from top or left side of screen, negative values means as many percent away from the right side or the bottom of screen.

    the window object is not finished yet.

    Since QA 886 libsota needs not to be pasted in your code. All .lua files in <datafolder>/Lua are loaded. So you may save the example into another file.
    Code:
    -- Mudmenu by Drake Aedus
    -- @see https://www.shroudoftheavatar.com/forum/index.php?threads/testing-text-info-window-qa-win-64-885.160145/#post-1266059
    
    -- initialize our variables
    ui.onInit(function()
           x = player.stat(14).value
           y = player.stat(30).value
           str = player.stat(46).value
           dex = player.stat(22).value
           int = player.stat(32).value
           ea_pw = player.stat(159).value
           ea_rs = player.stat(331).value
           ai_pw = player.stat(160).value
           ai_rs = player.stat(332).value
           fi_pw = player.stat(157).value
           fi_rs = player.stat(329).value
           wa_pw = player.stat(158).value
           wa_rs = player.stat(330).value
           su_pw = player.stat(161).value
           su_rs = player.stat(333).value
           mo_pw = player.stat(162).value
           mo_rs = player.stat(334).value
           li_pw = player.stat(155).value
           li_rs = player.stat(327).value
           de_pw = player.stat(156).value
           de_rs = player.stat(328).value
           ch_pw = player.stat(163).value
           ch_rs = player.stat(335).value
           ma_pw = player.stat(439).value
           ma_rs = player.stat(336).value
    
        -- MUD style menu :P
        mudmenu = {
            lblBorder1 = createLabel(250,40,240,600,"╔════════════════════╗\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n╚════════════════════╝"),
            lblBorder2 = createLabel(439,55,240,600,"║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║"),
    
            lblHealthbar = createLabel(265,55,180,580,""),
            lblStatPower = createLabel(335,190,50,300,""),
            lblStatResist = createLabel(395,190,50,300,""),
        }
    end)
    
    
    -- lets update our healthbar about once a second...
    setInterval(1, function()
        x = player.stat(14).value   -- CurrentHealth
        y = player.stat(30).value   -- Health
        z = math.floor((x/y) * 20)       -- how many bubbles
    
        ui.label[mudmenu.lblHealthbar].text = string.format("Health: %d/%d\n"..string.rep("■",z)..string.rep("□",20-z).."\n\nDate: "..os.date().."\n\nStr: %d   Dex: %d   Int: %d\n\n               -Power-   -Resist-\nEarth \nAir \nFire \nWater \nSun \nMoon \nLife \nDeath \nChaos \nMagic ",x,y,str,dex,int)
    
    end)
    
    
    -- lets update our stats less frequently
    setInterval(10, function()
           str = player.stat(46).value
           dex = player.stat(22).value
           int = player.stat(32).value
           ea_pw = player.stat(159).value
           ea_rs = player.stat(331).value
           ai_pw = player.stat(160).value
           ai_rs = player.stat(332).value
           fi_pw = player.stat(157).value
           fi_rs = player.stat(329).value
           wa_pw = player.stat(158).value
           wa_rs = player.stat(330).value
           su_pw = player.stat(161).value
           su_rs = player.stat(333).value
           mo_pw = player.stat(162).value
           mo_rs = player.stat(334).value
           li_pw = player.stat(155).value
           li_rs = player.stat(327).value
           de_pw = player.stat(156).value
           de_rs = player.stat(328).value
           ch_pw = player.stat(163).value
           ch_rs = player.stat(335).value
           ma_pw = player.stat(439).value
           ma_rs = player.stat(336).value
    
           ui.label[mudmenu.lblStatPower].text = string.format("%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f",ea_pw,ai_pw,fi_pw,wa_pw,su_pw,mo_pw,li_pw,de_pw,ch_pw,ma_pw)
           ui.label[mudmenu.lblStatResist].text = string.format("%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f",ea_rs,ai_rs,fi_rs,wa_rs,su_rs,mo_rs,li_rs,de_rs,ch_rs,ma_rs)
    
    end)
    
    
    -- show and hide using console
    ui.onConsoleCommand(function(cmd, src, dst, arg)
        if cmd == "mudmenu" then
            if not arg then
                for _,l in next, mudmenu do
                    ui.label[l].visible = not ui.label[l].visible
                end
            elseif arg == "show" or arg == "on" then
                for _,l in next, mudmenu do
                    showLabel(l)
                end
            elseif arg == "hide" or arg == "off" then
                for _,l in next, mudmenu do
                    hideLabel(l)
                end
            else
                log([[
                    mudmenu [{show|on|hide|off}]:
                    - without argument toggle visiblity
                    - show or on show mudmenu
                    - hide or off hides mudmenu
                ]])
            end
        end
    end)
    
    
    
    -- kill hooks
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart() end
    
     
    Last edited: Nov 15, 2019
  9. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.3.3 by Catweazle Waldschrath
    
    client = {
        started = os.time(),
        fps = 0,
        accuracy = 0,
        _frame = 0,
        _ts = os.time(),
        screen = nil,
        _statEnum = {},
        _statDescr = {},
        --_queue = {},
        _keyStrokes = {},
    }
    scene = {
        name = function() return ShroudGetCurrentSceneName() end,
        maxPlayer = function() return ShroudGetCurrentSceneMaxPlayerCount() end,
        isPvp = function() return ShroudGetCurrentSceneIsPVP() end,
        isPot = function() return ShroudGetCurrentSceneIsPOT() end,
    }
    player = {
        name = nil,
        flag = nil,
        isPvp = function() local ret=false; if player.flag and player.flag == "PVP" then ret = true end return ret end,
        x = function() return ShroudPlayerX end,
        y = function() return ShroudPlayerY end,
        z = function() return ShroudPlayerZ end,
        health = function() return { total = player.stat(30).value, current = player.stat(14).value, percentage = 0 } end,
        focus = function() return { total = player.stat(27).value, current = player.stat(13).value, percentage = 0 } end,
        --xp = function() return { producer = ShroudGetPooledProducerExperience(), adventurer = ShroudGetPooledAdventurerExperience() } end,
        xp = function() return { producer = -999, adventurer = -999 } 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.120,
            create = function(timeout, once, callback)
                local index = #ui.timer + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer[index] = {
                    index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    func = callback,
                }
                return index
            end,
        },
     
        handler = {
            add = function(name, callback)
                local index = #ui.handler + 1
    
                ui.handler[index] = {
                    index = index,
                    name = name,
                    func = callback,
                }
                return index
            end,
            remove = function(index)
                ui.handler[index] = nil
            end,
            invoke = function(name, ...)
                for _,h in next, ui.handler do
                    if type(h) == "table" and h.name == name then
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.func(...)
                    end
                end
            end,
        },
        onInit = function(callback) ui.handler.add("_init", 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,
     
        text = {
            style = function(string, styles)
                if styles.bold then string = "<b>"..string.."</b>" end
                if styles.italic then string = "<i>"..string.."</i>" end
                if styles.size and styles.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
                if styles.color and #styles.color == 7 then string = "<color="..style.color..">"..string.."</color>" end
                return string
            end
        },
        
        rect = {
            create = 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
        },
    
        label = {
            create = function(left, top, width, height, caption)
                local index = #ui.label + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.label[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    text = caption,
                    visible = false,
                }
                return index
            end
        },
     
        window = {
            create = function(left, top, width, height, title)
                local index = #ui.window + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.window[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    title = title,
                    visible = false,
                }
                return index
            end,
        
            _draw = function(w)
                local cw = w.width / 9.5
                local ch = w.height / 16.67
                local border = "╔" .. string.rep("═", cw) .. "╗" .. string.rep("\n║".."<color=#000000>"..string.rep("▒", cw).."</color>".."║", ch) .. "\n╚" .. string.rep("═", cw) .. "╝";
                --ShroudGUILabel(w.left, w.top, w.width, w.height, "<color=#2e3436>"..string.rep("▒", 1600).."</color>");
                ShroudGUILabel(w.left-3, w.top-5, w.width, w.height, "<color=#edd400>"..border.."</color>");
                ShroudGUILabel(w.left+10, w.top+10, w.width, w.height, "<b>"..w.title.."</b>");
            end,
        },
    }
    
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.create(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.create(interval, false, callback)
    end
    
    function cancelTimer(index)
        ui.timer[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, text)
        return ui.label.create(left, top, width, height, text)
    end
    
    function getLabel(index)
        return ui.label[index]
    end
    
    function getLabelText(index)
        return ui.label[index].text
    end
    
    function setLabelText(index, caption)
        ui.label[index].text = string
    end
    
    function removeLabel(index)
        ui.label[index] = nil
    end
    
    function showLabel(index)
        ui.label[index].visible = true
    end
    
    function hideLabel(index)
        ui.label[index].visible = false
    end
    
    function moveLabelBy(index, x, y)
        ui.label[index].left = ui.label[index].left + x
        ui.label[index].top = ui.label[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label[index].left = x
        ui.label[index].top = y
    end
    
    
    -- other utility functions
    function ui.consoleLog(message)
        for l in string.gmatch(message, "[^\n]+") do
            ShroudConsoleLog(l)
        end
    end
    function ui.registerKey(...)
        local key = {...}
        local callback = key[#key]; key[#key] = nil
        local name = key[#key]; key[#key] = nil
        if not client._keyStrokes[tostring(name)] then
            client._keyStrokes[tostring(name)] = {}
        end
        local id = #client._keyStrokes[name] + 1
        client._keyStrokes[name][id] = {
            index = index,
            name = name,
            key = key,
            callback = callback,
        }
        return name, id
    end
    
    
    
    -- shroud hooks
    
    function ShroudOnStart()
    
        -- init
        client.screen = {
            width = ShroudGetScreenX(),
            height = ShroudGetScreenY(),
            isFullScreen = ShroudGetFullScreen(),
        }
        local i
        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
        player.name, player.flag = ShroudGetPlayerName():match("^(.-)%[(.-)%]")
    
        ui.handler.invoke("_init")
    end
    
    function ShroudOnUpdate()
    
        -- client stats
        client._frame = client._frame + 1;
        if os.time() - client._ts >= 1 then
            client.fps = client._frame;
            client._frame = 0;
            client.accuracy = (os.time() - client._ts - 1) * 100
            client._ts = os.time();
            ui.timer._granulaty = 0.120 - (client.accuracy / 100)
        end
    
        -- queued callbacks
        --[[for i,f in next, client._queue do
            f()
            client._queue[i] = nil
        end]]
    
        -- timers
        if os.time() - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = os.time()
            for _,t in next, ui.timer do
                if type(t) == "table" and t.enabled and os.time() >= t.time then
                    if t.interval then
                        t.time = os.time() + t.interval
                    else
                        ui.timer[t.index] = nil
                    end
                    if t.func() then ui.timer[t.index] = nil end
                end
            end
        end
    
        -- check key strokes
        for ku,r in next, client._keyStrokes do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.key do
                        invoke = invoke and  kd == ku or ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
    
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
        for _,l in next, ui.label do
            if type(l) == "table" and l.visible then
                ShroudGUILabel(l.left, l.top, l.width, l.height, l.text);
            end
        end
        for _,w in next, ui.window do
            if type(w) == "table" and w.visible then
                ui.window._draw(w);
            end
        end
        --ShroudGUILabel(30,40,512,24, string.format("FPS: %d, Ungenauigkeit: %f ms", client.fps, client.accuracy));
    end
    
    function ShroudOnConsoleInput(type, src, msg)
        --ConsoleLog(type .. "|" .. src .. "|" .. msg)
        local s, dst, msg = string.match(msg, "^(.-) to (.-) %[.-:%s*(.*)$")
        if src == "" then src = s end
        if msg:byte() == 92 then
            local cmd, arg = string.match(msg, "^\\(%w+)%s*(.*)$")
            setTimeout(0, function() ui.handler.invoke("_consoleCommand", cmd, src, dst, arg) end)
        else
            setTimeout(0, function() ui.handler.invoke("_consoleInput", type, src, dst, msg) end)
        end
    end
    

    needs QA build 887
    I have not checked if the problem introduced in 886 still exists. You may need to add the following code to your script(s):
    Code:
    -- kill hooks
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart() end
    

    player.name is now retrived directly from the client
    player.flag -- returns the flag (like PVP)
    player.isPvp -- returns true when player is flagged PvP

    ui.consoleLog -- logs to the console supporting multiline messages (\n)

    ui.registerKey -- registers a function that is called when the given key(s) are pressed
    Code:
    -- ui.registerKey (key, key, ...., callback)
    --
    -- ui.registerKey("RightAlt", "M", function() <func body> end)
    --
    -- or
    --
    -- myFunctionInvokedByKeystroke()
    -- <func body>
    -- end
    -- ui.registerKey("LeftAlt", "M", myFunctionInvokedByKeystroke)
    --
    ui.registerKey("F", function()
        ui.consoleLog("toggle friend list")
    end)
    
    For key names see here: https://docs.unity3d.com/ScriptReference/KeyCode.html


    ui.onConsoleInput and ui.onConsoleCommand are now called in a non blocking manner. To prevent blocking user input. Also cleaned up this handler, player.name does not need to be retrieved from console input anymore.

    stat.descriptions are now cached this results to fewer calls to the client. (may more important for @Chris)

    Code:
    -- Mudmenu by Drake Aedus
    -- @see https://www.shroudoftheavatar.com/forum/index.php?threads/testing-text-info-window-qa-win-64-885.160145/#post-1266059
    
    -- initialize our variables
    ui.onInit(function()
           x = player.stat(14).value
           y = player.stat(30).value
           str = player.stat(46).value
           dex = player.stat(22).value
           int = player.stat(32).value
           ea_pw = player.stat(159).value
           ea_rs = player.stat(331).value
           ai_pw = player.stat(160).value
           ai_rs = player.stat(332).value
           fi_pw = player.stat(157).value
           fi_rs = player.stat(329).value
           wa_pw = player.stat(158).value
           wa_rs = player.stat(330).value
           su_pw = player.stat(161).value
           su_rs = player.stat(333).value
           mo_pw = player.stat(162).value
           mo_rs = player.stat(334).value
           li_pw = player.stat(155).value
           li_rs = player.stat(327).value
           de_pw = player.stat(156).value
           de_rs = player.stat(328).value
           ch_pw = player.stat(163).value
           ch_rs = player.stat(335).value
           ma_pw = player.stat(439).value
           ma_rs = player.stat(336).value
    
        -- MUD style menu :P
        mudmenu = {
            lblBorder1 = createLabel(250,40,240,600,"╔════════════════════╗\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n╚════════════════════╝"),
            lblBorder2 = createLabel(439,55,240,600,"║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║\n║"),
    
            lblHealthbar = createLabel(265,55,180,580,""),
            lblStatPower = createLabel(335,190,50,300,""),
            lblStatResist = createLabel(395,190,50,300,""),
        }
    end)
    
    
    -- lets update our healthbar about once a second...
    setInterval(1, function()
        x = player.stat(14).value   -- CurrentHealth
        y = player.stat(30).value   -- Health
        z = math.floor((x/y) * 20)       -- how many bubbles
    
        ui.label[mudmenu.lblHealthbar].text = string.format("Health: %d/%d\n"..string.rep("■",z)..string.rep("□",20-z).."\n\nDate: "..os.date().."\n\nStr: %d   Dex: %d   Int: %d\n\n               -Power-   -Resist-\nEarth \nAir \nFire \nWater \nSun \nMoon \nLife \nDeath \nChaos \nMagic ",x,y,str,dex,int)
    
    end)
    
    
    -- lets update our stats less frequently
    setInterval(10, function()
           str = player.stat(46).value
           dex = player.stat(22).value
           int = player.stat(32).value
           ea_pw = player.stat(159).value
           ea_rs = player.stat(331).value
           ai_pw = player.stat(160).value
           ai_rs = player.stat(332).value
           fi_pw = player.stat(157).value
           fi_rs = player.stat(329).value
           wa_pw = player.stat(158).value
           wa_rs = player.stat(330).value
           su_pw = player.stat(161).value
           su_rs = player.stat(333).value
           mo_pw = player.stat(162).value
           mo_rs = player.stat(334).value
           li_pw = player.stat(155).value
           li_rs = player.stat(327).value
           de_pw = player.stat(156).value
           de_rs = player.stat(328).value
           ch_pw = player.stat(163).value
           ch_rs = player.stat(335).value
           ma_pw = player.stat(439).value
           ma_rs = player.stat(336).value
    
           ui.label[mudmenu.lblStatPower].text = string.format("%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f",ea_pw,ai_pw,fi_pw,wa_pw,su_pw,mo_pw,li_pw,de_pw,ch_pw,ma_pw)
           ui.label[mudmenu.lblStatResist].text = string.format("%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f\n%3.2f",ea_rs,ai_rs,fi_rs,wa_rs,su_rs,mo_rs,li_rs,de_rs,ch_rs,ma_rs)
    
    end)
    
    
    -- show and hide using console
    ui.onConsoleCommand(function(cmd, src, dst, arg)
        if cmd == "mudmenu" then
            if not arg or arg == "" then
                for _,l in next, mudmenu do
                    ui.label[l].visible = not ui.label[l].visible
                end
            elseif arg == "show" or arg == "on" then
                for _,l in next, mudmenu do
                    showLabel(l)
                end
            elseif arg == "hide" or arg == "off" then
                for _,l in next, mudmenu do
                    hideLabel(l)
                end
            else
                ui.consoleLog([[
                    mudmenu [{show|on|hide|off}]:
                    - without argument toggle visiblity
                    - show or on show mudmenu
                    - hide or off hides mudmenu
                ]])
            end
        end
    end)
    
    
    -- toggle mudmenu using key stroke
    ui.registerKey("RightAlt", "M", function()
        for _,l in next, mudmenu do
            ui.label[l].visible = not ui.label[l].visible
        end
    end)
    
    
    -- kill hooks
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    function ShroudOnStart() end
    
     
    Last edited: Nov 16, 2019
    Drake Aedus and Browncoat Jayson like this.
  10. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.3.4 by Catweazle Waldschrath
    
    client = {
        started = os.time(),
        fps = 0,
        accuracy = 0,
        _frame = 0,
        _ts = os.time(),
        screen = nil,
        _statEnum = {},
        _statDescr = {},
        --_queue = {},
        _keyStrokes = {},
    }
    scene = {
        name = function() return ShroudGetCurrentSceneName() end,
        maxPlayer = function() return ShroudGetCurrentSceneMaxPlayerCount() end,
        isPvp = function() return ShroudGetCurrentSceneIsPVP() end,
        isPot = function() return ShroudGetCurrentSceneIsPOT() end,
    }
    player = {
        name = "none",
        flag = "",
        isPvp = false,
        x = function() return ShroudPlayerX end,
        y = function() return ShroudPlayerY end,
        z = function() return ShroudPlayerZ end,
        health = function() return { total = player.stat(30).value, current = player.stat(14).value, percentage = 0 } end,
        focus = function() return { total = player.stat(27).value, current = player.stat(13).value, percentage = 0 } end,
        --xp = function() return { producer = ShroudGetPooledProducerExperience(), adventurer = ShroudGetPooledAdventurerExperience() } end,
        xp = function() return { producer = -999, adventurer = -999 } 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.120,
            create = function(timeout, once, callback)
                local index = #ui.timer + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer[index] = {
                    index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    func = callback,
                }
                return index
            end,
        },
        
        handler = {
            add = function(name, callback)
                local index = #ui.handler + 1
    
                ui.handler[index] = {
                    index = index,
                    name = name,
                    func = callback,
                }
                return index    
            end,
            remove = function(index)
                ui.handler[index] = nil
            end,
            invoke = function(name, ...)
                for _,h in next, ui.handler do
                    if type(h) == "table" and h.name == name then
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.func(...)
                    end
                end
            end,
        },
        onInit = function(callback) ui.handler.add("_init", 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,
        
        text = {
            style = function(string, styles)
                if styles.bold then string = "<b>"..string.."</b>" end
                if styles.italic then string = "<i>"..string.."</i>" end
                if styles.size and styles.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
                if styles.color and #styles.color == 7 then string = "<color="..style.color..">"..string.."</color>" end
                return string
            end
        },
            
        rect = {
            create = 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
        },
    
        label = {
            create = function(left, top, width, height, caption)
                local index = #ui.label + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.label[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    text = caption,
                    visible = false,
                }
                return index
            end
        },
        
        window = {
            create = function(left, top, width, height, title)
                local index = #ui.window + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.window[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    title = title,
                    visible = false,
                }
                return index
            end,
            
            _draw = function(w)
                local cw = w.width / 9.5
                local ch = w.height / 16.67
                local border = "╔" .. string.rep("═", cw) .. "╗" .. string.rep("\n║".."<color=#000000>"..string.rep("▒", cw).."</color>".."║", ch) .. "\n╚" .. string.rep("═", cw) .. "╝";
                --ShroudGUILabel(w.left, w.top, w.width, w.height, "<color=#2e3436>"..string.rep("▒", 1600).."</color>");
                ShroudGUILabel(w.left-3, w.top-5, w.width, w.height, "<color=#edd400>"..border.."</color>");
                ShroudGUILabel(w.left+10, w.top+10, w.width, w.height, "<b>"..w.title.."</b>");
            end,
        },
    }
    
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.create(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.create(interval, false, callback)
    end
    
    function cancelTimer(index)
        ui.timer[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, text)
        return ui.label.create(left, top, width, height, text)
    end
    
    function getLabel(index)
        return ui.label[index]
    end
    
    function getLabelText(index)
        return ui.label[index].text
    end
    
    function setLabelText(index, caption)
        ui.label[index].text = string
    end
    
    function removeLabel(index)
        ui.label[index] = nil
    end
    
    function showLabel(index)
        ui.label[index].visible = true
    end
    
    function hideLabel(index)
        ui.label[index].visible = false
    end
    
    function moveLabelBy(index, x, y)
        ui.label[index].left = ui.label[index].left + x
        ui.label[index].top = ui.label[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label[index].left = x
        ui.label[index].top = y
    end
    
    
    -- other utility functions
    function ui.consoleLog(message)
        for l in string.gmatch(message, "[^\n]+") do
            ShroudConsoleLog(l)
        end
    end
    function ui.registerKey(...)
        local key = {...}
        local callback = key[#key]; key[#key] = nil
        local name = key[#key]; key[#key] = nil
        if not client._keyStrokes[tostring(name)] then
            client._keyStrokes[tostring(name)] = {}
        end
        local id = #client._keyStrokes[name] + 1
        client._keyStrokes[name][id] = {
            index = index,
            name = name,
            key = key,
            callback = callback,
        }
        return name, id
    end
    function ui._getPlayerName()
        player.name = ShroudGetPlayerName()
        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 == "PVP"
            --player.isAfk = player.flag and player.flag == "AFK"
        end
    end
    
    
    -- shroud hooks
    
    function ShroudOnStart()
    
        -- init
        client.screen = {
            width = ShroudGetScreenX(),
            height = ShroudGetScreenY(),
            isFullScreen = ShroudGetFullScreen(),
        }
        local i
        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._getPlayerName()
    
        ui.handler.invoke("_init")
    end
    
    function ShroudOnUpdate()
    
        -- client stats
        client._frame = client._frame + 1;
        if os.time() - client._ts >= 1 then
            client.fps = client._frame;
            client._frame = 0;
            client.accuracy = (os.time() - client._ts - 1) * 100
            client._ts = os.time();
            ui.timer._granulaty = 0.120 - (client.accuracy / 100)
        end
    
        -- queued callbacks
        --[[for i,f in next, client._queue do
            f()
            client._queue[i] = nil
        end]]
    
        -- timers
        if os.time() - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = os.time()
            for _,t in next, ui.timer do
                if type(t) == "table" and t.enabled and os.time() >= t.time then
                    if t.interval then
                        t.time = os.time() + t.interval
                    else
                        ui.timer[t.index] = nil
                    end
                    if t.func() then ui.timer[t.index] = nil end
                end
            end
        end
    
        -- check key strokes
        for ku,r in next, client._keyStrokes do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.key do
                        invoke = invoke and  kd == ku or ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
        
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
        for _,l in next, ui.label do
            if type(l) == "table" and l.visible then
                ShroudGUILabel(l.left, l.top, l.width, l.height, l.text);
            end
        end
        for _,w in next, ui.window do
            if type(w) == "table" and w.visible then
                ui.window._draw(w);
            end
        end
        --ShroudGUILabel(30,40,512,24, string.format("FPS: %d, Ungenauigkeit: %f ms", client.fps, client.accuracy));
    end
    
    function ShroudOnConsoleInput(type, src, msg)
        --ConsoleLog(type .. "|" .. src .. "|" .. msg)
    
        -- handle player flag changes
        if player.name == "none" then ui._getPlayerName() end -- player name seems not to be present when ShroudOnStart is invoked
        if (type == "Story" or type == "System") and msg:find("flagged.-PvP") then ui._getPlayerName() end -- oracle = story, oracle head = system
    
        -- parse message
        local s, dst, msg = string.match(msg, "^(.-) to (.-) %[.-:%s*(.*)$")
        if src == "" then src = s end
        if msg:byte() == 92 then
            local cmd, arg = string.match(msg, "^\\(%w+)%s*(.*)$")
            setTimeout(0, function() ui.handler.invoke("_consoleCommand", cmd, src, dst, arg) end)
        else
            setTimeout(0, function() ui.handler.invoke("_consoleInput", type, src, dst, msg) end)
        end
    end
    
    fix for retrieving player name in the case player is not flagged
    fixed player name is none
    fixed player flag and isPvP are not updated when flag is changed on oracle or oracle head
    changed player.isPvP from function to value
    Edit: removed a critical bug
     
    Last edited: Nov 17, 2019
    Drake Aedus and Browncoat Jayson like this.
  11. that_shawn_guy

    that_shawn_guy Bug Hunter

    Messages:
    1,036
    Likes Received:
    2,707
    Trophy Points:
    125
    Location:
    earth... mostly
    I created a function to make it easier to get text near the center of the screen.

    function OffsetCenterLabel(x, y, width, height, str)
    local s, n = string.gsub(str,"<[^>]*>","",n)
    local offset = string.len(s)*3.5
    local newx = (ShroudGetScreenX()/2 - x) - offset
    local newy = ShroudGetScreenY()/2 - y
    ShroudGUILabel(newx, newy, width, height, str)
    end



    You use it like this:

    OffsetCenterLabel(0,-150,512,124, string.format("✿ FPS: %d, Accuracy: %f ms", client.fps, client.accuracy));
    OffsetCenterLabel(0,0,512,124, string.format("1234567890"));
    OffsetCenterLabel(0,15,512,124, string.format("12345678901234567890"));
    OffsetCenterLabel(0,30,512,124, string.format("123456789012345678901234567890"));
    OffsetCenterLabel(0,45,512,124, string.format("1234567890123456789012345678901234567890"));

    To get this:

    [​IMG]

    Feel free to add it to libsota. probably still needs some tweaking too.
     
    Last edited: Nov 16, 2019
  12. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    I delayed the "graphical" part somewhat (windows, buttons and so), but i can add a print or printText function (printText: x,y,string,style). The length of a string is guess working we do not have a function like ShroudGetTextSize (returning width and height).
    Was thinking about using a helper function relativeTo usage similar like this: crateLabel(relativeTo("center", "center", x,y,with,height), text). So the user can set a point on screen where the rect is relative to.
    (rect=relativeTo(), createLabel(rect, text))
     
  13. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.3.5 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        _frame = 0,
        _ts = os.time(),
        _isLoading = false,
        _statEnum = {},
        _statDescr = {},
        --_queue = {},
        _keyStrokes = {},
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        x = function() return ShroudPlayerX end,
        y = function() return ShroudPlayerY end,
        z = function() return ShroudPlayerZ end,
        health = function() return { total = player.stat(30).value, current = player.stat(14).value, percentage = 0 } end,
        focus = function() return { total = player.stat(27).value, current = player.stat(13).value, percentage = 0 } end,
        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.120,
            create = function(timeout, once, callback, ...)
                local index = #ui.timer + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer[index] = {
                    index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    func = callback,
                    userdata = ...,
                }
                return index
            end,
        },
       
        handler = {
            add = function(name, callback)
                local index = #ui.handler + 1
    
                ui.handler[index] = {
                    index = index,
                    name = name,
                    func = callback,
                }
                return index   
            end,
            remove = function(index)
                ui.handler[index] = nil
            end,
            invoke = function(name, ...)
                for _,h in next, ui.handler do
                    if type(h) == "table" and h.name == name then
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.func(...)
                    end
                end
            end,
        },
        --onStart = function(callback) ui.handler.add("_start", callback) end,
        onInit = function(callback) ui.handler.add("_init", 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,
        onSceneChange = function(callback) ui.handler.add("_sceneChange", callback) end,
        onPlayerChange = function(callback) ui.handler.add("_playerChange", callback) end,
       
       
        text = {
            style = function(string, styles)
                if styles.bold then string = "<b>"..string.."</b>" end
                if styles.italic then string = "<i>"..string.."</i>" end
                if styles.underline then string = "<u>"..string.."</u>" end
                if styles.size and styles.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
                if styles.color and #styles.color == 7 then string = "<color="..style.color..">"..string.."</color>" end
                return string
            end
        },
           
        rect = {
            create = 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
        },
    
        label = {
            create = function(left, top, width, height, caption)
                local index = #ui.label + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.label[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    text = caption,
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                }
                return index
            end
        },
       
        window = {
            create = function(left, top, width, height, title)
                local index = #ui.window + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.window[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    title = title,
                    visible = false,
                }
                return index
            end,
           
            _draw = function(w)
                local cw = w.width / 9.5
                local ch = w.height / 16.67
                local border = "╔" .. string.rep("═", cw) .. "╗" .. string.rep("\n║".."<color=#000000>"..string.rep("▒", cw).."</color>".."║", ch) .. "\n╚" .. string.rep("═", cw) .. "╝";
                --ShroudGUILabel(w.left, w.top, w.width, w.height, "<color=#2e3436>"..string.rep("▒", 1600).."</color>");
                ShroudGUILabel(w.left-3, w.top-5, w.width, w.height, "<color=#edd400>"..border.."</color>");
                ShroudGUILabel(w.left+10, w.top+10, w.width, w.height, "<b>"..w.title.."</b>");
            end,
        },
    }
    
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.create(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.create(interval, false, callback)
    end
    
    function cancelTimer(index)
        ui.timer[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, text)
        return ui.label.create(left, top, width, height, text)
    end
    
    function getLabel(index)
        return ui.label[index]
    end
    
    function getLabelText(index)
        return ui.label[index].text
    end
    
    function setLabelText(index, caption)
        ui.label[index].text = string
    end
    
    function removeLabel(index)
        ui.label[index] = nil
    end
    
    function showLabel(index)
        ui.label[index].visible = true
    end
    
    function hideLabel(index)
        ui.label[index].visible = false
    end
    
    function moveLabelBy(index, x, y)
        ui.label[index].left = ui.label[index].left + x
        ui.label[index].top = ui.label[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label[index].left = x
        ui.label[index].top = y
    end
    
    
    -- other utility functions
    function ui.consoleLog(message)
        for l in string.gmatch(message, "[^\n]+") do
            ShroudConsoleLog(l)
        end
    end
    function ui.registerKey(...)
        local key = {...}
        local callback = key[#key]; key[#key] = nil
        local name = key[#key]; key[#key] = nil
        if not client._keyStrokes[tostring(name)] then
            client._keyStrokes[tostring(name)] = {}
        end
        local id = #client._keyStrokes[name] + 1
        client._keyStrokes[name][id] = {
            index = index,
            name = name,
            key = key,
            callback = callback,
        }
        return name, id
    end
    function ui._getPlayerName()
        if player.caption != ShroudGetPlayerName() then
            player.caption = ShroudGetPlayerName()
            player.name = player.caption
            player.flag = ""
            player.isPvp = false
            if string.byte(player.name, #player.name) == 93 then
                player.name, player.flag = string.match(player.name, ("^(.-)(%[.-%])$"))
                player.isPvp = player.flag and string.find(player.flag, "PVP")
                player.isAfk = player.flag and string.find(player.flag, "AFK")
            end
            ui.handler.invoke("_playerChange")
        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("_sceneChange")
        end
    end
    
    -- shroud hooks
    
    function ShroudOnStart()
    
        -- init
        client.timeStarted = os.time() - ShroudTime
        client.screen = {
            width = ShroudGetScreenX(),
            height = ShroudGetScreenY(),
            isFullScreen = ShroudGetFullScreen(),
        }
        local i
        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.handler.invoke("_start")
        ui.handler.invoke("_init")
    end
    
    function ShroudOnUpdate()
    
        if player == "none" then
            --ui.handler.invoke("_init")
            ui._getPlayerName()
        end
    
        -- client stats
        client._frame = client._frame + 1;
        if os.time() - 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 = os.time() - client._ts - 1
            client._ts = os.time()
            scene.timeInScene = os.time() - scene.timeStarted
            ui.timer._granulaty = 0.120 - client.accuracy
        end
    
        -- queued callbacks
        --[[for i,f in next, client._queue do
            f()
            client._queue[i] = nil
        end]]
    
        -- timers
        if os.time() - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = os.time()
            for _,t in next, ui.timer do
                if type(t) == "table" and t.enabled and os.time() >= t.time then
                    if t.interval then
                        t.time = os.time() + t.interval
                    else
                        ui.timer[t.index] = nil
                    end
                    if t.func(t.userdata) then ui.timer[t.index] = nil end
                end
            end
        end
    
        -- check key strokes
        for ku,r in next, client._keyStrokes do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.key do
                        invoke = invoke and  kd == ku or ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
       
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
        local isHitching = os.time() - client._ts >= 5 -- eigentlich 2 sekunden
       
        --[[if isHitching  then
            ShroudGUILabel(client.screen.width / 2 - 240, client.screen.height / 2 - 24, 512, 24, "Kein onUpdate mehr seit "..(os.time() - client._ts).." Sekunden")
        end]]
        if not client._isLoading and os.time() - client._ts >= 10 then
            --ShroudConsoleLog(os.date()..": invoke onLoadScreen")
            client._isLoading = true
        end
    
        for _,l in next, ui.label do
            if type(l) == "table" and l.visible then
                if not isHitching and l.shownInScene then
                    ShroudGUILabel(l.left, l.top, l.width, l.height, l.text)
                elseif isHitching and l.shownInLoadScreen then
                    ShroudGUILabel(l.left, l.top, l.width, l.height, l.text)
                end
            end
        end
        for _,w in next, ui.window do
            if type(w) == "table" and w.visible then
                ui.window._draw(w);
            end
        end
        --ShroudGUILabel(30,40,512,24, string.format("FPS: %d, Ungenauigkeit: %f", client.fps, client.accuracy));
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
        --ShroudConsoleLog(tostring(channel) .. "|" .. tostring(sender) .. "|" .. tostring(message))
       
        ui.timer.create(0, true, function(...)
            --ShroudConsoleLog(tostring(channel) .. "|" .. tostring(sender) .. "|" .. tostring(message))
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and string.find(message, "PvP") then ui._getPlayerName() end -- oracle = story, oracle head = system
    
            -- parse message
            local src, dst, msg = string.match(message, "^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if string.byte(msg) == 92 then
                local cmd, arg = string.match(msg, "^\\(%w+)%s*(.*)$")
                ui.handler.invoke("_consoleCommand", cmd, sender, dst, arg)
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
    
        end, channel, sender, message)
    end
    
    

    Needs QA 888

    The problem with the ShroudOn.. functions are still present in QA 888...

    There are two new events added:
    ui.onPlayerChange - this is invoked when the player name or player flagging changes (oracle, oracle head or entering/leaving pvp scene)
    ui.onSceneChange - this is invoked when the player has changed the scene

    labels are not shown anymore on loading screen, by default.

    reworked ShroudOnConsoleInput handler so it does not block player input, or delay it. tested it with some fire dots on some mobs.

    timers can now have userdata.
    id = ui.timer.create(timeout, once, callback, ...)
    used like this:
    id = ui.timer.create(10, true, function(...) <func body> end, variable1, variable2, variable3)
    The utility functions setTimeout and setInterval does not allow userdata, but that is mostly not needed


    client.timeStarted - tells when the client was started. persist over /lua reload. os.date("%c", client.timeStarted) can be used.
    client.timeInGame - tells how long the player is currently in game in seconds
    client.timeDelta - 1 / timeDelta tells you the fps
    client.fps - tells the frame rate of the lua script

    scene.name - name of the scene the player is currently in
    scene.maxPlayer - tells max number of player
    scene.isPvp - tells if this is a pvp scene
    scene.isPot - tells if this is a pot
    scene.timeInScene - tells how long the player is in the current scene in seconds
    scene.timeToLoad - tell how many seconds it tooks to load the current scene
    scene.timeStarted - tells the timestamp when the player entered the current scene. os.date("%c", scene.timeStarted) turn the timestamp into a human readable format

    player.caption - tells the caption of the playername. like it is shown in the game
    player.name - tells the name of the player without any flags (like [PVP] ...)
    player.flag - tell the flag string (like [AFK][PVP])
    player.isPvp - tells if the player is flagged pvp
    player.x() - returns x position of player
    player.y() - returns y position of player
    player.z() - returns z position of player
    player.health() - returns health values: total = current max health, current = current health
    player.focus() - returns focus values: total = current max focus, current = current focus
    player.xp() - returns xp values: producer = producer xp, adventurer = adventurer xp
    player.stat(index) - returns stat values: name = name of stat, number = number of stat, description = description of stat, value = value of stat. Index can be number or name of stat

    labels:
    left - x pos in pixel
    top - y pos in pixel
    width - width in pixel
    height - height in pixel
    text - string to show
    visible - if the label is visible or not
    shownInScene - this label is shown while in scene when visible is true
    shownInLoadScreen - this label is shown during load screen when visible is true

    ui.window is still delayed, like some other visual stuff
     
    StarLord likes this.
  14. Browncoat Jayson

    Browncoat Jayson Legend of the Hearth

    Messages:
    6,259
    Likes Received:
    13,937
    Trophy Points:
    153
    Spent, pool, or both? I'm assuming pool only, and we would need to do a lookup on each stat, determine the multiplier, and manually calculate the players xp spent, correct?
     
    StarLord likes this.
  15. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    That is what i get from the Shroud-API. It returns the XP the player has in pool. Not the total XP. May someone ask, that the total XP can also be queried?. If @Chris is ok with this, libsota will also tell you the total xp. (xp spent)
     
    StarLord likes this.
  16. Tiina Onir

    Tiina Onir Avatar

    Messages:
    1,053
    Likes Received:
    1,812
    Trophy Points:
    125
    Location:
    Bramble, South Paladis
    Chris has been adding things the game already tracks. If that's not something the game already tracks, it would be a harder one.
     
  17. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.3.6 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        _frame = 0,
        _ts = os.time(),
        _isLoading = false,
        _statEnum = {},
        _statDescr = {},
        --_queue = {},
        _keyStrokes = {},
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        location = function() return { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene.name } end,
        health = function() return { total = player.stat(30).value, current = player.stat(14).value, percentage = 0 } end,
        focus = function() return { total = player.stat(27).value, current = player.stat(13).value, percentage = 0 } end,
        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,
            create = function(timeout, once, callback, ...)
                local index = #ui.timer + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer[index] = {
                    index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    func = callback,
                    userdata = ...,
                }
                return index
            end,
        },
      
        handler = {
            add = function(name, callback)
                local index = #ui.handler + 1
    
                ui.handler[index] = {
                    index = index,
                    name = name,
                    func = callback,
                }
                return index  
            end,
            remove = function(index)
                ui.handler[index] = nil
            end,
            invoke = function(name, ...)
                for _,h in next, ui.handler do
                    if type(h) == "table" and h.name == name then
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.func(...)
                    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,
        onSceneChange = function(callback) ui.handler.add("_sceneChange", callback) end,
        onPlayerChange = function(callback) ui.handler.add("_playerChange", callback) end,
      
      
        text = {
            style = function(string, styles)
                if styles.bold then string = "<b>"..string.."</b>" end
                if styles.italic then string = "<i>"..string.."</i>" end
                if styles.underline then string = "<u>"..string.."</u>" end
                if styles.size and styles.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
                if styles.color and #styles.color == 7 then string = "<color="..style.color..">"..string.."</color>" end
                return string
            end
        },
          
        rect = {
            create = 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
        },
    
        label = {
            create = function(left, top, width, height, caption)
                local index = #ui.label + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.label[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    text = caption,
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                }
                return index
            end
        },
      
        window = {
            create = function(left, top, width, height, title)
                local index = #ui.window + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.window[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    title = title,
                    visible = false,
                }
                return index
            end,
          
            _draw = function(w)
                local cw = w.width / 9.5
                local ch = w.height / 16.67
                local border = "╔" .. string.rep("═", cw) .. "╗" .. string.rep("\n║".."<color=#000000>"..string.rep("▒", cw).."</color>".."║", ch) .. "\n╚" .. string.rep("═", cw) .. "╝";
                --ShroudGUILabel(w.left, w.top, w.width, w.height, "<color=#2e3436>"..string.rep("▒", 1600).."</color>");
                ShroudGUILabel(w.left-3, w.top-5, w.width, w.height, "<color=#edd400>"..border.."</color>");
                ShroudGUILabel(w.left+10, w.top+10, w.width, w.height, "<b>"..w.title.."</b>");
            end,
        },
    }
    
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.create(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.create(interval, false, callback)
    end
    
    function cancelTimer(index)
        ui.timer[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, text)
        return ui.label.create(left, top, width, height, text)
    end
    
    function getLabel(index)
        return ui.label[index]
    end
    
    function getLabelText(index)
        return ui.label[index].text
    end
    
    function setLabelText(index, caption)
        ui.label[index].text = string
    end
    
    function removeLabel(index)
        ui.label[index] = nil
    end
    
    function showLabel(index)
        ui.label[index].visible = true
    end
    
    function hideLabel(index)
        ui.label[index].visible = false
    end
    
    function moveLabelBy(index, x, y)
        ui.label[index].left = ui.label[index].left + x
        ui.label[index].top = ui.label[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label[index].left = x
        ui.label[index].top = y
    end
    
    
    -- other utility functions
    function ui.consoleLog(message)
        for l in string.gmatch(message, "[^\n]+") do
            ShroudConsoleLog(l)
        end
    end
    function ui.registerKey(...)
        local key = {...}
        local callback = key[#key]; key[#key] = nil
        local name = key[#key]; key[#key] = nil
        if not client._keyStrokes[tostring(name)] then
            client._keyStrokes[tostring(name)] = {}
        end
        local id = #client._keyStrokes[name] + 1
        client._keyStrokes[name][id] = {
            index = index,
            name = name,
            key = key,
            callback = callback,
        }
        return name, id
    end
    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("_playerChange")
        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("_sceneChange")
        end
    end
    
    -- shroud hooks
    
    function ShroudOnStart()
        ui.handler.invoke("_start")
    end
    
    function ShroudOnUpdate()
    
        -- init
        if not client.screen then
            client.screen = {
                width = ShroudGetScreenX(),
                height = ShroudGetScreenY(),
                isFullScreen = ShroudGetFullScreen(),
            }
            local i
            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.handler.invoke("_init")
        end
    
        -- client stats
        client._frame = client._frame + 1;
        if os.time() - 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 = os.time() - client._ts - 1
            client._ts = os.time()
            scene.timeInScene = os.time() - 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]]
    
        -- timers
        if os.time() - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = os.time()
            for _,t in next, ui.timer do
                if type(t) == "table" and t.enabled and os.time() >= t.time then
                    if t.interval then
                        t.time = os.time() + t.interval
                    else
                        ui.timer[t.index] = nil
                    end
                    if t.func(t.userdata) then ui.timer[t.index] = nil end
                end
            end
        end
    
        -- check key strokes
        for ku,r in next, client._keyStrokes do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.key do
                        invoke = invoke and  kd == ku or ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
      
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
        local isHitching = os.time() - client._ts >= 5 -- eigentlich 2 sekunden
      
        --[[if isHitching  then
            if not client.screen then
                ShroudGUILabel(30,24,1024,24, "Erster Start: noch kein onUpdate seit "..(os.time() - client._ts).." Sekunden")
            else
                ShroudGUILabel(client.screen.width / 2 - 240, client.screen.height / 2 - 24, 512, 24, "Kein onUpdate mehr seit "..(os.time() - client._ts).." Sekunden")
            end
        end]]
    
        for _,l in next, ui.label do
            if type(l) == "table" and l.visible then
                if not isHitching and l.shownInScene then
                    ShroudGUILabel(l.left, l.top, l.width, l.height, l.text)
                elseif isHitching and l.shownInLoadScreen then
                    ShroudGUILabel(l.left, l.top, l.width, l.height, l.text)
                end
            end
        end
        for _,w in next, ui.window do
            if type(w) == "table" and w.visible then
                ui.window._draw(w);
            end
        end
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
      
        ui.timer.create(0, true, function(...)
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and string.find(message, "PvP") then ui._getPlayerName() end -- oracle = story, oracle head = system
    
            -- parse message
            local src, dst, msg = string.match(message, "^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if string.byte(msg) == 92 then
                local cmd, arg = string.match(msg, "^\\(%w+)%s*(.*)$")
                ui.handler.invoke("_consoleCommand", cmd, sender, dst, arg)
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
    
        end, channel, sender, message)
    end
    
    

    @Tiina Onir reported a bug, that using ShroudOnStart it to early to retrive values from Shroud-API. https://www.shroudoftheavatar.com/forum/index.php?threads/responded-lua-loads-before-player.160294
    In libsota.0.3.6 the start sequence is changed, so when ui.onInit is invoked everything is access able.
    Also added ui.onStart that tells you that are all scripts are loaded, but at this point you can not access all values. Use ui.onInit()

    libsota should load, before all other scripts are loaded. Else you can not subscribe to one of the callbacks. But we can not control in wich order scripts are loaded, und the require function is not implmented...
    Either name libsota.lua 1.libsota.lua so it is loaded first, or wrap ui.onInit in ShroudOnStart() like this:
    Code:
    
    -- wrap in ShroudOnStart to be sure all scripts are loaded and libsota is available
    function ShroudOnStart()
    
        -- subscripe to onInit event.
        ui.onInit(function()
    
            lblHealThySelf = ui.label.create(800, 80, 300, 100, "<color=red><size=50>Heal thy self!</size></color>")
    
            setInterval(1, function()
                health = player.health()
                ui.label[lblHealThySelf].visible =  health.current / health.total < 0.5
            end)
    
        end)
    
    end
    
    -- kill hooks
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    
    
     
  18. devilcult

    devilcult Avatar

    Messages:
    1,096
    Likes Received:
    2,289
    Trophy Points:
    113
    Did you test / benchmark your libsota ?

    EDIT: He did, and result are pretty good!

    I can only recommend using this lib for anyone that want to get involved into making mods for sota. Not only all the api is there but there is actually a ton of functionality being added on top of it that will speed up your production.

    This can also be used for autocompletion tools in many IDE. Easier to write code!
     
    Last edited: Nov 19, 2019
    StarLord and Rentier like this.
  19. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.3.7 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        _frame = 0,
        _ts = os.time(),
        _isLoading = false,
        _statEnum = {},
        _statDescr = {},
        --_queue = {},
        _keyStrokes = {},
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        location = function() return { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene.name } end,
        health = function() return { total = player.stat(30).value, current = player.stat(14).value, percentage = 0 } end,
        focus = function() return { total = player.stat(27).value, current = player.stat(13).value, percentage = 0 } end,
        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,
            create = function(timeout, once, callback, ...)
                local index = #ui.timer + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer[index] = {
                    index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    func = callback,
                    userdata = ...,
                }
                return index
            end,
        },
     
        handler = {
            add = function(name, callback)
                local index = #ui.handler + 1
    
                ui.handler[index] = {
                    index = index,
                    name = name,
                    func = callback,
                }
                return index
            end,
            remove = function(index)
                ui.handler[index] = nil
            end,
            invoke = function(name, ...)
                for _,h in next, ui.handler do
                    if type(h) == "table" and h.name == name then
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.func(...)
                    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,
        onSceneChange = function(callback) ui.handler.add("_sceneChange", callback) end,
        onPlayerChange = function(callback) ui.handler.add("_playerChange", callback) end,
     
     
        text = {
            style = function(string, styles)
                if styles.bold then string = "<b>"..string.."</b>" end
                if styles.italic then string = "<i>"..string.."</i>" end
                if styles.underline then string = "<u>"..string.."</u>" end
                if styles.size and styles.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
                if styles.color and #styles.color == 7 then string = "<color="..style.color..">"..string.."</color>" end
                return string
            end
        },
       
        rect = {
            create = 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
        },
    
        label = {
            create = function(left, top, width, height, caption)
                local index = #ui.label + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.label[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    text = caption,
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                }
                return index
            end
        },
     
        window = {
            create = function(left, top, width, height, title)
                local index = #ui.window + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.window[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    title = title,
                    visible = false,
                }
                return index
            end,
       
            _draw = function(w)
                local cw = w.width / 9.5
                local ch = w.height / 16.67
                local border = "╔" .. string.rep("═", cw) .. "╗" .. string.rep("\n║".."<color=#000000>"..string.rep("▒", cw).."</color>".."║", ch) .. "\n╚" .. string.rep("═", cw) .. "╝";
                --ShroudGUILabel(w.left, w.top, w.width, w.height, "<color=#2e3436>"..string.rep("▒", 1600).."</color>");
                ShroudGUILabel(w.left-3, w.top-5, w.width, w.height, "<color=#edd400>"..border.."</color>");
                ShroudGUILabel(w.left+10, w.top+10, w.width, w.height, "<b>"..w.title.."</b>");
            end,
        },
    }
    
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.create(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.create(interval, false, callback)
    end
    
    function cancelTimer(index)
        ui.timer[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, text)
        return ui.label.create(left, top, width, height, text)
    end
    
    function getLabel(index)
        return ui.label[index]
    end
    
    function getLabelText(index)
        return ui.label[index].text
    end
    
    function setLabelText(index, caption)
        ui.label[index].text = caption
    end
    
    function removeLabel(index)
        ui.label[index] = nil
    end
    
    function showLabel(index)
        ui.label[index].visible = true
    end
    
    function hideLabel(index)
        ui.label[index].visible = false
    end
    
    function toggleLabel(index)
        ui.label[index].visible = not ui.label[index].visible
    end
    
    function moveLabelBy(index, x, y)
        ui.label[index].left = ui.label[index].left + x
        ui.label[index].top = ui.label[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label[index].left = x
        ui.label[index].top = y
    end
    
    
    -- other utility functions
    function ui.consoleLog(message)
        for l in string.gmatch(message, "[^\n]+") do
            ShroudConsoleLog(l)
        end
    end
    function ui.registerKey(...)
        local key = {...}
        local callback = key[#key]; key[#key] = nil
        local name = key[#key]; key[#key] = nil
        if not client._keyStrokes[tostring(name)] then
            client._keyStrokes[tostring(name)] = {}
        end
        local id = #client._keyStrokes[name] + 1
        client._keyStrokes[name][id] = {
            index = index,
            name = name,
            key = key,
            callback = callback,
        }
        return name, id
    end
    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("_playerChange")
        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("_sceneChange")
        end
    end
    
    -- shroud hooks
    
    function ShroudOnStart()
        ui.handler.invoke("_start")
    end
    
    function ShroudOnUpdate()
    
        -- init
        if not client.screen then
            client.screen = {
                width = ShroudGetScreenX(),
                height = ShroudGetScreenY(),
                isFullScreen = ShroudGetFullScreen(),
            }
            local i
            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.handler.invoke("_init")
        end
    
        -- client stats
        client._frame = client._frame + 1;
        if os.time() - 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 = os.time() - client._ts - 1
            client._ts = os.time()
            scene.timeInScene = os.time() - 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]]
    
        -- timers
        if os.time() - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = os.time()
            for _,t in next, ui.timer do
                if type(t) == "table" and t.enabled and os.time() >= t.time then
                    if t.interval then
                        t.time = os.time() + t.interval
                    else
                        ui.timer[t.index] = nil
                    end
                    if t.func(t.userdata) then ui.timer[t.index] = nil end
                end
            end
        end
    
        -- check key strokes
        for ku,r in next, client._keyStrokes do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.key do
                        invoke = invoke and  kd == ku or ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
     
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
    
        local isLoading = os.time() - client._ts >= 5
        gui_ts = os.time()
     
        for _,l in next, ui.label do
            if type(l) == "table" and l.visible then
                if not isLoading and l.shownInScene then
                    ShroudGUILabel(l.left, l.top, l.width, l.height, l.text)
                elseif isLoading and l.shownInLoadScreen then
                    ShroudGUILabel(l.left, l.top, l.width, l.height, l.text)
                end
            end
            if os.time() - gui_ts > 0.01 then break end
        end
     
        -- this loop does not stay this way
        --[[
        for _,w in next, ui.window do
            if type(w) == "table" and w.visible then
                ui.window._draw(w);
            end
            if os.time() - gui_ts > 0.0167 then break end
        end]]
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
     
        ui.timer.create(0, true, function(...)
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and string.find(message, "PvP") then ui._getPlayerName() end
    
            -- parse message
            local src, dst, msg = string.match(message, "^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if string.byte(msg) == 92 then
                local cmd, arg = string.match(msg, "^\\(%w+)%s*(.*)$")
                ui.handler.invoke("_consoleCommand", cmd, sender, dst, arg)
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
        end, channel, sender, message)
    end
    
    

    player.{x,y,z}() was removed
    player.location() now returns a table containing x,y,z and scene.

    onGui has now a execution time restriction. That should prevent "lagging" if to many labels are shown. How much "to many" means depends on your machine. On a slow machine (Dual Core E6600 @2.4 GHz with 8GByte Memory and a Radeon HD 5870 with 1GByte Memory) "to many" are 250 labels, a healthy amount is 150 labels.
    Anyway, i think 150 labels are more then enough. Many scripts already made using less then 10 labels.

    The testing.lua example contains a "benchmark" you can comment in the code and see yourself how many labels can be drawn on your machine.
    If you use the benchmark and find out that it is to laggy, the execution time allowed can be more restricted. Please tell me.

    testing.lua:
    \xp shows your pooled xp in the chat window
    \stat <number or name> shows the stat you ask for in console
    \keys shows wich keyStrokes are bound to a function
    keyStroke: "RightAlt + P" shows your location in the chat window.

    Code:
    function ShroudOnStart()
    
    
    ui.onInit(function()
    
        local id = ui.label.create(30,24,1024,24, "Init")
        ui.label[id].visible = true
        setInterval(1, function()
            ui.label[id].text = string.format("%d | %s | %s | %s | %f | %f | %f", client.fps, scene.name, player.name, player.flag, client.timeInGame / 60, scene.timeInScene / 60, scene.timeToLoad)
        end)
      
        local id2 = ui.label.create(30,24,1024,24, "Lade Szene bitte warten...")
        ui.label[id2].shownInLoadScreen = true
        ui.label[id2].shownInScene = false
        ui.label[id2].visible = true
      
        ConsoleLog("on init in testing")
      
        -- benchmark
        --[[
        local x=0
        local y=0
        local dx=10
        local dy=10
        for i=1,1024 do
            if x + dx > client.screen.width or x + dx < 0 then dx = -dx end
            if y + dy > client.screen.height or y + dy < 0 then dy = -dy end
            x = x + dx
            y = y + dy
            showLabel(createLabel(x, y, 256, 256, "Label "..i))
        end]]
    end)
    
    ui.onConsoleInput(function(type, src, dst, msg)
        ui.consoleLog(type .. "|" .. src .. "|" .. dst .. "|" .. msg)
    end)
    
    ui.onConsoleCommand(function(cmd, src, dst, arg)
            ShroudConsoleLog(cmd .. "|" .. src .. "|" .. dst .. "|" .. arg)
            ShroudConsoleLog(string.format("Player: %s Flag: %s, PvP: %s", player.name, player.flag, tostring(player.isPvp)))
              
        if cmd == "stat" then
            local stat = player.stat(arg)
            ui.consoleLog(string.format("Stat: %d, %s, %s, %s", stat.number, stat.name, stat.value, stat.description))
          
        elseif cmd == "xp" then
            local stat = player.xp()
            ui.consoleLog(string.format("XP: Abenteurer: %s, Handwerker: %s", stat.adventurer, stat.producer))
          
        elseif cmd == "keys" then
            local keyStroke=""
            for k,r in next, client._keyStrokes do
                keyStroke = ""
                for _,t in next, r do
                    for _,p in next, t.key do
                        keyStroke = p.." + "..keyStroke
                    end
                end
                ui.consoleLog(keyStroke..k)
            end
          
        end
    
    end)
    
    ui.registerKey("RightAlt", "P", function()
        local loc = player.location()
    
        ui.consoleLog(string.format("Position: %s: %d, %d, %d", loc.scene, loc.x, loc.y, loc.z))  
    end)
    
    
    
    
    end -- ShroudOnStart
    
    -- kill hooks
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    [/SPOLER]
     
    Last edited: Nov 20, 2019
    Browncoat Jayson and StarLord like this.
  20. CatweazleX

    CatweazleX Avatar

    Messages:
    643
    Likes Received:
    760
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Code:
    -- libsota.0.3.8 by Catweazle Waldschrath
    
    client = {
        timeStarted = os.time(),
        timeInGame = 0,
        timeDelta = 0,
        fps = 0,
        accuracy = 0,
        screen = nil,
        _frame = 0,
        _ts = os.time(),
        _isLoading = false,
        _statEnum = {},
        _statDescr = {},
        --_queue = {},
        _keyStrokes = {},
    }
    scene = {
        name = "none",
        maxPlayer = 0,
        isPvp = false,
        isPot = false,
        timeInScene = 0,
        timeToLoad = 0,
        timeStarted = 0,
    }
    player = {
        caption = "",
        name = "none",
        flag = "",
        isPvp = false,
        location = function() return { x = ShroudPlayerX, y = ShroudPlayerY, z = ShroudPlayerZ, scene = scene.name } end,
        health = function() return { total = player.stat(30).value, current = player.stat(14).value, percentage = 0 } end,
        focus = function() return { total = player.stat(27).value, current = player.stat(13).value, percentage = 0 } end,
        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,
            create = function(timeout, once, callback, ...)
                local index = #ui.timer + 1
                local interval = nil
    
                if not once then
                    interval = timeout
                end
    
                ui.timer[index] = {
                    index = index,
                    time = os.time() + timeout,
                    interval = interval,
                    enabled = true,
                    func = callback,
                    userdata = ...,
                }
                return index
            end,
        },
     
        handler = {
            add = function(name, callback)
                local index = #ui.handler + 1
    
                ui.handler[index] = {
                    index = index,
                    name = name,
                    func = callback,
                }
                return index
            end,
            remove = function(index)
                ui.handler[index] = nil
            end,
            invoke = function(name, ...)
                for _,h in next, ui.handler do
                    if type(h) == "table" and h.name == name then
                        --client._queue[#client._queue+1] = function() h.func(...) end
                        h.func(...)
                    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,
        onSceneChange = function(callback) ui.handler.add("_sceneChange", callback) end,
        onPlayerChange = function(callback) ui.handler.add("_playerChange", callback) end,
     
     
        text = {
            style = function(string, styles)
                if styles.bold then string = "<b>"..string.."</b>" end
                if styles.italic then string = "<i>"..string.."</i>" end
                if styles.underline then string = "<u>"..string.."</u>" end
                if styles.size and styles.size > 4 then string = "<size="..style.size..">"..string.."</size>" end
                if styles.color and #styles.color > 6 then string = "<color="..style.color..">"..string.."</color>" end
                return string
            end
        },
       
        rect = {
            create = 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
        },
    
        label = {
            create = function(left, top, width, height, caption)
                local index = #ui.label + 1
                local r = ui.rect.create(left, top, width, height)
    
                ui.label[index] = {
                    left = r.left,
                    top = r.top,
                    width = r.width,
                    height = r.height,
                    text = caption,
                    visible = false,
                    shownInScene = true,
                    shownInLoadScreen = false,
                }
                return index
            end
        },
    }
    
    
    
    -- timer utility functions
    
    function setTimeout(timeout, callback)
        return ui.timer.create(timeout, true, callback)
    end
    
    function setInterval(interval, callback)
        return ui.timer.create(interval, false, callback)
    end
    
    function getTimer(index)
        return ui.timer[index]
    end
    
    function cancelTimer(index)
        ui.timer[index] = nil;
    end
    
    function pauseTimer(index)
        ui.timer[index].enabled = false
    end
    
    function resumeTimer(index)
        ui.timer[index].enabled = true
    end
    
    
    -- label utility functions
    
    function createLabel(left, top, width, height, text)
        return ui.label.create(left, top, width, height, text)
    end
    
    function getLabel(index)
        return ui.label[index]
    end
    
    function getLabelText(index)
        return ui.label[index].text
    end
    
    function setLabelText(index, caption)
        ui.label[index].text = caption
    end
    
    function removeLabel(index)
        ui.label[index] = nil
    end
    
    function showLabel(index)
        ui.label[index].visible = true
    end
    
    function hideLabel(index)
        ui.label[index].visible = false
    end
    
    function toggleLabel(index)
        ui.label[index].visible = not ui.label[index].visible
    end
    
    function isLabelVisible(index)
        return ui.label[index].visible
    end
    
    function setLabelVisible(index, visible)
        ui.label[index].visible = visible
    end
    
    function moveLabelBy(index, x, y)
        ui.label[index].left = ui.label[index].left + x
        ui.label[index].top = ui.label[index].top + y
    end
    
    function moveLabelTo(index, x, y)
        ui.label[index].left = x
        ui.label[index].top = y
    end
    
    
    -- other utility functions
    function ui.consoleLog(message)
        for l in string.gmatch(message, "[^\n]+") do
            ShroudConsoleLog(l)
        end
    end
    function ui.registerKey(...)
        local key = {...}
        local callback = key[#key]; key[#key] = nil
        local name = key[#key]; key[#key] = nil
        if not client._keyStrokes[tostring(name)] then
            client._keyStrokes[tostring(name)] = {}
        end
        local id = #client._keyStrokes[name] + 1
        client._keyStrokes[name][id] = {
            index = index,
            name = name,
            key = key,
            callback = callback,
        }
        return name, id
    end
    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("_playerChange")
        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("_sceneChange")
        end
    end
    
    -- shroud hooks
    
    function ShroudOnStart()
        ShroudUseLuaConsoleForPrint(true)
        ui.handler.invoke("_start")
    end
    
    function ShroudOnUpdate()
    
        -- init
        if not client.screen then
            client.timeStarted = os.time()
            client.screen = {
                width = ShroudGetScreenX(),
                height = ShroudGetScreenY(),
                isFullScreen = ShroudGetFullScreen(),
            }
            local i
            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.handler.invoke("_init")
        end
    
        -- client stats
        client._frame = client._frame + 1;
        if os.time() - 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 = os.time() - client._ts - 1
            client._ts = os.time()
            scene.timeInScene = os.time() - 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]]
     
        -- user timers
        if os.time() - ui.timer._ts >= ui.timer._granulaty then
            ui.timer._ts = os.time()
            for _,t in next, ui.timer do
                if type(t) == "table" and t.enabled and os.time() >= t.time then
                    if t.interval then
                        t.time = os.time() + t.interval
                    else
                        ui.timer[t.index] = nil
                    end
                    if t.func(t.userdata) then ui.timer[t.index] = nil end
                end
            end
        end
    
        -- check key strokes
        for ku,r in next, client._keyStrokes do
            if ShroudGetOnKeyUp(ku) then
                for _,f in next, r do
                    local invoke = true
                    for _,kd in next, f.key do
                        invoke = invoke and  kd == ku or ShroudGetKeyDown(kd)
                    end
                    if invoke then f.callback() end
                end
            end
        end
     
        ui.handler.invoke("_update")
    end
    
    function ShroudOnGUI()
        local ts = os.time()
        local isLoading = os.time() - client._ts >= 5
       
        for _,l in next, ui.label do
            if type(l) == "table" and l.visible then
                if not isLoading and l.shownInScene then
                    ShroudGUILabel(l.left, l.top, l.width, l.height, l.text)
                elseif isLoading and l.shownInLoadScreen then
                    ShroudGUILabel(l.left, l.top, l.width, l.height, l.text)
                end
            end
            if os.time() - ts > 0.01 then break end
        end
    end
    
    function ShroudOnConsoleInput(channel, sender, message)
     
        ui.timer.create(0, true, function(...)
    
            -- handle player flag changes
            if (channel == "Story" or channel == "System") and string.find(message, "PvP") then ui._getPlayerName() end
    
            -- parse message
            local src, dst, msg = string.match(message, "^(.-) to (.-) %[.-:%s*(.*)$")
            if sender == "" then sender = src end
            if sender == "" then sender = player.name end
            if string.byte(msg) == 92 then
                local cmd, arg = string.match(msg, "^\\(%w+)%s*(.*)$")
                ui.handler.invoke("_consoleCommand", cmd, sender, dst, arg)
            else
                ui.handler.invoke("_consoleInput", channel, sender, dst, msg)
            end
    
    
        end, channel, sender, message)
    end
    
    

    Needs QA 890
    For QA 889 please comment out ShroudUseLuaConsoleForPrint(true) in function ShroudOnStart()


    added some utility functions:
    getTimer(index) - returns timer object
    toggleLabel(index) - toggle labels visibility
    isLabelVisible(index) - returns if label visible
    setLabelVisible(index) - set label visibility

    lua print now outputs to console
     
    Last edited: Nov 20, 2019