Initial push
This commit is contained in:
commit
2c1de57645
10
mfile
Normal file
10
mfile
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"package": "Avalon",
|
||||
"title": "Avalon UI",
|
||||
"description": "An extensible template for Mudlet UIs",
|
||||
"version": "0.1",
|
||||
"author": "Charles Click",
|
||||
"icon": "",
|
||||
"dependencies": "",
|
||||
"outputFile": false
|
||||
}
|
9
src/aliases/Avalon/aliases.json
Normal file
9
src/aliases/Avalon/aliases.json
Normal file
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"isActive": "yes",
|
||||
"isFolder": "no",
|
||||
"name": "lock UI",
|
||||
"regex": "^klock$",
|
||||
"script": ""
|
||||
}
|
||||
]
|
186
src/scripts/Avalon/Avalon_init.lua
Normal file
186
src/scripts/Avalon/Avalon_init.lua
Normal file
@ -0,0 +1,186 @@
|
||||
avalon = avalon or {
|
||||
bIsSetup = false,
|
||||
bEnableUserCss = false,
|
||||
containers = {},
|
||||
elements = {},
|
||||
alignment = {}
|
||||
}
|
||||
|
||||
function buildUI()
|
||||
-- Setup Individual Displays
|
||||
if not avalon.bIsSetup then
|
||||
|
||||
avalon.containers.top = Adjustable.Container:new({name = "avalonContainersTop", y="0%", height="10%", autoLoad=false})
|
||||
avalon.containers.left = Adjustable.Container:new({name = "avalonContainersLeft", x="0%", y="0%", height="100%", width="20%", autoLoad=false})
|
||||
avalon.containers.right = Adjustable.Container:new({name = "avalonContainersRight", x="-20%", y="0%", height="100%", width="20%", autoLoad=false})
|
||||
avalon.containers.bottom = Adjustable.Container:new({name = "avalonContainersBottom", y="-10%", height="10%", autoLoad=false})
|
||||
|
||||
avalon.containers.top:attachToBorder("top")
|
||||
avalon.containers.left:attachToBorder("left")
|
||||
avalon.containers.right:attachToBorder("right")
|
||||
avalon.containers.bottom:attachToBorder("bottom")
|
||||
|
||||
avalon.containers.top:connectToBorder("left")
|
||||
avalon.containers.top:connectToBorder("right")
|
||||
|
||||
avalon.containers.bottom:connectToBorder("left")
|
||||
avalon.containers.bottom:connectToBorder("right")
|
||||
|
||||
-- Setup Top Bar
|
||||
-- The Top Bar is just a single hbox
|
||||
avalon.alignment.topmain = Geyser.HBox:new(
|
||||
{
|
||||
name = "avalonAlignmentTopMain",
|
||||
width = "100%",
|
||||
height = "85%"
|
||||
},
|
||||
avalon.containers.top
|
||||
)
|
||||
|
||||
-- Setup Bottom Bar
|
||||
-- The Bottom Bar is a vbox that contains a top hbox that holds three gauges and a bottom hbox for whatever content you want
|
||||
avalon.alignment.bottommain = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonAlignmentBottomMain",
|
||||
width = "95%",
|
||||
height = "100%"
|
||||
},
|
||||
avalon.containers.bottom
|
||||
)
|
||||
|
||||
avalon.alignment.bottomgauges = Geyser.HBox:new(
|
||||
{
|
||||
name = "avalonAlignmentBottomgauges",
|
||||
width = "100%",
|
||||
height = "50%"
|
||||
},
|
||||
avalon.alignment.bottommain
|
||||
)
|
||||
|
||||
avalon.alignment.bottominfo = Geyser.HBox:new(
|
||||
{
|
||||
name="avalonAlignmentBottominfo",
|
||||
width = "100%",
|
||||
height = "-50%"
|
||||
},
|
||||
avalon.alignment.bottommain
|
||||
)
|
||||
|
||||
-- Setup Left Bar
|
||||
-- The Left bar consists of a vbox with four internal vboxes for information display
|
||||
-- These are named as:
|
||||
-- leftmain - Primary vbox that the other vboxes attach to
|
||||
-- lefttop - The top most vbox inside leftmain
|
||||
-- leftmidtop - The first of two vboxes between the top and bottom. This is the one below lefttop
|
||||
-- leftmidbottom - The second of two vboxes between the top and bottom. This is the one above leftbottom and below leftmidtop
|
||||
-- leftbottom - The bottom most vbox inside leftmain
|
||||
avalon.alignment.leftmain = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonAlignmentLeftmain",
|
||||
width = "95%",
|
||||
height = "100%"
|
||||
},
|
||||
avalon.containers.left
|
||||
)
|
||||
|
||||
avalon.alignment.lefttop = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonAlignmentLefttop",
|
||||
width = "100%",
|
||||
height = "25%"
|
||||
},
|
||||
avalon.alignment.leftmain
|
||||
)
|
||||
|
||||
avalon.alignment.leftmidtop = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonAlignmentLeftmidtop",
|
||||
width = "100%",
|
||||
height = "25%"
|
||||
},
|
||||
avalon.alignment.leftmain
|
||||
)
|
||||
|
||||
avalon.alignment.leftmidbottom = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonAlignmentLeftmidbottom",
|
||||
width = "100%",
|
||||
height = "25%"
|
||||
},
|
||||
avalon.alignment.leftmain
|
||||
)
|
||||
|
||||
avalon.alignment.leftbottom = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonAlignmentLeftbottom",
|
||||
width = "100%",
|
||||
height = "25%"
|
||||
},
|
||||
avalon.alignment.leftmain
|
||||
)
|
||||
|
||||
-- Setup Right Bar
|
||||
-- The Right Bar consists of vbox that contains a mapper in the top right and chat in the bottom right
|
||||
-- The chat utilizes Demonnic's great ECMO https://github.com/demonnic/MDK
|
||||
-- rightmain - Primary VB ox that other VBoxes attach to
|
||||
-- rightmap - The Mapper
|
||||
-- rightchat - The EMCO Chat window
|
||||
avalon.alignment.rightmain = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonAlignmentRightmain",
|
||||
width = "100%",
|
||||
height = "100%"
|
||||
},
|
||||
avalon.containers.right
|
||||
)
|
||||
|
||||
avalon.alignment.rightmap = Geyser.Container:new(
|
||||
{
|
||||
name = "avalonAlignmentRightmap",
|
||||
width = "100%",
|
||||
height = "60%"
|
||||
},
|
||||
avalon.alignment.rightmain
|
||||
)
|
||||
|
||||
avalon.alignment.rightchat = Geyser.Container:new(
|
||||
{
|
||||
name = "avalonAlignmentRightchat",
|
||||
width = "100%",
|
||||
height = "40%"
|
||||
},
|
||||
avalon.alignment.rightmain
|
||||
)
|
||||
|
||||
testLabel(avalon.alignment.topmain, "Top Main")
|
||||
testLabel(avalon.alignment.bottomgauges, "Bottom Gauges")
|
||||
testLabel(avalon.alignment.bottominfo, "Bottom Info")
|
||||
testLabel(avalon.alignment.lefttop, "Left Top")
|
||||
testLabel(avalon.alignment.leftmidtop, "Left Mid Top")
|
||||
testLabel(avalon.alignment.leftmidbottom, "Left Mid Bottom")
|
||||
testLabel(avalon.alignment.leftbottom, "Left Bottom")
|
||||
testLabel(avalon.alignment.rightmap, "Right Map")
|
||||
testLabel(avalon.alignment.rightchat, "Right Chat")
|
||||
|
||||
avalon.bIsSetup = true
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function testLabel(container, text)
|
||||
|
||||
local newLabel = Geyser.Label:new(
|
||||
{
|
||||
name = text,
|
||||
width = "100%",
|
||||
height = "100%",
|
||||
fgColor = "black",
|
||||
message = "<center>" .. text .. "</center>"
|
||||
},
|
||||
container
|
||||
)
|
||||
newLabel:setColor(0,255,0,150)
|
||||
table.insert(avalon.elements, newLabel)
|
||||
|
||||
end
|
1
src/scripts/Avalon/Framework/avalon_css.lua
Normal file
1
src/scripts/Avalon/Framework/avalon_css.lua
Normal file
@ -0,0 +1 @@
|
||||
-- End User CSS Functions
|
1486
src/scripts/Avalon/MDK/demontools.lua
Normal file
1486
src/scripts/Avalon/MDK/demontools.lua
Normal file
File diff suppressed because it is too large
Load Diff
2353
src/scripts/Avalon/MDK/emco.lua
Normal file
2353
src/scripts/Avalon/MDK/emco.lua
Normal file
File diff suppressed because it is too large
Load Diff
267
src/scripts/Avalon/MDK/figlet.lua
Normal file
267
src/scripts/Avalon/MDK/figlet.lua
Normal file
@ -0,0 +1,267 @@
|
||||
--- Figlet
|
||||
-- A module to read figlet fonts and produce figlet ascii art from text
|
||||
-- @module figlet
|
||||
-- @copyright 2010,2011 Nick Gammon
|
||||
-- @copyright 2022 Damian Monogue
|
||||
local Figlet = {}
|
||||
|
||||
--[[
|
||||
Based on figlet.
|
||||
|
||||
FIGlet Copyright 1991, 1993, 1994 Glenn Chappell and Ian Chai
|
||||
FIGlet Copyright 1996, 1997 John Cowan
|
||||
Portions written by Paul Burton
|
||||
Internet: <ianchai@usa.net>
|
||||
FIGlet, along with the various FIGlet fonts and documentation, is
|
||||
copyrighted under the provisions of the Artistic License (as listed
|
||||
in the file "artistic.license" which is included in this package.
|
||||
|
||||
--]]
|
||||
|
||||
--[[
|
||||
Latin-1 codes for German letters, respectively:
|
||||
LATIN CAPITAL LETTER A WITH DIAERESIS = A-umlaut
|
||||
LATIN CAPITAL LETTER O WITH DIAERESIS = O-umlaut
|
||||
LATIN CAPITAL LETTER U WITH DIAERESIS = U-umlaut
|
||||
LATIN SMALL LETTER A WITH DIAERESIS = a-umlaut
|
||||
LATIN SMALL LETTER O WITH DIAERESIS = o-umlaut
|
||||
LATIN SMALL LETTER U WITH DIAERESIS = u-umlaut
|
||||
LATIN SMALL LETTER SHARP S = ess-zed
|
||||
--]]
|
||||
|
||||
local deutsch = {196, 214, 220, 228, 246, 252, 223}
|
||||
local fcharlist = {}
|
||||
local magic, hardblank, charheight, maxlen, smush, cmtlines, ffright2left, smush2
|
||||
|
||||
local function readfontchar(fontfile, theord)
|
||||
|
||||
local t = {}
|
||||
fcharlist[theord] = t
|
||||
|
||||
-- read each character line
|
||||
|
||||
--[[
|
||||
|
||||
eg.
|
||||
|
||||
__ __ @
|
||||
| \/ |@
|
||||
| \ / |@
|
||||
| |\/| |@
|
||||
| | | |@
|
||||
|_| |_|@
|
||||
@
|
||||
@@
|
||||
--]]
|
||||
|
||||
for i = 1, charheight do
|
||||
local line = assert(fontfile:read("*l"), "Not enough character lines for character " .. theord)
|
||||
local line = string.gsub(line, "%s+$", "") -- remove trailing spaces
|
||||
assert(line ~= "", "Unexpected empty line")
|
||||
|
||||
-- find the last character (eg. @)
|
||||
local endchar = line:sub(-1) -- last character
|
||||
|
||||
-- trim one or more of the last character from the end
|
||||
while line:sub(-1) == endchar do
|
||||
line = line:sub(1, #line - 1)
|
||||
end -- while line ends with endchar
|
||||
|
||||
table.insert(t, line)
|
||||
|
||||
end -- for each line
|
||||
|
||||
end -- readfontchar
|
||||
|
||||
--- Reads a figlet font file (.flf) into memory and readies it for use by the next figlet
|
||||
-- These files are cached in memory so that future calls to load a font just read from there.
|
||||
-- @param filename the full path to the file to read the font from
|
||||
function Figlet.readfont(filename)
|
||||
local fontfile = assert(io.open(filename, "r"))
|
||||
local s
|
||||
|
||||
fcharlist = {}
|
||||
|
||||
-- header line
|
||||
s = assert(fontfile:read("*l"), "Empty FIGlet file")
|
||||
|
||||
-- eg. flf2a$ 8 6 59 15 10 0 24463 153
|
||||
-- magic charheight maxlen smush cmtlines ffright2left smush2 ??
|
||||
|
||||
-- configuration line
|
||||
magic, hardblank, charheight, maxlen, smush, cmtlines, ffright2left, smush2 = string.match(s,
|
||||
"^(flf2).(.) (%d+) %d+ (%d+) (%-?%d+) (%d+) ?(%d*) ?(%d*) ?(%-?%d*)")
|
||||
|
||||
assert(magic, "Not a FIGlet 2 font file")
|
||||
|
||||
-- convert to numbers
|
||||
charheight = tonumber(charheight)
|
||||
maxlen = tonumber(maxlen)
|
||||
smush = tonumber(smush)
|
||||
cmtlines = tonumber(cmtlines)
|
||||
|
||||
-- sanity check
|
||||
if charheight < 1 then
|
||||
charheight = 1
|
||||
end -- if
|
||||
|
||||
-- skip comment lines
|
||||
for i = 1, cmtlines do
|
||||
assert(fontfile:read("*l"), "Not enough comment lines")
|
||||
end -- for
|
||||
|
||||
-- get characters space to tilde
|
||||
for theord = string.byte(' '), string.byte('~') do
|
||||
readfontchar(fontfile, theord)
|
||||
end -- for
|
||||
|
||||
-- get 7 German characters
|
||||
for theord = 1, 7 do
|
||||
readfontchar(fontfile, deutsch[theord])
|
||||
end -- for
|
||||
|
||||
-- get extra ones like:
|
||||
-- 0x0395 GREEK CAPITAL LETTER EPSILON
|
||||
-- 246 LATIN SMALL LETTER O WITH DIAERESIS
|
||||
|
||||
repeat
|
||||
local extra = fontfile:read("*l")
|
||||
if not extra then
|
||||
break
|
||||
end -- if eof
|
||||
|
||||
local negative, theord = string.match(extra, "^(%-?)0[xX](%x+)")
|
||||
if theord then
|
||||
theord = tonumber(theord, 16)
|
||||
if negative == "-" then
|
||||
theord = -theord
|
||||
end -- if negative
|
||||
else
|
||||
theord = string.match(extra, "^%d+")
|
||||
assert(theord, "Unexpected line:" .. extra)
|
||||
theord = tonumber(theord)
|
||||
end -- if
|
||||
|
||||
readfontchar(fontfile, theord)
|
||||
|
||||
until false
|
||||
|
||||
fontfile:close()
|
||||
|
||||
-- remove leading/trailing spaces
|
||||
|
||||
for k, v in pairs(fcharlist) do
|
||||
|
||||
-- first see if all lines have a leading space or a trailing space
|
||||
local leading_space = true
|
||||
local trailing_space = true
|
||||
for _, line in ipairs(v) do
|
||||
if line:sub(1, 1) ~= " " then
|
||||
leading_space = false
|
||||
end -- if
|
||||
if line:sub(-1, -1) ~= " " then
|
||||
trailing_space = false
|
||||
end -- if
|
||||
end -- for each line
|
||||
|
||||
-- now remove them if necessary
|
||||
for i, line in ipairs(v) do
|
||||
if leading_space then
|
||||
v[i] = line:sub(2)
|
||||
end -- removing leading space
|
||||
if trailing_space then
|
||||
v[i] = line:sub(1, -2)
|
||||
end -- removing trailing space
|
||||
end -- for each line
|
||||
end -- for each character
|
||||
end -- readfont
|
||||
|
||||
-- add one character to output lines
|
||||
local function addchar(which, output, kern, smush)
|
||||
local c = fcharlist[string.byte(which)]
|
||||
if not c then
|
||||
return
|
||||
end -- if doesn't exist
|
||||
|
||||
for i = 1, charheight do
|
||||
|
||||
if smush and output[i] ~= "" and which ~= " " then
|
||||
local lhc = output[i]:sub(-1)
|
||||
local rhc = c[i]:sub(1, 1)
|
||||
output[i] = output[i]:sub(1, -2) -- remove last character
|
||||
if rhc ~= " " then
|
||||
output[i] = output[i] .. rhc
|
||||
else
|
||||
output[i] = output[i] .. lhc
|
||||
end
|
||||
output[i] = output[i] .. c[i]:sub(2)
|
||||
|
||||
else
|
||||
output[i] = output[i] .. c[i]
|
||||
end -- if
|
||||
|
||||
if not (kern or smush) or which == " " then
|
||||
output[i] = output[i] .. " "
|
||||
end -- if
|
||||
end -- for
|
||||
|
||||
end -- addchar
|
||||
|
||||
--- Returns a table of lines representing a string as figlet
|
||||
-- @tparam string s the text to make into a figlet
|
||||
-- @tparam boolean kern should we reduce spacing
|
||||
-- @tparam boolean smush causes the letters to share edges, condensing it even further
|
||||
function Figlet.ascii_art(s, kern, smush)
|
||||
assert(fcharlist)
|
||||
assert(charheight > 0)
|
||||
|
||||
-- make table of output lines
|
||||
local output = {}
|
||||
for i = 1, charheight do
|
||||
output[i] = ""
|
||||
end -- for
|
||||
|
||||
for i = 1, #s do
|
||||
local c = s:sub(i, i)
|
||||
|
||||
if c >= " " and c < "\127" then
|
||||
addchar(c, output, kern, smush)
|
||||
end -- if in range
|
||||
|
||||
end -- for
|
||||
|
||||
-- fix up blank character so we can do a string.gsub on it
|
||||
local fixedblank = string.gsub(hardblank, "[%%%]%^%-$().[*+?]", "%%%1")
|
||||
|
||||
for i, line in ipairs(output) do
|
||||
output[i] = string.gsub(line, fixedblank, " ")
|
||||
end -- for
|
||||
|
||||
return output
|
||||
end -- function ascii_art
|
||||
|
||||
--- Returns the figlet as a string, rather than a table
|
||||
-- @tparam string str the string the make into a figlet
|
||||
-- @tparam boolean kern should we reduce the space between letters?
|
||||
-- @tparam boolean smush should the letters share edges, further condensing the output?
|
||||
-- @see ascii_art
|
||||
function Figlet.getString(str, kern, smush)
|
||||
local tbl = Figlet.ascii_art(str, kern, smush)
|
||||
return table.concat(tbl, "\n")
|
||||
end
|
||||
|
||||
--- Returns a figlet as a string, with kern set to true.
|
||||
-- @tparam string str The string to turn into a figlet
|
||||
-- @see getString
|
||||
function Figlet.getKern(str)
|
||||
return Figlet.getString(str, true)
|
||||
end
|
||||
|
||||
--- Returns a figlet as a string, with smush set to true.
|
||||
-- @tparam string str The string to turn into a figlet
|
||||
-- @see getString
|
||||
function Figlet.getSmush(str)
|
||||
return Figlet.getString(str, true, true)
|
||||
end
|
||||
|
||||
return Figlet
|
1697
src/scripts/Avalon/MDK/ftext.lua
Normal file
1697
src/scripts/Avalon/MDK/ftext.lua
Normal file
File diff suppressed because it is too large
Load Diff
447
src/scripts/Avalon/MDK/ftext_spec.lua
Normal file
447
src/scripts/Avalon/MDK/ftext_spec.lua
Normal file
@ -0,0 +1,447 @@
|
||||
local ftext = require("MDK.ftext")
|
||||
|
||||
describe("ftext:", function()
|
||||
describe("ftext.fText:", function()
|
||||
local fText = ftext.fText
|
||||
|
||||
it("Should properly center text", function()
|
||||
local expected = " some text "
|
||||
local actual = fText("some text", {width = 20})
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
|
||||
it("Should properly pad left aligned text", function()
|
||||
local expected = "some text "
|
||||
local actual = fText("some text", {width = 20, alignment = "left"})
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
|
||||
it("Should properly pad right aligned text", function()
|
||||
local expected = " some text"
|
||||
local actual = fText("some text", {width = 20, alignment = "right"})
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
|
||||
it("Should wrap lines to the correct length", function()
|
||||
local str = "This is a test of the emergency broadcast system. This is only a test"
|
||||
local options = {width = 10, alignment = "centered"}
|
||||
local actual = fText(str, options)
|
||||
for _, line in ipairs(actual:split("\n")) do
|
||||
assert.equals(line:len(), 10)
|
||||
end
|
||||
options.width = 15
|
||||
actual = fText(str, options)
|
||||
for _, line in ipairs(actual:split("\n")) do
|
||||
assert.equals(line:len(), 15)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("non-space spacer character:", function()
|
||||
local str = "some text"
|
||||
local options = {width = "20", alignment = "left", spacer = "="}
|
||||
it("Should work with left align", function()
|
||||
local expected = "some text =========="
|
||||
local actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
|
||||
it("Should work with right align", function()
|
||||
local expected = "========== some text"
|
||||
options.alignment = "right"
|
||||
local actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
|
||||
it("Should work with center align", function()
|
||||
local expected = ("==== some text =====")
|
||||
options.alignment = "center"
|
||||
local actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("nogap option:", function()
|
||||
local str = "some text"
|
||||
local options = {width = "20", alignment = "left", spacer = "=", nogap = true}
|
||||
|
||||
it("Should work with left align", function()
|
||||
local expected = "some text==========="
|
||||
local actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
|
||||
it("Should work with right align", function()
|
||||
local expected = "===========some text"
|
||||
options.alignment = "right"
|
||||
local actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
|
||||
it("Should work with center align", function()
|
||||
local expected = "=====some text======"
|
||||
options.alignment = "center"
|
||||
local actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("cap functionality", function()
|
||||
local str = "some text"
|
||||
local options = {width = 20, spacer = "=", cap = "|"}
|
||||
|
||||
it("Should place the spacer outside the cap by default", function()
|
||||
local expected = "===| some text |===="
|
||||
local actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
|
||||
it("Should place it inside the cap if inside option is true", function()
|
||||
local expected = "|=== some text ====|"
|
||||
options.inside = true
|
||||
local actual = fText(str, options)
|
||||
options.inside = nil
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(20, actual:len())
|
||||
end)
|
||||
|
||||
it("Should mirror certain characters with their opposites", function()
|
||||
local expected = "===[ some text ]===="
|
||||
options.mirror = true
|
||||
options.cap = "["
|
||||
local actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
options.inside = true
|
||||
expected = "[=== some text ====]"
|
||||
actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
options.inside = nil
|
||||
options.cap = "<"
|
||||
expected = "===< some text >===="
|
||||
actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
options.cap = "{"
|
||||
expected = "==={ some text }===="
|
||||
actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
options.cap = "("
|
||||
expected = "===( some text )===="
|
||||
actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
options.cap = "|"
|
||||
expected = "===| some text |===="
|
||||
actual = fText(str, options)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("ftext.cfText", function()
|
||||
local cfText = ftext.cfText
|
||||
local str = "some text"
|
||||
local options = {
|
||||
width = 20,
|
||||
spacer = "=",
|
||||
cap = "[",
|
||||
inside = true,
|
||||
mirror = true,
|
||||
capColor = "<purple>",
|
||||
spacerColor = "<green>",
|
||||
textColor = "<red>",
|
||||
}
|
||||
it("Should handle cecho colored text", function()
|
||||
local expectedStripped = "[=== some text ====]"
|
||||
local expected = "<purple>[<reset><green>===<reset><red> some text <reset><green>====<reset><purple>]<reset>"
|
||||
local actual = cfText(str, options)
|
||||
local actualStripped = cecho2string(actual)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(expectedStripped, actualStripped)
|
||||
assert.equals(20, actualStripped:len())
|
||||
expectedStripped = "===[ some text ]===="
|
||||
expected = "<green>===<reset><purple>[<reset><red> some text <reset><purple>]<reset><green>====<reset>"
|
||||
options.inside = false
|
||||
actual = cfText(str, options)
|
||||
actualStripped = cecho2string(actual)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(expectedStripped, actualStripped)
|
||||
assert.equals(20, actualStripped:len())
|
||||
end)
|
||||
|
||||
it("Should wrap cecho lines to the correct length", function()
|
||||
local str = "This is a test of the emergency broadcast system. This is only a test"
|
||||
local options = {width = 10, alignment = "centered"}
|
||||
local actual = cfText(str, options)
|
||||
for _, line in ipairs(actual:split("\n")) do
|
||||
assert.equals(cecho2string(line):len(), 10)
|
||||
end
|
||||
options.width = 15
|
||||
actual = cfText(str, options)
|
||||
for _, line in ipairs(actual:split("\n")) do
|
||||
assert.equals(cecho2string(line):len(), 15)
|
||||
end
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
describe("ftext.dfText", function()
|
||||
local dfText = ftext.dfText
|
||||
local str = "some text"
|
||||
local options = {
|
||||
width = 20,
|
||||
spacer = "=",
|
||||
cap = "[",
|
||||
inside = true,
|
||||
mirror = true,
|
||||
capColor = "<160,32,240>",
|
||||
spacerColor = "<0,255,0>",
|
||||
textColor = "<255,0,0>",
|
||||
}
|
||||
it("Should handle decho colored text", function()
|
||||
local expectedStripped = "[=== some text ====]"
|
||||
local expected = "<160,32,240>[<r><0,255,0>===<r><255,0,0> some text <r><0,255,0>====<r><160,32,240>]<r>"
|
||||
local actual = dfText(str, options)
|
||||
local actualStripped = decho2string(actual)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(expectedStripped, actualStripped)
|
||||
assert.equals(20, actualStripped:len())
|
||||
expectedStripped = "===[ some text ]===="
|
||||
expected = "<0,255,0>===<r><160,32,240>[<r><255,0,0> some text <r><160,32,240>]<r><0,255,0>====<r>"
|
||||
options.inside = false
|
||||
actual = dfText(str, options)
|
||||
actualStripped = decho2string(actual)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(expectedStripped, actualStripped)
|
||||
assert.equals(20, actualStripped:len())
|
||||
end)
|
||||
|
||||
it("Should wrap decho lines to the correct length", function()
|
||||
local str = "This is a test of the emergency broadcast system. This is only a test"
|
||||
local options = {width = 10, alignment = "centered"}
|
||||
local actual = dfText(str, options)
|
||||
for _, line in ipairs(actual:split("\n")) do
|
||||
assert.equals(decho2string(line):len(), 10)
|
||||
end
|
||||
options.width = 15
|
||||
actual = dfText(str, options)
|
||||
for _, line in ipairs(actual:split("\n")) do
|
||||
assert.equals(decho2string(line):len(), 15)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("ftext.hfText", function()
|
||||
local hfText = ftext.hfText
|
||||
local str = "some text"
|
||||
local options = {
|
||||
width = 20,
|
||||
spacer = "=",
|
||||
cap = "[",
|
||||
inside = true,
|
||||
mirror = true,
|
||||
capColor = "#a020f0",
|
||||
spacerColor = "#00ff00",
|
||||
textColor = "#ff0000",
|
||||
}
|
||||
it("Should handle hecho colored text", function()
|
||||
local expectedStripped = "[=== some text ====]"
|
||||
local expected = "#a020f0[#r#00ff00===#r#ff0000 some text #r#00ff00====#r#a020f0]#r"
|
||||
local actual = hfText(str, options)
|
||||
local actualStripped = hecho2string(actual)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(expectedStripped, actualStripped)
|
||||
assert.equals(20, actualStripped:len())
|
||||
expectedStripped = "===[ some text ]===="
|
||||
expected = "#00ff00===#r#a020f0[#r#ff0000 some text #r#a020f0]#r#00ff00====#r"
|
||||
options.inside = false
|
||||
actual = hfText(str, options)
|
||||
actualStripped = hecho2string(actual)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(expectedStripped, actualStripped)
|
||||
assert.equals(20, actualStripped:len())
|
||||
end)
|
||||
|
||||
it("Should wrap hecho lines to the correct length", function()
|
||||
local str = "This is a test of the emergency broadcast system. This is only a test"
|
||||
local options = {width = 10, alignment = "centered"}
|
||||
local actual = hfText(str, options)
|
||||
for _, line in ipairs(actual:split("\n")) do
|
||||
assert.equals(hecho2string(line):len(), 10)
|
||||
end
|
||||
options.width = 15
|
||||
actual = hfText(str, options)
|
||||
for _, line in ipairs(actual:split("\n")) do
|
||||
assert.equals(hecho2string(line):len(), 15)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("ftext.TextFormatter", function()
|
||||
local tf = ftext.TextFormatter
|
||||
local str = "some text"
|
||||
local formatter
|
||||
|
||||
before_each(function()
|
||||
formatter = tf:new({width = 20})
|
||||
end)
|
||||
|
||||
it("Should let you change width using :setWidth", function()
|
||||
formatter:setWidth(80)
|
||||
local expected =
|
||||
"<white><reset><white> <reset><white> some text <reset><white> <reset><white><reset>"
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(80, cecho2string(actual):len())
|
||||
end)
|
||||
|
||||
it("Should format for cecho by default", function()
|
||||
local expected = "<white><reset><white> <reset><white> some text <reset><white> <reset><white><reset>"
|
||||
local expectedStripped = " some text "
|
||||
local actual = formatter:format(str)
|
||||
local actualStripped = cecho2string(actual)
|
||||
assert.equals(expected, actual)
|
||||
assert.equals(expectedStripped, actualStripped)
|
||||
assert.equals(20, actualStripped:len())
|
||||
end)
|
||||
|
||||
it("Should produce the same line as cfText given the same options", function()
|
||||
local expected = ftext.cfText(str, formatter.options)
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("Should let you change type using :setType", function()
|
||||
formatter:setType("h")
|
||||
local expected = ftext.hfText(str, formatter.options)
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
formatter:setType("d")
|
||||
expected = ftext.dfText(str, formatter.options)
|
||||
actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
formatter:setType("")
|
||||
expected = ftext.fText(str, formatter.options)
|
||||
actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("Should default to word wrapping, and let you change it with :setWrap", function()
|
||||
formatter:setWidth(10)
|
||||
local expected =
|
||||
"<white><reset><white> <reset><white> some <reset><white> <reset><white><reset>\n<white><reset><white> <reset><white> text <reset><white> <reset><white><reset>"
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
expected = "<white><reset><white><reset><white> some text <reset><white><reset><white><reset>"
|
||||
formatter:setWrap(false)
|
||||
actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("Should allow you to change the cap using :setCap", function()
|
||||
formatter:setCap('|')
|
||||
local expected = "<white>|<reset><white> <reset><white> some text <reset><white> <reset><white>|<reset>"
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("Should allow you to change the capColor using :setCapColor", function()
|
||||
formatter:setCapColor('<red>')
|
||||
local expected = "<red><reset><white> <reset><white> some text <reset><white> <reset><red><reset>"
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("Should allow you to change the spacer color using :setSpacerColor", function()
|
||||
formatter:setSpacerColor("<red>")
|
||||
local expected = "<white><reset><red> <reset><white> some text <reset><red> <reset><white><reset>"
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("Should allow you to change the text color using :setTextColor", function()
|
||||
formatter:setTextColor("<red>")
|
||||
local expected = "<white><reset><white> <reset><red> some text <reset><white> <reset><white><reset>"
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("Should allow you to change the spacer using :setSpacer", function()
|
||||
formatter:setSpacer("=")
|
||||
-- local expected = "<white><reset><white> <reset><white> some text <reset><white> <reset><white><reset>"
|
||||
local expected = "<white><reset><white>====<reset><white> some text <reset><white>=====<reset><white><reset>"
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("Should allow you to set the alignment using :setAlignment", function()
|
||||
formatter:setAlignment("left")
|
||||
local expected = "<white><reset><white><reset><white>some text <reset><white> <reset><white><reset>"
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
formatter:setAlignment("right")
|
||||
expected = "<white><reset><white> <reset><white> some text<reset><white><reset><white><reset>"
|
||||
actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("Should allow you to change the 'inside' option using :setInside", function()
|
||||
formatter:setInside(false)
|
||||
local expected = "<white> <reset><white><reset><white> some text <reset><white><reset><white> <reset>"
|
||||
local actual = formatter:format(str)
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("Should allow you to change the mirror option using :setMirror", function()
|
||||
formatter:setCap('<')
|
||||
formatter:setMirror(true)
|
||||
local expected = "<white><<reset><white> <reset><white> some text <reset><white> <reset><white>><reset>"
|
||||
local actual = formatter:format(str)
|
||||
assert.equal(expected, actual)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("ftext.TableMaker", function()
|
||||
local TableMaker = ftext.TableMaker
|
||||
local tm
|
||||
before_each(function()
|
||||
tm = TableMaker:new()
|
||||
tm:addColumn({name = "col1", width = 15, textColor = "<red>"})
|
||||
tm:addColumn({name = "col2", width = 15, textColor = "<blue>"})
|
||||
tm:addColumn({name = "col3", width = 15, textColor = "<green>"})
|
||||
tm:addRow({"some text", "more text", "other text"})
|
||||
tm:addRow({"little text", "bigger text", "text"})
|
||||
end)
|
||||
|
||||
it("Should assemble a formatted table given default options", function()
|
||||
local expected = [[<white>*************************************************<reset>
|
||||
<white>*<reset><white><reset><white> <reset><red> col1 <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><blue> col2 <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><green> col3 <reset><white> <reset><white><reset><white>*<reset>
|
||||
<white>*<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>*<reset>
|
||||
<white>*<reset><white><reset><white> <reset><red> some text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><blue> more text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><green> other text <reset><white> <reset><white><reset><white>*<reset>
|
||||
<white>*<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>*<reset>
|
||||
<white>*<reset><white><reset><white> <reset><red> little text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><blue> bigger text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><green> text <reset><white> <reset><white><reset><white>*<reset>
|
||||
<white>*************************************************<reset>
|
||||
]]
|
||||
local actual = tm:assemble()
|
||||
assert.equals(expected, actual)
|
||||
end)
|
||||
|
||||
it("TableMaker:getCell should return the text and formatter for a specific cell", function()
|
||||
local expectedText = "more text"
|
||||
local expectedFormatter = tm.columns[2]
|
||||
local actualText, actualFormatter = tm:getCell(1, 2)
|
||||
assert.equals(expectedText, actualText)
|
||||
assert.equals(expectedFormatter, actualFormatter)
|
||||
local expectedFormatted = "<white><reset><white> <reset><blue> more text <reset><white> <reset><white><reset>"
|
||||
local actualFormatted = actualFormatter:format(actualText)
|
||||
assert.equals(expectedFormatted, actualFormatted)
|
||||
end)
|
||||
end)
|
||||
end)
|
461
src/scripts/Avalon/MDK/loggingconsole.lua
Normal file
461
src/scripts/Avalon/MDK/loggingconsole.lua
Normal file
@ -0,0 +1,461 @@
|
||||
--- MiniConsole with logging capabilities
|
||||
-- @classmod LoggingConsole
|
||||
-- @author Damian Monogue <demonnic@gmail.com>
|
||||
-- @copyright 2020 Damian Monogue
|
||||
-- @license MIT, see LICENSE.lua
|
||||
local homedir = getMudletHomeDir():gsub("\\", "/")
|
||||
local pathOfThisFile = (...):match("(.-)[^%.]+$")
|
||||
local dt = require(pathOfThisFile .. "demontools")
|
||||
local exists, htmlHeader, htmlHeaderPattern = dt.exists, dt.htmlHeader, dt.htmlHeaderPattern
|
||||
|
||||
local LoggingConsole = {log = true, logFormat = "h", path = "|h/log/consoleLogs/|y/|m/|d/", fileName = "|n.|e"}
|
||||
|
||||
--- Creates and returns a new LoggingConsole.
|
||||
-- @param cons table of constraints. Includes all the valid Geyser.MiniConsole constraints, plus
|
||||
-- <table class="tg">
|
||||
-- <thead>
|
||||
-- <tr>
|
||||
-- <th>option name</th>
|
||||
-- <th>description</th>
|
||||
-- <th>default</th>
|
||||
-- </tr>
|
||||
-- </thead>
|
||||
-- <tbody>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">log</td>
|
||||
-- <td class="tg-1">Should the miniconsole be logging?</td>
|
||||
-- <td class="tg-1">true</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">logFormat</td>
|
||||
-- <td class="tg-2">"h" for html, "t" for plaintext, "l" for log (with ansi)</td>
|
||||
-- <td class="tg-2">h</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">path</td>
|
||||
-- <td class="tg-1">The path the file lives in. It is templated.<br>|h is replaced by the profile homedir.<br>|y by 4 digit year.<br>|m by 2 digit month<br>|d by 2 digit day<br>|n by the name constraint<br>|e by the file extension (html for h logType, log for others)</td>
|
||||
-- <td class="tg-1">"|h/log/consoleLogs/|y/|m/|d/"</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">fileName</td>
|
||||
-- <td class="tg-2">The name of the log file. It is templated, same as path above</td>
|
||||
-- <td class="tg-2">"|n.|e"</td>
|
||||
-- </tr>
|
||||
-- </tbody>
|
||||
-- </table>
|
||||
-- @param container the container for the console
|
||||
-- @usage
|
||||
-- local LoggingConsole = require("MDK.loggingconsole")
|
||||
-- myLoggingConsole = LoggingConsole:new({
|
||||
-- name = "my logging console",
|
||||
-- x = 0,
|
||||
-- y = 0,
|
||||
-- height = 200,
|
||||
-- width = 400,
|
||||
-- }) -- just like making a miniconsole, really
|
||||
function LoggingConsole:new(cons, container)
|
||||
cons = cons or {}
|
||||
local consType = type(cons)
|
||||
assert(consType == "table", "LoggingConsole:new(cons, container): cons must be a valid table of constraints. Got: " .. consType)
|
||||
local me = Geyser.MiniConsole:new(cons, container)
|
||||
setmetatable(me, self)
|
||||
self.__index = self
|
||||
return me
|
||||
end
|
||||
|
||||
--- Returns the file extension of the logfile this console will log to
|
||||
function LoggingConsole:getExtension()
|
||||
local extension = "log"
|
||||
if table.contains({"h", "html"}, self.logFormat) then
|
||||
extension = "html"
|
||||
end
|
||||
return extension
|
||||
end
|
||||
|
||||
--- Returns a string with all templated items replaced
|
||||
---@tparam string str The templated string to transform
|
||||
---@local
|
||||
function LoggingConsole:transformTemplate(str)
|
||||
local ttbl = getTime()
|
||||
local year = ttbl.year
|
||||
local month = string.format("%02d", ttbl.month)
|
||||
local day = string.format("%02d", ttbl.day)
|
||||
local name = self.name
|
||||
local extension = self:getExtension()
|
||||
str = str:gsub("|h", homedir)
|
||||
str = str:gsub("|y", year)
|
||||
str = str:gsub("|m", month)
|
||||
str = str:gsub("|d", day)
|
||||
str = str:gsub("|n", name)
|
||||
str = str:gsub("|e", extension)
|
||||
return str
|
||||
end
|
||||
|
||||
--- Returns the path to the logfile for this console
|
||||
function LoggingConsole:getPath()
|
||||
local path = self:transformTemplate(self.path)
|
||||
if not path:ends("/") then
|
||||
path = path .. "/"
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
--- Sets the path to use for the log file.
|
||||
-- @param path the path to put the log file in. It is templated.<br>|h is replaced by the profile homedir.<br>|y by 4 digit year.<br>|m by 2 digit month<br>|d by 2 digit day<br>|n by the name constraint<br>|e by the file extension (html for h logType, log for others)
|
||||
function LoggingConsole:setPath(path)
|
||||
self.path = path
|
||||
end
|
||||
|
||||
--- Returns the filename for the logfile for this console
|
||||
function LoggingConsole:getFileName()
|
||||
local fileName = self:transformTemplate(self.fileName)
|
||||
fileName = fileName:gsub("[<>:'\"/\\?*]", "_")
|
||||
return fileName
|
||||
end
|
||||
|
||||
--- Sets the fileName to use for the log file.
|
||||
-- @param fileName the fileName to use for the logfile. It is templated.<br>|h is replaced by the profile homedir.<br>|y by 4 digit year.<br>|m by 2 digit month<br>|d by 2 digit day<br>|n by the name constraint<br>|e by the file extension (html for h logType, log for others)
|
||||
function LoggingConsole:setFileName(fileName)
|
||||
self.fileName = fileName
|
||||
end
|
||||
|
||||
--- Returns the pull path and filename for the logfile for this console
|
||||
function LoggingConsole:getFullFilename()
|
||||
local path = self:getPath()
|
||||
local fileName = self:getFileName()
|
||||
local fullPath = path .. fileName
|
||||
fullPath = fullPath:gsub("|", "_")
|
||||
return fullPath
|
||||
end
|
||||
|
||||
--- Turns logging for this console on
|
||||
function LoggingConsole:enableLogging()
|
||||
self.log = true
|
||||
end
|
||||
|
||||
--- Turns logging for this console off
|
||||
function LoggingConsole:disableLogging()
|
||||
self.log = false
|
||||
end
|
||||
|
||||
--- Creates the path for the logfile for this console if necessary
|
||||
---@local
|
||||
function LoggingConsole:createPathIfNotExists()
|
||||
local path = self:transformTemplate(self.path)
|
||||
if not path:ends("/") then
|
||||
path = path .. "/"
|
||||
end
|
||||
if not exists(path) then
|
||||
local ok, err = dt.mkdir_p(path)
|
||||
if not ok then
|
||||
assert(false, "Could not create directory for log files:" .. path .. "\n Reason was: " .. err)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Handles actually writing to the log file
|
||||
---@local
|
||||
function LoggingConsole:writeToLog(str)
|
||||
local fileName = self:getFullFilename()
|
||||
self:createPathIfNotExists()
|
||||
if self:getExtension() == "html" then
|
||||
if not io.exists(fileName) then
|
||||
str = htmlHeader .. str
|
||||
end
|
||||
str = str
|
||||
end
|
||||
local file, err = io.open(fileName, "a")
|
||||
if not file then
|
||||
echo(err .. "\n")
|
||||
return
|
||||
end
|
||||
file:write(str)
|
||||
file:close()
|
||||
end
|
||||
|
||||
local parent = Geyser.MiniConsole
|
||||
--- Handler function which does the lifting for c/d/h/echo and appendBuffer to provide the logfile writing functionality
|
||||
---@param str the string to echo. Use "" for appends
|
||||
---@param etype the type of echo. Valid are "c", "d", "h", "e", and "a"
|
||||
---@param log Allows you to override the default behaviour defined by the .log property. Pass true to definitely log, false to skip logging.
|
||||
---@local
|
||||
function LoggingConsole:xEcho(str, etype, log)
|
||||
if log == nil then
|
||||
log = self.log
|
||||
end
|
||||
local logStr
|
||||
local logType = self.logFormat
|
||||
if logType:find("h") then
|
||||
logType = "h"
|
||||
elseif logType ~= "t" then
|
||||
logType = "l"
|
||||
end
|
||||
if etype == "d" then -- decho
|
||||
if logType == "h" then
|
||||
logStr = dt.decho2html(str)
|
||||
elseif logType == "t" then
|
||||
logStr = dt.decho2string(str)
|
||||
else
|
||||
logStr = dt.decho2ansi(str)
|
||||
end
|
||||
parent.decho(self, str)
|
||||
elseif etype == "c" then -- cecho
|
||||
if logType == "h" then
|
||||
logStr = dt.cecho2html(str)
|
||||
elseif logType == "t" then
|
||||
logStr = dt.cecho2string(str)
|
||||
else
|
||||
logStr = dt.cecho2ansi(str)
|
||||
end
|
||||
parent.cecho(self, str)
|
||||
elseif etype == "h" then -- hecho
|
||||
if logType == "h" then
|
||||
logStr = dt.hecho2html(str)
|
||||
elseif logType == "t" then
|
||||
logStr = dt.hecho2string(str)
|
||||
else
|
||||
logStr = dt.hecho2ansi(str)
|
||||
end
|
||||
parent.hecho(self, str)
|
||||
elseif etype == "a" then -- append
|
||||
str = dt.append2decho()
|
||||
str = str .. "\n"
|
||||
if logType == "h" then
|
||||
logStr = dt.decho2html(str)
|
||||
elseif logType == "t" then
|
||||
logStr = dt.decho2string(str)
|
||||
else
|
||||
logStr = dt.decho2ansi(str)
|
||||
end
|
||||
parent.appendBuffer(self)
|
||||
elseif etype == "e" then -- echo
|
||||
if logType == "h" then
|
||||
logStr = dt.decho2html(str)
|
||||
else
|
||||
logStr = str
|
||||
end
|
||||
parent.echo(self, str)
|
||||
end
|
||||
if log then
|
||||
self:writeToLog(logStr)
|
||||
end
|
||||
end
|
||||
|
||||
--- Does the actual lifting of echoing links/popups
|
||||
-- @local
|
||||
function LoggingConsole:xEchoLink(text, lType, command, hint, useFormat, log)
|
||||
if log == nil then
|
||||
log = self.log
|
||||
end
|
||||
local logStr = ""
|
||||
if lType:starts("c") then
|
||||
if self.logFormat == "h" then
|
||||
logStr = dt.cecho2html(text)
|
||||
elseif self.logFormat == "l" then
|
||||
logStr = dt.cecho2ansi(text)
|
||||
elseif self.logFormat == "t" then
|
||||
logStr = dt.cecho2string(text)
|
||||
end
|
||||
if lType:ends("p") then
|
||||
parent.cechoPopup(self, text, command, hint, useFormat)
|
||||
else
|
||||
parent.cechoLink(self, text, command, hint, useFormat)
|
||||
end
|
||||
elseif lType:starts("d") then
|
||||
if self.logFormat == "h" then
|
||||
logStr = dt.decho2html(text)
|
||||
elseif self.logFormat == "l" then
|
||||
logStr = dt.decho2ansi(text)
|
||||
elseif self.logFormat == "t" then
|
||||
logStr = dt.decho2string(text)
|
||||
end
|
||||
if lType:ends("p") then
|
||||
parent.dechoPopup(self, text, command, hint, useFormat)
|
||||
else
|
||||
parent.dechoLink(self, text, command, hint, useFormat)
|
||||
end
|
||||
elseif lType:starts("h") then
|
||||
if self.logFormat == "h" then
|
||||
logStr = dt.hecho2html(text)
|
||||
elseif self.logFormat == "l" then
|
||||
logStr = dt.hecho2ansi(text)
|
||||
elseif self.logFormat == "t" then
|
||||
logStr = dt.hecho2string(text)
|
||||
end
|
||||
if lType:ends("p") then
|
||||
parent.hechoPopup(self, text, command, hint, useFormat)
|
||||
else
|
||||
parent.hechoLink(self, text, command, hint, useFormat)
|
||||
end
|
||||
elseif lType:starts("e") then
|
||||
logStr = text
|
||||
if lType:ends("p") then
|
||||
parent.echoPopup(self, text, command, hint, useFormat)
|
||||
else
|
||||
parent.echoLink(self, text, command, hint, useFormat)
|
||||
end
|
||||
end
|
||||
if log then
|
||||
self:writeToLog(logStr)
|
||||
end
|
||||
end
|
||||
|
||||
--- cechoLink for LoggingConsole
|
||||
-- @param text the text to use for the link
|
||||
-- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]]
|
||||
-- @param hint A tooltip which is displayed when the mouse is over the link
|
||||
-- @param log Should we log this line? Defaults to self.log if not passed.
|
||||
function LoggingConsole:cechoLink(text, command, hint, log)
|
||||
self:xEchoLink(text, "c", command, hint, true, log)
|
||||
end
|
||||
|
||||
--- dechoLink for LoggingConsole
|
||||
-- @param text the text to use for the link
|
||||
-- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]]
|
||||
-- @param hint A tooltip which is displayed when the mouse is over the link
|
||||
-- @param log Should we log this line? Defaults to self.log if not passed.
|
||||
function LoggingConsole:dechoLink(text, command, hint, log)
|
||||
self:xEchoLink(text, "d", command, hint, true, log)
|
||||
end
|
||||
|
||||
--- hechoLink for LoggingConsole
|
||||
-- @param text the text to use for the link
|
||||
-- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]]
|
||||
-- @param hint A tooltip which is displayed when the mouse is over the link
|
||||
-- @param log Should we log this line? Defaults to self.log if not passed.
|
||||
function LoggingConsole:hechoLink(text, command, hint, log)
|
||||
self:xEchoLink(text, "h", command, hint, true, log)
|
||||
end
|
||||
|
||||
--- echoLink for LoggingConsole
|
||||
-- @param text the text to use for the link
|
||||
-- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]]
|
||||
-- @param hint A tooltip which is displayed when the mouse is over the link
|
||||
-- @param useCurrentFormat If set to true, will look like the text around it. If false it will be blue and underline.
|
||||
-- @param log Should we log this line? Defaults to self.log if not passed. If you want to pass this you must pass in useCurrentFormat
|
||||
-- @usage myLoggingConsole:echoLink("This is a link!", [[send("sleep")]], "sleep") -- text "This is a link" will send("sleep") when clicked and be blue w/ underline. Defaut log behaviour (self.log)
|
||||
-- @usage myLoggingConsole:echoLink("This is a link!", [[send("sleep")]], "sleep", false, false) -- same as above, but forces it not to log regardless of self.log setting
|
||||
-- @usage myLoggingConsole:echoLink("This is a link!", [[send("sleep")]], "sleep", true, true) -- same as above, but forces it to log regardless of self.log setting and the text will look like anything else echoed to the console.
|
||||
function LoggingConsole:echoLink(text, command, hint, useCurrentFormat, log)
|
||||
self:xEchoLink(text, "e", command, hint, useCurrentFormat, log)
|
||||
end
|
||||
|
||||
--- cechoPopup for LoggingConsole
|
||||
-- @param text the text to use for the link
|
||||
-- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]}
|
||||
-- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}}
|
||||
-- @param log Should we log this line? Defaults to self.log if not passed.
|
||||
function LoggingConsole:cechoPopup(text, commands, hints, log)
|
||||
self:xEchoLink(text, "cp", commands, hints, true, log)
|
||||
end
|
||||
|
||||
--- dechoPopup for LoggingConsole
|
||||
-- @param text the text to use for the link
|
||||
-- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]}
|
||||
-- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}}
|
||||
-- @param log Should we log this line? Defaults to self.log if not passed.
|
||||
function LoggingConsole:dechoPopup(text, commands, hints, log)
|
||||
self:xEchoLink(text, "dp", commands, hints, true, log)
|
||||
end
|
||||
|
||||
--- hechoPopup for LoggingConsole
|
||||
-- @param text the text to use for the link
|
||||
-- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]}
|
||||
-- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}}
|
||||
-- @param log Should we log this line? Defaults to self.log if not passed.
|
||||
function LoggingConsole:hechoPopup(text, commands, hints, log)
|
||||
self:xEchoLink(text, "hp", commands, hints, true, log)
|
||||
end
|
||||
|
||||
--- echoPopup for LoggingConsole
|
||||
-- @param text the text to use for the link
|
||||
-- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]}
|
||||
-- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}}
|
||||
-- @param useCurrentFormat If set to true, will look like the text around it. If false it will be blue and underline.
|
||||
-- @param log Should we log this line? Defaults to self.log if not passed. If you want to pass this you must pass in useCurrentFormat
|
||||
-- @usage myLoggingConsole:echoPopup("This is a link!", {[[send("sleep")]], [[send("stand")]], {"sleep", "stand"}) -- text "This is a link" will send("sleep") when clicked and be blue w/ underline. Defaut log behaviour (self.log)
|
||||
-- @usage myLoggingConsole:echoPopup("This is a link!", {[[send("sleep")]], [[send("stand")]], {"sleep", "stand"}, false, false) -- same as above, but forces it not to log regardless of self.log setting
|
||||
-- @usage myLoggingConsole:echoPopup("This is a link!", {[[send("sleep")]], [[send("stand")]], {"sleep", "stand"}, true, true) -- same as above, but forces it to log regardless of self.log setting and the text will look like anything else echoed to the console.
|
||||
function LoggingConsole:echoPopup(text, commands, hints, useCurrentFormat, log)
|
||||
self:xEchoLink(text, "ep", commands, hints, useCurrentFormat, log)
|
||||
end
|
||||
|
||||
--- Append copy()ed text to the console
|
||||
-- @param log should we log this?
|
||||
function LoggingConsole:appendBuffer(log)
|
||||
self:xEcho("", "a", log)
|
||||
end
|
||||
|
||||
--- Append copy()ed text to the console
|
||||
-- @param log should we log this?
|
||||
function LoggingConsole:append(log)
|
||||
self:xEcho("", "a", log)
|
||||
end
|
||||
|
||||
--- echo's a string to the console.
|
||||
-- @param str the string to echo
|
||||
-- @param log should this be logged? Used to override the .log constraint
|
||||
function LoggingConsole:echo(str, log)
|
||||
self:xEcho(str, "e", log)
|
||||
end
|
||||
|
||||
--- hecho's a string to the console.
|
||||
-- @param str the string to hecho
|
||||
-- @param log should this be logged? Used to override the .log constraint
|
||||
function LoggingConsole:hecho(str, log)
|
||||
self:xEcho(str, "h", log)
|
||||
end
|
||||
|
||||
--- decho's a string to the console.
|
||||
-- @param str the string to decho
|
||||
-- @param log should this be logged? Used to override the .log constraint
|
||||
function LoggingConsole:decho(str, log)
|
||||
self:xEcho(str, "d", log)
|
||||
end
|
||||
|
||||
--- cecho's a string to the console.
|
||||
-- @param str the string to cecho
|
||||
-- @param log should this be logged? Used to override the .log constraint
|
||||
function LoggingConsole:cecho(str, log)
|
||||
self:xEcho(str, "c", log)
|
||||
end
|
||||
|
||||
--- Replays the last X lines from the console's log file, if it exists
|
||||
-- @param numberOfLines The number of lines to replay from the end of the file
|
||||
function LoggingConsole:replay(numberOfLines)
|
||||
local fileName = self:getFullFilename()
|
||||
if not exists(fileName) then
|
||||
return
|
||||
end
|
||||
local file = io.open(fileName, "r")
|
||||
local lines = file:read("*a")
|
||||
if self:getExtension() == "html" then
|
||||
for _, line in ipairs(htmlHeaderPattern:split("\n")) do
|
||||
if line ~= "" then
|
||||
lines = lines:gsub(line .. "\n", "")
|
||||
end
|
||||
end
|
||||
lines = dt.html2decho(lines)
|
||||
else
|
||||
lines = ansi2decho(lines)
|
||||
end
|
||||
local linesTbl = lines:split("\n")
|
||||
local result
|
||||
if #linesTbl <= numberOfLines then
|
||||
result = lines
|
||||
else
|
||||
result = ""
|
||||
local start = #linesTbl - numberOfLines
|
||||
for index, str in ipairs(linesTbl) do
|
||||
if index >= start then
|
||||
result = string.format("%s\n%s", result, str)
|
||||
end
|
||||
end
|
||||
end
|
||||
self:decho(result, false)
|
||||
end
|
||||
|
||||
setmetatable(LoggingConsole, parent)
|
||||
|
||||
return LoggingConsole
|
456
src/scripts/Avalon/MDK/loginator.lua
Normal file
456
src/scripts/Avalon/MDK/loginator.lua
Normal file
@ -0,0 +1,456 @@
|
||||
--- Loginator creates an object which allows you to log things to file at
|
||||
-- various severity levels, with the ability to only log items above a specific
|
||||
-- severity to file.
|
||||
-- @classmod Loginator
|
||||
-- @author Damian Monogue <demonnic@gmail.com>
|
||||
-- @copyright 2021 Damian Monogue
|
||||
-- @license MIT, see LICENSE.lua
|
||||
local Loginator = {
|
||||
format = "h",
|
||||
name = "logname",
|
||||
fileNameTemplate = "|p/log/Loginator/|y-|M-|d-|n.|e",
|
||||
entryTemplate = "|y-|M-|d |h:|m:|s.|x [|c|l|r] |t",
|
||||
level = "warn",
|
||||
bgColor = "black",
|
||||
fontSize = 12,
|
||||
fgColor = "white",
|
||||
}
|
||||
|
||||
local levelColors = {error = "red", warn = "DarkOrange", info = "ForestGreen", debug = "ansi_yellow"}
|
||||
local loggerLevels = {error = 1, warn = 2, info = 3, debug = 4}
|
||||
|
||||
local function exists(path)
|
||||
local ok, err, code = os.rename(path, path)
|
||||
if not ok and code == 13 then
|
||||
return true
|
||||
end
|
||||
return ok, err
|
||||
end
|
||||
|
||||
local function isWindows()
|
||||
return package.config:sub(1, 1) == [[\]]
|
||||
end
|
||||
|
||||
local function mkdir_p(path)
|
||||
path = path:gsub("\\", "/")
|
||||
local pathTbl = path:split("/")
|
||||
local cwd = "/"
|
||||
if isWindows() then
|
||||
cwd = ""
|
||||
end
|
||||
for index, dirName in ipairs(pathTbl) do
|
||||
if index == 1 then
|
||||
cwd = cwd .. dirName
|
||||
else
|
||||
cwd = cwd .. "/" .. dirName
|
||||
cwd = cwd:gsub("//", "/")
|
||||
end
|
||||
if not table.contains({"/", "C:"}, cwd) and not exists(cwd) then
|
||||
local ok, err = lfs.mkdir(cwd)
|
||||
if not ok then
|
||||
return ok, err
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local htmlHeaderTemplate = [=[ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
|
||||
<link href='http://fonts.googleapis.com/css?family=Droid+Sans+Mono' rel='stylesheet' type='text/css'>
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: |b;
|
||||
color: |c;
|
||||
font-family: 'Droid Sans Mono';
|
||||
white-space: pre;
|
||||
font-size: |fpx;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body><span>
|
||||
]=]
|
||||
|
||||
--- Creates a new Loginator object
|
||||
--@tparam table options table of options for the logger
|
||||
-- <table class="tg">
|
||||
-- <thead>
|
||||
-- <tr>
|
||||
-- <th>option name</th>
|
||||
-- <th>description</th>
|
||||
-- <th>default</th>
|
||||
-- </tr>
|
||||
-- </thead>
|
||||
-- <tbody>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">format</td>
|
||||
-- <td class="tg-1">What format to log in? "h" for html, "a" for ansi, anything else for plaintext.</td>
|
||||
-- <td class="tg-1">"h"</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">name</td>
|
||||
-- <td class="tg-2">What is the name of the logger? Will replace |n in templates</td>
|
||||
-- <td class="tg-2">logname</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">level</td>
|
||||
-- <td class="tg-1">What level should the logger operate at? This will control what level the log function defaults to, as well as what logs will actually be written<br>
|
||||
-- Only items of an equal or higher severity to this will be written to the log file.</td>
|
||||
-- <td class="tg-1">"info"</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">bgColor</td>
|
||||
-- <td class="tg-2">What background color to use for html logs</td>
|
||||
-- <td class="tg-2">"black"</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">fgColor</td>
|
||||
-- <td class="tg-1">What color to use for the main text in html logs</td>
|
||||
-- <td class="tg-1">"white"</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">fontSize</td>
|
||||
-- <td class="tg-2">What font size to use in html logs</td>
|
||||
-- <td class="tg-2">12</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">levelColors</td>
|
||||
-- <td class="tg-1">Table with the log level as the key, and the color which corresponds to it as the value</td>
|
||||
-- <td class="tg-1">{ error = "red", warn = "DarkOrange", info = "ForestGreen", debug = "ansi_yellow" }</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">fileNameTemplate</td>
|
||||
-- <td class="tg-2">A template which will be transformed into the full filename, with path. See template options below for replacements</td>
|
||||
-- <td class="tg-2">"|p/log/Loginator/|y-|M-|d-|n.|e"</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">entryTemplate</td>
|
||||
-- <td class="tg-1">The template which controls the look of each log entry. See template options below for replacements</td>
|
||||
-- <td class="tg-1">"|y-|M-|d |h:|m:|s.|x [|c|l|r] |t"</td>
|
||||
-- </tr>
|
||||
-- </tbody>
|
||||
-- </table><br>
|
||||
-- Table of template options
|
||||
-- <table class="tg">
|
||||
-- <thead>
|
||||
-- <tr>
|
||||
-- <th>template code</th>
|
||||
-- <th>what it is replaced with</th>
|
||||
-- <th>example</th>
|
||||
-- </tr>
|
||||
-- </thead>
|
||||
-- <tbody>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">|y</td>
|
||||
-- <td class="tg-1">the year in 4 digits</td>
|
||||
-- <td class="tg-1">2021</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">|p</td>
|
||||
-- <td class="tg-2">getMudletHomeDir()</td>
|
||||
-- <td class="tg-2">/home/demonnic/.config/mudlet/profiles/testprofile</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">|M</td>
|
||||
-- <td class="tg-1">Month as 2 digits</td>
|
||||
-- <td class="tg-1">05</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">|d</td>
|
||||
-- <td class="tg-2">day, as 2 digits</td>
|
||||
-- <td class="tg-2">23</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">|h</td>
|
||||
-- <td class="tg-1">hour in 24hr time format, 2 digits</td>
|
||||
-- <td class="tg-1">03</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">|m</td>
|
||||
-- <td class="tg-2">minute as 2 digits</td>
|
||||
-- <td class="tg-2">42</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">|s</td>
|
||||
-- <td class="tg-1">seconds as 2 digits</td>
|
||||
-- <td class="tg-1">34</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">|x</td>
|
||||
-- <td class="tg-2">milliseconds as 3 digits</td>
|
||||
-- <td class="tg-2">194</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">|e</td>
|
||||
-- <td class="tg-1">Filename extension expected. "html" for html format, "log" for everything else</td>
|
||||
-- <td class="tg-1">html</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">|l</td>
|
||||
-- <td class="tg-2">The logging level of the entry, in ALLCAPS</td>
|
||||
-- <td class="tg-2">WARN</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">|c</td>
|
||||
-- <td class="tg-1">The color which corresponds with the logging level. Set via the levelColors table in the options. Example not included.</td>
|
||||
-- <td class="tg-1"></td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">|r</td>
|
||||
-- <td class="tg-2">Reset back to standard color. Used to close |c. Example not included</td>
|
||||
-- <td class="tg-2"></td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">|n</td>
|
||||
-- <td class="tg-1">The name of the logger, set via the options when you have Loginator create it.</td>
|
||||
-- <td class="tg-1">CoolPackageLog</td>
|
||||
-- </tr>
|
||||
--</tbody>
|
||||
--</table>
|
||||
--@return newly created logger object
|
||||
function Loginator:new(options)
|
||||
options = options or {}
|
||||
local optionsType = type(options)
|
||||
if optionsType ~= "table" then
|
||||
return nil, f "Loginator:new(options) options as table expected, got {optionsType}"
|
||||
end
|
||||
local me = table.deepcopy(options)
|
||||
me.levelColors = me.levelColors or {}
|
||||
local lcType = type(me.levelColors)
|
||||
if lcType ~= "table" then
|
||||
return nil, f "Loginator:new(options) provided options.levelColors must be a table, but you provided a {lcType}"
|
||||
end
|
||||
for lvl,clr in pairs(levelColors) do
|
||||
me.levelColors[lvl] = me.levelColors[lvl] or clr
|
||||
end
|
||||
setmetatable(me, self)
|
||||
self.__index = self
|
||||
return me
|
||||
end
|
||||
|
||||
---@local
|
||||
function Loginator:processTemplate(str, level)
|
||||
local lvl = level or self.level
|
||||
local timeTable = getTime()
|
||||
for what, with in pairs({
|
||||
["|y"] = function()
|
||||
return timeTable.year
|
||||
end,
|
||||
["|p"] = getMudletHomeDir,
|
||||
["|M"] = function()
|
||||
return string.format("%02d", timeTable.month)
|
||||
end,
|
||||
["|d"] = function()
|
||||
return string.format("%02d", timeTable.day)
|
||||
end,
|
||||
["|h"] = function()
|
||||
return string.format("%02d", timeTable.hour)
|
||||
end,
|
||||
["|m"] = function()
|
||||
return string.format("%02d", timeTable.min)
|
||||
end,
|
||||
["|s"] = function()
|
||||
return string.format("%02d", timeTable.sec)
|
||||
end,
|
||||
["|x"] = function()
|
||||
return string.format("%03d", timeTable.msec)
|
||||
end,
|
||||
["|e"] = function()
|
||||
return (self.format:starts("h") and "html" or "log")
|
||||
end,
|
||||
["|l"] = function()
|
||||
return lvl:upper()
|
||||
end,
|
||||
["|c"] = function()
|
||||
return self:getColor(lvl)
|
||||
end,
|
||||
["|r"] = function()
|
||||
return self:getReset()
|
||||
end,
|
||||
["|n"] = function()
|
||||
return self.name
|
||||
end,
|
||||
}) do
|
||||
if str:find(what) then
|
||||
str = str:gsub(what, with())
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
--- Set the color to associate with a logging level post-creation
|
||||
--@param color The color to set for the level, as a string. Can be any valid color string for cecho, decho, or hecho.
|
||||
--@param level The level to set the color for. Must be one of 'error', 'warn', 'info', or 'debug'
|
||||
--@return true if the color is updated, or nil+error if it could not be updated for some reason.
|
||||
function Loginator:setColorForLevel(color, level)
|
||||
if not color then
|
||||
return nil, "You must provide a color to set"
|
||||
end
|
||||
if not level then
|
||||
return nil, "You must provide a level to set the color for"
|
||||
end
|
||||
if not loggerLevels[level] then
|
||||
return nil, "Invalid level. Valid levels are 'error', 'warn', 'info', or 'debug'"
|
||||
end
|
||||
if not Geyser.Color.parse(color) then
|
||||
return nil, "You must provide a color which can be parsed by Geyser.Color.parse. Examples are 'blue' (cecho), '<128,0,0>' (decho), '#aa3388' (hecho), or {128,0,0} (table of r,g,b values)"
|
||||
end
|
||||
self.levelColors[level] = color
|
||||
return true
|
||||
end
|
||||
|
||||
---@local
|
||||
function Loginator:getColor(level)
|
||||
if self.format == "t" then
|
||||
return ""
|
||||
end
|
||||
local r, g, b = Geyser.Color.parse((self.levelColors[level] or {128, 128, 128}))
|
||||
if self.format == "h" then
|
||||
return string.format("<span style='color: rgb(%d,%d,%d);'>", r, g, b)
|
||||
elseif self.format == "a" then
|
||||
return string.format("\27[38:2::%d:%d:%dm", r, g, b)
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
---@local
|
||||
function Loginator:getReset()
|
||||
if self.format == "t" then
|
||||
return ""
|
||||
elseif self.format == "h" then
|
||||
return "</span>"
|
||||
elseif self.format == "a" then
|
||||
return "\27[39;49m"
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
--- Returns the full path and filename to the logfile
|
||||
function Loginator:getFullFilename()
|
||||
return self:processTemplate(self.fileNameTemplate)
|
||||
end
|
||||
|
||||
--- Write an error level message to the logfile. Error level messages are always written.
|
||||
--@param msg the message to log
|
||||
--@return true if msg written, nil+error if error
|
||||
function Loginator:error(msg)
|
||||
return self:log(msg, "error")
|
||||
end
|
||||
|
||||
--- Write a warn level message to the logfile.
|
||||
-- Msg is only written if the logger level is <= warn
|
||||
-- From most to least severe the levels are:
|
||||
-- error > warn > info > debug
|
||||
--@param msg the message to log
|
||||
--@return true if msg written, false if skipped due to level, nil+error if error
|
||||
function Loginator:warn(msg)
|
||||
return self:log(msg, "warn")
|
||||
end
|
||||
|
||||
--- Write an info level message to the logfile.
|
||||
-- Msg is only written if the logger level is <= info
|
||||
-- From most to least severe the levels are:
|
||||
-- error > warn > info > debug
|
||||
--@param msg the message to log
|
||||
--@return true if msg written, false if skipped due to level, nil+error if error
|
||||
function Loginator:info(msg)
|
||||
return self:log(msg, "info")
|
||||
end
|
||||
|
||||
--- Write a debug level message to the logfile.
|
||||
-- Msg is only written if the logger level is debug
|
||||
-- From most to least severe the levels are:
|
||||
-- error > warn > info > debug
|
||||
--@param msg the message to log
|
||||
--@return true if msg written, false if skipped due to level, nil+error if error
|
||||
function Loginator:debug(msg)
|
||||
return self:log(msg, "debug")
|
||||
end
|
||||
|
||||
--- Write a message to the log file and optionally specify the level
|
||||
--@param msg the message to log
|
||||
--@param level the level to log the message at. Defaults to the level of the logger itself if not provided.
|
||||
--@return true if msg written, false if skipped due to level, nil+error if error
|
||||
function Loginator:log(msg, level)
|
||||
level = level or self.level
|
||||
local levelNumber = loggerLevels[level]
|
||||
if not levelNumber then
|
||||
return nil, f"Unknown logging level: {level}. Valid levels are 'error', 'warn', 'info', and 'debug'"
|
||||
end
|
||||
local displayLevelNumber = loggerLevels[self.level]
|
||||
if levelNumber > displayLevelNumber then
|
||||
return false
|
||||
end
|
||||
local filename = self:getFullFilename()
|
||||
local filteredMsg = self:processTemplate(self.entryTemplate, level):gsub("|t", msg)
|
||||
local ok, err = self:createPathIfNotExists(filename)
|
||||
if err then
|
||||
debugc(err)
|
||||
return ok, err
|
||||
end
|
||||
if self.format == "h" and not io.exists(filename) then
|
||||
filteredMsg = self:getHtmlHeader() .. filteredMsg
|
||||
end
|
||||
local file, err = io.open(filename, "a")
|
||||
if not file then
|
||||
err = string.format("Logger %s failed to open %s because: %s\n", self.name, filename, err)
|
||||
debugc(err)
|
||||
return nil, err
|
||||
end
|
||||
file:write(filteredMsg .. "\n")
|
||||
file:close()
|
||||
return true
|
||||
end
|
||||
|
||||
--- Uses openUrl() to request your OS open the logfile in the appropriate application. Usually your web browser for html and text editor for all others.
|
||||
function Loginator:open()
|
||||
openUrl(self:getFullFilename())
|
||||
end
|
||||
|
||||
--- Uses openUrl() to request your OS open the directory the logfile resides in. This allows for easier browsing if you have more than one file.
|
||||
function Loginator:openDir()
|
||||
openUrl(self:getPath())
|
||||
end
|
||||
|
||||
--- Returns the path to the log file (directory in which the file resides) as a string
|
||||
--@param filename optional filename to return the path of. If not supplied, with use the logger's current filename
|
||||
function Loginator:getPath(filename)
|
||||
filename = filename or self:getFullFilename()
|
||||
filename = filename:gsub([[\]], "/")
|
||||
local filenameTable = filename:split("/")
|
||||
filenameTable[#filenameTable] = nil
|
||||
local path = table.concat(filenameTable, "/")
|
||||
return path
|
||||
end
|
||||
|
||||
---@local
|
||||
function Loginator:createPathIfNotExists(filename)
|
||||
if exists(filename) then
|
||||
return false
|
||||
end
|
||||
filename = filename:gsub([[\]], "/")
|
||||
local path = self:getPath(filename)
|
||||
if exists(path) then
|
||||
return false
|
||||
end
|
||||
local ok, err = mkdir_p(path)
|
||||
if not ok then
|
||||
err = string.format("Could not create directory for log files: %s\n Reason was: %s", path, err)
|
||||
return nil, err
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
---@local
|
||||
function Loginator:getHtmlHeader()
|
||||
local header = htmlHeaderTemplate
|
||||
header = header:gsub("|b", self.bgColor)
|
||||
header = header:gsub("|c", self.fgColor)
|
||||
header = header:gsub("|f", self.fontSize)
|
||||
return header
|
||||
end
|
||||
|
||||
return Loginator
|
255
src/scripts/Avalon/MDK/sug.lua
Normal file
255
src/scripts/Avalon/MDK/sug.lua
Normal file
@ -0,0 +1,255 @@
|
||||
--- Self Updating Gauge, extends <a href="https://www.mudlet.org/geyser/files/geyser/GeyserGauge.html">Geyser.Gauge</a>
|
||||
-- @classmod SUG
|
||||
-- @author Damian Monogue <demonnic@gmail.com>
|
||||
-- @copyright 2020 Damian Monogue
|
||||
-- @license MIT, see LICENSE.lua
|
||||
local SUG = {
|
||||
name = "SelfUpdatingGaugeClass",
|
||||
active = true,
|
||||
updateTime = 333,
|
||||
currentVariable = "",
|
||||
maxVariable = "",
|
||||
defaultCurrent = 50,
|
||||
defaultMax = 100,
|
||||
textTemplate = " |c/|m |p%",
|
||||
strict = true,
|
||||
}
|
||||
|
||||
-- Internal function, used to turn a string variable name into a value
|
||||
local function getValueAt(accessString)
|
||||
local ok, err = pcall(loadstring("return " .. tostring(accessString)))
|
||||
if ok then return err end
|
||||
return nil, err
|
||||
end
|
||||
|
||||
-- ========== End section copied from demontools.lua
|
||||
|
||||
--- Creates a new Self Updating Gauge.
|
||||
-- @tparam table cons table of options which control the Gauge's behaviour. In addition to all valid contraints for Geyser.Gauge, SUG adds:
|
||||
-- <br>
|
||||
-- <table class="tg">
|
||||
-- <tr>
|
||||
-- <th>name</th>
|
||||
-- <th>description</th>
|
||||
-- <th>default</th>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">active</td>
|
||||
-- <td class="tg-1">boolean, if true starts the timer updating</td>
|
||||
-- <td class="tg-1">true</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">updateTime</td>
|
||||
-- <td class="tg-2">How often should the gauge autoupdate? Milliseconds. 0 to disable the timer but still allow event updates</td>
|
||||
-- <td class="tg-2">333</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">currentVariable</td>
|
||||
-- <td class="tg-1">What variable will hold the 'current' value of the gauge? Pass the name as a string, IE "currentHP" or "gmcp.Char.Vitals.hp"</td>
|
||||
-- <td class="tg-1">""</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">maxVariable</td>
|
||||
-- <td class="tg-2">What variable will hold the 'current' value of the gauge? Pass the name as a string, IE "maxHP" or "gmcp.Char.Vitals.maxhp"</td>
|
||||
-- <td class="tg-2">""</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">textTemplate</td>
|
||||
-- <td class="tg-1">Template to use for the text on the gauge. "|c" replaced with current value, "|m" replaced with max value, "|p" replaced with the % full the gauge should be</td>
|
||||
-- <td class="tg-1">" |c/|m |p%"</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">defaultCurrent</td>
|
||||
-- <td class="tg-2">What value to use if the currentVariable points to nil or something which cannot be made a number?</td>
|
||||
-- <td class="tg-2">50</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">defaultMax</td>
|
||||
-- <td class="tg-1">What value to use if the maxVariable points to nil or something which cannot be made a number?</td>
|
||||
-- <td class="tg-1">100</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">updateEvent</td>
|
||||
-- <td class="tg-2">The name of an event to listen for to perform an update. Can be run alongside or instead of the timer updates. Empty string to turn off</td>
|
||||
-- <td class="tg-2">""</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">updateHook</td>
|
||||
-- <td class="tg-1">A function which is run each time the gauge updates. Should take 3 arguments, the gauge itself, current value, and max value. You can return new current and max values to be used, for example `return 34, 120` would cause the gauge to use 34 for current and 120 for max regardless of what the variables it reads say.</td>
|
||||
-- <td class="tg-1"></td>
|
||||
-- </tr>
|
||||
-- </table>
|
||||
-- @param container The Geyser container for this gauge
|
||||
-- @usage
|
||||
-- local SUG = require("MDK.sug") --the following will watch "gmcp.Char.Vitals.hp" and "gmcp.Char.Vitals.maxhp" and update itself every 333 milliseconds
|
||||
-- myGauge = SUG:new({
|
||||
-- name = "myGauge",
|
||||
-- currentVariable = "gmcp.Char.Vitals.hp", --if this is nil, it will use the defaultCurrent of 50
|
||||
-- maxVariable = "gmcp.Char.Vitals.maxhp", --if this is nil, it will use the defaultMax of 100.
|
||||
-- height = 50,
|
||||
-- })
|
||||
function SUG:new(cons, container)
|
||||
local funcName = "SUG:new(cons, container)"
|
||||
cons = cons or {}
|
||||
local consType = type(cons)
|
||||
assert(consType == "table", string.format("%s: cons as table expected, got %s", funcName, consType))
|
||||
local me = SUG.parent:new(cons, container)
|
||||
setmetatable(me, self)
|
||||
self.__index = self
|
||||
-- apply any styling requested
|
||||
if me.cssFront then
|
||||
if not me.cssBack then
|
||||
me.cssBack = me.cssFront .. "background-color: black;"
|
||||
end
|
||||
me:setStyleSheet(me.cssFront, me.cssBack, me.cssText)
|
||||
end
|
||||
if me.active then
|
||||
me:start()
|
||||
end
|
||||
me:update()
|
||||
return me
|
||||
end
|
||||
|
||||
--- Set how often to update the gauge on a timer
|
||||
-- @tparam number time time in milliseconds. 0 to disable the timer
|
||||
function SUG:setUpdateTime(time)
|
||||
if type(time) ~= "number" then
|
||||
debugc("SUG:setUpdateTime(time) time as number expected, got " .. type(time))
|
||||
return
|
||||
end
|
||||
self.updateTime = time
|
||||
if self.active then self:start() end
|
||||
end
|
||||
|
||||
--- Set the event to listen for to update the gauge
|
||||
-- @tparam string event the name of the event to listen for, use "" to disable events without stopping any existing timers
|
||||
function SUG:setUpdateEvent(event)
|
||||
if type(event) ~= string then
|
||||
debugc("SUG:setUpdateEvent(event) event name as string expected, got " .. type(event))
|
||||
return
|
||||
end
|
||||
self.updateEvent = event
|
||||
if self.active then self:start() end
|
||||
end
|
||||
|
||||
--- Set the name of the variable the Self Updating Gauge watches for the 'current' value of the gauge
|
||||
-- @tparam string variableName The name of the variable to get the current value for the gauge. For instance "currentHP", "gmcp.Char.Vitals.hp" etc
|
||||
function SUG:setCurrentVariable(variableName)
|
||||
local nameType = type(variableName)
|
||||
local funcName = "SUG:setCurrentVariable(variableName)"
|
||||
assert(nameType == "string", string.format("%s: variableName as string expected, got: %s", funcName, nameType))
|
||||
local val = getValueAt(variableName)
|
||||
local valType = type(tonumber(val))
|
||||
assert(valType == "number",
|
||||
string.format("%s: variableName must point to a variable which is a number or coercable into one. %s points to a %s", funcName, variableName,
|
||||
type(val)))
|
||||
self.currentVariable = variableName
|
||||
self:update()
|
||||
end
|
||||
|
||||
--- Set the name of the variable the Self Updating Gauge watches for the 'max' value of the gauge
|
||||
-- @tparam string variableName The name of the variable to get the max value for the gauge. For instance "maxHP", "gmcp.Char.Vitals.maxhp" etc. Set to "" to only check the current value
|
||||
function SUG:setMaxVariable(variableName)
|
||||
if variableName == "" then
|
||||
self.maxVariable = variableName
|
||||
self:update()
|
||||
return
|
||||
end
|
||||
local nameType = type(variableName)
|
||||
local funcName = "SUG:setMaxVariable(variableName)"
|
||||
assert(nameType == "string", string.format("%s: variableName as string expected, got: %s", funcName, nameType))
|
||||
local val = getValueAt(variableName)
|
||||
local valType = type(tonumber(val))
|
||||
assert(valType == "number",
|
||||
string.format("%s: variableName must point to a variable which is a number or coercable into one. %s points to a %s", funcName, variableName,
|
||||
type(val)))
|
||||
self.maxVariable = variableName
|
||||
self:update()
|
||||
end
|
||||
|
||||
--- Set the template for the Self Updating Gauge to set the text with. "|c" is replaced by the current value, "|m" is replaced by the max value, and "|p" is replaced by the percentage current/max
|
||||
-- @tparam string template The template to use for the text on the gauge. If the max value is 200 and current is 68, then |c will be replace by 68, |m replaced by 200, and |p replaced by 34.
|
||||
function SUG:setTextTemplate(template)
|
||||
local templateType = type(template)
|
||||
local funcName = "SUG:setTextTemplate(template)"
|
||||
assert(templateType == "string", string.format("%s: template as string expected, got %s", funcName, templateType))
|
||||
self.textTemplate = template
|
||||
self:update()
|
||||
end
|
||||
|
||||
--- Set the updateHook function which is run just prior to the gauge updating
|
||||
-- @tparam function func The function which will be called when the gauge updates. It should take 3 arguments, the gauge itself, the current value, and the max value. If you wish to override the current or max values used for the gauge, you can return new current and max values, like `return newCurrent newMax`
|
||||
function SUG:setUpdateHook(func)
|
||||
local funcType = type(func)
|
||||
if funcType ~= "function" then
|
||||
return nil, "setUpdateHook only takes functions, no strings or anything like that. You passed in: " .. funcType
|
||||
end
|
||||
self.updateHook = func
|
||||
end
|
||||
|
||||
--- Stops the Self Updating Gauge from updating
|
||||
function SUG:stop()
|
||||
self.active = false
|
||||
if self.timer then
|
||||
killTimer(self.timer)
|
||||
self.timer = nil
|
||||
end
|
||||
if self.eventHandler then
|
||||
killAnonymousEventHandler(self.eventHandler)
|
||||
self.eventHandler = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Starts the Self Updating Gauge updating. If it is already updating, it will restart it.
|
||||
function SUG:start()
|
||||
self:stop()
|
||||
self.active = true
|
||||
local update = function() self:update() end
|
||||
if self.updateTime > 0 then
|
||||
self.timer = tempTimer(self.updateTime / 1000, update, true)
|
||||
end
|
||||
local updateEvent = self.updateEvent
|
||||
if updateEvent and updateEvent ~= "" and updateEvent ~= "*" then
|
||||
self.eventHandler = registerAnonymousEventHandler(self.updateEvent, update)
|
||||
end
|
||||
end
|
||||
|
||||
--- Reads the values from currentVariable and maxVariable, and updates the gauge's value and text.
|
||||
function SUG:update()
|
||||
local current = getValueAt(self.currentVariable)
|
||||
local max = getValueAt(self.maxVariable)
|
||||
current = tonumber(current)
|
||||
max = tonumber(max)
|
||||
if current == nil then
|
||||
current = self.defaultCurrent
|
||||
debugc(string.format(
|
||||
"Self Updating Gauge named %s is trying to update with an invalid current value. Using the defaultCurrent instead. currentVariable: '%s' maxVariable: '%s'",
|
||||
self.name, self.currentVariable, self.maxVariable))
|
||||
end
|
||||
if max == nil then
|
||||
max = self.defaultMax
|
||||
if self.maxVariable ~= "" then
|
||||
debugc(string.format(
|
||||
"Self Updating Gauge named %s is trying to update with an invalid max value. Using the defaultCurrent instead. currentVariable: '%s' maxVariable: '%s'",
|
||||
self.name, self.currentVariable, self.maxVariable))
|
||||
end
|
||||
end
|
||||
if self.updateHook and type(self.updateHook) == "function" then
|
||||
local ok, newcur, newmax = pcall(self.updateHook, self, current, max)
|
||||
if ok and newcur then
|
||||
current = newcur
|
||||
max = newmax and newmax or self.defaultMax
|
||||
end
|
||||
end
|
||||
local text = self.textTemplate
|
||||
local percent = math.floor((current / max * 100) + 0.5)
|
||||
text = text:gsub("|c", current)
|
||||
text = text:gsub("|m", max)
|
||||
text = text:gsub("|p", percent)
|
||||
self:setValue(current, max, text)
|
||||
end
|
||||
|
||||
SUG.parent = Geyser.Gauge
|
||||
setmetatable(SUG, Geyser.Gauge)
|
||||
|
||||
return SUG
|
335
src/scripts/Avalon/MDK/textgauge.lua
Normal file
335
src/scripts/Avalon/MDK/textgauge.lua
Normal file
@ -0,0 +1,335 @@
|
||||
--- Creates a text based gauge, for use in miniconsoles and the like.
|
||||
-- @classmod TextGauge
|
||||
-- @author Damian Monogue <demonnic@gmail.com>
|
||||
-- @copyright 2020 Damian Monogue
|
||||
-- @copyright 2021 Damian Monogue
|
||||
-- @license MIT, see LICENSE.lua
|
||||
local TextGauge = {width = 24, fillCharacter = ":", emptyCharacter = "-", showPercent = true, showPercentSymbol = true, format = "c", value = 50}
|
||||
|
||||
--- Creates a new TextGauge.
|
||||
-- @tparam[opt] table options The table of options you would like the TextGauge to start with.
|
||||
-- <br><br>Table of new options
|
||||
-- <table class="tg">
|
||||
-- <thead>
|
||||
-- <tr>
|
||||
-- <th>option name</th>
|
||||
-- <th>description</th>
|
||||
-- <th>default</th>
|
||||
-- </tr>
|
||||
-- </thead>
|
||||
-- <tbody>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">width</td>
|
||||
-- <td class="tg-1">How many characters wide to make the gauge</td>
|
||||
-- <td class="tg-1">24</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">fillCharacter</td>
|
||||
-- <td class="tg-2">What character to use for the 'full' part of the gauge</td>
|
||||
-- <td class="tg-2">:</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">overflowCharacter</td>
|
||||
-- <td class="tg-1">What character to use for >100% part of the gauge</td>
|
||||
-- <td class="tg-1">if not set, it uses whatever you set fillCharacter to</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">emptyCharacter</td>
|
||||
-- <td class="tg-2">What character to use for the 'empty' part of the gauge</td>
|
||||
-- <td class="tg-2">-</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">showPercentSymbol</td>
|
||||
-- <td class="tg-1">Should we show the % sign itself?</td>
|
||||
-- <td class="tg-1">true</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">showPercent</td>
|
||||
-- <td class="tg-2">Should we show what % of the gauge is filled?</td>
|
||||
-- <td class="tg-2">true</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">value</td>
|
||||
-- <td class="tg-1">How much of the gauge should be filled</td>
|
||||
-- <td class="tg-1">50</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">format</td>
|
||||
-- <td class="tg-2">What type of color formatting to use? 'c' for cecho, 'd' for decho, 'h' for hecho</td>
|
||||
-- <td class="tg-2">c</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">fillColor</td>
|
||||
-- <td class="tg-1">What color to make the full part of the bar?</td>
|
||||
-- <td class="tg-1">"DarkOrange" or equivalent for your format type</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">emptyColor</td>
|
||||
-- <td class="tg-2">what color to use for the empty part of the bar?</td>
|
||||
-- <td class="tg-2">"white" or format appropriate equivalent</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">percentColor</td>
|
||||
-- <td class="tg-1">What color to print the percentage numvers in, if shown?</td>
|
||||
-- <td class="tg-1">"white" or fortmat appropriate equivalent</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">percentSymbolColor</td>
|
||||
-- <td class="tg-2">What color to make the % if shown?</td>
|
||||
-- <td class="tg-2">If not set, uses what percentColor is set to.</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">overflowColor</td>
|
||||
-- <td class="tg-1">What color to make the >100% portion of the bar?</td>
|
||||
-- <td class="tg-1">If not set, will use the same color as fillColor</td>
|
||||
-- </tr>
|
||||
-- </tbody>
|
||||
-- </table>
|
||||
-- @usage
|
||||
-- local TextGauge = require("MDK.textgauge")
|
||||
-- myTextGauge = TextGauge:new()
|
||||
-- gaugeText = myTextGauge:setValue(382, 830)
|
||||
function TextGauge:new(options)
|
||||
options = options or {}
|
||||
local optionsType = type(options)
|
||||
assert(optionsType == "table" or optionsType == "nil", "TextGauge:new(options): options expected as table, got " .. optionsType)
|
||||
local me = table.deepcopy(options)
|
||||
setmetatable(me, self)
|
||||
self.__index = self
|
||||
me:setDefaultColors()
|
||||
return me
|
||||
end
|
||||
|
||||
--- Sets the width in characters of the gauge
|
||||
-- @tparam number width number of characters wide to make the gauge
|
||||
function TextGauge:setWidth(width)
|
||||
local widthType = type(width)
|
||||
assert(widthType == "number", string.format("TextGauge:setWidth(width): width as number expected, got %s", widthType))
|
||||
self.width = width
|
||||
end
|
||||
|
||||
function TextGauge:setFormat(format)
|
||||
self.format = self:getColorType(format)
|
||||
self:setDefaultColors()
|
||||
end
|
||||
|
||||
--- Sets the character to use for the 'full' part of the gauge
|
||||
-- @tparam string character the character to use.
|
||||
function TextGauge:setFillCharacter(character)
|
||||
assert(character ~= nil, "TextGauge:setFillCharacter(character): character required, got nil")
|
||||
assert(utf8.len(character) == 1, "TextGauge:setFillCharacter(character): character must be a single character")
|
||||
self.fillCharacter = character
|
||||
end
|
||||
|
||||
--- Sets the character to use for the 'overflow' (>100%) part of the gauge
|
||||
-- @tparam string character the character to use.
|
||||
function TextGauge:setOverflowCharacter(character)
|
||||
assert(character ~= nil, "TextGauge:setOverflowCharacter(character): character required, got nil")
|
||||
assert(utf8.len(character) == 1, "TextGauge:setOverflowCharacter(character): character must be a single character")
|
||||
self.overflowCharacter = character
|
||||
end
|
||||
|
||||
--- Sets the character to use for the 'full' part of the gauge
|
||||
-- @tparam string character the character to use.
|
||||
function TextGauge:setEmptyCharacter(character)
|
||||
assert(character ~= nil, "TextGauge:setEmptyCharacter(character): character required, got nil")
|
||||
assert(utf8.len(character) == 1, "TextGauge:setEmptyCharacter(character): character must be a single character")
|
||||
self.emptyCharacter = character
|
||||
end
|
||||
|
||||
--- Sets the fill color for the gauge.
|
||||
-- @tparam string color the color to use for the full portion of the gauge. Will be run through Geyser.Golor
|
||||
function TextGauge:setFillColor(color)
|
||||
assert(color ~= nil, "TextGauge:setFillColor(color): color required, got nil")
|
||||
self.fillColor = color
|
||||
end
|
||||
|
||||
--- Sets the overflow color for the gauge.
|
||||
-- @tparam string color the color to use for the full portion of the gauge. Will be run through Geyser.Golor
|
||||
function TextGauge:setOverflowColor(color)
|
||||
assert(color ~= nil, "TextGauge:setOverflowColor(color): color required, got nil")
|
||||
self.overflowColor = color
|
||||
end
|
||||
|
||||
--- Sets the empty color for the gauge.
|
||||
-- @tparam string color the color to use for the empty portion of the gauge. Will be run through Geyser.Golor
|
||||
function TextGauge:setEmptyColor(color)
|
||||
assert(color ~= nil, "TextGauge:setEmptyColor(color): color required, got nil")
|
||||
self.emptyColor = color
|
||||
end
|
||||
|
||||
--- Sets the fill color for the gauge.
|
||||
-- @tparam string color the color to use for the numeric value. Will be run through Geyser.Golor
|
||||
function TextGauge:setPercentColor(color)
|
||||
assert(color ~= nil, "TextGauge:setPercentColor(color): color required, got nil")
|
||||
self.percentColor = color
|
||||
end
|
||||
--- Sets the fill color for the gauge.
|
||||
-- @tparam string color the color to use for the numeric value. Will be run through Geyser.Golor
|
||||
function TextGauge:setPercentSymbolColor(color)
|
||||
assert(color ~= nil, "TextGauge:setPercentSymbolColor(color): color required, got nil")
|
||||
self.percentSymbolColor = color
|
||||
end
|
||||
|
||||
--- Enables reversing the fill direction (right to left instead of the usual left to right)
|
||||
function TextGauge:enableReverse()
|
||||
self.reverse = true
|
||||
end
|
||||
|
||||
--- Disables reversing the fill direction (go back to the usual left to right)
|
||||
function TextGauge:disableReverse()
|
||||
self.reverse = false
|
||||
end
|
||||
|
||||
--- Enables showing the percent value of the gauge
|
||||
function TextGauge:enableShowPercent()
|
||||
self.showPercent = true
|
||||
end
|
||||
|
||||
--- Disables showing the percent value of the gauge
|
||||
function TextGauge:disableShowPercent()
|
||||
self.showPercent = false
|
||||
end
|
||||
|
||||
--- Enables showing the percent symbol (appears after the value)
|
||||
function TextGauge:enableShowPercentSymbol()
|
||||
self.showPercentSymbol = true
|
||||
end
|
||||
|
||||
--- Enables showing the percent symbol (appears after the value)
|
||||
function TextGauge:disableShowPercentSymbol()
|
||||
self.showPercentSymbol = false
|
||||
end
|
||||
|
||||
function TextGauge:getColorType(format)
|
||||
format = format or self.format
|
||||
local dec = {"d", "decimal", "dec", "decho"}
|
||||
local hex = {"h", "hexidecimal", "hex", "hecho"}
|
||||
local col = {"c", "color", "colour", "col", "name", "cecho"}
|
||||
if table.contains(col, format) then
|
||||
return "c"
|
||||
elseif table.contains(dec, format) then
|
||||
return "d"
|
||||
elseif table.contains(hex, format) then
|
||||
return "h"
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
-- internal function, used at instantiation to ensure some colors are set
|
||||
function TextGauge:setDefaultColors()
|
||||
local colorType = self:getColorType()
|
||||
if colorType == "c" then
|
||||
self.percentColor = self.percentColor or "white"
|
||||
self.percentSymbolColor = self.percentSymbolColor or self.percentColor
|
||||
self.fillColor = self.fillColor or "DarkOrange"
|
||||
self.emptyColor = self.emptyColor or "white"
|
||||
self.resetColor = "<reset>"
|
||||
elseif colorType == "d" then
|
||||
self.percentColor = self.percentColor or "<255,255,255>"
|
||||
self.percentSymbolColor = self.percentSymbolColor or self.percentColor
|
||||
self.fillColor = self.fillColor or "<255,140,0>"
|
||||
self.emptyColor = self.emptyColor or "<255,255,255>"
|
||||
self.resetColor = "<r>"
|
||||
elseif colorType == "h" then
|
||||
self.percentColor = self.percentColor or "#ffffff"
|
||||
self.percentSymbolColor = self.percentSymbolColor or self.percentColor
|
||||
self.fillColor = self.fillColor or "#ff8c00"
|
||||
self.emptyColor = self.emptyColor or "#ffffff"
|
||||
self.resetColor = "#r"
|
||||
else
|
||||
self.percentColor = self.percentColor or ""
|
||||
self.percentSymbolColor = self.percentSymbolColor or self.percentColor
|
||||
self.fillColor = self.fillColor or ""
|
||||
self.emptyColor = self.emptyColor or ""
|
||||
self.resetColor = ""
|
||||
end
|
||||
self.overflowColor = self.overflowColor or self.fillColor
|
||||
end
|
||||
|
||||
-- Internal function used to route Geyser.Color based on internally stored format
|
||||
function TextGauge:getColor(color)
|
||||
local colorType = self:getColorType()
|
||||
if colorType == "c" then
|
||||
return string.format("<%s>", color) -- pass the color back in <> for cecho
|
||||
elseif colorType == "d" then
|
||||
return Geyser.Color.hdec(color) -- return it in decho format
|
||||
elseif colorType == "h" then
|
||||
return Geyser.Color.hex(color) -- return it in hex format
|
||||
else
|
||||
return "" -- return an empty string for noncolored output
|
||||
end
|
||||
end
|
||||
|
||||
--- Used to set the gauge's value and return the string representation of the gauge
|
||||
-- @tparam[opt] number current current value. If no value is passed it will use the stored value. Defaults to 50 to prevent errors.
|
||||
-- @tparam[opt] number max maximum value. If not passed, the internally stored one will be used. Defaults to 100 so that it can be used with single values as a percent
|
||||
-- @usage myGauge:setValue(55) -- sets the gauge to 55% full
|
||||
-- @usage myGauge:setValue(2345, 2780) -- will figure out what the percentage fill is based on the given current/max values
|
||||
function TextGauge:setValue(current, max)
|
||||
current = current or self.value
|
||||
assert(type(current) == "number", "TextGauge:setValue(current,max) current as number expected, got " .. type(current))
|
||||
assert(max == nil or type(max) == "number", "TextGauge:setValue(current, max) option max as number expected, got " .. type(max))
|
||||
if current < 0 then
|
||||
current = 0
|
||||
end
|
||||
max = max or 100
|
||||
local value = math.floor(current / max * 100)
|
||||
self.value = value
|
||||
local width = self.width
|
||||
local percentString = ""
|
||||
local percentSymbolString = ""
|
||||
local fillCharacter = self.fillCharacter
|
||||
local overflowCharacter = self.overflowCharacter or fillCharacter
|
||||
local emptyCharacter = self.emptyCharacter
|
||||
local fillColor = self:getColor(self.fillColor)
|
||||
local overflowColor = self:getColor(self.overflowColor)
|
||||
local emptyColor = self:getColor(self.emptyColor)
|
||||
local percentColor = self:getColor(self.percentColor)
|
||||
local percentSymbolColor = self:getColor(self.percentSymbolColor)
|
||||
local resetColor = self.resetColor
|
||||
if self.showPercent then
|
||||
percentString = string.format("%s%02d%s", percentColor, value, resetColor)
|
||||
width = width - 2
|
||||
end
|
||||
if self.showPercentSymbol then
|
||||
percentSymbolString = string.format("%s%s%s", percentSymbolColor, "%", resetColor)
|
||||
width = width - 1
|
||||
end
|
||||
local perc = value / 100
|
||||
local overflow = perc - 1
|
||||
if overflow < 0 then
|
||||
overflow = 0
|
||||
end
|
||||
if overflow > 1 then
|
||||
perc = 2
|
||||
overflow = 1
|
||||
end
|
||||
local overflowWidth = math.floor(overflow * width)
|
||||
local fillWidth = math.floor((perc - overflow) * width)
|
||||
local emptyWidth = width - fillWidth
|
||||
fillWidth = fillWidth - overflowWidth
|
||||
if value >= 100 and self.showPercent then
|
||||
fillWidth = fillWidth - 1
|
||||
end
|
||||
if value >= 200 and self.showPercent then
|
||||
overflowWidth = overflowWidth - 1
|
||||
end
|
||||
local result = ""
|
||||
if self.reverse then
|
||||
result = string.format("%s%s%s%s%s%s%s%s%s%s%s", emptyColor, string.rep(emptyCharacter, emptyWidth), resetColor,fillColor, string.rep(fillCharacter, fillWidth), resetColor, overflowColor, string.rep(overflowCharacter, overflowWidth), resetColor, percentString, percentSymbolString, resetColor)
|
||||
else
|
||||
result = string.format("%s%s%s%s%s%s%s%s%s%s%s", overflowColor, string.rep(overflowCharacter, overflowWidth), fillColor,
|
||||
string.rep(fillCharacter, fillWidth), resetColor, emptyColor, string.rep(emptyCharacter, emptyWidth), resetColor,
|
||||
percentString, percentSymbolString, resetColor)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
--- Synonym for setValue
|
||||
function TextGauge:print(...)
|
||||
self:setValue(...)
|
||||
end
|
||||
|
||||
return TextGauge
|
529
src/scripts/Avalon/MDK/timergauge.lua
Normal file
529
src/scripts/Avalon/MDK/timergauge.lua
Normal file
@ -0,0 +1,529 @@
|
||||
--- Animated countdown timer, extends <a href="https://www.mudlet.org/geyser/files/geyser/GeyserGauge.html">Geyser.Gauge</a>
|
||||
-- @classmod TimerGauge
|
||||
-- @author Damian Monogue <demonnic@gmail.com>
|
||||
-- @copyright 2020 Damian Monogue
|
||||
-- @license MIT, see LICENSE.lua
|
||||
local TimerGauge = {
|
||||
name = "TimerGaugeClass",
|
||||
active = true,
|
||||
showTime = true,
|
||||
prefix = "",
|
||||
timeFormat = "S.t",
|
||||
suffix = "",
|
||||
updateTime = "10",
|
||||
autoHide = true,
|
||||
autoShow = true,
|
||||
manageContainer = false,
|
||||
}
|
||||
|
||||
function TimerGauge:setStyleSheet(cssFront, cssBack, cssText)
|
||||
cssFront = cssFront or self.cssFront
|
||||
cssBack = cssBack or self.cssBack
|
||||
cssBack = cssBack or self.cssFront .. "background-color: black;"
|
||||
cssText = cssText or self.cssText
|
||||
if cssFront then
|
||||
self.front:setStyleSheet(cssFront)
|
||||
end
|
||||
if cssBack then
|
||||
self.back:setStyleSheet(cssBack)
|
||||
end
|
||||
if cssText then
|
||||
self.text:setStyleSheet(cssText)
|
||||
end
|
||||
-- self.gauge:setStyleSheet(cssFront, cssBack, cssText)
|
||||
self.cssFront = cssFront
|
||||
self.cssBack = cssBack
|
||||
self.cssText = cssText
|
||||
end
|
||||
|
||||
--- Shows the TimerGauge. If the manageContainer property is true, then will add it back to its container
|
||||
function TimerGauge:show2()
|
||||
if self.manageContainer and self.savedContainer then
|
||||
self.savedContainer:add(self)
|
||||
self.savedContainer = nil
|
||||
end
|
||||
self:show()
|
||||
end
|
||||
|
||||
--- Hides the TimerGauge. If manageContainer property is true, then it will remove it from its container and if the container is an HBox or VBox it will initiate size/position management
|
||||
function TimerGauge:hide2()
|
||||
if self.manageContainer and self.container.name ~= Geyser.name then
|
||||
self.savedContainer = self.container
|
||||
Geyser:add(self)
|
||||
self.savedContainer:remove(self)
|
||||
if self.savedContainer.type == "VBox" or self.savedContainer.type == "HBox" then
|
||||
if self.savedContainer.organize then
|
||||
self.savedContainer:organize()
|
||||
else
|
||||
self.savedContainer:reposition()
|
||||
end
|
||||
end
|
||||
end
|
||||
self:hide()
|
||||
end
|
||||
|
||||
--- Starts the timergauge. Works whether the timer is stopped or not. Does not start a timer which is already at 0
|
||||
-- @tparam[opt] boolean show override the autoShow property. True will always show, false will never show.
|
||||
-- @usage myTimerGauge:start() --starts the timer, will show or not based on autoShow property
|
||||
-- myTimerGauge:start(false) --starts the timer, will not change hidden status, regardless of autoShow property
|
||||
-- myTimerGauge:start(true) --starts the timer, will show it regardless of autoShow property
|
||||
function TimerGauge:start(show)
|
||||
if show == nil then
|
||||
show = self.autoShow
|
||||
end
|
||||
self.active = true
|
||||
if self.timer then
|
||||
killTimer(self.timer)
|
||||
self.timer = nil
|
||||
end
|
||||
startStopWatch(self.stopWatchName)
|
||||
self:update()
|
||||
self.timer = tempTimer(self.updateTime / 1000, function()
|
||||
self:update()
|
||||
end, true)
|
||||
if show then
|
||||
self:show2()
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the timergauge. Works whether the timer is started or not.
|
||||
-- @tparam[opt] boolean hide override the autoHide property. True will always hide, false will never hide.
|
||||
-- @usage myTimerGauge:stop() --stops the timer, will hide or not based on autoHide property
|
||||
-- myTimerGauge:stop(false) --stops the timer, will not change hidden status, regardless of autoHide property
|
||||
-- myTimerGauge:stop(true) --stops the timer, will hide it regardless of autoHide property
|
||||
function TimerGauge:stop(hide)
|
||||
if hide == nil then
|
||||
hide = self.autoHide
|
||||
end
|
||||
self.active = false
|
||||
if self.timer then
|
||||
killTimer(self.timer)
|
||||
self.timer = nil
|
||||
end
|
||||
stopStopWatch(self.stopWatchName)
|
||||
if hide then
|
||||
self:hide2()
|
||||
end
|
||||
end
|
||||
|
||||
--- Alias for stop.
|
||||
-- @tparam[opt] boolean hide override the autoHide property. True will always hide, false will never hide.
|
||||
function TimerGauge:pause(hide)
|
||||
self:stop(hide)
|
||||
end
|
||||
|
||||
--- Resets the time on the timergauge to its original value. Does not alter the running state of the timer
|
||||
function TimerGauge:reset()
|
||||
resetStopWatch(self.stopWatchName)
|
||||
adjustStopWatch(self.stopWatchName, self.time * -1)
|
||||
self:update()
|
||||
end
|
||||
|
||||
--- Resets and starts the timergauge.
|
||||
-- @tparam[opt] boolean show override the autoShow property. true will always show, false will never show
|
||||
-- @usage myTimerGauge:restart() --restarts the timer, will show or not based on autoShow property
|
||||
-- myTimerGauge:restart(false) --restarts the timer, will not change hidden status, regardless of autoShow property
|
||||
-- myTimerGauge:restart(true) --restarts the timer, will show it regardless of autoShow property
|
||||
function TimerGauge:restart(show)
|
||||
self:reset()
|
||||
self:start(show)
|
||||
end
|
||||
|
||||
--- Get the amount of time remaining on the timer, in seconds
|
||||
-- @tparam string format Format string for how to return the time. If not provided defaults to self.timeFormat(which defaults to "S.t").<br>
|
||||
-- If "" is passed will return "" as the time. See below table for formatting codes<br>
|
||||
-- <table class="tg">
|
||||
-- <tr>
|
||||
-- <th>format code</th>
|
||||
-- <th>what it is replaced with</th>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">S</td>
|
||||
-- <td class="tg-1">Time left in seconds, unbroken down. Does not include milliseconds.<br>
|
||||
-- IE a timer with 2 minutes left it would replace S with 120
|
||||
-- </td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">dd</td>
|
||||
-- <td class="tg-2">Days, with 1 leading 0 (0, 01, 02-...)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">d</td>
|
||||
-- <td class="tg-1">Days, with no leading 0 (1,2,3-...)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">hh</td>
|
||||
-- <td class="tg-2">hours, with leading 0 (00-24)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">h</td>
|
||||
-- <td class="tg-1">hours, without leading 0 (0-24)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">MM</td>
|
||||
-- <td class="tg-2">minutes, with a leading 0 (00-59)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">M</td>
|
||||
-- <td class="tg-1">minutes, no leading 0 (0-59)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">ss</td>
|
||||
-- <td class="tg-2">seconds, with leading 0 (00-59)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">s</td>
|
||||
-- <td class="tg-1">seconds, no leading 0 (0-59)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">t</td>
|
||||
-- <td class="tg-2">tenths of a second<br>
|
||||
-- timer with 12.345 seconds left, t would<br>
|
||||
-- br replaced by 3.
|
||||
-- </td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">mm</td>
|
||||
-- <td class="tg-1">milliseconds with leadings 0s (000-999)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">m</td>
|
||||
-- <td class="tg-2">milliseconds with no leading 0s (0-999)</td>
|
||||
-- </tr>
|
||||
-- </table><br>
|
||||
-- @usage myTimerGauge:getTime() --returns the time using myTimerGauge.format
|
||||
-- myTimerGauge:getTime("hh:MM:ss") --returns the time as hours, minutes, and seconds, with leading 0s (01:23:04)
|
||||
-- myTimerGauge:getTime("S.mm") --returns the time as the total number of seconds, including milliseconds (114.004)
|
||||
function TimerGauge:getTime(format)
|
||||
format = format or self.timeFormat
|
||||
local time = getStopWatchTime(self.stopWatchName)
|
||||
local timerTable = getStopWatchBrokenDownTime(self.stopWatchName)
|
||||
if time > 0 then
|
||||
self:stop(self.autoHide)
|
||||
resetStopWatch(self.stopWatchName)
|
||||
time = getStopWatchTime(self.stopWatchName)
|
||||
timerTable = getStopWatchBrokenDownTime(self.stopWatchName)
|
||||
self.active = false
|
||||
end
|
||||
if format == "" then
|
||||
return format
|
||||
end
|
||||
local totalSeconds = string.split(math.abs(time), "%.")[1]
|
||||
local tenths = string.sub(string.format("%03d", timerTable.milliSeconds), 1, 1)
|
||||
format = format:gsub("S", totalSeconds)
|
||||
format = format:gsub("t", tenths)
|
||||
format = format:gsub("mm", string.format("%03d", timerTable.milliSeconds))
|
||||
format = format:gsub("m", timerTable.milliSeconds)
|
||||
format = format:gsub("MM", string.format("%02d", timerTable.minutes))
|
||||
format = format:gsub("M", timerTable.minutes)
|
||||
format = format:gsub("dd", string.format("%02d", timerTable.days))
|
||||
format = format:gsub("d", timerTable.days)
|
||||
format = format:gsub("ss", string.format("%02d", timerTable.seconds))
|
||||
format = format:gsub("s", timerTable.seconds)
|
||||
format = format:gsub("hh", string.format("%02d", timerTable.hours))
|
||||
format = format:gsub("h", timerTable.hours)
|
||||
return format
|
||||
end
|
||||
|
||||
-- Execute the timer's hook, if there is one. Internal function
|
||||
function TimerGauge:executeHook()
|
||||
local hook = self.hook
|
||||
if not hook then
|
||||
return
|
||||
end
|
||||
local hooktype = type(hook)
|
||||
if hooktype == "string" then
|
||||
local f, e = loadstring("return " .. hook)
|
||||
if not f then
|
||||
f, e = loadstring(hook)
|
||||
end
|
||||
if not f then
|
||||
debugc(string.format("TimerGauge encountered an error while executing the hook for TimerGauge with name: %s error: %s", self.name, tostring(e)))
|
||||
return
|
||||
end
|
||||
hook = f
|
||||
end
|
||||
hooktype = type(hook)
|
||||
if hooktype ~= "function" then
|
||||
debugc(string.format(
|
||||
"TimerGauge with name: %s was given a hook which is neither a function nor a string which can be made into one. Provided type was %s",
|
||||
self.name, hooktype))
|
||||
return
|
||||
end
|
||||
local worked, err = pcall(hook)
|
||||
if not worked then
|
||||
debugc(string.format("TimerGauge named %s encountered the following error while executing its hook: %s", self.name, err))
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the timer's remaining time to 0, stops it, and executes the hook if one exists.
|
||||
-- @tparam[opt] boolean skipHook use true to have it set the timer to 0 and stop, but not execute the hook.
|
||||
-- @usage myTimerGauge:finish() --executes the hook if it has one
|
||||
-- myTimerGauge:finish(false) --will not execute the hook
|
||||
function TimerGauge:finish(skipHook)
|
||||
resetStopWatch(self.stopWatchName)
|
||||
self:update(skipHook)
|
||||
end
|
||||
|
||||
-- Internal function, no ldoc
|
||||
-- Updates the gauge based on time remaining.
|
||||
-- @tparam[opt] boolean skipHook use true if you do not want to execute the hook if the timer is at 0.
|
||||
function TimerGauge:update(skipHook)
|
||||
local time = self.showTime and self:getTime(self.timeFormat) or ""
|
||||
local current = tonumber(self:getTime("S.mm"))
|
||||
local suffix = self.suffix or ""
|
||||
local prefix = self.prefix or ""
|
||||
local text = string.format("%s%s%s", prefix, time, suffix)
|
||||
self:setValue(current, self.time, text)
|
||||
if current == 0 then
|
||||
if self.timer then
|
||||
killTimer(self.timer)
|
||||
self.timer = nil
|
||||
end
|
||||
if not skipHook then
|
||||
self:executeHook()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the amount of time the timer will run for. Make sure to call :reset() or :restart()
|
||||
-- if you want to cause the timer to run for that amount of time. If you set it to a time lower
|
||||
-- than the time left on the timer currently, it will reset the current time, otherwise it is left alone
|
||||
-- @tparam number time how long in seconds the timer should run for
|
||||
-- @usage myTimerGauge:setTime(50) -- sets myTimerGauge's max time to 50.
|
||||
function TimerGauge:setTime(time)
|
||||
local timetype = type(time)
|
||||
if timetype ~= "number" then
|
||||
local err = string.format("TimerGauge:setTime(time): time as number expected, got %s", timetype)
|
||||
debugc(err)
|
||||
return nil, err
|
||||
end
|
||||
time = math.abs(time)
|
||||
if time == 0 then
|
||||
local err = "TimerGauge:setTime(time): you cannot pass in 0 as the max time for the timer"
|
||||
debugc(err)
|
||||
return nil, err
|
||||
end
|
||||
local currentTime = tonumber(self:getTime("S.t"))
|
||||
self.time = time
|
||||
if time < currentTime then
|
||||
self:reset()
|
||||
else
|
||||
self:update(currentTime == 0)
|
||||
end
|
||||
end
|
||||
|
||||
--- Changes the time between gauge updates.
|
||||
-- @tparam number updateTime amount of time in milliseconds between gauge updates. Must be a positive number.
|
||||
function TimerGauge:setUpdateTime(updateTime)
|
||||
local updateTimeType = type(updateTime)
|
||||
assert(updateTimeType == "number",
|
||||
string.format("TimerGauge:setUpdateTime(updateTime): name: %s updateTime as number expected, got %s", self.name, updateTimeType))
|
||||
assert(updateTime > 0,
|
||||
string.format("TimerGauge:setUpdateTime(updateTime): name: %s updateTime must be a positive number. You gave %d", self.name, updateTime))
|
||||
self.updateTime = updateTime
|
||||
if self.timer then
|
||||
killTimer(self.timer)
|
||||
self.timer = nil
|
||||
end
|
||||
if self.active then
|
||||
self.timer = tempTimer(updateTime / 1000, function()
|
||||
self:update()
|
||||
end, true)
|
||||
end
|
||||
end
|
||||
|
||||
TimerGauge.parent = Geyser.Gauge
|
||||
setmetatable(TimerGauge, Geyser.Gauge)
|
||||
--- Creates a new TimerGauge instance.
|
||||
-- @tparam table cons a table of options (or constraints) for how the TimerGauge will behave. Valid options include:
|
||||
-- <br>
|
||||
-- <table class="tg">
|
||||
-- <tr>
|
||||
-- <th>name</th>
|
||||
-- <th>description</th>
|
||||
-- <th>default</th>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">time</td>
|
||||
-- <td class="tg-1">how long the timer should run for</td>
|
||||
-- <td class="tg-1"></td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">active</td>
|
||||
-- <td class="tg-2">whether the timer should run or not</td>
|
||||
-- <td class="tg-2">true</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">showTime</td>
|
||||
-- <td class="tg-1">should we show the time remaining on the gauge?</td>
|
||||
-- <td class="tg-1">true</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">prefix</td>
|
||||
-- <td class="tg-2">text you want shown before the time.</td>
|
||||
-- <td class="tg-2">""</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">suffix</td>
|
||||
-- <td class="tg-1">text you want shown after the time.</td>
|
||||
-- <td class="tg-1">""</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">timerCaption</td>
|
||||
-- <td class="tg-2">Alias for suffix. Deprecated and may be remove in the future</td>
|
||||
-- <td class="tg-2"/>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">updateTime</td>
|
||||
-- <td class="tg-1">number of milliseconds between gauge updates.</td>
|
||||
-- <td class="tg-1">10</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">autoHide</td>
|
||||
-- <td class="tg-2">should the timer :hide() itself when it runs out/you stop it?</td>
|
||||
-- <td class="tg-2">true</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">autoShow</td>
|
||||
-- <td class="tg-1">should the timer :show() itself when you start it?</td>
|
||||
-- <td class="tg-1">true</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">manageContainer</td>
|
||||
-- <td class="tg-2">should the timer remove itself from its container when you call <br>:hide() and add itself back when you call :show()?</td>
|
||||
-- <td class="tg-2">false</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">timeFormat</td>
|
||||
-- <td class="tg-1">how should the time be displayed/returned if you call :getTime()? <br>See table below for more information</td>
|
||||
-- <td class="tg-1">"S.t"</td>
|
||||
-- </tr>
|
||||
-- </table>
|
||||
-- <br>Table of time format options
|
||||
-- <table class="tg">
|
||||
-- <tr>
|
||||
-- <th>format code</th>
|
||||
-- <th>what it is replaced with</th>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">S</td>
|
||||
-- <td class="tg-1">Time left in seconds, unbroken down. Does not include milliseconds.<br>
|
||||
-- IE a timer with 2 minutes left it would replace S with 120
|
||||
-- </td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">dd</td>
|
||||
-- <td class="tg-2">Days, with 1 leading 0 (0, 01, 02-...)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">d</td>
|
||||
-- <td class="tg-1">Days, with no leading 0 (1,2,3-...)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">hh</td>
|
||||
-- <td class="tg-2">hours, with leading 0 (00-24)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">h</td>
|
||||
-- <td class="tg-1">hours, without leading 0 (0-24)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">MM</td>
|
||||
-- <td class="tg-2">minutes, with a leading 0 (00-59)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">M</td>
|
||||
-- <td class="tg-1">minutes, no leading 0 (0-59)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">ss</td>
|
||||
-- <td class="tg-2">seconds, with leading 0 (00-59)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">s</td>
|
||||
-- <td class="tg-1">seconds, no leading 0 (0-59)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">t</td>
|
||||
-- <td class="tg-2">tenths of a second<br>
|
||||
-- timer with 12.345 seconds left, t would<br>
|
||||
-- br replaced by 3.
|
||||
-- </td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-1">mm</td>
|
||||
-- <td class="tg-1">milliseconds with leadings 0s (000-999)</td>
|
||||
-- </tr>
|
||||
-- <tr>
|
||||
-- <td class="tg-2">m</td>
|
||||
-- <td class="tg-2">milliseconds with no leading 0s (0-999)</td>
|
||||
-- </tr>
|
||||
-- </table><br>
|
||||
-- @param parent The Geyser parent for this TimerGauge
|
||||
-- @usage
|
||||
-- local TimerGauge = require("MDK.timergauge")
|
||||
-- myTimerGauge = TimerGauge:new({
|
||||
-- name = "testGauge",
|
||||
-- x = 100,
|
||||
-- y = 100,
|
||||
-- height = 40,
|
||||
-- width = 200,
|
||||
-- time = 10
|
||||
-- })
|
||||
function TimerGauge:new(cons, parent)
|
||||
-- type checking and error handling
|
||||
local consType = type(cons)
|
||||
if consType ~= "table" then
|
||||
local err = string.format("TimerGauge:new(options, parent): options must be provided as a table, received: %s", consType)
|
||||
debugc(err)
|
||||
return nil, err
|
||||
end
|
||||
local timetype = type(cons.time)
|
||||
local time = tonumber(cons.time)
|
||||
if not time then
|
||||
local err = string.format(
|
||||
"TimerGauge:new(options, parent): options table must include a time entry, which must be a number. We received: %s which is type: %s",
|
||||
cons.time or tostring(cons.time), timetype)
|
||||
debugc(err)
|
||||
return nil, err
|
||||
end
|
||||
cons.time = math.abs(time)
|
||||
if cons.time == 0 then
|
||||
local err = "TimerGauge:new(options, parent): time entry in options table must be non-0"
|
||||
debugc(err)
|
||||
return nil, err
|
||||
end
|
||||
|
||||
if cons.timerCaption and (not cons.suffix) then
|
||||
cons.suffix = cons.timerCaption
|
||||
end
|
||||
cons.type = cons.type or "timergauge"
|
||||
-- call parent constructor
|
||||
local me = self.parent:new(cons, parent)
|
||||
-- add TimerGauge as the metatable/index
|
||||
setmetatable(me, self)
|
||||
self.__index = self
|
||||
|
||||
-- apply any styling requested
|
||||
if me.cssFront then
|
||||
if not me.cssBack then
|
||||
me.cssBack = me.cssFront .. "background-color: black;"
|
||||
end
|
||||
me:setStyleSheet(me.cssFront, me.cssBack, me.cssText)
|
||||
end
|
||||
|
||||
-- create and reset the driving stopwatch
|
||||
me.stopWatchName = me.name .. "_timergauge"
|
||||
createStopWatch(me.stopWatchName)
|
||||
me:reset()
|
||||
|
||||
-- start it up?
|
||||
if me.active then
|
||||
me:start()
|
||||
end
|
||||
me:update()
|
||||
return me
|
||||
end
|
||||
|
||||
return TimerGauge
|
1
src/scripts/Avalon/UI/avalon_ui.lua
Normal file
1
src/scripts/Avalon/UI/avalon_ui.lua
Normal file
@ -0,0 +1 @@
|
||||
-- These functions create the windows
|
114
src/scripts/Avalon/additions
Normal file
114
src/scripts/Avalon/additions
Normal file
@ -0,0 +1,114 @@
|
||||
-- Setup Bottom Bar
|
||||
-- The Bottom Bar is a vbox that contains a top hbox that holds three gauges and a bottom hbox for whatever content you want
|
||||
avalon.containers.alignment.bottommain = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonContainersAlignmentBottomMain",
|
||||
width = "95%",
|
||||
height = "100%"
|
||||
},
|
||||
avalon.containers.bottom
|
||||
)
|
||||
|
||||
avalon.containers.alignment.bottomgauges = Geyser.HBox:new(
|
||||
{
|
||||
name = "avalonContainersAlignmentBottomgauges",
|
||||
width = "100%",
|
||||
height = "50%"
|
||||
},
|
||||
avalon.containers.alignment.bottommain
|
||||
)
|
||||
|
||||
avalon.containers.alignment.bottominfo = Geyser.HBox:new(
|
||||
{
|
||||
name="avalonContainersAlignmentBottominfo",
|
||||
width = "100%",
|
||||
height = "-50%"
|
||||
},
|
||||
avalon.containers.alignment.bottommain
|
||||
)
|
||||
|
||||
-- Setup Left Bar
|
||||
-- The Left bar consists of a vbox with four internal vboxes for information display
|
||||
-- These are named as:
|
||||
-- leftmain - Primary vbox that the other vboxes attach to
|
||||
-- lefttop - The top most vbox inside leftmain
|
||||
-- leftmidtop - The first of two vboxes between the top and bottom. This is the one below lefttop
|
||||
-- leftmidbottom - The second of two vboxes between the top and bottom. This is the one above leftbottom and below leftmidtop
|
||||
-- leftbottom - The bottom most vbox inside leftmain
|
||||
avalon.containers.alignment.leftmain = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonContainersAlignmentLeftmain",
|
||||
width = "95%",
|
||||
height = "100%"
|
||||
},
|
||||
avalon.containers.left
|
||||
)
|
||||
|
||||
avalon.containers.alignment.lefttop = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonContainersAlignmentLefttop",
|
||||
width = "100%",
|
||||
height = "25%"
|
||||
},
|
||||
avalon.containers.alignment.leftmain
|
||||
)
|
||||
|
||||
avalon.containers.alignment.leftmidtop = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonContainersAlignmentLeftmidtop",
|
||||
width = "100%",
|
||||
height = "25%"
|
||||
},
|
||||
avalon.containers.alignment.leftmain
|
||||
)
|
||||
|
||||
avalon.containers.alignment.leftmidbottom = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonContainersAlignmentLeftmidbottom",
|
||||
width = "100%",
|
||||
height = "25%"
|
||||
},
|
||||
avalon.containers.alignment.leftmain
|
||||
)
|
||||
|
||||
avalon.containers.alignment.leftbottom = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonContainersAlignmentLeftbottom",
|
||||
width = "100%",
|
||||
height = "25%"
|
||||
},
|
||||
avalon.containers.alignment.leftmain
|
||||
)
|
||||
|
||||
-- Setup Right Bar
|
||||
-- The Right Bar consists of vbox that contains a mapper in the top right and chat in the bottom right
|
||||
-- The chat utilizes Demonnic's great ECMO https://github.com/demonnic/MDK
|
||||
-- rightmain - Primary VBox that other VBoxes attach to
|
||||
-- rightmap - The Mapper
|
||||
-- rightchat - The EMCO Chat window
|
||||
avalon.containers.alignment.rightmain = Geyser.VBox:new(
|
||||
{
|
||||
name = "avalonContainersAlignmentRightmain",
|
||||
width = "100%",
|
||||
height = "100%"
|
||||
},
|
||||
avalon.containers.right
|
||||
)
|
||||
|
||||
avalon.containers.alignment.rightmap = Geyser.Container:new(
|
||||
{
|
||||
name = "avalonContainersAlignmentRightmap",
|
||||
width = "100%",
|
||||
height = "60%"
|
||||
},
|
||||
avalon.containers.alignment.rightmain
|
||||
)
|
||||
|
||||
avalon.containers.alignment.rightchat = Geyser.Container:new(
|
||||
{
|
||||
name = "avalonContainersAlignmentRightchat",
|
||||
width = "100%",
|
||||
height = "40%"
|
||||
},
|
||||
avalon.containers.alignment.rightmain
|
||||
)
|
8
src/scripts/Avalon/scripts.json
Normal file
8
src/scripts/Avalon/scripts.json
Normal file
@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"isActive": "yes",
|
||||
"isFolder": "no",
|
||||
"name": "KaiUI init",
|
||||
"script": ""
|
||||
}
|
||||
]
|
22
src/triggers/Avalon/triggers.json
Normal file
22
src/triggers/Avalon/triggers.json
Normal file
@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"name": "Initialize",
|
||||
"isActive": "yes",
|
||||
"isFolder": "no",
|
||||
"multiline": "no",
|
||||
"multielineDelta": "0",
|
||||
"matchall": "no",
|
||||
"filter": "no",
|
||||
"fireLength": "0",
|
||||
"highlight": "no",
|
||||
"highlightFG": "#ff0000",
|
||||
"highlightBG": "#ffff00",
|
||||
"patterns": [
|
||||
{
|
||||
"pattern": "Rapture Runtime Environment v2.4.5 -- (c) 2017 -- Iron Realms Entertainment",
|
||||
"type": "substring"
|
||||
}
|
||||
],
|
||||
"script": ""
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue
Block a user