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

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

Thread Status:
Not open for further replies.
  1. CatweazleX

    CatweazleX Avatar

    Messages:
    653
    Likes Received:
    777
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    I am not as good in documentation, here is a try.

    libsota was made to help other to create their own mods for Shroud. Also libsota contains some workarounds to take away some headache from working with the Shroud Api.
    libsota is object oriented and provides events and some additional data produced from data that is coming from Shroud Api.
    It does data polling and generate additional events from them and caches these data, so that calls to the client are reduced.
    All the mods that are using libsota sharing the data already retrieved.
    It is also carefully with callbacks coming from the Shroud Api that may produce lag on the client or may causes trouble with user input or system messages.
    Some counter measures are taken to not block/hindering the player from playing the game because of LUA.

    Labels are auto hidden during loading a scene, but can also be set to be shown on load screen

    libsota is still in development and when finished it is made from 3 files:
    libsota.lua - the core library interacting directly with the Shroud Api
    libsota.util.lua - helper functions for the global namespace; procedure style.
    libsota.ui.lua - UI controls like windows, labels, window manager, images, buttons, ...

    Even when libsota is object oriented and likes closures it can also be used with a procedural coding style.

    I would be happy when the one or other small or big mod appears here in the forum, based on libsota.
    Some here have good ideas for small goodies. I do not have so many ideas which mods may needed / wanted.

    Here are the objects:
    (when libsota and libsota.util are installed all objects can be outputted to the console with \info player, \info client, \info scene, \info ui)


    libsota.0.4.5:
    Code:
    
    client = {
        timeStarted = timestamp: time when the game was started
        timeToLoad = int: duration in seconds needed to load into the first scene
        timeInGame = int: duration in seconds already playing the game
        timeDelta = float: ShroudTimeDelta
        fps = int: fps
        accuracy = float: time drift between one second
        screen = {
            width = int: width of screen in pixel
            height = int: height of screen in pixel
            isFullScreen = bool: true if played in full screen mode
        },
        isHitching = bool: true when the client hitches
        isLoading = bool: true when the client is loading a scene
        api = {
            luaVersion = string: contains the version number of LUA
            luaPath = string: contains the path of the lua directory (/lua path)
            list = table: contains all available Shroud Api functions of the current client
            isImplemented = function(<shroudfunctionname>) - returns true when this function is available, false otherwise
        },
        mouse = {
            button = table: containing button states
            x = int: x position of mouse on screen
            y = int: y position of mouse on screen
        },
        window = {
            paperdoll = {
                open = bool: true when paperdoll/charactersheet is open
                left = int: left pos of this window
                top = int: top pos of this window
                width = int: with of this window
                height = int: height of this window
            }
        }
    }
    scene = {
        name = string: contains the display name of the current scene
        maxPlayer = int: contains the number of player allowed in this scene
        isPvp = bool: true if PvP is allowed in this scene
        isPot = bool: true if the current scene is a POT
        timeInScene = float: duration in seconds already in this scene
        timeToLoad = float: duration in seconds needed to load into this scene
        timeStarted = timestamp: time when this scene was loaded / entered
    }
    player = {
        caption = string: contains the caption of the player like it is shown on screen
        name = string: contains the name of the player (without any flags)
        flag = string: contains the flags the player currently has
        isPvp = bool: true when player is flagged for PvP
        isAfk = bool: true when player is AFK
        isGod = bool: true when the player is in god mode
        isMoving = bool: true when the player is moving
        isStill = bool: true when the player has stillness bonus
        lastMoved = timestamp: time when player last moved (or time since the player is standing)
        location = {
            x = float: x position of player in scene
            y = float: y position of player in scene (is height)
            z = float: z position of player in scene
            scene = points to the scene object
        },
        health = {
            current = float: current health of the player
            max = float: current max health of the player
            percentage = float: current percentage of max health (may used for progress bars)
        },
        focus = {
            current = float: current focus of the player
            max = float: current max focus of the player
            percentage = float: current percentage of max focus (may used for progress bars)
        },
        xp = {
            producer = number: current pooled producer xp
            adventurer = number: current pooled adventurer xp
        },
        inventory = array: a array that contains a table with this fields: {
            name = string: caption of the item
            durability = float: durability
            primaryDurability = float:
            maxDurability = float:
            weight = float:
            quantity = int:
            value = int:
        }
        stat = function(index) - returns a table containing: number, name, value and description of stat. Index is the name or number of the stat
    }
    
    ui = {
        version = string: contains the version of the library
    
        timer = {
            add = function(timeout, once, callback, ...) - add a callback that is called after/every (the) time slice. returns the index
            get = function(index) - returns a timer instance
            remove = function(index) - removes a timer instance
            enabled = function(index, enabled) - enable or disable a timer. returns the enabled state. if enabled is nil, only return
            pause = function(index) - pauses a timer (enabled = false)
            resume = function(index) - resumes a timer (enabled = true)
            toggle = function(index) - toggles the enabled state of a timer
        },
        setTimeout = function(timeout, callback) - utility function to call a function after the timeout
        setInterval = function(interval, callback) - utility function to call a function periodical (interval)
        
        handler = {
            add = function(name, callback) - adds a named callback handler / event you can use to provide events to other or your scripts. returns index
            remove = function(index) - removes a callback handler
            invoke = function(name, ...) - invokes a named callback handler
        },
        onInit = function(callback) - this event is invoked when the Shroud Api and the lib is fully initialized
        onStart = function(callback) - this event is invoked when all mods are initialized that are using this library
        onUpdate = function(callback) - this event is invoked on every frame. Please use timer instead.
        onConsoleInput = function(callback) - this event is invoked for each line reaching the chat window, except it is a command
        onConsoleCommand = function(callback) - this event is invoked when a command is entered in the chat window. Commands starting with \, _ or !
        onSceneChanged = function(callback) - this event is invoked when the new scene is finished with loading
        onPlayerChanged = function(callback) - this event is invoked when something has changed with the player (name, flag, moving, standing, stillness, damage, inventory)
        onPlayerMoveStart = function(callback) - this event is invoked when the player starts moving
        onPlayerMoveStop = function(callback) - this event is invoked when the player stops moving
        onPlayerIsStill = function(callback) - this event is invoked when the player is still (has stillness bonus)
        onPlayerDamage = function(callback) - this event is invoked when the players focus or health is about to change
        onPlayerInventory = function(callback) - this event is invoked when the inventory of the player changed
        onClientWindow = function(callback) - this event is invoked when a client window opens or closes (paperdoll)
        onClientIsHitching = function(callback) - this event is invoked when the client starts hitching
        onClientIsLoading = function(callback) - this event is invoked when the client is loading a scene
        onMouseMove = function(callback) - this event is invoked when the mouse is moved
        onMouseButton = function(callback) - this events is invoked when a mouse button is pressed
        
        label = {
            add = function(left, top, width, height, caption) - adds a GUI label. returns the index
            get = function(index) - returns the GUI label instance
            remove = function(index) - removes a GUI label
            rect = function(index, rect) - applies a rect structure for the label (from libsota.util)
            caption = function(index, caption) - sets or gets the caption of a GUI label. caption = nil to get only
            visible = function(index, visible) - sets or gets the visible state of a GUI label
            toggle = function(index) - toggles the visible state of a GUI label
            moveTo = function(index, x, y) - moves a GUI label to the given position
            moveBy = function(index, x, y) - moves a GUI label by the given values (delta)
            resizeTo = function(index, w, h) - resizes a GUI label to the given size
            resizeBy = function(index, x, y) - resizes a GUI label by the given values (delta)
            shownInScene = bool: true when the label should be shown in scene
            shownInLoadScreen = bool: true when the label should be shown on load screen
            zIndex = int: the z-index of the GUI label
        },
    
        texture = {
            add = function(left, top, filename, clamped, scaleMode, width, height) - add a GUI texture instance. returns index. loads the texture if necessary
            clone = function(index) - clones the texture instance and returns the index of the new instance. the new instance has visible set to false
            get = function(index) - returns the GUI texture instance
            remove = function(index) - removes a GUI texture instance. (purging textures are currently not supported) 
            clamp = function(index, clamped) - sets or gets if texture is clamped (bool)
            scaleMode = function(index, scaleMode) - set or gets the scaleMode (StretchToFill, ScaleAndCrop, ScaleToFit)
            rect = function(index, rect) - applies a rect structure to the texture (from libsota.util)
            visible = function(index, visible) - sets or gets the visible state of the texture
            toggle = function(index) - toggles the visible state of the texture
            moveTo = function(index, x, y) - moves a texture the given position
            moveBy = function(index, x, y) - moves a texture by the given values (delta)
            resizeTo = function(index, w, h) - resizes a texture to the given size
            resizeBy = function(index, x, y) - resizes a texture by the given values (delta)
            shownInScene = bool: true when the texture should be shown in scene
            shownInLoadScreen = bool: true when the texture should be shown on load screen
            zIndex = int: the z-index of the GUI texture
            textureID = int: shroud id of the texture
            textureWidth = int: width of the texture
            textureHeight = int: height of the texture
        },
    
        
        shortcut = {
            add = function(action, ...) - adds a shortcut that calls the given function. returns the index. 
            remove = function(index) - removes a shortcut
            invoke = function(index) - calls the function belonging to that index
        },
        
        command = {
            add = function(command, callback) - adds a command callback. returns the index
            remove = function(command) - removes a command callback
            invoke = function(command, ...) - invokes the callback belonging to the command
            restrict = function(command, channel, sender, receiver) - restrict command invokation on channel, sender or receiver
        },
        
        verbosity = int: verbosity level
        consoleLog = function(message, verbosity) - send a multiline message to the console depending on verbosity level
    }
    
    Remarks:
    callbacks:
    onInit(function() end)
    onStart(function() end)
    onUpdate(function() end)
    onConsoleInput(function(channel, sender, receiver, messages) end)
    onConsoleCommand(function(source, command, tail) end) - source is a table containing channel, sender, receiver
    onPlayerChanged(function(what, health, focus) end) - what contains what has changed. health and focus are only set when player takes damage
    onPlayerMoveStart(function() end)
    onPlayerMoveStop(function() end)
    onPlayerIsStill(function() end)
    onPlayerDamage(function(health, focus) end) - health and focus are the new values, the old ones are in player.health and player.focus. Both are tables
    onPlayerInventory(function(changed) end) - changed contains a table what has changed
    onClientWindow(function(which, window) end) - which contains the name, window { open = bool, left, top, width, height }
    onClientIsHitching(function() end)
    onClientIsLoading(function() end)
    onMouseMove(function(button, x, y) end)
    onMouseButton(function(state, button, x, y) end)
    
    

    libsota.util:
    Code:
    
    -- timer utility functions
    
    function setTimeout(timeout, callback) - calls the callback after the given timeout. returns the index
    function setInterval(interval, callback) - calls the callback each interval. returns the index
    function getTimer(index) - returns the timer instance
    function cancelTimer(index) - cancel the given timer
    function pauseTimer(index) - pause the given timer
    function resumeTimer(index) - resume the given timer
    
    -- label utility functions
    
    function createLabel(left, top, width, height, caption) - creates a label. returns the index
    function createLabelWithShadow(left, top, width, height, caption) - creates a label with shadow. returns the index. the utility functions have to be used
    function getLabel(index) - returns the GUI label instance
    function getLabelCaption(index) - retruns the caption of the GUI label
    function setLabelCaption(index, caption) - sets the caption of the GUI label
    function removeLabel(index) - removes GUI label
    function showLabel(index) - shows the GUI label
    function hideLabel(index) - hide the GUI label
    function toggleLabel(index) - toggle the visible state of the GUI label
    function isLabelVisible(index) - returns if the GUI label is visible
    function setLabelVisible(index, visible) - sets the visible state of the GUI label
    function moveLabelBy(index, x, y) - moves the GUI label by x,y (delta)
    function moveLabelTo(index, x, y) - moves the GUI label to x,y (absolute)
    function resizeLabelBy(index, w, h) - resizes the GUI label by w,h (delta)
    function resizeLabelTo(index, w, h) - resizes the GUI label to w,g (absolute)
    function moveLabelOffsetCenter(index, x, y) - moves the GUI label by x, y relative to the center of the screen and auto size it
    
    --- other
    
    ui.onShortcutPressed("key" [,key][,key]..., callback) - adds a shortcut and invokes the callback when the shortcut is pressed
    ui.onShortcut("key" [,key][,key]..., callback) - adds a shortcut and invoke the callback when ever something is changed: down, up, held, pressed
    ui.onCommand(command, callback) - adds a command and invoke the callback when the comand is entered. The first argument is the source after this all args entered following
    
    
    --- structurs / obejects
    
    rect = {
        new = function(left, top, width, height) - returns a rect object. can also be used r = rect(..)
        fromString = function(string) - returns a rect taken vom string. width and height are filled in. rect.fromString(string)
        moveTo = function(rect, x, y) - moves a rect to x,y (absolute). rect.moveTo(rect, x, y) or r:moveTo(x, y)
        moveBy = function(rect, x, y) - moves a rect by x,y (delta). rect.moveBy(rect, x, y) or r:moveBy(x, y)
        resizeTo = function(rect, w, h) - resizes a rect to w,h (absolute). rect.resizeTo(rect, w, h) or r:resizeTo(w, h)
        resizeBy = function(rect, w, h) - resizes a rect by w,h (delta). rect.resizeBy(rect, w, h) or r:resizeBy(w, h)
    }
    
    string.style = function(string, style) - applies a style to a string. a style is a table that may contain { size = nn, color = color, bold = true/false, italic = true/false }
    Eg: string:style({ color = blue, italic = true })
    string.rect(string) is a alias for rect.fromString - string:rect() returns a rect taken from the string
    
    
    Quick and dirty window from libsota.util
    Code:
    w = window(left, top, width, height, title) - create a window object
    window.moveAble - allows window to be moved by the "window manger" or not
    window.resizeAble - allows window to be resized by the "window manager" or not
    window:moveBy, window.moveTo - moves window (by = delta, to = absolute)
    window:resizeBy, window.resizeTo - resizes window (by = delta, to = absolute)
    window:visible(bool) - set window visible or not
    window:zIndex(zIndex) - set the z-index of the window
    window:createLabel(name, x, y, caption) - creates a label as child for that window.
    window:createTextbox(name, x, y, w, h) - creates a label as child for that window
    window:setCaption(name, caption) - set te caption of label that is child of that window
    window:setText(name, caption) - set the text of label that is a child of that window
    window:getLabel(name) - returns the index of the ui.label used
    
    wdm.add(window) - adds a window object to the window manager
    
    the window manager allows:
    switching through the windows using LeftControl + Tab.
    Focused window can be moved using LeftControl and the ArrowKeys.
    LeftControl + F4 hides or unhides the focused window.
    Focused window can be resized using LeftAlt and the ArrowKeys
    Windows are brought to front when they get the focus.
    
    Commands added by libsota.util
    \lua lua - shows lua version and path
    \lua api - shows list of available functions and globals from Shroud api
    \info xp -- shows players pooled xp
    \info stat <number or name> -- shows stat info and value
    \info client -- shows client info and variable names (client object from libsota)
    \info player -- shows player info and variable names (player object from libsota)
    \info scene -- shows scene info and variable names (scene object from libsota)
    \info ui -- shows the functions you can use (api) from libsota (ui object from libsota)
    \info lib -- shows other info from libsota. incl. list of registered / added commands
     
    Last edited: Nov 30, 2019
    Anpu and Browncoat Jayson like this.
  2. CatweazleX

    CatweazleX Avatar

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

    Added the new functions from QA that should also be available on the Live. But missed the ShroudButton. Will be in the next libsota.
    New is mouse support, textures, player inventory and events regarding the character sheet (paperdoll). Party interface is still delayed

    client.mouse.button - contains an array with the state of the mouse buttons. Eg, left button state: button[0] tells up, held or down. right button is button[1]. up to 6 buttons supported. Button not down: button[x] == nil
    client.mouse.x - tells the x position of the mouse
    client.mouse.y - tells the y positions of the mouse
    client.window.paperdoll - contains a table: { open = true/false, left, top, with, height } telling if the window is open and its metrics

    player.inventory - contains an array of tables { name, durability, primaryDurability, maxDurability, weight, quantity, value }. To get the name of the third item in inventory: player.inventory[3].name
    player.xp is now a table { adventurer, producer }. Changed from a function to table

    ui.onClientWindow(which, window) - is invoked when a window is opened or closed. which contains the name and window points to the window in client.window.
    ui.onMouseMove(button, x, y) - is invoked when the mouse is moved. button contains the table from client.mouse.button. x and y mouse position
    ui.onMouseButton(state, id, x, y) - is invoked on mouse button. state: down, up, held, clicked, dblclicked. id is the id that can be used to lookup client.button[id]. 0 is left button, 1 is right button. x and y is mouse position
    ui.onPlayerInventory(changes) - will invoked when something in the player inventory has changed (currently only quantity). for itemname, what in next, changes tells what is changed. what contains: { quantity, action } and action: added, removed or changed.
    ui.onPlayerChanged(what, changes) - will also invoked when something in the inventory has changed. what is "inventory" in this case

    ui.texture.add(left, top, filename, clamped, scaleMode, width, height) - adds a texture and returns the index to the instance
    ui.texture.clone(index) - clones the texture instance and returns the index of the new instance. The returned instance visible state is false.
    ui.texture.get(index) - get the texture instance
    ui.texture.remove(index) - removes a texture instance
    ui.texture.clamp(index, clamped) - sets or gets if the texture is/should be clamped
    ui.texture.scaleMode(index, scaleMode) - sets or gets the scale mode for this texture instance
    ui.texture.rect(index, rect) - applies a rect structure to the texture instance
    ui.texture.visibile(index, visible) - sets or gets if the texture instance visible state
    ui.texture.toggle(index) - toggles the visibility of this texture instance
    ui.texture.moveTo(index), ui.texture.moveBy(index) - moves the texture instance. by = relative, to = absolute
    ui.texture.resizeTo(index), ui.texture.resizeBy(index) - resizes the texture instance. by = relative, to = absolute
    ui.texture[index].shownInScene = bool: true to show in scene, false do not (default: true)
    ui.texture[index].shownInLoadScreen = bool: true to show on loadscreen, false do not (default: false)
    ui.texture[index].zIndex = number: the z-index of the texture instance
    ui.texture[index].texture.width = original width of the texture
    ui.texture[index].texture.height = original height of the texture
    ui.texture[index].texture.id = id of the texture the texture instance is using

    Textures are loaded only once. In the case that a texture is added with a filename that is already loaded a fresh instance is returned linked to the already loaded texture. It does not matter which mod has loaded this texture.
    Currently there is no support to purge a texture, so ui.texture.remove will only destroy the texture instance, but the texture keeps in memory.
    ScaleModes are: StretchToFill, ScaleAndCrop or ScaleToFit
    The .png file format supports transparency.
     
    Last edited: Nov 29, 2019
    Browncoat Jayson likes this.
  3. Archer

    Archer Avatar

    Messages:
    285
    Likes Received:
    196
    Trophy Points:
    40
    Location:
    UK, EU, Terra
    Would you consider changing the info commands to /info so the command isn't echoed to normal channel.

    Thank you very much for the work you have put into this.
     
    Last edited: Nov 29, 2019
  4. CatweazleX

    CatweazleX Avatar

    Messages:
    653
    Likes Received:
    777
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    The / is reserved for the game. Nothing what starts with / is send to ShroudOnConsoleInput. So, we can not capture it.
    Maybe, we can ask @Chris to send it to ShroudOnConsoleInput in the case of unknown commands and/or paramaters to allow a expansion of ingame commands.

    May also a /lua channel helps, where all you typed in is send to ShroudOnConsoleInput any may echoed there (and not sent to other channels where players may listen to)
     
  5. CatweazleX

    CatweazleX Avatar

    Messages:
    653
    Likes Received:
    777
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Download:
    libsota.0.4.6 and libsota.util.0.4


    Added the ShroudButton and did some optimizations, testing and bugfixes. Improved z ordering. That changed also the object model somewhat. This is also the preview of libsota.0.5.0 (will come with libsota.ui).
    Removed window and wdm. They are not working anymore and where quick and dirty. If chris does not add a bunch of new functions to the Shroud Api i continue with libsota.ui, so that window and a wdm will be available again.

    libsota.util is adjusted to the new model. The functions in libsota.util should continue to work.

    if you use string.style to set fontsize (string:style{ size = nn }) it should now be the same size at different resolutions. No more to small fonts at 4k monitors. Thx @Toular for the hint/tip to get this done.

    All drawing elements (ShroudGuiLabel / ui.label, ShroudDrawTexture / ui.texture, ShroudButton / ui.guiButton and ShroudButtonRepeat / ui.guiButton) are now derived from guiObject.
    As of this they share the same methods:

    for ui.label, ui.texture, ui.guiButton:
    ui.guiObject.add(left, top, with, height, drawFunc) - adds a guiObject (this function is considered private to the lib)

    ui.guiObject.remove(object) | object:remove() - removes the object
    ui.guiObject.shownInScene(object, bool) | object:shownInScene(bool) - sets or gets if the object is shown while in scene
    ui.guiObject.shownInLoadingScreen(object, bool) | object:shownInLoading(bool) - sets or gets if the object is shown on loading screen
    ui.guiObject.visible(object, bool) | object:visible(bool) - sets or gets if the object is shown at all
    ui.guiObject.zIndex(object, number) | object:zIndex(number) - sets or gets the objects z-index.
    ui.guiObject.toggle(object) | object:toggle() - toggles the visible state of the object
    ui.guiObject.moveTo(object, x, y) | object:moveTo(x, y) - move the object to the given position
    ui.guiObject.moveBy(object, x, y) | object:moveBy(x, y) - move the object by the values (delta)
    ui.guiObject.resizeTo(object, w, h) | object:resizeTo(w, h) - resizes the object to the given size
    ui.guiObject.resizeBy(object, w, h) | object:resizeBy(w, h) - resizes the object by the given size (delta)
    ui.guiObject.rect(object, rect) | object:rect(rect) - sets or gets the rect of the object. One can also use ui.guiObject.rect({ left = 10, width = 20 }) to only change this values from a rect. object:rect(rect) is currently buggy

    the object:rect() can also be used with the rect functions from libsota.util
    The ui.guObject is not directly used. Since gui objects are now derived from this class the methods are accessible directly from the object:

    label = ui.label.add(0, 0, 5, 5, "caption")
    label:moveTo(10, 10)
    label:resizeTo(20, 20)
    label:visible(true)
    texture = ui.texture.add(10, 10, "test.png")
    texture:zIndex(-10)
    texture:toggle()
    button = ui.guiButton.add(80, 80, texture, "caption", "tooltip", 80, 80)
    button:visible(true)
    button: onClick(function(x, y) ui.consolePrint(string.format("x: %d, y: %d", x, y)) end)


    label = ui.label.add(left, top, width, height, caption) - adds a label and returns the label object
    label.caption = string: the caption of the label

    texture = ui.texture.add(left, top, filename, clamped, scaleMode, width, height) - adds a texture instance, loads texture if not already loaded. returns a texture object. when width or height arr omitted with or height are taken from image
    texture = ui.texture.clone(texture) | texture = texture:clone() - clones the texture instance and returns the new instance
    bool = ui.texture.clamp(texture, clamped) | bool = texture:clamp(clamped) - clamps the the underlying texture, so the texture is clamped for all instances that use this image. returns if texture is clamped
    ui.texture.scaleMode(texture, scaleMode) | texture:scaleMode(scaleMode) - sets or gets the scaleMode for this instance. (ScaleModes are: StretchToFill, ScaleAndCrop or ScaleToFit)

    button = ui.guiButton.add(left, top, texture, caption, tooltip, width, height) - adds a button. texture is a texture instance. returns the button object.
    ui.guiButton.onClick(button, callback) | button: onClick(callback) - will call the callback when the button is clicked. the callback is callback(x, y). x and y contains the child position of the click (left, top of the button is 0,0)
    ui.guiButton.onRepeat(button, callback) | button: onRepeat(callback) - will call the callback when the button is pressed and as long/often it is pressed. callback is callback(x,y). x and y are child coordinates (0,0 is left,top of the button)
    button.tooltip = string: the tooltip of the button
    button.caption = string: the caption of the button
    button.texture = texture: the texture instance used. (can also be used to change the image on the button)

    Atos example regarding the buttons, converted to libsota:
    Code:
    function ShroudOnStart()
    
        ui.onInit(function()
          tex = ui.texture.add(0, 0, "test.png")
    
          btn = ui.guiButton.add(150, 150, tex, "", "tooltip", 80, 80)
          btn:onRepeat(function(x, y)
             ui.consoleLog(string.format("screen: %d %d, child: %d %d", client.mouse.x, client.mouse.y, x, y))
          end)
          btn:visible(true)
    
          tex:moveTo(300, 300)
          tex:visible(true)
        end)
    
    
    end
    
    -- implement Shroud calls
    function ShroudOnConsoleInput() end
    function ShroudOnGUI() end
    function ShroudOnUpdate() end
    
     
    Last edited: Dec 4, 2019
    Anpu and Browncoat Jayson like this.
  6. CatweazleX

    CatweazleX Avatar

    Messages:
    653
    Likes Received:
    777
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    Chris has added a button
     
    Anpu likes this.
  7. that_shawn_guy

    that_shawn_guy Bug Hunter

    Messages:
    1,409
    Likes Received:
    3,738
    Trophy Points:
    125
    Location:
    earth... mostly
    I'd love to see this on Github. Would make tracking the changes and contributing a lot easier for technical crowd
     
    Mishikal and Archer like this.
  8. CatweazleX

    CatweazleX Avatar

    Messages:
    653
    Likes Received:
    777
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    I'll put it into a repository, so changes are visible.
    Added patch files from 0.4.4 -> 0.4.5 and 0.4.5 -> 0.4.6 you found them here these are text files. (diff output). You can see the changes there

    I am using this tool. http://meldmerge.org/ . It helps me to see changes while i am writing the changes to the forum. Sometimes you forget something, it helps to not oversee such things
     
    Last edited: Dec 2, 2019
    Anpu and Browncoat Jayson like this.
  9. Archer

    Archer Avatar

    Messages:
    285
    Likes Received:
    196
    Trophy Points:
    40
    Location:
    UK, EU, Terra
    This has a typo somewhere (missing close parenthesis), but even with one added before the line containing 'btn:visible(true)' it doesn't appear to work as expected
     
    Last edited: Dec 3, 2019
  10. CatweazleX

    CatweazleX Avatar

    Messages:
    653
    Likes Received:
    777
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    I typed it blindly without testing. Updated the example and included the ShroudOn... functions. Should now work
     
  11. Tirrag

    Tirrag Avatar

    Messages:
    853
    Likes Received:
    1,820
    Trophy Points:
    93
    Location:
    Iowa, USA
    Thank you @CatweazleX for taking the time to provide and support this library. I am using it as a foundation for a few mods and have a quick question about the texture loading. Is it able to load images from a subfolder? I have been trying to do so unsuccessfully. From the root lua folder the images work great but once I place in a subfolder they no longer load. Thank you!
     
  12. CatweazleX

    CatweazleX Avatar

    Messages:
    653
    Likes Received:
    777
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    I would also like to see that we can use subfolders within the Lua folder. But currently there is only access to files residing directly in the Lua folder. (when you are on Linux you have to use the data-folder and prefix each image file with Lua\")
    May naming those files like this: usr.share.<mod>.<filename.ext> helps? it least it keep it sorted.

    Oh, and thank you for using the library.
     
    Last edited: Dec 4, 2019
  13. Drake Aedus

    Drake Aedus Avatar

    Messages:
    536
    Likes Received:
    886
    Trophy Points:
    75
    Gender:
    Male
    The only issue I had with loading textures was realizing that ShroudLoadTexture() assumes you are starting in the Lua folder. I have used texture = ShroudLoadTexture('mod/filename.png') before.
     
  14. CatweazleX

    CatweazleX Avatar

    Messages:
    653
    Likes Received:
    777
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    On Linux i have to create a folder named "Lua\mod" in the <datafolder> to load images that resides in the mod subfolder, using ui.texture.add(0, 0, "mod/filename.ext") (or ShroudLoadTexture("mod/filename.ext")). But from there in i can load from subfolders. ui.texture.add(0,0, "mod/libsota/filename.ext") works also. I can not tell if ui.texture.add(0, 0, "mod\filename.ext) or ui.texture.add(0,0 "mod\libsota\filename.ext) works on windows.
    Having a folder named "mod" in <datafolder>/Lua does nor work on Linux. The bug with the \ in folder-/filename is still present in the current QA 895.
     
  15. Tirrag

    Tirrag Avatar

    Messages:
    853
    Likes Received:
    1,820
    Trophy Points:
    93
    Location:
    Iowa, USA
    Thank you both. I cannot test at the moment but I believe my issue was that I was using an absolute path (via ShroudLuaPath) to the texture when referencing my mod folder. I will try it with relative and hopefully see better results.

    One other quick question, has the ability to change the alpha of a texture been exposed (I want to do a fade out/in)? I would assume no since it does not appear to be in this library but wanted to ask just in case.
     
    Last edited: Dec 4, 2019
  16. CatweazleX

    CatweazleX Avatar

    Messages:
    653
    Likes Received:
    777
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    No, but png-files are supporting transparency. You may change the texture and alpha of the text to archive this effect.
     
  17. Drake Aedus

    Drake Aedus Avatar

    Messages:
    536
    Likes Received:
    886
    Trophy Points:
    75
    Gender:
    Male
    I was considering this as well, I wonder if animated png is supported? A single loop fade-in and separate fade-out maybe? It's been years since I even thought about apng though...
     
  18. Archer

    Archer Avatar

    Messages:
    285
    Likes Received:
    196
    Trophy Points:
    40
    Location:
    UK, EU, Terra
    Is there a Git repo yet? I can't get the patch to update to 0.4.6
     
  19. CatweazleX

    CatweazleX Avatar

    Messages:
    653
    Likes Received:
    777
    Trophy Points:
    93
    Location:
    Veritas Sanctuary
    No Git, yet. You can look here to get the current version. Or here to get a patch file.
     
  20. Tirrag

    Tirrag Avatar

    Messages:
    853
    Likes Received:
    1,820
    Trophy Points:
    93
    Location:
    Iowa, USA
    Relative pathing fixed my texture loading issue. Thank you! I have a rough first mod finally and tried it out tonight. When I tank, and for my playstyle, keeping defensive stance on is extremely important. In the heat of battle its hard to keep an eye on my resistance value in the paperdoll window. I now have an indicator that alerts me to when I need to refresh it :) Hooked it up to rightalt+D. Once it is more refined I will be sure to post it out. Hoping that once more things are exposed in the API (ie buff info) I can really make this mod generic to monitor all kinds of buffs. Again thank you!
     
Thread Status:
Not open for further replies.