added even more options to PSBT ( if you got my temp PTS version, delete all folders in Addons with the name PSBT_ in the beginning )

Sammy James [03-22-14 - 05:20]
added even more options to PSBT ( if you got my temp PTS version, delete all folders in Addons with the name PSBT_ in the beginning )
Filename
PSBT.lua
PSBT.txt
PSBT.xml
PSBT_Auras.lua
PSBT_Combat.lua
PSBT_Constants.lua
PSBT_Cooldowns.lua
PSBT_Experience.lua
PSBT_Fifo.lua
PSBT_Label.lua
PSBT_LowSomething.lua
PSBT_Module.lua
PSBT_Options.lua
PSBT_ScrollArea.lua
PSBT_Settings.lua
core/PSBT.lua
core/PSBT.xml
core/PSBT_Constants.lua
core/PSBT_Fifo.lua
core/PSBT_Label.lua
core/PSBT_Media.lua
core/PSBT_Module.lua
core/PSBT_Options.lua
core/PSBT_ScrollArea.lua
core/PSBT_Settings.lua
fonts/adventure.ttf
fonts/bazooka.ttf
fonts/cooline.ttf
fonts/diogenes.ttf
fonts/ginko.ttf
fonts/heroic.ttf
fonts/porky.ttf
fonts/talisman.ttf
fonts/transformers.ttf
fonts/yellowjacket.ttf
modules/PSBT_Auras.lua
modules/PSBT_Combat.lua
modules/PSBT_Cooldowns.lua
modules/PSBT_Experience.lua
modules/PSBT_LowSomething.lua
diff --git a/PSBT.lua b/PSBT.lua
deleted file mode 100644
index 53f9269..0000000
--- a/PSBT.lua
+++ /dev/null
@@ -1,166 +0,0 @@
-------------------------------------------------
--- Pawkette's Scrolling Battle Text
---
--- @classmod PSBT
--- @author Pawkette ( pawkette.heals@gmail.com )
---[[
-The MIT License (MIT)
-
-Copyright (c) 2014 Pawkette
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-]]
-------------------------------------------------
-local PSBT = ZO_ObjectPool:Subclass()
-PSBT._modules  = {}
-PSBT._areas    = {}
-
-local CBM               = CALLBACK_MANAGER
-local PSBT_ScrollArea   = PSBT_ScrollArea
-local PSBT_AREAS        = PSBT_AREAS
-local PSBT_EVENTS       = PSBT_EVENTS
-local PSBT_MODULES      = PSBT_MODULES
-
-function PSBT:New( ... )
-    local result = ZO_ObjectPool.New( self, PSBT.CreateLabel, function( ... ) self:ResetLabel( ... ) end )
-    result:Initialize( ... )
-    return result
-end
-
-function PSBT:Initialize( control )
-    self.control = control
-    self.control:RegisterForEvent( EVENT_ADD_ON_LOADED, function( _, addon ) self:OnLoaded( addon ) end )
-end
-
-function PSBT:FormatFont( fontObject )
-    local path, size, decoration = fontObject:GetFontInfo()
-    local fmt = '%s|%d'
-    if ( decoration ) then
-        fmt = fmt .. '|%s'
-    end
-
-    return fmt:format( path, size, decoration )
-end
-
-function PSBT:OnLoaded( addon )
-    if ( addon ~= 'PSBT' ) then
-        return
-    end
-
-    print( 'Loading PSBT' )
-    CBM:FireCallbacks( PSBT_EVENTS.LOADED, self )
-
-    self._areas[ PSBT_AREAS.INCOMING ]     = PSBT_ScrollArea:New( self.control, PSBT_AREAS.INCOMING,     BOTTOM, self:GetSetting( PSBT_AREAS.INCOMING ) )
-    self._areas[ PSBT_AREAS.OUTGOING ]     = PSBT_ScrollArea:New( self.control, PSBT_AREAS.OUTGOING,     TOP, self:GetSetting( PSBT_AREAS.OUTGOING ) )
-    self._areas[ PSBT_AREAS.STATIC ]       = PSBT_ScrollArea:New( self.control, PSBT_AREAS.STATIC,       BOTTOM, self:GetSetting( PSBT_AREAS.STATIC ) )
-    self._areas[ PSBT_AREAS.NOTIFICATION ] = PSBT_ScrollArea:New( self.control, PSBT_AREAS.NOTIFICATION, TOP, self:GetSetting( PSBT_AREAS.NOTIFICATION ) )
-
-    CBM:RegisterCallback( PSBT_EVENTS.CONFIG, function( ... ) self:SetConfigurationMode( ... ) end )
-    self.control:SetHandler( 'OnUpdate', function( _, frameTime ) self:OnUpdate( frameTime ) end )
-end
-
-function PSBT:OnUpdate( frameTime )
-    for k,label in pairs( self:GetActiveObjects() ) do
-        if ( label:IsExpired( frameTime ) ) then
-            self:ReleaseObject( k )
-        end
-    end
-
-    for k,v in pairs( self._modules ) do
-        v:OnUpdate( frameTime )
-    end
-end
-
-function PSBT:CreateLabel()
-    return PSBT_Label:New( self )
-end
-
-function PSBT:ResetLabel( label )
-    label:Finalize()
-end
-
-function PSBT:SetConfigurationMode( mode )
-    if ( not mode ) then
-        for k,v in pairs( self._areas ) do
-            self:SetSetting( k, { v:GetAnchorOffsets() } )
-        end
-    end
-end
-
-function PSBT:RegisterModule( identity, class )
-    if ( self._modules[ identity ] ) then
-        return
-    end
-
-    print ( 'PSBT:RegisterModule %s', identity )
-    self._modules[ identity ] = class
-end
-
-function PSBT:GetModule( identity )
-    if ( not self._modules[ identity ] ) then
-        return nil
-    end
-
-    return self._modules[ identity ]
-end
-
-function PSBT:GetSetting( name )
-    local settings = self:GetModule( PSBT_MODULES.SETTINGS )
-    return settings:GetSetting( name )
-end
-
-function PSBT:SetSetting( name, value )
-    local settings = self:GetModule( PSBT_MODULES.SETTINGS )
-    settings:SetSetting( name, value )
-end
-
-function PSBT:RegisterForEvent( event, callback )
-    self.control:RegisterForEvent( event, callback )
-end
-
-function PSBT:UnregisterForEvent( event, callback )
-    self.control:UnregisterForEvent( event, callback )
-end
-
-function PSBT:NewEvent( scrollArea, sticky, icon, text )
-    local entry = self:AcquireObject()
-    local area = self._areas[ scrollArea ]
-    if ( not area ) then
-        return
-    end
-
-    if ( sticky ) then
-        entry.label:SetFont( self:FormatFont( ZoFontCallout ) )
-        entry.control:SetDrawTier( DT_HIGH )
-    else
-        entry.label:SetFont( self:FormatFont( ZoFontGameBold ) )
-        entry.control:SetDrawTier( DT_LOW )
-    end
-
-    entry:SetExpire( -1 ) --pending
-    entry:SetText( text )
-    entry:SetTexture( icon )
-
-    area:Push( entry, sticky )
-end
-
--- LEAVE ME THE FUARK ALONE
-function Initialized( control )
-    _G.PSBT = PSBT:New( control )
-end
diff --git a/PSBT.txt b/PSBT.txt
index cbec478..5e87c42 100644
--- a/PSBT.txt
+++ b/PSBT.txt
@@ -1,5 +1,5 @@
 ## Title: PSBT - By Pawkette
-## APIVersion: 100000
+## APIVersion: 100003
 ## OptionalDependsOn: LibStub LibMediaProvider-1.0 LibAddonMenu-1.0 LibAnimation-1.0
 ## SavedVariables: PSBT_DB

@@ -9,22 +9,27 @@ libs/libanimation-1.0/libanimation.lua
 libs/libmediaprovider-1.0/libmediaprovider-1.0.lua
 libs/libaddonmenu-1.0/libaddonmenu-1.0.lua

+## media
+core/PSBT_Media.lua
+
 ## constants
-PSBT_Constants.lua
+core/PSBT_Constants.lua
+
+## core modules
+core/PSBT_Module.lua
+core/PSBT_Settings.lua
+core/PSBT_Options.lua

-## modules
-PSBT_Module.lua
-PSBT_Settings.lua
-PSBT_Options.lua
-PSBT_Combat.lua
-PSBT_Cooldowns.lua
-PSBT_Auras.lua
-PSBT_Experience.lua
-PSBT_LowSomething.lua
+## extended modules
+modules/PSBT_Auras.lua
+modules/PSBT_Combat.lua
+modules/PSBT_Cooldowns.lua
+modules/PSBT_Experience.lua
+modules/PSBT_LowSomething.lua

 ## core
-PSBT_Fifo.lua
-PSBT_Label.lua
-PSBT_ScrollArea.lua
-PSBT.lua
-PSBT.xml
\ No newline at end of file
+core/PSBT_Fifo.lua
+core/PSBT_Label.lua
+core/PSBT_ScrollArea.lua
+core/PSBT.lua
+core/PSBT.xml
\ No newline at end of file
diff --git a/PSBT.xml b/PSBT.xml
deleted file mode 100644
index c8e9629..0000000
--- a/PSBT.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<GuiXml>
-    <Controls>
-         <Control name="PSBT_Label" virtual="true">
-            <Dimensions x="300" y="24" />
-
-            <Controls>
-                <Label name="$(parent)_Name" horizontalAlignment="CENTER" verticalAlignment="CENTER" wrapMode="ELLIPSIS">
-                    <AnchorFill/>
-                </Label>
-
-                <Texture name="$(parent)_Icon" layer="OVERLAY" visible="false">
-                    <Dimensions x="24" y="24" />
-                </Texture>
-            </Controls>
-        </Control>
-
-        <TopLevelControl name="PSBT" mouseEnabled="false" clampedToScreen="true" movable="false">
-            <OnInitialized>Initialized( self )</OnInitialized>
-            <Anchor point="TOPLEFT" relativeTo="GuiRoot" relativePoint="TOPLEFT" offsetX="0" offsetY="0"/>
-            <Anchor point="BOTTOMRIGHT" relativeTo="GuiRoot" relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="0"/>
-
-            <Controls>
-                <Control name="$(parent)_Notifications">
-                    <Dimensions x="500" y="60" />
-                    <Anchor point="CENTER" relativeTo="$(parent)" relativePoint="CENTER" offsetX="0" offsetY="450" />
-                    <Controls>
-                        <Texture name="$(parent)_BG" inherits="ZO_ThinListBgStrip" />
-                        <Label name="$(parent)_Name" inherits="ZO_WindowTitle" horizontalAlignment="CENTER" verticalAlignment="CENTER" wrapMode="ELLIPSIS" text="Notifications" >
-                            <AnchorFill/>
-                        </Label>
-                    </Controls>
-                </Control>
-
-                <Control name="$(parent)_Static">
-                    <Dimensions x="500" y="60" />
-                    <Anchor point="CENTER" relativeTo="$(parent)" relativePoint="CENTER" offsetX="0" offsetY="-300" />
-                    <Controls>
-                        <Texture name="$(parent)_BG" inherits="ZO_ThinListBgStrip" />
-                        <Label name="$(parent)_Name" inherits="ZO_WindowTitle" horizontalAlignment="CENTER" verticalAlignment="CENTER" wrapMode="ELLIPSIS" text="Static" >
-                            <AnchorFill/>
-                        </Label>
-                    </Controls>
-                </Control>
-
-                <Control name="$(parent)_Incoming">
-                    <Dimensions x="300" y="450" />
-                    <Anchor point="RIGHT" relativeTo="$(parent)" relativePoint="CENTER" offsetX="-300" offsetY="150" />
-                    <Controls>
-                        <Texture name="$(parent)_BG" inherits="ZO_ThinListBgStrip" />
-                        <Label name="$(parent)_Name" inherits="ZO_WindowTitle" horizontalAlignment="CENTER" verticalAlignment="CENTER" wrapMode="ELLIPSIS" text="Incoming" >
-                            <AnchorFill/>
-                        </Label>
-                    </Controls>
-                </Control>
-
-                <Control name="$(parent)_Outgoing">
-                    <Dimensions x="300" y="450" />
-                    <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="CENTER" offsetX="300" offsetY="150" />
-                    <Controls>
-                        <Texture name="$(parent)_BG" inherits="ZO_ThinListBgStrip" />
-                        <Label name="$(parent)_Name" inherits="ZO_WindowTitle" horizontalAlignment="CENTER" verticalAlignment="CENTER" wrapMode="ELLIPSIS" text="Outgoing" >
-                            <AnchorFill/>
-                        </Label>
-                    </Controls>
-                </Control>
-            </Controls>
-        </TopLevelControl>
-    </Controls>
-</GuiXml>
\ No newline at end of file
diff --git a/PSBT_Auras.lua b/PSBT_Auras.lua
deleted file mode 100644
index fecf234..0000000
--- a/PSBT_Auras.lua
+++ /dev/null
@@ -1,43 +0,0 @@
-local PSBT_Module   = PSBT_Module
-local PSBT_Auras    = PSBT_Module:Subclass()
-local CBM           = CALLBACK_MANAGER
-
-local EFFECT_RESULT_FADED   = EFFECT_RESULT_FADED
-local EFFECT_RESULT_GAINED  = EFFECT_RESULT_GAINED
-
-local PSBT_EVENTS   = PSBT_EVENTS
-local PSBT_AREAS    = PSBT_AREAS
-local PSBT_MODULES  = PSBT_MODULES
-
-function PSBT_Auras:Initialize( ... )
-    PSBT_Module.Initialize( self, ... )
-
-    self:RegisterForEvent( EVENT_EFFECT_CHANGED, function( eventCode, ... ) self:OnEffectChanged( ... ) end )
-end
-
-function PSBT_Auras:OnEffectChanged( changeType, effectSlot, effectName, unitTag, beginTime, endTime, stackCount, iconName, buffType, effectType, abilityType, statusEffectType )
-    if ( unitTag ~= 'player' ) then
-        return
-    end
-
-    print( 'OnEffectChanged' )
-
-    if ( changeType == EFFECT_RESULT_FADED ) then
-        self:Remove( effectName, iconName )
-    elseif ( changeType == EFFECT_RESULT_GAINED ) then
-        self:Add( effectName, iconName )
-    end
-end
-
-function PSBT_Auras:Add( name, iconName )
-    self:NewEvent( PSBT_AREAS.NOTIFICATION, true, iconName, name .. ' Gained' )
-end
-
-function PSBT_Auras:Remove( name, iconName )
-    self:NewEvent( PSBT_AREAS.NOTIFICATION, true, iconName, name .. ' Fades' )
-end
-
-CBM:RegisterCallback( PSBT_EVENTS.LOADED,
-    function( psbt )
-        psbt:RegisterModule( PSBT_MODULES.AURAS, PSBT_Auras:New( psbt ) )
-    end)
\ No newline at end of file
diff --git a/PSBT_Combat.lua b/PSBT_Combat.lua
deleted file mode 100644
index 7c018c0..0000000
--- a/PSBT_Combat.lua
+++ /dev/null
@@ -1,408 +0,0 @@
-local PSBT_Module           = PSBT_Module
-local PSBT_Combat           = PSBT_Module:Subclass()
-PSBT_Combat._iconRegistry   = setmetatable( {}, { __mode = 'kv' } )
-local CBM                   = CALLBACK_MANAGER
-
-local MAX_EVENTS            = 15
-local PlayerName            = GetUnitName( 'player' )
-local PlayerNameRaw         = GetRawUnitName( 'player' )
-local COMBAT_UNIT_TYPE_PLAYER       = COMBAT_UNIT_TYPE_PLAYER
-local COMBAT_UNIT_TYPE_PLAYER_PET   = COMBAT_UNIT_TYPE_PLAYER_PET
-local COMBAT_UNIT_TYPE_NONE         = COMBAT_UNIT_TYPE_NONE
-
-local PSBT_AREAS            = PSBT_AREAS
-local PSBT_EVENTS           = PSBT_EVENTS
-
-local zo_strformat          = zo_strformat
-local GetString             = GetString
-local select                = select
-
-local function IsPlayerType( targetType )
-    return targetType == COMBAT_UNIT_TYPE_PLAYER or
-           targetType == COMBAT_UNIT_TYPE_PLAYER_PET
-end
-
-local function IsPlayer( targetType, targetName )
-    if ( IsPlayerType( targetType ) ) then
-        return true
-    end
-
-    if ( targetType == COMBAT_UNIT_TYPE_NONE ) then
-        return targetName == PlayerName or targetName == PlayerNameRaw
-    end
-
-    return false
-end
-
-local combat_events =
-{
-    [ ACTION_RESULT_ABSORBED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( 'Absorbed <<1>>', abilityName ), area, false
-    end,
-    [ ACTION_RESULT_BLADETURN ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( 'Blocked <<1>>', abilityName ), area, false
-    end,
-    [ ACTION_RESULT_BLOCKED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( 'Blocked <<1>>', abilityName ), area, false
-    end,
-    [ ACTION_RESULT_BLOCKED_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( 'Blocked <<1>>', hitValue ), area, false
-    end,
-    [ ACTION_RESULT_CANT_SEE_TARGET ] = function( ... )
-        return 'Can\'t See Target!', PSBT_AREAS.STATIC, false
-    end,
-    [ ACTION_RESULT_CRITICAL_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( '<<1>>!', hitValue ), area, true
-    end,
-    [ ACTION_RESULT_CRITICAL_HEAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        local name = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            name = sourceName
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            name = targetName
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( '+<<1>> [|c80C3F2<<2>>|r]!', hitValue, name ), area, true
-    end,
-    [ ACTION_RESULT_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( "<<1>>", hitValue ), area, false
-    end,
-    [ ACTION_RESULT_DAMAGE_SHIELDED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( 'Shielded <<1>>', hitValue ), area, false
-    end,
-    [ ACTION_RESULT_DEFENDED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
-        local area = nil
-        if ( IsPlayer( targetType ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( 'Defended <<1>>', hitValue ), area, false
-    end,
-    [ ACTION_RESULT_DOT_TICK ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( '<<1>>', hitValue ), area, false
-    end,
-    [ ACTION_RESULT_DOT_TICK_CRITICAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( '<<1>>!', hitValue ), area, true
-    end,
-    [ ACTION_RESULT_HEAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        local name = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            name = sourceName
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            name = targetName
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( '+<<1>> [|c80C3F2<<2>>|r]' , hitValue, name ), area, false
-    end,
-    [ ACTION_RESULT_HOT_TICK ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        local name = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            name = sourceName
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            name = targetName
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( '+<<1>> [|c80C3F2<<2>>|r]', hitValue, name ), area, false
-    end,
-    [ ACTION_RESULT_HOT_TICK_CRITICAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        local name = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            name = sourceName
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            name = targetName
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( '+<<1>> [|c80C3F2<<2>>|r]!', hitValue, name ), area, true
-    end,
-    [ ACTION_RESULT_DODGED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( 'Dodged <<1>>', abilityName ), area, false
-    end,
-    [ ACTION_RESULT_MISS ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return 'Miss!', area, false
-    end,
-    [ ACTION_RESULT_PARRIED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( 'Parried <<1>>', abilityName ), area, false
-    end,
-    [ ACTION_RESULT_RESIST ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( 'Resisted <<1>>!', abilityName ), area, false
-    end,
-    [ ACTION_RESULT_PARTIAL_RESIST ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( 'Partially Resisted <<1>>!', abilityName ), nil, false
-    end,
-    [ ACTION_RESULT_FALL_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local area = nil
-        if ( IsPlayer( targetType, targetName ) ) then
-            area = PSBT_AREAS.INCOMING
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            return nil, nil, false
-        end
-
-        return zo_strformat( '-<<1>> falling', hitValue ), area, false
-    end,
-    [ ACTION_RESULT_KILLING_BLOW ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType )
-        if ( IsPlayer( targetType, targetName ) ) then
-            return zo_strformat( '<<1>> Kill You.', sourceName ), PSBT_AREAS.STATIC, true
-        elseif ( IsPlayer( sourceType, sourceName ) ) then
-            return zo_strformat( 'Killing Blow <<1>>!', targetName ), PSBT_AREAS.STATIC, true
-        end
-
-        return nil, nil, false
-    end,
-    [ EVENT_ALLIANCE_POINT_UPDATE ] = function(value, sound, diff)
-        local area = nil
-        if ( diff > 0 ) then
-            area = PSBT_AREAS.INCOMING
-        else
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( '<<1>> AP', diff ), area, false
-    end,
-    [ EVENT_RANK_POINT_UPDATE ] = function( tag , value, diff )
-        if (tag == "player") then
-            local area = nil
-            if ( diff > 0 ) then
-                area = PSBT_AREAS.INCOMING
-            else
-                area = PSBT_AREAS.OUTGOING
-            end
-
-            return zo_strformat( '<<1>> RP', diff ), area, false
-        end
-    end,
-    [ EVENT_BATTLE_TOKEN_UPDATE ] = function( value, sound, diff )
-        local area = nil
-        if ( diff > 0 ) then
-            area = PSBT_AREAS.INCOMING
-        else
-            area = PSBT_AREAS.OUTGOING
-        end
-
-        return zo_strformat( '<<1>> BT', diff ), area, false
-    end,
-    [ EVENT_EXPERIENCE_GAIN ] = function( value )
-        return zo_strformat( '+<<1>> XP', value ), PSBT_AREAS.INCOMING, false
-    end,
-    [ EVENT_EXPERIENCE_GAIN_DISCOVERY ] = function( areaName, value )
-        return zo_strformat( '+<<1>> XP', value ), PSBT_AREAS.INCOMING, false
-    end,
-
-    [ ACTION_RESULT_POWER_DRAIN ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
-        local mechanicName = GetString( 'SI_COMBATMECHANICTYPE', mechanicValue )
-        return zo_strformat( '-<<1>> (<<2>>)', hitValue, mechanicName ), PSBT_AREAS.OUTGOING, false
-    end,
-    ---------------------------------------------
-    --[[[ ACTION_RESULT_POWER_ENERGIZE ]            = nil,
-    [ ACTION_RESULT_EFFECT_GAINED_DURATION ]    = nil,
-    [ ACTION_RESULT_EFFECT_GAINED ]             = nil,
-    [ ACTION_RESULT_EFFECT_FADED ]              = nil,
-    [ ACTION_RESULT_DEBUFF ]                    = nil,
-    [ ACTION_RESULT_CASTER_DEAD ]               = nil,
-    [ ACTION_RESULT_COMPLETE ]                  = nil,
-    [ ACTION_RESULT_BUFF ]                      = nil,
-    [ ACTION_RESULT_BUSY ]                      = nil,
-    [ ACTION_RESULT_CANNOT_USE ]                = nil,
-    [ ACTION_RESULT_BEGIN_CHANNEL ]             = nil,
-    [ ACTION_RESULT_BAD_TARGET ]                = nil,
-    [ ACTION_RESULT_ABILITY_ON_COOLDOWN ]       = nil,
-    [ ACTION_RESULT_BEGIN ]                     = nil,
-    [ ACTION_RESULT_POWER_DRAIN ]               = nil,
-    [ ACTION_RESULT_RESURRECT ]                 = nil,
-    [ ACTION_RESULT_DIED ]                      = nil,
-    [ ACTION_RESULT_DIED_XP ]                   = nil,]]
-}
-
-
-function PSBT_Combat:Initialize( ... )
-    PSBT_Module.Initialize( self, ... )
-
-    self._buffer    = ZO_CircularBuffer:New( DEFAULT_MAX_BUFFERED_EVENTS )
-    self._index     = 1
-    self._free      = nil
-
-    for i=1,GetNumAbilities() do
-        local name, icon = GetAbilityInfoByIndex( i )
-        self._iconRegistry[ name ] = icon
-    end
-
-    self:RegisterForEvent( EVENT_COMBAT_EVENT, function( event, ... ) self:OnCombatEvent( ... ) end )
-end
-
-function PSBT_Combat:OnCombatEvent( ... )
-    local result        = select( 1, ... )
-    if ( not combat_events[ result ] ) then
-        return
-    end
-
-    local args = { ... }
-
-    -- did we get hit or do something?
-    if ( IsPlayer( args[ 7 ], args[ 6 ] )
-      or IsPlayer( args[ 9 ], args[ 8 ] ) ) then
-
-        if ( self._free ) then
-            local argCount = #args
-            for i = 1, argCount do
-                self._free[ i ] = args[ i ]
-            end
-
-            for i = #self._free, argCount + 1, -1 do
-                self._free[ i ] = nil
-            end
-
-            self._free = self._buffer:Add( self._free )
-        else
-            self._free = self._buffer:Add( args )
-        end
-
-        self._index = self._index - 1
-    end
-end
-
-function PSBT_Combat:OnUpdate( frameTime )
-    if ( self._index <= 0 ) then
-        self._index = 1
-    end
-
-    local bufferSize = self._buffer:Size()
-    local endPoint = zo_min( self._index + MAX_EVENTS, bufferSize )
-    for i = self._index, endPoint do
-        local entry = self._buffer:At( i )
-        if ( entry ) then
-            self:DispatchEvent( unpack( entry ) )
-        end
-    end
-
-    if ( endPoint >= bufferSize ) then
-        self._buffer:Clear()
-    else
-        self._index = endPoint + 1
-    end
-end
-
---integer result, bool isError, string abilityName, integer abilityGraphic, integer abilityActionSlotType, string sourceName, integer sourceType, string targetName, integer targetType, integer hitValue, integer powerType, integer damageType, bool log
-function PSBT_Combat:DispatchEvent( result, _, ... )
-    local func = combat_events[ result ]
-    local text, area, crit = func( ... )
-
-    local icon = self._iconRegistry[ select( 1, ... ) ]
-
-    self:NewEvent( area, crit, icon, text )
-end
-
-CBM:RegisterCallback( PSBT_EVENTS.LOADED,
-    function( psbt )
-        psbt:RegisterModule( PSBT_MODULES.COMBAT, PSBT_Combat:New( psbt ) )
-    end)
\ No newline at end of file
diff --git a/PSBT_Constants.lua b/PSBT_Constants.lua
deleted file mode 100644
index d63bbd2..0000000
--- a/PSBT_Constants.lua
+++ /dev/null
@@ -1,29 +0,0 @@
-PSBT_MODULES =
-{
-    SETTINGS = 'settings', -- saved variables
-    OPTIONS  = 'options',  -- options panel
-    COOLDOWNS = 'cooldowns',
-    COMBAT = 'combat',
-    AURAS = 'auras',
-    XP = 'experience',
-    LOW = 'lowsomething'
-}
-
-PSBT_AREAS =
-{
-    NOTIFICATION    = '_Notifications',
-    INCOMING        = '_Incoming',
-    OUTGOING        = '_Outgoing',
-    STATIC          = '_Static'
-}
-
-PSBT_SETTINGS =
-{
-
-}
-
-PSBT_EVENTS =
-{
-    LOADED = 'PSBT_LOADED',
-    CONFIG = 'PSBT_CONFIG'
-}
\ No newline at end of file
diff --git a/PSBT_Cooldowns.lua b/PSBT_Cooldowns.lua
deleted file mode 100644
index c65d4b1..0000000
--- a/PSBT_Cooldowns.lua
+++ /dev/null
@@ -1,17 +0,0 @@
-local PSBT_Cooldowns        = PSBT_Module:Subclass()
-local CBM = CALLBACK_MANAGER
-
-function PSBT_Cooldowns:Initialize( ... )
-    PSBT_Module.Initialize( self, ... )
-
-    self:RegisterForEvent( EVENT_ABILITY_COOLDOWN_UPDATED, function( abilityId ) self:OnAbilityCooldownUpdated( abilityId ) end )
-end
-
-function PSBT_Cooldowns:OnAbilityCooldownUpdated( abilityId )
-
-end
-
-CBM:RegisterCallback( PSBT_EVENTS.LOADED,
-    function( psbt )
-        psbt:RegisterModule( PSBT_MODULES.COOLDOWNS, PSBT_Cooldowns:New( psbt ) )
-    end)
\ No newline at end of file
diff --git a/PSBT_Experience.lua b/PSBT_Experience.lua
deleted file mode 100644
index 241cde6..0000000
--- a/PSBT_Experience.lua
+++ /dev/null
@@ -1,45 +0,0 @@
-local PSBT_Module       = PSBT_Module
-local PSBT_Experience   = PSBT_Module:Subclass()
-local CBM               = CALLBACK_MANAGER
-
-local zo_min            = zo_min
-local tostring          = tostring
-
-local PSBT_AREAS        = PSBT_AREAS
-local PSBT_EVENTS       = PSBT_EVENTS
-local PSBT_MODULES      = PSBT_MODULES
-
-function PSBT_Experience:Initialize( ... )
-    PSBT_Module.Initialize( self, ... )
-
-    self._currentExperience = GetUnitXP( 'player' )
-
-    self:RegisterForEvent( EVENT_EXPERIENCE_UPDATE, function( event, ... ) self:OnXPUpdated( ... )     end )
-end
-
-function PSBT_Experience:OnXPUpdated( tag, exp, maxExp, reason  )
-    if ( tag ~= 'player' ) then
-        return
-    end
-
-    local xp = zo_min( exp, maxExp )
-
-    if ( self._currentExperience == xp ) then
-        return
-    end
-
-    local gain = xp - self._currentExperience
-    self._currentExperience = xp
-
-    if ( gain <= 0 ) then
-        return
-    end
-
-    self:NewEvent( PSBT_AREAS.NOTIFICATION, true, nil, '+' .. tostring( gain ) .. ' XP' )
-end
-
-
-CBM:RegisterCallback( PSBT_EVENTS.LOADED,
-    function( psbt )
-        psbt:RegisterModule( PSBT_MODULES.XP, PSBT_Experience:New( psbt ) )
-    end)
\ No newline at end of file
diff --git a/PSBT_Fifo.lua b/PSBT_Fifo.lua
deleted file mode 100644
index 702e7a9..0000000
--- a/PSBT_Fifo.lua
+++ /dev/null
@@ -1,87 +0,0 @@
-------
--- First in First Out container
--- reference:
--- https://github.com/daurnimator/lomp2/blob/master/fifo.lua
-------
-
-local select , setmetatable = select , setmetatable
-
-PSBT_Fifo = {}
-local mt =
-{
-    __index = PSBT_Fifo,
-    __newindex = function( f, k, v )
-        if ( type( k ) == 'number' ) then
-            return rawset( f, k, v )
-        end
-    end,
-}
-
-function PSBT_Fifo.New( ... )
-    return setmetatable( { head = 1, tail = select( '#', ... ), ... }, mt )
-end
-
-function PSBT_Fifo:Size()
-    return self.tail - self.head + 1
-end
-
-function PSBT_Fifo:Peek()
-    return self[ self.head ]
-end
-
-function PSBT_Fifo:Push( value )
-    self.tail = self.tail + 1
-    self[ self.tail ] = value
-end
-
-function PSBT_Fifo:Pop()
-    local head, tail = self.head, self.tail
-    if ( head > tail ) then
-        return nil
-    end
-
-    local value = self[ head ]
-    self[ head ] = nil
-    self.head = head + 1
-    return value
-end
-
-function PSBT_Fifo:Remove( index )
-    local head, tail = self.head, self.tail
-
-    if head + index > tail then
-        return
-    end
-
-    local position  = head + index - 1
-    local value     = self[ position ]
-
-    if ( position <= (head + tail) * 0.5 ) then
-        for i = position, head, -1 do
-            self[ i ] = self[ i - 1 ]
-        end
-        self.head = head + 1
-    else
-        for i = position, tail do
-            self[ i ] = self[ i + 1 ]
-        end
-        self.tail = tail - 1
-    end
-
-    return value
-end
-
-local iterator = function( fifo, previous )
-    local i = fifo.head + previous
-    if ( i > fifo.tail ) then
-        return nil
-    end
-
-    return previous + 1, fifo[ i ]
-end
-
-function PSBT_Fifo:Iterator()
-    return iterator, self, 0
-end
-
-mt.__len = PSBT_Fifo.Size
\ No newline at end of file
diff --git a/PSBT_Label.lua b/PSBT_Label.lua
deleted file mode 100644
index 8195199..0000000
--- a/PSBT_Label.lua
+++ /dev/null
@@ -1,71 +0,0 @@
-PSBT_Label = ZO_Object:Subclass()
-
-local CENTER = CENTER
-
-function PSBT_Label:New( ... )
-    local result = ZO_Object.New( self )
-    result:Initialize( ... )
-    return result
-end
-
-function PSBT_Label:Initialize( objectPool )
-    self.objectPool = objectPool
-    self.control = CreateControlFromVirtual( 'PSBT_Label', self.objectPool.control, 'PSBT_Label', self.objectPool:GetNextControlId() )
-    self.label   = self.control:GetNamedChild( '_Name' )
-    self.icon    = self.control:GetNamedChild( '_Icon' )
-    self.expire  = 0
-    self.moving  = false
-
-    self.control:SetAlpha( 0.0 )
-end
-
-function PSBT_Label:SetMoving( set )
-    self.moving = set
-end
-
-function PSBT_Label:IsMoving()
-    return self.moving
-end
-
-function PSBT_Label:SetExpire( expire )
-    self.expire = expire
-end
-
-function PSBT_Label:WillExpire( frameTime )
-    return frameTime > self.expire
-end
-
-function PSBT_Label:IsExpired( frameTime )
-    if ( self.expire == -1 ) then
-        return false
-    end
-
-    if ( self.moving ) then
-        return false
-    end
-
-    return frameTime > self.expire
-end
-
-function PSBT_Label:Finalize()
-    self:SetText( '' )
-    self:SetTexture( 0 )
-    self:SetExpire( 0 )
-    self:SetMoving( false )
-end
-
-function PSBT_Label:SetText( text )
-    self.label:SetText( text )
-
-    local textWidth = self.label:GetTextDimensions()
-    self.icon:SetAnchor( CENTER, self.control, CENTER, ( textWidth * -0.45 ) - self.icon:GetWidth(), 0 )
-end
-
-function PSBT_Label:SetTexture( texture )
-    if ( type( texture ) == 'string' ) then
-        self.icon:SetHidden( false )
-        self.icon:SetTexture( texture )
-    else
-        self.icon:SetHidden( true )
-    end
-end
\ No newline at end of file
diff --git a/PSBT_LowSomething.lua b/PSBT_LowSomething.lua
deleted file mode 100644
index da92fbb..0000000
--- a/PSBT_LowSomething.lua
+++ /dev/null
@@ -1,71 +0,0 @@
-local PSBT_Module           = PSBT_Module
-local PSBT_LowSomething     = PSBT_Module:Subclass()
-PSBT_LowSomething._pools    = {}
-local CBM                   = CALLBACK_MANAGER
-
-local threshold             = 0.33
-
-local PSBT_AREAS            = PSBT_AREAS
-local PSBT_MODULES          = PSBT_MODULES
-local PSBT_EVENTS           = PSBT_EVENTS
-
-local POWERTYPE_HEALTH      = POWERTYPE_HEALTH
-local POWERTYPE_MAGICKA     = POWERTYPE_MAGICKA
-local POWERTYPE_STAMINA     = POWERTYPE_STAMINA
-local POWERTYPE_MOUNT_STAMINA = POWERTYPE_MOUNT_STAMINA
-
-function PSBT_LowSomething:Initialize( ... )
-    PSBT_Module.Initialize( self, ... )
-
-    self._pools[POWERTYPE_HEALTH]        = 0
-    self._pools[POWERTYPE_MAGICKA]       = 0
-    self._pools[POWERTYPE_STAMINA]       = 0
-    self._pools[POWERTYPE_MOUNT_STAMINA] = 0
-
-    self:RegisterForEvent( EVENT_POWER_UPDATE, function( event, ... ) self:OnPowerUpdate( ... ) end )
-end
-
-function PSBT_LowSomething:OnPowerUpdate( unit, powerPoolIndex, powerType, powerPool, powerPoolMax )
-    if ( unit ~= 'player' ) then
-        return
-    end
-
-    if ( powerPool == 0 ) then
-        return
-    end
-
-    if ( not self._pools[ powerType ] ) then
-        return
-    end
-
-    local newValue = powerPool / powerPoolMax
-
-    if ( self._pools[ powerType ] < threshold
-        or newValue > self._pools[ powerType ]
-        or newValue > threshold ) then
-
-        self._pools[ powerType ] = newValue
-        return
-    end
-
-    self._pools[ powerType ] = newValue
-
-    local string = nil
-    if ( powerType == POWERTYPE_HEALTH ) then
-        string = 'Health Low! (|cF2920C' .. powerPool .. '|r)'
-    elseif ( powerType == POWERTYPE_MAGICKA ) then
-        string = 'Magicka Low! (|cCC0CF2' .. powerPool .. '|r)'
-    elseif ( powerType == POWERTYPE_STAMINA ) then
-        string = 'Stamina Low! (|c0CF2B9' .. powerPool .. '|r)'
-    elseif ( powerType == POWERTYPE_MOUNT_STAMINA ) then
-        string = 'Mount Stamina Low! (|c0CF2B9' .. powerPool .. '|r)'
-    end
-
-    PlaySound( 'Quest_StepFailed' )
-    self:NewEvent( PSBT_AREAS.STATIC, true, nil, string )
-end
-
-CBM:RegisterCallback( PSBT_EVENTS.LOADED,
-    function( psbt )
-        psbt:RegisterModule( PSBT_MODULES.LOW, PSBT_LowSomething:New( psbt ) )
-    end)
\ No newline at end of file
diff --git a/PSBT_Module.lua b/PSBT_Module.lua
deleted file mode 100644
index 6152312..0000000
--- a/PSBT_Module.lua
+++ /dev/null
@@ -1,30 +0,0 @@
-PSBT_Module             = ZO_Object:Subclass()
-PSBT_Module._root       = nil
-
-local CBM = CALLBACK_MANAGER
-
-function PSBT_Module:New( ... )
-    local result = ZO_Object.New( self )
-    result:Initialize( ... )
-    return result
-end
-
-function PSBT_Module:Initialize( root )
-    self._root = root
-end
-
-function PSBT_Module:RegisterForEvent( event, callback )
-    self._root:RegisterForEvent( event, callback )
-end
-
-function PSBT_Module:UnregisterForEvent( event, callback )
-    self._root:UnregisterForEvent( event, callback )
-end
-
-function PSBT_Module:OnUpdate( frametime )
-    -- stub, implement if needed
-end
-
-function PSBT_Module:NewEvent( scrollArea, sticky, icon, text )
-    self._root:NewEvent( scrollArea, sticky, icon, text )
-end
\ No newline at end of file
diff --git a/PSBT_Options.lua b/PSBT_Options.lua
deleted file mode 100644
index 10115bc..0000000
--- a/PSBT_Options.lua
+++ /dev/null
@@ -1,35 +0,0 @@
-local LAM = LibStub( 'LibAddonMenu-1.0' )
-if ( not LAM ) then return end
-
-local LMP = LibStub( 'LibMediaProvider-1.0' )
-if ( not LMP ) then return end
-
-local PSBT_Module       = PSBT_Module
-local PSBT_Options      = PSBT_Module:Subclass()
-local CBM               = CALLBACK_MANAGER
-
-local PSBT_MODULES      = PSBT_MODULES
-local PSBT_EVENTS       = PSBT_EVENTS
-
-local decorations = { 'none', 'soft-shadow-thin', 'soft-shadow-thick', 'shadow' }
-
-function PSBT_Options:Initialize( root )
-    PSBT_Module.Initialize( self, root )
-    self:InitialzeControlPanel()
-end
-
-function PSBT_Options:InitialzeControlPanel()
-    self.config_panel = LAM:CreateControlPanel( '_psbt', 'PSBT' )
-    self.config_mode = false
-
-    LAM:AddButton( self.config_panel, '_psbt_editlayout_btn', 'Edit Layout', '',
-        function()
-            CBM:FireCallbacks( PSBT_EVENTS.CONFIG, not self.config_mode )
-            self.config_mode = not self.config_mode
-        end )
-end
-
-CBM:RegisterCallback( PSBT_EVENTS.LOADED,
-    function( psbt )
-        psbt:RegisterModule( PSBT_MODULES.OPTIONS, PSBT_Options:New( psbt ) )
-    end)
\ No newline at end of file
diff --git a/PSBT_ScrollArea.lua b/PSBT_ScrollArea.lua
deleted file mode 100644
index e32b8d5..0000000
--- a/PSBT_ScrollArea.lua
+++ /dev/null
@@ -1,174 +0,0 @@
-local LibAnim = LibStub( 'LibAnimation-1.0' )
-if ( not LibAnim ) then return end
-
-PSBT_ScrollArea     = ZO_Object:Subclass()
-local CBM           = CALLBACK_MANAGER
-local tinsert       = table.insert
-local tremove       = table.remove
-local NUM_STICKY    = 4
-
-local PSBT_Fifo     = PSBT_Fifo
-local CENTER        = CENTER
-
-local PSBT_EVENTS   = PSBT_EVENTS
-
-function PSBT_ScrollArea:New( ... )
-    local result = ZO_Object.New( self )
-    result:Initialize( ... )
-    return result
-end
-
-function PSBT_ScrollArea:Initialize( super, areaName, anchor, position )
-    self.name           = areaName
-    self.control        = super:GetNamedChild( areaName )
-    self.background     = self.control:GetNamedChild( '_BG' )
-    self.label          = self.control:GetNamedChild( '_Name' )
-    self._anchor        = anchor
-    self._animHeight    = nil
-    self._newSticky     = false
-    self._sticky        = PSBT_Fifo.New()
-    self._pendingSticky = PSBT_Fifo.New()
-    self._normal        = {}
-    self._pendingNormal = PSBT_Fifo.New()
-
-    if ( anchor == TOP ) then
-        self._animHeight = self.control:GetHeight()
-    else
-        self._animHeight = -1 * self.control:GetHeight()
-    end
-    self:Position( unpack( position ) )
-    self:SetConfigurationMode( false )
-    self.control:SetHandler( 'OnUpdate', function( event, ... ) self:OnUpdate( ... ) end )
-
-    CBM:RegisterCallback( PSBT_EVENTS.CONFIG, function( ... ) self:SetConfigurationMode( ... ) end )
-end
-
-function PSBT_ScrollArea:SetConfigurationMode( enable )
-    self.control:SetMovable( enable )
-    self.control:SetMouseEnabled( enable )
-    self.label:SetHidden( not enable )
-    if ( enable ) then
-        local enter = LibAnim:New( self.background )
-        enter:AlphaTo( 1.0, 500 )
-        enter:Play()
-    else
-        local exit = LibAnim:New( self.background )
-        exit:AlphaTo( 0.0, 500 )
-        exit:Play()
-    end
-end
-
-function PSBT_ScrollArea:Position( point, relPoint, x, y )
-    self.control:SetAnchor( point, self.control:GetParent(), relPoint, x, y )
-end
-
-function PSBT_ScrollArea:GetAnchorOffsets()
-    local _, point, _, relPoint, offsX, offsY = self.control:GetAnchor( 0 )
-    return point, relPoint, offsX, offsY
-end
-
-function PSBT_ScrollArea:Push( entry, sticky )
-    if ( sticky ) then
-        entry.control:SetAnchor( CENTER, self.control, CENTER, 0, self.control:GetHeight() )
-        self._pendingSticky:Push( entry )
-        return
-    end
-
-    entry.control:SetAnchor( CENTER, self.control, self._anchor, 0, 0 )
-    self._pendingNormal:Push( entry )
-end
-
-function PSBT_ScrollArea:OnUpdate( frameTime )
-    while ( self._sticky:Size() > NUM_STICKY ) do
-        local old = self._sticky:Pop()
-        local anim = LibAnim:New( old.control )
-        anim:AlphaTo( 0.0, 200 )
-        anim:Play()
-
-        old:SetMoving( false )
-
-        old:SetExpire( frameTime + 2 )
-        self._newSticky = true
-    end
-
-    repeat
-        local entry = self._sticky:Peek()
-        if ( entry and entry:WillExpire( frameTime + 2 ) ) then
-            local anim = LibAnim:New( entry.control )
-            anim:AlphaTo( 0.0, 200 )
-            anim:TranslateTo( 0, -200, 200 )
-            anim:Play()
-
-            entry:SetMoving( false )
-
-            self._sticky:Pop()
-            self._newSticky = true
-        end
-    until( not entry or not entry:WillExpire( frameTime + 2 )  )
-
-    if ( self._pendingNormal:Size() ) then
-        local newEntry = self._pendingNormal:Pop()
-        if ( newEntry ) then
-            newEntry:SetExpire( frameTime + 5 )
-
-            local anim = LibAnim:New( newEntry.control )
-            anim:AlphaTo( 1.0, 200 )
-            anim:Play()
-
-            tinsert( self._normal, newEntry )
-        end
-    end
-
-    if ( self._pendingSticky:Size() ) then
-        local newEntry = self._pendingSticky:Pop()
-        if ( newEntry ) then
-            newEntry:SetExpire( frameTime + 5 )
-
-            local anim = LibAnim:New( newEntry.control )
-            anim:AlphaTo( 1.0, 200 )
-            anim:Play()
-
-            self._sticky:Push( newEntry )
-            self._newSticky = true
-        end
-    end
-
-    local i = 1
-    while ( i <= #self._normal ) do
-        local entry = self._normal[ i ]
-
-        if ( entry:WillExpire( frameTime + 2 ) ) then
-            local anim = LibAnim:New( entry.control )
-            anim:AlphaTo( 0.0, 200 )
-            anim:Play()
-
-            entry:SetMoving( false )
-
-            tremove( self._normal, i )
-        else
-            if ( not entry:IsMoving() ) then
-                local anim = LibAnim:New( entry.control )
-                anim:TranslateTo( 0, self._animHeight, 3000 )
-                anim:Play()
-
-                entry:SetMoving( true )
-            end
-            i = i + 1
-        end
-    end
-
-    local top = 0
-    for i, entry in self._sticky:Iterator() do
-        if ( self._newSticky ) then
-            local anim = LibAnim:New( entry.control )
-            anim:TranslateTo( 0, top, 200 )
-            anim:Play()
-
-            entry:SetMoving( true )
-
-            top = top + entry.control:GetHeight()
-        end
-    end
-
-    self._newSticky = false
-end
\ No newline at end of file
diff --git a/PSBT_Settings.lua b/PSBT_Settings.lua
deleted file mode 100644
index d1cb247..0000000
--- a/PSBT_Settings.lua
+++ /dev/null
@@ -1,45 +0,0 @@
-local PSBT_Module   = PSBT_Module
-local PSBT_Settings = PSBT_Module:Subclass()
-local CBM           = CALLBACK_MANAGER
-
-local PSBT_MODULES  = PSBT_MODULES
-local PSBT_EVENTS   = PSBT_EVENTS
-local PSBT_AREAS    = PSBT_AREAS
-
-local ZO_SavedVars  = ZO_SavedVars
-
-local RIGHT = RIGHT
-local LEFT = LEFT
-local CENTER = CENTER
-
-local defaults =
-{
-    [ PSBT_AREAS.INCOMING       ] = { RIGHT,  CENTER, -300,  150   },
-    [ PSBT_AREAS.OUTGOING       ] = { LEFT,   CENTER, 300,   150   },
-    [ PSBT_AREAS.STATIC         ] = { CENTER, CENTER, 0,     -300  },
-    [ PSBT_AREAS.NOTIFICATION   ] = { CENTER, CENTER, 0,     450   }
-}
-
-function PSBT_Settings:Initialize( ... )
-    PSBT_Module.Initialize( self, ... )
-
-    self.db = ZO_SavedVars:New( 'PSBT_DB', 1.5, nil, defaults )
-    self.profile = self.db:GetInterfaceForCharacter( GetDisplayName(), GetUnitName( 'player' ) )
-end
-
-function PSBT_Settings:GetSetting( identity )
-    if ( not self.profile[ identity ] ) then
-        return nil
-    end
-
-    return self.profile[ identity ]
-end
-
-function PSBT_Settings:SetSetting( identity, value )
-    self.profile[ identity ] = value
-end
-
-CBM:RegisterCallback( PSBT_EVENTS.LOADED,
-    function( psbt )
-        psbt:RegisterModule( PSBT_MODULES.SETTINGS, PSBT_Settings:New( psbt ) )
-    end)
\ No newline at end of file
diff --git a/core/PSBT.lua b/core/PSBT.lua
new file mode 100644
index 0000000..28daf91
--- /dev/null
+++ b/core/PSBT.lua
@@ -0,0 +1,178 @@
+------------------------------------------------
+-- Pawkette's Scrolling Battle Text
+--
+-- @classmod PSBT
+-- @author Pawkette ( pawkette.heals@gmail.com )
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2014 Pawkette
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+]]
+------------------------------------------------
+local LMP = LibStub( 'LibMediaProvider-1.0' )
+if ( not LMP ) then return end
+
+local PSBT = ZO_ObjectPool:Subclass()
+PSBT._modules  = {}
+PSBT._areas    = {}
+
+local CBM               = CALLBACK_MANAGER
+local PSBT_ScrollArea   = PSBT_ScrollArea
+local PSBT_AREAS        = PSBT_AREAS
+local PSBT_EVENTS       = PSBT_EVENTS
+local PSBT_MODULES      = PSBT_MODULES
+local PSBT_SETTINGS     = PSBT_SETTINGS
+
+function PSBT:New( ... )
+    local result = ZO_ObjectPool.New( self, PSBT.CreateLabel, function( ... ) self:ResetLabel( ... ) end )
+    result:Initialize( ... )
+    return result
+end
+
+function PSBT:Initialize( control )
+    self.control = control
+    self.control:RegisterForEvent( EVENT_ADD_ON_LOADED, function( _, addon ) self:OnLoaded( addon ) end )
+end
+
+function PSBT:FormatFont( font )
+    local face, size, decoration = font.face, font.size, font.deco
+    local fmt = '%s|%d'
+    if ( decoration and decoration ~= 'none' ) then
+        fmt = fmt .. '|%s'
+    end
+
+    return fmt:format( LMP:Fetch( LMP.MediaType.FONT, face ), size, decoration )
+end
+
+function PSBT:OnLoaded( addon )
+    if ( addon ~= 'PSBT' ) then
+        return
+    end
+
+    print( 'Loading PSBT' )
+    CBM:FireCallbacks( PSBT_EVENTS.LOADED, self )
+
+    self._areas[ PSBT_AREAS.INCOMING ]     = PSBT_ScrollArea:New( self.control, PSBT_AREAS.INCOMING,     self:GetSetting( PSBT_AREAS.INCOMING ) )
+    self._areas[ PSBT_AREAS.OUTGOING ]     = PSBT_ScrollArea:New( self.control, PSBT_AREAS.OUTGOING,     self:GetSetting( PSBT_AREAS.OUTGOING ) )
+    self._areas[ PSBT_AREAS.STATIC ]       = PSBT_ScrollArea:New( self.control, PSBT_AREAS.STATIC,       self:GetSetting( PSBT_AREAS.STATIC ) )
+    self._areas[ PSBT_AREAS.NOTIFICATION ] = PSBT_ScrollArea:New( self.control, PSBT_AREAS.NOTIFICATION, self:GetSetting( PSBT_AREAS.NOTIFICATION ) )
+
+    CBM:RegisterCallback( PSBT_EVENTS.CONFIG, function( ... ) self:SetConfigurationMode( ... ) end )
+    self.control:SetHandler( 'OnUpdate', function( _, frameTime ) self:OnUpdate( frameTime ) end )
+end
+
+function PSBT:OnUpdate( frameTime )
+    for k,label in pairs( self:GetActiveObjects() ) do
+        if ( label:IsExpired( frameTime ) ) then
+            self:ReleaseObject( k )
+        end
+    end
+
+    for k,v in pairs( self._modules ) do
+        v:OnUpdate( frameTime )
+    end
+end
+
+function PSBT:CreateLabel()
+    return PSBT_Label:New( self )
+end
+
+function PSBT:ResetLabel( label )
+    label:Finalize()
+end
+
+function PSBT:SetConfigurationMode( mode )
+    if ( not mode ) then
+        for k,v in pairs( self._areas ) do
+            self:SetSetting( k, v:GetAnchorOffsets() )
+        end
+    end
+end
+
+function PSBT:RegisterModule( identity, class )
+    if ( self._modules[ identity ] ) then
+        return
+    end
+
+    print ( 'PSBT:RegisterModule %s', identity )
+    self._modules[ identity ] = class
+end
+
+function PSBT:GetModule( identity )
+    if ( not self._modules[ identity ] ) then
+        return nil
+    end
+
+    return self._modules[ identity ]
+end
+
+function PSBT:GetSetting( name )
+    local settings = self:GetModule( PSBT_MODULES.SETTINGS )
+    return settings:GetSetting( name )
+end
+
+function PSBT:SetSetting( name, value )
+    local settings = self:GetModule( PSBT_MODULES.SETTINGS )
+    settings:SetSetting( name, value )
+
+    if ( name == PSBT_AREAS.INCOMING or
+         name == PSBT_AREAS.OUTGOING or
+         name == PSBT_AREAS.STATIC or
+         name == PSBT_AREAS.NOTIFICATION ) then
+
+        self._areas[ name ]:SetSettings( value )
+    end
+end
+
+function PSBT:RegisterForEvent( event, callback )
+    self.control:RegisterForEvent( event, callback )
+end
+
+function PSBT:UnregisterForEvent( event, callback )
+    self.control:UnregisterForEvent( event, callback )
+end
+
+function PSBT:NewEvent( scrollArea, sticky, icon, text )
+    local entry = self:AcquireObject()
+    local area = self._areas[ scrollArea ]
+    if ( not area ) then
+        return
+    end
+
+    if ( sticky ) then
+        entry.label:SetFont( self:FormatFont( self:GetSetting( PSBT_SETTINGS.sticky_font ) ) )
+        entry.control:SetDrawTier( DT_HIGH )
+    else
+        entry.label:SetFont( self:FormatFont( self:GetSetting( PSBT_SETTINGS.normal_font ) ) )
+        entry.control:SetDrawTier( DT_LOW )
+    end
+
+    entry:SetExpire( -1 )
+    entry:SetText( text )
+    entry:SetTexture( icon )
+
+    area:Push( entry, sticky, direction )
+end
+
+-- LEAVE ME THE FUARK ALONE
+function Initialized( control )
+    _G.PSBT = PSBT:New( control )
+end
diff --git a/core/PSBT.xml b/core/PSBT.xml
new file mode 100644
index 0000000..52f9c55
--- /dev/null
+++ b/core/PSBT.xml
@@ -0,0 +1,67 @@
+<GuiXml>
+    <Controls>
+         <Control name="PSBT_Label" virtual="true">
+            <Dimensions x="500" y="24" />
+
+            <Controls>
+                <Label name="$(parent)_Name" horizontalAlignment="CENTER" verticalAlignment="CENTER" wrapMode="ELLIPSIS">
+                    <AnchorFill/>
+                </Label>
+
+                <Texture name="$(parent)_Icon" layer="OVERLAY" visible="false" />
+            </Controls>
+        </Control>
+
+        <TopLevelControl name="PSBT" mouseEnabled="false" clampedToScreen="true" movable="false">
+            <OnInitialized>Initialized( self )</OnInitialized>
+            <Anchor point="TOPLEFT" relativeTo="GuiRoot" relativePoint="TOPLEFT" offsetX="0" offsetY="0"/>
+            <Anchor point="BOTTOMRIGHT" relativeTo="GuiRoot" relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="0"/>
+
+            <Controls>
+                <Control name="$(parent)_Notifications">
+                    <Dimensions x="500" y="60" />
+                    <Anchor point="CENTER" relativeTo="$(parent)" relativePoint="CENTER" offsetX="0" offsetY="450" />
+                    <Controls>
+                        <Texture name="$(parent)_BG" inherits="ZO_ThinListBgStrip" />
+                        <Label name="$(parent)_Name" inherits="ZO_WindowTitle" horizontalAlignment="CENTER" verticalAlignment="CENTER" wrapMode="ELLIPSIS" text="Notifications" >
+                            <AnchorFill/>
+                        </Label>
+                    </Controls>
+                </Control>
+
+                <Control name="$(parent)_Static">
+                    <Dimensions x="500" y="60" />
+                    <Anchor point="CENTER" relativeTo="$(parent)" relativePoint="CENTER" offsetX="0" offsetY="-300" />
+                    <Controls>
+                        <Texture name="$(parent)_BG" inherits="ZO_ThinListBgStrip" />
+                        <Label name="$(parent)_Name" inherits="ZO_WindowTitle" horizontalAlignment="CENTER" verticalAlignment="CENTER" wrapMode="ELLIPSIS" text="Static" >
+                            <AnchorFill/>
+                        </Label>
+                    </Controls>
+                </Control>
+
+                <Control name="$(parent)_Incoming">
+                    <Dimensions x="300" y="450" />
+                    <Anchor point="RIGHT" relativeTo="$(parent)" relativePoint="CENTER" offsetX="-300" offsetY="150" />
+                    <Controls>
+                        <Texture name="$(parent)_BG" inherits="ZO_ThinListBgStrip" />
+                        <Label name="$(parent)_Name" inherits="ZO_WindowTitle" horizontalAlignment="CENTER" verticalAlignment="CENTER" wrapMode="ELLIPSIS" text="Incoming" >
+                            <AnchorFill/>
+                        </Label>
+                    </Controls>
+                </Control>
+
+                <Control name="$(parent)_Outgoing">
+                    <Dimensions x="300" y="450" />
+                    <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="CENTER" offsetX="300" offsetY="150" />
+                    <Controls>
+                        <Texture name="$(parent)_BG" inherits="ZO_ThinListBgStrip" />
+                        <Label name="$(parent)_Name" inherits="ZO_WindowTitle" horizontalAlignment="CENTER" verticalAlignment="CENTER" wrapMode="ELLIPSIS" text="Outgoing" >
+                            <AnchorFill/>
+                        </Label>
+                    </Controls>
+                </Control>
+            </Controls>
+        </TopLevelControl>
+    </Controls>
+</GuiXml>
\ No newline at end of file
diff --git a/core/PSBT_Constants.lua b/core/PSBT_Constants.lua
new file mode 100644
index 0000000..e63b95f
--- /dev/null
+++ b/core/PSBT_Constants.lua
@@ -0,0 +1,44 @@
+PSBT_MODULES =
+{
+    SETTINGS            = 'settings', -- saved variables
+    OPTIONS             = 'options',  -- options panel
+    COOLDOWNS           = 'cooldowns',
+    COMBAT              = 'combat',
+    AURAS               = 'auras',
+    XP                  = 'experience',
+    LOW                 = 'lowsomething',
+}
+
+PSBT_AREAS =
+{
+    NOTIFICATION        = '_Notifications',
+    INCOMING            = '_Incoming',
+    OUTGOING            = '_Outgoing',
+    STATIC              = '_Static'
+}
+
+PSBT_SETTINGS =
+{
+    normal_font         = 'normal_font',
+    sticky_font         = 'sticky_font'
+}
+
+PSBT_EVENTS =
+{
+    LOADED              = 'PSBT_LOADED',
+    CONFIG              = 'PSBT_CONFIG',
+    REGISTER_ANIMATIONS = 'PSBT_REGISTER_ANIMATIONS',
+}
+
+PSBT_SCROLL_DIRECTIONS =
+{
+    UP                  = 'up',
+    DOWN                = 'down',
+}
+
+PSBT_ICON_SIDE =
+{
+    NONE = 'none',
+    LEFT = 'left',
+    RIGHT = 'right',
+}
\ No newline at end of file
diff --git a/core/PSBT_Fifo.lua b/core/PSBT_Fifo.lua
new file mode 100644
index 0000000..702e7a9
--- /dev/null
+++ b/core/PSBT_Fifo.lua
@@ -0,0 +1,87 @@
+------
+-- First in First Out container
+-- reference:
+-- https://github.com/daurnimator/lomp2/blob/master/fifo.lua
+------
+
+local select , setmetatable = select , setmetatable
+
+PSBT_Fifo = {}
+local mt =
+{
+    __index = PSBT_Fifo,
+    __newindex = function( f, k, v )
+        if ( type( k ) == 'number' ) then
+            return rawset( f, k, v )
+        end
+    end,
+}
+
+function PSBT_Fifo.New( ... )
+    return setmetatable( { head = 1, tail = select( '#', ... ), ... }, mt )
+end
+
+function PSBT_Fifo:Size()
+    return self.tail - self.head + 1
+end
+
+function PSBT_Fifo:Peek()
+    return self[ self.head ]
+end
+
+function PSBT_Fifo:Push( value )
+    self.tail = self.tail + 1
+    self[ self.tail ] = value
+end
+
+function PSBT_Fifo:Pop()
+    local head, tail = self.head, self.tail
+    if ( head > tail ) then
+        return nil
+    end
+
+    local value = self[ head ]
+    self[ head ] = nil
+    self.head = head + 1
+    return value
+end
+
+function PSBT_Fifo:Remove( index )
+    local head, tail = self.head, self.tail
+
+    if head + index > tail then
+        return
+    end
+
+    local position  = head + index - 1
+    local value     = self[ position ]
+
+    if ( position <= (head + tail) * 0.5 ) then
+        for i = position, head, -1 do
+            self[ i ] = self[ i - 1 ]
+        end
+        self.head = head + 1
+    else
+        for i = position, tail do
+            self[ i ] = self[ i + 1 ]
+        end
+        self.tail = tail - 1
+    end
+
+    return value
+end
+
+local iterator = function( fifo, previous )
+    local i = fifo.head + previous
+    if ( i > fifo.tail ) then
+        return nil
+    end
+
+    return previous + 1, fifo[ i ]
+end
+
+function PSBT_Fifo:Iterator()
+    return iterator, self, 0
+end
+
+mt.__len = PSBT_Fifo.Size
\ No newline at end of file
diff --git a/core/PSBT_Label.lua b/core/PSBT_Label.lua
new file mode 100644
index 0000000..f450858
--- /dev/null
+++ b/core/PSBT_Label.lua
@@ -0,0 +1,97 @@
+PSBT_Label = ZO_Object:Subclass()
+
+local CENTER = CENTER
+local PSBT_SCROLL_DIRECTIONS = PSBT_SCROLL_DIRECTIONS
+local PSBT_ICON_SIDE = PSBT_ICON_SIDE
+
+function PSBT_Label:New( ... )
+    local result = ZO_Object.New( self )
+    result:Initialize( ... )
+    return result
+end
+
+function PSBT_Label:Initialize( objectPool )
+    self.objectPool = objectPool
+    self.control = CreateControlFromVirtual( 'PSBT_Label', self.objectPool.control, 'PSBT_Label', self.objectPool:GetNextControlId() )
+    self.label   = self.control:GetNamedChild( '_Name' )
+    self.icon    = self.control:GetNamedChild( '_Icon' )
+    self.expire  = 0
+    self.moving  = false
+    self.direction = PSBT_SCROLL_DIRECTIONS.UP
+    self.iconPos = PSBT_ICON_SIDE.LEFT
+
+    self.control:SetAlpha( 0.0 )
+end
+
+function PSBT_Label:SetMoving( set )
+    self.moving = set
+end
+
+function PSBT_Label:IsMoving()
+    return self.moving
+end
+
+function PSBT_Label:SetExpire( expire )
+    self.expire = expire
+end
+
+function PSBT_Label:GetExpire()
+    return self.expire
+end
+
+function PSBT_Label:WillExpire( frameTime )
+    return frameTime > self.expire
+end
+
+function PSBT_Label:IsExpired( frameTime )
+    if ( self.expire == -1 ) then
+        return false
+    end
+
+    if ( self.moving ) then
+        return false
+    end
+
+    return frameTime > self.expire
+end
+
+function PSBT_Label:Finalize()
+    self:SetText( '' )
+    self:SetTexture( 0 )
+    self:SetExpire( 0 )
+    self:SetMoving( false )
+end
+
+function PSBT_Label:SetText( text )
+    self.label:SetText( text )
+end
+
+function PSBT_Label:SetIconPosition( side )
+    self.iconPos = side
+
+    if ( side ~= PSBT_ICON_SIDE.NONE ) then
+        local textWidth, textHeight = self.label:GetTextDimensions()
+        self.icon:SetWidth( textHeight )
+        self.icon:SetHeight( textHeight )
+
+        local xpos = 0
+        if ( side == PSBT_ICON_SIDE.LEFT ) then
+            xpos = ( textWidth * -0.5 ) - self.icon:GetWidth()
+        else
+            xpos = ( textWidth * 0.5 ) + self.icon:GetWidth()
+        end
+
+        self.icon:SetAnchor( CENTER, self.control, CENTER, xpos, 0 )
+    else
+        self.icon:SetHidden( true )
+    end
+end
+
+function PSBT_Label:SetTexture( texture )
+    if ( type( texture ) == 'string' and not self.iconPos ~= PSBT_ICON_SIDE.NONE ) then
+        self.icon:SetHidden( false )
+        self.icon:SetTexture( texture )
+    else
+        self.icon:SetHidden( true )
+    end
+end
\ No newline at end of file
diff --git a/core/PSBT_Media.lua b/core/PSBT_Media.lua
new file mode 100644
index 0000000..1cabe7c
--- /dev/null
+++ b/core/PSBT_Media.lua
@@ -0,0 +1,14 @@
+local LMP = LibStub( 'LibMediaProvider-1.0' )
+if ( not LMP ) then return end
+
+LMP:Register( LMP.MediaType.FONT, 'Adventure',   [[psbt/fonts/adventure.ttf]]    )
+LMP:Register( LMP.MediaType.FONT, 'Bazooka',     [[psbt/fonts/baooka.ttf]]       )
+LMP:Register( LMP.MediaType.FONT, 'Cooline',     [[psbt/fonts/cooline.ttf]]      )
+LMP:Register( LMP.MediaType.FONT, 'Diogenes',    [[psbt/fonts/diogenes.ttf]]     )
+LMP:Register( LMP.MediaType.FONT, 'Ginko',       [[psbt/fonts/ginko.ttf]]        )
+LMP:Register( LMP.MediaType.FONT, 'Heroic',      [[psbt/fonts/heroic.ttf]]       )
+LMP:Register( LMP.MediaType.FONT, 'Porky',       [[psbt/fonts/porky.ttf]]        )
+LMP:Register( LMP.MediaType.FONT, 'Talisman',    [[psbt/fonts/talisman.ttf]]     )
+LMP:Register( LMP.MediaType.FONT, 'Transformers',[[psbt/fonts/transformers.ttf]] )
+LMP:Register( LMP.MediaType.FONT, 'Yellowjacket',[[psbt/fonts/yellowjacket.ttf]] )
+
diff --git a/core/PSBT_Module.lua b/core/PSBT_Module.lua
new file mode 100644
index 0000000..20feaa5
--- /dev/null
+++ b/core/PSBT_Module.lua
@@ -0,0 +1,29 @@
+PSBT_Module             = ZO_Object:Subclass()
+
+local CBM = CALLBACK_MANAGER
+
+function PSBT_Module:New( ... )
+    local result = ZO_Object.New( self )
+    result:Initialize( ... )
+    return result
+end
+
+function PSBT_Module:Initialize( root )
+    self._root = root
+end
+
+function PSBT_Module:RegisterForEvent( event, callback )
+    self._root:RegisterForEvent( event, callback )
+end
+
+function PSBT_Module:UnregisterForEvent( event, callback )
+    self._root:UnregisterForEvent( event, callback )
+end
+
+function PSBT_Module:OnUpdate( frametime )
+    -- stub, implement if needed
+end
+
+function PSBT_Module:NewEvent( scrollArea, sticky, icon, text )
+    self._root:NewEvent( scrollArea, sticky, icon, text )
+end
\ No newline at end of file
diff --git a/core/PSBT_Options.lua b/core/PSBT_Options.lua
new file mode 100644
index 0000000..ae33708
--- /dev/null
+++ b/core/PSBT_Options.lua
@@ -0,0 +1,193 @@
+local LAM = LibStub( 'LibAddonMenu-1.0' )
+if ( not LAM ) then return end
+
+local LMP = LibStub( 'LibMediaProvider-1.0' )
+if ( not LMP ) then return end
+
+local PSBT_Module       = PSBT_Module
+local PSBT_Options      = PSBT_Module:Subclass()
+local CBM               = CALLBACK_MANAGER
+
+local PSBT_MODULES      = PSBT_MODULES
+local PSBT_EVENTS       = PSBT_EVENTS
+local PSBT_SETTINGS     = PSBT_SETTINGS
+local PSBT_ICON_SIDE    = PSBT_ICON_SIDE
+
+local decorations = { 'none', 'soft-shadow-thin', 'soft-shadow-thick', 'shadow' }
+local iconside = { PSBT_ICON_SIDE.NONE, PSBT_ICON_SIDE.LEFT, PSBT_ICON_SIDE.RIGHT }
+local direction = { PSBT_SCROLL_DIRECTIONS.UP, PSBT_SCROLL_DIRECTIONS.DOWN }
+
+function PSBT_Options:Initialize( root )
+    PSBT_Module.Initialize( self, root )
+    self:InitialzeControlPanel()
+end
+
+function PSBT_Options:InitialzeControlPanel()
+    self.config_panel = LAM:CreateControlPanel( '_psbt', 'PSBT' )
+    self.config_mode = false
+
+    LAM:AddHeader( self.config_panel, '_psbt_layout', 'Layout' )
+    LAM:AddButton( self.config_panel, '_psbt_editlayout_btn', 'Edit Layout', '',
+        function()
+            CBM:FireCallbacks( PSBT_EVENTS.CONFIG, not self.config_mode )
+            self.config_mode = not self.config_mode
+        end )
+
+    -- normal font
+    local normal_font = LAM:AddHeader( self.config_panel, '_psbt_normal_font_header', 'Normal Font' ):GetNamedChild( 'Label' )
+    normal_font:SetFont( self._root:FormatFont( self._root:GetSetting( PSBT_SETTINGS.normal_font ) ) )
+    LAM:AddDropdown( self.config_panel, '_psbt_normal_font_dd', 'Font:', '', LMP:List( LMP.MediaType.FONT ),
+        function()
+            return self._root:GetSetting( PSBT_SETTINGS.normal_font ).face
+        end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_SETTINGS.normal_font )
+            current.face = selection
+            self._root:SetSetting( PSBT_SETTINGS.normal_font, current )
+            normal_font:SetFont( self._root:FormatFont( current ) )
+        end )
+
+    LAM:AddSlider( self.config_panel, '_psbt_normal_font_slider', 'Size:', '', 5, 50, 1,
+        function()
+            return self._root:GetSetting( PSBT_SETTINGS.normal_font ).size
+        end,
+        function( size )
+            local current = self._root:GetSetting( PSBT_SETTINGS.normal_font )
+            current.size = size
+            self._root:SetSetting( PSBT_SETTINGS.normal_font, current )
+            normal_font:SetFont( self._root:FormatFont( current ) )
+        end )
+
+    LAM:AddDropdown( self.config_panel, '_psbt_normal_font_deco_dd', 'Decoration:', '', decorations,
+        function()
+            return self._root:GetSetting( PSBT_SETTINGS.normal_font ).deco end,
+        function( selection )
+             local current = self._root:GetSetting( PSBT_SETTINGS.normal_font )
+            current.deco = selection
+            self._root:SetSetting( PSBT_SETTINGS.normal_font, current )
+            normal_font:SetFont( self._root:FormatFont( current ) )
+        end )
+
+    -- sticky
+    local sticky_font = LAM:AddHeader( self.config_panel, '_psbt_sticky_font_header', 'Sticky Font' ):GetNamedChild( 'Label' )
+    sticky_font:SetFont( self._root:FormatFont( self._root:GetSetting( PSBT_SETTINGS.sticky_font ) ) )
+    LAM:AddDropdown( self.config_panel, '_psbt_sticky_font_dd', 'Font:', '', LMP:List( LMP.MediaType.FONT ),
+        function()
+            return self._root:GetSetting( PSBT_SETTINGS.sticky_font ).face
+        end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_SETTINGS.sticky_font )
+            current.face = selection
+            self._root:SetSetting( PSBT_SETTINGS.sticky_font, current )
+            sticky_font:SetFont( self._root:FormatFont( current ) )
+        end )
+
+    LAM:AddSlider( self.config_panel, '_psbt_sticky_font_slider', 'Size:', '', 5, 50, 1,
+        function()
+            return self._root:GetSetting( PSBT_SETTINGS.sticky_font ).size
+        end,
+        function( size )
+            local current = self._root:GetSetting( PSBT_SETTINGS.sticky_font )
+            current.size = size
+            self._root:SetSetting( PSBT_SETTINGS.sticky_font, current )
+            sticky_font:SetFont( self._root:FormatFont( current ) )
+        end )
+
+    LAM:AddDropdown( self.config_panel, '_psbt_sticky_font_deco_dd', 'Decoration:', '', decorations,
+        function()
+            return self._root:GetSetting( PSBT_SETTINGS.sticky_font ).deco end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_SETTINGS.sticky_font )
+            current.deco = selection
+            self._root:SetSetting( PSBT_SETTINGS.sticky_font, current )
+            sticky_font:SetFont( self._root:FormatFont( current ) )
+        end )
+
+    -- INCOMMING
+    LAM:AddHeader( self.config_panel, '_psbt_incomming', 'Incomming' )
+    LAM:AddDropdown( self.config_panel, '_psbt_incomming_iconside_dd', 'Icon Side:', '', iconside,
+        function()
+            return self._root:GetSetting( PSBT_AREAS.INCOMING ).icon end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_AREAS.INCOMING )
+            current.icon = selection
+            self._root:SetSetting( PSBT_AREAS.INCOMING, current )
+        end )
+
+    LAM:AddDropdown( self.config_panel, '_psbt_incomming_direction_dd', 'Direction:', '', direction,
+        function()
+            return self._root:GetSetting( PSBT_AREAS.INCOMING ).dir end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_AREAS.INCOMING )
+            current.dir = selection
+            self._root:SetSetting( PSBT_AREAS.INCOMING, current )
+        end )
+
+    -- OUTGOING
+    LAM:AddHeader( self.config_panel, '_psbt_outgoing', 'Outgoing' )
+    LAM:AddDropdown( self.config_panel, '_psbt_outgoing_iconside_dd', 'Icon Side:', '', iconside,
+        function()
+            return self._root:GetSetting( PSBT_AREAS.OUTGOING ).icon end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_AREAS.OUTGOING )
+            current.icon = selection
+            self._root:SetSetting( PSBT_AREAS.OUTGOING, current )
+        end )
+
+
+    LAM:AddDropdown( self.config_panel, '_psbt_outgoing_direction_dd', 'Direction:', '', direction,
+        function()
+            return self._root:GetSetting( PSBT_AREAS.OUTGOING ).dir end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_AREAS.OUTGOING )
+            current.dir = selection
+            self._root:SetSetting( PSBT_AREAS.OUTGOING, current )
+        end )
+
+    -- STATIC
+    LAM:AddHeader( self.config_panel, '_psbt_static', 'Static' )
+    LAM:AddDropdown( self.config_panel, '_psbt_static_iconside_dd', 'Icon Side:', '', iconside,
+        function()
+            return self._root:GetSetting( PSBT_AREAS.STATIC ).icon end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_AREAS.STATIC )
+            current.icon = selection
+            self._root:SetSetting( PSBT_AREAS.STATIC, current )
+        end )
+
+    LAM:AddDropdown( self.config_panel, '_psbt_static_direction_dd', 'Direction:', '', direction,
+        function()
+            return self._root:GetSetting( PSBT_AREAS.STATIC ).dir end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_AREAS.STATIC )
+            current.dir = selection
+            self._root:SetSetting( PSBT_AREAS.STATIC, current )
+        end )
+
+    -- NOTIFICATIONS
+    LAM:AddHeader( self.config_panel, '_psbt_notifications', 'Notifications' )
+    LAM:AddDropdown( self.config_panel, '_psbt_notifications_iconside_dd', 'Icon Side:', '', iconside,
+        function()
+            return self._root:GetSetting( PSBT_AREAS.NOTIFICATION ).icon end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_AREAS.NOTIFICATION )
+            current.icon = selection
+            self._root:SetSetting( PSBT_AREAS.NOTIFICATION, current )
+        end )
+
+    LAM:AddDropdown( self.config_panel, '_psbt_notifications_direction_dd', 'Direction:', '', direction,
+        function()
+            return self._root:GetSetting( PSBT_AREAS.NOTIFICATION ).dir end,
+        function( selection )
+            local current = self._root:GetSetting( PSBT_AREAS.NOTIFICATION )
+            current.dir = selection
+            self._root:SetSetting( PSBT_AREAS.NOTIFICATION, current )
+        end )
+
+
+end
+
+CBM:RegisterCallback( PSBT_EVENTS.LOADED,
+    function( psbt )
+        psbt:RegisterModule( PSBT_MODULES.OPTIONS, PSBT_Options:New( psbt ) )
+    end)
\ No newline at end of file
diff --git a/core/PSBT_ScrollArea.lua b/core/PSBT_ScrollArea.lua
new file mode 100644
index 0000000..fc3f5ed
--- /dev/null
+++ b/core/PSBT_ScrollArea.lua
@@ -0,0 +1,208 @@
+local LibAnim = LibStub( 'LibAnimation-1.0' )
+if ( not LibAnim ) then return end
+
+PSBT_ScrollArea     = ZO_Object:Subclass()
+local CBM           = CALLBACK_MANAGER
+local tinsert       = table.insert
+local tremove       = table.remove
+local NUM_STICKY    = 4
+
+local PSBT_Fifo     = PSBT_Fifo
+local CENTER        = CENTER
+local BOTTOM        = BOTTOM
+local TOP           = TOP
+
+local PSBT_EVENTS   = PSBT_EVENTS
+local PSBT_SCROLL_DIRECTIONS = PSBT_SCROLL_DIRECTIONS
+
+function PSBT_ScrollArea:New( ... )
+    local result = ZO_Object.New( self )
+    result:Initialize( ... )
+    return result
+end
+
+function PSBT_ScrollArea:Initialize( super, areaName, settings )
+    self.name           = areaName
+    self.control        = super:GetNamedChild( areaName )
+    self.background     = self.control:GetNamedChild( '_BG' )
+    self.label          = self.control:GetNamedChild( '_Name' )
+    self._newSticky     = false
+    self._height        = self.control:GetHeight()
+    self._sticky        = PSBT_Fifo.New()
+    self._pendingSticky = PSBT_Fifo.New()
+    self._normal        = {}
+    self._pendingNormal = PSBT_Fifo.New()
+    self._direction     = settings.dir
+    self._iconSide      = settings.icon
+
+    self:Position( settings )
+    self:SetConfigurationMode( false )
+    self.control:SetHandler( 'OnUpdate', function( event, ... ) self:OnUpdate( ... ) end )
+
+    CBM:RegisterCallback( PSBT_EVENTS.CONFIG, function( ... ) self:SetConfigurationMode( ... ) end )
+end
+
+function PSBT_ScrollArea:SetConfigurationMode( enable )
+    self.control:SetMovable( enable )
+    self.control:SetMouseEnabled( enable )
+    self.label:SetHidden( not enable )
+    if ( enable ) then
+        local enter = LibAnim:New( self.background )
+        enter:AlphaTo( 1.0, 500 )
+        enter:Play()
+    else
+        local exit = LibAnim:New( self.background )
+        exit:AlphaTo( 0.0, 500 )
+        exit:Play()
+    end
+end
+
+function PSBT_ScrollArea:Position( settings )
+    self.control:SetAnchor( settings.to, self.control:GetParent(), settings.from, settings.x, settings.y )
+end
+
+function PSBT_ScrollArea:GetAnchorOffsets()
+    local _, point, _, relPoint, offsX, offsY = self.control:GetAnchor( 0 )
+    return { to = point, from = relPoint, x = offsX, y = offsY }
+end
+
+function PSBT_ScrollArea:AnchorChild( label, sticky )
+    local relativeTo = CENTER
+    if ( not sticky ) then
+        if ( self._direction == PSBT_SCROLL_DIRECTIONS.UP ) then
+            relativeTo = BOTTOM
+        elseif ( self._direction == PSBT_SCROLL_DIRECTIONS.DOWN ) then
+            relativeTo = TOP
+        end
+    end
+
+    label.control:SetAnchor( CENTER, self.control, relativeTo, 0, 0 )
+end
+
+function PSBT_ScrollArea:Push( entry, sticky )
+    self:AnchorChild( entry, sticky )
+
+    entry:SetIconPosition( self._iconSide )
+
+    if ( sticky ) then
+        self._pendingSticky:Push( entry )
+    else
+        self._pendingNormal:Push( entry )
+    end
+end
+
+function PSBT_ScrollArea:SetSettings( settings )
+    self._iconSide = settings.icon
+    self._direction = settings.dir
+end
+
+function PSBT_ScrollArea:OnUpdate( frameTime )
+    if ( not self._sticky:Size() and
+         not #self._normal and
+         not self._pendingNormal:Size() and
+         not self._pendingSticky:Size() ) then
+        return
+    end
+
+    while ( self._sticky:Size() > NUM_STICKY ) do
+        local old = self._sticky:Pop()
+        local anim = LibAnim:New( old.control )
+        anim:AlphaTo( 0.0, 200 )
+        anim:Play()
+
+        old:SetMoving( false )
+
+        old:SetExpire( frameTime + 2 )
+        self._newSticky = true
+    end
+
+    repeat
+        local entry = self._sticky:Peek()
+        if ( entry and entry:WillExpire( frameTime + 2 ) ) then
+            local anim = LibAnim:New( entry.control )
+            anim:AlphaTo( 0.0, 200 )
+            anim:TranslateTo( 0, -100, 200 )
+            anim:Play()
+
+            entry:SetMoving( false )
+
+            self._sticky:Pop()
+            self._newSticky = true
+        end
+    until( not entry or not entry:WillExpire( frameTime + 2 )  )
+
+    if ( self._pendingNormal:Size() ) then
+        local newEntry = self._pendingNormal:Pop()
+        if ( newEntry ) then
+            newEntry:SetExpire( frameTime + 5 )
+
+            local anim = LibAnim:New( newEntry.control )
+            anim:AlphaTo( 1.0, 200 )
+            anim:Play()
+
+            tinsert( self._normal, newEntry )
+        end
+    end
+
+    if ( self._pendingSticky:Size() ) then
+        local newEntry = self._pendingSticky:Pop()
+        if ( newEntry ) then
+            newEntry:SetExpire( frameTime + 5 )
+
+            local anim = LibAnim:New( newEntry.control )
+            anim:AlphaTo( 1.0, 200 )
+            anim:Play()
+
+            self._sticky:Push( newEntry )
+            self._newSticky = true
+        end
+    end
+
+    local i = 1
+    while ( i <= #self._normal ) do
+        local entry = self._normal[ i ]
+
+        if ( entry:WillExpire( frameTime + 2 ) ) then
+            local anim = LibAnim:New( entry.control )
+            anim:AlphaTo( 0.0, 200 )
+            anim:Play()
+
+            entry:SetMoving( false )
+
+            tremove( self._normal, i )
+        else
+            if ( not entry:IsMoving() ) then
+                local anim = LibAnim:New( entry.control )
+
+                local targetY = 0
+                if ( self._direction == PSBT_SCROLL_DIRECTIONS.UP ) then
+                    targetY = self._height * -1
+                else
+                    targetY = self._height
+                end
+
+
+                anim:TranslateTo( 0, targetY, 3000 )
+                anim:Play()
+
+                entry:SetMoving( true )
+            end
+            i = i + 1
+        end
+    end
+
+    local top = 0
+    for i, entry in self._sticky:Iterator() do
+        if ( self._newSticky ) then
+            local anim = LibAnim:New( entry.control )
+            anim:TranslateTo( 0, top, 200 )
+            anim:Play()
+
+            entry:SetMoving( true )
+
+            top = top + entry.control:GetHeight()
+        end
+    end
+
+    self._newSticky = false
+end
\ No newline at end of file
diff --git a/core/PSBT_Settings.lua b/core/PSBT_Settings.lua
new file mode 100644
index 0000000..426854b
--- /dev/null
+++ b/core/PSBT_Settings.lua
@@ -0,0 +1,96 @@
+local PSBT_Module   = PSBT_Module
+local PSBT_Settings = PSBT_Module:Subclass()
+local CBM           = CALLBACK_MANAGER
+
+local PSBT_MODULES  = PSBT_MODULES
+local PSBT_EVENTS   = PSBT_EVENTS
+local PSBT_AREAS    = PSBT_AREAS
+local PSBT_ICON_SIDE= PSBT_ICON_SIDE
+local PSBT_SCROLL_DIRECTIONS = PSBT_SCROLL_DIRECTIONS
+
+local ZO_SavedVars  = ZO_SavedVars
+
+local RIGHT = RIGHT
+local LEFT = LEFT
+local CENTER = CENTER
+
+local defaults =
+{
+    normal_font =
+    {
+        face = 'Cooline',
+        size = 14,
+        deco = 'shadow'
+    },
+
+    sticky_font =
+    {
+        face = 'Adventure',
+        size = 18,
+        deco = 'shadow'
+    },
+
+    [ PSBT_AREAS.INCOMING ] =
+    {
+        to      = RIGHT,
+        from    = CENTER,
+        x       = -300,
+        y       = 150,
+        icon    = PSBT_ICON_SIDE.LEFT,
+        dir     = PSBT_SCROLL_DIRECTIONS.UP
+    },
+
+    [ PSBT_AREAS.OUTGOING ] =
+    {
+        to      = LEFT,
+        from    = CENTER,
+        x       = 300,
+        y       = 150,
+        icon    = PSBT_ICON_SIDE.LEFT,
+        dir     = PSBT_SCROLL_DIRECTIONS.DOWN
+    },
+
+    [ PSBT_AREAS.STATIC ] =
+    {
+        to      = CENTER,
+        from    = CENTER,
+        x       = 0,
+        y       = -300,
+        icon    = PSBT_ICON_SIDE.LEFT,
+        dir     = PSBT_SCROLL_DIRECTIONS.DOWN
+    },
+
+    [ PSBT_AREAS.NOTIFICATION ] =
+    {
+        to      = CENTER,
+        from    = CENTER,
+        x       = 0,
+        y       = 450,
+        icon    = PSBT_ICON_SIDE.LEFT,
+        dir     = PSBT_SCROLL_DIRECTIONS.UP
+    }
+}
+
+function PSBT_Settings:Initialize( ... )
+    PSBT_Module.Initialize( self, ... )
+
+    self.db = ZO_SavedVars:New( 'PSBT_DB', 2.5, nil, defaults )
+    self.profile = self.db:GetInterfaceForCharacter( GetDisplayName(), GetUnitName( 'player' ) )
+end
+
+function PSBT_Settings:GetSetting( identity )
+    if ( not self.profile[ identity ] ) then
+        return nil
+    end
+
+    return self.profile[ identity ]
+end
+
+function PSBT_Settings:SetSetting( identity, value )
+    self.profile[ identity ] = value
+end
+
+CBM:RegisterCallback( PSBT_EVENTS.LOADED,
+    function( psbt )
+        psbt:RegisterModule( PSBT_MODULES.SETTINGS, PSBT_Settings:New( psbt ) )
+    end)
\ No newline at end of file
diff --git a/fonts/adventure.ttf b/fonts/adventure.ttf
new file mode 100755
index 0000000..964ab3c
Binary files /dev/null and b/fonts/adventure.ttf differ
diff --git a/fonts/bazooka.ttf b/fonts/bazooka.ttf
new file mode 100755
index 0000000..c57ce67
Binary files /dev/null and b/fonts/bazooka.ttf differ
diff --git a/fonts/cooline.ttf b/fonts/cooline.ttf
new file mode 100755
index 0000000..894749e
Binary files /dev/null and b/fonts/cooline.ttf differ
diff --git a/fonts/diogenes.ttf b/fonts/diogenes.ttf
new file mode 100755
index 0000000..43b0af3
Binary files /dev/null and b/fonts/diogenes.ttf differ
diff --git a/fonts/ginko.ttf b/fonts/ginko.ttf
new file mode 100755
index 0000000..f2cf06b
Binary files /dev/null and b/fonts/ginko.ttf differ
diff --git a/fonts/heroic.ttf b/fonts/heroic.ttf
new file mode 100755
index 0000000..fc2d225
Binary files /dev/null and b/fonts/heroic.ttf differ
diff --git a/fonts/porky.ttf b/fonts/porky.ttf
new file mode 100755
index 0000000..00bb1b6
Binary files /dev/null and b/fonts/porky.ttf differ
diff --git a/fonts/talisman.ttf b/fonts/talisman.ttf
new file mode 100755
index 0000000..214ed3b
Binary files /dev/null and b/fonts/talisman.ttf differ
diff --git a/fonts/transformers.ttf b/fonts/transformers.ttf
new file mode 100755
index 0000000..494f588
Binary files /dev/null and b/fonts/transformers.ttf differ
diff --git a/fonts/yellowjacket.ttf b/fonts/yellowjacket.ttf
new file mode 100755
index 0000000..6aae8e7
Binary files /dev/null and b/fonts/yellowjacket.ttf differ
diff --git a/modules/PSBT_Auras.lua b/modules/PSBT_Auras.lua
new file mode 100644
index 0000000..10daac0
--- /dev/null
+++ b/modules/PSBT_Auras.lua
@@ -0,0 +1,43 @@
+local PSBT_Module           = PSBT_Module
+local PSBT_Auras            = PSBT_Module:Subclass()
+local CBM                   = CALLBACK_MANAGER
+
+local EFFECT_RESULT_FADED   = EFFECT_RESULT_FADED
+local EFFECT_RESULT_GAINED  = EFFECT_RESULT_GAINED
+
+local PSBT_EVENTS           = PSBT_EVENTS
+local PSBT_AREAS            = PSBT_AREAS
+local PSBT_MODULES          = PSBT_MODULES
+
+function PSBT_Auras:Initialize( ... )
+    PSBT_Module.Initialize( self, ... )
+
+    self:RegisterForEvent( EVENT_EFFECT_CHANGED, function( eventCode, ... ) self:OnEffectChanged( ... ) end )
+end
+
+function PSBT_Auras:OnEffectChanged( changeType, effectSlot, effectName, unitTag, beginTime, endTime, stackCount, iconName, buffType, effectType, abilityType, statusEffectType )
+    if ( unitTag ~= 'player' ) then
+        return
+    end
+
+    print( 'OnEffectChanged' )
+
+    if ( changeType == EFFECT_RESULT_FADED ) then
+        self:Remove( effectName, iconName )
+    elseif ( changeType == EFFECT_RESULT_GAINED ) then
+        self:Add( effectName, iconName )
+    end
+end
+
+function PSBT_Auras:Add( name, iconName )
+    self:NewEvent( PSBT_AREAS.NOTIFICATION, true, iconName, name .. ' Gained' )
+end
+
+function PSBT_Auras:Remove( name, iconName )
+    self:NewEvent( PSBT_AREAS.NOTIFICATION, true, iconName, name .. ' Fades' )
+end
+
+CBM:RegisterCallback( PSBT_EVENTS.LOADED,
+    function( psbt )
+        psbt:RegisterModule( PSBT_MODULES.AURAS, PSBT_Auras:New( psbt ) )
+    end)
\ No newline at end of file
diff --git a/modules/PSBT_Combat.lua b/modules/PSBT_Combat.lua
new file mode 100644
index 0000000..484f8b2
--- /dev/null
+++ b/modules/PSBT_Combat.lua
@@ -0,0 +1,373 @@
+local PSBT_Module           = PSBT_Module
+local PSBT_Combat           = PSBT_Module:Subclass()
+PSBT_Combat._iconRegistry   = setmetatable( {}, { __mode = 'kv' } )
+local CBM                   = CALLBACK_MANAGER
+
+local MAX_EVENTS            = 15
+local PlayerName            = GetUnitName( 'player' )
+local PlayerNameRaw         = GetRawUnitName( 'player' )
+local COMBAT_UNIT_TYPE_PLAYER       = COMBAT_UNIT_TYPE_PLAYER
+local COMBAT_UNIT_TYPE_PLAYER_PET   = COMBAT_UNIT_TYPE_PLAYER_PET
+local COMBAT_UNIT_TYPE_NONE         = COMBAT_UNIT_TYPE_NONE
+
+local PSBT_AREAS            = PSBT_AREAS
+local PSBT_EVENTS           = PSBT_EVENTS
+
+local zo_strformat          = zo_strformat
+local GetString             = GetString
+local select                = select
+
+local function IsPlayerType( targetType )
+    return targetType == COMBAT_UNIT_TYPE_PLAYER or
+           targetType == COMBAT_UNIT_TYPE_PLAYER_PET
+end
+
+local function IsPlayer( targetType, targetName )
+    if ( IsPlayerType( targetType ) ) then
+        return true
+    end
+
+    if ( targetType == COMBAT_UNIT_TYPE_NONE ) then
+        return targetName == PlayerName or targetName == PlayerNameRaw
+    end
+
+    return false
+end
+
+local combat_events =
+{
+    [ ACTION_RESULT_ABSORBED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( 'Absorbed <<1>>', abilityName ), area, false
+    end,
+    [ ACTION_RESULT_BLADETURN ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( 'Blocked <<1>>', abilityName ), area, false
+    end,
+    [ ACTION_RESULT_BLOCKED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( 'Blocked <<1>>', abilityName ), area, false
+    end,
+    [ ACTION_RESULT_BLOCKED_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( 'Blocked <<1>>', hitValue ), area, false
+    end,
+    [ ACTION_RESULT_CANT_SEE_TARGET ] = function( ... )
+        return 'Can\'t See Target!', PSBT_AREAS.STATIC, false
+    end,
+    [ ACTION_RESULT_CRITICAL_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( '<<1>>!', hitValue ), area, true
+    end,
+    [ ACTION_RESULT_CRITICAL_HEAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( '+<<1>>!', hitValue ), area, true
+    end,
+    [ ACTION_RESULT_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
+        local area = nil
+        local format = '<<1>>'
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+            format = '-' .. format
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( format, hitValue ), area, false
+    end,
+    [ ACTION_RESULT_DAMAGE_SHIELDED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( 'Shielded <<1>>', hitValue ), area, false
+    end,
+    [ ACTION_RESULT_DEFENDED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
+        local area = nil
+        if ( IsPlayer( targetType ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( 'Defended <<1>>', hitValue ), area, false
+    end,
+    [ ACTION_RESULT_DOT_TICK ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
+        local area = nil
+        local format = '<<1>>'
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+            format = '-' .. format
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( format, hitValue ), area, false
+    end,
+    [ ACTION_RESULT_DOT_TICK_CRITICAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType )
+        local area = nil
+        local format = '<<1>>!'
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+            format = '-' .. format
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( format, hitValue ), area, true
+    end,
+    [ ACTION_RESULT_HEAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( '+<<1>>' , hitValue ), area, false
+    end,
+    [ ACTION_RESULT_HOT_TICK ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( '+<<1>>', hitValue ), area, false
+    end,
+    [ ACTION_RESULT_HOT_TICK_CRITICAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( '+<<1>>!', hitValue ), area, true
+    end,
+    [ ACTION_RESULT_DODGED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( 'Dodged <<1>>', abilityName ), area, false
+    end,
+    [ ACTION_RESULT_MISS ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return 'Miss!', area, false
+    end,
+    [ ACTION_RESULT_PARRIED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( 'Parried <<1>>!', abilityName ), area, false
+    end,
+    [ ACTION_RESULT_RESIST ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( 'Resisted <<1>>!', abilityName ), area, false
+    end,
+    [ ACTION_RESULT_PARTIAL_RESIST ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            area = PSBT_AREAS.OUTGOING
+        end
+
+        return zo_strformat( 'Partially Resisted <<1>>!', abilityName ), nil, false
+    end,
+    [ ACTION_RESULT_FALL_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local area = nil
+        if ( IsPlayer( targetType, targetName ) ) then
+            area = PSBT_AREAS.INCOMING
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            return nil, nil, false
+        end
+
+        return zo_strformat( '-<<1>> falling', hitValue ), area, false
+    end,
+    [ ACTION_RESULT_KILLING_BLOW ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType )
+        if ( IsPlayer( targetType, targetName ) ) then
+            return zo_strformat( '<<1>> Killed You.', sourceName ), PSBT_AREAS.STATIC, true
+        elseif ( IsPlayer( sourceType, sourceName ) ) then
+            return zo_strformat( 'Killing Blow <<1>>!', targetName ), PSBT_AREAS.STATIC, true
+        end
+
+        return nil, nil, false
+    end,
+
+    [ ACTION_RESULT_POWER_DRAIN ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue )
+        local mechanicName = GetString( 'SI_COMBATMECHANICTYPE', mechanicValue )
+        return zo_strformat( '-<<1>> (<<2>>)', hitValue, mechanicName ), PSBT_AREAS.OUTGOING, false
+    end,
+
+    [ ACTION_RESULT_BAD_TARGET ] = function( ... )
+        return 'Bad Target', PSBT_AREAS.STATIC, false
+    end,
+
+    [ ACTION_RESULT_CANNOT_USE ] = function( ... )
+        return 'Cannot Use', PSBT_AREAS.STATIC, false
+    end,
+
+    ---------------------------------------------
+    --[[[ ACTION_RESULT_POWER_ENERGIZE ]            = nil,
+    [ ACTION_RESULT_EFFECT_GAINED_DURATION ]    = nil,
+    [ ACTION_RESULT_EFFECT_GAINED ]             = nil,
+    [ ACTION_RESULT_EFFECT_FADED ]              = nil,
+    [ ACTION_RESULT_DEBUFF ]                    = nil,
+    [ ACTION_RESULT_CASTER_DEAD ]               = nil,
+    [ ACTION_RESULT_COMPLETE ]                  = nil,
+    [ ACTION_RESULT_BUFF ]                      = nil,
+    [ ACTION_RESULT_BUSY ]                      = nil,
+    [ ACTION_RESULT_CANNOT_USE ]                = nil,
+    [ ACTION_RESULT_BEGIN_CHANNEL ]             = nil,
+    [ ACTION_RESULT_BAD_TARGET ]                = nil,
+    [ ACTION_RESULT_ABILITY_ON_COOLDOWN ]       = nil,
+    [ ACTION_RESULT_BEGIN ]                     = nil,
+    [ ACTION_RESULT_POWER_DRAIN ]               = nil,
+    [ ACTION_RESULT_RESURRECT ]                 = nil,
+    [ ACTION_RESULT_DIED ]                      = nil,
+    [ ACTION_RESULT_DIED_XP ]                   = nil,]]
+}
+
+
+function PSBT_Combat:Initialize( ... )
+    PSBT_Module.Initialize( self, ... )
+
+    self._buffer    = ZO_CircularBuffer:New( DEFAULT_MAX_BUFFERED_EVENTS )
+    self._index     = 1
+    self._free      = nil
+
+    for i=1,GetNumAbilities() do
+        local name, icon = GetAbilityInfoByIndex( i )
+        self._iconRegistry[ name ] = icon
+    end
+
+    self:RegisterForEvent( EVENT_COMBAT_EVENT, function( event, ... ) self:OnCombatEvent( ... ) end )
+end
+
+function PSBT_Combat:OnCombatEvent( ... )
+    local result        = select( 1, ... )
+    if ( not combat_events[ result ] ) then
+        return
+    end
+
+    local args = { ... }
+
+    -- did we get hit or do something?
+    if ( IsPlayer( args[ 7 ], args[ 6 ] )
+      or IsPlayer( args[ 9 ], args[ 8 ] ) ) then
+
+        if ( self._free ) then
+            local argCount = #args
+            for i = 1, argCount do
+                self._free[ i ] = args[ i ]
+            end
+
+            for i = #self._free, argCount + 1, -1 do
+                self._free[ i ] = nil
+            end
+
+            self._free = self._buffer:Add( self._free )
+        else
+            self._free = self._buffer:Add( args )
+        end
+
+        self._index = self._index - 1
+    end
+end
+
+function PSBT_Combat:OnUpdate( frameTime )
+    if ( self._index <= 0 ) then
+        self._index = 1
+    end
+
+    local bufferSize = self._buffer:Size()
+    local endPoint = zo_min( self._index + MAX_EVENTS, bufferSize )
+    for i = self._index, endPoint do
+        local entry = self._buffer:At( i )
+        if ( entry ) then
+            self:DispatchEvent( unpack( entry ) )
+        end
+    end
+
+    if ( endPoint >= bufferSize ) then
+        self._buffer:Clear()
+    else
+        self._index = endPoint + 1
+    end
+end
+
+--integer result, bool isError, string abilityName, integer abilityGraphic, integer abilityActionSlotType, string sourceName, integer sourceType, string targetName, integer targetType, integer hitValue, integer powerType, integer damageType, bool log
+function PSBT_Combat:DispatchEvent( result, _, ... )
+    local func = combat_events[ result ]
+    local text, area, crit = func( ... )
+
+    local icon = self._iconRegistry[ select( 1, ... ) ]
+
+    self:NewEvent( area, crit, icon, text )
+end
+
+CBM:RegisterCallback( PSBT_EVENTS.LOADED,
+    function( psbt )
+        psbt:RegisterModule( PSBT_MODULES.COMBAT, PSBT_Combat:New( psbt ) )
+    end)
\ No newline at end of file
diff --git a/modules/PSBT_Cooldowns.lua b/modules/PSBT_Cooldowns.lua
new file mode 100644
index 0000000..363e858
--- /dev/null
+++ b/modules/PSBT_Cooldowns.lua
@@ -0,0 +1,21 @@
+local PSBT_Module           = PSBT_Module
+local PSBT_Cooldowns        = PSBT_Module:Subclass()
+local CBM                   = CALLBACK_MANAGER
+
+local PSBT_EVENTS           = PSBT_EVENTS
+local PSBT_MODULES          = PSBT_MODULES
+
+function PSBT_Cooldowns:Initialize( ... )
+    PSBT_Module.Initialize( self, ... )
+
+    self:RegisterForEvent( EVENT_ABILITY_COOLDOWN_UPDATED, function( abilityId ) self:OnAbilityCooldownUpdated( abilityId ) end )
+end
+
+function PSBT_Cooldowns:OnAbilityCooldownUpdated( abilityId )
+
+end
+
+CBM:RegisterCallback( PSBT_EVENTS.LOADED,
+    function( psbt )
+        psbt:RegisterModule( PSBT_MODULES.COOLDOWNS, PSBT_Cooldowns:New( psbt ) )
+    end )
\ No newline at end of file
diff --git a/modules/PSBT_Experience.lua b/modules/PSBT_Experience.lua
new file mode 100644
index 0000000..91c5315
--- /dev/null
+++ b/modules/PSBT_Experience.lua
@@ -0,0 +1,44 @@
+local PSBT_Module       = PSBT_Module
+local PSBT_Experience   = PSBT_Module:Subclass()
+local CBM               = CALLBACK_MANAGER
+
+local zo_min            = zo_min
+local tostring          = tostring
+
+local PSBT_AREAS        = PSBT_AREAS
+local PSBT_EVENTS       = PSBT_EVENTS
+local PSBT_MODULES      = PSBT_MODULES
+
+function PSBT_Experience:Initialize( ... )
+    PSBT_Module.Initialize( self, ... )
+
+    self._currentExperience = GetUnitXP( 'player' )
+
+    self:RegisterForEvent( EVENT_EXPERIENCE_UPDATE, function( event, ... ) self:OnXPUpdated( ... ) end )
+end
+
+function PSBT_Experience:OnXPUpdated( tag, exp, maxExp, reason  )
+    if ( tag ~= 'player' ) then
+        return
+    end
+
+    local xp = zo_min( exp, maxExp )
+
+    if ( self._currentExperience == xp ) then
+        return
+    end
+
+    local gain = xp - self._currentExperience
+    self._currentExperience = xp
+
+    if ( gain <= 0 ) then
+        return
+    end
+    self:NewEvent( PSBT_AREAS.NOTIFICATION, true, nil, '+' .. tostring( gain ) .. ' XP' )
+end
+
+
+CBM:RegisterCallback( PSBT_EVENTS.LOADED,
+    function( psbt )
+        psbt:RegisterModule( PSBT_MODULES.XP, PSBT_Experience:New( psbt ) )
+    end)
\ No newline at end of file
diff --git a/modules/PSBT_LowSomething.lua b/modules/PSBT_LowSomething.lua
new file mode 100644
index 0000000..da92fbb
--- /dev/null
+++ b/modules/PSBT_LowSomething.lua
@@ -0,0 +1,71 @@
+local PSBT_Module           = PSBT_Module
+local PSBT_LowSomething     = PSBT_Module:Subclass()
+PSBT_LowSomething._pools    = {}
+local CBM                   = CALLBACK_MANAGER
+
+local threshold             = 0.33
+
+local PSBT_AREAS            = PSBT_AREAS
+local PSBT_MODULES          = PSBT_MODULES
+local PSBT_EVENTS           = PSBT_EVENTS
+
+local POWERTYPE_HEALTH      = POWERTYPE_HEALTH
+local POWERTYPE_MAGICKA     = POWERTYPE_MAGICKA
+local POWERTYPE_STAMINA     = POWERTYPE_STAMINA
+local POWERTYPE_MOUNT_STAMINA = POWERTYPE_MOUNT_STAMINA
+
+function PSBT_LowSomething:Initialize( ... )
+    PSBT_Module.Initialize( self, ... )
+
+    self._pools[POWERTYPE_HEALTH]        = 0
+    self._pools[POWERTYPE_MAGICKA]       = 0
+    self._pools[POWERTYPE_STAMINA]       = 0
+    self._pools[POWERTYPE_MOUNT_STAMINA] = 0
+
+    self:RegisterForEvent( EVENT_POWER_UPDATE, function( event, ... ) self:OnPowerUpdate( ... ) end )
+end
+
+function PSBT_LowSomething:OnPowerUpdate( unit, powerPoolIndex, powerType, powerPool, powerPoolMax )
+    if ( unit ~= 'player' ) then
+        return
+    end
+
+    if ( powerPool == 0 ) then
+        return
+    end
+
+    if ( not self._pools[ powerType ] ) then
+        return
+    end
+
+    local newValue = powerPool / powerPoolMax
+
+    if ( self._pools[ powerType ] < threshold
+        or newValue > self._pools[ powerType ]
+        or newValue > threshold ) then
+
+        self._pools[ powerType ] = newValue
+        return
+    end
+
+    self._pools[ powerType ] = newValue
+
+    local string = nil
+    if ( powerType == POWERTYPE_HEALTH ) then
+        string = 'Health Low! (|cF2920C' .. powerPool .. '|r)'
+    elseif ( powerType == POWERTYPE_MAGICKA ) then
+        string = 'Magicka Low! (|cCC0CF2' .. powerPool .. '|r)'
+    elseif ( powerType == POWERTYPE_STAMINA ) then
+        string = 'Stamina Low! (|c0CF2B9' .. powerPool .. '|r)'
+    elseif ( powerType == POWERTYPE_MOUNT_STAMINA ) then
+        string = 'Mount Stamina Low! (|c0CF2B9' .. powerPool .. '|r)'
+    end
+
+    PlaySound( 'Quest_StepFailed' )
+    self:NewEvent( PSBT_AREAS.STATIC, true, nil, string )
+end
+
+CBM:RegisterCallback( PSBT_EVENTS.LOADED,
+    function( psbt )
+        psbt:RegisterModule( PSBT_MODULES.LOW, PSBT_LowSomething:New( psbt ) )
+    end)
\ No newline at end of file