vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
--[[
DamageMeters.lua
See the Readme for author and permissions.


TODO:
 FEATURES
- Change commands to /dm command args
- Help tooltips.
- Test matrix cases (falling in party).
- Per-character settings.
- Individual reports.
- Accumulate self data only option?
- time monitoring, DPS over total combat time.
- How are sessions cleared?
- Sort-by-class or class filter or something.
- Store min/max.

- Sync'ing of events for plugin dmi's.
- Make a second plug-in for testing?
- Document plug-in system somewhat.

- Some way of seeing overheal percentage.
- Better tracking of unit health.  Maybe actually query target for health?  This seems
like it would either be a net hit or no more accurate than the messages.

- Automatically use CTRA channel if available.


 KNOWN BUGS
- Sometime tooltip doesn't show for the first bar.  Making window big and small seems to fix it.
]]--

-------------------------------------------------------------------------------

-- Plugin stuff --
DamageMeters_PLUGINS = {};

-- CONSTANTS ---
DamageMeters_BARCOUNT_MAX = 40; -- Note this is also the table max size atm.
DamageMeters_DEFAULTBARWIDTH = 119;
DamageMeters_BARHEIGHT = 12;
DamageMeters_PULSE_TIME = 1.00;
-- This number represents the version number where the saved data format
-- was last changed.  Every time the data stored in the tables are changed
-- this number needs to be changed so the mod knows not to load in obsolete
-- data.
DamageMeters_VERSION = 5110;
DamageMeters_VERSIONSTRING = "5.1.1";
DamageMeters_SYNCMSGCOLOR = { r = 0.35, g = 1.00, b = 0.75 };
DamageMeters_RPSCOLOR = { r = 0.00, g = 1.00, b = 1.00 };
DamageMeters_SYNCEMOTECOLOR = { r = 1.00, g = 0.60, b = 0.00 };
DM_COMBAT_TIMEOUT_SECONDS = 5.0;

DamageMeters_Sort_DECREASING = 1;
DamageMeters_Sort_INCREASING = 2;
DamageMeters_Sort_ALPHABETICAL = 3;
DamageMeters_Sort_MAX = 3;

DamageMeters_Text_RANK = 1;
DamageMeters_Text_NAME = 2;
DamageMeters_Text_TOTALPERCENTAGE = 3;
DamageMeters_Text_LEADERPERCENTAGE = 4;
DamageMeters_Text_VALUE = 5;
DamageMeters_Text_DELTA = 6;
DamageMeters_Text_MAX = 6;

DamageMeters_EventData_NONE = 1;
DamageMeters_EventData_SELF = 2;
DamageMeters_EventData_ALL = 3;

DamageMeters_colorScheme_RELATIONSHIP = 1;
DamageMeters_colorScheme_CLASS = 2;
DamageMeters_colorScheme_MAX = 2;

DM_Pause_Not = 0;
DM_Pause_Paused = 1;
DM_Pause_Ready = 2;

DMSYNC_PREFIX = "SYNC_12_";
DMSYNC_EVENT_PREFIX = "SYNCE_12_";
DamageMeters_SYNCREQUEST = "REQ_SYNC_12_";
DamageMeters_SYNCSTART = "SYNC_START_12_";
DamageMeters_SYNCCLEARREQUEST = "REQ_CLEAR_12_";
DamageMeters_SYNCCLEARACK = "REQ_CLEARACK_12_";
DamageMeters_SYNCPAUSEREQ = "SYNC_PAUSE_12_";
DamageMeters_SYNCUNPAUSEREQ = "SYNC_UNPAUSE_12_";
DamageMeters_SYNCREADYREQ = "SYNC_READY_12_";
DamageMeters_SYNCEND = "SYNC_END_12_";
--DamageMeters_SYNCBOSSSTART = "SYNC_BOSS_12_"
--DamageMeters_SYNCBOSSEND = "SYNC_BOSSEND_12_"

DamageMeters_SYNC_ID = "DamageMeters";

-- These are maintenance or informational functions, can be on a different version safely with 
-- the real sync functions.
DamageMeters_SYNCMSG = "SYNC_MSG_6_";
DamageMeters_SYNCPING = "SYNC_PING_6_"
DamageMeters_SYNCPINGREQ = "SYNC_PINGREQ_6_";
DamageMeters_SYNCEMOTE = "SYNC_EMOTE_6_";
--DamageMeters_SYNCKICK = "SYNC_KICK_6_";
DamageMeters_SYNCJOINED = "SYNC_JOINED_7_";
DamageMeters_SYNCSESSIONMISMATCH = "SYNC_SESSIONMISMATCH_6_";
DamageMeters_SYNCHALT = "SYNC_HALT_6_";
DamageMeters_SYNCRPS = "SYNC_RPS_1_";
DamageMeters_SYNCRPSRESPONSE = "SYNC_RPSR_1_";
DamageMeters_SYNCRPSCOUNTERRESPONSE = "SYNC_RPSCR_1_";

DamageMeters_MINSYNCCOOLDOWN = 1.0;
DamageMeters_SYNCMSGSENDDELAY = 0.25; -- seconds.  increased from 0.05.  again from 0.1 (dc'ed a few people).
DamageMeters_SYNCMSGPROCESSDELAY = 0.010; -- seconds. increased from 0.005 (slower processing should cause less fps lag)
-- 1 / (frame/sec * sec/msg) = msg /frame
-- 1 / (5 * 0.15) = 1.333, approx = 1;
DamageMeters_MAXSYNCMSGPERFRAME = 1;

-- Default quantity color text codes (ex. "cFF..."). Construct at runtime.
DamageMeters_quantityColorCodeDefault = {};

DamageMeters_TEXTSTATEDURATION = 6.0;
DamageMeters_BARFADEINMINTIME = 0.5;
DamageMeters_BARFADEINTIME = 0.01;
DamageMeters_BARCHARTIME = 0.02;
DamageMeters_QUANTITYSHOWDURATION = 6.0;

DMVIEW_NORMAL = 1;
DMVIEW_MAX = 2;
DMVIEW_MIN = 3;

DMPROF_PARSEMESSAGE = 1;
DMPROF_ADDVALUE = 2;
DMPROF_UPDATE = 3;
DMPROF_BARS = 4;
DMPROF_SORT = 5;
DMPROF_COUNT = 5;

DMPROF_NAMES = {
        "Parse",
        "AddValue",
        "Update",
        "Bars",
        "Sort",
};

DamageMeters_watchedEventsTable = {
        "PARTY_MEMBERS_CHANGED",
        "PLAYER_REGEN_ENABLED",
        "PLAYER_REGEN_DISABLED",
        --"CHAT_MSG_CHANNEL_NOTICE",

        -- Messages to measure how much damage is dealt.
        "CHAT_MSG_COMBAT_SELF_HITS",    -- Melee you do on things.
        "CHAT_MSG_COMBAT_PET_HITS",             -- Melee your pets do.
        "CHAT_MSG_COMBAT_PARTY_HITS",   -- Melee done by part.
        "CHAT_MSG_COMBAT_FRIENDLYPLAYER_HITS",  -- Melee done by friendlies.
        "CHAT_MSG_SPELL_SELF_DAMAGE",   -- Your spells that damage other things.
        "CHAT_MSG_SPELL_PET_DAMAGE",
        "CHAT_MSG_SPELL_PARTY_DAMAGE",  -- Party member's spell hits.
        "CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE", -- Spells other people cast on things.
        "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE", -- Blah suffers # Arcane damage from #'s/your Spell.  Works on self, party, friendly.
        "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE",
        "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF",         -- Thorns on self.
        "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS",       -- Thorns on others.

        -- Messages to measure healing done and received.
        "CHAT_MSG_SPELL_SELF_BUFF",
        "CHAT_MSG_SPELL_PARTY_BUFF",
        "CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF",
        "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS",
        "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS",
        "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS",
        "CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF",
        "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS",

        -- Messages to measure damage taken.
        "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS",
        --"CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES",
        "CHAT_MSG_COMBAT_CREATURE_VS_PARTY_HITS",
        --"CHAT_MSG_COMBAT_CREATURE_VS_PARTY_MISSES",
        "CHAT_MSG_COMBAT_CREATURE_VS_CREATURE_HITS",
        --"CHAT_MSG_COMBAT_CREATURE_VS_CREATURE_MISSES",
        "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE",
        "CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE",
        "CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE",
        "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE",
        "CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE",
        "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE",
        -- The HOSTILEPLAYER ones are for dueling and pvp.
        "CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS",
        "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE",
        "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE",

        -- For sync stuff.
        --"CHAT_MSG_CHANNEL",
        --"CHAT_MSG_RAID",
        --"CHAT_MSG_PARTY",
        "CHAT_MSG_ADDON",

        -- For overhealing.
        "UNIT_HEALTH",
        "UNIT_MAXHEALTH",
};

-- GLOBALS ---
DamageMeters_bars = {};
DamageMeters_text = {};
--DamageMeters_table = {}; -- player, hitCount, critCount, lastTime, relationship, class, damageThisCombat, firstMsg
DamageMeters_tables = {};
DamageMeters_tableInfo = {}; -- sessionLabel, sessionIndex
DamageMeters_tableSortHash = {}; -- DamageMeters_tableSortHash[quantity][rank] = index in DamageMeters_tables[DMT_VISIBLE]
DMT_ACTIVE = DM_TABLE_A;
DMT_SAVED = DM_TABLE_B;
DMT_VISIBLE = DM_TABLE_A;
DamageMeters_bannedTable = {};
DamageMeters_tooltipBarIndex = nil;
DamageMeters_frameNeedsToBeGenerated = true;
DamageMeters_clickedBarIndex = nil;
DamageMeters_lastSyncTime = 0;
DamageMeters_listLocked = false;
DamageMeters_startCombatOnNextValue = true;
DamageMeters_inCombat = false;  -- This doesn't mean the player is in combat, but rather that someone we're monitoring appears to be.
DamageMeters_playerInCombat = false; -- This means we are in combat or not.
DamageMeters_combatStartTime = 0;
DamageMeters_combatEndTime = 0;
DamageMeters_reportBuffer = "";
DamageMeters_textStateStartTime = 0;
DamageMeters_currentQuantStartTime = -1;
DamageMeters_firstGeneration = true;
DamageMeters_lastEvent = {};
DamageMeters_missedMessages = {};
--DamageMeters_requestSyncWhenReportDone = false;
DamageMeters_lastUpdateTime = -1;
DamageMeters_lastSyncMsgTime = 0;
DamageMeters_lastSyncIncMsgTime = 0;
DamageMeters_syncMsgQueue = {};
DamageMeters_syncIncMsgQueue = {};
DamageMeters_syncIncMsgSourceQueue = {};
DamageMeters_debug_syncTestFactor = 1;
DamageMeters_syncStartTime = -1;
DamageMeters_sendMsgQueueBar = nil
DamageMeters_sendMsgQueueBarText = nil;
DamageMeters_processMsgQueueBar = nil;
DamageMeters_processMsgQueueBarText = nil;
DamageMeters_totals = {0,0,0,0,0,0};
DamageMeters_lastProcessQueueTime = -1;
DamageMeters_syncEvents = false;
DamageMeters_waitingForChainHeal = false;
DamageMeters_queuedChainHealCount = 0;
DamageMeters_queuedChainHealValue = {};
DamageMeters_queuedChainHealCrit = {};
DamageMeters_lastEventTime = nil;
DamageMeters_lastPlayerPosition = -1;
DamageMeters_barStartIndex = -1;
DamageMeters_debugTimers = {};
DamageMeters_lastDebugTime = -1;
DamageMeters_tablesDirty = true;
DamageMeters_lastBarUpdateTime = 0;
DamageMeters_activeDebugTimer = 0;
DamageMeters_currentlyInParty = false;
DamageMeters_pluginDMITable = {};

-- SETTINGS ---
DamageMeters_barCount = 10;
DamageMeters_quantity = DamageMeters_Quantity_DAMAGE;
DamageMeters_sort = DamageMeters_Sort_DECREASING;
DamageMeters_textOptions = {false, true, false, false};
DamageMeters_colorScheme = DamageMeters_colorScheme_CLASS;
DamageMeters_autoCountLimit = 0;
--DamageMeters_syncChannel = "";
DamageMeters_loadedDataVersion = 0;
DamageMeters_pauseState = DM_Pause_Not;
DamageMeters_quantityColor = {};
DamageMeters_contributorList = {}; 
DamageMeters_eventDataLevel = DamageMeters_EventData_SELF;
DamageMeters_textState = 0;
DamageMeters_savedBarCount = 1;
DamageMeters_syncEventDataLevel = DamageMeters_EventData_ALL;
DamageMeters_quantitiesFilter = {};
DamageMeters_viewMode = DMVIEW_NORMAL;
DMTIMERMODE = 0;
DamageMeters_debugEnabled = false;      -- Debug: Enables display of various debug messages.
DamageMeters_BARWIDTH = DamageMeters_DEFAULTBARWIDTH;

-- Flags.  These are stored in a table to reduce the overall number of variables saved.
-- The .toc reader seems to only be able to handle lines that are under 1024 characters.  
-- (Rather, it ignores text after that cutoff.)
DamageMeters_flags = {};
DMFLAG_showFightAsPS = 1;
DMFLAG_justifyTextLeft = 2;
DMFLAG_applyFilterToAutoCycle = 3;
DMFLAG_applyFilterToManualCycle = 4;
DMFLAG_playerAlwaysVisible = 5;
DMFLAG_groupDPSMode = 6;
DMFLAG_showEventTooltipsFirst = 7;
--DMFLAG_onlySyncWithGroup = 8;
--DMFLAG_permitAutoSyncChanJoin = 9;
DMFLAG_enableDMM = 10;
DMFLAG_visibleOnlyInParty = 11;
--DMFLAG_autoClearOnChannelJoin = 12;
DMFLAG_positionLocked = 13;
DMFLAG_isVisible = 14;
DMFLAG_groupMembersOnly = 15;
DMFLAG_addPetToPlayer = 16;
DMFLAG_showTotal = 17;
DMFLAG_resizeLeft = 18;
DMFLAG_resizeUp = 19;
DMFLAG_haveContributors = 20; -- For some reason this list can be non-empty but getn returns zero: thus this variable was added.
DMFLAG_cycleVisibleQuantity = 21;
DMFLAG_accumulateToMemory = 22;
DMFLAG_constantVisualUpdate = 23;
DMFLAG_resetWhenCombatStarts = 24;
DMFLAG_clearWhenJoinParty = 25;
DMFLAG_autoSync = 26;

function DamageMeters_SetDefaultOptions()
        DamageMeters_barCount = 10;
        DamageMeters_quantity = DamageMeters_Quantity_DAMAGE;
        DamageMeters_sort = DamageMeters_Sort_DECREASING;
        DamageMeters_textOptions = {false, true, false, false};
        DamageMeters_colorScheme = DamageMeters_colorScheme_CLASS;
        DamageMeters_autoCountLimit = 0;
        --DamageMeters_syncChannel = "";
        DamageMeters_debugEnabled = false;      -- Debug: Enables display of various debug messages.
        DamageMeters_loadedDataVersion = 0;
        DamageMeters_pauseState = DM_Pause_Not;
        DamageMeters_quantityColor = {};
        DamageMeters_contributorList = {}; 
        DamageMeters_eventDataLevel = DamageMeters_EventData_SELF;
        DamageMeters_textState = 0;
        DamageMeters_savedBarCount = 1;
        DamageMeters_syncEventDataLevel = DamageMeters_EventData_ALL;
        DamageMeters_quantitiesFilter = {};
        DamageMeters_viewMode = DMVIEW_NORMAL;
        DMTIMERMODE = 0;
        DamageMeters_BARWIDTH = DamageMeters_DEFAULTBARWIDTH;
        
        -- Flags
        DamageMeters_flags = {};
        DamageMeters_flags[DMFLAG_showFightAsPS] = true;
        DamageMeters_flags[DMFLAG_justifyTextLeft] = false;
        DamageMeters_flags[DMFLAG_applyFilterToAutoCycle] = true;
        DamageMeters_flags[DMFLAG_applyFilterToManualCycle] = false;
        DamageMeters_flags[DMFLAG_playerAlwaysVisible] = false;
        DamageMeters_flags[DMFLAG_groupDPSMode] = true;
        DamageMeters_flags[DMFLAG_showEventTooltipsFirst] = false;
        --DamageMeters_flags[DMFLAG_onlySyncWithGroup] = true;
        --DamageMeters_flags[DMFLAG_permitAutoSyncChanJoin] = true;
        DamageMeters_flags[DMFLAG_enableDMM] = true;
        DamageMeters_flags[DMFLAG_visibleOnlyInParty] = false;
        --DamageMeters_flags[DMFLAG_autoClearOnChannelJoin] = true;
        DamageMeters_flags[DMFLAG_positionLocked] = false;
        DamageMeters_flags[DMFLAG_isVisible] = true;
        DamageMeters_flags[DMFLAG_groupMembersOnly] = true;
        DamageMeters_flags[DMFLAG_addPetToPlayer] = false;
        DamageMeters_flags[DMFLAG_showTotal] = false;
        DamageMeters_flags[DMFLAG_resizeLeft] = true;
        DamageMeters_flags[DMFLAG_resizeUp] = true;
        DamageMeters_flags[DMFLAG_haveContributors] = false;
        DamageMeters_flags[DMFLAG_cycleVisibleQuantity] = false;
        DamageMeters_flags[DMFLAG_accumulateToMemory] = false;
        DamageMeters_flags[DMFLAG_constantVisualUpdate] = false;
        DamageMeters_flags[DMFLAG_resetWhenCombatStarts] = false;
        DamageMeters_flags[DMFLAG_clearWhenJoinParty] = true;
        DamageMeters_flags[DMFLAG_autoSync] = false;

        -- Init cyclable quantities.
        DamageMeters_quantitiesFilter = {};
        DamageMeters_quantitiesFilter[DamageMeters_Quantity_DAMAGE] = true;
        DamageMeters_quantitiesFilter[DamageMeters_Quantity_HEALING] = true;
        DamageMeters_quantitiesFilter[DamageMeters_Quantity_DAMAGED] = true;
        DamageMeters_quantitiesFilter[DamageMeters_Quantity_HEALINGRECEIVED] = true;
        DamageMeters_quantitiesFilter[DamageMeters_Quantity_DPS] = true;
        DamageMeters_quantitiesFilter[DamageMeters_Quantity_HPS] = false;
        DamageMeters_quantitiesFilter[DamageMeters_Quantity_DTPS] = false;
        DamageMeters_quantitiesFilter[DamageMeters_Quantity_HTPS] = false;
        DamageMeters_quantitiesFilter[DamageMeters_Quantity_TIME] = true;
        
        --Debug
        DamageMeters_ResetDebug();
        
        -- Initialize color table.
        DamageMeters_SetDefaultColors();

        DamageMeters_InitTables();
end

function DamageMeters_InitTables()
        -- Init the tables.
        local tableIndex;
        for tableIndex = 1, DMT_MAX do
                DamageMeters_tables[tableIndex] = {};
                DamageMeters_tableInfo[tableIndex] = {};
                DamageMeters_tableInfo[tableIndex].sessionLabel = "Default";
                DamageMeters_tableInfo[tableIndex].sessionIndex = 1;
        end
end

-- DEBUG --
DamageMeters_msgCounts = {};

-- NOTE: Whenever you add/remove a new variable, increase the number in this
-- tables name so that it gets reset.
DamageMeters_debug4 = {};
function DamageMeters_ResetDebug()
        DamageMeters_debug4 = {};
        -- Shows all messages.
        DamageMeters_debug4.showAll = false;
        DamageMeters_debug4.showParse = false;
        -- When true, allows you to parse your own sync messages.
        DamageMeters_debug4.syncSelf = false;
        DamageMeters_debug4.syncSelfTestMode = false; -- Adds "x" to the end of player names for self-sync testing.
        DamageMeters_debug4.showValueChanges = false;
        -- When true, each incoming message becomes instead a 1 point of damage message 
        -- caused by a player by the name of the message.
        DamageMeters_debug4.msgWatchMode = false;
        DamageMeters_debug4.showSyncChanges = false;
        DamageMeters_debug4.showSyncQueueInfo = false;
        DamageMeters_debug4.showHealthChanges = false;
        DamageMeters_debug4.showGCInfo = false;
end


-------------------------------------------------------------------------------

function DMPrint(msg, color, bSecondChatWindow)
        local r = 0.50;
        local g = 0.50;
        local b = 1.00;

        if (color) then
                r = color.r;
                g = color.g;
                b = color.b;
        end

        local frame = DEFAULT_CHAT_FRAME;
        if (bSecondChatWindow) then
                frame = ChatFrame2;
        end

        if (frame) then
                frame:AddMessage(msg,r,g,b);
        end
end

function DM_DUMP()
        local table = DamageMeters_tables[DMT_ACTIVE];
        DM_DUMP_RECURSIVE(table, "[root]", "");
end

-- stolen from sky (assertEquals)
function DMASSERTEQUALS(expected, actual)
        if  actual ~= expected  then
                local function wrapValue( v )
                        if type(v) == 'string' then return "'"..v.."'" end
                        return tostring(v)
                end
                errorMsg = "expected: "..wrapValue(expected)..", actual: "..wrapValue(actual)
                DMPrintD( errorMsg, 2 )
        end
end
function DMASSERTNOTEQUALS(expected, actual)
        if  actual == expected  then
                local function wrapValue( v )
                        if type(v) == 'string' then return "'"..v.."'" end
                        return tostring(v)
                end
                errorMsg = "not expected: "..wrapValue(expected)..", actual: "..wrapValue(actual)
  DMPrintD( errorMsg, 2 )
        end
end

function DMPrintD(msg, color, bSecondChatWindow)
        if (DamageMeters_debugEnabled) then
                --SendChatMessage(msg, "CHANNEL", nil, GetChannelName("dmdebug"));
                DMPrint(msg, color, bSecondChatWindow);
        end
end

function DMVerbose(msg)
        --DMPrint(msg);
end

-------------------------------------------------------------------------------

function DamageMetersFrame_OnLoad()
        -- Initialize debug timers.
        for timer = 1, DMPROF_COUNT do
                DamageMeters_debugTimers[timer] = {};
                DamageMeters_debugTimers[timer].time = 0;
                DamageMeters_debugTimers[timer].count = 0;
                DamageMeters_debugTimers[timer].peak = 0;
        end

        -- Define DamageMeters_Quantity_MAX.
        -- ALL QUANTITIES MUST BE DEFINED BEFORE THIS POINT.
        DamageMeters_Quantity_MAX = table.getn(DM_QUANTDEFS);
        DMPrintD("DamageMeters_Quantity_MAX = "..DamageMeters_Quantity_MAX);

        -- Inititalize quantity color codes.
        for quant = 1, DamageMeters_Quantity_MAX do
                local color = DM_QUANTDEFS[quant].defaultColor;
                local code = string.format("|cFF%02X%02X%02X",
                        floor(color[1] * 255.0),
                        floor(color[2] * 255.0),
                        floor(color[3] * 255.0));
                DamageMeters_quantityColorCodeDefault[quant] = code;
        end

        -- Set default options.  Variable loading from SavedVars happens after OnLoad.
        DamageMeters_SetDefaultOptions();

        if (not DamageMetersFrame:IsUserPlaced()) then
                DMPrintD("Not user placed: resetting pos.");
                DamageMeters_ResetPos();
        end

        -- Build arrays for easy reference of Bars and BarText, and initialize those elements.
        local name = this:GetName();
        local i;
        for i = 1,DamageMeters_BARCOUNT_MAX do
                DamageMeters_bars[i] = getglobal(name.."Bar"..i);
                DamageMeters_bars[i]:SetID(i);
                DamageMeters_bars[i]:Hide();
                DamageMeters_text[i] = getglobal(name.."Text"..i);
                SetTextStatusBarText(DamageMeters_bars[i], DamageMeters_text[i]);
                -- Force text on always.
                ShowTextStatusBarText(DamageMeters_bars[i]);
        end

        -- Initialize the sync bars.
        DamageMeters_sendMsgQueueBar = getglobal("DamageMetersFrame_SendMsgQueueBar");
        DamageMeters_sendMsgQueueBarText = getglobal("DamageMetersFrame_SendMsgQueueBarText");
        SetTextStatusBarText(DamageMeters_sendMsgQueueBar, DamageMeters_sendMsgQueueBarText);
        DamageMeters_sendMsgQueueBar:SetMinMaxValues(0, 100);
        DamageMeters_sendMsgQueueBar:SetValue(100);
        DamageMeters_sendMsgQueueBar:SetStatusBarColor(1.00, 0.0, 0.00);
        -- Force text on always.
        ShowTextStatusBarText(DamageMeters_sendMsgQueueBar);
        DamageMeters_sendMsgQueueBarText:SetPoint("CENTER", DamageMeters_sendMsgQueueBar:GetName(), "CENTER", 0, 0);
        DamageMeters_sendMsgQueueBarText:SetText("");
        DamageMeters_sendMsgQueueBar:Hide();

        DamageMeters_processMsgQueueBar = getglobal("DamageMetersFrame_ProcessMsgQueueBar");
        DamageMeters_processMsgQueueBarText = getglobal("DamageMetersFrame_ProcessMsgQueueBarText");
        SetTextStatusBarText(DamageMeters_processMsgQueueBar, DamageMeters_processMsgQueueBarText);
        DamageMeters_processMsgQueueBar:SetMinMaxValues(0, 250);
        DamageMeters_processMsgQueueBar:SetValue(0);
        DamageMeters_processMsgQueueBar:SetStatusBarColor(1.00, 0.0, 0.00);
        -- Force text on always.
        ShowTextStatusBarText(DamageMeters_processMsgQueueBar);
        DamageMeters_processMsgQueueBarText:SetPoint("CENTER", DamageMeters_processMsgQueueBar:GetName(), "CENTER", 0, 0);
        DamageMeters_processMsgQueueBarText:SetText("");
        DamageMeters_processMsgQueueBar:Hide();

        this:RegisterEvent("VARIABLES_LOADED");
        -- Register these events, which will then cause us to register/unregister the rest.
        this:RegisterEvent("PLAYER_ENTERING_WORLD");
        this:RegisterEvent("PLAYER_LEAVING_WORLD");
        
        -- Console commands.
        SlashCmdList["DAMAGEMETERSHELP"] = DamageMeters_Help;
        SLASH_DAMAGEMETERSHELP1 = "/dmhelp";
        SlashCmdList["DAMAGEMETERSCMD"] = DamageMeters_ListCommands;
        SLASH_DAMAGEMETERSCMD1 = "/dmcmd";
        SlashCmdList["DAMAGEMETERSSHOW"] = DamageMeters_ToggleShow;
        SLASH_DAMAGEMETERSSHOW1 = "/dmshow";
        SlashCmdList["DAMAGEMETERSHIDE"] = DamageMeters_Hide;
        SLASH_DAMAGEMETERSHIDE1 = "/dmhide";
        SlashCmdList["DAMAGEMETERSCLEAR"] = DamageMeters_Clear;
        SLASH_DAMAGEMETERSCLEAR1 = "/dmclear";
        SlashCmdList["DAMAGEMETERSREPORT"] = DamageMeters_Report;
        SLASH_DAMAGEMETERSREPORT1 = "/dmreport";
        SlashCmdList["DAMAGEMETERSSORT"] = DamageMeters_SetSort;
        SLASH_DAMAGEMETERSSORT1 = "/dmsort";
        SlashCmdList["DAMAGEMETERSCOUNT"] = DamageMeters_SetCount;
        SLASH_DAMAGEMETERSCOUNT1 = "/dmcount";
        SlashCmdList["DAMAGEMETERSSAVE"] = DamageMeters_Save;
        SLASH_DAMAGEMETERSSAVE1 = "/dmsave";
        SlashCmdList["DAMAGEMETERSRESTORE"] = DamageMeters_Restore;
        SLASH_DAMAGEMETERSRESTORE1 = "/dmrestore";
        SlashCmdList["DAMAGEMETERSMERGE"] = DamageMeters_Merge;
        SLASH_DAMAGEMETERSMERGE1 = "/dmmerge";
        SlashCmdList["DAMAGEMETERSSWAP"] = DamageMeters_Swap;
        SLASH_DAMAGEMETERSSWAP1 = "/dmswap";
        SlashCmdList["DAMAGEMETERSMEMCLEAR"] = DamageMeters_MemClear;
        SLASH_DAMAGEMETERSMEMCLEAR1 = "/dmmemclear";
        SlashCmdList["DAMAGEMETERSRESETPOS"] = DamageMeters_ResetPos;
        SLASH_DAMAGEMETERSRESETPOS1 = "/dmresetpos";
        SlashCmdList["DAMAGEMETERSSHOWTEXT"] = DamageMeters_SetTextOptions;
        SLASH_DAMAGEMETERSSHOWTEXT1 = "/dmtext";
        SlashCmdList["DAMAGEMETERSCOLORSCHEME"] = DamageMeters_SetColorScheme;
        SLASH_DAMAGEMETERSCOLORSCHEME1 = "/dmcolor";
        SlashCmdList["DAMAGEMETERSQUANTITY"] = DamageMeters_SetQuantity;
        SLASH_DAMAGEMETERSQUANTITY1 = "/dmquant";
        SlashCmdList["DAMAGEMETERSVISINPARTY"] = DamageMeters_SetVisibleInParty;
        SLASH_DAMAGEMETERSVISINPARTY1 = "/dmvisinparty";
        SlashCmdList["DAMAGEMETERSAUTOCOUNT"] = DamageMeters_SetAutoCount;
        SLASH_DAMAGEMETERSAUTOCOUNT1 = "/dmautocount";
        SlashCmdList["DAMAGEMETERSLISTBANNED"] = DamageMeters_ListBanned;
        SLASH_DAMAGEMETERSLISTBANNED1 = "/dmlistbanned";
        SlashCmdList["DAMAGEMETERSCLEARBANNED"] = DamageMeters_ClearBanned;
        SLASH_DAMAGEMETERSCLEARBANNED1 = "/dmclearbanned";
        SlashCmdList["DAMAGEMETERSSYNC"] = DamageMeters_Sync;
        SLASH_DAMAGEMETERSSYNC1 = "/dmsync";
        --SlashCmdList["DAMAGEMETERSSYNCCHAN"] = DamageMeters_SyncChan;
        --SLASH_DAMAGEMETERSSYNCCHAN1 = "/dmsyncchan";
        --SlashCmdList["DAMAGEMETERSSYNCLEAVE"] = DamageMeters_SyncLeaveChanCmd;
        --SLASH_DAMAGEMETERSSYNCLEAVE1 = "/dmsyncleave";
        SlashCmdList["DAMAGEMETERSSYNCSEND"] = DamageMeters_SyncReport;
        SLASH_DAMAGEMETERSSYNCSEND1 = "/dmsyncsend";
        SlashCmdList["DAMAGEMETERSSYNCREQUEST"] = DamageMeters_SyncRequest;
        SLASH_DAMAGEMETERSSYNCREQUEST1 = "/dmsyncrequest";
        SlashCmdList["DAMAGEMETERSSYNCCLEAR"] = DamageMeters_SyncClear;
        SLASH_DAMAGEMETERSSYNCCLEAR1 = "/dmsyncclear";
        SlashCmdList["DAMAGEMETERSSYNCMSG"] = DamageMeters_SendSyncMsg;
        SLASH_DAMAGEMETERSSYNCMSG1 = "/dmsyncmsg";
        SLASH_DAMAGEMETERSSYNCMSG2 = "/dmm";
        --[[
        SlashCmdList["DAMAGEMETERSSYNCBROADCASTCHAN"] = DamageMeters_SyncBroadcastChan;
        SLASH_DAMAGEMETERSSYNCBROADCASTCHAN1 = "/dmsyncbroadcastchan";
        SLASH_DAMAGEMETERSSYNCBROADCASTCHAN2 = "/dmsyncb";
        ]]--
        SlashCmdList["DAMAGEMETERSSYNCPING"] = DamageMeters_SyncPingRequest;
        SLASH_DAMAGEMETERSSYNCPING1 = "/dmsyncping";
        SlashCmdList["DAMAGEMETERSSYNCPAUSE"] = DamageMeters_SyncPause;
        SLASH_DAMAGEMETERSSYNCPAUSE1 = "/dmsyncpause";
        SlashCmdList["DAMAGEMETERSSYNCUNPAUSE"] = DamageMeters_SyncUnpause;
        SLASH_DAMAGEMETERSSYNCUNPAUSE1 = "/dmsyncunpause";
        SlashCmdList["DAMAGEMETERSSYNCREADY"] = DamageMeters_SyncReady;
        SLASH_DAMAGEMETERSSYNCREADY1 = "/dmsyncready";
        --SlashCmdList["DAMAGEMETERSSYNCKICK"] = DamageMeters_SyncKick;
        --SLASH_DAMAGEMETERSSYNCKICK1 = "/dmsynckick";
        SlashCmdList["DAMAGEMETERSSYNCLABEL"] = DamageMeters_SyncLabel;
        SLASH_DAMAGEMETERSSYNCLABEL1 = "/dmsynclabel";
        SlashCmdList["DAMAGEMETERSSYNCSTART"] = DamageMeters_SyncStart;
        SLASH_DAMAGEMETERSSYNCSTART1 = "/dmsyncstart";
        SlashCmdList["DAMAGEMETERSSYNCHALT"] = DamageMeters_SyncHalt;
        SLASH_DAMAGEMETERSSYNCHALT1 = "/dmsynchalt";
        --SlashCmdList["DAMAGEMETERSSYNCBOSSSTART"] = DamageMeters_SyncBossStart;
        --SLASH_DAMAGEMETERSSYNCBOSSSTART1 = "/dmsyncbossstart";
        --SlashCmdList["DAMAGEMETERSSYNCBOSSEND"] = DamageMeters_SyncBossEnd;
        --SLASH_DAMAGEMETERSSYNCBOSSEND1 = "/dmsyncbossend";

        -- Undocumented atm.
        SlashCmdList["DAMAGEMETERSSYNCEMOTE"] = DamageMeters_SyncEmote;
        SLASH_DAMAGEMETERSSYNCEMOTE1 = "/dme";
        SlashCmdList["DAMAGEMETERSRPS"] = DamageMeters_RPSChallenge;
        SLASH_DAMAGEMETERSRPS1 = "/dmrps";
        SlashCmdList["DAMAGEMETERSRPSR"] = DamageMeters_RPSResponse;
        SLASH_DAMAGEMETERSRPSR1 = "/dmrpsr";

        SlashCmdList["DAMAGEMETERSPOPULATE"] = DamageMeters_Populate;
        SLASH_DAMAGEMETERSPOPULATE1 = "/dmpop";
        SlashCmdList["DAMAGEMETERSTOGGLELOCK"] = DamageMeters_ToggleLock;
        SLASH_DAMAGEMETERSTOGGLELOCK1 = "/dmlock";
        SlashCmdList["DAMAGEMETERSTOGGLEPAUSE"] = DamageMeters_TogglePause;
        SLASH_DAMAGEMETERSTOGGLEPAUSE1 = "/dmpause";
        SlashCmdList["DAMAGEMETERSSETREADY"] = DamageMeters_SetReady;
        SLASH_DAMAGEMETERSSETREADY1 = "/dmready";
        SlashCmdList["DAMAGEMETERSTOGGLELOCKPOS"] = DamageMeters_ToggleLockPos;
        SLASH_DAMAGEMETERSTOGGLELOCKPOS1 = "/dmlockpos";
        SlashCmdList["DAMAGEMETERSTOGGLEGROUPONLY"] = DamageMeters_ToggleGroupMembersOnly;
        SLASH_DAMAGEMETERSTOGGLEGROUPONLY1 = "/dmgrouponly";
        SlashCmdList["DAMAGEMETERSTOGGLEADDPETTOPLAYER"] = DamageMeters_ToggleAddPetToPlayer;
        SLASH_DAMAGEMETERSTOGGLEADDPETTOPLAYER1 = "/dmaddpettoplayer";
        SlashCmdList["DAMAGEMETERSTOGGLERESETWHENCOMBATSTARTS"] = DamageMeters_ToggleResetWhenCombatStarts;
        SLASH_DAMAGEMETERSTOGGLERESETWHENCOMBATSTARTS1 = "/dmresetoncombat";
        SlashCmdList["DAMAGEMETERSVERSION"] = DamageMeters_ShowVersion;
        SLASH_DAMAGEMETERSVERSION1 = "/dmversion";
        SLASH_DAMAGEMETERSVERSION2 = "/dmver";
        SlashCmdList["DAMAGEMETERSTOGGLETOTAL"] = DamageMeters_ToggleTotal;
        SLASH_DAMAGEMETERSTOGGLETOTAL1 = "/dmtotal";
        SlashCmdList["DAMAGEMETERSTOGGLESHOWMAX"] = DamageMeters_ToggleMaxBars;
        SLASH_DAMAGEMETERSTOGGLESHOWMAX1 = "/dmshowmax";

        -- Commands for testing.
        -- ["reset"] = "/dmreset - (For Testing) Forces a re-layout of the visual elements.",
        SlashCmdList["DAMAGEMETERSRESET"] = DamageMeters_Reset;
        SLASH_DAMAGEMETERSRESET1 = "/dmreset";
        -- ["test"] = "/dmtest [#] - (For Testing) Adds # test entries to the list.  If no number specified, adds one entry for each visible bar.",
        SlashCmdList["DAMAGEMETERSTEST"] = DamageMeters_Test;
        SLASH_DAMAGEMETERSTEST1 = "/dmtest";
        -- ["add"] = "/dmadd name - (For Testing) Simulates player 'name' doing 1 damage.",
        SlashCmdList["DAMAGEMETERSADD"] = DamageMeters_Add;
        SLASH_DAMAGEMETERSADD1 = "/dmadd";
        -- ["dumptable"] = "/dmdumptable - (For Testing) Dumps the entire internal data table."
        SlashCmdList["DAMAGEMETERSDUMPTABLE"] = DamageMeters_DumpTable;
        SLASH_DAMAGEMETERSDUMPTABLE1 = "/dmdumptable";
        SlashCmdList["DAMAGEMETERSDEBUGPRINT"] = DM_ToggleDMPrintD;
        SLASH_DAMAGEMETERSDEBUGPRINT1 = "/dmdebug";
        SlashCmdList["DAMAGEMETERSDUMPMSG"] = DM_DumpMsg;
        SLASH_DAMAGEMETERSDUMPMSG1 = "/dmdumpmsg";
        SlashCmdList["DAMAGEMETERSCONSOLEPRINT"] = DM_ConsolePrint;
        SLASH_DAMAGEMETERSCONSOLEPRINT1 = "/dmp";
        SlashCmdList["DAMAGEMETERSCONSOLEPRINTTABLE"] = DM_ConsolePrintTable;
        SLASH_DAMAGEMETERSCONSOLEPRINTTABLE1 = "/dmpt";

        -- Tell the frame to rebuild itself.
        DamageMeters_UpdateVisibility();
        DamageMeters_frameNeedsToBeGenerated = true;

        DamageMeters_lastUpdateTime = GetTime();
        DamageMeters_lastProcessQueueTime = DamageMeters_lastUpdateTime;
        DamageMeters_lastEventTime = GetTime();

        DamageMeters_FixPatterns();
end

function DamageMeters_RegisterEvents()
        for index, event in DamageMeters_watchedEventsTable do
                this:RegisterEvent(event);
        end

        -- This is a hack--if the frame is initially hidden the stupid dropdown 
        -- menu doesn't work right and needs to be opened twice to be seen the first
        -- time.
        DMReportFrame:Hide();
end

function DamageMeters_UnregisterEvents()
        for index, event in DamageMeters_watchedEventsTable do
                this:UnregisterEvent(event);
        end
end

function DamageMeters_DeleteDMIData(dmi, dmiCount)
        for tableIx, table in DamageMeters_tables do
                for playerIx, playerStruct in table do
                        local dmiTemp;
                        for dmiTemp = dmi, dmiCount do 
                                playerStruct[dmiTemp] = playerStruct[dmiTemp - 1];
                        end
                end
        end
end

-- This function should house code that needs to run after variables have been loaded
-- but before the mod starts updating.
function DamageMeters_OnLoadComplete()

        local dmiCount = DMI_MAX;
        local plugin, savedDMI;
        for plugin, savedDMI in DamageMeters_pluginDMITable do
                if (savedDMI > dmiCount) then
                        dmiCount = savedDMI;
                end
        end

        local bPluginsMissing = false;

        --DMPrintD("DamageMeters_pluginDMITable loaded:");
        --DM_DUMP_RECURSIVE(DamageMeters_pluginDMITable, "[root]", "");

        -- go through list of saved plugin data
        for plugin, savedDMI in DamageMeters_pluginDMITable do
                -- if no plugin for data
                if (nil == DamageMeters_PLUGINS[plugin]) then
                        DMPrintD("Clearing saved data for not-loaded plugin "..plugin..", dmi = "..savedDMI);

                        -- delete plugin data 
                        DamageMeters_DeleteDMIData(savedDMI, dmiCount);
                        dmiCount = dmiCount - 1;

                        -- renumber saved plugin indexes
                        DamageMeters_pluginDMITable[plugin] = nil;
                        for plugin2, savedDMI2 in DamageMeters_pluginDMITable do
                                DamageMeters_pluginDMITable[plugin2] = savedDMI2 - 1;
                        end

                        bPluginsMissing = true;
                else
                        DMPrintD("Plugin >"..plugin.."< has saved data and is assigned DMI "..savedDMI);

                        -- save dmi into the plugin table
                        DamageMeters_PLUGINS[plugin].dmi = savedDMI;
                end
        end

        -- go through the list of plugins
        DamageMeters_pluginDMITable = {};
        for plugin, pluginStruct in DamageMeters_PLUGINS do
                -- assign new dmis for any that still dont have any
                if (pluginStruct.dmi == nil) then
                        dmiCount = dmiCount + 1;
                        pluginStruct.dmi = dmiCount;
                        DMPrintD("Plugin "..plugin.." has no saved data. Was assigned DMI "..dmiCount);
                end

                -- build the new DamageMeters_pluginDMITable
                DamageMeters_pluginDMITable[plugin] = pluginStruct.dmi;

                -- Inform the plugin of its new dmi.
                pluginStruct.pfnAssignDMI(pluginStruct.dmi);
                pluginStruct.quantDefs.dmi = pluginStruct.dmi;
        end

        --DMPrintD("DamageMeters_pluginDMITable after load:");
        --DM_DUMP_RECURSIVE(DamageMeters_pluginDMITable, "[root]", "");

        DMI_MAX = dmiCount;
        DMPrintD("DMI_MAX after loading plugins = "..DMI_MAX);

        --------------

        -- Clean up other variables that may have depended on missing plugins.
        if (bPluginsMissing) then
                if (DamageMeters_quantity > DamageMeters_Quantity_MAX) then
                        DMPrintD("Plugins missing, and fixing out of range DamageMeters_quantity.");
                        DamageMeters_quantity = DamageMeters_Quantity_MAX;
                else
                        DMPrintD("DamageMeters_quantity within range.");
                end
        end
end

function DamageMeters_InParty()
        local inParty = false;
        local p = GetNumPartyMembers();
        local r = GetNumRaidMembers();

        if ((p + r) > 0) then
                inParty = true;
        end

        return inParty;
end

function DamageMeters_UpdateVisibility(userCaused)
        local inParty = DamageMeters_InParty();
        if (inParty and not DamageMeters_currentlyInParty) then
                DMPrintD("DM: Joined party.");
                if (DamageMeters_flags[DMFLAG_clearWhenJoinParty]) then
                        DamageMeters_Clear()
                end             
        end
        DamageMeters_currentlyInParty = inParty;

        if (DamageMeters_flags[DMFLAG_visibleOnlyInParty]) then
                if (inParty and not DamageMetersFrame:IsVisible()) then
                        DMPrintD("DMFLAG_visibleOnlyInParty, inParty, and not DamageMetersFrame:IsVisible() - calling _Show()");
                        DamageMeters_Show();
                elseif (not inParty and DamageMetersFrame:IsVisible()) then
                        DMPrintD("DMFLAG_visibleOnlyInParty, not inParty, and DamageMetersFrame:IsVisible() - calling _Hide()");
                        DamageMeters_Hide();
                end
        elseif (userCaused) then
                if (not DamageMetersFrame:IsVisible()) then
                        DamageMeters_Show();
                end
        end
end

function DamageMeters_UpdateCount()
        local newCount = DamageMeters_barCount;
        if (DMVIEW_MAX == DamageMeters_viewMode) then
                newCount = DamageMeters_BARCOUNT_MAX;
        elseif (DMVIEW_MIN == DamageMeters_viewMode) then
                newCount = 1;
        else
                if (DamageMeters_autoCountLimit > 0) then
                        newCount = table.getn(DamageMeters_tables[DMT_VISIBLE]);
                        if (newCount > DamageMeters_autoCountLimit) then
                                newCount = DamageMeters_autoCountLimit;
                        elseif (newCount == 0) then
                                newCount = 1;
                        end
                end
        end

        if (newCount ~= DamageMeters_barCount) then
                DamageMeters_barCount = newCount;
                DMPrintD("Frame dirty: count changed.");
                DamageMeters_frameNeedsToBeGenerated = true;
        end
end

function DamageMeters_UpdateDebugTimers()
        local now = GetTime();

        -- /script DMPrint(GetTime().." - "..DamageMeters_lastDebugTime.." = "..(GetTime() - DamageMeters_lastDebugTime));
        if (DamageMeters_lastDebugTime < 0) then
                DamageMeters_lastDebugTime = now;

                local timer;
                for timer = 1, DMPROF_COUNT do
                        DamageMeters_debugTimers[timer] = {};
                        DamageMeters_debugTimers[timer].time = 0;
                        DamageMeters_debugTimers[timer].count = 0;
                        DamageMeters_debugTimers[timer].peak = 0;
                end
        end

        local debugTime = now - DamageMeters_lastDebugTime;
        if (debugTime > 1.0) then
                DamageMeters_lastDebugTime = now;

                local timer;

                if (DamageMeters_debugEnabled) then
                        if (DMTIMERMODE == 1) then
                                local msg = string.format("(%.2f) ", debugTime);
                                for timer = 1, DMPROF_COUNT do                          
                                        msg = msg..string.format("%s=%d(%d) ", DMPROF_NAMES[timer], DamageMeters_debugTimers[timer].time, DamageMeters_debugTimers[timer].count);
                                end
                                DMPrint(msg, nil, true);
                        elseif (DMTIMERMODE == 2) then
                                local msg = "";
                                local uCount = ceil(DamageMeters_debugTimers[DMPROF_UPDATE].time / 10);
                                local pCount = ceil(DamageMeters_debugTimers[DMPROF_PARSEMESSAGE].time / 10);
                                local aCount = ceil(DamageMeters_debugTimers[DMPROF_ADDVALUE].time / 10);
                                local bCount = ceil(DamageMeters_debugTimers[DMPROF_BARS].time / 10);
                                msg = msg.."|cFFFF0000"..string.rep("U", uCount);
                                msg = msg.."|cFF00FF00"..string.rep("P", pCount);
                                msg = msg.."|cFF60FF60"..string.rep("A", aCount);
                                msg = msg.."|cFF0000FF"..string.rep("B", bCount);
                                DMPrint(msg, nil, true);
                        elseif (DMTIMERMODE == 3) then
                                local msPerFrame = 1000 / GetFramerate();
                                local msg = string.format("Frames (%.2f) ", debugTime);
                                for timer = 1, DMPROF_COUNT do                          
                                        msg = msg..string.format("%s=%.2f(%d) ", 
                                                        DMPROF_NAMES[timer], 
                                                        DamageMeters_debugTimers[timer].time / msPerFrame, 
                                                        DamageMeters_debugTimers[timer].count);
                                end
                                DMPrint(msg, nil, true);
                        elseif (DMTIMERMODE == 4) then
                                local totalTime = 0;
                                local msPerFrame = 1000 / GetFramerate();
                                for timer = 1, DMPROF_COUNT do                          
                                        totalTime = totalTime + DamageMeters_debugTimers[timer].time;
                                end
                                local debugMS = floor(debugTime * 1000);
                                local msg = string.format("%.2f Frames @ %.2f FPS | %4d / %4d ms = %.2f%%", 
                                                totalTime / msPerFrame, 
                                                GetFramerate(), 
                                                totalTime,
                                                debugMS,
                                                100 * totalTime / debugMS);
                                DMPrint(msg, nil, true);
                        end
                end

                for timer = 1, DMPROF_COUNT do  
                        if (DamageMeters_debugTimers[timer].peak < DamageMeters_debugTimers[timer].time) then
                                DamageMeters_debugTimers[timer].peak = DamageMeters_debugTimers[timer].time;
                        end
                        DamageMeters_debugTimers[timer].time = 0;
                        DamageMeters_debugTimers[timer].count = 0;
                end
        end
end

function DMPEAKINFO()
        local msg = "";
        local timer;
        local total = 0;
        for timer = 1, DMPROF_COUNT do
                msg = msg..string.format("%s=%d ", DMPROF_NAMES[timer], DamageMeters_debugTimers[timer].peak);
        end
        DMPrint(msg);
end

-- Call this when the table is dirty to "clean" it.
-- Do sorting and such here.
function DamageMeters_UpdateTables()
        if (DM_Bypass["Update Tables"] == true) then
                return;
        end

        --DMPrintD(GetTime()..": Update Tables called.", nil, true);

        DamageMeters_StartDebugTimer(DMPROF_SORT);

        -- Determine totals -first-, as some quantities (ie. Dande-Rating) require totals in order to
        -- calculate their own values.
        DamageMeters_DetermineTotals();

        -- Sort
        DamageMeters_DoSort(DamageMeters_tables[DMT_VISIBLE], DamageMeters_quantity);
        DamageMeters_tablesDirty = false;

        -- Determine ranks for Titan display.
        -- Eventually the rank table could be used to index into the main table, rather than sorting the 
        -- main table itself.  Would add some indirection but would keep us from having to shuffle that 
        -- table around.  Dunno which way is faster, honestly.
        DamageMeters_DetermineRanks(DMT_ACTIVE, true);

        DamageMeters_StopDebugTimer(DMPROF_SORT);
end

function DamageMetersFrame_OnUpdate()
        ----------------------

        if (DM_Bypass["Generate AddValues"] == true) then
                DamageMeters_StartDebugTimer(DMPROF_PARSEMESSAGE);
                DamageMeters_AddDamage("CHAT_MSG_COMBAT_SELF_HITS", UnitName("player"), "[Test]", 0, DM_HIT, DamageMeters_Relation_SELF, "[Stress Test]");

                DamageMeters_StartDebugTimer(DMPROF_PARSEMESSAGE);
                DamageMeters_AddHealing("CHAT_MSG_SPELL_SELF_BUFF", UnitName("player"), UnitName("player"), 0, DM_HIT, DamageMeters_Relation_SELF, DamageMeters_Relation_SELF, "[Stress Test]")
        end

        if (DM_Bypass["Generate Events"] == true) then
                --DamageMeters_ParseMessage("You hit Bob for 0.", "CHAT_MSG_COMBAT_SELF_HITS");
                --DamageMeters_ParseMessage("Bob hits you for 0.", "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS");
                
                DamageMeters_ParseMessage("Your TestSpell heals you for 0.", "CHAT_MSG_SPELL_SELF_BUFF");
                DamageMeters_ParseMessage("Your TestSpell heals you for 0.", "CHAT_MSG_SPELL_SELF_BUFF");
                DamageMeters_ParseMessage("Your TestSpell heals you for 0.", "CHAT_MSG_SPELL_SELF_BUFF");
        end

        ----------------------
        -- Debug Start
        DamageMeters_StartDebugTimer(DMPROF_UPDATE);

        local updateBars = false;

        local currentTime = GetTime();
        local elapsed = currentTime - DamageMeters_lastUpdateTime;

        if (DamageMeters_debug4.showGCInfo) then
                local gcAmt, gcLimit = gcinfo();

                if (DM_Bypass["Constant Update"] == true) then
                        DamageMeters_tablesDirty = 1;
                end

                local first = false;
                if (DM_lastgcAmt == nil) then
                        DM_lastgcAmt = 0;
                        DM_gcDelta = 0;
                        first = true;
                end
                local delta = max(0, gcAmt - DM_lastgcAmt);
                DM_gcDelta = max(DM_gcDelta, delta);
                DM_gcDelta = min(DM_gcDelta, 50);
                DamageMeters_sendMsgQueueBar:Show();
                DamageMeters_sendMsgQueueBar:SetMinMaxValues(0, 50);
                DamageMeters_sendMsgQueueBar:SetValue(ceil(DM_gcDelta));
                DM_gcDelta = max(0, DM_gcDelta - 0.5);
                DM_lastgcAmt = gcAmt;
                if (first) then
                        DM_gcDelta = 0;
                end

                DamageMeters_processMsgQueueBar:Show();
                DamageMeters_processMsgQueueBar:SetMinMaxValues(0, gcLimit);
                DamageMeters_processMsgQueueBar:SetValue(gcAmt);
        end


        -- If we have queued chain heals, process them now.
        if (DamageMeters_queuedChainHealCount > 0) then
                if (DamageMeters_queuedChainHealCount > 3) then
                        -- If we have an unreasonable number, nuke them
                        DamageMeters_queuedChainHealCount = 0;
                else
                        --DMPrintD("Processing queued chain heals, total = "..DamageMeters_queuedChainHealCount);
                        local activeIndex = DamageMeters_GetPlayerIndex(UnitName("Player"), DMT_ACTIVE);
                        local fightIndex = DamageMeters_GetPlayerIndex(UnitName("Player"), DMT_FIGHT);
                        if (not activeIndex or not fightIndex) then
                                -- If the player has no index, maybe we had a clear happen between the heal being queued
                                -- and this tick: just clear it.
                                DamageMeters_queuedChainHealCount = 0;
                        else
                                local hitCount = 1;
                                while (DamageMeters_queuedChainHealCount > 0) do
                                        -- Add events for the additional chain heals, but don't add the values again to
                                        -- the totals.  Also, mark the events with a * at the end to tell other bits of
                                        -- code not to count this one.
                                        local spell = "Chain Heal "..hitCount.."*";

                                        -- index, quantity, spell, amount, crit, relationship
                                        DamageMeters_AddEvent(DMT_ACTIVE,
                                                        activeIndex, 
                                                        DamageMeters_Quantity_HEALING, 
                                                        spell, 
                                                        DamageMeters_queuedChainHealValue[DamageMeters_queuedChainHealCount], 
                                                        DamageMeters_queuedChainHealCrit[DamageMeters_queuedChainHealCount],
                                                        DamageMeters_Relation_SELF,
                                                        nil );
                                        DamageMeters_AddEvent(DMT_FIGHT,
                                                        fightIndex, 
                                                        DamageMeters_Quantity_HEALING, 
                                                        spell, 
                                                        DamageMeters_queuedChainHealValue[DamageMeters_queuedChainHealCount], 
                                                        DamageMeters_queuedChainHealCrit[DamageMeters_queuedChainHealCount],
                                                        DamageMeters_Relation_SELF,
                                                        nil );

                                        DamageMeters_queuedChainHealCount = DamageMeters_queuedChainHealCount - 1;
                                        hitCount = hitCount + 1;
                                end
                        end
                end

                DamageMeters_waitingForChainHeal = false;
        end

        ------------------

        if (DM_Bypass["Update All"] == true) then
                return;
        end

        -- Update quant cycling.
        if (DamageMeters_flags[DMFLAG_cycleVisibleQuantity]) then
                if (DamageMeters_textState < 1) then
                        if (GetTime() - DamageMeters_currentQuantStartTime > DamageMeters_QUANTITYSHOWDURATION) then
                                DamageMeters_CycleQuant(false, DamageMeters_flags[DMFLAG_applyFilterToAutoCycle]);
                                DamageMeters_textStateStartTime = GetTime();
                                updateBars = true;
                        end
                end
        end

        -- Update visibility.
        DamageMeters_UpdateVisibility();

        -- Update count.
        DamageMeters_UpdateCount();

        -- Generate the frame if needed.
        local forceSort = false;
        if (DamageMeters_frameNeedsToBeGenerated) then
                DamageMetersFrame_GenerateFrame();
                DamageMeters_tablesDirty = true;
                updateBars = true;
                forceSort = true;
        end

        -- Update Background
        -- Determine if we are still in combat.
        local updateBackground = false;
        if (DamageMeters_inCombat) then
                if (DamageMeters_IsQuantityPS(DamageMeters_quantity)) then
                        updateBackground = true;
                end

                -- If the player isn't in combat and we haven't received any messages
                -- in a while, automatically end combat.
                if (not DamageMeters_playerInCombat and
                        DamageMeters_combatEndTime - DamageMeters_lastEventTime > DM_COMBAT_TIMEOUT_SECONDS) then
                        --DMPrintD("Stopping combat due to inactivity.");
                        DamageMeters_OnCombatEnd();                     
                end
        end
        if (DM_Pause_Not ~= DamageMeters_pauseState) then
                updateBackground = true;
        end
        if (updateBackground) then
                DamageMeters_SetBackgroundColor();
        end

        -- Start delayed Sync.
        if (DamageMeters_syncStartTime > 0) then
                if (currentTime > DamageMeters_syncStartTime) then
                        DamageMeters_DoSync();
                        DamageMeters_syncStartTime = -1;
                end
        end

        -- Update text state.
        if (DamageMeters_textState > 0) then
                local now = GetTime();
                if (now - DamageMeters_textStateStartTime > DamageMeters_TEXTSTATEDURATION) then
                        local lastState = DamageMeters_textState;
                        repeat
                                DamageMeters_textState = DamageMeters_textState + 1;

                                if (DamageMeters_textState > DamageMeters_Text_MAX) then
                                        DamageMeters_textState = 1;
                                        if (DamageMeters_flags[DMFLAG_cycleVisibleQuantity]) then
                                                DamageMeters_CycleQuant(false, DamageMeters_flags[DMFLAG_applyFilterToAutoCycle]);
                                        end
                                end
                                
                                -- This is a safety to keep us from looping forever.
                                if (DamageMeters_textState == lastState) then
                                        -- Unnecessary, just break.  Stay with the last state.
                                        --DMPrintD("DamageMeters_textState infinite loop protection activated.");
                                        --DamageMeters_textOptions[DamageMeters_Text_NAME] = true;
                                        --DamageMeters_textState = DamageMeters_Text_NAME;
                                        break;
                                end
                        until (DamageMeters_textOptions[DamageMeters_textState])
                        DamageMeters_textStateStartTime = now;
                        updateBars = true;              
                end
        end

        DamageMeters_StopDebugTimer(DMPROF_UPDATE);

        ----------------------------------

        local bSecondHasPassedSinceLastBarUpdate = (currentTime - DamageMeters_lastBarUpdateTime > 1.0);

        -- NOTE: DamageMeters_lastBarUpdateTime also means "last sort time".  When the hidden frame
        -- takes over sorting duties when we are hidden it uses that variable, even though no bars are
        -- actually sorted.
        if (DamageMeters_flags[DMFLAG_constantVisualUpdate] or bSecondHasPassedSinceLastBarUpdate) then
                updateBars = true;
                DamageMeters_lastBarUpdateTime = currentTime;
        end
        
        -- Sort the table.
        if (forceSort or (DamageMeters_tablesDirty and (DamageMeters_flags[DMFLAG_constantVisualUpdate] or updateBars))) then
                DamageMeters_UpdateTables();
        end

        ----------------------------------
        -- Code which calculates and uses totals.
        -- Must come after Sorting, as some quantity's values are calculated from totals.

        -- Calculate totals.  These are used by tooltips and reports, and should be 
        -- calculated every update.
        local quantIndex;
        local totalValue = 0;
        local maxUnitIndex = 0;
        local maxUnitValue = 0;
        local playerValue = 0;
        local playerIndex = DamageMeters_GetPlayerIndex(UnitName("player"));
        for quantIndex = 1, DMI_MAX do
                DamageMeters_totals[quantIndex] = 0;
        end
        local dmi = DamageMeters_GetQuantityDMI(DamageMeters_quantity);
        local index, playerStruct;
        for index, playerStruct in DamageMeters_tables[DMT_VISIBLE] do 
                local unitValue = DamageMeters_GetQuantityValue(DamageMeters_quantity, DMT_VISIBLE, index);

                if (playerIndex == index) then
                        playerValue = unitValue;
                end

                if (unitValue > maxUnitValue) then
                        maxUnitIndex = index;
                        maxUnitValue = unitValue;
                end

                totalValue = totalValue + unitValue;
                
                for dmiIndex = 1, DMI_MAX do
                        DamageMeters_totals[dmiIndex] = DamageMeters_totals[dmiIndex] + playerStruct.dmiData[dmiIndex].q;
                end
        end

        -- Total Button
        if (DamageMeters_flags[DMFLAG_showTotal]) then
                if (DamageMeters_quantity == DamageMeters_Quantity_TIME) then
                        DamageMeters_TotalButtonText:SetText("-");
                elseif (DamageMeters_IsQuantityPS(DamageMeters_quantity)) then
                        DamageMeters_TotalButtonText:SetText(string.format("T=%.1f", totalValue));
                else
                        DamageMeters_TotalButtonText:SetText(string.format("T=%d", totalValue));
                end
        end

        -- Tooltip
        if (DamageMetersTooltip:IsOwned(this)) then
                DamageMeters_SetTooltipText();
        end

        ----------------------------------

        if (DM_Bypass["Update Bars"] == true) then
                return;
        end

        -- Bar updating.
        if (updateBars) then
                --DMPrintD(string.format("Updating bars. %.3f", currentTime - DamageMeters_lastBarUpdateTime), nil, true);

                DamageMeters_StartDebugTimer(DMPROF_BARS);

                -- Initialize and clear the bars.
                local i;
                for i = 1,DamageMeters_barCount do
                        DamageMeters_bars[i]:SetMinMaxValues(0, maxUnitValue);
                        DamageMeters_bars[i]:SetValue(0);
                        DamageMeters_text[i]:SetText("");
                end
                
                -- Table index of first bar.
                DamageMeters_barStartIndex = 1;
                local playerIndex = DamageMeters_GetPlayerIndex(UnitName("Player"), DMT_VISIBLE);
                if (DMVIEW_MIN == DamageMeters_viewMode) then
                        if (not playerIndex) then
                                if (DMVIEW_MIN == DamageMeters_viewMode) then
                                        -- If we are in miniMode we need the player to be in the table: 
                                        -- add her by giving her some dummy data.
                                        DamageMeters_AddValue(UnitName("Player"), 0, DM_DOT, DamageMeters_Relation_SELF, DamageMeters_Quantity_HEALINGRECEIVED, nil);
                                        playerIndex = DamageMeters_GetPlayerIndex(UnitName("Player"), DMT_VISIBLE);
                                        if (not playerIndex) then
                                                -- Could fail if the table was full.
                                                playerIndex = 1;
                                        end
                                else
                                        playerIndex = 1;
                                end
                        end
                        DamageMeters_barStartIndex = playerIndex;

                        if (DamageMeters_lastPlayerPosition ~= DamageMeters_barStartIndex) then
                                DMPrintD("Frame dirty: Player index changed..");
                                DamageMeters_frameNeedsToBeGenerated = true;
                        end
                elseif (DMVIEW_MAX ~= DamageMeters_viewMode and DamageMeters_flags[DMFLAG_playerAlwaysVisible] and (playerIndex ~= nil) and DamageMeters_barCount) then
                        -- /script DMPrint(DamageMeters_flags[DMFLAG_playerAlwaysVisible] and "true" or "false");
                        --DMPrint("yes", nil, true);
                        local nonPlayerBars = DamageMeters_barCount - 1;                                -- 0
                        local top = ceil(nonPlayerBars / 2);                                                    -- 0
                        local first = playerIndex - top;                                                                -- 2
                        local last = playerIndex + (nonPlayerBars - top);                               -- 2
                        local totalBars = table.getn(DamageMeters_tables[DMT_VISIBLE]); -- 2

                        if (last > totalBars) then
                                first = totalBars - DamageMeters_barCount + 1;
                        end
                        if (first < 1) then
                                first = 1;
                        end
                        DamageMeters_barStartIndex = first;
                end
                DamageMeters_lastPlayerPosition = playerIndex;

                --DMPrintD(string.format("setting bars %d to %d", DamageMeters_barStartIndex, table.getn(DamageMeters_tables[DMT_VISIBLE])));

                -- Set bar info.
                local barIndex = 1;
                local struct;
                for i,struct in DamageMeters_tables[DMT_VISIBLE] do 
                        if (i >= DamageMeters_barStartIndex) then
                                if (barIndex <= DamageMeters_barCount) then
                                        -- Wonky special case for health.
                                        if (DamageMeters_Quantity_HEALTH == DamageMeters_quantity) then
                                                DamageMeters_bars[i]:SetMinMaxValues(0, struct.maxHealth);
                                        end
                                        DamageMetersFrame_SetBarInfo(barIndex, i, totalValue, maxUnitValue, p == maxUnitIndex, playerValue);
                                        barIndex = barIndex + 1;
                                end
                        end
                end

                DamageMeters_StopDebugTimer(DMPROF_BARS);
        end

        DamageMeters_lastUpdateTime = currentTime;

        ----------------------
        -- Debug End
        DamageMeters_UpdateDebugTimers();
end

function DamageMetersFrame_GenerateFrame(frame)
        if (not frame) then
                frame = DamageMetersFrame;
                if (not frame) then
                        return;
                end
        end

        -- Hide the title button if mini mode.
        if (DMVIEW_MIN == DamageMeters_viewMode) then
                DamageMetersFrame_TitleButton:Hide();
        else
                DamageMetersFrame_TitleButton:Show();
        end

        -- Show/hide the total button.
        if (DamageMeters_flags[DMFLAG_showTotal] and not (DMVIEW_MIN == DamageMeters_viewMode)) then
                DamageMetersFrame_TotalButton:Show();
        else
                DamageMetersFrame_TotalButton:Hide();
        end

        -- Hide all bars: update will reshow those that need to be seen.
        local i;
        for i = 1,DamageMeters_BARCOUNT_MAX do
                DamageMeters_bars[i]:Hide();
                DamageMeters_bars[i]:SetValue(0);
                DamageMeters_text[i]:SetText("");
                -- Put all bars under the first bar.
                DamageMeters_bars[i]:SetPoint("TOPLEFT", frame:GetName(), "TOPLEFT", 5, -6);
        end

        --DMPrint("GenerateFrame : bar count = "..DamageMeters_barCount);

        -- Set the size of the frame.
        local rowCount = 0;
        local columnCount = 1;
        local newWidth = 0;
        if (DamageMeters_barCount > (DamageMeters_BARCOUNT_MAX / 2)) then
                rowCount = ceil(DamageMeters_barCount / 2);
                columnCount = 2;
                newWidth = DamageMeters_BARWIDTH * 2 + 10 + 2;
        else
                columnCount = 1;
                rowCount = DamageMeters_barCount;
                newWidth = DamageMeters_BARWIDTH + 10;
        end
        local newHeight = (DamageMeters_BARHEIGHT * rowCount) + 11;

        local oldWidth = frame:GetWidth();
        local oldHeight = frame:GetHeight();

        frame:SetWidth( newWidth );
        frame:SetHeight( newHeight );

        --if (DamageMeters_debugEnabled) then
        --      if (DamageMeters_firstGeneration) then
        --              DMPrintD("Initializing position to "..frame:GetLeft()..", "..frame:GetTop());
        --      end
        --end

        -- Update pos according to resize direction.
        if (not DamageMeters_firstGeneration) then
                if (DamageMeters_flags[DMFLAG_resizeLeft] or DamageMeters_flags[DMFLAG_resizeUp]) then
                        --DMPrint("Resizing...");
                        local xPos = frame:GetLeft();
                        local yPos = frame:GetTop();

                        if (DamageMeters_flags[DMFLAG_resizeLeft]) then
                                xPos = xPos - (newWidth - oldWidth);
                        end
                        if (DamageMeters_flags[DMFLAG_resizeUp]) then
                                yPos = yPos + (newHeight - oldHeight);
                        end

                        -- Note: anchoring to bottomleft since apparently the GetLeft and GetTop
                        -- values are relative to that point.
                        frame:SetPoint("TOPLEFT", "UIParent", "BOTTOMLEFT", xPos, yPos);
                end
        end

        --DMPrint("DamageMeters: "..rowCount.." rows, "..columnCount.." columns.");

        -- Position the bars.
        local name = frame:GetName();
        local row;
        local column;
        for row = 1, rowCount do
                for column = 1, columnCount do
                        --DMPrint("Row = "..row..", column = "..column);
                        local index = row + (column - 1) * rowCount;
                        if (index <= DamageMeters_barCount) then
                                local itemButton = DamageMeters_bars[index];
                                local itemText = DamageMeters_text[index];

                                itemButton:SetWidth(DamageMeters_BARWIDTH);

                                local x = 5 + (column - 1) * (DamageMeters_BARWIDTH + 2);
                                local y = -6 - (row - 1) * DamageMeters_BARHEIGHT;
                                itemButton:SetPoint("TOPLEFT", name, "TOPLEFT", x, y);

                                itemText:SetPoint("CENTER", itemButton:GetName(), "CENTER", 0, 0);
                                itemText:SetPoint("LEFT", itemButton:GetName(), "LEFT", 0, 0);
                                itemText:SetPoint("TOP", itemButton:GetName(), "TOP", 0, 0);
                                itemText:SetPoint("RIGHT", itemButton:GetName(), "RIGHT", 0, 0);
                                itemText:SetPoint("BOTTOM", itemButton:GetName(), "BOTTOM", 0, 0);

                                -- Justify text
                                if (DMVIEW_MIN == DamageMeters_viewMode or DamageMeters_flags[DMFLAG_justifyTextLeft]) then
                                        itemText:SetJustifyH("LEFT");
                                else
                                        itemText:SetJustifyH("CENTER");
                                end
                        end
                end
        end

        DamageMeters_SetBackgroundColor();

        DamageMeters_frameNeedsToBeGenerated = false;
        DamageMeters_firstGeneration = false;
end

function DamageMetersFrame_SetBarInfo(barIndex, tableIndex, totalValue, maxValue, isMax, playerValue)
        if (DM_Bypass["SetBarInfo All"] == true) then
                return;
        end

        --DMPrintD("DamageMetersFrame_SetBarInfo, barIndex = "..barIndex..", totalValue = "..totalValue..", maxValue = "..maxValue);

        local red = 0.00;
        local green = 0.00;
        local blue = 0.00;
        local barString = "";

        local tableEntry = DamageMeters_tables[DMT_VISIBLE][tableIndex];
        local player = tableEntry.player;
        local dmi = DamageMeters_GetQuantityDMI(DamageMeters_quantity);
        local age;
        if (DamageMeters_Quantity_TIME == DamageMeters_quantity) then
                age = GetTime() - tableEntry.lastTime;
        elseif (dmi ~= nil) then
                age = GetTime() - tableEntry.dmiData[dmi].lastQuantTime;
        else
                -- This case covers quantities without dmis (and hence no last times).
                -- Could put a system in for calculating it, but I doubt we really care.
                age = 60;
        end

        local relationship = tableEntry.relationship;
        if (DMVIEW_MIN == DamageMeters_viewMode) then
                local color = DamageMeters_quantityColor[DamageMeters_quantity];
                red = color[1];
                green = color[2] 
                blue = color[3];
        else
                if (DamageMeters_colorScheme == 1) then
                        if (DamageMeters_Relation_SELF == relationship) then
                                green = 1.00;
                        elseif (DamageMeters_Relation_PET == relationship) then
                                green = 0.80;
                        elseif (DamageMeters_Relation_PARTY == relationship) then
                                blue = 1.00;
                        elseif (DamageMeters_Relation_FRIENDLY == relationship) then
                                red = 1.00;
                                green = 0.50;
                        end
                else
                        local class = tableEntry.class;
                        if (class) then
                                local color = DamageMeters_GetClassColor(class);
                                red = color.r;
                                green = color.g;
                                blue = color.b;
                        elseif (DamageMeters_Relation_PET == relationship) then
                                red = 0.00;
                                green = 0.80;
                                blue = 0.00;
                        else
                                red = 0.70;
                                green = 0.70;
                                blue = 0.70;
                        end
                end
        end

        -- Bar color pulse magnitude.
        local pulseMag = 0.00;
        if (DamageMeters_flags[DMFLAG_constantVisualUpdate]) then
                if (age < DamageMeters_PULSE_TIME) then
                        pulseMag = 1.00 - age / DamageMeters_PULSE_TIME;
                end
        end

        -- Calc value
        local value;
        local dmi = DamageMeters_GetQuantityDMI(DamageMeters_quantity);
        if (DamageMeters_quantity == DamageMeters_Quantity_TIME) then
                value = age;
        else
                value = DamageMeters_GetQuantityValue(DamageMeters_quantity, DMT_VISIBLE, tableIndex);
        end


        -- TEXT --
        local stateAge = GetTime() - DamageMeters_textStateStartTime;
        local barAge = stateAge - (barIndex / DamageMeters_barCount) * (DamageMeters_BARFADEINMINTIME + DamageMeters_BARFADEINTIME * DamageMeters_barCount);

        if ((barAge > 0) or (not DamageMeters_flags[DMFLAG_constantVisualUpdate])) then
                -- DamageMeters_Text_MAX many entries.
                local rankString = nil;
                local nameString = nil;
                local totalPercentString = nil;
                local leaderPercentString = nil;
                local valueString = nil;
                local deltaString = nil;

                -- Rank
                if (DMVIEW_MIN == DamageMeters_viewMode or
                        ((DamageMeters_textState > 0 and DamageMeters_textState == DamageMeters_Text_RANK) or
                        (DamageMeters_textState <= 0 and DamageMeters_textOptions[DamageMeters_Text_RANK]))) then
                        rankString = tostring(tableIndex);
                end

                -- Name
                if (not (DMVIEW_MIN == DamageMeters_viewMode)) then
                        if ((DamageMeters_textState > 0 and DamageMeters_textState == DamageMeters_Text_NAME) or
                                (DamageMeters_textState <= 0 and DamageMeters_textOptions[DamageMeters_Text_NAME])) then
                                nameString = player;
                        end
                end

                -- Total Percentage
                if (DamageMeters_Quantity_TIME ~= DamageMeters_quantity and totalValue ~= nil and (not DamageMeters_IsQuantityPS(DamageMeters_quantity) or (not DamageMeters_flags[DMFLAG_showFightAsPS]))) then
                        if ((DamageMeters_textState > 0 and DamageMeters_textState == DamageMeters_Text_TOTALPERCENTAGE) or
                                (DamageMeters_textState <= 0 and DamageMeters_textOptions[DamageMeters_Text_TOTALPERCENTAGE])) then
                                local percent = (totalValue > 0) and ((value / totalValue) * 100) or 0;
                                totalPercentString = string.format("%.2f%%", percent);
                        end
                end

                -- Leader Percentage
                if (DamageMeters_Quantity_TIME ~= DamageMeters_quantity and totalValue ~= nil and (not DamageMeters_IsQuantityPS(DamageMeters_quantity) or (not DamageMeters_flags[DMFLAG_showFightAsPS]))) then
                        if ((DamageMeters_textState > 0 and DamageMeters_textState == DamageMeters_Text_LEADERPERCENTAGE) or
                                (DamageMeters_textState <= 0 and DamageMeters_textOptions[DamageMeters_Text_LEADERPERCENTAGE])) then
                                local percent = (maxValue > 0) and ((value / maxValue) * 100) or 0;
                                leaderPercentString = format("%.2f%%", percent);
                        end
                end

                -- Value
                local buildValueString = false;
                if (DamageMeters_textState > 0 and DamageMeters_textState == DamageMeters_Text_VALUE) then
                        buildValueString = true;
                elseif (DamageMeters_textState <= 0 and DamageMeters_textOptions[DamageMeters_Text_VALUE]) then
                        buildValueString = true;
                elseif (DamageMeters_textState == 0 and 
                                (DamageMeters_quantity == DamageMeters_Quantity_TIME or DamageMeters_IsQuantityPS(DamageMeters_quantity))) then
                        buildValueString = true;
                end

                -- Delta
                if (DamageMeters_Quantity_TIME ~= DamageMeters_quantity and totalValue ~= nil and (not DamageMeters_IsQuantityPS(DamageMeters_quantity) or (not DamageMeters_flags[DMFLAG_showFightAsPS]))) then
                        if ((DamageMeters_textState > 0 and DamageMeters_textState == DamageMeters_Text_DELTA) or
                                (DamageMeters_textState <= 0 and DamageMeters_textOptions[DamageMeters_Text_DELTA])) then
                                local delta = value - playerValue;
                                deltaString = string.format("%s%d", (delta >= 0 and "+" or ""), delta);
                        end
                end
        
                if (buildValueString) then
                        if (DamageMeters_quantity == DamageMeters_Quantity_TIME) then
                                valueString = string.format("%d:%.2d", value / 60, math.mod(value, 60));
                        elseif (DamageMeters_IsQuantityPS(DamageMeters_quantity)) then
                                valueString = string.format("%.1f", value);
                        else
                                valueString = tostring(value);
                        end
                end

                -- Concatenate strings.
                local strIx;
                local first = true;
                if (rankString) then
                        barString = barString..rankString.." ";
                end
                if (nameString) then
                        barString = barString..nameString.." ";
                end
                if (totalPercentString) then
                        barString = barString..totalPercentString.." ";
                end
                if (leaderPercentString) then
                        barString = barString..leaderPercentString.." ";
                end
                if (valueString) then
                        barString = barString..valueString.." ";
                end
                if (deltaString) then
                        barString = barString..deltaString;
                end
                if (string.sub(barString, -1) == " ") then
                        barString = string.sub(barString, 1, -2);
                end
        end

        if (DamageMeters_flags[DMFLAG_constantVisualUpdate]) then
                -- Apply pulse.
                if (red == 1.00 and green == 1.00 and blue == 1.00) then
                        red = 0.5 + 0.5 * (1.0 - pulseMag);
                        green = red;
                        blue = red;
                else
                        red = pulseMag > red and pulseMag or red;
                        green = pulseMag > green and pulseMag or green;
                        blue = pulseMag > blue and pulseMag or blue;
                end
        end

        -------

        if (DamageMeters_flags[DMFLAG_constantVisualUpdate]) then
                if (barAge > 0) then
                        local charsToShow = floor(barAge / DamageMeters_BARCHARTIME);
                        local strLen = string.len(barString);
                        if (strLen > charsToShow) then
                                local charsToRemove = strLen - charsToShow;
                                local leftToRemove, rightToRemove;
                                if (DMVIEW_MIN == DamageMeters_viewMode) then
                                        leftToRemove = 0;
                                        rightToRemove = charsToRemove;
                                else
                                        leftToRemove = floor(charsToRemove / 2);
                                        rightToRemove = charsToRemove - leftToRemove;
                                end

                                barString = string.sub(barString, leftToRemove, -rightToRemove);
                        end
                end
        end

        if (DMVIEW_MIN == DamageMeters_viewMode) then
                local titleText = DamageMeters_GetPausedTitleText();
                if (titleText) then
                        barString = titleText.." "..barString;
                end
        end

        DamageMeters_bars[barIndex]:Show();
        DamageMeters_bars[barIndex]:SetStatusBarColor(red, green, blue);
        DamageMeters_bars[barIndex]:SetValue(value);
        DamageMeters_text[barIndex]:SetText(barString);
        -- After 1300 patch text wouldn't appear without this.
        DamageMeters_text[barIndex]:Show();
end

-------------------------------------------------------------------------------

function DamageMeters_Clear(leave, silent)
        -- Clear contributor list full: on a partical clear it is impossible to
        -- tell who contributed what.
        DamageMeters_contributorList = {};
        DamageMeters_flags[DMFLAG_haveContributors] = false;

        DamageMetersPlugin_Clear();

        DamageMeters_DoClear(DMT_ACTIVE, leave, silent)
        DamageMeters_DoClear(DMT_FIGHT, 0, true)
end

function DamageMeters_DoClear(tableIndex, leave, silent)
        --DMPrintD("DamageMeters_DoClear("..tableIndex..")");

        DamageMeters_tablesDirty = true;

        -- In case we get a clear call between ticks.
        DamageMeters_queuedChainHealCount = 0;

        local last = table.getn(DamageMeters_tables[tableIndex]);
        if (last == 0) then
                -- This line just to ensure its really wiped out.
                DamageMeters_tables[tableIndex] = {};
                return;
        end

        local first = 1;
        if (leave ~= nil) then
                --DMPrint("leave = '"..leave.."'");
                local c = tonumber(leave);
                if (c) then
                        first = leave + 1;
                        if (first < 1) then
                                first = 1;
                        end
                end
        end

        if (not silent) then
                DMPrint(format(DM_MSG_CLEAR, first, last));
        end

        local i;
        for i = last,first,-1 do
                if (DamageMeters_flags[DMFLAG_constantVisualUpdate]) then
                        if (tableIndex == DMT_VISIBLE) then
                                if (DamageMeters_bars[i]) then
                                        DamageMeters_bars[i]:SetValue(0);
                                        DamageMeters_text[i]:SetText("");
                                end
                        end
                end

                table.remove(DamageMeters_tables[tableIndex]);
        end

        --if (not silent) then
        --      DMPrint(format(DM_MSG_REMAINING, table.getn(DamageMeters_tables[DMT_ACTIVE])));
        --end
end


function DamageMeters_Test(countArg)
        DamageMeters_Clear();

        local count = DamageMeters_barCount;
        if (countArg) then
                count = tonumber(countArg);
                if (not count) then
                        count = DamageMeters_barCount
                end

                if (count > DamageMeters_barCount) then
                        count = DamageMeters_barCount;
                end
        end

        DamageMeters_lastEvent = {};

        DMPrintD("Adding "..count.." test entries...");
        local index;
        local groupMembersOnlySave = DamageMeters_flags[DMFLAG_groupMembersOnly];
        DamageMeters_flags[DMFLAG_groupMembersOnly] = false;
        for index = 1,count do
                DamageMeters_AddValue("Test"..index, 1*index, DM_HIT, DamageMeters_Relation_FRIENDLY, DamageMeters_Quantity_DAMAGE, "[Test]");
                DamageMeters_AddValue("Test"..index, 2*index, DM_HIT, DamageMeters_Relation_FRIENDLY, DamageMeters_Quantity_HEALING, "[Test]");
                DamageMeters_AddValue("Test"..index, 3*(count - index), DM_HIT, DamageMeters_Relation_FRIENDLY, DamageMeters_Quantity_DAMAGED, "[Test]");
                DamageMeters_AddValue("Test"..index, 4*(count - index), DM_HIT, DamageMeters_Relation_FRIENDLY, DamageMeters_Quantity_HEALINGRECEIVED, "[Test]");
        end
        DamageMeters_flags[DMFLAG_groupMembersOnly] = groupMembersOnlySave;
end

function DamageMeters_Add(player)
        if (player) then
                DamageMeters_AddDamage(player, 0, DM_HIT, DamageMeters_Relation_FRIENDLY, "[Test]");
        end
end

function DamageMeters_SetSort(sortArg)
        local usage = true;
        if (sortArg) then
                local sort = tonumber(sortArg);
                if (sort) then
                        if (sort >= 1 and sort <= DamageMeters_Sort_MAX) then
                                DamageMeters_sort = sort;
                                DamageMeters_tablesDirty = true;
                                DMPrint(DM_MSG_SORT..DamageMeters_sort);
                                usage = false;
                        else
                                DMPrint(DM_ERROR_INVALIDARG);
                        end
                end
        end

        if (usage) then
                DMPrint(DM_MSG_CURRENTSORT..DamageMeters_Sort_STRING[DamageMeters_sort]);
                local i;
                for i=1,DamageMeters_Sort_MAX do
                        DMPrint(" "..i..": "..DamageMeters_Sort_STRING[i]);
                end
        end
end

function DamageMeters_SetQuantity(quantArg, bSilent)
        local usage = true;
        if (quantArg) then
                local quant = tonumber(quantArg);
                if (quant) then
                        if (quant >= 1 and quant <= DamageMeters_Quantity_MAX) then
                                DamageMeters_quantity = quant;
                                if (not bSilent) then
                                        DMPrint(DM_MSG_SETQUANT..DM_QUANTDEFS[DamageMeters_quantity].name);
                                end
                                usage = false;
                        else
                                if (not bSilent) then
                                        DMPrint(DM_ERROR_INVALIDARG);
                                end
                                DamageMeters_quantity = 1;
                        end
                end
        end

        if (usage) then
                DMPrint(DM_MSG_CURRENTQUANT..DM_QUANTDEFS[DamageMeters_quantity].name);
                local i;
                for i=1,DamageMeters_Quantity_MAX do
                        DMPrint(" "..i..": "..DM_QUANTDEFS[i].name);
                end
        end

        DamageMeters_SetBackgroundColor();
        --DMPrintD("Frame dirty: quantity changed.");
        DamageMeters_frameNeedsToBeGenerated = true;
        DamageMeters_currentQuantStartTime = GetTime();

        -- If a Fight quantity, make the visible table the combat table.
        if (DamageMeters_IsQuantityFight(DamageMeters_quantity)) then
                --DMPrintD("Visible table set to Combat");
                DMT_VISIBLE = DMT_FIGHT;
        else
                --DMPrintD("Visible table set to Active");
                DMT_VISIBLE = DMT_ACTIVE;
        end
end

function DamageMeters_SetBackgroundColor()
        local frame = DamageMetersFrame;
        if (frame) then
                local color = DamageMeters_quantityColor[DamageMeters_quantity];
                local titleR, titleG, titleB, titleA = DamageMeters_GetTitleButtonColors();

                if (DMVIEW_MIN == DamageMeters_viewMode) then
                        frame:SetBackdropColor(titleR, titleG, titleB, titleA);
                else
                        frame:SetBackdropColor(color[1], color[2], color[3], color[4]);
                end

                -- Set title button text.
                local pausedText = DamageMeters_GetPausedTitleText();
                if (pausedText) then
                        DamageMeters_TitleButtonText:SetText(pausedText);
                else
                        if (DamageMeters_IsQuantityPS(DamageMeters_quantity)) then
                                local title;
                                local combatTime = DamageMeters_combatEndTime - DamageMeters_combatStartTime;
                                if (combatTime > 60) then
                                        title = format("%s %d:%.2d", DM_QUANTDEFS[DamageMeters_quantity].psName, combatTime / 60, math.mod(combatTime, 60));
                                else
                                        title = format("%s %.1fs", DM_QUANTDEFS[DamageMeters_quantity].psName, combatTime);
                                end
                                DamageMeters_TitleButtonText:SetText(title);
                        else
                                local title = DM_QUANTDEFS[DamageMeters_quantity].name;
                                DamageMeters_TitleButtonText:SetText(title);
                        end
                end

                -- Set title button color.
                DamageMetersFrame_TitleButton:SetBackdropColor(titleR, titleG, titleB, titleA);

                -- Set total button color.
                DamageMetersFrame_TotalButton:SetBackdropColor(color[1], color[2], color[3], color[4]);
        end
end

function DamageMeters_GetPausedTitleText()
        local time = GetTime();
        local showPausedText = false;
        
        if (DMVIEW_MIN ~= DamageMeters_viewMode) then
                if (DM_Pause_Not == DamageMeters_pauseState) then
                        return nil;
                else
                        local flooredTime = floor(time);
                        if (math.mod(flooredTime, 4) > 1) then
                                return nil;
                        end
                end
        end

        if (DM_Pause_Paused == DamageMeters_pauseState) then
                return DM_MSG_PAUSEDTITLE;
        elseif (DM_Pause_Ready == DamageMeters_pauseState) then
                return DM_MSG_READYTITLE;
        end

        return nil;
end

function DamageMeters_GetTitleButtonColors()
        local color = DamageMeters_quantityColor[DamageMeters_quantity];
        if (DM_Pause_Ready == DamageMeters_pauseState) then
                local time = GetTime();
                local pulseFactor = 1.0 - (time * 0.5 - floor(time * 0.5));
                return color[1] * pulseFactor, color[2] * pulseFactor, color[3] * pulseFactor, 1.0;
        elseif (DM_Pause_Paused == DamageMeters_pauseState) then
                return 0.0, 0.0, 0.0, color[4];
        end

        return color[1], color[2], color[3], color[4];
end

function DamageMeters_SetRelationship(index, relationship)
        local player = DamageMeters_tables[DMT_ACTIVE][index].player;
        if (nil == relationship) then
                DMPrintD("DamageMeters_SetRelationship ("..player.."), relationship = nil.");
                relationship = DamageMeters_Relation_FRIENDLY;
        end

        relationship = tonumber(relationship);
        local unitID = nil;

        if (relationship < 1 or relationship > DamageMeters_Relation_MAX) then
                DMPrintD("DamageMeters_SetRelationship ("..player.."), relationship = "..relationship);
                relationship = DamageMeters_Relation_FRIENDLY;
        end
        DamageMeters_tables[DMT_ACTIVE][index].relationship = relationship;
        --Print("relationship = "..relationship);

        if (relationship == DamageMeters_Relation_SELF) then
                DamageMeters_tables[DMT_ACTIVE][index].class = UnitClass("player");
                --Print("Adding self, class = "..DamageMeters_tables[DMT_ACTIVE][index].class);
                unitID = "player";
        elseif (relationship == DamageMeters_Relation_PET) then
                unitID = "pet";
        elseif (relationship == DamageMeters_Relation_PARTY) then
                local i;
                for i=1,5 do
                        unitID = "party"..i;
                        local partyName = UnitName(unitID);
                        if (partyName == player) then
                                DamageMeters_tables[DMT_ACTIVE][index].class = UnitClass(unitID);
                                --Print("Party member found: index = "..i..", class = "..DamageMeters_tables[DMT_ACTIVE][index].class);
                                break;
                        end
                end
        else
                --[[
                local i;
                for i=1,40 do
                        unitID = "raid"..i;
                        local partyName = UnitName(unitID);
                        if (partyName == player) then
                                DamageMeters_tables[DMT_ACTIVE][index].class = UnitClass(unitID);
                                --Print("Raid member found: index = "..i..", class = "..DamageMeters_tables[DMT_ACTIVE][index].class);
                                break;
                        end
                end
                ]]--

                DamageMeters_UpdateRaidMemberClasses();
        end


        if (nil == unitID) then
                unitID = DamageMeters_GetUnitID(player, relationship);
        end
        if (unitID) then
                DamageMeters_tables[DMT_ACTIVE][index].health = UnitHealth(unitID);
                DamageMeters_tables[DMT_ACTIVE][index].maxHealth = UnitHealthMax(unitID);
                --DMPrint("New Player "..player.." has id <"..unitID.."> and health "..DamageMeters_tables[DMT_ACTIVE][index].health.."/"..DamageMeters_tables[DMT_ACTIVE][index].maxHealth, nil, true);
        else
                --DMPrint("New Player "..player.." unitID = nil.");
                DamageMeters_tables[DMT_ACTIVE][index].health = 0;
                DamageMeters_tables[DMT_ACTIVE][index].maxHealth = 0;

                --[[
                --DMPrint(GetNumRaidMembers().." raid members:");
                for index = 1, GetNumRaidMembers() do
                        DMPrint(index.." "..GetRaidRosterInfo(index));
                        unitId = "raid"..index;
                        DMPrint(unitId.." = "..UnitName(unitId));
                end
                ]]--
        end

        return relationship;
end

function DamageMeters_AddValue(player, amount, crit, relationship, quantity, spell, damageType)
        if (DM_Bypass["AddValue"] == true) then
                return;
        end

        if (nil == player) then
                DMPrint("DamageMeters: INTERNAL ERROR! player = nil.");
                return 0;
        end
        if (nil == quantity) then
                DMPrint("DamageMeters: INTERNAL ERROR! quantity = nil.");
        end

        if (DM_UNKNOWNENTITY == player) then
                return 0;
        end

        if (DamageMeters_debugEnabled) then
                if (string.find(player, "Julie's") or
                        string.find(player, "Night Dragon")) then
                        DMPrintD(string.format("HEY!: Player = %s, spell = %s", player, spell));
                        DMPrintD(string.format("[%s]: %s (%s)", DamageMeters_lastEvent.event, DamageMeters_lastEvent.fullMsg, DamageMeters_lastEvent.desc));
                end
        end

        if (DamageMeters_debug4.msgWatchMode) then
                if (spell ~= "[Msg]") then
                        return 0;
                end
        end

        -- Because sometimes messages say "x dmg done by spell."/"x dmg done by spell (blah absorbed)", the
        -- spell sometimes appears as "spell.".  This code strips it off.
        if (spell ~= nil and "." == string.sub(spell, -1)) then
                --DMPrint("Stripping period from spell "..spell);
                spell = string.sub(spell, 1, -2);
        end

        if (relationship == nil) then
                DMPrintD("Relationship = nil, player("..player.."), amount("..amount.."), quantity("..quantity..")");
                relationship = DamageMeters_Relation_FRIENDLY;
        end

        -- Fix pet relationship if necessary.
        if (DamageMeters_Relation_PET ~= relationship and player == UnitName("Pet")) then
                --DMPrintD("Fixing Pet Relationship: spell = "..spell, nil, true);
                relationship = DamageMeters_Relation_PET;
        end

        -- Assign to self if it is a pet and the option is set.
        if (DamageMeters_flags[DMFLAG_addPetToPlayer] and relationship == DamageMeters_Relation_PET) then
                relationship = DamageMeters_Relation_SELF;
                player = UnitName("Player");
        end

        amount = tonumber(amount);
        relationship = tonumber(relationship);
        local currentTime = GetTime();

        --DMPrint("DamageMeters_AddValue : relationship = "..relationship);

        local index = DamageMeters_GetPlayerIndex(player);
        local found = (index ~= nil);
        if (nil == index) then

                -- Reject if list locked.
                if (DamageMeters_listLocked) then
                        return 0;
                end
                
                -- Reject if list full.
                if (table.getn(DamageMeters_tables[DMT_ACTIVE]) >= DamageMeters_TABLE_MAX) then
                        return 0;
                end

                -- Reject if player is banned.
                if (DamageMeters_IsBanned(player)) then
                        --DMPrintD("Rejecting banned player "..player);
                        return 0;
                end
                
                -- Reject if we are excluding non-group members.
                -- This code lets the player's pets through, btw.
                if (DamageMeters_flags[DMFLAG_groupMembersOnly] and 
                                (DamageMeters_Relation_PARTY == relationship or DamageMeters_Relation_FRIENDLY == relationship)) then
                        local foundRelation = DamageMeters_GetGroupRelation(player);
                        if (foundRelation < 0) then
                                --DMPrintD(player.." not in party or raid, rejecting.");
                                return 0;
                        end
                end
                
                -- ** At this point we've determined the value is ok to add to the table. **

                -- If we are "ready", unpause.
                if (DamageMeters_DoReadyCheck(quantity, spell)) then
                        return;
                end

                if (quantity == DamageMeters_Quantity_DAMAGE and spell ~= DM_SYNCSPELLNAME) then
                        if (DamageMeters_startCombatOnNextValue) then
                                DamageMeters_startCombatOnNextValue = false;
                                if (DamageMeters_flags[DMFLAG_resetWhenCombatStarts]) then
                                        DamageMeters_Clear(0, true);
                                end
                                DamageMeters_OnCombatStart();
                        end
                end

                -- OK: Add the new player.
                index = DamageMeters_AddNewPlayer(DamageMeters_tables[DMT_ACTIVE], player);
                relationship = DamageMeters_SetRelationship(index, relationship);

                if (index <= DamageMeters_barCount) then
                        DamageMeters_bars[index]:Show();
                end
        else
                -- If we are "ready", unpause.
                if (DamageMeters_DoReadyCheck(quantity, spell)) then
                        return;
                end

                if (DamageMeters_startCombatOnNextValue and quantity == DamageMeters_Quantity_DAMAGE) then
                        DamageMeters_startCombatOnNextValue = false;
                        if (DamageMeters_flags[DMFLAG_resetWhenCombatStarts]) then
                                DamageMeters_Clear(0, true);
                        end
                        DamageMeters_OnCombatStart();

                        -- Very dangerous!  Recursing here.
                        DamageMeters_AddValue(player, amount, crit, relationship, quantity, spell);
                        return;
                end

                if (relationship < DamageMeters_tables[DMT_ACTIVE][index].relationship) then
                        DMPrintD("Updating "..player.."'s relationship from "..DamageMeters_tables[DMT_ACTIVE][index].relationship.." to "..relationship);
                        relationship = DamageMeters_SetRelationship(index, relationship);
                end
        end

        if (DM_Bypass["AddValue 1"] == true) then
                return;
        end

        -----------------------------------------------------------------
        -- ADD THE DATA TO THE MAIN TABLE
        --DMPrintD("DamageMeters_UpdateTableEntry active");
        DamageMeters_UpdateTableEntry(DMT_ACTIVE, index, quantity, amount, crit, found, relationship, spell, damageType);

        -----------------------------------------------------------------
        -- This is where the data gets added to the combat table.

        if (DamageMeters_inCombat) then
                local combatIndex = DamageMeters_GetPlayerIndex(player, DMT_FIGHT);
                if (nil == combatIndex) then
                        combatIndex = DamageMeters_AddNewPlayer(DamageMeters_tables[DMT_FIGHT], player);
                        -- Relationship and class is tricky: copy it directly from the main table.
                        DamageMeters_tables[DMT_FIGHT][combatIndex].relationship = relationship;
                        DamageMeters_tables[DMT_FIGHT][combatIndex].class = DamageMeters_tables[DMT_ACTIVE][index].class;
                end

                -- ADD THE DATA TO THE COMBAT TABLE
                --DMPrintD("DamageMeters_UpdateTableEntry combat");
                DamageMeters_UpdateTableEntry(DMT_FIGHT, combatIndex, quantity, amount, crit, found, relationship, spell, damageType);
        end

        -----------------------------------------------------------------
        -- This is where data is accumulated to the memory table.

        if (DamageMeters_flags[DMFLAG_accumulateToMemory]) then
                local memIndex = DamageMeters_GetPlayerIndex(player, DMT_SAVED);
                if (nil == memIndex) then
                        memIndex = DamageMeters_AddNewPlayer(DamageMeters_tables[DMT_SAVED], player);
                        -- Relationship is tricky: copy it directly from the main table.
                        DamageMeters_tables[DMT_SAVED][memIndex].relationship = relationship;
                        DamageMeters_tables[DMT_SAVED][memIndex].class = DamageMeters_tables[DMT_ACTIVE][index].class;
                end
        
                DamageMeters_UpdateTableEntry(DMT_SAVED, memIndex, quantity, amount, crit, found, relationship, spell, damageType);

                -- Take time from master table.
                DamageMeters_tables[DMT_SAVED][memIndex].lastTime = DamageMeters_tables[DMT_ACTIVE][index].lastTime;
        end

        -----------------------------------------------------------------

        if (DamageMeters_Quantity_DAMAGE == quantity or DamageMeters_Quantity_DAMAGED == quantity) then
                DamageMeters_lastEventTime = currentTime;
        end

        if (DamageMeters_debug4.showValueChanges) then
                DMPrint(format("Added %d %s to %s from %s.", amount, DM_QUANTDEFS[quantity].name, DamageMeters_tables[DMT_ACTIVE][index].player, spell), nil, true);
        end

        --[[
        -- Debug info.
        if (DamageMeters_lastEvent.event and DamageMeters_lastEvent.event ~= "") then
                if (nil == DamageMeters_tables[DMT_ACTIVE][index].firstMsg) then
                        DamageMeters_tables[DMT_ACTIVE][index].firstMsg = {};
                end
                DamageMeters_tables[DMT_ACTIVE][index].firstMsg.event = DamageMeters_lastEvent.event;
                DamageMeters_tables[DMT_ACTIVE][index].firstMsg.desc = DamageMeters_lastEvent.desc;
                DamageMeters_tables[DMT_ACTIVE][index].firstMsg.fullMsg = DamageMeters_lastEvent.fullMsg;

                DamageMeters_lastEvent.event = "";
        end
        ]]--

        return index;
end

function DamageMeters_UpdateTableEntry(destTableIndex, index, quantity, amount, crit, existingEntry, relationship, spell, damageType)
        if (DM_Bypass["UpdateTableEntry"] == true) then
                return;
        end     

        DamageMeters_tablesDirty = true;

        local destTable = DamageMeters_tables[destTableIndex];

        -----------------------------------------------------------------
        -- This is where the table entry gets modified with the new data.

        if (index == nil) then
                DMPrintD("index = nil, spell = "..spell);
        end
        if (quantity == nil) then
                DMPrintD("quantity = nil, spell = "..spell);
        end

        -- Lookup the DMI.
        local dmi = DM_QUANTDEFS[quantity].dmi;
        
        -- Update quantity.
        --DMPrintD("index = "..index..", quantity = "..quantity);
        destTable[index].dmiData[dmi].q = destTable[index].dmiData[dmi].q + amount;

        -- Update crit count.
        if (crit ~= DM_DOT) then
                destTable[index].dmiData[dmi].hitCount = destTable[index].dmiData[dmi].hitCount + 1;
                if (crit == DM_CRT) then
                        destTable[index].dmiData[dmi].critCount = destTable[index].dmiData[dmi].critCount + 1;
                end
        end

        -- We use amount = 0 just to add empty people to the list.  
        -- Only update time if it was because of a player action.
        if (existingEntry and (amount > 0) and (dmi == DMI_DAMAGE or dmi == DMI_HEALING)) then
                destTable[index].lastTime = GetTime();
        end
        destTable[index].dmiData[dmi].lastQuantTime = GetTime();

        -----------------------------------------------------------------
        -- Event Data collection. --

        if (spell and 
                spell ~= DM_SYNCSPELLNAME and
                ((DamageMeters_eventDataLevel == DamageMeters_EventData_ALL) or 
                 (DamageMeters_eventDataLevel == DamageMeters_EventData_SELF and (DamageMeters_Relation_SELF == relationship)))) then

                -- Special case for Chain Heal.
                -- If we get one, queue into a global array.  Chain heal messages come in reverse order
                -- (smallest to biggest) and all in the same frame.  At the next tick a bit of code in Update
                -- adds fake events for them (fake events have a * at the end and aren't counted towards 
                -- totals.
                if (destTableIndex == DMT_ACTIVE and
                        DamageMeters_Relation_SELF == relationship and 
                        "Chain Heal" == spell and
                        DamageMeters_Quantity_HEALING == quantity and
                        DamageMeters_waitingForChainHeal) then

                        DamageMeters_queuedChainHealCount = DamageMeters_queuedChainHealCount + 1;
                        DamageMeters_queuedChainHealValue[DamageMeters_queuedChainHealCount] = amount;
                        DamageMeters_queuedChainHealCrit[DamageMeters_queuedChainHealCount] = crit;

                        --DMPrintD("Chain Heal cast for "..amount);
                end

                DamageMeters_AddEvent(destTableIndex, index, quantity, spell, amount, crit, relationship, damageType);
        end
end

function DamageMeters_StartChainHeal()
        DamageMeters_queuedChainHealCount = 0;
        DamageMeters_waitingForChainHeal = true;
end

function DamageMeters_DebugError(msg)
        DMPrint(msg);
        DamageMeters_SendSyncMsg("ERROR: "..msg);
end

function DamageMeters_AddEvent(destTableIx, index, quantity, spell, amount, crit, relationship, damageType)
        if (DM_Bypass["AddEvent"] == true) then
                return;
        end

        local destTable = DamageMeters_tables[destTableIx];
        local countIndex = crit;
        if (crit == DM_DOT) then
                countIndex = DM_HIT;

                if ((string.sub(spell, 1, 1) ~= "[")) then
                        if (quantity == DamageMeters_Quantity_HEALING or
                                quantity == DamageMeters_Quantity_HEALINGRECEIVED) then
                                spell = spell.." [HOT]";
                        else
                                spell = spell.." [DOT]";
                        end
                end
        end
        
        -- Beta error handling--this shouldn't ever happen, think I fixed the bug elsewhere.
        -- (Calls to this function not using proper table's index).
        if (not index) then
                DamageMeters_DebugError("DamageMeters_AddEvent index = nil.  Spell = "..spell);
                return;
        end
        if (index == 0) then
                DamageMeters_DebugError("DamageMeters_AddEvent index = 0.  Spell = "..spell);
                return;
        end
        if (not quantity or quantity == 0) then
                DamageMeters_DebugError("DamageMeters_AddEvent bad quantity.  Spell = "..spell);
                return;
        end
        if (not destTable[index]) then
                DamageMeters_DebugError("DamageMeters_AddEvent destTable["..index.."] = nil.  Spell = "..spell);
                return;
        end

        -- Lookup the DMI.
        local dmi = DM_QUANTDEFS[quantity].dmi;

        if (not destTable[index].dmiData[dmi].events) then
                destTable[index].dmiData[dmi].events = {};
                destTable[index].dmiData[dmi].events.spellTable = {};
                destTable[index].dmiData[dmi].events.hash = {};
                destTable[index].dmiData[dmi].events.dirty = true;
        end
        if (not destTable[index].dmiData[dmi].events.spellTable[spell]) then
                --DMPrintD("EventTable: Adding spell "..spell);
                destTable[index].dmiData[dmi].events.spellTable[spell] = {};
                destTable[index].dmiData[dmi].events.spellTable[spell].value = 0;
                destTable[index].dmiData[dmi].events.spellTable[spell].counts = {0,0};
                destTable[index].dmiData[dmi].events.spellTable[spell].damageType = DM_DMGTYPE_DEFAULT;
                destTable[index].dmiData[dmi].events.spellTable[spell].resistanceSum = 0;
                destTable[index].dmiData[dmi].events.spellTable[spell].resistanceCount = 0;
        end
        destTable[index].dmiData[dmi].events.spellTable[spell].value = destTable[index].dmiData[dmi].events.spellTable[spell].value + amount;
        destTable[index].dmiData[dmi].events.spellTable[spell].counts[countIndex] = destTable[index].dmiData[dmi].events.spellTable[spell].counts[countIndex] + 1;

        if (DM_Bypass["AddEvent 1"] == true) then
                return;
        end

        if (relationship == DamageMeters_Relation_SELF and dmi == DMI_DAMAGED) then
                local dmgType = DM_DMGTYPE_DEFAULT;
                local resistance = 0;
                if (damageType) then
                        if (damageType ~= DM_DMGTYPE_DEFAULT) then
                                dmgType = DM_DMGNAMETOID[damageType];

                                if (nil == dmgType) then
                                        DMPrintD("ERROR: Unrecognized damage type >"..damageType.."<, fixing.");
                                        dmgType = DM_DMGTYPE_DEFAULT;
                                end
                        end
        
                        if (destTable[index].dmiData[dmi].events.spellTable[spell].damageType ~= DM_DMGTYPE_DEFAULT and
                                destTable[index].dmiData[dmi].events.spellTable[spell].damageType ~= dmgType) then
                                --DMPrintD("Switching types from "..destTable[index].dmiData[dmi].events.spellTable[spell].damageType .." to "..dmgType);
                        end

                        destTable[index].dmiData[dmi].events.spellTable[spell].damageType = dmgType;
                        resistance = DamageMeters_GetResistance(dmgType);

                        --DMPrintD(spell..": "..damageType.." damage, "..resistance.." resistance.");

                        destTable[index].dmiData[dmi].events.spellTable[spell].resistanceSum = destTable[index].dmiData[dmi].events.spellTable[spell].resistanceSum + resistance;
                        destTable[index].dmiData[dmi].events.spellTable[spell].resistanceCount = destTable[index].dmiData[dmi].events.spellTable[spell].resistanceCount + 1;
                end
        end

        if (DM_Bypass["AddEvent 2"] == true) then
                return;
        end

        destTable[index].dmiData[dmi].events.hash = nil;
        destTable[index].dmiData[dmi].events.dirty = true;

        if (crit == DM_CRT) then
                destTable[index].dmiData[dmi].events.spellTable[spell].counts[DM_HIT] = destTable[index].dmiData[dmi].events.spellTable[spell].counts[DM_HIT] + 1;
        end
end

function DamageMeters_DoReadyCheck(quantity, spell)
        if (DM_Pause_Ready == DamageMeters_pauseState) then
                if (DamageMeters_Quantity_DAMAGE == quantity or DamageMeters_Quantity_DAMAGED == quantity) then
                        if (DM_DMG_FALLING == spell or DM_DMG_LAVA == spell or DM_SYNCSPELLNAME == spell) then
                                return true;
                        else
                                if (DamageMeters_CheckSyncChan(true)) then
                                        DMPrint(DM_MSG_READYUNPAUSING, nil, true);
                                end
                                DamageMeters_SyncUnpause(true, spell);
                        end
                else
                        return true;
                end
        end

        return false;
end

function DamageMeters_AddNewPlayer(destTable, player)
        --DMPrintD("Adding player "..player);

        local now = GetTime();

        local index = table.getn(destTable) + 1;
        table.setn(destTable, index);
        destTable[index] = {};
        destTable[index].dmiData = {};

        local quant;
        for quant = 1, DMI_MAX do
                destTable[index].dmiData[quant] = {};
                destTable[index].dmiData[quant].q = 0;
                destTable[index].dmiData[quant].hitCount = 0;
                destTable[index].dmiData[quant].critCount = 0;
                destTable[index].dmiData[quant].lastQuantTime = now;
        end
        destTable[index].player = player;
        destTable[index].lastTime = now;

        return index;
end

function DamageMeters_GetResistance(resistId)
        if (resistId < DM_DMGTYPE_ARCANE or resistId > DM_DMGTYPE_SHADOW) then
                return 0;
        end

        local base, resistance, positive, negative;
        local frame = getglobal("MagicResFrame"..resistId);
        base, resistance, positive, negative = UnitResistance("player", frame:GetID());
        return resistance;
end

function DamageMeters_AddDamage(event, player, creatureName, amount, crit, relationship, spell, damageType)
        DamageMeters_StopDebugTimer(DMPROF_PARSEMESSAGE);

        DamageMeters_StartDebugTimer(DMPROF_ADDVALUE);
        DamageMeters_AddValue(player, amount, crit, relationship, DamageMeters_Quantity_DAMAGE, spell);
        DamageMeters_StopDebugTimer(DMPROF_ADDVALUE);
        
        DamageMetersPlugin_AddDamage(event, player, creatureName, amount, crit, relationship, spell, damageType)
end

function DamageMeters_AddDamageReceived(event, player, creatureName, amount, crit, relationship, spell, damageType, resisted)
        DamageMeters_StopDebugTimer(DMPROF_PARSEMESSAGE);

        DamageMeters_StartDebugTimer(DMPROF_ADDVALUE);
        local index = DamageMeters_AddValue(player, amount, crit, relationship, DamageMeters_Quantity_DAMAGED, spell, damageType);
        DamageMeters_StopDebugTimer(DMPROF_ADDVALUE);
        
        --[[
        -- Adjust the health value we're storing for this player
        if (index ~= nil and index > 0) then
                local playerStruct = DamageMeters_tables[DMT_ACTIVE][index];
                if ((playerStruct ~= nil) and (playerStruct.health ~= nil and playerStruct.health > 0)) then
                        playerStruct.health = max(0, playerStruct.health - amount);

                        if (DamageMeters_debug4.showHealthChanges) then
                                DMPrintD(-amount.." to "..player.."'s health.  New = "..playerStruct.health, {r=1,g=1,b=1}, true);
                        end
                end

                if (playerStruct == nil) then
                        DMPrintD("playerStruct = nil, index = "..index);
                end
        end
        ]]--

        DamageMetersPlugin_AddDamageReceived(event, player, creatureName, amount, crit, relationship, spell)
end

function DamageMeters_GetUnitID(entity, relationship)
        --DMPrintD("DamageMeters_GetUnitID "..entity.." "..relationship, nil, true);

        if (relationship == DamageMeters_Relation_SELF) then
                return "player";
        elseif (relationship == DamageMeters_Relation_PET) then
                return "pet";
        elseif (GetNumRaidMembers() > 0) then
                for index = 1, GetNumRaidMembers() do
                        if (entity == GetRaidRosterInfo(index)) then
                                return "raid"..index;
                        end
                end
        elseif (GetNumPartyMembers() > 0) then
                for i=1,5 do
                        local partyUnitName = "party"..i;
                        local partyName = UnitName(partyUnitName);
                        if (partyName and partyName ~= "" and partyName == entity) then
                                return partyUnitName;
                        end
                end     
        end
        
        return nil;
end

function DamageMeters_AddHealing(event, player, target, amount, crit, relationship, targetRelationship, spell)
        DamageMeters_StopDebugTimer(DMPROF_PARSEMESSAGE);

        if (DM_UNKNOWNENTITY == player or DM_UNKNOWNENTITY == target) then
                return;
        end

        --DMPrintD(DamageMeters_Relation_STRING[relationship].." healed "..DamageMeters_Relation_STRING[targetRelationship].." for "..amount);

        -------------------------
        -- Absolute healing.
        DamageMeters_StartDebugTimer(DMPROF_ADDVALUE);
        DamageMeters_AddValue(player, amount, crit, relationship, DamageMeters_Quantity_ABSHEAL, spell);
        DamageMeters_StopDebugTimer(DMPROF_ADDVALUE);

        -------------------------
        -- Overheal stuff.
        -- using DMT_ACTIVE instead of DMT_VISIBLE since overhealing was always zero.
        local targetIndex = DamageMeters_GetPlayerIndex(target, DMT_ACTIVE);
        local health = nil;
        local maxHealth = nil;
        local playerStruct;
        if (targetIndex) then
                playerStruct = DamageMeters_tables[DMT_ACTIVE][targetIndex];
                if ((playerStruct.health ~= nil and playerStruct.health > 0) and (playerStruct.maxHealth ~= nil and playerStruct.maxHealth > 0)) then
                        health = playerStruct.health;
                        maxHealth = playerStruct.maxHealth;             
                end
        else
                -- If the given player isn't already in the list we still want to know if they are being overhealed.
                -- Therefore, try to figure out their unitID.
                local unitID = DamageMeters_GetUnitID(player, relationship);
                if (unitID) then
                        health = UnitHealth(unitID);
                        maxHealth = UnitHealthMax(unitID);
                end
        end

        if (health ~= nil and maxHealth ~= nil) then
                local newHealth = health + amount;
                local overhealing = newHealth - maxHealth;
                if (overhealing > 0) then
                        if (DamageMeters_debug4.showHealthChanges) then
                                DMPrintD(player.." overhealed "..target.." by "..overhealing.." ("..health.."/"..maxHealth..")", nil, true);
                        end
                        DamageMeters_AddValue(player, overhealing, crit, relationship, DamageMeters_Quantity_OVERHEAL, spell);

                        newHealth = maxHealth;
                        amount = maxHealth - health;
                end

                if (targetIndex) then
                        playerStruct.health = newHealth;
                end
        end

        -------------------------
        -- Healing Done & Healing Taken

        --DMPrint("AddHealing: "..player.." healed "..target.." for "..amount);
        DamageMeters_StartDebugTimer(DMPROF_ADDVALUE);
        DamageMeters_AddValue(player, amount, crit, relationship, DamageMeters_Quantity_HEALING, spell);
        DamageMeters_AddValue(target, amount, crit, targetRelationship, DamageMeters_Quantity_HEALINGRECEIVED, spell);
        DamageMeters_StopDebugTimer(DMPROF_ADDVALUE);

        -------------------------

        DamageMetersPlugin_AddHealing(event, player, target, amount, crit, relationship, targetRelationship, spell)
end

function DamageMeters_Report(arg1)
        local destChar = "c";
        local count = table.getn(DamageMeters_tables[DMT_ACTIVE]);
        local tellTarget = "";
        local params = arg1;
        local reportQuantity;
        
        local argsParsed = false;

        if ("help" == params) then
                DamageMeters_ShowReportHelp();
                return;
        end

        local totalStrLen = 5;
        if ("total" == string.lower(string.sub(params, 1, totalStrLen))) then
                reportQuantity = DamageMeters_ReportQuantity_Total;
                if (string.len(params) > totalStrLen) then
                        params = string.sub(params, totalStrLen + 1);
                else
                        params = "";
                end
        else
                reportQuantity = DamageMeters_quantity;
        end

        if (params == "") then
                argsParsed = true;
        end

        local a, b, c;
        if (not argsParsed) then
                for a, b, c in string.gfind(params, "(.)(%d+) (.+)") do
                        destChar = a;
                        count = tonumber(b);
                        tellTarget = c;
                        --DMPrint(1);
                        argsParsed = true;
                end
        end
        if (not argsParsed) then
                for a, c in string.gfind(params, "(.) (%d+)") do
                        destChar = a;
                        --tellTarget = c;
                        tellTarget = format("%d", c);
                        --DMPrint(2);
                        argsParsed = true;
                end
        end
        if (not argsParsed) then
                for a, b in string.gfind(params, "(.)(%d+)") do
                        destChar = a;
                        count = tonumber(b);
                        --DMPrint(3);
                        argsParsed = true;
                end
        end
        if (not argsParsed) then
                for a, c in string.gfind(params, "(.) (.+)") do
                        destChar = a;
                        tellTarget = c;
                        --DMPrint(4);
                        argsParsed = true;
                end
        end
        if (not argsParsed) then
                for a in string.gfind(params, "(.)") do
                        destChar = a;
                        --DMPrint(5);
                        argsParsed = true;
                end
        end
        --DMPrint("."..destChar.."."..count.."."..tellTarget..".");

        if (not argsParsed) then
                DMPrint(DM_ERROR_INVALIDARG);
                DamageMeters_PrintHelp("report");
        end

        local destination;
        local invert = false;
        if (destChar) then
                --DMPrint("DamageMeters_Report("..params..")");

                local lowerDestChar = string.lower(destChar);
                if (lowerDestChar ~= destChar) then
                        invert = true;
                        destChar = lowerDestChar;
                end

                if (destChar == "c") then
                        destination = "CONSOLE";
                elseif (destChar == "p") then
                        destination = "PARTY";
                elseif (destChar == "s") then
                        destination = "SAY";
                elseif (destChar == "r") then
                        destination = "RAID";
                elseif (destChar == "w") then
                        destination = "WHISPER";
                elseif (destChar == "h") then
                        destination = "CHANNEL";
                elseif (destChar == "g") then
                        destination = "GUILD";
                elseif (destChar == "o") then
                        destination = "OFFICER";
                elseif (destChar == "f") then
                        destination = "BUFFER";

                else
                        DMPrint(DM_ERROR_BADREPORTTARGET..destChar);
                        return;
                end
        end

        if (destination == "WHISPER" and tellTarget == "") then
                DMPrint(DM_ERROR_MISSINGWHISPERTARGET);
                return;
        elseif (destination == "CHANNEL" and tellTarget == "") then
                DMPrint(DM_ERROR_MISSINGCHANNEL);
                return;
        end

        DamageMeters_DoReport(reportQuantity, destination, invert, 1, count, tellTarget);

        if (destination == "BUFFER") then
                DamageMeters_OpenReportFrame();
        end
end

function DamageMeters_DumpTable()
        DMPrint(table.getn(DamageMeters_tables[DMT_ACTIVE]).." elements:");

        local index;
        local info;
        for index,info in DamageMeters_tables[DMT_ACTIVE] do 
                DMPrint(index..": "..info.player);
        end
end

function DamageMeters_SendReportMsg(msg, destination, tellTarget)
        local editBox = DEFAULT_CHAT_FRAME.editBox;
        if (destination == "CONSOLE") then
                DMPrint(msg);
        elseif (destination == "TOOLTIP_TITLE") then
                DamageMetersTooltip:AddLine(msg, 1.0, 1.0, 1.0, 1);
        elseif (destination == "TOOLTIP") then
                DamageMetersTooltip:AddLine(msg, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 0);
        elseif (destination == "CHANNEL") then
                --DMPrint("Destination = "..destination..", tellTarget = "..tellTarget);
                SendChatMessage(msg, destination, editBox.language, GetChannelName(tellTarget));
        elseif (destination == "BUFFER") then
                DamageMeters_reportBuffer = DamageMeters_reportBuffer..msg.."\n";
        else
                SendChatMessage(msg, destination, editBox.language, tellTarget);
        end
end

function DamageMeters_SetCount(arg1, bSilent)
        local count = 0;
        if (not arg1 or arg1 == "") then
                DMPrint(DM_MSG_SETCOUNTTOMAX);
                count = DamageMeters_BARCOUNT_MAX;
        else 
                count = tonumber(arg1);
                if (count > DamageMeters_BARCOUNT_MAX) then
                        count = DamageMeters_BARCOUNT_MAX;
                end
        end
        
        DamageMeters_barCount = count;
        if (not bSilent) then
                DMPrint(DM_MSG_SETCOUNT..DamageMeters_barCount);
        end
        DMPrintD("Frame dirty: SetCount.");
        DamageMeters_frameNeedsToBeGenerated = true;
        DamageMeters_autoCountLimit = 0;
        DamageMeters_ForceNormalView();
end

function DamageMeters_Reset()
        DamageMeters_UpdateVisibility();
        DamageMeters_UpdateCount();
        DamageMeters_frameNeedsToBeGenerated = true;
end

function DamageMeters_ResetPos()
        local frame = DamageMetersFrame;
        if (not frame) then
                DMPrintD("DamageMetersFrame_Reset : Error getting frame.");
                return;
        end

        DMPrint(DM_MSG_RESETFRAMEPOS);
        local frameWidth = frame:GetWidth();
        local frameHeight = frame:GetHeight();
        frame:ClearAllPoints();
        frame:SetPoint("TOPLEFT", "UIParent", "RIGHT", -frameWidth, floor(frameHeight / 2));

        CloseMenus();

        -- Make sure the window is visible, too.
        DamageMeters_Show();
end

function DamageMeters_SetBarWidth(arg1, bSilent)
        if (arg1 == nil or arg1 == "") then
                DMPrint(string.format(DM_MSG_CURRENTBARWIDTH, DamageMeters_BARWIDTH));
        else
                if (string.lower(arg1) == "default") then
                        DamageMeters_BARWIDTH = DamageMeters_DEFAULTBARWIDTH;
                else
                        DamageMeters_BARWIDTH = tonumber(arg1);
                end
                if (not bSilent) then
                        DMPrint(string.format(DM_MSG_NEWBARWIDTH, DamageMeters_BARWIDTH));
                end
                DamageMeters_frameNeedsToBeGenerated = true;
        end
end

function DamageMeters_ToggleShow()
        local frame = getglobal("DamageMetersFrame");
        DamageMeters_flags[DMFLAG_visibleOnlyInParty] = false;
        if (frame:IsVisible()) then
                DMPrintD("ToggleShow called - calling _Hide()");
                DamageMeters_Hide();
        else
                DMPrintD("ToggleShow called - calling _Show()");
                DamageMeters_Show();
        end
end

function DamageMeters_Show()
        DMPrintD("DamageMeters_Show called.");
        DamageMetersFrame:Show();
        DamageMeters_flags[DMFLAG_isVisible] = true;
        DamageMeters_frameNeedsToBeGenerated = true;
end

function DamageMeters_Hide()
        DamageMetersFrame:Hide();
        DamageMetersFrame_TitleButton:Hide();
        DamageMetersFrame_TotalButton:Hide();
        DamageMeters_flags[DMFLAG_isVisible] = false;
end

function DM_CountMsg(arg1, desc, event, filter)
        event = string.sub(event, 10);

        if (DamageMeters_debugEnabled) then
                DamageMeters_lastEvent.event = event;
                DamageMeters_lastEvent.desc = desc;
                DamageMeters_lastEvent.fullMsg = arg1;
                
                local filterOn = false;
                --if (DamageMeters_debug4.showParse) then
                --      if (not filterOn or filter) then
                --              DMPrint("Parsed("..event..") "..arg1.." ["..desc.."]", nil, true);
                --      end
                --end

                if (DamageMeters_msgCounts[desc]) then
                        DamageMeters_msgCounts[desc] = DamageMeters_msgCounts[desc] + 1;
                else
                        DamageMeters_msgCounts[desc] = 1;
                end

                if (DamageMeters_debug4.msgWatchMode) then
                        DamageMeters_AddValue(desc, 1, DM_HIT, DamageMeters_Relation_PET, DamageMeters_Quantity_DAMAGE, "[Msg]");
                end
        end
end

function DM_DumpMsg()
        local i;
        local desc;
        for key,count in DamageMeters_msgCounts do
                DMPrintD(key.." : "..count)
        end;
end

function DM_ConsolePrint(arg1)
        local script = "DMPrint(("..arg1.."))";
        RunScript(script);
end

function DM_ConsolePrintTable(arg1)
        local script = "DM_DUMP_RECURSIVE(("..arg1.."), \"[root]\", \"\", \"CONSOLE\")";
        RunScript(script);
end

function DM_ToggleDMPrintD()
        DamageMeters_debugEnabled = not DamageMeters_debugEnabled;
        DMPrint("Debug mode enabled = "..(DamageMeters_debugEnabled and "true" or "false"));
end

-------------------------------------------------------------------------------

function DamageMetersBarTemplate_OnEnter()
        if (not DamageMetersFrame:IsVisible()) then
                return;
        end
        -- Getting onenters for invisible bars.  This doesn't seem to work :/
        if (not this:IsVisible()) then
                return;
        end
        -- This to work around above condition not working.
        if (this:GetID() > DamageMeters_barCount) then
                return;
        end

        -- no workee
        if (DamageMetersTooltip:IsVisible()) then
                return;
        end

        -- no workee
        if (DamageMetersFrameBarDropDown:IsVisible()) then
                --DMPrint("Not showing because drop down visible");
                return;
        end
        
        DamageMeters_tooltipBarIndex = this:GetID();

        -- Determine anchor.
        local anchorStyle = "ANCHOR_LEFT";
        local x,y = DamageMetersFrame:GetCenter();
        local screenWidth = UIParent:GetWidth();
        if (x~=nil and screenWidth~=nil) then
                if (x < (screenWidth / 2)) then
                        anchorStyle = "ANCHOR_RIGHT";
                end
        end
        
        -- Set owner and anchor.
        DamageMetersTooltip:SetOwner(DamageMetersFrame, anchorStyle);

        -- added 2005-01-13 by arys
        -- makes clock tooltip scale correctly
        DamageMetersTooltip:SetScale(this:GetScale());

        -- Set text.
        DamageMeters_SetTooltipText();
end

function DamageMetersBarTemplate_OnLeave()
        DamageMeters_tooltipBarIndex = nil;
        DamageMetersTooltip:Hide();
end

function DamageMetersBarTemplate_OnClick()
        local index = this:GetID();
        
        --DMPrint("Click '"..arg1.."'");
        if ( arg1 == "LeftButton" ) then
                if (index <= table.getn(DamageMeters_tables[DMT_VISIBLE])) then
                        --Print("Targetting "..DamageMeters_tables[DMT_VISIBLE][index].player);
                        local tableIndex = DamageMeters_barStartIndex + index - 1;
                        local targetName = DamageMeters_tables[DMT_VISIBLE][tableIndex].player;
                        local targetID = DamageMeters_GetUnitID(targetName, DamageMeters_tables[DMT_VISIBLE][tableIndex].relationship)

                        if (targetID) then
                                TargetUnit(targetID);
                        else
                                TargetByName(targetName);
                        end
                end
        elseif ( arg1 == "RightButton" ) then
                if (DMVIEW_MIN == DamageMeters_viewMode) then
                        DamageMeters_ShowMainMenu();
                else
                        if (index <= table.getn(DamageMeters_tables[DMT_VISIBLE])) then
                                local frame = DamageMetersFrame;

                                local distance;
                                distance = ( UIParent:GetWidth() - frame:GetRight() );
                                
                                DamageMeters_clickedBarIndex = index + DamageMeters_barStartIndex - 1;

                                --DMPrint("distance = "..distance);
                                local menuMoveDist = -75;
                                if ( distance <= menuMoveDist ) then
                                        local newOffset = distance - menuMoveDist;
                                        --DMPrint("Too close, new offset = "..newOffset);
                                        ToggleDropDownMenu(1, nil, DamageMetersFrameBarDropDown, "DamageMetersFrameBarDropDown", newOffset, 0);
                                else
                                        ToggleDropDownMenu(1, nil, DamageMetersFrameBarDropDown, "DamageMetersFrameBarDropDown", 0, 0);
                                end
                        end
                end
        end
end

function DamageMeters_SetTooltipText()
        local tableIndex = DamageMeters_barStartIndex + DamageMeters_tooltipBarIndex - 1;
        local playerStruct = DamageMeters_tables[DMT_VISIBLE][tableIndex];

        if ((nil == playerStruct) or (not tableIndex) or (tableIndex > table.getn(DamageMeters_tables[DMT_VISIBLE]))) then
                -- (nil == struct.player) or 
                DamageMeters_tooltipBarIndex = nil;
                DamageMetersTooltip:Hide();
                return;
        end

        local player = playerStruct.player;
        DamageMetersTooltip:SetText(player, 1.00, 1.00, 1.00);

        if (not IsControlKeyDown()) then
                local msg = DM_MSG_PRESSCONTROLEVENT;
                if (DamageMeters_flags[DMFLAG_showEventTooltipsFirst]) then
                        msg = DM_MSG_PRESSCONTROLQUANTITY;
                end
                DamageMetersTooltip:AddLine(msg, 1.0, 0.5, 0.5, 1);     
        end

        local tableIx = DMT_VISIBLE;

        if ((IsControlKeyDown() and not DamageMeters_flags[DMFLAG_showEventTooltipsFirst]) or (not IsControlKeyDown() and DamageMeters_flags[DMFLAG_showEventTooltipsFirst])) then
                local singleQuantity = nil;
                if (DamageMeters_quantity ~= DamageMeters_Quantity_TIME) then
                        if (IsAltKeyDown()) then
                                singleQuantity = DamageMeters_quantity;
                        else
                                DamageMetersTooltip:AddLine(DM_MSG_PRESSALTSINGLEQUANTITY, 1.0, 0.5, 0.5, 1);   
                        end
                end
                DamageMeters_DumpPlayerEvents(DamageMeters_tables[tableIx][tableIndex].player, "TOOLTIP", false, nil, nil, singleQuantity);
        else
                local percentages = {0, 0, 0, 0};
                local dmi;
                for dmi = 1, DMI_MAX do
                        percentages[dmi] = (DamageMeters_totals[dmi] > 0) and (100 * playerStruct.dmiData[dmi].q / DamageMeters_totals[dmi]) or 0;
                end

                local text = "";
                for dmi = 1, DMI_MAX do
                        local rank = (DamageMeters_rankTables[tableIx][player] and DamageMeters_rankTables[tableIx][player][dmi]) and " #"..tostring(DamageMeters_rankTables[tableIx][player][dmi]) or "";
                        text = text..(format("%s = %d (%.2f%%)%s\n", DMI_NAMES[dmi], playerStruct.dmiData[dmi].q, percentages[dmi], rank));
                        local critPercentage = (playerStruct.dmiData[dmi].hitCount > 0) and (playerStruct.dmiData[dmi].critCount / playerStruct.dmiData[dmi].hitCount) * 100 or 0;
                        text = text..(format("    %d/%d (%.2f%%) %s\n", playerStruct.dmiData[dmi].critCount, playerStruct.dmiData[dmi].hitCount, critPercentage, DM_CRITSTR));
                end

                --DMPrint("relationship = "..playerStruct.relationship);

                text = text..format(DM_TOOLTIP, 
                                GetTime() - playerStruct.lastTime,
                                DamageMeters_Relation_STRING[playerStruct.relationship]);
                if (playerStruct.class) then
                        text = text.."\n"..DM_CLASS.." = "..playerStruct.class;
                end

                DamageMetersTooltip:AddLine(text, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1);
        end

        DamageMetersTooltip:Show();
end

function DamageMeters_DoPlayerReport(player, destination)
        local playerIndex = DamageMeters_GetPlayerIndex(player);
        if (not playerIndex) then
                return;
        end
        local playerStruct = DamageMeters_tables[DMT_ACTIVE][playerIndex];

        DamageMeters_SendReportMsg(string.format(DM_MSG_PLAYERREPORTHEADER, player), destination);

        local percentages = {0, 0, 0, 0};
        local quantIndex;
        for quantIndex = 1, DMI_MAX do
                percentages[quantIndex] = (DamageMeters_totals[quantIndex] > 0) and (100 * playerStruct.dmiData[quantIndex].q / DamageMeters_totals[quantIndex]) or 0;
        end

        local text = "";
        for quantIndex = 1, DMI_MAX do
                DamageMeters_SendReportMsg(format("%s = %d (%.2f%%)", DM_QUANTDEFS[quantIndex].name, playerStruct.dmiData[quantIndex].q, percentages[quantIndex]), destination);
                local critPercentage = (playerStruct.dmiData[quantIndex].hitCount > 0) and (playerStruct.dmiData[quantIndex].critCount / playerStruct.dmiData[quantIndex].hitCount) * 100 or 0;
                DamageMeters_SendReportMsg(format("    %d/%d (%.2f%%) %s", playerStruct.dmiData[quantIndex].critCount, playerStruct.dmiData[quantIndex].hitCount, critPercentage, DM_CRITSTR), destination);
        end
end

function DamageMeters_PrintHelp(command)
        local index, help;
        for index, help in DamageMeters_helpTable do
                if (1 == string.find(help, "dm"..command)) then
                        DMPrint(help);
                        return;
                end
        end
end

function DamageMeters_ListCommands()
        local index, help;
        for index, help in DamageMeters_helpTable do
                DMPrint(help);
        end
end

function DamageMeters_Help()
        DamageMeters_ShowVersion();
        DMPrint(DM_MSG_HELP);
end

-- /script DM_MemInfo();
function DM_MemInfo()
        DMPrint("VISIBLE = "..DMT_VISIBLE);
        DMPrint("ACTIVE = "..DMT_ACTIVE..", "..table.getn(DamageMeters_tables[DMT_ACTIVE]).." entries.");
        DMPrint("SAVED = "..DMT_SAVED..", "..table.getn(DamageMeters_tables[DMT_SAVED]).." entries.");
end

function DamageMeters_Save()
        DMPrint(DM_MSG_SAVE);
        
        -- This makes the saved table just a reference to the active, so when the active
        -- is cleared it kills the saved.
        --DamageMeters_tables[DMT_SAVED] = DamageMeters_tables[DMT_ACTIVE];

        -- Clear the saved.
        DamageMeters_DoClear(DMT_SAVED, 0, true);

        -- Duplicate the table.
        DamageMeters_tables[DMT_SAVED] = DM_clone(DamageMeters_tables[DMT_ACTIVE]);
end

function DamageMeters_Restore()
        if (DamageMeters_tables[DMT_SAVED] and (table.getn(DamageMeters_tables[DMT_SAVED]) > 0)) then
                DMPrint(DM_MSG_RESTORE);
                DamageMeters_tables[DMT_ACTIVE] = DamageMeters_tables[DMT_SAVED];
                DamageMeters_tables[DMT_SAVED] = {};
        else
                DMPrint(DM_ERROR_NOSAVEDTABLE);
        end
end

function DamageMeters_Merge()
        local sourceTableIx = DMT_SAVED;
        local destTableIx = DMT_ACTIVE;

        for sourceIx, playerStruct in DamageMeters_tables[sourceTableIx] do
                local destIx = GetPlayerIndex(playerStruct.player, destTableIx);
                if (nil == destIx) then
                                        
                end
        end
end

function DamageMeters_Swap(silent)
        local tempTable = {};

        if (not silent) then
                DMPrint(format(DM_MSG_SWAP, table.getn(DamageMeters_tables[DMT_ACTIVE]), table.getn(DamageMeters_tables[DMT_SAVED])));
                DMPrintD("DMT_ACTIVE = "..DMT_ACTIVE..", DMT_SAVED = "..DMT_SAVED);
        end

        DMT_ACTIVE = DMT_SAVED;
        DMT_SAVED = (DMT_SAVED == DM_TABLE_B) and DM_TABLE_A or DM_TABLE_B;
        if (not DamageMeters_IsQuantityFight(DamageMeters_quantity)) then
                DMT_VISIBLE = DMT_ACTIVE;
        end

        -- Regen frame to hide any buttons now not needed.
        DamageMeters_frameNeedsToBeGenerated = true;
        --DMPrintD("Frame dirty: Swap.");
end

function DamageMeters_MemClear()
        DMPrint(DM_MSG_MEMCLEAR);
        DamageMeters_tables[DMT_SAVED] = {};
end

function DamageMeters_SetTextOptions(arg1)
        if (arg1 == "") then
                DMPrint(DM_ERROR_MISSINGARG);
                DamageMeters_PrintHelp("text");
                return;
        end

        DamageMeters_textOptions[DamageMeters_Text_RANK] = false;
        DamageMeters_textOptions[DamageMeters_Text_NAME] = false;
        DamageMeters_textOptions[DamageMeters_Text_TOTALPERCENTAGE] = false;
        DamageMeters_textOptions[DamageMeters_Text_LEADERPERCENTAGE] = false;
        DamageMeters_textOptions[DamageMeters_Text_VALUE] = false;
        DamageMeters_textOptions[DamageMeters_Text_DELTA] = false;

        if (arg1 == "0") then
                return; 
        end

        local i;
        for i = 1, string.len(arg1) do
                local char = string.lower(string.sub(arg1, i, i));

                if (char == "n") then
                        DamageMeters_textOptions[DamageMeters_Text_NAME] = true;
                elseif (char == "r") then
                        DamageMeters_textOptions[DamageMeters_Text_RANK] = true;
                elseif (char == "p") then
                        DamageMeters_textOptions[DamageMeters_Text_TOTALPERCENTAGE] = true;
                elseif (char == "l") then
                        DamageMeters_textOptions[DamageMeters_Text_LEADERPERCENTAGE] = true;
                elseif (char == "v") then
                        DamageMeters_textOptions[DamageMeters_Text_VALUE] = true;
                elseif (char == "d") then
                        DamageMeters_textOptions[DamageMeters_Text_DELTA] = true;
                end
        end
end

function DamageMeters_SetColorScheme(arg1)
        local showUsage = false;
        local colorScheme;
        if (not arg1 or arg1 == "") then
                showUsage = true;
        else
                colorScheme = tonumber(arg1);
                if (colorScheme < 1 or colorScheme > DamageMeters_colorScheme_MAX) then
                        showUsage = true;
                end     
        end
        
        if (showUsage) then
                DamageMeters_PrintHelp("color");

                local i;
                for i=1, DamageMeters_colorScheme_MAX do
                        DMPrint(i..": "..DamageMeters_colorScheme_STRING[i]);
                end

                return;
        end

        DMPrint(DM_MSG_SETCOLORSCHEME..DamageMeters_colorScheme_STRING[colorScheme]);
        DamageMeters_colorScheme = colorScheme;
end

function DamageMeters_UpdateRaidMemberClasses()
        local numRaidMembers = GetNumRaidMembers();
        local name, rank, subgroup, level, class, fileName, zone, online, isDead;
        local i;
        for i = 1,numRaidMembers do
                name, rank, subgroup, level, class, fileName, zone, online, isDead = GetRaidRosterInfo(i);

                local tableIndex = DamageMeters_GetPlayerIndex(name);
                if (tableIndex) then
                        DamageMeters_tables[DMT_ACTIVE][tableIndex].class = class;
                end

                if (DMT_VISIBLE ~= DMT_ACTIVE) then
                        tableIndex = DamageMeters_GetPlayerIndex(name, DMT_VISIBLE);
                        if (tableIndex) then
                                DamageMeters_tables[DMT_VISIBLE][tableIndex].class = class;
                        end
                end
        end
end

function DamageMeters_GetGroupRelation(player)
        
        if (player == UnitName("Player")) then
                return DamageMeters_Relation_SELF;
        end

        local numPartyMembers = GetNumPartyMembers();
        if (numPartyMembers > 0) then
                for i=1,5 do
                        local partyUnitName = "party"..i;
                        local partyName = UnitName(partyUnitName);
                        if (partyName == player) then
                                return DamageMeters_Relation_PARTY;
                        end
                end                     
        end

        local numRaidMembers = GetNumRaidMembers();
        if (numRaidMembers > 0) then
                local name, rank, subgroup, level, class, fileName, zone, online, isDead;
                local i;
                for i = 1,numRaidMembers do
                        name, rank, subgroup, level, class, fileName, zone, online, isDead = GetRaidRosterInfo(i);
                        if (name == player) then
                                return DamageMeters_Relation_FRIENDLY;
                        end
                end
        end

        return -1;
end

function DamageMeters_SetVisibleInParty(arg1)
        if (arg1 and arg1 ~= "") then
                local arg = string.lower(arg1);
                if (arg == "y") then
                        DamageMeters_flags[DMFLAG_visibleOnlyInParty] = true;
                        DamageMeters_UpdateVisibility(true);
                elseif (arg == "n") then
                        DamageMeters_flags[DMFLAG_visibleOnlyInParty] = false;
                        DamageMeters_UpdateVisibility(true);
                else
                        DMPrint(DM_ERROR_INVALIDARGS);
                        DamageMeters_PrintHelp("visinparty");
                end
        end

        DMPrint(DM_MSG_SETVISINPARTY..(DamageMeters_flags[DMFLAG_visibleOnlyInParty] and DM_MSG_TRUE or DM_MSG_FALSE));
end

function DamageMeters_SetAutoCount(arg1)
        if (not arg1 or arg1 == "") then
                DMPrint(DM_ERROR_MISSINGARG);
                DamageMeters_PrintHelp("autocount");
                return;
        end

        local newAutoCountLimit = tonumber(arg1);
        if (not newAutoCountLimit or newAutoCountLimit <= 0 or newAutoCountLimit > DamageMeters_BARCOUNT_MAX) then
                DMPrint(DM_ERROR_INVALIDARG);
                return;
        end

        DMPrint(DM_MSG_SETAUTOCOUNT..newAutoCountLimit);
        DamageMeters_autoCountLimit = newAutoCountLimit;
        DamageMeters_ForceNormalView();
end
--[[
function DamageMeters_ToggleOnlySyncWithGroup()
        DamageMeters_flags[DMFLAG_onlySyncWithGroup] = not DamageMeters_flags[DMFLAG_onlySyncWithGroup];
        if (DamageMeters_flags[DMFLAG_onlySyncWithGroup]) then
                DMPrint(DM_MSG_SYNCINGROUPON);
        else
                DMPrint(DM_MSG_SYNCINGROUPOFF);
        end
end

function DamageMeters_TogglePermitAutoJoin()
        DamageMeters_flags[DMFLAG_permitAutoSyncChanJoin] = not DamageMeters_flags[DMFLAG_permitAutoSyncChanJoin];
        if (DamageMeters_flags[DMFLAG_permitAutoSyncChanJoin]) then
                DMPrint(DM_MSG_AUTOSYNCJOINON);
        else
                DMPrint(DM_MSG_AUTOSYNCJOINOFF);
        end
end
]]--

-----------------------------------------

function DamageMeters_FrameDropDown_OnLoad()
        --DMPrint("this = "..this:GetName());
        UIDropDownMenu_Initialize(this, DamageMeters_FrameDropDown_Initialize, "MENU");
end

function DamageMeters_FrameDropDown_Initialize()

        -- If level 2
        if ( UIDROPDOWNMENU_MENU_LEVEL == 2 ) then
                -- If this is the sort style menu then create dropdown
                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_SORT ) then
                        local index;
                        for index = 1,DamageMeters_Sort_MAX do
                                info = {};
                                info.text = DamageMeters_Sort_STRING[index];
                                info.value = index;
                                info.func = DamageMetersFrame_TitleButton_SetSort;
                                if ( index == DamageMeters_sort ) then
                                        info.checked = 1;
                                end
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        end
                        return; 
                end

                -- If this is the quantity menu then create dropdown
                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_VISIBLEQUANTITY ) then
                        local index;
                        for index = 1,DamageMeters_Quantity_MAX do
                                info = {};
                                info.text = DamageMeters_GetQuantityString(index);
                                info.value = index;
                                info.func = DamageMetersFrame_TitleButton_SetQuantity;
                                if ( index == DamageMeters_quantity ) then
                                        info.checked = 1;
                                end
                                info.swatchFunc = DamageMeters_SetQuantityColor;
                                -- Set the swatch color info
                                info.hasColorSwatch = 1;
                                info.r = DamageMeters_quantityColor[index][1];
                                info.g = DamageMeters_quantityColor[index][2];
                                info.b = DamageMeters_quantityColor[index][3];
                                info.opacity = 1.0 - DamageMeters_quantityColor[index][4];
                                info.cancelFunc = DamageMeters_CancelQuantityColorSettings;
                                info.hasOpacity = 1;
                                info.opacityFunc = DamageMeters_SetQuantityColorOpacity;
                                --info.keepShownOnClick = 1;
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        end

                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Show fight data as PS.
                        info = {};
                        info.text = DM_MENU_SHOWFIGHTASPS;
                        if (DamageMeters_flags[DMFLAG_showFightAsPS]) then
                                info.checked = 1;
                        end
                        info.func = DamageMeters_ToggleShowFightAsPS;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Set for all
                        info = {};
                        info.text = DM_MENU_SETCOLORFORALL;
                        info.func = DamageMeters_SetQuantityColorAllOnClick;
                        info.swatchFunc = DamageMeters_SetQuantityColorAll;
                        info.hasColorSwatch = 1;
                        info.r = DamageMeters_quantityColor[DamageMeters_quantity][1];
                        info.g = DamageMeters_quantityColor[DamageMeters_quantity][2];
                        info.b = DamageMeters_quantityColor[DamageMeters_quantity][3];
                        info.hasOpacity = 1;
                        info.opacity = 1.0 - DamageMeters_quantityColor[DamageMeters_quantity][4];
                        info.opacityFunc = DamageMeters_SetQuantityColorOpacityAll;
                        info.notCheckable = 1;
                        info.keepShownOnClick = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Default Colors
                        info = {};
                        info.text = DM_MENU_DEFAULTCOLORS;
                        info.notCheckable = 1;
                        info.func = DamageMeters_SetDefaultColors;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Show Total
                        info = {};
                        info.text = DM_MENU_SHOWTOTAL;
                        if (DamageMeters_flags[DMFLAG_showTotal]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_showTotal;
                        info.func = DamageMetersMenu_ToggleVariableAndRegen;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        return; 
                end

                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_QUANTITYFILTER ) then
                        local index;
                        for index = 1, DamageMeters_Quantity_MAX do
                                info = {};
                                info.text = DamageMeters_GetQuantityString(index);
                                info.value = index;
                                info.func = DamageMetersFrame_TitleButton_ToggleCycleQuantity;
                                info.keepShownOnClick = 1;
                                if ( DamageMeters_quantitiesFilter[index] ) then
                                        info.checked = 1;
                                end
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        end

                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Apply filter to auto-cycle
                        info = {};
                        info.text = DM_MENU_APPLYFILTERTOAUTOCYCLING;
                        if (DamageMeters_flags[DMFLAG_applyFilterToAutoCycle]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_applyFilterToAutoCycle;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Apply filter to manual-cycle
                        info = {};
                        info.text = DM_MENU_APPLYFILTERTOMANUALCYCLING;
                        if (DamageMeters_flags[DMFLAG_applyFilterToManualCycle]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_applyFilterToManualCycle;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Cycle text
                        info = {};
                        info.text = DM_MENU_QUANTCYCLE;
                        if (DamageMeters_flags[DMFLAG_cycleVisibleQuantity]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.func = DamageMeters_ToggleCycleVisibleQuant;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        return; 
                end

                --[[
                -- If this is the color scheme menu then create dropdown
                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_COLORSCHEME ) then
                        local index;
                        for index = 1, DamageMeters_colorScheme_MAX do
                                info = {};
                                info.text = DamageMeters_colorScheme_STRING[index];
                                info.value = index;
                                info.func = DamageMetersFrame_TitleButton_SetColorScheme;
                                if ( index == DamageMeters_colorScheme ) then
                                        info.checked = 1;
                                end
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        end
                        return; 
                end
                ]]--

                local barCounts = {3,5,10,15,20,25,30,35,40};

                -- If this is the Bar count menu then create dropdown
                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_BARCOUNT ) then
                        local index;
                        for index = 1, table.getn(barCounts) do
                                info = {};
                                info.text = barCounts[index];
                                info.value = barCounts[index];
                                info.func = DamageMetersFrame_TitleButton_SetCount;
                                if ( info.value == DamageMeters_barCount ) then
                                        info.checked = 1;
                                end
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        end

                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Show Max
                        info = {};
                        info.text = DM_MENU_SHOWMAX;
                        if (DMVIEW_MAX == DamageMeters_viewMode) then
                                info.checked = 1;
                        end
                        info.func = DamageMeters_ToggleMaxBars;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        return; 
                end

                -- If this is the Bar auto count menu then create dropdown
                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_AUTOCOUNTLIMIT ) then
                        local index;
                        for index = 1, table.getn(barCounts) do
                                info = {};
                                info.text = barCounts[index];
                                info.value = barCounts[index];
                                info.func = DamageMetersFrame_TitleButton_SetAutoCount;
                                if ( info.value == DamageMeters_autoCountLimit ) then
                                        info.checked = 1;
                                end
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        end
                        return; 
                end

                -- If this is the Bar auto count menu then create dropdown
                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_REPORT ) then
                        local reportTypes = {"f", "c", "s", "p", "r", "g", "o"};
                        local index;
                        for index = 1, table.getn(reportTypes) do
                                info = {};
                                info.text = DM_MENU_REPORTNAMES[index];
                                info.value = reportTypes[index];
                                info.func = DamageMetersFrame_TitleButton_Report;
                                info.notCheckable = 1;
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        end
                        
                        info = {};
                        info.text = DM_MENU_REPORTCHANNEL;
                        info.notCheckable = 1;
                        info.func = function() StaticPopup_Show("DM_REPORT_CHANNEL") end;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        info = {};
                        info.text = DM_MENU_REPORTWHISPER;
                        info.notCheckable = 1;
                        info.func = function() StaticPopup_Show("DM_REPORT_WHISPER") end;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Reset Pos
                        info = {};
                        info.text = DM_MENU_HELP;
                        info.notCheckable = 1;
                        info.func = DamageMeters_ShowReportHelp;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        return;
                end

                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_MEMORY ) then
                        -- Session
                        if (DamageMeters_tableInfo[DMT_SAVED].sessionLabel) then
                                info = {};
                                info.isTitle =  true;
                                info.notCheckable = 1;
                                info.text = DM_MENU_SAVEDSESSION..DamageMeters_tableInfo[DMT_SAVED].sessionLabel.." "..DamageMeters_tableInfo[DMT_SAVED].sessionIndex;
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);      
                        end

                        -- Save
                        info = {};
                        info.text = DM_MENU_SAVE;
                        info.notCheckable = 1;
                        info.func = DamageMeters_Save;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Clear
                        info = {};
                        info.text = DM_MENU_CLEAR;
                        info.notCheckable = 1;
                        info.func = DamageMeters_MemClear;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Restore
                        info = {};
                        info.text = DM_MENU_RESTORE;
                        info.notCheckable = 1;
                        info.func = DamageMeters_Restore;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Swap
                        info = {};
                        info.text = DM_MENU_SWAP;
                        info.notCheckable = 1;
                        info.func = DamageMeters_Swap;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Accumulate
                        info = {};
                        info.text = DM_MENU_ACCUMULATEINMEMORY;
                        if (DamageMeters_flags[DMFLAG_accumulateToMemory]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.func = DamageMeters_ToggleAccumulateToMemory;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        return;
                end

                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_POSITION ) then
                        -- Reset Pos
                        info = {};
                        info.text = DM_MENU_RESETPOS;
                        info.notCheckable = 1;
                        info.func = DamageMeters_ResetPos;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Lock Pos
                        info = {};
                        if (DamageMeters_flags[DMFLAG_positionLocked]) then
                                info.text = DM_MENU_UNLOCKPOS;
                        else
                                info.text = DM_MENU_LOCKPOS;
                        end
                        info.notCheckable = 1;
                        info.func = DamageMeters_ToggleLockPos;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Resize left
                        info = {};
                        info.text = DM_MENU_RESIZELEFT;
                        info.value = DamageMeters_Text_RANK;
                        if (DamageMeters_flags[DMFLAG_resizeLeft]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_resizeLeft;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Resize left
                        info = {};
                        info.text = DM_MENU_RESIZEUP;
                        info.value = DamageMeters_Text_RANK;
                        if (DamageMeters_flags[DMFLAG_resizeUp]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_resizeUp;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        return;
                end

                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_TEXT ) then
                        local optionStrings = 
                        {
                                "RANK",
                                "NAME",
                                "TOTALPERCENTAGE",
                                "LEADERPERCENTAGE",
                                "VALUE",
                                "DELTA",
                        };

                        for index, optionString in optionStrings do
                                info = {};
                                info.text = getglobal("DM_MENU_TEXT_"..optionString);
                                info.value = getglobal("DamageMeters_Text_"..optionString);
                                if (DamageMeters_textOptions[info.value]) then
                                        info.checked = 1;
                                end
                                info.keepShownOnClick = 1;
                                info.func = DamageMeters_ToggleTextOption;
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        end
        
                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Cycle text
                        info = {};
                        info.text = DM_MENU_TEXTCYCLE;
                        if (DamageMeters_textState > 0) then
                                info.checked = 1;
                        end
                        info.func = DamageMeters_ToggleTextState;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Left Justify Text
                        info = {};
                        info.text = DM_MENU_LEFTJUSTIFYTEXT;
                        if (DamageMeters_flags[DMFLAG_justifyTextLeft]) then
                                info.checked = 1;
                        end
                        info.value = DMFLAG_justifyTextLeft;
                        info.func = DamageMetersMenu_ToggleVariableAndRegen;
                        info.keepShownOnClick = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        return;
                end

                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_EVENTDATA ) then

                        -- No Data
                        info = {};
                        info.text = DM_MENU_EVENTDATA_NONE;
                        if (DamageMeters_eventDataLevel == DamageMeters_EventData_NONE) then
                                info.checked = 1;
                        end
                        info.value = DamageMeters_EventData_NONE;
                        info.func = DamageMeters_ChangeEventDataLevel;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Self Data
                        info = {};
                        info.text = DM_MENU_EVENTDATA_PLAYER;
                        if (DamageMeters_eventDataLevel == DamageMeters_EventData_SELF) then
                                info.checked = 1;
                        end
                        info.value = DamageMeters_EventData_SELF;
                        info.func = DamageMeters_ChangeEventDataLevel;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- All Data
                        info = {};
                        info.text = DM_MENU_EVENTDATA_ALL;
                        if (DamageMeters_eventDataLevel == DamageMeters_EventData_ALL) then
                                info.checked = 1;
                        end
                        info.value = DamageMeters_EventData_ALL;
                        info.func = DamageMeters_ChangeEventDataLevel;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Tooltip Default
                        info = {};
                        info.text = DM_MENU_SHOWEVENTDATATOOLTIP;
                        if (DamageMeters_flags[DMFLAG_showEventTooltipsFirst]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_showEventTooltipsFirst;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        return;
                end

                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_SYNC ) then
                        --[[
                        -- Title
                        info = {};
                        info.isTitle =  true;
                        info.notCheckable = 1;
                        if (DamageMeters_syncChannel == "") then
                                info.text = DM_MENU_NOSYNCCHAN;
                        else
                                info.text = DM_MENU_SYNCCHAN..DamageMeters_syncChannel;
                        end
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        ]]--
                        -- Session
                        if (DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel) then
                                info = {};
                                info.isTitle =  true;
                                info.notCheckable = 1;
                                info.text = DM_MENU_SESSION..DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel.." "..DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex;
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);      
                        end
                        
                        info = {};
                        info.text = DM_MENU_STARTNEWSESSION;
                        info.notCheckable = 1;
                        info.func = function() StaticPopup_Show("DM_START_NEW_SESSION") end;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        info = {};
                        info.text = DM_MENU_SYNCGROUPDATA;
                        info.notCheckable = 1;
                        info.func = DamageMeters_Sync;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        info = {};
                        info.text = DM_MENU_SYNCREADY;
                        info.notCheckable = 1;
                        info.func = DamageMeters_SyncReady;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        info = {};
                        info.text = DM_MENU_SYNCPAUSE;
                        info.notCheckable = 1;
                        info.func = DamageMeters_SyncPause;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        info = {};
                        info.text = DM_MENU_SYNCCLEAR;
                        info.notCheckable = 1;
                        info.func = DamageMeters_SyncClear;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        --[[
                        -- Title 2
                        info = {};
                        info.isTitle =  true;
                        info.notCheckable = 1;
                        info.text = DM_MENU_JOINSYNCCHAN;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        info = {};
                        info.text = DM_MENU_SYNCLEAVECHAN;
                        info.notCheckable = 1;
                        info.func = DamageMeters_SyncLeaveChan;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        info = {};
                        info.text = DM_MENU_SYNCBROADCASTCHAN;
                        info.notCheckable = 1;
                        info.func = DamageMeters_SyncBroadcastChan;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        ]]--

                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        --[[
                        info = {};
                        info.text = DM_MENU_PERMITSYNCAUTOJOIN;
                        if (DamageMeters_flags[DMFLAG_permitAutoSyncChanJoin]) then
                                info.checked = 1;
                        end
                        info.func = DamageMeters_TogglePermitAutoJoin;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        info = {};
                        info.text = DM_MENU_CLEARONAUTOJOIN;
                        if (DamageMeters_flags[DMFLAG_autoClearOnChannelJoin]) then
                                info.checked = 1;
                        end
                        info.value = DMFLAG_autoClearOnChannelJoin;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        ]]--
                        info = {};
                        info.text = DM_MENU_ENABLEDMM;
                        if (DamageMeters_flags[DMFLAG_enableDMM]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_enableDMM;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        --[[
                        info = {};
                        info.text = DM_MENU_ONLYSYNCWITHGROUP;
                        if (DamageMeters_flags[DMFLAG_onlySyncWithGroup]) then
                                info.checked = 1;
                        end
                        info.func = DamageMeters_ToggleOnlySyncWithGroup;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        ]]--
                        info = {};
                        info.text = DM_MENU_AUTOSYNC;
                        if (DamageMeters_flags[DMFLAG_autoSync]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_autoSync;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        
                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Help
                        info = {};
                        info.text = DM_MENU_HELP;
                        info.notCheckable = 1;
                        info.func = DamageMeters_ShowSyncHelp;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        return;
                end

                if ( UIDROPDOWNMENU_MENU_VALUE == "Debug" ) then
                        local varName, value;
                        for varName, value in DamageMeters_debug4 do
                                info = {};
                                info.text = varName;
                                if (value) then
                                        info.checked = 1;
                                end
                                info.keepShownOnClick = 1;
                                info.value = varName;
                                info.func = DamageMeters_ToggleDebugVariable;
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        end

                        return;
                end

                if ( UIDROPDOWNMENU_MENU_VALUE == "Debug Bypass" ) then
                        for index, yesno in DM_Bypass do
                                info = {};
                                info.text = index;
                                if (yesno) then
                                        info.checked = 1;
                                end
                                info.keepShownOnClick = 1;
                                info.value = index;
                                info.func = DamageMeters_ToggleBypass;
                                UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);
                        end
                end

                if ( UIDROPDOWNMENU_MENU_VALUE == DM_MENU_GENERAL ) then
                        -- Player Always Visible
                        info = {};
                        info.text = DM_MENU_PLAYERALWAYSVISIBLE;
                        if (DamageMeters_flags[DMFLAG_playerAlwaysVisible]) then
                                info.checked = 1
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_playerAlwaysVisible;
                        info.func = DamageMetersMenu_ToggleVariableAndRegen;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);      

                        -- visible in party
                        info = {};
                        info.text = DM_MENU_VISINPARTY;
                        if (DamageMeters_flags[DMFLAG_visibleOnlyInParty]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.func = DamageMeters_ToggleVisibleInParty;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Group members only
                        info = {};
                        info.text = DM_MENU_GROUPMEMBERSONLY;
                        if (DamageMeters_flags[DMFLAG_groupMembersOnly]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_groupMembersOnly;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Add pet to player
                        info = {};
                        info.text = DM_MENU_ADDPETTOPLAYER;
                        if (DamageMeters_flags[DMFLAG_addPetToPlayer]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.func = DamageMeters_ToggleAddPetToPlayer;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Reset on combat
                        info = {};
                        info.text = DM_MENU_RESETONCOMBATSTARTS;
                        if (DamageMeters_flags[DMFLAG_resetWhenCombatStarts]) then
                                info.checked = 1;
                        end
                        info.keepShownOnClick = 1;
                        info.func = DamageMeters_ToggleResetWhenCombatStarts;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Group DPS Mode
                        info = {};
                        info.text = DM_MENU_GROUPDPSMODE;
                        if (DamageMeters_flags[DMFLAG_groupDPSMode]) then
                                info.checked = 1
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_groupDPSMode;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);      

                        -- Constant Update
                        info = {};
                        info.text = DM_MENU_CONSTANTVISUALUPDATE;
                        if (DamageMeters_flags[DMFLAG_constantVisualUpdate]) then
                                info.checked = 1
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_constantVisualUpdate;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);      

                        -- Clear when joining party.
                        info = {};
                        info.text = DM_MENU_CLEARWHENJOINPARTY;
                        if (DamageMeters_flags[DMFLAG_clearWhenJoinParty]) then
                                info.checked = 1
                        end
                        info.keepShownOnClick = 1;
                        info.value = DMFLAG_clearWhenJoinParty;
                        info.func = DamageMetersMenu_ToggleVariable;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);      

                        -- Spacer
                        info = {};
                        info.disabled = 1;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        -- Restore defaults
                        info = {};
                        info.text = DM_MENU_RESTOREDEFAULTOPTIONS;
                        info.notCheckable = 1;
                        info.func = DamageMeters_DoRestoreDefaultOptions;
                        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

                        return;
                end
                
                DamageMetersPlugin_DropDownInitialize();
                return;
        end

        -- Title
        info = {};
        info.isTitle =  true;
        info.text = "DamageMeters "..DamageMeters_VERSIONSTRING;
        UIDropDownMenu_AddButton(info);

        --[[
        -- Boss mode. 
        if (DamageMeters_bInBossMode) then
                info = {};
                info.isTitle =  true;
                
                info.text = "* In Boss Mode *";
                UIDropDownMenu_AddButton(info);
        end
        ]]--

        -- Hide
        info = {};
        if (DamageMetersFrame:IsVisible()) then
                info.text = DM_MENU_HIDE;
        else
                info.text = DM_MENU_SHOW;
        end
        info.notCheckable = 1;
        info.func = DamageMetersBar_ToggleShow;
        -- Just a test.  These fields are only used if noob tooltips are enabled.
        --info.tooltipTitle = info.text;
        --info.tooltipText = "Hello!";
        UIDropDownMenu_AddButton(info);

        -- Clear
        info = {};
        info.text = DM_MENU_CLEAR;
        info.notCheckable = 1;
        info.func = DamageMeters_Clear;
        UIDropDownMenu_AddButton(info);

        -- Pause
        info = {};
        info.text = DM_MENU_PAUSE;
        if (DM_Pause_Not ~= DamageMeters_pauseState) then
                info.checked = 1;
        end
        info.keepShownOnClick = 1;
        info.func = DamageMeters_TogglePause;
        UIDropDownMenu_AddButton(info);

        -- Minimize
        info = {};
        info.text = DM_MENU_MINIMIZE;
        if (DMVIEW_MIN == DamageMeters_viewMode) then
                info.checked = 1;
        end
        --info.keepShownOnClick = 1;
        info.func = DamageMeters_ToggleMiniMode;
        UIDropDownMenu_AddButton(info);

        -- Spacer
        info = {};
        info.disabled = 1;
        UIDropDownMenu_AddButton(info);

        -- General
        info = {};
        info.text = DM_MENU_GENERAL;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        -- Report
        info = {};
        info.text = DM_MENU_REPORT;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        -- Position
        info = {};
        info.text = DM_MENU_POSITION;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        -- Count
        info = {};
        info.text = DM_MENU_BARCOUNT;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        -- AutoCount
        info = {};
        info.text = DM_MENU_AUTOCOUNTLIMIT;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        -- Sort
        info = {};
        info.text = DM_MENU_SORT;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        -- Quantity
        info = {};
        info.text = DM_MENU_VISIBLEQUANTITY;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        -- Quantity Filter
        info = {};
        info.text = DM_MENU_QUANTITYFILTER;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        --[[
        -- Bar Colors
        info = {};
        info.text = DM_MENU_COLORSCHEME;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);
        ]]--

        -- Bar Colors
        info = {};
        info.text = DM_MENU_MEMORY;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        -- text optionss
        info = {};
        info.text = DM_MENU_TEXT;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        -- Event Options
        info = {};
        info.text = DM_MENU_EVENTDATA;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        -- Sync Options
        info = {};
        info.text = DM_MENU_SYNC;
        --info.notClickable = 1;
        info.hasArrow = 1;
        info.func = nil;
        info.notCheckable = 1;
        UIDropDownMenu_AddButton(info);

        if (DamageMeters_debugEnabled) then
                -- Debug Menu
                info = {};
                info.text = "Debug";
                --info.notClickable = 1;
                info.hasArrow = 1;
                info.func = nil;
                info.notCheckable = 1;
                UIDropDownMenu_AddButton(info);         

                -- Bypass Debug Menu
                info = {};
                info.text = "Debug Bypass";
                --info.notClickable = 1;
                info.hasArrow = 1;
                info.func = nil;
                info.notCheckable = 1;
                UIDropDownMenu_AddButton(info);         
        end
        
        DamageMetersPlugin_DropDownInitialize();
end

--------------------------------------------

function DamageMetersFrame_TitleButton_OnLoad()
        DamageMeters_TitleButtonText:SetText("Damage Meters");
        -- Color tables not initialized yet.
        --local color = DamageMeters_quantityColor[DamageMeters_quantity];
        --DamageMetersFrame_TitleButton:SetBackdropColor(color[1], color[2], color[3], color[4]);
        this:RegisterForClicks("LeftButtonDown", "LeftButtonUp", "RightButtonUp");
end

function DamageMetersFrame_TitleButton_OnClick()
        local button = arg1;    

        --DMPrint("DamageMetersFrame_TitleButton_OnClick : "..button);

        if ( button  == "LeftButton" ) then
                if ( this:GetButtonState() == "PUSHED" ) then
                        DamageMetersFrame:StopMovingOrSizing();
                else
                        if (not DamageMeters_flags[DMFLAG_positionLocked] and not DamageMetersFrame.isLocked) then
                                DamageMetersFrame:StartMoving();
                        end
                end
        elseif ( button == "RightButton" ) then
                DamageMeters_ShowMainMenu();
        end
end

function DamageMeters_ShowMainMenu()
        local frame = DamageMetersFrame_TitleButton;
        local distance;
        distance = ( UIParent:GetWidth() - frame:GetRight() );
        
        --DMPrint("distance = "..distance);
        local menuMoveDist = 250;
        if ( distance <= menuMoveDist ) then
                local newOffset = distance - menuMoveDist;
                --DMPrint("Too close, new offset = "..newOffset);
                ToggleDropDownMenu(1, nil, DamageMetersFrameDropDown, "DamageMetersFrameDropDown", newOffset, 0);
        else
                ToggleDropDownMenu(1, nil, DamageMetersFrameDropDown, "DamageMetersFrameDropDown", 0, 0);
        end
end

function DamageMetersFrame_TitleButton_SetSort()
        DamageMeters_SetSort(this.value);
end

function DamageMetersFrame_TitleButton_SetQuantity()
        DamageMeters_flags[DMFLAG_cycleVisibleQuantity] = false;
        DamageMeters_SetQuantity(this.value, true);
end

function DamageMetersFrame_TitleButton_ToggleCycleQuantity()
        DamageMeters_quantitiesFilter[this.value] = not DamageMeters_quantitiesFilter[this.value];
end

function DamageMetersFrame_TitleButton_SetColorScheme()
        DamageMeters_SetColorScheme(this.value);
end

function DamageMetersFrame_TotalButton_OnLoad()
        DamageMeters_TotalButtonText:SetText("Damage Meters");
end

function DamageMeters_ToggleTextOption()
        DamageMeters_textOptions[this.value] = not DamageMeters_textOptions[this.value];

        if (DamageMeters_textState > 0) then
                -- If we have turned off all text options, make sure we stop cycling.
                local option;
                for option = 1, DamageMeters_Text_MAX do
                        if (DamageMeters_textOptions[option]) then
                                return;
                        end
                end
                -- Fell through...turn off cycling.
                DMPrintD("Turned off all text options: disabling text cycling.");
                DamageMeters_textState = 0;
        end
end

function DamageMeters_ToggleTextState()
        if (DamageMeters_textState < 1) then
                DamageMeters_textStateStartTime = GetTime();

                -- Set the text state to the first set option.
                local nextState;
                for nextState = 1, DamageMeters_Text_MAX do
                        if (DamageMeters_textOptions[nextState]) then
                                DamageMeters_textState = nextState;
                                DMPrintD("Setting text state to "..DamageMeters_textState);
                                return;
                        end
                end

                -- Cycling enabled but no text options turned on: turn on the name option.
                DamageMeters_textOptions[DamageMeters_Text_NAME] = true;
                DamageMeters_textState = DamageMeters_Text_NAME;
                DMPrintD("Setting text state to (and enabling) "..DamageMeters_textState);
        else
                DamageMeters_textState = 0;
                DMPrintD("Setting text state to 0.");
        end
end

function DamageMeters_ToggleCycleVisibleQuant()
        if (DamageMeters_flags[DMFLAG_cycleVisibleQuantity]) then
                DamageMeters_flags[DMFLAG_cycleVisibleQuantity] = false;
        else
                DamageMeters_flags[DMFLAG_cycleVisibleQuantity] = true;
                DamageMeters_currentQuantStartTime = GetTime();
        end
end

function DamageMeters_CycleQuant(manual, useFilter)
        DamageMeters_flags[DMFLAG_cycleVisibleQuantity] = not manual;
        local newQuant = DamageMeters_quantity;
        repeat
                newQuant = newQuant + 1;
                if (newQuant > DamageMeters_Quantity_MAX) then
                        newQuant = 1;
                end

                if (newQuant == DamageMeters_quantity) then
                        newQuant = 1;
                        DamageMeters_quantitiesFilter[newQuant] = true;
                        DMPrintD("No quantities selected for cycling, aborting loop.");
                end
        until ((not useFilter) or DamageMeters_quantitiesFilter[newQuant]);

        DamageMeters_SetQuantity(newQuant, true);
end

function DamageMeters_CycleQuantBack(manual, useFilter)
        DamageMeters_flags[DMFLAG_cycleVisibleQuantity] = not manual;
        local newQuant = DamageMeters_quantity;
        repeat
                newQuant = newQuant - 1;
                if (newQuant < 1) then
                        newQuant = DamageMeters_Quantity_MAX;
                end

                if (newQuant == DamageMeters_quantity) then
                        newQuant = 1;
                        DamageMeters_quantitiesFilter[newQuant] = true;
                        DMPrintD("No quantities selected for cycling, aborting loop.");
                end
        until ((not useFilter) or DamageMeters_quantitiesFilter[newQuant]);

        DamageMeters_SetQuantity(newQuant, true);
end


function DamageMeters_ToggleVisibleInParty()
        DamageMeters_flags[DMFLAG_visibleOnlyInParty] = not DamageMeters_flags[DMFLAG_visibleOnlyInParty];
        DamageMeters_UpdateVisibility(true);
end

function DamageMeters_ToggleAddPetToPlayer()
        DamageMeters_flags[DMFLAG_addPetToPlayer] = not DamageMeters_flags[DMFLAG_addPetToPlayer];
        if (DamageMeters_flags[DMFLAG_addPetToPlayer]) then
                DMPrint(DM_MSG_ADDINGPETTOPLAYER);
        else
                DMPrint(DM_MSG_NOTADDINGPETTOPLAYER);
                return;
        end

        -- This code automatically merges pet information into player's.

        local playerName = UnitName("Player");
        local playerIndex = DamageMeters_GetPlayerIndex(playerName);
        if (not playerIndex or playerIndex < 1) then
                DamageMeters_AddValue(playerName, 0, DM_DOT, DamageMeters_Relation_SELF, DamageMeters_Quantity_DAMAGE, "[Pet]");
                playerIndex = DamageMeters_GetPlayerIndex(playerName);
                if (not playerIndex or playerIndex < 1) then
                        DMPrint(DM_ERROR_NOROOMFORPLAYER);
                        return;
                end
        end
        local target = DamageMeters_tables[DMT_ACTIVE][playerIndex];

        local index;
        local tableN = table.getn(DamageMeters_tables[DMT_ACTIVE]);
        for index = tableN, 1, -1 do
                local playerStruct = DamageMeters_tables[DMT_ACTIVE][index];
                if (DamageMeters_Relation_PET == playerStruct.relationship) then
                        DMPrint(format(DM_MSG_PETMERGE, playerStruct.player));

                        local quantIndex;
                        for quantIndex = 1, DMI_MAX do
                                target.dmiData[quantIndex].q = target.dmiData[quantIndex].q + playerStruct.dmiData[quantIndex].q;
                                target.dmiData[quantIndex].hitCount = target.dmiData[quantIndex].hitCount + playerStruct.dmiData[quantIndex].hitCount;
                                target.dmiData[quantIndex].critCount = target.dmiData[quantIndex].critCount + playerStruct.dmiData[quantIndex].critCount;
                        end
                        
                        target.lastTime = (target.lastTime > playerStruct.lastTime) and target.lastTime or playerStruct.lastTime;

                        table.remove(DamageMeters_tables[DMT_ACTIVE], index);
                        DamageMeters_frameNeedsToBeGenerated = true;
                        --DMPrintD("Frame dirty: Pet removed.");
                end
        end
end

function DamageMetersFrame_TitleButton_SetCount()
        DamageMeters_SetCount(this.value);
        CloseMenus();
end

function DamageMetersFrame_TitleButton_SetAutoCount()
        DamageMeters_SetAutoCount(this.value);
end

function DamageMetersFrame_TitleButton_Report()
        local command = format("%s%d", this.value, DamageMeters_barCount);
        DamageMeters_Report(command);
        CloseMenus();
end

--------------------------------------------

function DamageMeters_BarDropDown_OnLoad()
        UIDropDownMenu_Initialize(this, DamageMeters_BarFrameDropDown_Initialize, "MENU");
end

function DamageMeters_BarFrameDropDown_Initialize()
        DamageMetersTooltip:Hide();

        -- Header
        info = {};
        if (DamageMeters_clickedBarIndex) then
                info.text = DamageMeters_tables[DMT_VISIBLE][DamageMeters_clickedBarIndex].player;
        else
                info.text = "";
        end
        info.notClickable = 1;
        info.isTitle = 1;
        UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL);

        -- Delete
        info = {};
        info.text = DM_MENU_DELETE;
        info.notCheckable = 1;
        info.func = DamageMetersBar_DeleteEntry;
        UIDropDownMenu_AddButton(info); 

        -- Ban
        info = {};
        info.text = DM_MENU_BAN;
        info.notCheckable = 1;
        info.func = DamageMetersBar_BanEntry;
        UIDropDownMenu_AddButton(info); 

        -- Clear above
        info = {};
        info.text = DM_MENU_CLEARABOVE;
        info.notCheckable = 1;
        info.func = DamageMetersBar_ClearAboveEntry;
        UIDropDownMenu_AddButton(info); 

        -- Clear below
        info = {};
        info.text = DM_MENU_CLEARBELOW;
        info.notCheckable = 1;
        info.func = DamageMetersBar_ClearBelowEntry;
        UIDropDownMenu_AddButton(info); 

        -- Event Data
        info = {};
        info.text = DM_MENU_PLAYERREPORT;
        info.notCheckable = 1;
        info.func = DamageMetersBar_DoPlayerReport;
        UIDropDownMenu_AddButton(info); 

        -- Event Data
        info = {};
        info.text = DM_MENU_EVENTREPORT;
        info.notCheckable = 1;
        info.func = DamageMetersBar_DumpPlayerEvents;
        UIDropDownMenu_AddButton(info); 

        -- Spacer
        info = {};
        info.disabled = 1;
        UIDropDownMenu_AddButton(info);

        -- Clear banned
        info = {};
        info.text = DM_MENU_CLEARBANNED;
        info.notCheckable = 1;
        info.func = DamageMeters_ClearBanned;
        UIDropDownMenu_AddButton(info); 
end

function DamageMetersBar_DeleteEntry()
        --DMPrint("DamageMetersBar_DeleteEntry "..this:GetName());

        DamageMeters_DoDelete(DamageMeters_clickedBarIndex);
end

function DamageMetersBar_BanEntry()
        --DMPrint("DamageMetersBar_BanEntry "..this:GetID());
        DamageMeters_DoBan(DamageMeters_clickedBarIndex);
        DamageMeters_clickedBarIndex = nil;
end

function DamageMetersBar_ClearAboveEntry()
        --DMPrint("DamageMetersBar_ClearAboveEntry "..this:GetID());

        local index;
        for index = 1, (DamageMeters_clickedBarIndex - 1) do
                table.remove(DamageMeters_tables[DMT_VISIBLE], 1);
        end

        DamageMeters_frameNeedsToBeGenerated = true;
        DMPrintD("Frame dirty: ClearAboveEntry.");
        DamageMeters_clickedBarIndex = nil;
end

function DamageMetersBar_ClearBelowEntry()
        --DMPrint("DamageMetersBar_ClearBelowEntry");
        DamageMeters_Clear(DamageMeters_clickedBarIndex);
        DamageMeters_clickedBarIndex = nil;
end

function DamageMeters_DoDelete(index)
        table.remove(DamageMeters_tables[DMT_VISIBLE], index);
        DamageMeters_frameNeedsToBeGenerated = true;
        DamageMeters_clickedBarIndex = nil;
end

function DamageMeters_DoBan(index)
        if (index <= 0 or index > table.getn(DamageMeters_tables[DMT_VISIBLE])) then
                DMPrint(DM_ERROR_INVALIDARG);
                return;
        end

        DamageMeters_bannedTable[DamageMeters_tables[DMT_VISIBLE][index].player] = 1;
        DamageMeters_DoDelete(index);
end

function DamageMeters_ListBanned()
        local index, name, unused;
        index = 1;
        DMPrint(DM_MSG_LISTBANNED);
        for name, unused in DamageMeters_bannedTable do
                DMPrint(index..": "..name);
                index = index + 1;
        end
end

function DamageMeters_ClearBanned()
        DMPrint(DM_MSG_CLEARBANNED);
        DamageMeters_bannedTable = {};
end

function DamageMeters_IsBanned(newPlayer)
        local name, unused;
        for name, unused in DamageMeters_bannedTable do
                if (name == newPlayer) then
                        return true;
                end
        end     

        return false;
end

function DamageMetersBar_ToggleShow()
        if (DamageMetersFrame:IsVisible()) then
                DMPrint(DM_MSG_HOWTOSHOW);
        end

        DamageMeters_ToggleShow();
end

function DamageMeters_Sync(msg)

        if (not DamageMeters_CheckSyncChan()) then
                return;
        end

        DamageMeters_syncEvents = false;
        local delaySync = false;
        if (type(msg) == "string") then
                for     optionLetter, optionValue in string.gfind(msg, "(%a)(%d*)") do
                        if (optionLetter == "d") then
                        --DMPrintD("d found");
                                if (optionValue) then
                                        --DMPrintD("optionValue = "..optionValue);
                                        local timeUntilSync = tonumber(optionValue);
                                        if (timeUntilSync and timeUntilSync > 0) then
                                                DamageMeters_syncStartTime = GetTime() + timeUntilSync;                 
                                                DamageMeters_SendSyncMsg(string.format("<Sync incoming in %d seconds.>", timeUntilSync));
                                                delaySync = true;
                                        end
                                end
                        elseif (optionLetter == "e") then
                                --DMPrintD("e found");
                                DamageMeters_syncEvents = true;
                        end
                end
        end

        if (not delaySync) then
                DamageMeters_DoSync();
        end
end

function DamageMeters_SyncStart(label)
        if (not label or label == "") then
                DMPrint(DM_ERROR_MISSINGARG);
                return;
        end     
        if (not DamageMeters_CheckSyncChan()) then
                return;
        end

        DamageMeters_SyncLabel(label);
        if (DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex == 1) then
                DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex = 0;
        end
        DamageMeters_SyncPause();
        DamageMeters_SyncClear();
end

function DamageMeters_SyncLabel(label)
        if (not label or label == "") then
                DMPrint(DM_ERROR_MISSINGARG);
                return;
        end     

        if (DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel ~= label) then
                DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex = 1;
        end
        DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel = label;
        DMPrintD(label..", new label = "..DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel);

        DMPrint(string.format(DM_MSG_SETLABEL, DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel, DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex));
end

function DamageMeters_DoSync()
        DamageMeters_SyncRequest();
        DamageMeters_SyncReport();
        --DamageMeters_requestSyncWhenReportDone = true;
end

function DamageMeters_SyncReport()

        if (not DamageMeters_CheckSyncChan()) then
                return;
        end

        if (table.getn(DamageMeters_syncMsgQueue) > 0) then
                DMPrintD("Already reporting.");
                return;
        end
        
        DamageMeters_LastSyncTime = GetTime();
        
        if (DamageMeters_syncEvents) then
                DMPrint(DM_MSG_SYNCEVENTS, nil, true);
        else
                DMPrint(DM_MSG_SYNC, nil, true);
        end
        --local channelName = GetChannelName(DamageMeters_syncChannel)  
        --SendChatMessage(DamageMeters_SYNCSTART, "CHANNEL", nil, channelName);
        --Telepathy.sendMessage(DamageMeters_SYNC_ID, DamageMeters_SYNCSTART, "RAID");
        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, DamageMeters_SYNCSTART, "RAID");

        DamageMeters_DoSyncReport();

        -- Add finishing msg to queue, including any extra data.
        if (1) then
                local msg = string.format("%s", DamageMeters_SYNCEND);
                DamageMeters_AddMsgToSyncQueue(msg);
        end
end

function DamageMeters_DoSyncReport()

        if (not DamageMeters_CheckSyncChan()) then
                return;
        end

        local startIndex = 1;
        local endIndex = table.getn(DamageMeters_tables[DMT_ACTIVE]);
        --DMPrintD("sync indexes: "..startIndex.."->"..endIndex.."("..table.getn(DamageMeters_tables[DMT_ACTIVE])..")");

        --local channelName = GetChannelName(DamageMeters_syncChannel)

        local index;
        for index = startIndex, endIndex do
                local info = DamageMeters_tables[DMT_ACTIVE][index];
                local playerName = info.player;
                if (DamageMeters_debug4.syncSelfTestMode) then
                        playerName = playerName.."x";
                end

                local msg = DamageMeters_GenerateQuantityString(playerName, info);
                DamageMeters_AddMsgToSyncQueue(msg);
        end

        -----
        if (DamageMeters_syncEvents) then
                -- Dump events for ourselves only.

                local index, playerStruct;
                for index, playerStruct in DamageMeters_tables[DMT_ACTIVE] do 
                        DamageMeters_SyncSendPlayerEvents(playerStruct);
                end
        end
        -- Reset variable: we are done with it.
        DamageMeters_syncEvents = false;

        -------------

        local queueSize = table.getn(DamageMeters_syncMsgQueue);
        DamageMeters_sendMsgQueueBar:Show();
        DamageMeters_sendMsgQueueBar:SetMinMaxValues(0, queueSize);
        DamageMeters_sendMsgQueueBar:SetValue(queueSize);
        DamageMeters_sendMsgQueueBarText:SetText(DM_MENU_SENDINGBAR);
end

function DamageMeters_GenerateQuantityString(playerName, info)
        local msg = DMSYNC_PREFIX.." "..playerName;
        for dmi = 1, DMI_BUILTIN_MAX do
                msg = msg.." "..info.dmiData[dmi].q.." "..info.dmiData[dmi].hitCount.." "..info.dmiData[dmi].critCount;
        end
        return msg;
end

function DamageMeters_SyncSendPlayerEvents(playerStruct)
        local playerName = playerStruct.player;
        if (DamageMeters_debug4.syncSelfTestMode) then
                playerName = playerName.."x";
        end

        local quantity;
        -- BUILTIN dmi's only--would have to name the plugin dmi explicitly because the numbers can
        -- be different on different machines.
        for quantity = 1, DMI_BUILTIN_MAX do
                local quantityStruct = playerStruct.dmiData[quantity].events;
                --DMPrintD(quantity.." hash n = "..table.getn(quantityStruct.hash));

                if (quantityStruct and quantityStruct.spellTable) then
                        if (quantityStruct.dirty) then
                                DamageMeters_BuildSpellHash(quantityStruct);
                        end

                        local hashCount = table.getn(quantityStruct.hash);
                        for hashIndex = 1, hashCount do
                                local formatStr = " %d <%s> %d %d %d %d %d %d; ";
                                local msg = DMSYNC_EVENT_PREFIX.."["..playerName.."]";
                                local msgLen = string.len(msg);

                                local eventIx;
                                -- 10 max events per msg.
                                local eventCount = math.min(10, hashCount - hashIndex + 1);
                                hashIndex = hashIndex - 1;
                                for eventIx = 1, eventCount do
                                        hashIndex = hashIndex + 1;
                                        local spell = quantityStruct.hash[hashIndex].spell;
                                        spellStruct = quantityStruct.spellTable[spell];

                                        newMsgPart = string.format(formatStr, quantity, spell,
                                                spellStruct.value, spellStruct.counts[1], spellStruct.counts[2],
                                                spellStruct.damageType, spellStruct.resistanceSum, spellStruct.resistanceCount);
                                        local newPartLen = string.len(newMsgPart);

                                        local MAX_MSG_LEN = 255;
                                        if (newPartLen + msgLen >= MAX_MSG_LEN) then
                                                --DMPrintD("Aborting at eventIx = "..eventIx);
                                                hashIndex = hashIndex - 1;
                                                break;
                                        else
                                                msg = msg..newMsgPart;  
                                                msgLen = msgLen + newPartLen;
                                        end
                                end

                                DamageMeters_AddMsgToSyncQueue(msg);
                        end
                end
        end     
end

--[[
function DamageMeters_SyncChan(arg1)
        if (not arg1 or arg1 == "") then
                DMPrint(DM_ERROR_MISSINGARG);
        else 
                local autobroadcast = false;
                local params = arg1;
                if (string.sub(params, 1, 3) == "-b ") then
                        autobroadcast = true;
                        params = string.sub(params, 4);
                end

                -- Leave the current channel if we are in a different one.
                if (params ~= DamageMeters_syncChannel) then
                        if (DamageMeters_syncChannel and (GetChannelName(DamageMeters_syncChannel) ~= 0)) then
                                LeaveChannelByName(DamageMeters_syncChannel);
                        end     
                end

                DamageMeters_syncChannel = params;
                DMPrint(DM_MSG_SYNCCHAN..DamageMeters_syncChannel);

                if (0 == GetChannelName(DamageMeters_syncChannel)) then
                        -- Autojoin the channel.
                        JoinChannelByName(DamageMeters_syncChannel, nil, DEFAULT_CHAT_FRAME:GetID());
                end

                -- Automatically remove channel from the frame's list.
                ChatFrame_RemoveChannel(DEFAULT_CHAT_FRAME, DamageMeters_syncChannel);

                if (autobroadcast) then
                        DamageMeters_SyncBroadcastChan();
                end
        end
end
]]--
--[[
function DamageMeters_SyncBroadcastChan()
        if (not DamageMeters_CheckSyncChan()) then
                --DMPrint(DM_ERROR_NOSYNCCHANNEL);
                return;
        end

        local targetChannel = "";
        if (GetNumRaidMembers() > 0) then
                targetChannel = "RAID";
        elseif (GetNumPartyMembers() > 0) then
                targetChannel = "PARTY";
        else
                DMPrint(DM_ERROR_BROADCASTNOGROUP);
                return;
        end

        local msg = DM_MSG_SYNCCHANBROADCAST..DamageMeters_syncChannel;
        SendChatMessage(msg, targetChannel, nil, nil);
end


function DamageMeters_SyncLeaveChanCmd()
        DamageMeters_SyncLeaveChan(true);
end


function DamageMeters_SyncLeaveChan(bVerbose)

        if (GetChannelName(DamageMeters_syncChannel) ~= 0) then
                if (bVerbose) then
                        DMPrint(string.format(DM_MSG_LEAVECHAN, DamageMeters_syncChannel));
                end

                LeaveChannelByName(DamageMeters_syncChannel);
        end

        DamageMeters_syncChannel = "";
end
]]--

function DamageMeters_SyncRequest()
        if (not DamageMeters_CheckSyncChan()) then
                return;
        end

        local msg = DamageMeters_SYNCREQUEST;
        if (DamageMeters_syncEvents) then
                msg = msg.." E";
        end
        if (DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel) then
                msg = string.format("%s <%s %d>", msg, DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel, DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex);
        end
        --SendChatMessage(msg, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
        --Telepathy.sendMessage(DamageMeters_SYNC_ID, msg, "RAID");
        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, msg, "RAID");
end

function DamageMeters_SyncClear()
        if (not DamageMeters_CheckSyncChan()) then
                return;
        end     

        DMPrint(DM_MSG_SYNCCLEARREQ);
        DamageMeters_Clear(0, true);

        local msg = DamageMeters_SYNCCLEARREQUEST;
        if (DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel) then
                DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex = DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex + 1;
                DMPrintD("Incremented DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex to "..DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex);
                msg = string.format("%s %s %d", msg, DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel, DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex);
        end
        --SendChatMessage(msg, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
        --Telepathy.sendMessage(DamageMeters_SYNC_ID, msg, "RAID");
        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, msg, "RAID");
end

function DamageMeters_CheckSyncChan(bSilent)
        if (GetNumRaidMembers() > 1 or GetNumPartyMembers() > 1) then
                return true;
        end

        return false;
end

function DamageMeters_SyncPingRequest()
        DMPrint(DM_MSG_PINGING);
        --SendChatMessage(DamageMeters_SYNCPINGREQ, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
        --Telepathy.sendMessage(DamageMeters_SYNC_ID, DamageMeters_SYNCPINGREQ, "RAID");
        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, DamageMeters_SYNCPINGREQ, "RAID");
end

function DamageMeters_SyncPause()
        DMPrint(DM_MSG_SYNCPAUSEREQ);
        --SendChatMessage(DamageMeters_SYNCPAUSEREQ, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
        --Telepathy.sendMessage(DamageMeters_SYNC_ID, DamageMeters_SYNCPAUSEREQ, "RAID");
        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, DamageMeters_SYNCPAUSEREQ, "RAID");

        if (DM_Pause_Pause ~= DamageMeters_pauseState) then
                DamageMeters_pauseState = DM_Pause_Paused;
                DamageMeters_CompletePauseChange();
        end
end

function DamageMeters_SyncUnpause(silent, details)
        if (not silent) then
                DMPrint(DM_MSG_SYNCUNPAUSEREQ);
        end
        local msg = DamageMeters_SYNCUNPAUSEREQ;
        if (details) then
                msg = msg.." "..details;
        end
        --SendChatMessage(msg, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
        --Telepathy.sendMessage(DamageMeters_SYNC_ID, msg, "RAID");
        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, msg, "RAID");

        if (DM_Pause_Not ~= DamageMeters_pauseState) then
                DamageMeters_pauseState = DM_Pause_Not;
                DamageMeters_CompletePauseChange(silent);
        end
end

function DamageMeters_SyncReady()
        DamageMeters_SetReady();
        if (DamageMeters_CheckSyncChan(true)) then
                DMPrint(DM_MSG_SYNCREADYREQ);
                --SendChatMessage(DamageMeters_SYNCREADYREQ, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                --Telepathy.sendMessage(DamageMeters_SYNC_ID, DamageMeters_SYNCREADYREQ, "RAID");
                ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, DamageMeters_SYNCREADYREQ, "RAID");
        end
end
--[[
function DamageMeters_SyncKick(arg1)
        if (not DamageMeters_CheckSyncChan()) then
                return;
        end     

        if (nil == arg1 or "" == arg1) then
                DMPrint(DM_ERROR_MISSINGARG);
                return;
        end

        --SendChatMessage(DamageMeters_SYNCKICK.." "..arg1, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
        --Telepathy.sendMessage(DamageMeters_SYNC_ID, DamageMeters_SYNCKICK.." "..arg1, "RAID");
        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, DamageMeters_SYNCKICK.." "..arg1, "RAID");
end
]]--
function DamageMeters_SyncEmote(arg1)
        if (not DamageMeters_CheckSyncChan()) then
                return;
        end     

        if (not arg1) then
                return;
        end

        local player = nil;
        local message = arg1;
        if (string.sub(arg1, 1, 1) == "@") then
                local a,b;
                for a, b in string.gfind(arg1, "@(%a+) (.+)") do
                        player = a;
                        message = b;
                end
        end

        -- Do target replacement.
        local target = UnitName("Target");
        if (nil == target) then
                target = "nobody";
        end
        local repStart, repEnd = string.find(message, "%%t");
        while (repStart ~= nil) do
                message = string.sub(message, 1, repStart - 1)..target..string.sub(message, repEnd + 1);
                repStart, repEnd = string.find(message, "%%t");
        end

        --------

        -- Send the message.
        if (nil == player) then player = ""; end
        local msg = DamageMeters_SYNCEMOTE.." <"..player.."> "..message;
        --SendChatMessage(msg, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
        --Telepathy.sendMessage(DamageMeters_SYNC_ID, msg, "RAID");
        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, msg, "RAID");

        if (player ~= "") then
                DMPrint("[DME -> "..player.."] "..UnitName("Player").." "..message, DamageMeters_SYNCEMOTECOLOR);
        else
                DMPrint("[DME] "..UnitName("Player").." "..message, DamageMeters_SYNCEMOTECOLOR);
        end
end

function DamageMeters_DecodeEmote(source, msg)
        for target, message in string.gfind(msg, "<(.*)> (.+)") do
                
                if (target ~= "") then
                        local target = string.lower(target);
                        local ourName = string.lower(UnitName("Player"));
                        if (target ~= ourName) then
                                return;
                        end

                        DMPrint("[DME "..WHISPER.."] "..source.." "..message, DamageMeters_SYNCEMOTECOLOR);
                else
                        DMPrint(source.." "..message, DamageMeters_SYNCEMOTECOLOR);
                end
        end
end

function DamageMeters_SyncPingReply(pinger)
        local msg = DamageMeters_SYNCPING.." <"..pinger.."> <"..DamageMeters_VERSIONSTRING;
        if (DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel) then
                msg = string.format("%s [%s #%d]", msg, DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel, DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex);
        end
        msg = msg..">";
        --SendChatMessage(msg, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
        --Telepathy.sendMessage(DamageMeters_SYNC_ID, msg, "RAID");
        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, msg, "RAID");
end

function DamageMeters_AddNewPlayerComplete(tableIndex, playerName, relationship)
        local index = DamageMeters_GetPlayerIndex(playerName, tableIndex);
        if (nil == index) then
                index = DamageMeters_AddNewPlayer(DamageMeters_tables[tableIndex], playerName);
                DamageMeters_SetRelationship(index, relationship);
        end

        return index;
end

function DamageMeters_Populate()
        local numPartyMembers = GetNumPartyMembers();
        local numRaidMembers = GetNumRaidMembers();

        local oldLocked = DamageMeters_listLocked;
        DamageMeters_listLocked = false;

        if (numRaidMembers > 0) then
                DamageMeters_AddNewPlayerComplete(DMT_ACTIVE, UnitName("Player"), DamageMeters_Relation_SELF);

                local name, rank, subgroup, level, class, fileName, zone, online, isDead;
                local i;
                for i = 1,numRaidMembers do
                        name, rank, subgroup, level, class, fileName, zone, online, isDead = GetRaidRosterInfo(i);
                        DamageMeters_AddNewPlayerComplete(DMT_ACTIVE, name, DamageMeters_Relation_FRIENDLY);
                end
        elseif (numPartyMembers > 0) then
                DamageMeters_AddNewPlayerComplete(DMT_ACTIVE, UnitName("Player"), DamageMeters_Relation_SELF);

                for i=1,5 do
                        local partyUnitName = "party"..i;
                        local partyName = UnitName(partyUnitName);
                        if (partyName and partyName ~= "") then
                                DamageMeters_AddNewPlayerComplete(DMT_ACTIVE, partyName, DamageMeters_Relation_PARTY);
                        end
                end     
        else
                DMPrint(DM_ERROR_POPNOPARTY);
        end

        DamageMeters_listLocked = oldLocked;
end

function DamageMeters_ToggleLock()
        DamageMeters_listLocked = not DamageMeters_listLocked;
        if (DamageMeters_listLocked) then
                DMPrint(DM_MSG_LOCKED);
        else
                DMPrint(DM_MSG_NOTLOCKED);
        end
end

function DamageMeters_CompletePauseChange(silent)
        if (not silent) then
                if (DM_Pause_Not ~= DamageMeters_pauseState) then
                        DMPrint(DM_MSG_PAUSED);
                else
                        DMPrint(DM_MSG_UNPAUSED);
                end
        end
        DamageMeters_SetBackgroundColor();

        if (DM_Pause_Not == DamageMeters_pauseState) then
                if (DamageMeters_playerInCombat) then
                        --DMPrintD("Starting combat via unpausing.");
                        DamageMeters_OnCombatStart();
                end
        else
                if (DamageMeters_inCombat) then
                        DamageMeters_OnCombatEnd();
                end
        end
end

function DamageMeters_TogglePause(silent)
        if (DM_Pause_Not ~= DamageMeters_pauseState) then
                DamageMeters_pauseState = DM_Pause_Not;
        else
                DamageMeters_pauseState = DM_Pause_Paused;
        end

        DamageMeters_CompletePauseChange(silent);
end

function DamageMeters_SetReady()
        if (DamageMeters_pauseState ~= DM_Pause_Ready) then
                DamageMeters_pauseState = DM_Pause_Ready;
                DamageMeters_CompletePauseChange(true);
                --DMPrint("DamageMeters: Ready state activated; DM will automatically unpause on the next damage event.");
        end
end

function DamageMeters_ToggleLockPos()
        DamageMeters_flags[DMFLAG_positionLocked] = not DamageMeters_flags[DMFLAG_positionLocked];
        if (DamageMeters_flags[DMFLAG_positionLocked]) then
                DamageMetersFrame:StopMovingOrSizing();
                DamageMetersFrame:EnableMouse(0);
                DMPrint(DM_MSG_POSLOCKED);
        else
                DamageMetersFrame:EnableMouse(1);
                DMPrint(DM_MSG_POSNOTLOCKED);
        end
        CloseMenus();
end

function DamageMeters_SetQuantityColor()
        local r,g,b = ColorPickerFrame:GetColorRGB();
        DamageMeters_quantityColor[UIDROPDOWNMENU_MENU_VALUE][1] = r;
        DamageMeters_quantityColor[UIDROPDOWNMENU_MENU_VALUE][2] = g;
        DamageMeters_quantityColor[UIDROPDOWNMENU_MENU_VALUE][3] = b;
        DamageMeters_flags[DMFLAG_cycleVisibleQuantity] = false;
        DamageMeters_frameNeedsToBeGenerated = true;
        DamageMeters_tablesDirty = true;
end

function DamageMeters_SetQuantityColorOpacity()
        local alpha = 1.0 - OpacitySliderFrame:GetValue();
        DamageMeters_quantityColor[UIDROPDOWNMENU_MENU_VALUE][4] = alpha;
        DamageMeters_frameNeedsToBeGenerated = true;
end

function DamageMeters_CancelQuantityColorSettings(previousValues)
        if ( previousValues.r ) then
                DamageMeters_quantityColor[UIDROPDOWNMENU_MENU_VALUE][1] = previousValues.r;
                DamageMeters_quantityColor[UIDROPDOWNMENU_MENU_VALUE][2] = previousValues.g;
                DamageMeters_quantityColor[UIDROPDOWNMENU_MENU_VALUE][3] = previousValues.b;
                if (previousValues.opacity) then
                        --DMPrint("previousValues.opacity = "..previousValues.opacity);
                        DamageMeters_quantityColor[UIDROPDOWNMENU_MENU_VALUE][4] = 1.0 - previousValues.opacity
                end
        end
        DamageMeters_frameNeedsToBeGenerated = true;
end

function DamageMeters_SetQuantityColorAllOnClick()
        local frame = this:GetParent();

        frame.text = DM_MENU_SETCOLORFORALL;
        frame.func = DamageMeters_SetQuantityColorAll;
        frame.swatchFunc = DamageMeters_SetQuantityColorAll;
        frame.hasColorSwatch = 1;
        frame.r = DamageMeters_quantityColor[DamageMeters_quantity][1];
        frame.g = DamageMeters_quantityColor[DamageMeters_quantity][2];
        frame.b = DamageMeters_quantityColor[DamageMeters_quantity][3];
        frame.hasOpacity = 1;
        frame.opacity = 1.0 - DamageMeters_quantityColor[DamageMeters_quantity][4];
        frame.opacityFunc = DamageMeters_SetQuantityColorOpacityAll;
        frame.notCheckable = 1;

        ColorPickerFrame.frame = frame;
        CloseMenus();
        UIDropDownMenuButton_OpenColorPicker(frame);
end

function DamageMeters_SetDefaultColors()
        DamageMeters_quantityColor = {};
        local quant;
        for quant = 1, DamageMeters_Quantity_MAX do
                DamageMeters_quantityColor[quant] = DM_clone(DM_QUANTDEFS[quant].defaultColor);
        end
        DamageMeters_frameNeedsToBeGenerated = true;
end

function DamageMeters_SetQuantityColorAll()
        local r,g,b = ColorPickerFrame:GetColorRGB();
        local index;
        for index = 1, DamageMeters_Quantity_MAX do
                DamageMeters_quantityColor[index][1] = r;
                DamageMeters_quantityColor[index][2] = g;
                DamageMeters_quantityColor[index][3] = b;
        end
        DamageMeters_frameNeedsToBeGenerated = true;
end

function DamageMeters_SetQuantityColorOpacityAll()
        local alpha = 1.0 - OpacitySliderFrame:GetValue();
        local index;
        for index = 1, DamageMeters_Quantity_MAX do
                DamageMeters_quantityColor[index][4] = alpha;
        end
        DamageMeters_frameNeedsToBeGenerated = true;
end

function DamageMeters_ToggleResetWhenCombatStarts()
        DamageMeters_flags[DMFLAG_resetWhenCombatStarts] = not DamageMeters_flags[DMFLAG_resetWhenCombatStarts];
        -- Only print message if we weren't called from the menu option.
        if (not this.value) then
                DMPrint(DM_MSG_RESETWHENCOMBATSTARTSCHANGE..(DamageMeters_flags[DMFLAG_resetWhenCombatStarts] and DM_MSG_TRUE or DM_MSG_FALSE));
        end
end

function DamageMeters_ToggleShowFightAsPS()
        DamageMeters_flags[DMFLAG_showFightAsPS] = not DamageMeters_flags[DMFLAG_showFightAsPS];
        -- Only print message if we weren't called from the menu option.
        if (this and not this.value) then
                DMPrint(DM_MSG_SHOWFIGHTASPS..(DamageMeters_flags[DMFLAG_showFightAsPS] and DM_MSG_TRUE or DM_MSG_FALSE));
        end
        DamageMeters_SetQuantity(DamageMeters_quantity, true);
end

function DamageMeters_ToggleNormalAndFight()
        local newQuant = DM_QUANTDEFS[DamageMeters_quantity].toggleQuant;
        if (newQuant ~= nil) then
                DamageMeters_SetQuantity(newQuant, true);
        end
end

function DamageMeters_OnCombatStart()
        DamageMeters_combatStartTime = GetTime();
        DamageMeters_combatEndTime = DamageMeters_combatStartTime;
        DamageMeters_inCombat = true;
        --DMPrintD("Starting combat...");

        -- Clear the combat table.
        DamageMeters_DoClear(DMT_FIGHT, 0, true);
end

function DamageMeters_OnCombatEnd()
        DamageMeters_combatEndTime = GetTime();
        --DMPrintD("Ending combat, duration = "..(DamageMeters_combatEndTime - DamageMeters_combatStartTime));
        DamageMeters_inCombat = false;

        -- This is...complicated.
        --DamageMeters_tableInfo[DMT_ACTIVE].totalCombatTime = (DamageMeters_combatEndTime - DamageMeters_combatStartTime) + DamageMeters_tableInfo[DMT_ACTIVE].totalCombatTime;
        
        if ( DamageMeters_flags[DMFLAG_autoSync] and not DamageMeters_GroupInCombat() ) then
                if (not DamageMeters_LastSyncTime or GetTime() - DamageMeters_LastSyncTime > 180) then
                        DamageMeters_DoSync();
                end
        end
        
        DamageMeters_startCombatOnNextValue = true;
end

function DamageMeters_GroupInCombat()
        if (DamageMeters_inCombat) then
                return true;
        end
        
        local num = GetNumRaidMembers();
        if ( num > 1 ) then
                for i=1, num do
                        if ( UnitAffectingCombat("raid"..i) ) then
                                return true;
                        end
                end
        else
                num = GetNumPartyMembers();
                if ( num > 1 ) then
                        for i=1, num do
                                if ( UnitAffectingCombat("party"..i) ) then
                                        return true;
                                end
                        end
                end
        end
        
        return false;
end

function DamageMeters_GetCombatValuePS(combatValue)
        local combatTime = DamageMeters_combatEndTime - DamageMeters_combatStartTime;
        if (combatTime <= 1.0) then
                combatTime = 1.0;
        end
        return combatValue / combatTime;
end

function DamageMeters_OpenReportFrame()
        CloseDropDownMenus();

        ShowUIPanel(DMReportFrame);
        DMReportFrame:SetBackdropColor(0, 0, 0, 1);
        DMSendMailScrollFrame:SetBackdropColor(0, 0, 0, 1);
        DamageMeters_UpdateReportText();
end

function DamageMeters_UpdateReportText()
        DMReportFrame_SendMailBodyEditBox:SetText(DamageMeters_reportBuffer);
        DMReportFrame_SendMailBodyEditBox:SetFocus("");
        DMReportFrame_SendMailBodyEditBox:HighlightText();
end

function DamageMeters_ReportTypeDropDown_OnLoad()
        UIDropDownMenu_Initialize(this, DamageMeters_ReportTypeDropDown_Initialize, "MENU");
end

function DamageMeters_ReportTypeDropDown_Initialize()
        local index;
        for index = 1,DamageMeters_Quantity_MAX do
                info = {};
                info.text = DM_QUANTDEFS[index].name;
                info.value = index;
                info.func = DamageMeters_ReportFrame_DoReport;
                info.notCheckable = 1
                UIDropDownMenu_AddButton(info);
        end     

        info = {};
        info.text = DM_MENU_TOTAL;
        info.value = DamageMeters_ReportQuantity_Total;
        info.func = DamageMeters_ReportFrame_DoReport;
        info.notCheckable = 1
        UIDropDownMenu_AddButton(info);

        info = {};
        info.text = DM_MENU_LEADERS;
        info.value = DamageMeters_ReportQuantity_Leaders;
        info.func = DamageMeters_ReportFrame_DoReport;
        info.notCheckable = 1
        UIDropDownMenu_AddButton(info);

        local index;
        for index = 1, 3 do
                info = {};
                info.text = getglobal("DM_MENU_EVENTS"..index);
                info.value = index;
                info.func = DamageMeters_ReportFrame_DoEventReport;
                info.notCheckable = 1
                UIDropDownMenu_AddButton(info);
        end

        if (DamageMeters_debugEnabled) then
                info = {};
                info.text = "Missed Messages";
                info.func = DamageMeters_ReportMissed;
                info.notCheckable = 1
                UIDropDownMenu_AddButton(info);
        end
end

function DamageMeters_ReportFrame_DoReport()
        DamageMeters_DoReport(this.value, "BUFFER", false, 1, DamageMeters_TABLE_MAX, "");
        DamageMeters_UpdateReportText();
end

function DamageMeters_ReportFrame_DoEventReport()
        local start = ((this.value - 1) * 20) + 1;
        DamageMeters_DoReport(DamageMeters_ReportQuantity_Events, "BUFFER", false, start, 20, "");
        DamageMeters_UpdateReportText();
end

function DamageMeters_ReportTypeButton_OnClick()
        ToggleDropDownMenu(1, nil, DMReportTypeDropDown, "DMReportTypeDropDown", 0, 0);
end

function DamageMeters_ShowReportFrame()
        DamageMeters_Report("f");
end

function DamageMeters_DumpContributors()
        local playerName, unused;
        DMPrint(table.getn(DamageMeters_contributorList).." contributors:");
        for playerName, unused in DamageMeters_contributorList do
                DMPrint(" "..playerName);
        end
end

function DamageMetersBar_DoPlayerReport()
        local destination = "BUFFER";
        DamageMeters_reportBuffer = "";

        DamageMeters_DoPlayerReport(DamageMeters_tables[DMT_VISIBLE][DamageMeters_clickedBarIndex].player, destination);

        if (destination == "BUFFER") then
                DamageMeters_OpenReportFrame();
        end
end

function DamageMetersBar_DumpPlayerEvents()
        local destination = "BUFFER";
        DamageMeters_reportBuffer = "";

        DamageMeters_DumpPlayerEvents(DamageMeters_tables[DMT_VISIBLE][DamageMeters_clickedBarIndex].player, destination, true);

        if (destination == "BUFFER") then
                DamageMeters_OpenReportFrame();
        end
end

function DamageMeters_ShowVersion()
        DMPrint(string.format(DM_MSG_VERSION, DamageMeters_VERSIONSTRING));
end

function DamageMeters_ShowReportHelp()
        DamageMeters_reportBuffer = DM_MSG_REPORTHELP;
        DamageMeters_OpenReportFrame();
        DMReportFrame_SendMailBodyEditBox:HighlightText(0, 0)
end

function DamageMeters_ShowSyncHelp()
        DamageMeters_reportBuffer = DM_MSG_SYNCHELP;
        DamageMeters_OpenReportFrame();
        DMReportFrame_SendMailBodyEditBox:HighlightText(0, 0)
end

function DamageMeters_ToggleAccumulateToMemory()
        DamageMeters_flags[DMFLAG_accumulateToMemory] = not DamageMeters_flags[DMFLAG_accumulateToMemory];      
        DMPrintD("Accumulate = "..(DamageMeters_flags[DMFLAG_accumulateToMemory] and "true" or "false"));
end

function DamageMeters_SendSyncMsg(arg1)
        if (not DamageMeters_CheckSyncChan()) then
                return;
        end

        if (not arg1 or arg1 == "") then
                DMPrint(DM_ERROR_MISSINGARG);
                return;
        end

        -- "|HPlayer:%s|h[%s]|h"
        DMPrint(format(DM_MSG_SYNCMSG, UnitName("Player"), UnitName("Player"), arg1), DamageMeters_SYNCMSGCOLOR);

        local msg = DamageMeters_SYNCMSG.." "..arg1;
        --SendChatMessage(msg, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
        --Telepathy.sendMessage(DamageMeters_SYNC_ID, msg, "RAID", nil, "BULK")
        ChatThrottleLib:SendAddonMessage("BULK", DamageMeters_SYNC_ID, msg, "RAID");
end

function DamageMeters_ToggleMaxBars(bSilent)
        DamageMeters_ToggleViewMode(DMVIEW_MAX);

        DamageMeters_frameNeedsToBeGenerated = true;
        if (not bSilent) then
                DMPrint(format(DM_MSG_MAXBARS, ((DMVIEW_MAX == DamageMeters_viewMode) and DM_MSG_TRUE or DM_MSG_FALSE)));
        end
end

function DamageMeters_ToggleViewMode(toggleMode)
        if (toggleMode == DamageMeters_viewMode) then
                DMPrintD("Setting viewmode to normal.");
                DamageMeters_viewMode = DMVIEW_NORMAL;
                DamageMeters_barCount = DamageMeters_savedBarCount;
        else
                if (DMVIEW_NORMAL == DamageMeters_viewMode) then
                        DMPrintD("Setting saved bar count to "..DamageMeters_barCount);
                        DamageMeters_savedBarCount = DamageMeters_barCount;     
                end
                DMPrintD("Setting viewmode to "..toggleMode);
                DamageMeters_viewMode = toggleMode;
        end
end

function DamageMeters_ForceNormalView()
        if (DMVIEW_NORMAL ~= DamageMeters_viewMode) then
                DamageMeters_barCount = DamageMeters_savedBarCount;
                DMPrintD("Setting viewmode to normal.");
                DamageMeters_viewMode = DMVIEW_NORMAL;
        end
end

function DamageMeters_ChangeEventDataLevel()
        DamageMeters_eventDataLevel = this.value;
        DMPrint(DM_MSG_EVENTDATALEVEL[DamageMeters_eventDataLevel]);
end

function DamageMeters_ChangeSyncEventDataLevel()
        DamageMeters_syncEventDataLevel = this.value;
        DMPrint(DM_MSG_SYNCEVENTDATALEVEL[DamageMeters_syncEventDataLevel]);
end

function DamageMeters_ReportMissed()
        local destination = "BUFFER";
        DamageMeters_reportBuffer = "";

        local msg = table.getn(DamageMeters_missedMessages).." missed messages:";
        DamageMeters_SendReportMsg(msg, destination);

        local index;
        for index = 1, table.getn(DamageMeters_missedMessages) do
                DamageMeters_SendReportMsg(DamageMeters_missedMessages[index], destination);
        end

        if (destination == "BUFFER") then
                DamageMeters_OpenReportFrame();
        end
end

function DamageMeters_ToggleDebugVariable()
        DamageMeters_debug4[this.value] = not DamageMeters_debug4[this.value];
end

function DamageMeters_ToggleBypass()
        DM_Bypass[this.value] = not DM_Bypass[this.value];
end

function DamageMeters_ProcessSyncMsg(msg, arg2)
        local dmi;
        local formatStr = DMSYNC_PREFIX.." (.+)";
        for dmi = 1, DMI_BUILTIN_MAX do
                formatStr = formatStr.." (%d+) (%d+) (%d+)";
        end

        local incQuant = {};
        local incHitCount = {};
        local incCritCount = {};
        local found;
        found, _, player, 
                        incQuant[1], incHitCount[1], incCritCount[1], 
                        incQuant[2], incHitCount[2], incCritCount[2], 
                        incQuant[3], incHitCount[3], incCritCount[3], 
                        incQuant[4], incHitCount[4], incCritCount[4], 
                        incQuant[5], incHitCount[5], incCritCount[5], 
                        incQuant[6], incHitCount[6], incCritCount[6], 
                        incQuant[7], incHitCount[7], incCritCount[7] 
                = string.find(msg, formatStr);
                
        if (found) then
                for dmi = 1, DMI_BUILTIN_MAX do
                        incQuant[dmi] = tonumber(incQuant[dmi]);
                        incHitCount[dmi] = tonumber(incHitCount[dmi]);
                        incCritCount[dmi] = tonumber(incCritCount[dmi]);
                end

                -- We have recieved data from a contributor: add him/her to the list.
                DamageMeters_contributorList[arg2] = true;
                DamageMeters_flags[DMFLAG_haveContributors] = true;

                local index = DamageMeters_GetPlayerIndex(player);
                if (not index) then
                        -- There is a problem: if the other player is parsing damage for our pet
                        -- we could erroneously add it again to our own if we have the option turned 
                        -- on.  This condition attempts to fix that problem.
                        if (DamageMeters_flags[DMFLAG_addPetToPlayer] and player == UnitName("Pet")) then
                                return;
                        end

                        index = DamageMeters_AddNewPlayer(DamageMeters_tables[DMT_ACTIVE], player);
                        DamageMeters_SetRelationship(index, DamageMeters_Relation_FRIENDLY);

                        if (not index) then
                                DMPrintD("Couldn't add new player from sync'ing.");
                                return;
                        end
                        if (DamageMeters_debug4.showSyncChanges) then
                                DMPrintD("DMSYNC("..arg2.."): Adding new player, "..player..".", nil, true);
                        end
                end
                
                local localStruct = DamageMeters_tables[DMT_ACTIVE][index];

                local quantIndex;
                for quantIndex = 1, DMI_BUILTIN_MAX do
                        if (incQuant[quantIndex] > localStruct.dmiData[quantIndex].q) then
                                if (DamageMeters_debug4.showSyncChanges) then
                                        DMPrintD("DMSYNC("..arg2.."): "..player.."["..DM_QUANTDEFS[quantIndex].name.."] "..localStruct.dmiData[quantIndex].q.." -> "..incQuant[quantIndex], nil, true);
                                end
                                localStruct.dmiData[quantIndex].q = incQuant[quantIndex];
                                DamageMeters_tablesDirty = true;
                        end

                        if (incHitCount[quantIndex] > localStruct.dmiData[quantIndex].hitCount) then
                                if (DamageMeters_debug4.showSyncChanges) then
                                        DMPrintD("DMSYNC("..arg2.."): "..player.."["..DM_QUANTDEFS[quantIndex].name.." hits] "..localStruct.dmiData[quantIndex].hitCount.." -> "..incHitCount[quantIndex], nil, true);
                                end
                                localStruct.dmiData[quantIndex].hitCount = incHitCount[quantIndex];
                                DamageMeters_tablesDirty = true;
                        end
                        if (incCritCount[quantIndex] > localStruct.dmiData[quantIndex].critCount) then
                                if (DamageMeters_debug4.showSyncChanges) then
                                        DMPrintD("DMSYNC("..arg2.."): "..player.."["..DM_QUANTDEFS[quantIndex].name.." crits] "..localStruct.dmiData[quantIndex].critCount.." -> "..incCritCount[quantIndex], nil, true);
                                end
                                localStruct.dmiData[quantIndex].critCount = incCritCount[quantIndex];
                                DamageMeters_tablesDirty = true;
                        end
                end

                return;
        end

        ----------------------

        local prefixLen = string.len(DMSYNC_EVENT_PREFIX);
        if (strsub(msg, 1, prefixLen) == DMSYNC_EVENT_PREFIX) then
                msg = strsub(msg, prefixLen + 2);
                local originalMsg = msg;
                local msgLeft = "";

                local quantity, spell, total, hit1, hit2, hit3, misc1, misc2;
                --                   name quant spell   val   c1    c2;

                local endName = string.find(msg, "]");
                local player = string.sub(msg, 1, endName - 1);

                -- may not be an index for this player if it is someone's totem or something
                local index = DamageMeters_GetPlayerIndex(player);
                if (index) then
                        msg = string.sub(msg, endName + 1);

                        local parsed;
                        repeat
                                local semicolonIndex = string.find(msg, ";");
                                if (semicolonIndex) then
                                        msgLeft = string.sub(msg, semicolonIndex + 3); -- 1 for semicolon, 1 for following space
                                        msg = string.sub(msg, 1, semicolonIndex - 1);
                                end

                                --DMPrint("Parsing '"..msg.."'");

                                local formatStr = "(%d+) <(.+)> (%d+) (%d+) (%d+) (%d+) (%d+) (%d+)";
                                parsed = false;
                                for     quantity, spell, total, hit1, hit2, misc1, misc2, misc3 in string.gfind(msg, formatStr) do
                                        parsed = true;
                                        --DMPrint("Parsed! "..player..","..quantity..","..spell..","..total..","..hit1..","..hit2);
                                        if (DamageMeters_debugEnabled) then
                                                if (string.sub(player, 1, 1) == " ") then
                                                        DMPrintD("ERROR: Player <"..player.."> starts with space: fixing.");
                                                        DMPrintD("org = >"..originalMsg.."<");
                                                        DMPrintD("msg = >"..msg.."<");
                                                        player = string.sub(player, 2);
                                                end
                                        end

                                        if ((DamageMeters_EventData_ALL == DamageMeters_eventDataLevel) or
                                                ((DamageMeters_EventData_SELF == DamageMeters_eventDataLevel) and (player == UnitName("Player")))) then
                                                --DMPrintD("Accepting >"..spell.."< event for player "..player, nil, true)

                                                quantity = tonumber(quantity);
                                                total = tonumber(total);
                                                hit1 = tonumber(hit1);
                                                hit2 = tonumber(hit2);
                                                misc1 = tonumber(misc1);
                                                misc2 = tonumber(misc2);
                                                misc3 = tonumber(misc3);

                                                local bMsgFromPlayer = (string.lower(player) == string.lower(arg2));

                                                local playerEventStruct = DamageMeters_tables[DMT_ACTIVE][index].dmiData[quantity].events;
                                                if (nil == playerEventStruct) then
                                                        if (DamageMeters_debug4.showSyncChanges) then
                                                                DMPrintD("DMSYNCE("..arg2.."): Adding new player, <"..player..">.", nil, true);
                                                        end
                                                        DMPrintD("EventTable: Adding player <"..player..">.");

                                                        DamageMeters_tables[DMT_ACTIVE][index].dmiData[quantity].events = {};
                                                        playerEventStruct = DamageMeters_tables[DMT_ACTIVE][index].dmiData[quantity].events;

                                                        playerEventStruct.spellTable = {};
                                                        playerEventStruct.hash = {};
                                                        playerEventStruct.dirty = true;
                                                end

                                                if (nil == playerEventStruct.spellTable[spell]) then
                                                        if (DamageMeters_debug4.showSyncChanges) then
                                                                DMPrintD("DMSYNCE("..arg2.."): Adding spell "..spell.." to player <"..player..">.", nil, true);
                                                        end
                                                        playerEventStruct.spellTable[spell] = {};
                                                        playerEventStruct.spellTable[spell].value = 0;
                                                        playerEventStruct.spellTable[spell].counts = {0,0};
                                                        playerEventStruct.spellTable[spell].damageType = DM_DMGTYPE_DEFAULT;
                                                        playerEventStruct.spellTable[spell].resistanceSum = 0;
                                                        playerEventStruct.spellTable[spell].resistanceCount = 0;
                                                end

                                                if (playerEventStruct.spellTable[spell].counts[DM_HIT] < hit1 or
                                                        (playerEventStruct.spellTable[spell].counts[DM_HIT] == hit1 and bMsgFromPlayer)) then
                                                        if (DamageMeters_debug4.showSyncChanges) then
                                                                DMPrintD(string.format("DMSYNCE("..arg2.."): %s %s: %d -> %d", player, spell, playerEventStruct.spellTable[spell].value, total), nil, true);
                                                        end
                                                        playerEventStruct.spellTable[spell].value = total;
                                                        playerEventStruct.spellTable[spell].counts = {hit1, hit2};
                                                        playerEventStruct.spellTable[spell].damageType = misc1;
                                                        playerEventStruct.spellTable[spell].resistanceSum = misc2;
                                                        playerEventStruct.spellTable[spell].resistanceCount = misc3;
                                                end

                                                DamageMeters_tablesDirty = true;
                                        else
                                                --DMPrintD("Rejecting >"..spell.."< event for player "..player, nil, true)
                                        end
                                end

                                msg = msgLeft;
                                msgLeft = "";
                        until ((msg == "") or (msg == " ") or (not parsed));

                        return;
                end
        end
end

function DamageMeters_AddMsgToSyncQueue(msg, bAddToFront)
        if (bAddToFront == true) then
                table.insert(DamageMeters_syncMsgQueue, 1, msg);
        else
                table.insert(DamageMeters_syncMsgQueue, msg);
        end
end

function DamageMeters_ProcessMessages()
        local now = GetTime();

        -- In the case of lag spikes the time can be very large.
        local numToSend = DamageMeters_MAXSYNCMSGPERFRAME;

        local sent = 0;
        local toSend = getn(DamageMeters_syncMsgQueue);
        if ( getn(DamageMeters_syncMsgQueue) > 0 ) then
                while ( now - DamageMeters_lastSyncMsgTime > DamageMeters_SYNCMSGSENDDELAY) do
                        if ( getn(DamageMeters_syncMsgQueue) > 0 ) then
                                sent = sent + 1;
                                --SendChatMessage(DamageMeters_syncMsgQueue[1], "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                                --Telepathy.sendMessage(DamageMeters_SYNC_ID, DamageMeters_syncMsgQueue[1], "RAID", nil, "BULK");
                                ChatThrottleLib:SendAddonMessage("BULK", DamageMeters_SYNC_ID, DamageMeters_syncMsgQueue[1], "RAID");
                                table.remove(DamageMeters_syncMsgQueue, 1);

                                numToSend = numToSend - 1;
                                if (numToSend <= 0) then
                                        DamageMeters_lastSyncMsgTime = now;
                                        break;
                                end
                        else
                                DamageMeters_lastSyncMsgTime = now;
                                break;
                        end

                        DamageMeters_lastSyncMsgTime = DamageMeters_lastSyncMsgTime + DamageMeters_SYNCMSGSENDDELAY;
                end
        else
                DamageMeters_lastSyncMsgTime = now;
        end

        if (not DamageMeters_debug4.showGCInfo) then
                local leftToSend = getn(DamageMeters_syncMsgQueue);
                -- Note: Using 2 here so periodic update messages don't cause the text to show up.
                if (leftToSend < 2) then
                        DamageMeters_sendMsgQueueBar:Hide();
                        DamageMeters_sendMsgQueueBarText:SetText("");
                else
                        DamageMeters_sendMsgQueueBar:SetValue(leftToSend);
                        DamageMeters_sendMsgQueueBarText:SetText(DM_MENU_SENDINGBAR);
                end
        end

        -------------------------

        local processed = 0;
        local toProcess = getn(DamageMeters_syncIncMsgQueue);
        local initialElapsed = now - DamageMeters_lastSyncIncMsgTime;

        if (toProcess > 0) then
                DamageMeters_lastProcessQueueTime = now;
        end

        if ( getn(DamageMeters_syncIncMsgQueue) > 0 ) then
                while (now - DamageMeters_lastSyncIncMsgTime > DamageMeters_SYNCMSGPROCESSDELAY) do
                        if ( getn(DamageMeters_syncIncMsgQueue) > 0 ) then
                                processed = processed + 1;
                                DamageMeters_ProcessSyncMsg(DamageMeters_syncIncMsgQueue[1], DamageMeters_syncIncMsgSourceQueue[1]);
                                
                                table.remove(DamageMeters_syncIncMsgQueue, 1);
                                table.remove(DamageMeters_syncIncMsgSourceQueue, 1);
                        else
                                DamageMeters_lastSyncIncMsgTime = now;
                                break;
                        end

                        DamageMeters_lastSyncIncMsgTime = DamageMeters_lastSyncIncMsgTime + DamageMeters_SYNCMSGPROCESSDELAY;
                end
        else
                DamageMeters_lastSyncIncMsgTime = now;
        end

        -- GCInfo doesn't show if this code runs.  Hope this doesn't break anything.
        if (not DamageMeters_debug4.showGCInfo) then
                local leftToProcess = getn(DamageMeters_syncIncMsgQueue);
                if ((now - DamageMeters_lastProcessQueueTime) < 1.0) then
                        DamageMeters_processMsgQueueBar:Show();
                        DamageMeters_processMsgQueueBar:SetValue(leftToProcess);
                        DamageMeters_processMsgQueueBarText:SetText(DM_MENU_PROCESSINGBAR);
                elseif (DamageMeters_processMsgQueueBar:IsVisible()) then
                        DamageMeters_processMsgQueueBar:Hide();
                        DamageMeters_processMsgQueueBarText:SetText("");
                end
        end

        if (DamageMeters_debug4.showSyncQueueInfo) then
                if (sent > 0 or processed > 0) then
                        DMPrint(string.format("%.4f: sent = %d/%d, processed = %d/%d", initialElapsed, sent, toSend, processed, toProcess));
                end
        end
end


function DamageMeters_ToggleVariable(varName)
        DamageMeters_flags[varName] = not DamageMeters_flags[varName];
end

function DamageMeters_ToggleMiniMode(bSilent)
        DamageMeters_ToggleViewMode(DMVIEW_MIN);

        DamageMeters_frameNeedsToBeGenerated = true;
        if (not bSilent) then
                DMPrint(format(DM_MSG_MINBARS, ((DMVIEW_MIN == DamageMeters_viewMode) and DM_MSG_TRUE or DM_MSG_FALSE)));
        end
end

function DamageMeters_DoRestoreDefaultOptions()
        DamageMeters_SetDefaultOptions();
        DamageMeters_frameNeedsToBeGenerated = true;
end

function DamageMetersMenu_ToggleVariable()
        DamageMeters_ToggleVariable(this.value);
end

function DamageMetersMenu_ToggleVariableAndRegen()
        DamageMeters_ToggleVariable(this.value);
        DamageMeters_frameNeedsToBeGenerated = true;
end

-- Put updating here that must happen even when the meters are hidden.
-- Note that this mostly is for TitanPanel's benefit, as we still need values
-- updated and sorted when hidden because of the titan plugin.
function DamageMetersHiddenFrame_OnUpdate()
        -- Process the message queue.
        DamageMeters_ProcessMessages(elapsed);  

        -- Update combat end time so that the Titan DPS display can be accurate.
        if (DamageMeters_inCombat) then
                DamageMeters_combatEndTime = GetTime();
        end

        -- If we aren't visible then we fire off the table updating (sorting) here.
        if (not DamageMeters_flags[DMFLAG_isVisible]) then
                local bSecondHasPassedSinceLastBarUpdate = (GetTime() - DamageMeters_lastBarUpdateTime) > 1.0;
                if (DamageMeters_tablesDirty and (DamageMeters_flags[DMFLAG_constantVisualUpdate] or bSecondHasPassedSinceLastBarUpdate)) then
                        DamageMeters_lastBarUpdateTime = GetTime();
                        DamageMeters_UpdateTables();
                end
        end

        DamageMetersFrame_DragUpdate();
end

function DamageMeters_SyncHalt()
        if (DamageMeters_CheckSyncChan()) then
                --SendChatMessage(DamageMeters_SYNCHALT, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                --Telepathy.sendMessage(DamageMeters_SYNC_ID, DamageMeters_SYNCHALT, "RAID");
                ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, DamageMeters_SYNCHALT, "RAID");
                
                DMPrint(DM_MSG_SYNCHALTSENT);

                -- Clear the message queues.
                DamageMeters_syncMsgQueue = {};
                DamageMeters_syncIncMsgQueue = {};
                DamageMeters_syncIncMsgSourceQueue = {};
        end
end

function DamageMeters_CheckSession(tableIndex, sessionLabel, sessionIndex)
        if (sessionLabel ~= DamageMeters_tableInfo[tableIndex].sessionLabel or
                sessionIndex ~= DamageMeters_tableInfo[tableIndex].sessionIndex) then
                DMPrint(string.format("Local = <%s> #%d.  Incoming = <%s> #%d.", 
                                DamageMeters_tableInfo[tableIndex].sessionLabel, 
                                DamageMeters_tableInfo[tableIndex].sessionIndex, 
                                sessionLabel, 
                                sessionIndex));
                DamageMeters_Clear();
                DamageMeters_tableInfo[tableIndex].sessionLabel = sessionLabel;
                DamageMeters_tableInfo[tableIndex].sessionIndex = sessionIndex;
                return false;
        else
                return true;
        end
end

--[[
function DamageMeters_SyncBossStart(arg1)
        if (DamageMeters_CheckSyncChan()) then
                if (nil == arg1 or "" == arg1) then
                        DMPrint(DM_ERROR_MISSINGARG);
                        return;
                end

                local bossLabel = arg1;
                DamageMeters_DoSyncBossStart(DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel, DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex, bossLabel);

                local message = DamageMeters_SYNCBOSSSTART.." "..label;
                SendChatMessage(message, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));             
        end
end

function DamageMeters_DoSyncBossStart(mainLabel, mainIndex, bossLabel)
        if (DamageMeters_bInBossMode) then
                --!
                DMPrint("Warning: Already in boss mode, replacing current boss table with new one.");
        else
                DamageMeters_Swap();
        end

        DamageMeters_pauseState = DM_Pause_Paused;
        DamageMeters_CompletePauseChange(true);

        DamageMeters_flags[DMFLAG_accumulateToMemory] = true;

        -- SyncLabel
        DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex = 1;
        DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel = label;

        DamageMeters_bInBossMode = true;
end

function DamageMeters_SyncBossEnd()
        if (DamageMeters_CheckSyncChan()) then
                DamageMeters_DoSyncBossEnd();

                local message = DamageMeters_SYNCBOSSEND;
                SendChatMessage(message, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
        end
end

function DamageMeters_DoSyncBossEnd()
        if (DamageMeters_bInBossMode) then
                DamageMeters_pauseState = DM_Pause_Paused;
                DamageMeters_CompletePauseChange(true);

                DamageMeters_Swap();
                DamageMeters_flags[DMFLAG_accumulateToMemory] = false;
                DamageMeters_bInBossMode = false;
        else
                --!
                DMPrint("Warning: Not in boss mode, but instructed to end boss mode.  Doing nothing.");
        end
end
]]--

-------------------------------------------------------------------------------
-- Dragging

-- Drag global variables
DamageMeters_dragStartX = nil;
DamageMeters_dragStartY = nil;
DamageMeters_widthAtDragStart = nil;
DamageMeters_draggingRight = nil;
DamageMeters_draggingBottom = nil;
DamageMeters_barCountAtStart = nil;
DamageMeters_resizeLeftAtDragStart = nil;
DamageMeters_resizeUpAtDragStart = nil;

function DamageMetersFrame_OnMouseDown()
        local xLoc, yLoc = GetCursorPosition();
        --DMPrintD("local = "..xLoc..", "..yLoc);

        local effectiveScale = this:GetEffectiveScale();
        local left = this:GetLeft() * effectiveScale;
        local top = this:GetTop() * effectiveScale;
        local right = this:GetRight() * effectiveScale;
        local bottom = this:GetBottom() * effectiveScale;
        --DMPrintD("Frame: x = ("..left.."->"..right.."), y = ("..top.."->"..bottom..")");

        if (abs(xLoc - left) < 10) then
                DamageMeters_draggingRight = false;
        elseif (abs(xLoc - right) < 10) then
                DamageMeters_draggingRight = true;
        end

        if (abs(yLoc - bottom) < 10) then
                DamageMeters_draggingBottom = true;
        elseif (abs(yLoc - top) < 10) then
                DamageMeters_draggingBottom = false;
        end

        if (not DamageMeters_flags[DMFLAG_positionLocked]) then
                if (nil ~= DamageMeters_draggingRight or nil ~= DamageMeters_draggingBottom) then
                        if (DMVIEW_MAX == DamageMeters_viewMode or
                                (DMVIEW_MIN == DamageMeters_viewMode and DamageMeters_draggingBottom ~= nil)) then
                                -- Do Moving.
                                this:StartMoving();
                                this.isMoving = true;

                                --DMPrintD("Moving");
                                DamageMeters_draggingRight = nil;
                                DamageMeters_draggingBottom = nil;
                        else
                                DamageMeters_dragStartX, DamageMeters_dragStartY = GetCursorPosition(UIParent);
                                --DMPrintD("Dragging");
                        end
                end
        end
end

function DamageMetersFrame_OnMouseUp()
        if (this.isMoving == true) then
                -- Stop dragging.
                this:StopMovingOrSizing();
                this.isMoving = false;
        end
end

function DamageMetersFrame_OnDragStart()
        if ((not DamageMeters_flags[DMFLAG_positionLocked]) and this.isMoving ~= true and (DamageMeters_dragStartX ~= nil)) then
                DamageMeters_widthAtDragStart = DamageMeters_BARWIDTH;
                DamageMeters_barCountAtStart = DamageMeters_barCount;
                DamageMeters_resizeLeftAtDragStart = DamageMeters_flags[DMFLAG_resizeLeft];
                DamageMeters_resizeUpAtDragStart = DamageMeters_flags[DMFLAG_resizeUp];

                DMPrintD("DamageMetersFrame: OnDragStart ("..DamageMeters_dragStartX..", "..DamageMeters_dragStartY..")");
                DMPrintD("Effective Scale = "..this:GetEffectiveScale());

                if (DamageMeters_draggingRight) then
                        DamageMeters_flags[DMFLAG_resizeLeft] = false;
                elseif (not DamageMeters_draggingRight) then
                        DamageMeters_flags[DMFLAG_resizeLeft] = true;
                end

                if (DMVIEW_MIN == DamageMeters_viewMode) then
                        
                else
                        if (DamageMeters_draggingBottom) then
                                DamageMeters_flags[DMFLAG_resizeUp] = false;
                        elseif(not DamageMeters_draggingBottom) then
                                DamageMeters_flags[DMFLAG_resizeUp] = true;
                        end
                end
        end
end

function DamageMetersFrame_OnDragStop()
        if (this.isMoving ~= true and (DamageMeters_dragStartX ~= nil)) then
                local endX, endY = GetCursorPosition(UIParent);
                --DMPrintD("DamageMetersFrame: OnDragStop ("..endX..", "..endY..")");
                local deltaX = endX - DamageMeters_dragStartX;
                local deltaY = endY - DamageMeters_dragStartY;
                --DMPrintD("Delta = ("..deltaX..", "..deltaY..")");

                DamageMeters_flags[DMFLAG_resizeLeft] = DamageMeters_resizeLeftAtDragStart
                DamageMeters_flags[DMFLAG_resizeUp] = DamageMeters_resizeUpAtDragStart;

                DamageMeters_dragStartX = nil;
                DamageMeters_dragStartY = nil;
                DamageMeters_widthAtDragStart = nil;
                DamageMeters_draggingRight = nil;
                DamageMeters_draggingBottom = nil;
                DamageMeters_barCountAtStart = nil;
        end
end

function DamageMetersFrame_DragUpdate()
        if ((not DamageMeters_flags[DMFLAG_positionLocked]) and not this.isMoving) then
                if (DamageMeters_widthAtDragStart ~= nil) then
                        local endX, endY = GetCursorPosition(UIParent);
                        local deltaX = (endX - DamageMeters_dragStartX) / this:GetEffectiveScale();
                        local deltaY = (endY - DamageMeters_dragStartY) / this:GetEffectiveScale();

                        if (DamageMeters_draggingRight ~= nil) then
                                if (not DamageMeters_draggingRight) then
                                        deltaX = -deltaX;
                                end
                                if (DamageMeters_barCount > 20) then
                                        deltaX = deltaX / 2;
                                end

                                local newWidth = max(50, DamageMeters_widthAtDragStart + deltaX);
                                newWidth = min(600, newWidth);
                                DamageMeters_SetBarWidth(newWidth, true);
                        end

                        if (DamageMeters_draggingBottom ~= nil) then
                                if (DamageMeters_draggingBottom) then
                                        deltaY = -deltaY;
                                end
                                local barDelta = deltaY / DamageMeters_BARHEIGHT;
                                barDelta = floor(barDelta);
                                if (DamageMeters_barCount > 20) then
                                        barDelta = barDelta * 2;
                                end
                                if (barDelta ~= 0) then
                                        local newBarCount = DamageMeters_barCountAtStart + barDelta;
                                        if (((DamageMeters_barCount < 20) and newBarCount < 20 and newBarCount > 0 and DamageMeters_barCount ~= newBarCount) or
                                                ((DamageMeters_barCount > 20) and newBarCount > 20 and newBarCount < 40 and DamageMeters_barCount ~= newBarCount)) then
                                                DamageMeters_SetCount(newBarCount, true);
                                        end
                                        else
                                end
                        end
                end
        end
end

-------------------------------------------------------------------------------

DMRPS_PREFIX = "DMRPS: ";

DamageMeters_RPSChallengedList = {};
DamageMeters_RPSChallengedByList = {};

function DamageMeters_RPSChallenge(arg)
        if (not DamageMeters_CheckSyncChan()) then
                return;
        end

        if (arg == nil or arg == "") then
                DMPrint(DM_ERROR_MISSINGARG);
                return;
        end

        local found, player, move;
        found,_,player,move = string.find(arg, "(.+) (.)");
        if (not found) then
                DMPrint(DM_ERROR_INVALIDARG);
                return;
        end

        move = string.lower(move);
        if (move ~= "r" and move ~= "p" and move ~= "s") then
                DMPrint(DM_ERROR_INVALIDARG);
                return;
        end

        if (player == nil or player == "") then
                DMPrint(DM_ERROR_MISSINGARG);
                return;
        end

        player = string.lower(player);
        player = string.upper(string.sub(player, 1, 1))..string.sub(player, 2);
        DamageMeters_RPSChallengedList[player] = move;

        DMPrint(string.format(DMRPS_PREFIX..DM_MSG_RPS_CHALLENGE, player, DamageMeters_RPSmoveStrings[move]), DamageMeters_RPSCOLOR);

        local moveNumber = string.byte(move);
        DamageMeters_AddMsgToSyncQueue(string.format("%s %s", DamageMeters_SYNCRPS, player));
end

function DamageMeters_RPSChallengeReceived(player, arg)
        local defender = arg;
        if (string.lower(UnitName("Player")) == string.lower(defender)) then
                DMPrint(string.format(DMRPS_PREFIX..DM_MSG_RPS_CHALLENGED, player), DamageMeters_RPSCOLOR);

                local struct = {player = player, move = ""};
                table.insert(DamageMeters_RPSChallengedByList, struct);
        end
end

function DamageMeters_RPSResponse(arg)
        if (not DamageMeters_CheckSyncChan()) then
                return;
        end

        if (arg == nil or arg == "") then
                DMPrint(DM_ERROR_MISSINGARG);
                return;
        end

        local found, player, move;
        found,_,player,move = string.find(arg, "(.+) (.+)");
        if (not found) then
                found,_,move = string.find(arg, "(.+)");
                if (found) then
                        if (table.getn(DamageMeters_RPSChallengedByList) == 1) then
                                local index, struct;
                                for index, struct in DamageMeters_RPSChallengedByList do
                                        player = struct.player;
                                        break;
                                end
                        else
                                DMPrint(table.getn(DamageMeters_RPSChallengedByList));
                                for _player, unused in DamageMeters_RPSChallengedByList do
                                        DMPrint(player);
                                        break;
                                end

                                DMPrint(DM_MSG_RPS_MISSING_PLAYER, DamageMeters_RPSCOLOR);
                        end
                else
                        DMPrint(DM_ERROR_INVALIDARG);
                        return;
                end
        end
        if (move == nil or move == "") then
                DMPrint(DM_ERROR_MISSINGARG);
                return;
        end
        if (player == nil or player == "") then
                DMPrint(DM_ERROR_MISSINGARG);
                return;
        end
        player = string.lower(player);
        player = string.upper(string.sub(player, 1, 1))..string.sub(player, 2);

        move = string.lower(move);
        if (move ~= "r" and move ~= "p" and move ~= "s") then
                DMPrint(DM_ERROR_INVALIDARG);
                return;
        end

        local playerListIndex = nil;
        for index, struct in DamageMeters_RPSChallengedByList do
                if (struct.player == player) then
                        playerListIndex = index;
                        break;
                end
        end

        if (playerListIndex == nil) then
                DMPrint(string.format(DM_MSG_RPS_NOTCHALLENGED, player), DamageMeters_RPSCOLOR);
                return;
        end

        DMPrint(string.format(DMRPS_PREFIX..DM_MSG_RPS_YOUPLAY, DamageMeters_RPSmoveStrings[move]), DamageMeters_RPSCOLOR);

        DamageMeters_RPSChallengedByList[playerListIndex].move = move;

        local moveNumber = string.byte(move);
        DamageMeters_AddMsgToSyncQueue(string.format("%s %s %d", DamageMeters_SYNCRPSRESPONSE, player, moveNumber));
end

function DamageMeters_RPSResponseReceived(sourcePlayer, arg)
        local found, player, moveNumber;
        found,_,player,moveNumber = string.find(arg, "(.+) (%d+)");
        if (not found) then
                DMPrint("Error parsing RPS response.");
                return;
        end

        if (string.lower(player) ~= string.lower(UnitName("player"))) then
                -- Not for us.
                return;
        end

        if (DamageMeters_RPSChallengedList[sourcePlayer] ~= nil) then
                -- We were the attacker.        
                local move = string.char(tonumber(moveNumber));
                DamageMeters_RPSPrintResult(DamageMeters_RPSChallengedList[sourcePlayer], move, sourcePlayer);

                -- Send our move
                local moveNumber = string.byte(DamageMeters_RPSChallengedList[sourcePlayer]);
                DamageMeters_AddMsgToSyncQueue(string.format("%s %s %d", DamageMeters_SYNCRPSCOUNTERRESPONSE, sourcePlayer, moveNumber));

                -- Game over for us.
                DamageMeters_RPSChallengedList[sourcePlayer] = nil;
        end
end

function DamageMeters_RPSCounterresponseRecieved(sourcePlayer, arg)
        local found, player, moveNumber;
        found,_,player,moveNumber = string.find(arg, "(.+) (%d+)");
        if (not found) then
                DMPrint("Error parsing RPS response.");
                return;
        end

        if (string.lower(player) ~= string.lower(UnitName("player"))) then
                -- Not for us.
                return;
        end

        local move = string.char(tonumber(moveNumber));

        if (DamageMeters_RPSChallengedByList[sourcePlayer] ~= nil) then
                -- We were the defender.        
                local move = string.char(tonumber(moveNumber));
                DamageMeters_RPSPrintResult(DamageMeters_RPSChallengedByList[sourcePlayer], move, sourcePlayer);

                -- Game over.
                DamageMeters_RPSChallengedByList[sourcePlayer] = nil;
        end
end

function DamageMeters_RPSPrintResult(myMove, opponentsMove, opponent)
        DMPrint(string.format(DMRPS_PREFIX..DM_MSG_RPS_PLAYS, opponent, DamageMeters_RPSmoveStrings[opponentsMove]), DamageMeters_RPSCOLOR);

        if (myMove == opponentsMove) then
                DMPrint(string.format(DM_MSG_RPS_TIE, opponent), DamageMeters_RPSCOLOR);
        elseif ((opponentsMove == "r" and myMove == "s") or
                         (opponentsMove == "p" and myMove == "r") or
                         (opponentsMove == "s" and myMove == "p")) then
                DMPrint(string.format(DMRPS_PREFIX..DM_MSG_RPS_DEFEATED, opponent), DamageMeters_RPSCOLOR);
        else
                DMPrint(string.format(DMRPS_PREFIX..DM_MSG_RPS_VICTORIOUS, opponent), DamageMeters_RPSCOLOR);
        end
end

function DamageMeters_RPSReset()
        DamageMeters_queuedRPSMove = nil;
        DamageMeters_queuedRPSOpponent = nil;
        DamageMeters_bRPSAttacker = false;
end

-------------------------------------------------------------------------------

--DM_useEventCaseTableDebug = true;

local function convertPattern(patternString, patternInfo)
        -- Add % to escape all magic characters used in LUA pattern matching, except $ and %
        patternString = string.gsub(patternString,"([%^%(%)%.%[%]%*%+%-%?])","%%%1")

        -- Do these AFTER escaping the magic characters.
        patternString = string.gsub(patternString,"%%s","(.-)") -- %s to (.-)
        patternString = string.gsub(patternString,"%%d","(%%d+)") -- %d to (%d+)

        if string.find(patternString, "%$") then
                local field = { } -- fills with ordered list of $s as they appear
                local idx = 1 -- incremental index into field[]

                for i in string.gfind(patternString,"%%(%d)%$.") do
                        field[idx] = tonumber(i)
                        idx = idx + 1
                end

                for k in field do
                        for j in patternInfo do
                                if patternInfo[j] == field[k] then
                                        patternInfo[j] = tostring(k)
                                end
                        end
                end

                -- Convert them back to number, if they are number (6 will not be converted), tonumber() will not change their values.
                for j in patternInfo do
                        if tonumber(patternInfo[j]) then
                                patternInfo[j] = tonumber(patternInfo[j])
                        end
                end

                patternString = string.gsub(patternString,"%%%d%$s","(.-)") -- %1$s to (.-)
                patternString = string.gsub(patternString,"%%%d%$d","(%%d+)") -- %1$d to (%d+)

        end

        -- Escape $ now.
        patternString = string.gsub(patternString,"%$","%%$")

        -- If the pattern ends with (.-), libace it with (.+), or the capsule will be lost.
        if string.sub(patternString, -4) == "(.-)" then
                pattern = string.sub(patternString, 0, -5) .. "(.+)"
        end

        return "^"..patternString;
end



function DamageMeters_FixPatterns()
        for messageID, msgInfo in DamageMeters_msgInfo do
                if (not msgInfo.custom) then
                        local pattern = msgInfo.pattern;
                        if (nil == pattern) then
                                DMPrint("INTERNAL ERROR: Pattern for "..messageID.." = nil.");
                        else
                                pattern = convertPattern(pattern, msgInfo);
                                DMPrintD(msgInfo.pattern.." -> "..pattern, nil, true);
                                msgInfo.pattern = pattern;
                        end
                end
        end

--[[function DamageMeters_FixPatterns()
        for messageID, msgInfo in DamageMeters_msgInfo do
                if (not msgInfo.custom) then
                        local pattern = msgInfo.pattern;
                        if (nil == pattern) then
                                DMPrint("INTERNAL ERROR: Pattern for "..messageID.." = nil.");
                        else
      --AmbigousFix, needed?--
      --pattern = string.gsub(string.gsub(pattern, "(%%%d%$s)s", "%1\'s"), "%%ss", "%%s\'s")
      
      --Strip string format indicators, might seriously mess parsing up if arguments are not in the same order as in enGB/enUS
      --pattern = string.gsub(pattern, "%%%d%$(%a)", "%%%1")
      
                                pattern = string.gsub(pattern, "%.", "%%.");
                                pattern = string.gsub(pattern, "%%s", "(.+)");
                                pattern = string.gsub(pattern, "%%d", "(%%d+)");
                                DMPrintD(msgInfo.pattern.." -> "..pattern, nil, true);
                                msgInfo.pattern = pattern;
                        end
                end
        end]]

        -- Dand: Note that if you set this variable to true then all kinds of crazy stuff
        -- will be stored in this DamageMeters_eventCaseTable variable--namely, which messages
        -- where parsed how and which messages weren't parsed.  The system is pretty crude atm
        -- so you'll have to figure this out on your own, I'm afraid.
        --if (DM_useEventCaseTableDebug) then
        if (false) then
                DM_eventCaseTableDebug = DM_clone(DamageMeters_eventCaseTable);
                for msgType, msgTypeTable in DM_eventCaseTableDebug do
                        for event, eventTable in msgTypeTable do
                                for caseIx, case in eventTable do
                                        case.hitCount = 0;
                                end
                                        
                                eventTable.hitCount = 0;
                                eventTable.parseCount = 0;
                        end
                end
        end
end

-- This made global to keep it from hammering the GC.
DM_elem = {"", "", "", "", "", ""};

function DamageMeters_CheckMsgInfoAndProcess(event, message, msgInfoName, msgInfo, msgType, sourceRelationOverride, destRelationOverride)
        if (DM_Bypass["CheckMsgInfoAndProcess"] == true) then
                return;
        end     

        if (nil == msgInfo.pattern) then
                DMPrint("INTERNAL ERROR: msgInfo "..msgInfoName.." has a nil pattern.");

                DM_DUMP_TABLE(msgInfo);

                return false;
        end

        if (DM_Bypass["CheckMsgInfoAndProcess 1"] == true) then
                return;
        end     

        local found;
        DMPrintD("message: "..message);
        DMPrintD("msgInfo.pattern: "..msgInfo.pattern);
        found, _, DM_elem[1], DM_elem[2], DM_elem[3], DM_elem[4], DM_elem[5], DM_elem[6]  = string.find(message, msgInfo.pattern)
        if (found) then
                local sourceRelation = msgInfo.relationship;

                local sourceName;
                if (msgInfo.source == 0) then
                        sourceRelation = DamageMeters_Relation_SELF;
                        sourceName = UnitName("Player");
                else
                        sourceRelation = DamageMeters_Relation_FRIENDLY;
                        sourceName = DM_elem[msgInfo.source];
                        if (sourceName == nil) then
                                DMPrintD("INTERNAL ERROR: msgInfo "..msgInfoName.." has source = nil.");
                                return true;
                        end
                end
                if (sourceRelationOverride ~= nil) then
                        sourceRelation = sourceRelationOverride;
                end

                local destRelation = DamageMeters_Relation_FRIENDLY;
                local destName;
                if (nil == msgInfo.dest) then
                        destName = "";
                elseif (0 == msgInfo.dest) then
                        destRelation = DamageMeters_Relation_SELF
                        destName = UnitName("Player");
                else
                        destName = DM_elem[msgInfo.dest];
                end
                if (nil == destName) then
                        DMPrint("ERROR: destName = nil.  msgInfo.dest = "..msgInfo.dest..", event = "..event);
                        destName = "";
                end
                if (destRelationOverride ~= nil) then
                        destRelation = destRelationOverride;
                end

                local amount = 0;
                if (msgInfo.amount > 0) then
                        amount = tonumber(DM_elem[msgInfo.amount]);
                        if (nil == amount) then
                                if (msgInfo.amount == nil) then
                                        DMPrint("INTERNAL ERROR: msgInfo.amount == nil");
                                else
                                        DMPrint("INTERNAL ERROR: DM_elem[msgInfo.amount] == nil");
                                end
                                return true;
                        end
                end

                local crit = msgInfo.crit;
                if (crit == nil) then
                        DMPrintD("CRIT = NIL", {r=1, g=0, b=0}, true);
                        DM_DUMP_TABLE(msgInfo);
                        crit = DM_HIT;
                end

                local spell;
                if (nil == msgInfo.spell) then
                        spell = DM_DMG_MELEE;
                elseif (type(msgInfo.spell) == "string") then
                        spell = msgInfo.spell;
                elseif (0 == msgInfo.spell) then
                        spell = DM_DMG_MELEE;
                else
                        spell = DM_elem[msgInfo.spell];
                        if (nil == spell) then
                                DMPrintD("INTERNAL ERROR: ("..msgInfoName..") DM_elem "..msgInfo.spell.." is nil in: "..message);
                        end
                end

                local damageType;
                if (msgInfo.damageType == 0 or msgInfo.damageType == nil) then
                        damageType = DM_DMGTYPE_DEFAULT;
                else
                        damageType = DM_elem[msgInfo.damageType];
                        if (nil == damageType) then
                                DMPrint("INTERNAL ERROR: "..msgInfoName.." damageType = nil. (msgInfo.damageType = "..msgInfo.damageType..")");
                                return true;
                        end
                end

                local resisted = 0;
                --if (msgInfo.resisted) then
                --      resisted = DM_elem[msgInfo.resisted];
                --end

                --[[
                DMPrintD(event.."->"..msgInfoName.." ("..msgType..")", {r=0, g=1.0, b=0}, true);
                DMPrintD(string.format("source = %s, dest = %s, amount = %d, crit = %d, sourceRelation = %s, targetRelation = %s, spell = %s, damageType = %s",
                        sourceName, destName, amount, crit, DamageMeters_Relation_STRING[sourceRelation], DamageMeters_Relation_STRING[destRelation], spell, damageType), nil, true);
                ]]--
                if (DamageMeters_debug4.showParse) then
                        DMPrintD("["..event.."] ["..msgInfoName.."]:", {r=0,g=1,b=0}, true);
                        DMPrintD(": "..message, {r=1,g=1,b=1}, true);
                end

                --DM_CountMsg(message, msgInfoName, event);
                if (msgType == DM_MSGTYPE_DAMAGE) then
                        DamageMeters_AddDamage                  (event, sourceName, destName, amount, crit, sourceRelation, spell, damageType);
                elseif (msgType == DM_MSGTYPE_DAMAGERECEIVED) then
                        DamageMeters_AddDamageReceived  (event, destName, sourceName, amount, crit, destRelation, spell, damageType, resisted)
                elseif (msgType == DM_MSGTYPE_HEALING) then
                        DamageMeters_AddHealing                 (event, sourceName, destName, amount, crit, sourceRelation, destRelation, spell)
                else 
                        DMPrintD("INTERNAL ERROR: invalid msgType "..msgType);
                end

                return true;
        end

        return false;
end

function DamageMeters_ParseMessage(arg1, event)

        ----------------------
        -- CURING MESSAGES --
        ----------------------

        if (event == "CHAT_MSG_SPELL_SELF_BUFF" or
                event == "CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF" or
                event == "CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF") then

                for i in DM_Spellometer_Patterns do
                        local caster, spell, target;
                        local matches = {};
                        local found;

                        found,_,matches[1],matches[2],matches[3] = string.find(arg1, DM_Spellometer_Patterns[i].pattern);

                        if found then
                                local casterRelationship = DamageMeters_Relation_FRIENDLY;

                                caster = matches[DM_Spellometer_Patterns[i].caster];
                                spell = matches[DM_Spellometer_Patterns[i].spell];
                                if DM_Spellometer_Patterns[i].target ~= nil then 
                                        target = matches[DM_Spellometer_Patterns[i].target];
                                else
                                        target = UnitName("player");
                                end
                                if caster == "You" then
                                        caster = UnitName("player");
                                        casterRelationship = DamageMeters_Relation_SELF;
                                end

                                local spellFound = false;
                                for index, cureSpell in DM_CURESPELLS do
                                        if (cureSpell == spell) then
                                                spellFound = true;
                                                break;
                                        end
                                end

                                if (spellFound == true) then
                                        DamageMeters_AddValue(caster, 1, DM_HIT, casterRelationship, DamageMeters_Quantity_CURING, spell);
                                end
                                return;
                        end
                end
        end

        ----------------------
        -- REGULAR MESSAGES --
        ----------------------

        DamageMeters_StartDebugTimer(DMPROF_PARSEMESSAGE);

        --DMPrint("["..event.."]: "..arg1, nil, true);

        local eventCase = nil;
        local msgType;
        for _msgType, msgTypeTable in DamageMeters_eventCaseTable do
                eventCase = msgTypeTable[event];
                msgType = _msgType;
                if (eventCase ~= nil) then
                        break;
                end
        end

        if (DM_useEventCaseTableDebug) then
                DM_eventCaseTableDebug[msgType][event].hitCount = DM_eventCaseTableDebug[msgType][event].hitCount + 1;
        end

        if (nil == eventCase) then
                DMPrintD("INTERNAL ERROR: Attempted to parse message but no handler for it.  event = "..event);
        else
                for msgInfoIndex, msgInfoDetails in eventCase do
                        local msgInfoName = msgInfoDetails.n;
                        local msgInfo = DamageMeters_msgInfo[msgInfoName];
                        if (nil == msgInfo) then
                                DMPrintD("INTERNAL ERROR: msgInfoName "..msgInfoName.." invalid.");
                                return;
                        end
                        local sourceRelation = msgInfoDetails.sourceRelation;
                        local destRelation = msgInfoDetails.destRelation;

                        local adjustedMsgType = msgType;
                        if (nil ~= msgInfoDetails.msgType) then
                                adjustedMsgType = msgInfoDetails.msgType;
                        end

                        if (DamageMeters_CheckMsgInfoAndProcess(event, arg1, msgInfoName, msgInfo, adjustedMsgType, sourceRelation, destRelation)) then
                                if (DM_useEventCaseTableDebug) then
                                        DM_eventCaseTableDebug[msgType][event].parseCount = DM_eventCaseTableDebug[msgType][event].parseCount + 1;
                                
                                        local debugCase = DM_eventCaseTableDebug[msgType][event][msgInfoIndex];
                                        if (nil == debugCase) then
                                                DMPrintD("debugCase = nil, msgType = "..msgType..", event = "..event..", msgInfoIndex = "..msgInfoIndex);
                                        else
                                                if (nil == debugCase.parsed) then
                                                        debugCase.parsed = {};
                                                end
                                                debugCase.hitCount = debugCase.hitCount + 1;
                                                if (table.getn(debugCase.parsed) < 10) then
                                                        local parsedMsg = arg1;
                                                        table.insert(debugCase.parsed, parsedMsg);
                                                end
                                        end
                                end

                                return;
                        end
                end

                if (DM_useEventCaseTableDebug) then
                        -- If the message had a number in it we are probably interested in it.
                        for amount in string.gfind(arg1, "(%d+)") do

                                -- Eliminate numeric messages we aren't interested in:
                                -- Wierd (#) messages (buffs)
                                for player, amount, type in string.gfind(arg1, "%(%d+%)") do    return; end
                                -- GENERICPOWERGAIN_OTHER 
                                -- GENERICPOWERGAIN_SELF 
                                for player, amount, type in string.gfind(arg1, "(.+) gains (%d+) (.+)%.") do    return; end
                                for player, amount, type in string.gfind(arg1, "You gain (%d+) (.+)%.") do return; end
                                -- SPELLEXTRAATTACKSOTHER_SINGULAR etc
                                if (string.find(arg1, "extra attack")) then return; end
                                -- ITEMENCHANTMENTADDOTHEROTHER, etc
                                for player in string.gfind(arg1, "(.+) casts (.+) on (.+)'s (.+)%.") do return; end
                                for player in string.gfind(arg1, "(.+) casts (.+) on your (.+)%.") do return; end
                                for player in string.gfind(arg1, "You cast (.+) on (.+)'s (.+)%.") do return; end
                                for player in string.gfind(arg1, "You cast (.+) on your (.+)%.") do return; end
                                -- VSENVIRONMENTALDAMAGE_DROWNING_OTHER etc.
                                for player in string.gfind(arg1, "You are drowning and lose (%d+) health.") do return; end
                                for player in string.gfind(arg1, "(.+) is exhausted and and loses (%d+) health.") do return; end
                                for player in string.gfind(arg1, "You are exhausted and lose (%d+) health.") do return; end
                                for player in string.gfind(arg1, "(.+) suffers (%d+) points of fire damage.") do return; end
                                for player in string.gfind(arg1, "You suffer (%d+) points of fire damage.") do return; end
                                for player in string.gfind(arg1, "(.+) loses (%d+) health for swimming in (.+)%.") do return; end
                                for player in string.gfind(arg1, "You lose (%d+) health for swimming in (.+)%.") do return; end
                                -- SPELLPOWERDRAINOTHEROTHER etc
                                for player in string.gfind(arg1, "(.+) drains (%d+) Mana from you.") do return; end
                                for player in string.gfind(arg1, "(.+) drains (%d+) Mana from (.+)%.") do return; end
                                for player in string.gfind(arg1, "You drain (%d+) Mana from (.+)%.") do return; end
                                for player in string.gfind(arg1, "(.+) drains (%d+) Mana from you and gains (%d+)") do return; end
                                for player in string.gfind(arg1, "You drain (%d+) Mana from (.+) and gain (%d+)") do return; end
                                        
                                if (DamageMeters_debugEnabled) then
                                        DMPrintD("["..event.."]:", {r=1,g=0.25,b=0.25});
                                        DMPrintD(": "..arg1, {r=1,g=0.75,b=0.75});
                                end

                                local debugCase = DM_eventCaseTableDebug[msgType][event];
                                if (nil == debugCase.unparsed) then
                                        debugCase.unparsed = {};
                                end
                                if (table.getn(debugCase.unparsed) < 10) then
                                        local unparsedMsg = arg1;
                                        table.insert(debugCase.unparsed, unparsedMsg);
                                end
                                return;
                        end
                end
        end

        --? Right?
        DamageMeters_StopDebugTimer(DMPROF_PARSEMESSAGE);
        return;
end


-------------------------------------------------------------------------------
-- Message Parsing

function DamageMeters_ParseSyncMessage(msg, sender, method)

                -- Ignore messages from ourself.
        if (not DamageMeters_debug4.syncSelf) then
                if (sender == UnitName("Player")) then
                        return;
                end
        end

        local firstSpace = string.find(msg, " ");
        local command, commandArgs;
        if (firstSpace ~= nil) then
                command = string.sub(msg, 1, firstSpace - 1);
                commandArgs = string.sub(msg, firstSpace + 1);
        else
                command = msg;
                commandArgs = "";
        end

        if ((strsub(msg, 1, string.len(DMSYNC_PREFIX)) == DMSYNC_PREFIX) or
                        ((strsub(msg, 1, string.len(DMSYNC_EVENT_PREFIX)) == DMSYNC_EVENT_PREFIX))) then
                if (DamageMeters_debug4.syncSelf) then
                        for index = 1, DamageMeters_debug_syncTestFactor do
                                table.insert(DamageMeters_syncIncMsgQueue, msg);
                                table.insert(DamageMeters_syncIncMsgSourceQueue, sender);
                        end
                else
                        table.insert(DamageMeters_syncIncMsgQueue, msg);
                        table.insert(DamageMeters_syncIncMsgSourceQueue, sender);
                end
                return;
        elseif (command == DamageMeters_SYNCREQUEST) then
                -- Note: +1 doesn't allow for a space between prefix and params.
                local params = commandArgs;
                --DMPrintD("Incoming sync params = >"..params.."<");
                --SendChatMessage("Incoming sync params = >"..params.."<", "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                if (string.sub(params, 1, 1) == "E") then
                        --SendChatMessage("E detected: syncing events.", "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                        DamageMeters_syncEvents = true;
                        params = string.sub(params, 3);
                end
                if (params) then
                        for syncLabel, syncIndex in string.gfind(params, "<(.+) (%d+)>") do
                                syncLabel = tostring(syncLabel);
                                syncIndex = tonumber(syncIndex);
                                -- Potential error: local sessionLabel can be nil. UPDATE: I think this is fixed.
                                local currentLabel = DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel;
                                local currentIndex = DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex;
                                if (false == DamageMeters_CheckSession(DMT_ACTIVE, syncLabel, syncIndex)) then
                                        DMPrint(DM_MSG_SESSIONMISMATCH);
                                        local mismatchMsg = DamageMeters_SYNCSESSIONMISMATCH.." "..currentLabel.." "..currentIndex;
                                        --SendChatMessage(mismatchMsg, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                                        --Telepathy.sendMessage(DamageMeters_SYNC_ID, mismatchMsg, "RAID");
                                        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, mismatchMsg, "RAID");
                                        
                                else
                                        DMPrintD("Sync request recieved, session matches our own.");
                                end
                        end
                end

                local now = GetTime();
                if (now - DamageMeters_lastSyncTime > DamageMeters_MINSYNCCOOLDOWN) then
                        local ackmsg = DamageMeters_syncEvents and DM_MSG_SYNCREQUESTACKEVENTS or DM_MSG_SYNCREQUESTACK;
                        DMPrint(ackmsg.. sender, nil, true);
                        DamageMeters_lastSyncTime = now;
                        DamageMeters_SyncReport();
                else
                        DMPrint(DM_ERROR_SYNCTOOSOON);
                end
                return;
        elseif (command == DamageMeters_SYNCCLEARREQUEST) then
                DMPrint(DM_MSG_CLEARRECEIVED.. sender, nil, true);
                --SendChatMessage(DamageMeters_SYNCCLEARACK, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                --Telepathy.sendMessage(DamageMeters_SYNC_ID, DamageMeters_SYNCCLEARACK, "RAID");
                ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, DamageMeters_SYNCCLEARACK, "RAID");
                DamageMeters_Clear();
                
                -- Check for session information, and update ours if it is there.
                for newLabel, newIndex in string.gfind(commandArgs, "(.+) (%d+)") do
                        DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel = tostring(newLabel);
                        DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex = tonumber(newIndex);
                        break;
                end

                return;
        elseif (command == DamageMeters_SYNCCLEARACK) then
                --DMPrint(format(DM_MSG_CLEARACKNOWLEDGED, sender), nil, true);
                return;
        elseif (command == DamageMeters_SYNCJOINED) then
                local version = commandArgs;
                DMPrint(string.format(DM_MSG_PLAYERJOINEDSYNCCHAN, sender, version), nil, true);
                return;
        elseif (command == DamageMeters_SYNCSTART) then
                DMPrint(format(DM_MSG_RECEIVEDSYNCDATA, sender), nil, true);
                return;
        elseif (command == DamageMeters_SYNCEND) then
                -- Nothing atm.
                return;
        elseif (command == DamageMeters_SYNCPINGREQ) then
                DamageMeters_SyncPingReply(sender);
                return;
        elseif (msg == DamageMeters_SYNCHALT) then
                DMPrint(string.format(DM_MSG_SYNCHALTRECEIVED, sender), nil, true);
                -- Clear the message queues.
                DamageMeters_syncMsgQueue = {};
                DamageMeters_syncIncMsgQueue = {};
                DamageMeters_syncIncMsgSourceQueue = {};
                return;
        elseif (command == DamageMeters_SYNCPAUSEREQ) then
                DMPrint(string.format(DM_MSG_SYNCPAUSE, sender), nil, true);
                DamageMeters_pauseState = DM_Pause_Paused;
                DamageMeters_CompletePauseChange(true);
                return;
        elseif (command == DamageMeters_SYNCUNPAUSEREQ) then
                local unpauseMsg = DM_MSG_SYNCUNPAUSE;                          
                local additionalInfo = strsub(msg, string.len(DamageMeters_SYNCUNPAUSEREQ) + 2);
                if (additionalInfo and string.len(additionalInfo) > 0) then
                        unpauseMsg = unpauseMsg.." ("..additionalInfo..")";
                end

                DMPrint(string.format(unpauseMsg, sender), nil, true);
                if (DamageMeters_pauseState ~= DM_Pause_Not) then
                        DamageMeters_pauseState = DM_Pause_Not;
                        DamageMeters_CompletePauseChange(true);
                end
                return;
        elseif (command == DamageMeters_SYNCREADYREQ) then
                DMPrint(string.format(DM_MSG_SYNCREADY, sender), nil, true);
                DamageMeters_SetReady();
                return;
        elseif (command == DamageMeters_SYNCPING) then
                for     pinger, additionalInfo in string.gfind(msg, DamageMeters_SYNCPING.." <(.+)> <(.+)>") do
                        if (pinger == UnitName("Player")) then
                                DMPrint("DMSYNCPING: ".. sender..", "..additionalInfo.."");
                        end
                end
                return;
        elseif (command == DamageMeters_SYNCSESSIONMISMATCH) then
                local badSession, badIndex;
                for     badSession, badIndex in string.gfind(msg, DamageMeters_SYNCSESSIONMISMATCH.." (.+) (%d+)") do                                           
                        local mismatchMsg = string.format(DM_MSG_SYNCSESSIONMISMATCH,
                                        sender,
                                        badSession,
                                        badIndex);
                        DMPrint(mismatchMsg, nil, true);
                        return;
                end
                return;
        elseif (command == DamageMeters_SYNCMSG) then
                if (DamageMeters_flags[DMFLAG_enableDMM]) then
                        local syncMsg = commandArgs;
                        DMPrint(format(DM_MSG_SYNCMSG, sender, sender, syncMsg), DamageMeters_SYNCMSGCOLOR);
                end
                return;
        elseif (command == DamageMeters_SYNCEMOTE) then
                DamageMeters_DecodeEmote(sender, strsub(msg, string.len(DamageMeters_SYNCEMOTE) + 1));
                return;

        elseif (command == DamageMeters_SYNCRPS) then
                DamageMeters_RPSChallengeReceived(sender, strsub(msg, string.len(DamageMeters_SYNCRPS) + 2));
                return;                                 
        elseif (command == DamageMeters_SYNCRPSRESPONSE) then
                DamageMeters_RPSResponseReceived(sender, strsub(msg, string.len(DamageMeters_SYNCRPSRESPONSE) + 2));
                return;
        elseif (command == DamageMeters_SYNCRPSCOUNTERRESPONSE) then
                DamageMeters_RPSCounterresponseRecieved(sender, strsub(msg, string.len(DamageMeters_SYNCRPSCOUNTERRESPONSE) + 2));
                return;
        --[[
        elseif (command == DamageMeters_SYNCKICK) then
                local kicked = string.lower(string.sub(msg, string.len(DamageMeters_SYNCKICK) + 2));
                DMPrintD("kicked = >"..kicked.."<");
                if (string.lower(UnitName("Player")) == kicked) then
                        DMPrint(string.format(DM_MSG_KICKED, sender));
                        DamageMeters_SyncLeaveChan();
                end
                return;
        elseif (command == DamageMeters_SYNCBOSSSTART) then
                local label = commandArgs;
                --!
                DMPrint(string.format("DamageMeters: Received Boss (%s) Start command from %s.", label, sender));
                DamageMeters_DoSyncBossStart(label);
                return;
        elseif (command == DamageMeters_SYNCBOSSEND) then
                DamageMeters_DoSyncBossEnd();
                return;
        ]]--
        end
        
end




-------------------------------------------------------------------------------
-- OnEvent

function DamageMetersFrame_OnEvent()

        if (event == "PLAYER_ENTERING_WORLD") then
                DamageMeters_RegisterEvents();
                return;
        elseif (event == "PLAYER_LEAVING_WORLD") then
                DamageMeters_UnregisterEvents();
                return;
        end
        
        --[[
        if (event == "CHAT_MSG_CHANNEL_NOTICE") then
                if (arg1 == "YOU_JOINED" and 
                        string.lower(arg9) == string.lower(DamageMeters_syncChannel)) then
                        
                        -- Broadcast that we've joined.
                        --SendChatMessage(DamageMeters_SYNCJOINED.." "..DamageMeters_VERSIONSTRING, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                        --Telepathy.sendMessage(DamageMeters_SYNC_ID, DamageMeters_SYNCJOINED.." "..DamageMeters_VERSIONSTRING, "RAID");
                        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, DamageMeters_SYNCJOINED.." "..DamageMeters_VERSIONSTRING, "RAID");
                end
                return;
        end
        ]]--

        if (event == "VARIABLES_LOADED") then
                -- If a variable table is nil, reset to defaults
                local reset = (not DamageMeters_flags or not DamageMeters_quantitiesFilter or not DamageMeters_debug4)
                if (reset) then
                        DamageMeters_SetDefaultOptions()
                end
                
                -- If the saved data is of a different version clear the table.
                if (DamageMeters_loadedDataVersion ~= DamageMeters_VERSION) then
                        DMPrintD("Saved data version mismatch: loaded = "..DamageMeters_loadedDataVersion..", current = "..DamageMeters_VERSION);
                        DamageMeters_pluginDMITable = {};
                        
                        if (not reset) then
                                DamageMeters_InitTables();
                                if (DamageMeters_loadedDataVersion < 4500) then
                                        DMPrintD("Restoring default colors.");
                                        DamageMeters_SetDefaultColors();
                                end
                        end

                        DamageMeters_loadedDataVersion = DamageMeters_VERSION;
                end
                
                --Telepathy.registerListener(DamageMeters_SYNC_ID, { "RAID", "PARTY" }, DamageMeters_ParseSyncMessage);
                
                -- Reset the ages, as age values from old sessions are pointless.
                -- Reset the combat dmg too.
                local index, value;
                local now = GetTime();
                for tableIndex = 1, DMT_MAX do
                        for index, value in DamageMeters_tables[tableIndex] do
                                value.lastTime = now;
                                for quantIx = 1, DMI_MAX do
                                        value.dmiData[quantIx].lastQuantTime = now;
                                end
                        end
                end

                DamageMeters_Reset();

                DamageMeters_textStateStartTime = GetTime();
                DamageMeters_currentQuantStartTime = DamageMeters_textStateStartTime;

                if (not DamageMeters_flags[DMFLAG_isVisible]) then
                        DamageMeters_Hide();
                end

                -- Intro text.
                DamageMeters_ShowVersion();
                if (DamageMeters_flags[DMFLAG_accumulateToMemory]) then
                        DMPrint(DM_MSG_ACCUMULATING);
                end
                if (DamageMeters_debugEnabled) then
                        DMPrintD("DamageMeters: Debug enabled.");
                end
                --if (not DamageMeters_takeOnlyPlayerData) then
                --      DMPrint(DM_MSG_PLAYERONLYEVENTDATAOFF);
                --end
                
                DMPrintD("Loading DM into mode "..DamageMeters_viewMode..", count = "..DamageMeters_barCount..", saved = "..DamageMeters_savedBarCount);


                -- This is a hack--if the frame is initially hidden the stupid dropdown 
                -- menu doesn't work right and needs to be opened twice to be seen the first
                -- time.
                DMReportFrame:Hide();

                -- Make sure this is called last.
                DamageMeters_OnLoadComplete();

                -- This needs to know that DamageMeters_quantity is valid.
                DamageMeters_SetQuantity(DamageMeters_quantity, true);

                return;
        end

        -----------------------

        if ( event == "PARTY_MEMBERS_CHANGED" ) then
                DamageMeters_UpdateVisibility();
                return;
        end

        if ( event == "RAID_ROSTER_UPDATE" ) then
                DamageMeters_UpdateVisibility();
                DamageMeters_UpdateRaidMemberClasses();
                return;
        end

        if ( event == "PLAYER_REGEN_DISABLED" ) then
                --DamageMeters_startCombatOnNextValue = true;
                DamageMeters_playerInCombat = true;

                if (not DamageMeters_flags[DMFLAG_groupDPSMode]) then
                        DamageMeters_OnCombatStart();
                end
                return;
        end
        if ( event == "PLAYER_REGEN_ENABLED" ) then
                DamageMeters_playerInCombat = false;

                if (DamageMeters_flags[DMFLAG_groupDPSMode]) then
                        if (DamageMeters_inCombat) then
                                if (DamageMeters_InParty() and UnitIsDead("Player")) then
                                        -- Dont stop combat: the party could be continuing combat.      
                                else
                                        --DMPrintD("In Combat, but either not In Party or Not Dead: Ending Combat.");
                                        DamageMeters_OnCombatEnd();
                                end
                        end
                        --BAD DamageMeters_startCombatOnNextValue = true;
                else
                        if (DamageMeters_inCombat) then
                                DamageMeters_OnCombatEnd();
                        end
                end
                return;
        end

        if (DM_Bypass["Event All"] == true) then
                return;
        end

        -----------------------

        if ( event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" ) then
                local unitName = UnitName(arg1);
                --DMPrintD(event..": "..arg1.." "..unitName, nil, true);

                local playerIndex = DamageMeters_GetPlayerIndex(unitName, DMT_ACTIVE);
                if (playerIndex) then
                        local field, value;
                        if (event == "UNIT_HEALTH") then
                                field = "health";
                                value = UnitHealth(arg1);
                        else
                                field = "maxHealth";
                                value = UnitHealthMax(arg1);
                        end
                        -- Debug Spam.
                        if (DamageMeters_debug4.showHealthChanges) then
                                if (value ~= DamageMeters_tables[DMT_ACTIVE][playerIndex][field]) then
                                        local delta = value - DamageMeters_tables[DMT_ACTIVE][playerIndex][field];
                                        local color = (delta >= 0) and ({r=0.25,g=1.00,b=0.25}) or ({r=1.00,g=0.25,b=0.25});
                                        DMPrintD(delta..": Updating "..field.." for "..DamageMeters_tables[DMT_ACTIVE][playerIndex].player.." from "..DamageMeters_tables[DMT_ACTIVE][playerIndex][field].." to "..value, color, true);
                                end
                        end
                        DamageMeters_tables[DMT_ACTIVE][playerIndex][field] = value;

                        DamageMeters_tablesDirty = true;
                end

                return;
        end

        -----------------------
        --[[
        if (event == "CHAT_MSG_RAID" or event == "CHAT_MSG_PARTY") then
                if (DamageMeters_flags[DMFLAG_permitAutoSyncChanJoin]) then

                        local first, last = string.find(arg1, DM_MSG_SYNCCHANBROADCAST);
                        if (last) then
                                local sub = string.sub(arg1, last+1);
                                DMPrint(sub);

                                local newChan;
                                local newLabel = nil;
                                for newChan, newLabel in string.gfind(sub, "(.+) %((.+)%)") do
                                end
                                if (newLabel == nil) then
                                        newChan = sub;
                                end

                                local changeChan = false;
                                if (string.lower(newChan) ~= string.lower(DamageMeters_syncChannel)) then
                                        changeChan = true;
                                elseif (0 == GetChannelName(newChan)) then      
                                        changeChan = true;
                                end

                                if (changeChan) then
                                        DamageMeters_SyncChan(newChan); 
                                        if (DamageMeters_flags[DMFLAG_autoClearOnChannelJoin]) then
                                                DamageMeters_Clear();
                                        end
                                end
                                return;
                        end
                        return;
                end

                return;
        end
        ]]--
        
        if (event == "CHAT_MSG_ADDON") then
                --[[
                arg1 - id
                arg2 - message
                arg3 - "PARTY","RAID", "GUILD" or "BATTLEGROUND"
                arg4 - sender 
                ]]--
                if (arg1 == DamageMeters_SYNC_ID and (arg3 == "PARTY" or arg3 == "RAID") ) then
                        DamageMeters_ParseSyncMessage(arg2, arg4, arg3);
                end
                return;
        end
        
        --[[
        if (event == "CHAT_MSG_CHANNEL") then
                local type = strsub(event, 10);
                local info = ChatTypeInfo[type];
                
                if (DamageMeters_flags[DMFLAG_onlySyncWithGroup] and DamageMeters_GetGroupRelation(arg2) < 0) then
                        return;
                end

                local channelLength = strlen(arg4);
                if ( (strsub(type, 1, 7) == "CHANNEL") and (type ~= "CHANNEL_LIST") and (arg1 ~= "INVITE") ) then

                        if (info and (string.lower(arg9) == string.lower(DamageMeters_syncChannel))) then
                                -- arg2 = source, arg9 = channel name
                                --DMPrint("arg2 = "..arg2..", arg3 = "..arg3..", arg4 = "..arg4);
                                --DMPrint("arg5 = "..arg5..", arg6 = "..arg6..", arg7 = "..arg7);
                                --DMPrint("arg8 = "..arg8..", arg9 = "..arg9);
                                --DMPrint("arg7 = "..arg7..", arg9 = "..arg9);

                                local sender = string.lower(arg2);

        
                                -- Ignore messages from ourself.
                                if (not DamageMeters_debug4.syncSelf) then
                                        if (arg2 == UnitName("Player")) then
                                                return;
                                        end
                                end
                                
                                local msg = arg1;

                                local firstSpace = string.find(msg, " ");
                                local command, commandArgs;
                                if (firstSpace ~= nil) then
                                        command = string.sub(msg, 1, firstSpace - 1);
                                        commandArgs = string.sub(msg, firstSpace + 1);
                                else
                                        command = msg;
                                        commandArgs = "";
                                end

                                if ((strsub(msg, 1, string.len(DMSYNC_PREFIX)) == DMSYNC_PREFIX) or
                                                ((strsub(msg, 1, string.len(DMSYNC_EVENT_PREFIX)) == DMSYNC_EVENT_PREFIX))) then
                                        if (DamageMeters_debug4.syncSelf) then
                                                for index = 1, DamageMeters_debug_syncTestFactor do
                                                        table.insert(DamageMeters_syncIncMsgQueue, msg);
                                                        table.insert(DamageMeters_syncIncMsgSourceQueue, arg2);
                                                end
                                        else
                                                table.insert(DamageMeters_syncIncMsgQueue, msg);
                                                table.insert(DamageMeters_syncIncMsgSourceQueue, arg2);
                                        end
                                        return;
                                elseif (command == DamageMeters_SYNCREQUEST) then
                                        -- Note: +1 doesn't allow for a space between prefix and params.
                                        local params = commandArgs;
                                        --DMPrintD("Incoming sync params = >"..params.."<");
                                        --SendChatMessage("Incoming sync params = >"..params.."<", "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                                        if (string.sub(params, 1, 1) == "E") then
                                                --SendChatMessage("E detected: syncing events.", "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                                                DamageMeters_syncEvents = true;
                                                params = string.sub(params, 3);
                                        end
                                        if (params) then
                                                for syncLabel, syncIndex in string.gfind(params, "<(.+) (%d+)>") do
                                                        syncLabel = tostring(syncLabel);
                                                        syncIndex = tonumber(syncIndex);
                                                        -- Potential error: local sessionLabel can be nil. UPDATE: I think this is fixed.
                                                        local currentLabel = DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel;
                                                        local currentIndex = DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex;
                                                        if (false == DamageMeters_CheckSession(DMT_ACTIVE, syncLabel, syncIndex)) then
                                                                DMPrint(DM_MSG_SESSIONMISMATCH);
                                                                local mismatchMsg = DamageMeters_SYNCSESSIONMISMATCH.." "..currentLabel.." "..currentIndex;
                                                                --SendChatMessage(mismatchMsg, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                                                                --Telepathy.sendMessage(DamageMeters_SYNC_ID, mismatchMsg, "RAID");
                                                                ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, mismatchMsg, "RAID");
                                                                
                                                        else
                                                                DMPrintD("Sync request recieved, session matches our own.");
                                                        end
                                                end
                                        end

                                        local now = GetTime();
                                        if (now - DamageMeters_lastSyncTime > DamageMeters_MINSYNCCOOLDOWN) then
                                                local ackmsg = DamageMeters_syncEvents and DM_MSG_SYNCREQUESTACKEVENTS or DM_MSG_SYNCREQUESTACK;
                                                DMPrint(ackmsg..arg2, nil, true);
                                                DamageMeters_lastSyncTime = now;
                                                DamageMeters_SyncReport();
                                        else
                                                DMPrint(DM_ERROR_SYNCTOOSOON);
                                        end
                                        return;
                                elseif (command == DamageMeters_SYNCCLEARREQUEST) then
                                        DMPrint(DM_MSG_CLEARRECEIVED..arg2, nil, true);
                                        --SendChatMessage(DamageMeters_SYNCCLEARACK, "CHANNEL", nil, GetChannelName(DamageMeters_syncChannel));
                                        --Telepathy.sendMessage(DamageMeters_SYNC_ID, DamageMeters_SYNCCLEARACK, "RAID");
                                        ChatThrottleLib:SendAddonMessage("NORMAL", DamageMeters_SYNC_ID, DamageMeters_SYNCCLEARACK, "RAID");
                                        DamageMeters_Clear();
                                        
                                        -- Check for session information, and update ours if it is there.
                                        for newLabel, newIndex in string.gfind(commandArgs, "(.+) (%d+)") do
                                                DamageMeters_tableInfo[DMT_ACTIVE].sessionLabel = tostring(newLabel);
                                                DamageMeters_tableInfo[DMT_ACTIVE].sessionIndex = tonumber(newIndex);
                                                break;
                                        end

                                        return;
                                elseif (command == DamageMeters_SYNCCLEARACK) then
                                        --DMPrint(format(DM_MSG_CLEARACKNOWLEDGED, arg2), nil, true);
                                        return;
                                elseif (command == DamageMeters_SYNCJOINED) then
                                        local version = commandArgs;
                                        DMPrint(string.format(DM_MSG_PLAYERJOINEDSYNCCHAN, arg2, version), nil, true);
                                        return;
                                elseif (command == DamageMeters_SYNCSTART) then
                                        DMPrint(format(DM_MSG_RECEIVEDSYNCDATA, arg2), nil, true);
                                        return;
                                elseif (command == DamageMeters_SYNCEND) then
                                        -- Nothing atm.
                                        return;
                                elseif (command == DamageMeters_SYNCPINGREQ) then
                                        DamageMeters_SyncPingReply(arg2);
                                        return;
                                elseif (msg == DamageMeters_SYNCHALT) then
                                        DMPrint(string.format(DM_MSG_SYNCHALTRECEIVED, arg2), nil, true);
                                        -- Clear the message queues.
                                        DamageMeters_syncMsgQueue = {};
                                        DamageMeters_syncIncMsgQueue = {};
                                        DamageMeters_syncIncMsgSourceQueue = {};
                                        return;
                                elseif (command == DamageMeters_SYNCPAUSEREQ) then
                                        DMPrint(string.format(DM_MSG_SYNCPAUSE, arg2), nil, true);
                                        DamageMeters_pauseState = DM_Pause_Paused;
                                        DamageMeters_CompletePauseChange(true);
                                        return;
                                elseif (command == DamageMeters_SYNCUNPAUSEREQ) then
                                        local unpauseMsg = DM_MSG_SYNCUNPAUSE;                          
                                        local additionalInfo = strsub(msg, string.len(DamageMeters_SYNCUNPAUSEREQ) + 2);
                                        if (additionalInfo and string.len(additionalInfo) > 0) then
                                                unpauseMsg = unpauseMsg.." ("..additionalInfo..")";
                                        end

                                        DMPrint(string.format(unpauseMsg, arg2), nil, true);
                                        if (DamageMeters_pauseState ~= DM_Pause_Not) then
                                                DamageMeters_pauseState = DM_Pause_Not;
                                                DamageMeters_CompletePauseChange(true);
                                        end
                                        return;
                                elseif (command == DamageMeters_SYNCREADYREQ) then
                                        DMPrint(string.format(DM_MSG_SYNCREADY, arg2), nil, true);
                                        DamageMeters_SetReady();
                                        return;
                                elseif (command == DamageMeters_SYNCPING) then
                                        for     pinger, additionalInfo in string.gfind(msg, DamageMeters_SYNCPING.." <(.+)> <(.+)>") do
                                                if (pinger == UnitName("Player")) then
                                                        DMPrint("DMSYNCPING: "..arg2..", "..additionalInfo.."");
                                                end
                                        end
                                        return;
                                elseif (command == DamageMeters_SYNCSESSIONMISMATCH) then
                                        local badSession, badIndex;
                                        for     badSession, badIndex in string.gfind(msg, DamageMeters_SYNCSESSIONMISMATCH.." (.+) (%d+)") do                                           
                                                local mismatchMsg = string.format(DM_MSG_SYNCSESSIONMISMATCH,
                                                                arg2,
                                                                badSession,
                                                                badIndex);
                                                DMPrint(mismatchMsg, nil, true);
                                                return;
                                        end
                                        return;
                                elseif (command == DamageMeters_SYNCMSG) then
                                        if (DamageMeters_flags[DMFLAG_enableDMM]) then
                                                local syncMsg = commandArgs;
                                                DMPrint(format(DM_MSG_SYNCMSG, arg2, arg2, syncMsg), DamageMeters_SYNCMSGCOLOR);
                                        end
                                        return;
                                elseif (command == DamageMeters_SYNCEMOTE) then
                                        DamageMeters_DecodeEmote(arg2, strsub(msg, string.len(DamageMeters_SYNCEMOTE) + 1));
                                        return;

                                elseif (command == DamageMeters_SYNCRPS) then
                                        DamageMeters_RPSChallengeReceived(arg2, strsub(msg, string.len(DamageMeters_SYNCRPS) + 2));
                                        return;                                 
                                elseif (command == DamageMeters_SYNCRPSRESPONSE) then
                                        DamageMeters_RPSResponseReceived(arg2, strsub(msg, string.len(DamageMeters_SYNCRPSRESPONSE) + 2));
                                        return;
                                elseif (command == DamageMeters_SYNCRPSCOUNTERRESPONSE) then
                                        DamageMeters_RPSCounterresponseRecieved(arg2, strsub(msg, string.len(DamageMeters_SYNCRPSCOUNTERRESPONSE) + 2));
                                        return;

                                elseif (command == DamageMeters_SYNCKICK) then
                                        local kicked = string.lower(string.sub(msg, string.len(DamageMeters_SYNCKICK) + 2));
                                        DMPrintD("kicked = >"..kicked.."<");
                                        if (string.lower(UnitName("Player")) == kicked) then
                                                DMPrint(string.format(DM_MSG_KICKED, arg2));
                                                DamageMeters_SyncLeaveChan();
                                        end
                                        return;
                                elseif (command == DamageMeters_SYNCBOSSSTART) then
                                        local label = commandArgs;
                                        --!
                                        DMPrint(string.format("DamageMeters: Received Boss (%s) Start command from %s.", label, arg2));
                                        DamageMeters_DoSyncBossStart(label);
                                        return;
                                elseif (command == DamageMeters_SYNCBOSSEND) then
                                        DamageMeters_DoSyncBossEnd();
                                        return;
                                end
                        end
                end

                return;
        end
        ]]--

        ---------------------------

        if (DamageMeters_pauseState == DM_Pause_Paused) then
                return;
        end

        if (DamageMeters_debug4.showAll) then
                local subEvent = string.sub(event, 9);
                DMPrintD("("..subEvent.."): "..arg1, nil, true);
        end

        DamageMeters_ParseMessage(arg1, event);

        ---------------------------
end

function DamageMeters_StartDebugTimer(index)
        DamageMeters_activeDebugTimer = index;
        debugprofilestart();
end

function DamageMeters_StopDebugTimer(index)
        if (DamageMeters_activeDebugTimer ~= index) then
                DMPrintD("DM DEBUG ERROR: Wrong debug timer stopped. "..(DamageMeters_activeDebugTimer and DMPROF_NAMES[DamageMeters_activeDebugTimer] or "[none]").." expected, got "..DMPROF_NAMES[index]);
                debugprofilestop()
                DamageMeters_activeDebugTimer = 0;
                return;
        end

        DamageMeters_debugTimers[index].time = DamageMeters_debugTimers[index].time + debugprofilestop();
        DamageMeters_debugTimers[index].count = DamageMeters_debugTimers[index].count + 1;
        DamageMeters_activeDebugTimer = 0;
end

---------------------------
StaticPopupDialogs["DM_START_NEW_SESSION"] = {
        text = DM_NEWSESSIONID,
        button1 = ACCEPT,
        button2 = CANCEL,
        hasEditBox = 1,
        maxLetters = 12,
        OnAccept = function()
                local editBox = getglobal(this:GetParent():GetName().."EditBox");
                DamageMeters_SyncStart(editBox:GetText());
        end,
        OnShow = function()
                getglobal(this:GetName().."EditBox"):SetFocus();
        end,
        OnHide = function()
                getglobal(this:GetName().."EditBox"):SetText("");
        end,
        EditBoxOnEnterPressed = function()
                local editBox = getglobal(this:GetParent():GetName().."EditBox");
                DamageMeters_SyncStart(editBox:GetText());
                this:GetParent():Hide();
        end,
        EditBoxOnEscapePressed = function()
                this:GetParent():Hide();
        end,
        timeout = 0,
        exclusive = 1,
        whileDead = 1,
        hideOnEscape = 1
};

StaticPopupDialogs["DM_REPORT_CHANNEL"] = {
        text = DM_REPORTCHANNEL,
        button1 = ACCEPT,
        button2 = CANCEL,
        hasEditBox = 1,
        maxLetters = 12,
        OnAccept = function()
                local editBox = getglobal(this:GetParent():GetName().."EditBox");
                local command = format("%s%d %s", "h", DamageMeters_barCount, editBox:GetText());
                DamageMeters_Report(command);
        end,
        OnShow = function()
                getglobal(this:GetName().."EditBox"):SetFocus();
        end,
        OnHide = function()
                getglobal(this:GetName().."EditBox"):SetText("");
        end,
        EditBoxOnEnterPressed = function()
                local editBox = getglobal(this:GetParent():GetName().."EditBox");
                local command = format("%s%d %s", "h", DamageMeters_barCount, editBox:GetText());
                DamageMeters_Report(command);
                this:GetParent():Hide();
        end,
        EditBoxOnEscapePressed = function()
                this:GetParent():Hide();
        end,
        timeout = 0,
        exclusive = 1,
        whileDead = 1,
        hideOnEscape = 1
};

StaticPopupDialogs["DM_REPORT_WHISPER"] = {
        text = DM_REPORTWHISPER,
        button1 = ACCEPT,
        button2 = CANCEL,
        hasEditBox = 1,
        maxLetters = 12,
        OnAccept = function()
                local editBox = getglobal(this:GetParent():GetName().."EditBox");
                local command = format("%s%d %s", "w", DamageMeters_barCount, editBox:GetText());
                DamageMeters_Report(command);
        end,
        OnShow = function()
                getglobal(this:GetName().."EditBox"):SetFocus();
        end,
        OnHide = function()
                getglobal(this:GetName().."EditBox"):SetText("");
        end,
        EditBoxOnEnterPressed = function()
                local editBox = getglobal(this:GetParent():GetName().."EditBox");
                local command = format("%s%d %s", "w", DamageMeters_barCount, editBox:GetText());
                DamageMeters_Report(command);
                this:GetParent():Hide();
        end,
        EditBoxOnEscapePressed = function()
                this:GetParent():Hide();
        end,
        timeout = 0,
        exclusive = 1,
        whileDead = 1,
        hideOnEscape = 1
};

---------------------------
function DamageMetersPlugin_Register(pinfo)
        if pinfo and pinfo.name then
                DamageMeters_PLUGINS[pinfo.name] = pinfo;
        end
        
        -- fix up DMI
        -- DMI_MAX = DMI_MAX + 1;
        
        -- insert into quant thingy
        table.insert(DM_QUANTDEFS, pinfo.quantDefs);
        DamageMeters_Quantity_MAX = table.getn(DM_QUANTDEFS);
        --DM_QUANTDEFS[DamageMeters_Quantity_MAX].dmi = DMI_MAX;

        -- Assume that if they went to the trouble of loading the plugin they want to see the quantity by default.
        DamageMeters_quantitiesFilter[DamageMeters_Quantity_MAX] = true;
        
        table.insert(DMI_NAMES, pinfo.name);
        
        -- insert quantity color code.
        local color = DM_QUANTDEFS[DamageMeters_Quantity_MAX].defaultColor;
        local code = string.format("|cFF%02X%02X%02X",
                floor(color[1] * 255.0),
                floor(color[2] * 255.0),
                floor(color[3] * 255.0));
        DamageMeters_quantityColorCodeDefault[DamageMeters_Quantity_MAX] = code;
        DamageMeters_quantityColor[DamageMeters_Quantity_MAX] = DM_clone(DM_QUANTDEFS[DamageMeters_Quantity_MAX].defaultColor);
        
        -- PC: Made this unnecessary.   
        --DamageMeters_Clear(0, true);

        DMPrintD("Plugin >"..pinfo.name.."< registered, quantity = "..DamageMeters_Quantity_MAX);

        --return DMI_MAX, DamageMeters_Quantity_MAX;
        return DamageMeters_Quantity_MAX;
end


---------------------------
function DamageMetersPlugin_Unregister(pname)
        DamageMeters_PLUGINS[pname] = nil;
end


---------------------------
function DamageMetersPlugin_AddHealing(event, player, target, amount, crit, relationship, targetRelationship, spell)
        for k, v in DamageMeters_PLUGINS do
                if v.pfnAddHealing ~= nil then
                        v.pfnAddHealing(event, player, target, amount, crit, relationship, targetRelationship, spell);
                end
        end
end


---------------------------
function DamageMetersPlugin_AddDamage(event, player, creatureName, amount, crit, relationship, spell, damageType)
        for k, v in DamageMeters_PLUGINS do
                if v.pfnAddDamage ~= nil then
                        v.pfnAddDamage(event, player, creatureName, amount, crit, relationship, spell, damageType);
                end
        end
end


---------------------------
function DamageMetersPlugin_AddDamageReceived(event, player, creatureName, amount, crit, relationship, spell, damageType, resisted)
        for k, v in DamageMeters_PLUGINS do
                if v.pfnAddDamageReceived ~= nil then
                        v.pfnAddDamageReceived(event, player, creatureName, amount, crit, relationship, spell, damageType, resisted);
                end
        end
end



---------------------------
function DamageMetersPlugin_Clear()
        for k, v in DamageMeters_PLUGINS do
                if v.pfnClear ~= nil then
                        v.pfnClear();
                end
        end
end


---------------------------
function DamageMetersPlugin_DropDownInitialize()
        for k, v in DamageMeters_PLUGINS do
                if v.pfnDropDownInitialize ~= nil then
                        v.pfnDropDownInitialize();
                end
        end
end