<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE MudletPackage>
<MudletPackage version="1.0">
    <TriggerPackage>
        <TriggerGroup isActive="yes" isFolder="yes" isTempTrigger="no" isMultiline="no" isPerlSlashGOption="no" isColorizerTrigger="no" isFilterTrigger="no" isSoundTrigger="no" isColorTrigger="no" isColorTriggerFg="no" isColorTriggerBg="no">
            <name>Tabbed Chat</name>
            <script></script>
            <triggerType>0</triggerType>
            <conditonLineDelta>0</conditonLineDelta>
            <mStayOpen>0</mStayOpen>
            <mCommand></mCommand>
            <packageName></packageName>
            <mFgColor>#ff0000</mFgColor>
            <mBgColor>#ffff00</mBgColor>
            <mSoundFile></mSoundFile>
            <colorTriggerFgColor>#000000</colorTriggerFgColor>
            <colorTriggerBgColor>#000000</colorTriggerBgColor>
            <regexCodeList/>
            <regexCodePropertyList/>
            <Trigger isActive="yes" isFolder="no" isTempTrigger="no" isMultiline="no" isPerlSlashGOption="no" isColorizerTrigger="no" isFilterTrigger="no" isSoundTrigger="no" isColorTrigger="no" isColorTriggerFg="no" isColorTriggerBg="no">
                <name>Org</name>
                <script>demonnic.chat:append(&quot;Org&quot;)</script>
                <triggerType>0</triggerType>
                <conditonLineDelta>39</conditonLineDelta>
                <mStayOpen>0</mStayOpen>
                <mCommand></mCommand>
                <packageName></packageName>
                <mFgColor>#ff0000</mFgColor>
                <mBgColor>#ffff00</mBgColor>
                <mSoundFile></mSoundFile>
                <colorTriggerFgColor>#000000</colorTriggerFgColor>
                <colorTriggerBgColor>#000000</colorTriggerBgColor>
                <regexCodeList>
                    <string>(Glomdoring):</string>
                </regexCodeList>
                <regexCodePropertyList>
                    <integer>2</integer>
                </regexCodePropertyList>
            </Trigger>
        </TriggerGroup>
    </TriggerPackage>
    <AliasPackage>
        <AliasGroup isActive="yes" isFolder="yes">
            <name>Tabbed Chat</name>
            <script></script>
            <command></command>
            <packageName></packageName>
            <regex></regex>
            <Alias isActive="yes" isFolder="no">
                <name>Toggle blinking (temporary change)</name>
                <script>if demonnic.chat.config.blink then
  demonnic.chat.config.blink = false
  demonnic.chat.tabsToBlink = {}
  demonnic:echo(&quot;Blinking temporarily turned &lt;red&gt;off&lt;grey&gt;. It will reset if you edit your tabbed chat configuration, or close and reopen mudlet. To make it permanent, change demonnic.chat.config.blink to false in \&quot;Demonnic-&gt;Tabbed Chat-&gt;Configuration options\&quot; under scripts\n&quot;)
else
  demonnic.chat.config.blink = true
  demonnic.chat:blink()
  demonnic:echo(&quot;Blinking temporarily turned &lt;red&gt;on&lt;grey&gt;. It will reset if you edit your tabbed chat configuration, or close and reopen mudlet. To make it permanent, change demonnic.chat.config.blink to true in \&quot;Demonnic-&gt;Tabbed Chat-&gt;Configuration options\&quot; under scripts\n&quot;)
end</script>
                <command></command>
                <packageName></packageName>
                <regex>^dblink$</regex>
            </Alias>
        </AliasGroup>
    </AliasPackage>
    <ScriptPackage>
        <ScriptGroup isActive="yes" isFolder="yes">
            <name>Tabbed Chat</name>
            <packageName></packageName>
            <script>--Do not remove the following lines. Or change them.
demonnic = demonnic or {}
demonnic.chat = demonnic.chat or {}
demonnic.chat.tabsToBlink = demonnic.chat.tabsToBlink or {}
demonnic.chat.config = demonnic.chat.config or {}
demonnic.chat.tabs = demonnic.chat.tabs or {}
demonnic.chat.windows = demonnic.chat.windows or {}
demonnic.chat.config.activeColors = demonnic.chat.config.activeColors or {}
demonnic.chat.config.inactiveColors = demonnic.chat.config.inactiveColors or {}</script>
            <eventHandlerList/>
            <Script isActive="yes" isFolder="no">
                <name>Configuration Options</name>
                <packageName></packageName>
                <script>--[[
This is where all of the configuration options can be set. 
Anything I've put in this script object can be changed, but please do pay attention to what you're doing.
If you change one of the values to something it shouldn't be, you could break it. 
]]

--This is where you tell it to use tabbed chat.
demonnic.chat.use = true

--[[
This is where you say what corner of the screen you want the tabbed chat on
Valid settings are &quot;topright&quot;, &quot;topleft&quot;, &quot;bottomright&quot;, &quot;bottomleft&quot;
]]--
demonnic.chat.config.location = &quot;topright&quot;

--[[
This is a table of channels you would like.
AKA the place you tell the script what tabs you want.
Each entry must be a string. The defaults should be a pretty effective guide.
]]

demonnic.chat.config.channels = {
  &quot;All&quot;,
  &quot;Org&quot;,
  &quot;Guild&quot;,
  &quot;Clans&quot;,
  &quot;Tells&quot;,
  &quot;Says&quot;,
  &quot;Misc&quot;,
}


--Set this to the name of the channel you want to have everything sent to. 
--Per the default, this would be the &quot;All&quot; channel. If you have a different name for it:
--
--demonnic.chat.config.Alltab = &quot;Bucket&quot;  
--
--And if you don't want it turned on at all:
--
--demonnic.chat.config.Alltab = false

demonnic.chat.config.Alltab = &quot;All&quot;



---------------------------------------------------------------------------------
--                                                                             --
--The infamous blinking stuff!!!                                               --
--                                                                             --
---------------------------------------------------------------------------------

--[[
Do you want tabs to blink when you get new messages, until you click on the tab?
True if yes, false if no.
]]
demonnic.chat.config.blink = true

--How long (in seconds) between blinks? For example, 1 would mean a 1 second pause in between blinks.
demonnic.chat.config.blinkTime = 3

--Blink if the bucket tab (&quot;All&quot; by default, but configured above) is in focus?
demonnic.chat.config.blinkFromAll = false




--Font size for the chat messages

demonnic.chat.config.fontSize = 8

--[[
Should we preserve the formatting of the text. 
Or should we set the background of it to match the window color?
Set this to false if you want the background for all chat to match the background of the window.
Useful if you change the background from black, and don't like the way the pasted chat makes blocks in it
]]

demonnic.chat.config.preserveBackground = false

--[[
Gag the chat lines in the main window?
defaults to false, set to true if you want to gag.
]]

demonnic.chat.config.gag = false

--[[
Number of lines of chat visible at once. 
Will determine how tall the window for the chats is.
]]

demonnic.chat.config.lines = 50

--[[
Number of characters to wrap the chatlines at.
This will also determine how wide the chat windows are.
]]

demonnic.chat.config.width = 60

--[[
Set the color for the active tab. R,G,B format.
The default here is a brightish green
]]

demonnic.chat.config.activeColors = {
  r = 0,
  g = 180,
  b = 0,
}

--[[
Set the color for the inactive tab. R,G,B format.
The default here is a drab grey
]]

demonnic.chat.config.inactiveColors = {
  r = 60,
  g = 60,
  b = 60,
}

--[[
Set the color for the chat window itself. R,G,B format.
Defaulted to the black of my twisted hardened soul. Or something.
]]

demonnic.chat.config.windowColors = {
  r = 0,
  g = 0,
  b = 0,
}

--[[
Set the color for the text on the active tab. Uses color names.
Set the default to purple. So the tab you're looking at, by default will be purple on bright green. 
Did I mention I'm a bit colorblind?
]]

demonnic.chat.config.activeTabText = &quot;purple&quot;

--[[
Set the color for the text on the inactive tabs. Uses color names.
Defaulted this to white. So the tabs you're not looking at will be white text on boring grey background.
]]

demonnic.chat.config.inactiveTabText = &quot;white&quot;

--[[
have to make sure a currentTab is set... 
so we'll use the one for the bucket, or the first one in the channels table
Or, you know... what it's currently set to, if it's already set.
]]
demonnic.chat.currentTab = demonnic.chat.currentTab or demonnic.chat.config.Alltab or demonnic.chat.config.channels[1]
</script>
                <eventHandlerList/>
            </Script>
            <Script isActive="yes" isFolder="no">
                <name>Code</name>
                <packageName></packageName>
                <script>--[[
If the label callbacks ever decide to start taking a function which is part of a table, 0then this will change.
Or if it's modified to take actual functions. Anonymouse function clickcallback would be awfully nice.
]]

function demonnicChatSwitch(chat)
  local r = demonnic.chat.config.inactiveColors.r
  local g = demonnic.chat.config.inactiveColors.g
  local b = demonnic.chat.config.inactiveColors.b
  local newr = demonnic.chat.config.activeColors.r
  local newg = demonnic.chat.config.activeColors.g
  local newb = demonnic.chat.config.activeColors.b
  local oldchat = demonnic.chat.currentTab
  if demonnic.chat.currentTab ~= chat then
    demonnic.chat.windows[oldchat]:hide()
    demonnic.chat.tabs[oldchat]:setColor(r,g,b)
    demonnic.chat.tabs[oldchat]:echo(oldchat, demonnic.chat.config.inactiveTabText, &quot;c&quot;)
    if demonnic.chat.config.blink and demonnic.chat.tabsToBlink[chat] then
      demonnic.chat.tabsToBlink[chat] = nil
    end
    if demonnic.chat.config.blink and chat == demonnic.chat.config.Alltab then
      demonnic.chat.tabsToBlink = {}
    end
  end
  demonnic.chat.tabs[chat]:setColor(newr,newg,newb)
  demonnic.chat.tabs[chat]:echo(chat, demonnic.chat.config.activeTabText, &quot;c&quot;)
  demonnic.chat.windows[chat]:show()
  demonnic.chat.currentTab = chat  
end

function demonnic.chat:resetUI()
  demonnic.chat.container = Geyser.Container:new(demonnic.chat[demonnic.chat.config.location]())
  demonnic.chat.tabBox = Geyser.HBox:new({
    x=0,
    y=0,
    width = &quot;100%&quot;,
    height = &quot;25px&quot;,
    name = &quot;DemonChatTabs&quot;,
  },demonnic.chat.container)

end

function demonnic.chat:create()
  --reset the UI
  demonnic.chat:resetUI()
  --Set some variables locally to increase readability
  local r = demonnic.chat.config.inactiveColors.r
  local g = demonnic.chat.config.inactiveColors.g
  local b = demonnic.chat.config.inactiveColors.b
  local winr = demonnic.chat.config.windowColors.r
  local wing = demonnic.chat.config.windowColors.g
  local winb = demonnic.chat.config.windowColors.b

  --iterate the table of channels and create some windows and tabs
  for i,tab in ipairs(demonnic.chat.config.channels) do
    demonnic.chat.tabs[tab] = Geyser.Label:new({
      name=string.format(&quot;tab%s&quot;, tab),
    }, demonnic.chat.tabBox)
    demonnic.chat.tabs[tab]:echo(tab, demonnic.chat.config.inactiveTabText, &quot;c&quot;)
    demonnic.chat.tabs[tab]:setColor(r,g,b)
    demonnic.chat.tabs[tab]:setClickCallback(&quot;demonnicChatSwitch&quot;, tab)
    demonnic.chat.windows[tab] = Geyser.MiniConsole:new({
--      fontSize = demonnic.chat.config.fontSize,
      x = 0,
      y = 25,
      height = &quot;100%&quot;,
      width = &quot;100%&quot;,
      name = string.format(&quot;win%s&quot;, tab),
    }, demonnic.chat.container)
    demonnic.chat.windows[tab]:setFontSize(demonnic.chat.config.fontSize)
    demonnic.chat.windows[tab]:setColor(winr,wing,winb)
    demonnic.chat.windows[tab]:setWrap(demonnic.chat.config.width)
    demonnic.chat.windows[tab]:hide()
  end
  local showme = demonnic.chat.config.Alltab or demonnic.chat.config.channels[1]
  demonnicChatSwitch(showme)
  --start the blink timers, if enabled
  if demonnic.chat.config.blink and not demonnic.chat.blinkTimerOn then
    demonnic.chat:blink()
  end
end

function demonnic.chat:append(chat)
  local r = demonnic.chat.config.windowColors.r
  local g = demonnic.chat.config.windowColors.g
  local b = demonnic.chat.config.windowColors.b
  selectCurrentLine()
  if demonnic.chat.config.preserveBackground then
    setBgColor(r,g,b)
  end
  copy()
  demonnic.chat.windows[chat]:append()
  if demonnic.chat.config.gag then 
    deleteLine() 
    tempLineTrigger(1,1, [[if isPrompt() then deleteLine() end]])
  end
  if demonnic.chat.config.Alltab then appendBuffer(string.format(&quot;win%s&quot;, demonnic.chat.config.Alltab)) end
  if demonnic.chat.config.blink and chat ~= demonnic.chat.currentTab then 
    if (demonnic.chat.config.Alltab == demonnic.chat.currentTab) and not demonnic.chat.config.blinkOnAll then
      return
    else
      demonnic.chat.tabsToBlink[chat] = true
    end
  end
end



function demonnic.chat:blink()
  if demonnic.chat.blinkID then killTimer(demonnic.chat.blinkID) end
  if not demonnic.chat.config.blink then 
    demonnic.chat.blinkTimerOn = false
    return 
  end
  for tab,_ in pairs(demonnic.chat.tabsToBlink) do
    demonnic.chat.tabs[tab]:flash()
  end
  demonnic.chat.blinkID = tempTimer(demonnic.chat.config.blinkTime, function () demonnic.chat:blink() end)
end

function demonnic.chat:topright()
  return {
    fontSize = demonnic.chat.config.fontSize,
    x=string.format(&quot;-%sc&quot;,demonnic.chat.config.width + 2),
    y=0,
    width=&quot;-15px&quot;,
    height=string.format(&quot;%ic&quot;, demonnic.chat.config.lines + 2),
  }
end

function demonnic.chat:topleft()
  return {
    fontSize = demonnic.chat.config.fontSize,
    x=0,
    y=0,
    width=string.format(&quot;%sc&quot;,demonnic.chat.config.width),
    height=string.format(&quot;%ic&quot;, demonnic.chat.config.lines + 2),
  }
end

function demonnic.chat:bottomright()
  return {
    fontSize = demonnic.chat.config.fontSize,
    x=string.format(&quot;-%sc&quot;,demonnic.chat.config.width + 2),
    y=string.format(&quot;-%sc&quot;,demonnic.chat.config.lines + 2),
    width=&quot;-15px&quot;,
    height=string.format(&quot;%ic&quot;, demonnic.chat.config.lines + 2),
  }
end

function demonnic.chat:bottomleft()
  return {
    fontSize = demonnic.chat.config.fontSize,
    x=0,
    y=string.format(&quot;-%sc&quot;,demonnic.chat.config.lines + 2),
    width=string.format(&quot;%sc&quot;,demonnic.chat.config.width),
    height=string.format(&quot;%ic&quot;, demonnic.chat.config.lines + 2),
  }
end</script>
                <eventHandlerList/>
            </Script>
            <Script isActive="yes" isFolder="no">
                <name>demonnicOnStart</name>
                <packageName></packageName>
                <script>function demonnicOnStart()
  if demonnic.chat.use then
    demonnic.chat:create()
  end
end</script>
                <eventHandlerList>
                    <string>sysLoadEvent</string>
                </eventHandlerList>
            </Script>
            <Script isActive="yes" isFolder="no">
                <name>echo functions</name>
                <packageName></packageName>
                <script>
function demonnic.chat:cecho(chat, message)
  local alltab = demonnic.chat.config.Alltab
  local blink = demonnic.chat.config.blink
  cecho(string.format(&quot;win%s&quot;,chat), message)
  if alltab and chat ~= alltab then 
    cecho(string.format(&quot;win%s&quot;, alltab), message)
  end
  if blink and chat ~= demonnic.chat.currentTab then
    if (alltab == demonnic.chat.currentTab) and not demonnic.chat.config.blinkOnAll then
      return
    else
      demonnic.chat.tabsToBlink[chat] = true
    end
  end
end

function demonnic.chat:decho(chat, message)
  local alltab = demonnic.chat.config.Alltab
  local blink = demonnic.chat.config.blink
  decho(string.format(&quot;win%s&quot;,chat), message)
  if alltab and chat ~= alltab then 
    decho(string.format(&quot;win%s&quot;, alltab), message)
  end
  if blink and chat ~= demonnic.chat.currentTab then
    if (alltab == demonnic.chat.currentTab) and not demonnic.chat.config.blinkOnAll then
      return
    else
      demonnic.chat.tabsToBlink[chat] = true
    end
  end
end

function demonnic.chat:hecho(chat, message)
  local alltab = demonnic.chat.config.Alltab
  local blink = demonnic.chat.config.blink
  hecho(string.format(&quot;win%s&quot;,chat), message)
  if alltab and chat ~= alltab then 
    hecho(string.format(&quot;win%s&quot;, alltab), message)
  end
  if blink and chat ~= demonnic.chat.currentTab then
    if (alltab == demonnic.chat.currentTab) and not demonnic.chat.config.blinkOnAll then
      return
    else
      demonnic.chat.tabsToBlink[chat] = true
    end
  end
end

function demonnic.chat:echo(chat, message)
  local alltab = demonnic.chat.config.Alltab
  local blink = demonnic.chat.config.blink
  echo(string.format(&quot;win%s&quot;,chat), message)
  if alltab and chat ~= alltab then 
    echo(string.format(&quot;win%s&quot;, alltab), message)
  end
  if blink and chat ~= demonnic.chat.currentTab then
    if (alltab == demonnic.chat.currentTab) and not demonnic.chat.config.blinkOnAll then
      return
    else
      demonnic.chat.tabsToBlink[chat] = true
    end
  end
end</script>
                <eventHandlerList/>
            </Script>
        </ScriptGroup>
        <ScriptGroup isActive="yes" isFolder="yes">
            <name>Shared</name>
            <packageName></packageName>
            <script>--Bootstrapping variables/etc. Don't touch this unless you really know what you're doing

--I mean it. I'll point. AND laugh. loudly. 
demonnic = demonnic or {}
demonnic.config = demonnic.config or {}
demonnic.balances = demonnic.balances or {}
demonnic.balances.balance = demonnic.balances.balance or 1
demonnic.balances.equilibrium = demonnic.balances.equilibrium or 1
demonnic.debug = demonnic.debug or {}
demonnic.debug.active = demonnic.debug.active or nil
demonnic.debug.categories = demonnic.debug.categories or { }


function demonnic:echo(msg)
 cecho(string.format(&quot;\n&lt;blue&gt;(&lt;green&gt;Demonnic&lt;blue&gt;):&lt;white&gt; %s&quot;, msg))
end</script>
            <eventHandlerList/>
            <Script isActive="yes" isFolder="no">
                <name>Debugging</name>
                <packageName></packageName>
                <script>--Adds debugging functionality 

function demonnic:Debug(category,debugData)
   if category then
      if table.contains(demonnic.debug.categories, category) then
         if type(debugData) == &quot;table&quot; then
            demonnic:echo(&quot;&lt;red&gt;DEBUG &quot; .. category .. &quot;:&lt;white&gt;&quot;)
            display(debugData)
         elseif type(debugData) == &quot;string&quot; or type(debugData) == &quot;number&quot; then
            demonnic:echo(&quot;&lt;red&gt;DEBUG &quot; .. category .. &quot;:&lt;white&gt; &quot; .. debugData .. &quot;\n&quot; )
         else
            demonnic:echo(&quot;&lt;red&gt;DEBUG &quot; .. category .. &quot;:&lt;white&gt; &quot; .. tostring(debugData) .. &quot;\n&quot; )
         end
      end
   else
      if type(debugData) == &quot;table&quot; then
         demonnic:echo(&quot;&lt;red&gt;DEBUG:&lt;white&gt;&quot;)
         display(debugData)
      elseif type(debugData) == &quot;string&quot; or type(debugData) == &quot;number&quot; then
         demonnic:echo(&quot;&lt;red&gt;DEBUG:&lt;white&gt; &quot; .. debugData)
      else
         demonnic:echo(&quot;&lt;red&gt;DEBUG:&lt;white&gt; &quot; .. tostring(debugData))
      end
   end
end

function demonnic:printDebug(category, debugData)
   if not demonnic.debug.active then return end
   demonnic:Debug(category, debugData)
end

function demonnic:toggleDebug()
   if demonnic.debug.active then demonnic.debug.active = nil
   else demonnic.debug.active = true
   end
   demonnic:echo(&quot;Debugging is currently &quot; .. (( demonnic.debug.active and &quot;&lt;green&gt;ON&lt;white&gt;&quot;) or &quot;&lt;red&gt;OFF&lt;white&gt;&quot;))
end

function demonnic:watchCategory( category )
   if table.contains(demonnic.debug.categories, category) then
      for i,v in ipairs(demonnic.debug.categories) do
         if v == category then
            table.remove(demonnic.debug.categories, i)
         end
      end
      demonnic:echo(&quot;No longer watching the '&lt;red&gt;&quot;..category..&quot;&lt;white&gt;' category.&quot;) 
   else
      table.insert(demonnic.debug.categories, category)
      demonnic:echo(&quot;Now watching the '&lt;red&gt;&quot;..category..&quot;&lt;white&gt;' category.&quot;)
   end
   demonnic:echo(&quot;Debugging is currently &quot; .. (( demonnic.debug.active and &quot;&lt;green&gt;ON&lt;white&gt;&quot;) or &quot;&lt;red&gt;OFF&lt;white&gt;&quot;))
end

function demonnic:listCategories()
   if #demonnic.debug.categories &gt; 0 then
      demonnic:echo(&quot;You are currently watching the following categories:\n&quot; .. table.concat(demonnic.debug.categories,&quot;, &quot;) )
   else
      demonnic:echo(&quot;You are not watching any debugs.&quot;)
   end
end
</script>
                <eventHandlerList/>
            </Script>
            <Script isActive="yes" isFolder="no">
                <name>demonnicVitals</name>
                <packageName></packageName>
                <script>--Set some vital statistics. Will work with both ATCP and GMCP. 

function demonnicVitals( msg, arg )
  demonnic.nextLevel = tonumber(gmcp.Char.Vitals.nl)
  demonnic.curHealth = tonumber(gmcp.Char.Vitals.hp)    
  demonnic.maxHealth = tonumber(gmcp.Char.Vitals.maxhp)
  demonnic.curMana = tonumber(gmcp.Char.Vitals.mp)
  demonnic.maxMana = tonumber(gmcp.Char.Vitals.maxmp)
  demonnic.curEgo = tonumber(gmcp.Char.Vitals.ego)
  demonnic.maxEgo = tonumber(gmcp.Char.Vitals.maxego)
  demonnic.curPower = tonumber(gmcp.Char.Vitals.pow)
  demonnic.maxPower = tonumber(gmcp.Char.Vitals.maxpow)
  demonnic.curWillpower = tonumber(gmcp.Char.Vitals.wp)
  demonnic.maxWillpower = tonumber(gmcp.Char.Vitals.maxwp)
  demonnic.curEndurance = tonumber(gmcp.Char.Vitals.ep)
  demonnic.maxEndurance = tonumber(gmcp.Char.Vitals.maxep)
end</script>
                <eventHandlerList>
                    <string>gmcp.Char</string>
                </eventHandlerList>
            </Script>
            <Script isActive="yes" isFolder="no">
                <name>Align</name>
                <packageName></packageName>
                <script>
function align(str,options) --str is a string, options is a table
--[[ If they sent anything but a table as the second argument, return useful
info. But if they didn't send a second argument then that's ok, the defaults
will be enough to get by and just center the txt
]]--
  if (type(options) ~= &quot;table&quot;) and (options ~= nil) then return &quot;You call this with align(\&quot;some text to format\&quot;, &lt;table of options&gt;. Pls check comments for what options and usage information&quot; end
  options = options or {} --if they sent options, don't overwrite them
  options.width = options.width or 80 --default line length of 80
  options.alignment = options.alignment or &quot;center&quot; --if we don't specify, it's centered
  options.cap = options.cap or &quot;&quot; --default endcap of nothing (an empty string, technically)
  options.spacer = options.spacer or &quot; &quot; --default spacer is.. well.. space
  options.inside = options.inside or false --by default, when centering, formation as spacers|cap|text|cap|spacers
  if not options.mirror == false then options.mirror = options.mirror or true end--by default, we do want to use mirroring for the caps
  local strLen = string.len(str)
  local leftCap = options.cap
  local rightCap = options.cap
  local leftPadLen = math.floor((options.width - strLen)/2,1) - 1
  local rightPadLen = leftPadLen + ((options.width - strLen)%2)
  local maxPad = 0
  local capLen = string.len(options.cap)
  if capLen &gt; leftPadLen then --if the cap is bigger than the left total padding
    options.cap = options.cap:sub(1, leftPadLen) -- trim it up right!
    capLen = string.len(options.cap)
  end --otherwise, don't mess with it

 
  if options.alignment == &quot;center&quot; then --we're going to center something
    leftPadLen = math.floor((options.width - strLen)/2,1) - 1 --get the padding needed on the left
    rightPadLen = leftPadLen + ((options.width - strLen)%2) --and on the right
    if options.mirror then --if we're reversing the left cap and the right cap (IE {{[[ turns into ]]}} )
      rightCap = string.gsub(rightCap, &quot;&lt;&quot;, &quot;&gt;&quot;)
      rightCap = string.gsub(rightCap, &quot;%[&quot;, &quot;%]&quot;)
      rightCap = string.gsub(rightCap, &quot;{&quot;, &quot;}&quot;)
      rightCap = string.gsub(rightCap, &quot;%(&quot;, &quot;%)&quot;)
      rightCap = string.reverse(rightCap)
    end --otherwise, they'll be the same, so don't do anything
    str = string.format(&quot; %s &quot;, str)
   
  elseif options.alignment == &quot;right&quot; then --we'll right-align the text
    leftPadLen = options.width - strLen - 1
    rightPadLen = 0
    rightCap = &quot;&quot;
    str = string.format(&quot; %s&quot;, str)
   
  else --Ok, so if it's not center or right, we assume it's left. We don't do justified. Sorry.
    leftPadLen = 0
    rightPadLen = options.width - strLen -1
    leftCap = &quot;&quot;
    str = string.format(&quot;%s &quot;, str)
  end--that's it, took care of both left, right, and center formattings, now to output the durn thing.
 
  if options.inside then
  --if we're placing the repated spacer inside
  --&quot;=====endcap some text endcap=====&quot;
  --&quot;=====endcap some text pacdne=====&quot;
  --&quot;=================endcap some text&quot;
  --&quot;some text endcap=================&quot;
    return leftCap .. string.rep(options.spacer, (leftPadLen - capLen)) .. str ..string.rep(options.spacer, (rightPadLen - capLen)).. rightCap
  else
  --otherwise, it''s be the spaces on the 'inside'
  -- &quot;endcap===== some text =====endcap&quot;
  -- &quot;endcap===== some text =====pacdne&quot;
  -- &quot;endcap================= some text&quot;
  -- &quot;some text =================endcap&quot;
    return string.rep(options.spacer, (leftPadLen - capLen)) .. leftCap .. str .. rightCap .. string.rep(options.spacer, (rightPadLen - capLen))
  end
end


function calign(str,options) --str is a string, options is a table
--[[ If they sent anything but a table as the second argument, return useful 
info. But if they didn't send a second argument then that's ok, the defaults 
will be enough to get by and just center the txt
]]--
  if (not type(options) == &quot;table&quot;) and (not options == nil) then return &quot;You call this with align(\&quot;some text to format\&quot;, &lt;table of options&gt;. Pls check comments for what options and usage information&quot; end 
  options = options or {} --if they sent options, don't overwrite them
  options.width = options.width or 80 --default line length of 80
  options.alignment = options.alignment or &quot;center&quot; --if we don't specify, it's centered
  options.cap = options.cap or &quot;&quot; --default endcap of nothing (an empty string, technically)
  options.spacer = options.spacer or &quot; &quot; --default spacer is.. well.. space
  options.inside = options.inside or false --by default, when centering, formation as spacers|cap|text|cap|spacers
  options.capColor = options.capColor or &quot;&lt;white&gt;&quot;--by default, don't change the color of the caps
  options.spacerColor = options.spacerColor or &quot;&lt;white&gt;&quot;
  options.textColor = options.textColor or &quot;&lt;white&gt;&quot;--or the text
  if not options.mirror == false then options.mirror = options.mirror or true end--by default, we do want to use mirroring for the caps
  local strLen = string.len(str)
  local leftCap = options.cap
  local rightCap = options.cap
  local leftPadLen = math.floor((options.width - strLen)/2,1) - 1
  local rightPadLen = leftPadLen + ((options.width - strLen)%2)
  local maxPad = 0
  local capLen = string.len(options.cap)
  if capLen &gt; leftPadLen then --if the cap is bigger than the left total padding
    options.cap = options.cap:sub(1, leftPadLen) -- trim it up right!
    capLen = string.len(options.cap)
  end --otherwise, don't mess with it

  
  if options.alignment == &quot;center&quot; then --we're going to center something
    leftPadLen = math.floor((options.width - strLen)/2,1) - 1 --get the padding needed on the left
    rightPadLen = leftPadLen + ((options.width - strLen)%2) --and on the right
    if options.mirror then --if we're reversing the left cap and the right cap (IE {{[[ turns into ]]}} )
      rightCap = string.gsub(rightCap, &quot;&lt;&quot;, &quot;&gt;&quot;)
      rightCap = string.gsub(rightCap, &quot;%[&quot;, &quot;%]&quot;)
      rightCap = string.gsub(rightCap, &quot;{&quot;, &quot;}&quot;)
      rightCap = string.gsub(rightCap, &quot;%(&quot;, &quot;%)&quot;)
      rightCap = string.reverse(rightCap)
    end --otherwise, they'll be the same, so don't do anything
    str = string.format(&quot; %s &quot;, str)
    
  elseif options.alignment == &quot;right&quot; then --we'll right-align the text
    leftPadLen = options.width - strLen - 1
    rightPadLen = 0
    rightCap = &quot;&quot;
    str = string.format(&quot; %s&quot;, str)
    
  else --Ok, so if it's not center or right, we assume it's left. We don't do justified. Sorry.
    leftPadLen = 0
    rightPadLen = options.width - strLen -1
    leftCap = &quot;&quot;
    str = string.format(&quot;%s &quot;, str)
  end--that's it, took care of both left, right, and center formattings, now to output the durn thing. 
  
  if options.inside then 
  --if we're placing the repated spacer inside
  --&quot;=====endcap some text endcap=====&quot; 
  --&quot;=====endcap some text pacdne=====&quot;
  --&quot;=================endcap some text&quot; 
  --&quot;some text endcap=================&quot;
    return options.capColor .. leftCap .. options.spacerColor.. string.rep(options.spacer, (leftPadLen - capLen)) .. options.textColor .. str .. options.spacerColor ..string.rep(options.spacer, (rightPadLen - capLen)) .. options.capColor .. rightCap
  else 
  --otherwise, it''s be the spaces on the 'inside'
  -- &quot;endcap===== some text =====endcap&quot;
  -- &quot;endcap===== some text =====pacdne&quot; 
  -- &quot;endcap================= some text&quot; 
  -- &quot;some text =================endcap&quot;
    return options.spacerColor .. string.rep(options.spacer, (leftPadLen - capLen)) .. options.capColor .. leftCap .. options.textColor .. str .. options.capColor .. rightCap .. options.spacerColor .. string.rep(options.spacer, (rightPadLen - capLen))
  end
end

function dalign(str,options) --str is a string, options is a table
--[[ If they sent anything but a table as the second argument, return useful 
info. But if they didn't send a second argument then that's ok, the defaults 
will be enough to get by and just center the txt
]]--
  if (not type(options) == &quot;table&quot;) and (not options == nil) then return &quot;You call this with align(\&quot;some text to format\&quot;, &lt;table of options&gt;. Pls check comments for what options and usage information&quot; end 
  options = options or {} --if they sent options, don't overwrite them
  options.width = options.width or 80 --default line length of 80
  options.alignment = options.alignment or &quot;center&quot; --if we don't specify, it's centered
  options.cap = options.cap or &quot;&quot; --default endcap of nothing (an empty string, technically)
  options.spacer = options.spacer or &quot; &quot; --default spacer is.. well.. space
  options.inside = options.inside or false --by default, when centering, formation as spacers|cap|text|cap|spacers
  options.capColor = options.capColor or &quot;&lt;255,255,255&gt;&quot;--by default, don't change the color of the caps
  options.spacerColor = options.spacerColor or &quot;&lt;255,255,255&gt;&quot; 
  options.textColor = options.textColor or &quot;&lt;255,255,255&gt;&quot;--or the text
  if not options.mirror == false then options.mirror = options.mirror or true end--by default, we do want to use mirroring for the caps
  local strLen = string.len(str)
  local leftCap = options.cap
  local rightCap = options.cap
  local leftPadLen = math.floor((options.width - strLen)/2,1) - 1
  local rightPadLen = leftPadLen + ((options.width - strLen)%2)
  local maxPad = 0
  local capLen = string.len(options.cap)
  if capLen &gt; leftPadLen then --if the cap is bigger than the left total padding
    options.cap = options.cap:sub(1, leftPadLen) -- trim it up right!
    capLen = string.len(options.cap)
  end --otherwise, don't mess with it

  
  if options.alignment == &quot;center&quot; then --we're going to center something
    leftPadLen = math.floor((options.width - strLen)/2,1) - 1 --get the padding needed on the left
    rightPadLen = leftPadLen + ((options.width - strLen)%2) --and on the right
    if options.mirror then --if we're reversing the left cap and the right cap (IE {{[[ turns into ]]}} )
      rightCap = string.gsub(rightCap, &quot;&lt;&quot;, &quot;&gt;&quot;)
      rightCap = string.gsub(rightCap, &quot;%[&quot;, &quot;%]&quot;)
      rightCap = string.gsub(rightCap, &quot;{&quot;, &quot;}&quot;)
      rightCap = string.gsub(rightCap, &quot;%(&quot;, &quot;%)&quot;)
      rightCap = string.reverse(rightCap)
    end --otherwise, they'll be the same, so don't do anything
    str = string.format(&quot; %s &quot;, str)
    
  elseif options.alignment == &quot;right&quot; then --we'll right-align the text
    leftPadLen = options.width - strLen - 1
    rightPadLen = 0
    rightCap = &quot;&quot;
    str = string.format(&quot; %s&quot;, str)
    
  else --Ok, so if it's not center or right, we assume it's left. We don't do justified. Sorry.
    leftPadLen = 0
    rightPadLen = options.width - strLen -1
    leftCap = &quot;&quot;
    str = string.format(&quot;%s &quot;, str)
  end--that's it, took care of both left, right, and center formattings, now to output the durn thing. 
  
  if options.inside then 
  --if we're placing the repated spacer inside
  --&quot;=====endcap some text endcap=====&quot; 
  --&quot;=====endcap some text pacdne=====&quot;
  --&quot;=================endcap some text&quot; 
  --&quot;some text endcap=================&quot;
    return options.capColor .. leftCap .. options.spacerColor.. string.rep(options.spacer, (leftPadLen - capLen)) .. options.textColor .. str .. options.spacerColor ..string.rep(options.spacer, (rightPadLen - capLen)) .. options.capColor .. rightCap
  else 
  --otherwise, it''s be the spaces on the 'inside'
  -- &quot;endcap===== some text =====endcap&quot;
  -- &quot;endcap===== some text =====pacdne&quot; 
  -- &quot;endcap================= some text&quot; 
  -- &quot;some text =================endcap&quot;
    return options.spacerColor .. string.rep(options.spacer, (leftPadLen - capLen)) .. options.capColor .. leftCap .. options.textColor .. str .. options.capColor .. rightCap .. options.spacerColor .. string.rep(options.spacer, (rightPadLen - capLen))
  end
end

function halign(str,options) --str is a string, options is a table
--[[ If they sent anything but a table as the second argument, return useful 
info. But if they didn't send a second argument then that's ok, the defaults 
will be enough to get by and just center the txt
]]--
  if (not type(options) == &quot;table&quot;) and (not options == nil) then return &quot;You call this with align(\&quot;some text to format\&quot;, &lt;table of options&gt;. Pls check comments for what options and usage information&quot; end 
  options = options or {} --if they sent options, don't overwrite them
  options.width = options.width or 80 --default line length of 80
  options.alignment = options.alignment or &quot;center&quot; --if we don't specify, it's centered
  options.cap = options.cap or &quot;&quot; --default endcap of nothing (an empty string, technically)
  options.spacer = options.spacer or &quot; &quot; --default spacer is.. well.. space
  options.inside = options.inside or false --by default, when centering, formation as spacers|cap|text|cap|spacers
  options.capColor = options.capColor or &quot;|cFFFFFF&quot;--by default, don't change the color of the caps
  options.spacerColor = options.spacerColor or &quot;|cFFFFFF&quot; 
  options.textColor = options.textColor or &quot;|cFFFFFF&quot;--or the text
  if not options.mirror == false then options.mirror = options.mirror or true end--by default, we do want to use mirroring for the caps
  local strLen = string.len(str)
  local leftCap = options.cap
  local rightCap = options.cap
  local leftPadLen = math.floor((options.width - strLen)/2,1) - 1
  local rightPadLen = leftPadLen + ((options.width - strLen)%2)
  local maxPad = 0
  local capLen = string.len(options.cap)
  if capLen &gt; leftPadLen then --if the cap is bigger than the left total padding
    options.cap = options.cap:sub(1, leftPadLen) -- trim it up right!
    capLen = string.len(options.cap)
  end --otherwise, don't mess with it

  
  if options.alignment == &quot;center&quot; then --we're going to center something
    leftPadLen = math.floor((options.width - strLen)/2,1) - 1 --get the padding needed on the left
    rightPadLen = leftPadLen + ((options.width - strLen)%2) --and on the right
    if options.mirror then --if we're reversing the left cap and the right cap (IE {{[[ turns into ]]}} )
      rightCap = string.gsub(rightCap, &quot;&lt;&quot;, &quot;&gt;&quot;)
      rightCap = string.gsub(rightCap, &quot;%[&quot;, &quot;%]&quot;)
      rightCap = string.gsub(rightCap, &quot;{&quot;, &quot;}&quot;)
      rightCap = string.gsub(rightCap, &quot;%(&quot;, &quot;%)&quot;)
      rightCap = string.reverse(rightCap)
    end --otherwise, they'll be the same, so don't do anything
    str = string.format(&quot; %s &quot;, str)
    
  elseif options.alignment == &quot;right&quot; then --we'll right-align the text
    leftPadLen = options.width - strLen - 1
    rightPadLen = 0
    rightCap = &quot;&quot;
    str = string.format(&quot; %s&quot;, str)
    
  else --Ok, so if it's not center or right, we assume it's left. We don't do justified. Sorry.
    leftPadLen = 0
    rightPadLen = options.width - strLen -1
    leftCap = &quot;&quot;
    str = string.format(&quot;%s &quot;, str)
  end--that's it, took care of both left, right, and center formattings, now to output the durn thing. 
  
  if options.inside then 
  --if we're placing the repated spacer inside
  --&quot;=====endcap some text endcap=====&quot; 
  --&quot;=====endcap some text pacdne=====&quot;
  --&quot;=================endcap some text&quot; 
  --&quot;some text endcap=================&quot;
    return options.capColor .. leftCap .. options.spacerColor.. string.rep(options.spacer, (leftPadLen - capLen)) .. options.textColor .. str .. options.spacerColor ..string.rep(options.spacer, (rightPadLen - capLen)) .. options.capColor .. rightCap
  else 
  --otherwise, it''s be the spaces on the 'inside'
  -- &quot;endcap===== some text =====endcap&quot;
  -- &quot;endcap===== some text =====pacdne&quot; 
  -- &quot;endcap================= some text&quot; 
  -- &quot;some text =================endcap&quot;
    return options.spacerColor .. string.rep(options.spacer, (leftPadLen - capLen)) .. options.capColor .. leftCap .. options.textColor .. str .. options.capColor .. rightCap .. options.spacerColor .. string.rep(options.spacer, (rightPadLen - capLen))
  end
end
 </script>
                <eventHandlerList/>
            </Script>
            <Script isActive="yes" isFolder="no">
                <name>Geyser Additions</name>
                <packageName></packageName>
                <script>function Geyser.MiniConsole:clear()
   clearWindow(self.name)
end

function Geyser.MiniConsole:append()
  appendBuffer(self.name)
end</script>
                <eventHandlerList/>
            </Script>
            <ScriptGroup isActive="yes" isFolder="yes">
                <name>GMCP Events</name>
                <packageName></packageName>
                <script></script>
                <eventHandlerList/>
                <ScriptGroup isActive="yes" isFolder="yes">
                    <name>Room Items</name>
                    <packageName></packageName>
                    <script>--[[
The events kept within this folder will populate demonnic.roomItems on entering a room or doing 'look'
It also adds items to the list as they enter the room (or get dropped)
And removes them when they get picked up
you can do display(demonnic.roomItems) to see the format to work with
]]</script>
                    <eventHandlerList/>
                    <ScriptGroup isActive="yes" isFolder="yes">
                        <name>Populate on look</name>
                        <packageName></packageName>
                        <script>--[[
This event resets demonnic.roomItems, since it gives the complete list of items
It fires when you move, or LOOK.
]]</script>
                        <eventHandlerList/>
                        <Script isActive="yes" isFolder="no">
                            <name>demonnic_rilist</name>
                            <packageName></packageName>
                            <script>function demonnic_rilist()
  local list = gmcp.Char.Items.List
  if list.location == &quot;room&quot; then
    demonnic.roomItems = {}
    for _,v in ipairs(list.items) do
      if not demonnic.roomItems[v.name] then demonnic.roomItems[v.name] = {} end
      table.insert(demonnic.roomItems[v.name], v.id)
    end
  end
  if list.location == &quot;inv&quot; then
    return
  end
end</script>
                            <eventHandlerList>
                                <string>gmcp.Char.Items.List</string>
                            </eventHandlerList>
                        </Script>
                    </ScriptGroup>
                    <ScriptGroup isActive="yes" isFolder="yes">
                        <name>Add items when they enter</name>
                        <packageName></packageName>
                        <script>--[[
This event adds items when gmcp tells you items have been added to the room
IE a creature enters, someone drops something, etc.
]]</script>
                        <eventHandlerList/>
                        <Script isActive="yes" isFolder="no">
                            <name>demonnic_riadd</name>
                            <packageName></packageName>
                            <script>function demonnic_riadd()
  local item = gmcp.Char.Items.Add
  if item.location == &quot;room&quot; then 
    item = item.item 
    if not demonnic.roomItems[item.name] then demonnic.roomItems[item.name] = {} end
    table.insert(demonnic.roomItems[item.name], item.id)
  end
end</script>
                            <eventHandlerList>
                                <string>gmcp.Char.Items.Add</string>
                            </eventHandlerList>
                        </Script>
                    </ScriptGroup>
                    <ScriptGroup isActive="yes" isFolder="yes">
                        <name>Remove items when they leave</name>
                        <packageName></packageName>
                        <script>--[[
This event fires when gmcp says an item leaves the room
IE someone picks it up, it's a mob and it moves out of the room, etc.
]]</script>
                        <eventHandlerList/>
                        <Script isActive="yes" isFolder="no">
                            <name>demonnic_rileave</name>
                            <packageName></packageName>
                            <script>function demonnic_rileave()
  local item = gmcp.Char.Items.Remove
  if item.location == &quot;room&quot; then
    for name,items in pairs(demonnic.roomItems) do
      for i,id in ipairs(items) do
        if id == tostring(item.item) then
          table.remove(demonnic.roomItems[name],i) 
          if #demonnic.roomItems[name] == 0 then
            demonnic.roomItems[name] = nil
          end
        end
      end
    end
  end
end</script>
                            <eventHandlerList>
                                <string>gmcp.Char.Items.Remove</string>
                            </eventHandlerList>
                        </Script>
                    </ScriptGroup>
                </ScriptGroup>
                <ScriptGroup isActive="yes" isFolder="yes">
                    <name>Skill Events</name>
                    <packageName></packageName>
                    <script>--[[
set of events which will autopopulate a skills table called demonnic.skills
You can use this to determine what skills are available to your char
and adjust other variables accordingly
display(demonnic.skills) to see the format
]]</script>
                    <eventHandlerList/>
                    <ScriptGroup isActive="yes" isFolder="yes">
                        <name>Get list of groups</name>
                        <packageName></packageName>
                        <script>--[[
This will create demonnic.skills.skillgroup as a table, and send a GMCP request for the list of skills in each skillgroup
IOW, this tells us what skills we can lookup (like AB with no argument)
It also sends a newline, so that we get all the returns from the gmcp requests
]]</script>
                        <eventHandlerList/>
                        <Script isActive="yes" isFolder="no">
                            <name>demonnic_skillGroups</name>
                            <packageName></packageName>
                            <script>function demonnic_skillGroups()
  demonnic.skills = {}
  for _,extra in ipairs(gmcp.Char.Skills.Groups) do
    skillstring = string.format(&quot;Char.Skills.Get %s&quot;, yajl.to_string({group=extra.name}))
    sendGMCP(skillstring)
  end
  send(&quot;\n&quot;)
end</script>
                            <eventHandlerList>
                                <string>gmcp.Char.Skills.Groups</string>
                            </eventHandlerList>
                        </Script>
                    </ScriptGroup>
                    <ScriptGroup isActive="yes" isFolder="yes">
                        <name>Get skills in group</name>
                        <packageName></packageName>
                        <script>--[[
This populates the subtable for each skill group. It is recieved for an individual skillgroup, and will fire once for each one
demonnic.skills.skillgroup will be the name (IE demonnic.skills.environment will hold the list of all the skills you've learned in environment
Because when we get the list of groups it sends a request for each group of skills we know, it will populate the whole table.
]]</script>
                        <eventHandlerList/>
                        <Script isActive="yes" isFolder="no">
                            <name>demonnic_skillsList</name>
                            <packageName></packageName>
                            <script>function demonnic_skillsList()
  local group = gmcp.Char.Skills.List.group
  local list = gmcp.Char.Skills.List.list
  if group then
    if not demonnic.skills then demonnic.skills = {} end
    demonnic.skills[group] = list
    raiseEvent(&quot;demonnicSkillFilled&quot;, group)
  end
end</script>
                            <eventHandlerList>
                                <string>gmcp.Char.Skills.List</string>
                            </eventHandlerList>
                        </Script>
                    </ScriptGroup>
                </ScriptGroup>
                <Script isActive="yes" isFolder="no">
                    <name>EnableIRE.Rift</name>
                    <packageName></packageName>
                    <script>gmod.registerUser(&quot;Demonnic&quot;)
gmod.enableModule(&quot;Demonnic&quot;, &quot;IRE.Rift&quot;)</script>
                    <eventHandlerList/>
                </Script>
            </ScriptGroup>
        </ScriptGroup>
    </ScriptPackage>
    </ScriptPackage>
</MudletPackage>
