text:
local quowMapsByID = {
	--    { filename, display name, gridX, gridY, ~centerX, ~centerY, region }
	[1] = { "am.png", "Ankh-Morpork", 14, 14, 718, 802, "AM" },
	[2] = { "am_assassins.png", "AM Assassins", 28, 28, 457, 61, "AM" },
	[3] = { "am_buildings.png", "AM Buildings", 25, 25, 208, 76, "AM" },
	[4] = { "am_cruets.png", "AM Cruets", 24, 24, 300, 69, "AM" },
	[5] = { "am_docks.png", "AM Docks", 14, 14, 174, 216, "AM" },
	[6] = { "am_guilds.png", "AM Guilds", 28, 28, 487, 245, "AM" },
	[7] = { "am_isle_gods.png", "AM Isle of Gods", 24, 24, 342, 587, "AM" },
	[8] = { "am_shades.png", "Shades Maze", 80, 80, 46, 179, "AM" },
	[9] = { "am_smallgods.png", "Temple of Small Gods", 24, 24, 221, 224, "AM" },
	[10] = { "am_temples.png", "AM Temples", 24, 24, 575, 419, "AM" },
	[11] = { "am_thieves.png", "AM Thieves", 28, 28, 431, 300, "AM" },
	[12] = { "am_uu.png", "Unseen University", 28, 28, 166, 393, "AM" },
	[13] = { "am_warriors.png", "AM Warriors", 32, 25, 135, 104, "AM" },
	[14] = { "am_watch_house.png", "Pseudopolis Watch House", 24, 24, 88, 104, "AM" },
	[15] = { "magpyr.png", "Magpyr's Castle", 20, 20, 141, 440, "Magpyr" },
	[16] = { "bois.png", "Bois", 14, 14, 239, 169, "Bois" },
	[17] = { "bp.png", "Bes Pelargic", 14, 14, 1070, 748, "BP" },
	[18] = { "bp_buildings.png", "BP Buildings", 24, 24, 428, 177, "BP" },
	[19] = { "bp_estates.png", "BP Estates", 14, 14, 540, 506, "BP" },
	[20] = { "bp_wizards.png", "BP Wizards", 20, 20, 101, 517, "BP" },
	[21] = { "brown_islands.png", "Brown Islands", 28, 28, 105, 101, "Brown" },
	[22] = { "deaths_domain.png", "Death's Domain", 28, 28, 98, 86, "Death" },
	[23] = { "djb.png", "Djelibeybi", 14, 14, 438, 369, "DJB" },
	[24] = { "djb_wizards.png", "IIL - DJB Wizards", 28, 28, 210, 210, "DJB" },
	[25] = { "ephebe.png", "Ephebe", 14, 14, 407, 349, "Ephebe" },
	[26] = { "ephebe_under.png", "Ephebe Underdocks", 14, 14, 247, 285, "Ephebe" },
	[27] = { "genua.png", "Genua", 14, 14, 470, 242, "Genua" },
	[28] = { "genua_sewers.png", "Genua Sewers", 21, 21, 405, 312, "Genua" },
	[29] = { "grflx.png", "GRFLX Caves", 20, 20, 303, 222, "GRFLX" },
	[30] = { "hashishim_caves.png", "Hashishim Caves", 28, 28, 258, 132, "Klatch" },
	[31] = { "klatch.png", "Klatch Region", 14, 14, 724, 515, "Klatch" },
	[32] = { "lancre_castle.png", "Lancre Region", 14, 14, 285, 33, "Ramtops" },
	[33] = { "mano_rossa.png", "Mano Rossa", 28, 28, 298, 202, "Genua" },
	[34] = { "monks_cool.png", "Monks of Cool", 14, 14, 113, 170, "Ramtops" },
	[35] = { "netherworld.png", "Netherworld", 14, 14, 42, 75, "Nether" },
	[37] = { "pumpkin_town.png", "Pumpkin Town", 48, 48, 375, 194, "Pumpkin" },
	[38] = { "ramtops.png", "Ramtops Regions", 14, 14, 827, 223, "Ramtops" },
	[39] = { "sl.png", "Sto-Lat", 14, 14, 367, 222, "Sto-Lat" },
	[40] = { "sl_aoa.png", "Academy of Artificers", 25, 25, 47, 87, "Sto-Lat" },
	[41] = { "sl_cabbages.png", "Cabbage Warehouse", 28, 28, 60, 92, "Sto-Lat" },
	[42] = { "sl_library.png", "AoA Library", 57, 57, 220, 411, "Sto-Lat" },
	[43] = { "sl_sewers.png", "Sto-Lat Sewers", 14, 14, 162, 204, "Sto-Lat" },
	[44] = { "sprite_caves.png", "Sprite Caves", 14, 14, 113, 182, "Sprites" },
	[45] = { "sto_plains.png", "Sto Plains Region", 14, 14, 752, 390, "Sto-Plains" },
	[46] = { "uberwald.png", "Uberwald Region", 14, 14, 673, 643, "Uber" },
	[47] = { "uu_library_full.png", "UU Library", 30, 30, 165, 4810, "UU" },
	[48] = { "farmsteads.png", "Klatchian Farmsteads", 28, 28, 445, 171, "Klatch" },
	[49] = { "ctf_arena.png", "CTF Arena", 48, 48, 307, 283, "CTF" },
	[50] = { "pk_arena.png", "PK Arena", 30, 30, 155, 331, "PK" },
	[51] = { "am_postoffice.png", "AM Post Office", 28, 28, 156, 69, "AM" },
	[52] = { "bp_ninjas.png", "Ninja Guild", 28, 28, 109, 56, "BP" },
	[53] = { "tshop.png", "The Travelling Shop", 28, 28, 355, 315, "T-Shop" },
	[54] = { "slippery_hollow.png", "Slippery Hollow", 14, 14, 215, 123, "S-Hollow" },
	[55] = { "creel_guild.png", "House of Magic - Creel", 28, 28, 38, 86, "Ramtops" },
	[56] = { "quow_specials.png", "Special Areas", 28, 28, 288, 28, "Misc" },
	[57] = { "skund_wolftrails.png", "Skund Wolf Trail", 12, 12, 41, 587, "Skund" },
	[58] = { "medina.png", "Medina", 38, 38, 131, 126, "BP" },
	[59] = { "copperhead.png", "Copperhead", 12, 12, 55, 47, "Copper" },
	[60] = { "ephebe_citadel.png", "The Citadel", 11, 11, 37, 74, "Ephebe" },
	[61] = { "am_fools.png", "AM Fools' Guild", 28, 28, 13, 65, "AM" },
	[62] = { "thursday.png", "Thursday's Island", 28, 28, 112, 65, "Thursday" },
	[63] = { "unsinkable.png", "SS Unsinkable", 28, 28, 143, 124, "Unsinkable" },
	[99] = { "discwhole.png", "Whole Disc", 1, 1, 1175, 3726, "Terrains" },
}

return quowMapsByID


text:
--# tilemap ------------------------------------------------#--

leaf.mainground = {}
leaf.background = {}

local block = {}

function block:new(pos, spr)

    local other = {}

    setmetatable(other, self)
    self.__index = self

    other.bpos = pos
    other.sprt = spr

    return other
end

function block:draw()
    local pos
    if leaf.drawmode == "default" then
        pos = self.bpos
    elseif leaf.drawmode == "pixper" then
        pos = leaf.vector(
            math.floor(self.bpos.x),
            math.floor(self.bpos.y)
        )
    end
    love.graphics.draw(
        leaf.tiled,
        self.sprt ,
        pos.x,
        pos.y
    )
end

--- sets the tilemap in the foreground (solid) and in the background\
--- note that the tilemap is ereased before set again
--- @param back table background tiles
--- @param main table mainground tiles
--- @param info table contains the tilemap metadata
--- @param itm  table contains the catchable items of the map
--- @param obj  table contains the data of the spawnable enemies in the map
--- @return vector spawn the spawn position found in the map
--- @return table  enemies all the enemies instantiated by the map
function leaf.tilemap(back, main, info, itm, obj)
    -- info.skipt: nonsolid tiles --
    -- info.jthru: jumpthru tiles --
    -- info.index: metatiles data --
    leaf.background = {}
    leaf.mainground = {}

    -- clear platforms --
    leaf.del_plat()

    -- item count --
    local itmc = 0
    -- metatiles --
    local spawn
    local _temp
    local enemy = {}

    assert(back, 'missing tilemap data (back is nil)')

    -- for layer in background --
    for _, l in ipairs(back) do
        -- for tile in layer --
        for _, t in ipairs(l) do

            local sprt = love.graphics.newQuad(

                t.s.x, t.s.y, 8, 8, leaf.tiled:getDimensions()
            )
            local tile = block:new(leaf.vector(t.p.x, t.p.y, 8), sprt)

            if info then
                -- avoid nil indexing --
                if not (info.skipt or info.jthru) then

                    leaf.add_plat('solid', leaf.newsqr(t.p.x * 8, t.p.y * 8, 8))
                    goto continue
                end

                if     info.skipt[t.c] then goto continue
                elseif info.jthru[t.c] then

                    leaf.add_plat('jthru', leaf.newsqr(t.p.x * 8, t.p.y * 8, 8))

                else leaf.add_plat('solid', leaf.newsqr(t.p.x * 8, t.p.y * 8, 8)) end
            end

            ::continue::
            leaf.background[#leaf.background + 1] = tile
        end
    end

    if main then
        for _, t in pairs(main) do

            local sprt = love.graphics.newQuad(

                t.s.x, t.s.y, 8, 8, leaf.tiled:getDimensions()
            )
            local tile = block:new(leaf.vector(t.p.x, t.p.y, 8), sprt)

            if info and info.index then

                if itm and itm.tile[t.c] then

                    leaf.add_itm(itm.name[t.c] .. (itmc), t.p * 8, t.s, itm.wall[t.c])

                    itmc = itmc + 1
                    goto continue
                end

                if info.index.spawn == t.c then

                    spawn = t.p
                    goto continue
                end

                if info.index.enemy[t.c] then

                    if not _temp then

                        _temp = t.p
                    else
                        enemy[#enemy + 1] = leaf.create(

                            obj[t.c].name,
                            _temp.x * 8 + 8,
                                t.p.x * 8 - 8,
                            leaf.vector(t.p.x - 1, _temp.y, 8),
                            obj[t.c].clip
                        )
                        _temp = nil
                    end

                else leaf.mainground[#leaf.mainground + 1] = tile end

            else leaf.mainground[#leaf.mainground + 1] = tile end
            ::continue::
        end
    end
    return spawn, enemy
end

--- decodes a binary file outputed by the Ethereal tilemap editor
--- @param file string file path
--- @return table back the
--- @return table main
function leaf.decoder(file)
    local info = love.filesystem.getInfo(file)
    local cntt = love.filesystem.read(file)

    local backdata
    local maindata = cntt:sub(info.size - 288, info.size):split('ç')

    local _back = {}
    local _main = {}

    for chunk = 0, info.size - 376, 288 do
        -- init table --
        _back[chunk / 288 + 1] = {}

        backdata =
        cntt:sub(chunk, chunk + 288):split('ç')

        for y = 0, #backdata - 1 do
            --- internal class\
            --- type that holds the (final) tiles data, i.e. the tiles used by leaf.tilemap
            --- @class tileobj
            --- @field p vector
            --- @field s vector
            --- @field c number

            local line = backdata[y + 1]
            for x = 0, #line - 1 do

                local rt = line:sub(x + 1, x + 1):byte()
                local sx = rt % 16
                local sy = (rt - sx) / 16

                --- @type tileobj
                local tile = {
                    p = leaf.vector(x * 8 + 4, y * 8 + 4, 0.125),
                    s = leaf.vector(sx, sy, 8),
                    c = rt
                }
                table.insert(_back[chunk / 288 + 1], tile)
            end
        end
    end

    for y = 0, #maindata - 1 do

        local line = maindata[y + 1]
        for x = 0, #line - 1 do

            local rt = line:sub(x + 1, x + 1):byte()
            local sx = rt % 16
            local sy = (rt - sx) / 16

            --- @type tileobj
            local tile = {
                p = leaf.vector(x * 8 + 4, y * 8 + 4, 0.125),
                s = leaf.vector(sx, sy, 8),
                c = rt
            }
            table.insert(_main, tile)
        end
    end
    return _back, _main
end

local function blink(it)

    if not it.exst then
        -- blink --
        if it.t > 0 then

            leaf.color(99, 199, 77, it.t)

            leaf.rectb(it.x, it.y, 8)
            it.t = it.t - 10

            leaf.color()
        -- del item --
        else leaf.items[it.name] = nil end
    end
end

function leaf.draw_tilemap()
    -- draw each tile --
    for _, tile in pairs(leaf.background) do

        tile:draw()
    end

    for _, tile in pairs(leaf.mainground) do

        tile:draw()
    end
    -- draw items --
    for _, itm in pairs(leaf.items) do

        blink(itm)
    end
end

function leaf.add_tile(name, spos, sprt, wall)

    local spr = love.graphics.newQuad(
        sprt.x,
        sprt.y,
        8,
        8,
        leaf.tiled:getDimensions()
    )

    leaf.mainground[name] = block:new(spos, spr)

    if wall then

        leaf.add_plat('solid', leaf.newsqr(spos.x, spos.y, 8), name)
    end
end

function leaf.del_tile(name)

    if not name then

        leaf.mainground = {}
        leaf.background = {}

        leaf.del_plat()
    else
        leaf.mainground[name] = nil
        leaf.del_plat(name)
    end
end

--# animator -----------------------------------------------#--

--- animator class\
--- you should not access the animator fields. instead,
--- it supposed to be used only by the other classes, internally
--- @class animator
--- @field afps number the current count of frames per second of the animation
--- @field canm string the name of the current animation
--- @field cfrm number current frame of the animation
--- @field nfrm number the index of the current frame in the animation src table
--- @field reload boolean sets the animator to restart the animation (set things to firt frame)
--- @field timr number the internal timer of the animation
local anim = {}

--- animation src
--- @class asrc
--- @field count number the count of frames of the animation
--- @field row number the sprite sheet table row where the animation is
--- @field name string the name of the animation

function anim:load()

    local other = {}

    setmetatable(other, self)
    self.__index = self

    return other
end

function anim:init(frame)
    -- time cotrol --
    self.afps = 0
    self.timr = 1 / self.afps

    -- initidal frame --
    self.reload = false
    if not frame then frame = leaf.vector(0, 0) end
    -- open and store data to animate --
    self.quad = love.graphics.newQuad(frame.x * 8, frame.y * 8, 8, 8, leaf.sheet:getDimensions())
end

function anim:play(dt, anm, speed, loop)
    -- reset to a new animation --
    if self.canm ~= anm.name then self.reload = true end
    if self.reload then

        self.canm = anm.name
        self.afps = speed
        self.timr = 1 / self.afps

        self.cfrm = anm[0]
        self.nfrm = 0

        -- Set the first frame --
        self.quad:setViewport(self.cfrm * 8, anm.row * 8, 8, 8)

        self.reload = false
    end

    -- wait if is too slow --
    if dt > 0.035 then return end
    -- wait for next frame --
    self.timr = self.timr - dt

    -- no time left --
    if self.timr <= 0 then
        -- reset timer --
        self.timr = 1 / self.afps

        self.nfrm = self.nfrm + 1

        -- for Loop --
        if self.nfrm > anm.count - 1 and loop then self.nfrm = 0 end
        if self.nfrm > anm.count - 1 and not loop then return true end

        -- next frame --
        self.cfrm = anm[self.nfrm]
        -- update frame --
        self.quad:setViewport(self.cfrm * 8, anm.row * 8, 8, 8)
    end
end

function anim:loop()

    self.reload = true
end

function anim:draw(pos, side)
    -- check for nil --
    assert(pos and side, 'the given values are nil')

    if math.abs(side) > 1 then

        side = math.abs(side) / side
    end

    local xoff = math.min(math.min(8 * side, 8), 0)

    if leaf.drawmode == "pixper" then
        pos = leaf.vector(
            math.floor(pos.x),
            math.floor(pos.y)
        )
    end

    love.graphics.draw(leaf.sheet, self.quad,
    math.floor(pos.x - xoff), math.floor(pos.y), 0, side, 1)
end

function leaf.anim(ifrm)

    local out = anim:load()
    out:init(ifrm)

    return out
end

-- animation source --
local function default(name, rw, fx, lx, op)

    fx = fx or 0
    lx = lx or fx

    local src = {}
    -- frames --
    for a = 0, lx - fx do

        src[a] = fx + a
    end
    -- optional frames --
    if op then

        for i = lx - fx + 1, lx - fx + op[1] + 1 do

            src[i] = op[2]
        end
    end

    -- frame count --
    src.count = #src
    -- animation row --
    src.row = rw or 0

    -- animation --
    src.name = name

    return src
end

local function stepped(name, rw, fx, mx, lx)

    fx = fx or 1
    mx = mx or fx - 1
    lx = lx or fx + 1

    local src = {[0] = fx, [1] = mx, [2] = lx, [3] = mx}

    -- frame count --
    src.count = 4
    -- animation row --
    src.row = rw or 0

    -- animation --
    src.name = name

    return src
end

--- returns a new animation source (sample of sprites for the animator class).
--- the aditional arguments can be int pairs (table) or ints themselves.
--- when in pairs the [1] sprite will be appended [2] times at the end of the animation.
--- when not, simply will be appended at the end of the animation.
--- @param name string the name of the animation
--- @param row  number the row of the sprite table where the animation is
--- @param frst number the position of the first sprite of the animation
--- @param last number the position of the last sprite of the animation
--- @return asrc src
function leaf.asrc(name, row, frst, last, ...)

    local src
    if name:sub(1, 4) == 'stp-' then

        src = stepped(name, row, frst, last, ...)

    elseif name:sub(1, 4) == 'def-' then

        src = default(name, row, frst, last, ...)

    else src = default(name, row, frst, last, ...) end

    return src
end

--# Text type ----------------------------------------------#--

leaf.texts = {}

local random_offset = leaf.vector(0, 0)

function leaf.popup(usr, msg)

    local os_n = love.system.getOS()

    if os_n == 'Windows' then

        os.execute('msg ' .. usr .. ' ' .. msg)

    elseif os_n == 'Linux' then

        os.execute('zenity --info --text="' .. msg .. '"')
    end
end

function leaf.txt_conf(font, size, speed)

    leaf.font = love.graphics.newFont('resources/' .. font, size)
    love.graphics.setFont(leaf.font)

    leaf.font_size  = size
    leaf.text_speed = speed
end

function leaf.new_txt(tmsg, ypos, effect, trigger, tgrTime)

    -- avoid overlaping --
    if leaf.txt_exist(tmsg) then return end
    if not effect then effect = "" end

    -- Invalid arguments --
    assert(type(tmsg) == "string", 'Attempt to create a text with a not-string message')
    assert(type(ypos) == "number", 'Attempt to draw a text at a non-numeric position (ypos)')
    assert(type(effect) == "string", 'Attempt to create a text with a invalid effect type')

    local t = {
        pos = leaf.vector(0, ypos),
        -- effects --
        tgr = trigger,
        ttm = tgrTime,
        efx = effect ,
        -- timer --
        cps   = leaf.text_speed    ,
        speed = leaf.text_speed    ,
        timer = 1 / leaf.text_speed,
        -- text --
        msg   = tmsg ,
        ctext = ''   ,
        ended = false,
    }

    table.insert(leaf.texts, t)
end

function leaf.type_txt(dt, sound, channel)

    for _, t in pairs(leaf.texts) do
        -- set random offset --
        random_offset.x = math.random(1, 64)
        random_offset.y = math.random(1, 64)

        -- dramatic waiting --
        if t.tgr and leaf.table_find(t.tgr, #t.ctext) then t.cps = t.ttm
        else t.cps = t.speed end

        -- if isn't too slow --
        if dt > 0.035 then return end

        t.timer = t.timer - dt

        -- show text --
        if t.timer <= 0 then

            t.timer = 1 / t.cps
            if t.ctext ~= t.msg then

                t.ctext = t.ctext .. t.msg:sub(#t.ctext + 1, #t.ctext + 1)
                -- text sound --
                if sound then leaf.gramo.play(sound, channel or 7) end

            else t.ended = true end
        end

        -- set the midle position --
        t.pos.x = leaf.s_wdth / 2 - leaf.font:getWidth(t.ctext) / 2
    end
end

function leaf.draw_text()

    for _, t in pairs(leaf.texts) do
        local pos
        if leaf.drawmode == "default" then
            pos = t.pos
        elseif leaf.drawmode == "pixper" then
            pos = leaf.vector(
                math.floor(t.pos.x),
                math.floor(t.pos.y)
            )
        end
        -- draw effects --
        if t.efx == 'noises' then

            leaf.color(0, 0, 255, 127.5)
            love.graphics.print(t.ctext,
            pos.x + math.floor(random_offset.x / 12), pos.y)

            leaf.color(255, 0, 0, 127.5)
            love.graphics.print(t.ctext,
            pos.x - math.floor(random_offset.y / 12), pos.y)

            -- draw main text --
            leaf.color(255, 255, 255)
            love.graphics.print(t.ctext, pos.x, pos.y)

        elseif t.efx == 'wobbly' then

            leaf.color(255, 255, 255, 127.5)
            -- loop thru the chars --
            for c = 1, #t.ctext do
                love.graphics.print(t.ctext:sub(c, c),
                    pos.x + leaf.font:getWidth(t.ctext:sub(1, c - 1)),
                    pos.y + math.floor(math.sin(leaf.ctm * 16 + c * leaf.font_size) * 2)
                )
            end
        elseif t.efx == 'glitch' then
            -- random control of noise direction
            local ctrl
            if random_offset.y % 24 == 0 then ctrl = 1
            else ctrl = 0 end

            -- control of non-user-end variables --
            if not t.__txt then t.__txt = t.ctext end
            if not t.__fxt then t.__fxt = 0
            elseif t.__fxt > 0 then t.__fxt = t.__fxt - 1 end

            -- if the wating time has ended --
            if t.__fxt == 0 then
                -- update drawing-text value --
                t.__txt = t.ctext

                -- if god has picked up this frame, --
                -- change one of the chars by other --
                if random_offset.x % 32 == 0 then
                    -- pick a random char and replace it --
                    local _p = random_offset.y % #t.ctext + 1
                    t.__txt = t.ctext:gsub(
                        t.ctext:sub(_p, math.min(_p + 2, #t.ctext)),
                        string.char(math.random(33, 127)) ..
                        t.ctext:sub(_p + 1, math.min(_p + 2, #t.ctext))
                    )
                    t.__fxt = 10
                end
            end

            -- once in a while slice the string --
            local half = math.random(1, #t.ctext) * ctrl

            leaf.color(0, 0, 255, 127.5)
            love.graphics.print(t.__txt,
                pos.x - math.floor(random_offset.y / leaf.font_size) *
                math.random(-1, 1) * math.floor(half / 2),

                pos.y - math.floor(random_offset.x / leaf.font_size) *
                math.random(-1, 1) * math.floor(half / 2))

            -- set random offset --
            random_offset.x = math.random(1, 64)
            random_offset.y = math.random(1, 64)

            leaf.color(255, 0, 0, 127.5)
            love.graphics.print(t.__txt,
                pos.x - math.floor(random_offset.y / leaf.font_size) *
                math.random(-1, 1) * math.floor(half / 2),

                pos.y - math.floor(random_offset.x / leaf.font_size) *
                math.random(-1, 1) * math.floor(half / 2))

            -- set random offset --
            random_offset.x = math.random(1, 64)
            random_offset.y = math.random(1, 64)

            leaf.color(255, 255, 255, 127.5)
            love.graphics.print(t.__txt:sub(1, half),
                pos.x - math.floor(random_offset.y / leaf.font_size) *
                math.random(-1, 1) * math.floor(half / 2),

                pos.y - math.floor(random_offset.x / leaf.font_size) *
                math.random(-1, 1) * math.floor(half / 2))

            -- set random offset --
            random_offset.x = math.random(1, 64)
            random_offset.y = math.random(1, 64)

            love.graphics.print(t.__txt:sub(half),
                pos.x + leaf.font:getWidth(t.__txt:sub(1, half))
                - math.floor(random_offset.y / leaf.font_size) * math.random(-1, 1) * half,
                pos.y - math.floor(random_offset.x / leaf.font_size) * math.random(-1, 1) * half)

        elseif t.efx == 'shaking' then
            for c = 1, #t.ctext do
                local r = random_offset.x % 8 == 0
                if r then r = 1 else r = 0 end

                leaf.color(255, 255, 255, 127.5)

                love.graphics.print(t.ctext:sub(c, c),
                    pos.x + (c - 1) * leaf.font:getWidth(t.ctext)
                    - math.floor(random_offset.y % (leaf.font_size / 5)) * math.random(r - 1, 1),
                    pos.y - math.floor(random_offset.x % (leaf.font_size / 5)) * math.random(r - 1, 1))

                -- set random offset --
                random_offset.x = math.random(1, 64)
                random_offset.y = math.random(1, 64)
            end
        end
    end

    leaf.color()
end

function leaf.txt_exist(idxr)

    for i, t in pairs(leaf.texts) do

        if t.msg == idxr then return true end
    end

    return false
end

function leaf.txt_end(idxr)

    for i, t in pairs(leaf.texts) do

        if t.msg == idxr then return t.ended end
    end
end

function leaf.del_txt(idxr)

    if not idxr then
        local cntt = #leaf.texts
        local unff = 0

        for _, t in ipairs(leaf.texts) do

            if not leaf.txt_end(t.msg) then unff = unff + 1 end
        end

        leaf.texts = {}
        return cntt, unff
    end

    for i, t in pairs(leaf.texts) do

        if t.msg == idxr then leaf.texts[i] = nil end
    end
end

function leaf.pop_txt()

    for i, t in pairs(leaf.texts) do

        if t.ended then leaf.texts[i] = nil end
    end

    collectgarbage('collect')

domain: lua


text:
package("entt")

    set_homepage("https://github.com/skypjack/entt")
    set_description("Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more.")

    set_urls("https://github.com/skypjack/entt/archive/$(version).tar.gz",
             "https://github.com/skypjack/entt.git")

    add_versions("v3.6.0", "94b7dc874acd0961cfc28cf6b0342eeb0b7c58250ddde8bdc6c101e84b74c190")
    add_deps("cmake")

    on_install(function (package)
        local configs = {}
        import("package.tools.cmake").install(package, configs)
    end)

    on_test(function (package)
        assert(package:check_cxxsnippets({test = [[
            #include <entt/entt.hpp>
            void test() {
                entt::registry r;
            }
        ]]}, {configs = {languages = "c++17"}}))
    end)


text:
AuctionatorAPIv1MultiSearchFrame = CreateFromMixins(AuctionatorKeywordSearchProviderMixin)

function AuctionatorAPIv1MultiSearchFrame:OnLoad()
  self:InitSearch(
    function(results)
      self:EndSearch(results)
    end,
    function(current, total)
      self:ShowSpinner()
    end
  )
end

function AuctionatorAPIv1MultiSearchFrame:OnEvent(eventName, ...)
  self:OnSearchEvent(eventName, ...)
end

function AuctionatorAPIv1MultiSearchFrame:ShowSpinner()
  AuctionHouseFrame.BrowseResultsFrame.ItemList.LoadingSpinner:Show()
end

function AuctionatorAPIv1MultiSearchFrame:HideSpinner()
  AuctionHouseFrame.BrowseResultsFrame.ItemList.LoadingSpinner:Hide()
end

function AuctionatorAPIv1MultiSearchFrame:StartSearch(searchTerms)
  self:Search(searchTerms)
  self:ShowSpinner()
end

function AuctionatorAPIv1MultiSearchFrame:EndSearch(results)
  Auctionator.Search.SafeItemKeysSearch(results)
  self:HideSpinner()
end


text:
---@class HairStyles : zombie.core.skinnedmodel.population.HairStyles
---@field public m_MaleStyles ArrayList|Unknown
---@field public m_FemaleStyles ArrayList|Unknown
---@field public instance HairStyles
HairStyles = {}

---@public
---@param arg0 HairStyle
---@param arg1 String
---@return HairStyle
function HairStyles:getAlternateForHat(arg0, arg1) end

---@public
---@param arg0 String
---@return HairStyles
function HairStyles:parse(arg0) end

---@public
---@return void
function HairStyles:init() end

---@public
---@param arg0 String
---@return HairStyle
function HairStyles:FindMaleStyle(arg0) end

---@public
---@return ArrayList|Unknown
function HairStyles:getAllFemaleStyles() end

---@public
---@param arg0 String
---@return HairStyle
function HairStyles:FindFemaleStyle(arg0) end

---@public
---@param arg0 String
---@return HairStyles
function HairStyles:Parse(arg0) end

---@public
---@param arg0 String
---@return String
function HairStyles:getRandomMaleStyle(arg0) end

---@public
---@param arg0 String
---@return String
function HairStyles:getRandomFemaleStyle(arg0) end

---@public
---@return void
function HairStyles:Reset() end

---@private
---@param arg0 ArrayList|Unknown
---@param arg1 String
---@return HairStyle
function HairStyles:FindStyle(arg0, arg1) end

---@public
---@return ArrayList|Unknown
function HairStyles:getAllMaleStyles() end


text:
local options = {
  backup = false,                          -- creates a backup file
  clipboard = "unnamedplus",               -- allows neovim to access the system clipboard
  cmdheight = 2,                           -- more space in the neovim command line for displaying messages
  completeopt = { "menuone", "noselect" }, -- mostly just for cmp
  conceallevel = 0,                        -- so that `` is visible in markdown files
  fileencoding = "utf-8",                  -- the encoding written to a file
  hlsearch = true,                         -- highlight all matches on previous search pattern
  ignorecase = true,                       -- ignore case in search patterns
  mouse = "a",                             -- allow the mouse to be used in neovim
  pumheight = 10,                          -- pop up menu height
  showmode = false,                        -- we don't need to see things like -- INSERT -- anymore
  showtabline = 2,                         -- always show tabs
  smartcase = true,                        -- smart case
  smartindent = true,                      -- make indenting smarter again
  splitbelow = true,                       -- force all horizontal splits to go below current window
  splitright = true,                       -- force all vertical splits to go to the right of current window
  swapfile = false,                        -- creates a swapfile
  -- termguicolors = true,                    -- set term gui colors (most terminals support this)
  timeoutlen = 100,                        -- time to wait for a mapped sequence to complete (in milliseconds)
  undofile = true,                         -- enable persistent undo
  updatetime = 300,                        -- faster completion (4000ms default)
  writebackup = false,                     -- if a file is being edited by another program (or was written to file while editing with another program), it is not allowed to be edited
  expandtab = true,                        -- convert tabs to spaces
  shiftwidth = 2,                          -- the number of spaces inserted for each indentation
  tabstop = 2,                             -- insert 2 spaces for a tab
  cursorline = true,                       -- highlight the current line
  number = true,                           -- set numbered lines
  relativenumber = false,                  -- set relative numbered lines
  numberwidth = 4,                         -- set number column width to 2 {default 4}
  signcolumn = "yes",                      -- always show the sign column, otherwise it would shift the text each time
  wrap = false,                            -- display lines as one long line
  scrolloff = 8,                           -- is one of my fav
  sidescrolloff = 8,
  guifont = "monospace:h17",               -- the font used in graphical neovim applications
}

vim.opt.shortmess:append "c"

for k, v in pairs(options) do
  vim.opt[k] = v
end

vim.cmd "set whichwrap+=<,>,[,],h,l"
vim.cmd [[set iskeyword+=-]]
vim.cmd [[set formatoptions-=cro]] -- TODO: this doesn't seem to work


text:
local pdtest = require("pdtest")

if arg[1] == nil then
   print("First argument must be OS type:\nie: linux, macos, win")
   os.exit()
end

-- the OS in use
local platform = arg[1]

local tests = assert(pdtest.get_fields("./suite.txt"), "PROBLEM OPENING: suite.txt")

print("TESTS:")
for k, v in ipairs(tests) do print(k, v) end
print()

-- in case there are flags to pass to the test
local flags = {}
if #arg > 1 then
   for i = 2, #arg do
      table.insert(flags, arg[i])
   end
end

pdtest.run(tests, platform, flags)


text:
require("firecast.lua");
local __o_rrpgObjs = require("rrpgObjs.lua");
require("rrpgGUI.lua");
require("rrpgDialogs.lua");
require("rrpgLFM.lua");
require("ndb.lua");
require("locale.lua");
local __o_Utils = require("utils.lua");

local function constructNew_frmtotem()
    local obj = GUI.fromHandle(_obj_newObject("form"));
    local self = obj;
    local sheet = nil;

    rawset(obj, "_oldSetNodeObjectFunction", rawget(obj, "setNodeObject"));

    function obj:setNodeObject(nodeObject)
        sheet = nodeObject;
        self.sheet = nodeObject;
        self:_oldSetNodeObjectFunction(nodeObject);
    end;

    function obj:setNodeDatabase(nodeObject)
        self:setNodeObject(nodeObject);
    end;

    _gui_assignInitialParentForForm(obj.handle);
    obj:beginUpdate();
    obj:setName("frmtotem");
    obj:setFormType("sheetTemplate");
    obj:setDataType("br.com.RRPG.WTFTV3_S3");
    obj:setTitle("Lobisomem: O Apocalipse - Ficha do Totem");
    obj:setAlign("client");
    obj:setTheme("light");

    obj.frmtotem1_svg = GUI.fromHandle(_obj_newObject("form"));
    obj.frmtotem1_svg:setParent(obj);
    obj.frmtotem1_svg:setName("frmtotem1_svg");
    obj.frmtotem1_svg:setAlign("client");
    obj.frmtotem1_svg:setTheme("light");
    obj.frmtotem1_svg:setMargins({top=1});

    obj.scrollBox1 = GUI.fromHandle(_obj_newObject("scrollBox"));
    obj.scrollBox1:setParent(obj.frmtotem1_svg);
    obj.scrollBox1:setAlign("client");
    obj.scrollBox1:setName("scrollBox1");

    obj.rectangle1 = GUI.fromHandle(_obj_newObject("rectangle"));
    obj.rectangle1:setParent(obj.scrollBox1);
    obj.rectangle1:setWidth(1369);
    obj.rectangle1:setHeight(1937);
    obj.rectangle1:setColor("white");
    obj.rectangle1:setName("rectangle1");

    obj.image1 = GUI.fromHandle(_obj_newObject("image"));
    obj.image1:setParent(obj.rectangle1);
    obj.image1:setLeft(0);
    obj.image1:setTop(0);
    obj.image1:setWidth(1369);
    obj.image1:setHeight(1937);
    obj.image1:setSRC("/Totem/images/t.png");
    obj.image1:setStyle("stretch");
    obj.image1:setOptimize(true);
    obj.image1:setName("image1");

    obj.layout1 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout1:setParent(obj.rectangle1);
    obj.layout1:setLeft(133);
    obj.layout1:setTop(680);
    obj.layout1:setWidth(537);
    obj.layout1:setHeight(112);
    obj.layout1:setName("layout1");

    obj.textEditor1 = GUI.fromHandle(_obj_newObject("textEditor"));
    obj.textEditor1:setParent(obj.layout1);
    obj.textEditor1:setTransparent(true);
    obj.textEditor1:setFontSize(21.9);
    obj.textEditor1:setFontColor("#000000");
    obj.textEditor1:setWidth(537);
    obj.textEditor1:setHeight(113);
    obj.textEditor1:setField("Caractersticas_1");
    obj.textEditor1:setName("textEditor1");

    obj.layout2 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout2:setParent(obj.rectangle1);
    obj.layout2:setLeft(701);
    obj.layout2:setTop(677);
    obj.layout2:setWidth(429);
    obj.layout2:setHeight(34);
    obj.layout2:setName("layout2");

    obj.edit1 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit1:setParent(obj.layout2);
    obj.edit1:setTransparent(true);
    obj.edit1:setFontSize(21.9);
    obj.edit1:setFontColor("#000000");
    obj.edit1:setHorzTextAlign("leading");
    obj.edit1:setVertTextAlign("center");
    obj.edit1:setLeft(0);
    obj.edit1:setTop(0);
    obj.edit1:setWidth(429);
    obj.edit1:setHeight(35);
    obj.edit1:setField("OOOOO");
    obj.edit1:setName("edit1");

    obj.layout3 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout3:setParent(obj.rectangle1);
    obj.layout3:setLeft(701);
    obj.layout3:setTop(713);
    obj.layout3:setWidth(429);
    obj.layout3:setHeight(31);
    obj.layout3:setName("layout3");

    obj.edit2 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit2:setParent(obj.layout3);
    obj.edit2:setTransparent(true);
    obj.edit2:setFontSize(21.1);
    obj.edit2:setFontColor("#000000");
    obj.edit2:setHorzTextAlign("leading");
    obj.edit2:setVertTextAlign("center");
    obj.edit2:setLeft(0);
    obj.edit2:setTop(0);
    obj.edit2:setWidth(429);
    obj.edit2:setHeight(32);
    obj.edit2:setField("OOOOO_2");
    obj.edit2:setName("edit2");

    obj.layout4 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout4:setParent(obj.rectangle1);
    obj.layout4:setLeft(701);
    obj.layout4:setTop(745);
    obj.layout4:setWidth(429);
    obj.layout4:setHeight(31);
    obj.layout4:setName("layout4");

    obj.edit3 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit3:setParent(obj.layout4);
    obj.edit3:setTransparent(true);
    obj.edit3:setFontSize(21.1);
    obj.edit3:setFontColor("#000000");
    obj.edit3:setHorzTextAlign("leading");
    obj.edit3:setVertTextAlign("center");
    obj.edit3:setLeft(0);
    obj.edit3:setTop(0);
    obj.edit3:setWidth(429);
    obj.edit3:setHeight(32);
    obj.edit3:setField("OOOOO_3");
    obj.edit3:setName("edit3");

    obj.layout5 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout5:setParent(obj.rectangle1);
    obj.layout5:setLeft(701);
    obj.layout5:setTop(778);
    obj.layout5:setWidth(429);
    obj.layout5:setHeight(32);
    obj.layout5:setName("layout5");

    obj.edit4 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit4:setParent(obj.layout5);
    obj.edit4:setTransparent(true);
    obj.edit4:setFontSize(21.8);
    obj.edit4:setFontColor("#000000");
    obj.edit4:setHorzTextAlign("leading");
    obj.edit4:setVertTextAlign("center");
    obj.edit4:setLeft(0);
    obj.edit4:setTop(0);
    obj.edit4:setWidth(429);
    obj.edit4:setHeight(33);
    obj.edit4:setField("OOOOO_4");
    obj.edit4:setName("edit4");

    obj.layout6 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout6:setParent(obj.rectangle1);
    obj.layout6:setLeft(701);
    obj.layout6:setTop(811);
    obj.layout6:setWidth(429);
    obj.layout6:setHeight(31);
    obj.layout6:setName("layout6");

    obj.edit5 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit5:setParent(obj.layout6);
    obj.edit5:setTransparent(true);
    obj.edit5:setFontSize(21.1);
    obj.edit5:setFontColor("#000000");
    obj.edit5:setHorzTextAlign("leading");
    obj.edit5:setVertTextAlign("center");
    obj.edit5:setLeft(0);
    obj.edit5:setTop(0);
    obj.edit5:setWidth(429);
    obj.edit5:setHeight(32);
    obj.edit5:setField("OOOOO_5");
    obj.edit5:setName("edit5");

    obj.layout7 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout7:setParent(obj.rectangle1);
    obj.layout7:setLeft(133);
    obj.layout7:setTop(870);
    obj.layout7:setWidth(537);
    obj.layout7:setHeight(105);
    obj.layout7:setName("layout7");

    obj.textEditor2 = GUI.fromHandle(_obj_newObject("textEditor"));
    obj.textEditor2:setParent(obj.layout7);
    obj.textEditor2:setTransparent(true);
    obj.textEditor2:setFontSize(21.9);
    obj.textEditor2:setFontColor("#000000");
    obj.textEditor2:setLeft(0);
    obj.textEditor2:setTop(0);
    obj.textEditor2:setWidth(537);
    obj.textEditor2:setHeight(106);
    obj.textEditor2:setField("Dogma_1");
    obj.textEditor2:setName("textEditor2");

    obj.layout8 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout8:setParent(obj.rectangle1);
    obj.layout8:setLeft(701);
    obj.layout8:setTop(844);
    obj.layout8:setWidth(429);
    obj.layout8:setHeight(31);
    obj.layout8:setName("layout8");

    obj.edit6 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit6:setParent(obj.layout8);
    obj.edit6:setTransparent(true);
    obj.edit6:setFontSize(21.1);
    obj.edit6:setFontColor("#000000");
    obj.edit6:setHorzTextAlign("leading");
    obj.edit6:setVertTextAlign("center");
    obj.edit6:setLeft(0);
    obj.edit6:setTop(0);
    obj.edit6:setWidth(429);
    obj.edit6:setHeight(32);
    obj.edit6:setField("OOOOO_6");
    obj.edit6:setName("edit6");

    obj.layout9 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout9:setParent(obj.rectangle1);
    obj.layout9:setLeft(701);
    obj.layout9:setTop(877);
    obj.layout9:setWidth(429);
    obj.layout9:setHeight(31);
    obj.layout9:setName("layout9");

    obj.edit7 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit7:setParent(obj.layout9);
    obj.edit7:setTransparent(true);
    obj.edit7:setFontSize(21.1);
    obj.edit7:setFontColor("#000000");
    obj.edit7:setHorzTextAlign("leading");
    obj.edit7:setVertTextAlign("center");
    obj.edit7:setLeft(0);
    obj.edit7:setTop(0);
    obj.edit7:setWidth(429);
    obj.edit7:setHeight(32);
    obj.edit7:setField("OOOOO_7");
    obj.edit7:setName("edit7");

    obj.layout10 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout10:setParent(obj.rectangle1);
    obj.layout10:setLeft(701);
    obj.layout10:setTop(910);
    obj.layout10:setWidth(429);
    obj.layout10:setHeight(31);
    obj.layout10:setName("layout10");

    obj.edit8 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit8:setParent(obj.layout10);
    obj.edit8:setTransparent(true);
    obj.edit8:setFontSize(21.1);
    obj.edit8:setFontColor("#000000");
    obj.edit8:setHorzTextAlign("leading");
    obj.edit8:setVertTextAlign("center");
    obj.edit8:setLeft(0);
    obj.edit8:setTop(0);
    obj.edit8:setWidth(429);
    obj.edit8:setHeight(32);
    obj.edit8:setField("OOOOO_8");
    obj.edit8:setName("edit8");

    obj.layout11 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout11:setParent(obj.rectangle1);
    obj.layout11:setLeft(701);
    obj.layout11:setTop(943);
    obj.layout11:setWidth(429);
    obj.layout11:setHeight(31);
    obj.layout11:setName("layout11");

    obj.edit9 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit9:setParent(obj.layout11);
    obj.edit9:setTransparent(true);
    obj.edit9:setFontSize(21.1);
    obj.edit9:setFontColor("#000000");
    obj.edit9:setHorzTextAlign("leading");
    obj.edit9:setVertTextAlign("center");
    obj.edit9:setLeft(0);
    obj.edit9:setTop(0);
    obj.edit9:setWidth(429);
    obj.edit9:setHeight(32);
    obj.edit9:setField("OOOOO_9");
    obj.edit9:setName("edit9");

    obj.layout12 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout12:setParent(obj.rectangle1);
    obj.layout12:setLeft(136);
    obj.layout12:setTop(1037);
    obj.layout12:setWidth(343);
    obj.layout12:setHeight(34);
    obj.layout12:setName("layout12");

    obj.edit10 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit10:setParent(obj.layout12);
    obj.edit10:setTransparent(true);
    obj.edit10:setFontSize(21.9);
    obj.edit10:setFontColor("#000000");
    obj.edit10:setHorzTextAlign("leading");
    obj.edit10:setVertTextAlign("center");
    obj.edit10:setLeft(0);
    obj.edit10:setTop(0);
    obj.edit10:setWidth(343);
    obj.edit10:setHeight(35);
    obj.edit10:setField("_1aaa");
    obj.edit10:setName("edit10");

    obj.layout13 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout13:setParent(obj.rectangle1);
    obj.layout13:setLeft(136);
    obj.layout13:setTop(1072);
    obj.layout13:setWidth(343);
    obj.layout13:setHeight(34);
    obj.layout13:setName("layout13");

    obj.edit11 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit11:setParent(obj.layout13);
    obj.edit11:setTransparent(true);
    obj.edit11:setFontSize(21.9);
    obj.edit11:setFontColor("#000000");
    obj.edit11:setHorzTextAlign("leading");
    obj.edit11:setVertTextAlign("center");
    obj.edit11:setLeft(0);
    obj.edit11:setTop(0);
    obj.edit11:setWidth(343);
    obj.edit11:setHeight(35);
    obj.edit11:setField("_2d");
    obj.edit11:setName("edit11");

    obj.layout14 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout14:setParent(obj.rectangle1);
    obj.layout14:setLeft(136);
    obj.layout14:setTop(1107);
    obj.layout14:setWidth(343);
    obj.layout14:setHeight(34);
    obj.layout14:setName("layout14");

    obj.edit12 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit12:setParent(obj.layout14);
    obj.edit12:setTransparent(true);
    obj.edit12:setFontSize(21.9);
    obj.edit12:setFontColor("#000000");
    obj.edit12:setHorzTextAlign("leading");
    obj.edit12:setVertTextAlign("center");
    obj.edit12:setLeft(0);
    obj.edit12:setTop(0);
    obj.edit12:setWidth(343);
    obj.edit12:setHeight(35);
    obj.edit12:setField("_3");
    obj.edit12:setName("edit12");

    obj.layout15 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout15:setParent(obj.rectangle1);
    obj.layout15:setLeft(136);
    obj.layout15:setTop(1142);
    obj.layout15:setWidth(343);
    obj.layout15:setHeight(34);
    obj.layout15:setName("layout15");

    obj.edit13 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit13:setParent(obj.layout15);
    obj.edit13:setTransparent(true);
    obj.edit13:setFontSize(21.9);
    obj.edit13:setFontColor("#000000");
    obj.edit13:setHorzTextAlign("leading");
    obj.edit13:setVertTextAlign("center");
    obj.edit13:setLeft(0);
    obj.edit13:setTop(0);
    obj.edit13:setWidth(343);
    obj.edit13:setHeight(35);
    obj.edit13:setField("_4aaaaaa");
    obj.edit13:setName("edit13");

    obj.layout16 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout16:setParent(obj.rectangle1);
    obj.layout16:setLeft(514);
    obj.layout16:setTop(1037);
    obj.layout16:setWidth(343);
    obj.layout16:setHeight(34);
    obj.layout16:setName("layout16");

    obj.edit14 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit14:setParent(obj.layout16);
    obj.edit14:setTransparent(true);
    obj.edit14:setFontSize(21.9);
    obj.edit14:setFontColor("#000000");
    obj.edit14:setHorzTextAlign("leading");
    obj.edit14:setVertTextAlign("center");
    obj.edit14:setLeft(0);
    obj.edit14:setTop(0);
    obj.edit14:setWidth(343);
    obj.edit14:setHeight(35);
    obj.edit14:setField("_1_2b");
    obj.edit14:setName("edit14");

    obj.layout17 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout17:setParent(obj.rectangle1);
    obj.layout17:setLeft(514);
    obj.layout17:setTop(1072);
    obj.layout17:setWidth(343);
    obj.layout17:setHeight(34);
    obj.layout17:setName("layout17");

    obj.edit15 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit15:setParent(obj.layout17);
    obj.edit15:setTransparent(true);
    obj.edit15:setFontSize(21.9);
    obj.edit15:setFontColor("#000000");
    obj.edit15:setHorzTextAlign("leading");
    obj.edit15:setVertTextAlign("center");
    obj.edit15:setLeft(0);
    obj.edit15:setTop(0);
    obj.edit15:setWidth(343);
    obj.edit15:setHeight(35);
    obj.edit15:setField("_2_2aaaaa");
    obj.edit15:setName("edit15");

    obj.layout18 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout18:setParent(obj.rectangle1);
    obj.layout18:setLeft(514);
    obj.layout18:setTop(1107);
    obj.layout18:setWidth(343);
    obj.layout18:setHeight(34);
    obj.layout18:setName("layout18");

    obj.edit16 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit16:setParent(obj.layout18);
    obj.edit16:setTransparent(true);
    obj.edit16:setFontSize(21.9);
    obj.edit16:setFontColor("#000000");
    obj.edit16:setHorzTextAlign("leading");
    obj.edit16:setVertTextAlign("center");
    obj.edit16:setLeft(0);
    obj.edit16:setTop(0);
    obj.edit16:setWidth(343);
    obj.edit16:setHeight(35);
    obj.edit16:setField("_3_2");
    obj.edit16:setName("edit16");

    obj.layout19 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout19:setParent(obj.rectangle1);
    obj.layout19:setLeft(514);
    obj.layout19:setTop(1142);
    obj.layout19:setWidth(343);
    obj.layout19:setHeight(34);
    obj.layout19:setName("layout19");

    obj.edit17 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit17:setParent(obj.layout19);
    obj.edit17:setTransparent(true);
    obj.edit17:setFontSize(21.9);
    obj.edit17:setFontColor("#000000");
    obj.edit17:setHorzTextAlign("leading");
    obj.edit17:setVertTextAlign("center");
    obj.edit17:setLeft(0);
    obj.edit17:setTop(0);
    obj.edit17:setWidth(343);
    obj.edit17:setHeight(35);
    obj.edit17:setField("_4_2");
    obj.edit17:setName("edit17");

    obj.layout20 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout20:setParent(obj.rectangle1);
    obj.layout20:setLeft(892);
    obj.layout20:setTop(1037);
    obj.layout20:setWidth(343);
    obj.layout20:setHeight(34);
    obj.layout20:setName("layout20");

    obj.edit18 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit18:setParent(obj.layout20);
    obj.edit18:setTransparent(true);
    obj.edit18:setFontSize(21.9);
    obj.edit18:setFontColor("#000000");
    obj.edit18:setHorzTextAlign("leading");
    obj.edit18:setVertTextAlign("center");
    obj.edit18:setLeft(0);
    obj.edit18:setTop(0);
    obj.edit18:setWidth(343);
    obj.edit18:setHeight(35);
    obj.edit18:setField("_1_3c");
    obj.edit18:setName("edit18");

    obj.layout21 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout21:setParent(obj.rectangle1);
    obj.layout21:setLeft(892);
    obj.layout21:setTop(1072);
    obj.layout21:setWidth(343);
    obj.layout21:setHeight(34);
    obj.layout21:setName("layout21");

    obj.edit19 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit19:setParent(obj.layout21);
    obj.edit19:setTransparent(true);
    obj.edit19:setFontSize(21.9);
    obj.edit19:setFontColor("#000000");
    obj.edit19:setHorzTextAlign("leading");
    obj.edit19:setVertTextAlign("center");
    obj.edit19:setLeft(0);
    obj.edit19:setTop(0);
    obj.edit19:setWidth(343);
    obj.edit19:setHeight(35);
    obj.edit19:setField("_2_3");
    obj.edit19:setName("edit19");

    obj.layout22 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout22:setParent(obj.rectangle1);
    obj.layout22:setLeft(892);
    obj.layout22:setTop(1107);
    obj.layout22:setWidth(343);
    obj.layout22:setHeight(34);
    obj.layout22:setName("layout22");

    obj.edit20 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit20:setParent(obj.layout22);
    obj.edit20:setTransparent(true);
    obj.edit20:setFontSize(21.9);
    obj.edit20:setFontColor("#000000");
    obj.edit20:setHorzTextAlign("leading");
    obj.edit20:setVertTextAlign("center");
    obj.edit20:setLeft(0);
    obj.edit20:setTop(0);
    obj.edit20:setWidth(343);
    obj.edit20:setHeight(35);
    obj.edit20:setField("_3_3");
    obj.edit20:setName("edit20");

    obj.layout23 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout23:setParent(obj.rectangle1);
    obj.layout23:setLeft(892);
    obj.layout23:setTop(1142);
    obj.layout23:setWidth(343);
    obj.layout23:setHeight(34);
    obj.layout23:setName("layout23");

    obj.edit21 = GUI.fromHandle(_obj_newObject("edit"));
    obj.edit21:setParent(obj.layout23);
    obj.edit21:setTransparent(true);
    obj.edit21:setFontSize(21.9);
    obj.edit21:setFontColor("#000000");
    obj.edit21:setHorzTextAlign("leading");
    obj.edit21:setVertTextAlign("center");
    obj.edit21:setLeft(0);
    obj.edit21:setTop(0);
    obj.edit21:setWidth(343);
    obj.edit21:setHeight(35);
    obj.edit21:setField("_4_3");
    obj.edit21:setName("edit21");

    obj.layout24 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout24:setParent(obj.rectangle1);
    obj.layout24:setLeft(136);
    obj.layout24:setTop(1286);
    obj.layout24:setWidth(343);
    obj.layout24:setHeight(284);
    obj.layout24:setName("layout24");

    obj.textEditor3 = GUI.fromHandle(_obj_newObject("textEditor"));
    obj.textEditor3:setParent(obj.layout24);
    obj.textEditor3:setTransparent(true);
    obj.textEditor3:setFontSize(21.9);
    obj.textEditor3:setFontColor("#000000");
    obj.textEditor3:setWidth(343);
    obj.textEditor3:setHeight(285);
    obj.textEditor3:setField("Outras_Caractersticas_1");
    obj.textEditor3:setName("textEditor3");

    obj.layout25 = GUI.fromHandle(_obj_newObject("layout"));
    obj.layout25:setParent(obj.rectangle1);
    obj.layout25:setLeft(133);
    obj.layout25:setTop(1632);
    obj.layout25:setWidth(1105);
    obj.layout25:setHeight(185);
    obj.layout25:setName("layout25");

    obj.textEditor4 = GUI.fromHandle(_obj_newObject("textEditor"));


text:
-- Current SHA: 9d2cca3846a96fee53000085e36638e74ed392ed
-- This is a generated file
local Qless = {
  ns = 'ql:'
}

local QlessQueue = {
  ns = Qless.ns .. 'q:'
}
QlessQueue.__index = QlessQueue

local QlessWorker = {
  ns = Qless.ns .. 'w:'
}
QlessWorker.__index = QlessWorker

local QlessJob = {
  ns = Qless.ns .. 'j:'
}
QlessJob.__index = QlessJob

local QlessRecurringJob = {}
QlessRecurringJob.__index = QlessRecurringJob

Qless.config = {}

function table.extend(self, other)
  for i, v in ipairs(other) do
    table.insert(self, v)
  end
end

function Qless.publish(channel, message)
  redis.call('publish', Qless.ns .. channel, message)
end

function Qless.job(jid)
  assert(jid, 'Job(): no jid provided')
  local job = {}
  setmetatable(job, QlessJob)
  job.jid = jid
  return job
end

function Qless.recurring(jid)
  assert(jid, 'Recurring(): no jid provided')
  local job = {}
  setmetatable(job, QlessRecurringJob)
  job.jid = jid
  return job
end

function Qless.failed(group, start, limit)
  start = assert(tonumber(start or 0),
    'Failed(): Arg "start" is not a number: ' .. (start or 'nil'))
  limit = assert(tonumber(limit or 25),
    'Failed(): Arg "limit" is not a number: ' .. (limit or 'nil'))

  if group then
    return {
      total = redis.call('llen', 'ql:f:' .. group),
      jobs  = redis.call('lrange', 'ql:f:' .. group, start, start + limit - 1)
    }
  else
    local response = {}
    local groups = redis.call('smembers', 'ql:failures')
    for index, group in ipairs(groups) do
      response[group] = redis.call('llen', 'ql:f:' .. group)
    end
    return response
  end
end

function Qless.jobs(now, state, ...)
  assert(state, 'Jobs(): Arg "state" missing')
  if state == 'complete' then
    local offset = assert(tonumber(arg[1] or 0),
      'Jobs(): Arg "offset" not a number: ' .. tostring(arg[1]))
    local count  = assert(tonumber(arg[2] or 25),
      'Jobs(): Arg "count" not a number: ' .. tostring(arg[2]))
    return redis.call('zrevrange', 'ql:completed', offset,
      offset + count - 1)
  else
    local name  = assert(arg[1], 'Jobs(): Arg "queue" missing')
    local offset = assert(tonumber(arg[2] or 0),
      'Jobs(): Arg "offset" not a number: ' .. tostring(arg[2]))
    local count  = assert(tonumber(arg[3] or 25),
      'Jobs(): Arg "count" not a number: ' .. tostring(arg[3]))

    local queue = Qless.queue(name)
    if state == 'running' then
      return queue.locks.peek(now, offset, count)
    elseif state == 'stalled' then
      return queue.locks.expired(now, offset, count)
    elseif state == 'scheduled' then
      queue:check_scheduled(now, queue.scheduled.length())
      return queue.scheduled.peek(now, offset, count)
    elseif state == 'depends' then
      return queue.depends.peek(now, offset, count)
    elseif state == 'recurring' then
      return queue.recurring.peek('+inf', offset, count)
    else
      error('Jobs(): Unknown type "' .. state .. '"')
    end
  end
end

function Qless.track(now, command, jid)
  if command ~= nil then
    assert(jid, 'Track(): Arg "jid" missing')
    assert(Qless.job(jid):exists(), 'Track(): Job does not exist')
    if string.lower(command) == 'track' then
      Qless.publish('track', jid)
      return redis.call('zadd', 'ql:tracked', now, jid)
    elseif string.lower(command) == 'untrack' then
      Qless.publish('untrack', jid)
      return redis.call('zrem', 'ql:tracked', jid)
    else
      error('Track(): Unknown action "' .. command .. '"')
    end
  else
    local response = {
      jobs = {},
      expired = {}
    }
    local jids = redis.call('zrange', 'ql:tracked', 0, -1)
    for index, jid in ipairs(jids) do
      local data = Qless.job(jid):data()
      if data then
        table.insert(response.jobs, data)
      else
        table.insert(response.expired, jid)
      end
    end
    return response
  end
end

function Qless.tag(now, command, ...)
  assert(command,
    'Tag(): Arg "command" must be "add", "remove", "get" or "top"')

  if command == 'add' then
    local jid  = assert(arg[1], 'Tag(): Arg "jid" missing')
    local tags = redis.call('hget', QlessJob.ns .. jid, 'tags')
    if tags then
      tags = cjson.decode(tags)
      local _tags = {}
      for i,v in ipairs(tags) do _tags[v] = true end

      for i=2,#arg do
        local tag = arg[i]
        if _tags[tag] == nil or _tags[tag] == false then
          _tags[tag] = true
          table.insert(tags, tag)
        end
        redis.call('zadd', 'ql:t:' .. tag, now, jid)
        redis.call('zincrby', 'ql:tags', 1, tag)
      end

      redis.call('hset', QlessJob.ns .. jid, 'tags', cjson.encode(tags))
      return tags
    else
      error('Tag(): Job ' .. jid .. ' does not exist')
    end
  elseif command == 'remove' then
    local jid  = assert(arg[1], 'Tag(): Arg "jid" missing')
    local tags = redis.call('hget', QlessJob.ns .. jid, 'tags')
    if tags then
      tags = cjson.decode(tags)
      local _tags = {}
      for i,v in ipairs(tags) do _tags[v] = true end

      for i=2,#arg do
        local tag = arg[i]
        _tags[tag] = nil
        redis.call('zrem', 'ql:t:' .. tag, jid)
        redis.call('zincrby', 'ql:tags', -1, tag)
      end

      local results = {}
      for i,tag in ipairs(tags) do if _tags[tag] then table.insert(results, tag) end end

      redis.call('hset', QlessJob.ns .. jid, 'tags', cjson.encode(results))
      return results
    else
      error('Tag(): Job ' .. jid .. ' does not exist')
    end
  elseif command == 'get' then
    local tag    = assert(arg[1], 'Tag(): Arg "tag" missing')
    local offset = assert(tonumber(arg[2] or 0),
      'Tag(): Arg "offset" not a number: ' .. tostring(arg[2]))
    local count  = assert(tonumber(arg[3] or 25),
      'Tag(): Arg "count" not a number: ' .. tostring(arg[3]))
    return {
      total = redis.call('zcard', 'ql:t:' .. tag),
      jobs  = redis.call('zrange', 'ql:t:' .. tag, offset, offset + count - 1)
    }
  elseif command == 'top' then
    local offset = assert(tonumber(arg[1] or 0) , 'Tag(): Arg "offset" not a number: ' .. tostring(arg[1]))
    local count  = assert(tonumber(arg[2] or 25), 'Tag(): Arg "count" not a number: ' .. tostring(arg[2]))
    return redis.call('zrevrangebyscore', 'ql:tags', '+inf', 2, 'limit', offset, count)
  else
    error('Tag(): First argument must be "add", "remove" or "get"')
  end
end

function Qless.cancel(...)
  local dependents = {}
  for _, jid in ipairs(arg) do
    dependents[jid] = redis.call(
      'smembers', QlessJob.ns .. jid .. '-dependents') or {}
  end

  for i, jid in ipairs(arg) do
    for j, dep in ipairs(dependents[jid]) do
      if dependents[dep] == nil or dependents[dep] == false then
        error('Cancel(): ' .. jid .. ' is a dependency of ' .. dep ..
           ' but is not mentioned to be canceled')
      end
    end
  end

  for _, jid in ipairs(arg) do
    local state, queue, failure, worker = unpack(redis.call(
      'hmget', QlessJob.ns .. jid, 'state', 'queue', 'failure', 'worker'))

    if state ~= 'complete' then
      local encoded = cjson.encode({
        jid    = jid,
        worker = worker,
        event  = 'canceled',
        queue  = queue
      })
      Qless.publish('log', encoded)

      if worker and (worker ~= '') then
        redis.call('zrem', 'ql:w:' .. worker .. ':jobs', jid)
        Qless.publish('w:' .. worker, encoded)
      end

      if queue then
        local queue = Qless.queue(queue)
        queue.work.remove(jid)
        queue.locks.remove(jid)
        queue.scheduled.remove(jid)
        queue.depends.remove(jid)
      end

      for i, j in ipairs(redis.call(
        'smembers', QlessJob.ns .. jid .. '-dependencies')) do
        redis.call('srem', QlessJob.ns .. j .. '-dependents', jid)
      end

      redis.call('del', QlessJob.ns .. jid .. '-dependencies')

      if state == 'failed' then
        failure = cjson.decode(failure)
        redis.call('lrem', 'ql:f:' .. failure.group, 0, jid)
        if redis.call('llen', 'ql:f:' .. failure.group) == 0 then
          redis.call('srem', 'ql:failures', failure.group)
        end
        local bin = failure.when - (failure.when % 86400)
        local failed = redis.call(
          'hget', 'ql:s:stats:' .. bin .. ':' .. queue, 'failed')
        redis.call('hset',
          'ql:s:stats:' .. bin .. ':' .. queue, 'failed', failed - 1)
      end

      local tags = cjson.decode(
        redis.call('hget', QlessJob.ns .. jid, 'tags') or '{}')
      for i, tag in ipairs(tags) do
        redis.call('zrem', 'ql:t:' .. tag, jid)
        redis.call('zincrby', 'ql:tags', -1, tag)
      end

      if redis.call('zscore', 'ql:tracked', jid) ~= false then
        Qless.publish('canceled', jid)
      end

      redis.call('del', QlessJob.ns .. jid)
      redis.call('del', QlessJob.ns .. jid .. '-history')
    end
  end

  return arg
end

Qless.config.defaults = {
  ['application']        = 'qless',
  ['heartbeat']          = 60,
  ['grace-period']       = 10,
  ['stats-history']      = 30,
  ['histogram-history']  = 7,
  ['jobs-history-count'] = 50000,
  ['jobs-history']       = 604800
}

Qless.config.get = function(key, default)
  if key then
    return redis.call('hget', 'ql:config', key) or
      Qless.config.defaults[key] or default
  else
    local reply = redis.call('hgetall', 'ql:config')
    for i = 1, #reply, 2 do
      Qless.config.defaults[reply[i]] = reply[i + 1]
    end
    return Qless.config.defaults
  end
end

Qless.config.set = function(option, value)
  assert(option, 'config.set(): Arg "option" missing')
  assert(value , 'config.set(): Arg "value" missing')
  Qless.publish('log', cjson.encode({
    event  = 'config_set',
    option = option,
    value  = value
  }))

  redis.call('hset', 'ql:config', option, value)
end

Qless.config.unset = function(option)
  assert(option, 'config.unset(): Arg "option" missing')
  Qless.publish('log', cjson.encode({
    event  = 'config_unset',
    option = option
  }))

  redis.call('hdel', 'ql:config', option)
end

function QlessJob:data(...)
  local job = redis.call(
      'hmget', QlessJob.ns .. self.jid, 'jid', 'klass', 'state', 'queue',
      'worker', 'priority', 'expires', 'retries', 'remaining', 'data',
      'tags', 'failure', 'spawned_from_jid')

  if not job[1] then
    return nil
  end

  local data = {
    jid              = job[1],
    klass            = job[2],
    state            = job[3],
    queue            = job[4],
    worker           = job[5] or '',
    tracked          = redis.call(
      'zscore', 'ql:tracked', self.jid) ~= false,
    priority         = tonumber(job[6]),
    expires          = tonumber(job[7]) or 0,
    retries          = tonumber(job[8]),
    remaining        = math.floor(tonumber(job[9])),
    data             = job[10],
    tags             = cjson.decode(job[11]),
    history          = self:history(),
    failure          = cjson.decode(job[12] or '{}'),
    spawned_from_jid = job[13],
    dependents       = redis.call(
      'smembers', QlessJob.ns .. self.jid .. '-dependents'),
    dependencies     = redis.call(
      'smembers', QlessJob.ns .. self.jid .. '-dependencies')
  }

  if #arg > 0 then
    local response = {}
    for index, key in ipairs(arg) do
      table.insert(response, data[key])
    end
    return response
  else
    return data
  end
end

function QlessJob:complete(now, worker, queue, raw_data, ...)
  assert(worker, 'Complete(): Arg "worker" missing')
  assert(queue , 'Complete(): Arg "queue" missing')
  local data = assert(cjson.decode(raw_data),
    'Complete(): Arg "data" missing or not JSON: ' .. tostring(raw_data))

  local options = {}
  for i = 1, #arg, 2 do options[arg[i]] = arg[i + 1] end

  local nextq   = options['next']
  local delay   = assert(tonumber(options['delay'] or 0))
  local depends = assert(cjson.decode(options['depends'] or '[]'),
    'Complete(): Arg "depends" not JSON: ' .. tostring(options['depends']))

  if options['delay'] and nextq == nil then
    error('Complete(): "delay" cannot be used without a "next".')
  end

  if options['depends'] and nextq == nil then
    error('Complete(): "depends" cannot be used without a "next".')
  end

  local bin = now - (now % 86400)

  local lastworker, state, priority, retries, current_queue = unpack(
    redis.call('hmget', QlessJob.ns .. self.jid, 'worker', 'state',
      'priority', 'retries', 'queue'))

  if lastworker == false then
    error('Complete(): Job ' .. self.jid .. ' does not exist')
  elseif (state ~= 'running') then
    error('Complete(): Job ' .. self.jid .. ' is not currently running: ' ..
      state)
  elseif lastworker ~= worker then
    error('Complete(): Job ' .. self.jid ..
      ' has been handed out to another worker: ' .. tostring(lastworker))
  elseif queue ~= current_queue then
    error('Complete(): Job ' .. self.jid .. ' running in another queue: ' ..
      tostring(current_queue))
  end

  self:history(now, 'done')

  if raw_data then
    redis.call('hset', QlessJob.ns .. self.jid, 'data', raw_data)
  end

  local queue_obj = Qless.queue(queue)
  queue_obj.work.remove(self.jid)
  queue_obj.locks.remove(self.jid)
  queue_obj.scheduled.remove(self.jid)

  local time = tonumber(
    redis.call('hget', QlessJob.ns .. self.jid, 'time') or now)
  local waiting = now - time
  Qless.queue(queue):stat(now, 'run', waiting)
  redis.call('hset', QlessJob.ns .. self.jid,
    'time', string.format("%.20f", now))

  redis.call('zrem', 'ql:w:' .. worker .. ':jobs', self.jid)

  if redis.call('zscore', 'ql:tracked', self.jid) ~= false then
    Qless.publish('completed', self.jid)
  end

  if nextq then
    queue_obj = Qless.queue(nextq)
    Qless.publish('log', cjson.encode({
      jid   = self.jid,
      event = 'advanced',
      queue = queue,
      to    = nextq
    }))

    self:history(now, 'put', {q = nextq})

    if redis.call('zscore', 'ql:queues', nextq) == false then
      redis.call('zadd', 'ql:queues', now, nextq)
    end

    redis.call('hmset', QlessJob.ns .. self.jid,
      'state', 'waiting',
      'worker', '',
      'failure', '{}',
      'queue', nextq,
      'expires', 0,
      'remaining', tonumber(retries))

    if (delay > 0) and (#depends == 0) then
      queue_obj.scheduled.add(now + delay, self.jid)
      return 'scheduled'
    else
      local count = 0
      for i, j in ipairs(depends) do
        local state = redis.call('hget', QlessJob.ns .. j, 'state')
        if (state and state ~= 'complete') then
          count = count + 1
          redis.call(
            'sadd', QlessJob.ns .. j .. '-dependents',self.jid)
          redis.call(
            'sadd', QlessJob.ns .. self.jid .. '-dependencies', j)
        end
      end
      if count > 0 then
        queue_obj.depends.add(now, self.jid)
        redis.call('hset', QlessJob.ns .. self.jid, 'state', 'depends')
        if delay > 0 then
          queue_obj.depends.add(now, self.jid)
          redis.call('hset', QlessJob.ns .. self.jid, 'scheduled', now + delay)
        end
        return 'depends'
      else
        queue_obj.work.add(now, priority, self.jid)
        return 'waiting'
      end
    end
  else
    Qless.publish('log', cjson.encode({
      jid   = self.jid,
      event = 'completed',
      queue = queue
    }))

    redis.call('hmset', QlessJob.ns .. self.jid,
      'state', 'complete',
      'worker', '',
      'failure', '{}',
      'queue', '',
      'expires', 0,
      'remaining', tonumber(retries))

    local count = Qless.config.get('jobs-history-count')
    local time  = Qless.config.get('jobs-history')

    count = tonumber(count or 50000)
    time  = tonumber(time  or 7 * 24 * 60 * 60)

    redis.call('zadd', 'ql:completed', now, self.jid)

    local jids = redis.call('zrangebyscore', 'ql:completed', 0, now - time)
    for index, jid in ipairs(jids) do
      local tags = cjson.decode(
        redis.call('hget', QlessJob.ns .. jid, 'tags') or '{}')
      for i, tag in ipairs(tags) do
        redis.call('zrem', 'ql:t:' .. tag, jid)
        redis.call('zincrby', 'ql:tags', -1, tag)
      end
      redis.call('del', QlessJob.ns .. jid)
      redis.call('del', QlessJob.ns .. jid .. '-history')
    end
    redis.call('zremrangebyscore', 'ql:completed', 0, now - time)

    jids = redis.call('zrange', 'ql:completed', 0, (-1-count))
    for index, jid in ipairs(jids) do
      local tags = cjson.decode(
        redis.call('hget', QlessJob.ns .. jid, 'tags') or '{}')
      for i, tag in ipairs(tags) do
        redis.call('zrem', 'ql:t:' .. tag, jid)
        redis.call('zincrby', 'ql:tags', -1, tag)
      end
      redis.call('del', QlessJob.ns .. jid)
      redis.call('del', QlessJob.ns .. jid .. '-history')
    end
    redis.call('zremrangebyrank', 'ql:completed', 0, (-1-count))

    for i, j in ipairs(redis.call(
      'smembers', QlessJob.ns .. self.jid .. '-dependents')) do
      redis.call('srem', QlessJob.ns .. j .. '-dependencies', self.jid)
      if redis.call(
        'scard', QlessJob.ns .. j .. '-dependencies') == 0 then
        local q, p, scheduled = unpack(
          redis.call('hmget', QlessJob.ns .. j, 'queue', 'priority', 'scheduled'))
        if q then
          local queue = Qless.queue(q)
          queue.depends.remove(j)
          if scheduled then
            queue.scheduled.add(scheduled, j)
            redis.call('hset', QlessJob.ns .. j, 'state', 'scheduled')
            redis.call('hdel', QlessJob.ns .. j, 'scheduled')
          else
            queue.work.add(now, p, j)
            redis.call('hset', QlessJob.ns .. j, 'state', 'waiting')
          end
        end
      end
    end

    redis.call('del', QlessJob.ns .. self.jid .. '-dependents')

    return 'complete'
  end
end

function QlessJob:fail(now, worker, group, message, data)
  local worker  = assert(worker           , 'Fail(): Arg "worker" missing')
  local group   = assert(group            , 'Fail(): Arg "group" missing')
  local message = assert(message          , 'Fail(): Arg "message" missing')

  local bin = now - (now % 86400)

  if data then
    data = cjson.decode(data)
  end

  local queue, state, oldworker = unpack(redis.call(
    'hmget', QlessJob.ns .. self.jid, 'queue', 'state', 'worker'))

  if not state then
    error('Fail(): Job ' .. self.jid .. 'does not exist')
  elseif state ~= 'running' then
    error('Fail(): Job ' .. self.jid .. 'not currently running: ' .. state)
  elseif worker ~= oldworker then
    error('Fail(): Job ' .. self.jid .. ' running with another worker: ' ..
      oldworker)
  end

  Qless.publish('log', cjson.encode({
    jid     = self.jid,
    event   = 'failed',
    worker  = worker,
    group   = group,
    message = message
  }))

  if redis.call('zscore', 'ql:tracked', self.jid) ~= false then
    Qless.publish('failed', self.jid)
  end

  redis.call('zrem', 'ql:w:' .. worker .. ':jobs', self.jid)

  self:history(now, 'failed', {worker = worker, group = group})

  redis.call('hincrby', 'ql:s:stats:' .. bin .. ':' .. queue, 'failures', 1)
  redis.call('hincrby', 'ql:s:stats:' .. bin .. ':' .. queue, 'failed'  , 1)

  local queue_obj = Qless.queue(queue)
  queue_obj.work.remove(self.jid)
  queue_obj.locks.remove(self.jid)
  queue_obj.scheduled.remove(self.jid)

  if data then
    redis.call('hset', QlessJob.ns .. self.jid, 'data', cjson.encode(data))
  end

  redis.call('hmset', QlessJob.ns .. self.jid,
    'state', 'failed',
    'worker', '',
    'expires', '',
    'failure', cjson.encode({
      ['group']   = group,
      ['message'] = message,
      ['when']    = math.floor(now),
      ['worker']  = worker
    }))

  redis.call('sadd', 'ql:failures', group)
  redis.call('lpush', 'ql:f:' .. group, self.jid)

  return self.jid
end

function QlessJob:retry(now, queue, worker, delay, group, message)
  assert(queue , 'Retry(): Arg "queue" missing')
  assert(worker, 'Retry(): Arg "worker" missing')
  delay = assert(tonumber(delay or 0),
    'Retry(): Arg "delay" not a number: ' .. tostring(delay))

  local oldqueue, state, retries, oldworker, priority, failure = unpack(