--[[
DamageMeters.lua
See the Readme for author and permissions.


TODO:
 FEATURES
- 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.


 KNOWN BUGS
- Sometime tooltip doesnt 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 = 5200;
DamageMeters_VERSIONSTRING = "5.2.0";
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",
	"PLAYER_LOGIN",

	-- 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()
	-- Chat Sub-Commands --
	DamageMeters_ChatCommands = {

		help = 		DamageMeters_Help;
		cmd = 		DamageMeters_ListCommands;
		show = 		DamageMeters_ToggleShow;
		hide = 		DamageMeters_Hide;
		clear = 	DamageMeters_Clear;
		report = 	DamageMeters_Report;
		sort = 		DamageMeters_SetSort;
		count = 	DamageMeters_SetCount;
		save = 		DamageMeters_Save;
		restore = 	DamageMeters_Restore;
		merge = 	DamageMeters_Merge;
		swap = 		DamageMeters_Swap;
		memclear = 	DamageMeters_MemClear;
		resetpos = 	DamageMeters_ResetPos;
		text = 		DamageMeters_SetTextOptions;
		color = 	DamageMeters_SetColorScheme;
		quant = 	DamageMeters_SetQuantity;
		visinparty = 	DamageMeters_SetVisibleInParty;
		autocount = 	DamageMeters_SetAutoCount;
		listbanned = 	DamageMeters_ListBanned;
		clearbanned = 	DamageMeters_ClearBanned;
		sync = 		DamageMeters_Sync;
		syncsend = 	DamageMeters_SyncReport;
		syncrequest = 	DamageMeters_SyncRequest;
		syncclear = 	DamageMeters_SyncClear;
		syncmsg = 	DamageMeters_SendSyncMsg;
		m = 	DamageMeters_SendSyncMsg;
		syncping = 	DamageMeters_SyncPingRequest;
		syncpause = 	DamageMeters_SyncPause;
		syncunpause = 	DamageMeters_SyncUnpause;
		syncready = 	DamageMeters_SyncReady;
		synclabel = 	DamageMeters_SyncLabel;
		syncstart = 	DamageMeters_SyncStart;
		synchalt = 		DamageMeters_SyncHalt;
		--syncbossstart = DamageMeters_SyncBossStart;
		--syncbossend = DamageMeters_SyncBossEnd;

		-- Undocumented atm.
		me = 		DamageMeters_SyncEmote;
		mrps = 		DamageMeters_RPSChallenge;
		rpsr = 		DamageMeters_RPSResponse;

		pop = 		DamageMeters_Populate;
		lock = 		DamageMeters_ToggleLock;
		pause = 	DamageMeters_TogglePause;
		ready = 	DamageMeters_SetReady;
		lockpos = 	DamageMeters_ToggleLockPos;
		grouponly = 		DamageMeters_ToggleGroupMembersOnly;
		addpettoplayer = 	DamageMeters_ToggleAddPetToPlayer;
		resetoncombat = 	DamageMeters_ToggleResetWhenCombatStarts;
		version = 	DamageMeters_ShowVersion;
		ver = 	DamageMeters_ShowVersion;
		total = 	DamageMeters_ToggleTotal;
		showmax = 	DamageMeters_ToggleMaxBars;

		-- Commands for testing.
		-- "/dm reset - (For Testing) Forces a re-layout of the visual elements.",
		reset = 	DamageMeters_Reset;
		-- "/dm test [#] - (For Testing) Adds # test entries to the list.  If no number specified, adds one entry for each visible bar.",
		test = 		DamageMeters_Test;
		-- "/dm add name - (For Testing) Simulates player 'name' doing 1 damage.",
		add = 		DamageMeters_Add;
		-- "/dm dumptable - (For Testing) Dumps the entire internal data table."
		dumptable = DamageMeters_DumpTable;
		debug = 	DM_ToggleDMPrintD;
		dumpmsg = 	DM_DumpMsg;
		p = 		DM_ConsolePrint;
		pt = 		DM_ConsolePrintTable;

	}

	-- 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 = #(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();

	-- 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["DAMAGEMETERS"] = DamageMeters_ChatCommandHandler;
	SLASH_DAMAGEMETERS1 = "/damagemeters";
	SLASH_DAMAGEMETERS2 = "/dm";

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

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

	DamageMeters_FixPatterns();

	if ( EarthFeature_AddButton ) then
		EarthFeature_AddButton( {
			id="DamageMeters";
			name=BINDING_HEADER_DAMAGEMETERSHEADER;
			subtext=BINDING_NAME_DAMAGEMETERS_TOGGLESHOW;
			tooltip=BINDING_HEADER_DAMAGEMETERSHEADER;
			icon="Interface\\Icons\\INV_Misc_EngGizmos_20";
			callback=DamageMeters_ToggleShow;
		} );
	end;
end

function DamageMeters_RegisterEvents()
	for index, event in ipairs(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 ipairs(DamageMeters_watchedEventsTable) do
		this:UnregisterEvent(event);
	end
end

function DamageMeters_DeleteDMIData(dmi, dmiCount)
	for tableIx, table in pairs(DamageMeters_tables) do
		for playerIx, playerStruct in pairs(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 pairs(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 pairs(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 pairs(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 pairs(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 = #(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 pairs(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 = #(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, #(DamageMeters_tables[DMT_VISIBLE])));

		-- Set bar info.
		local barIndex = 1;
		local struct;
		for i,struct in pairs(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 = #(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, #(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 (#(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 newTable = {};
	
	newTable.dmiData = {};

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

	return #destTable;
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 = #(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.gmatch(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.gmatch(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.gmatch(params, "(.)(%d+)") do
			destChar = a;
			count = tonumber(b);
			--DMPrint(3);
			argsParsed = true;
		end
	end
	if (not argsParsed) then
		for a, c in string.gmatch(params, "(.) (.+)") do
			destChar = a;
			tellTarget = c;
			--DMPrint(4);
			argsParsed = true;
		end
	end
	if (not argsParsed) then
		for a in string.gmatch(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(#(DamageMeters_tables[DMT_ACTIVE]).." elements:");

	local index;
	local info;
	for index,info in pairs( 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 pairs( 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 <= #(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 <= #(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 > #(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_ChatCommandHandler(msg)
	local _, _, subcom, input = string.find(msg, "[ ]*(%w+)[ ]*(.*)");
	if (subcom) then
		subcom = strlower(subcom);
	else
		DamageMeters_Help();
		return;
	end
	if (DamageMeters_ChatCommands[subcom]) then
		DamageMeters_ChatCommands[subcom](input);
	else
		DamageMeters_Help();
	end
end

function DamageMeters_PrintHelp(command)
	local index, help;
	for index, help in pairs( 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 pairs( 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..", "..#(DamageMeters_tables[DMT_ACTIVE]).." entries.");
	DMPrint("SAVED = "..DMT_SAVED..", "..#(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 (#(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 pairs( 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, #(DamageMeters_tables[DMT_ACTIVE]), #(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, #(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, #(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, #(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 pairs( 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 pairs( 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 pairs( 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 = #(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 > #(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 pairs( 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 pairs( 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.gmatch(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 (#(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 = #(DamageMeters_tables[DMT_ACTIVE]);
	--DMPrintD("sync indexes: "..startIndex.."->"..endIndex.."("..#(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 pairs( DamageMeters_tables[DMT_ACTIVE] ) do
			DamageMeters_SyncSendPlayerEvents(playerStruct);
		end
	end
	-- Reset variable: we are done with it.
	DamageMeters_syncEvents = false;

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

	local queueSize = #(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 = "..#(quantityStruct.hash));

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

			local hashCount = #(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.gmatch(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.gmatch(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(#(DamageMeters_contributorList).." contributors:");
	for playerName, unused in pairs( 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 = #(DamageMeters_missedMessages).." missed messages:";
	DamageMeters_SendReportMsg(msg, destination);

	local index;
	for index = 1, #(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.gmatch(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 = #(DamageMeters_syncMsgQueue);
	if ( #(DamageMeters_syncMsgQueue) > 0 ) then
		while ( now - DamageMeters_lastSyncMsgTime > DamageMeters_SYNCMSGSENDDELAY) do
			if ( #(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 = #(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 = #(DamageMeters_syncIncMsgQueue);
	local initialElapsed = now - DamageMeters_lastSyncIncMsgTime;

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

	if ( #(DamageMeters_syncIncMsgQueue) > 0 ) then
		while (now - DamageMeters_lastSyncIncMsgTime > DamageMeters_SYNCMSGPROCESSDELAY) do
			if ( #(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 = #(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 (#(DamageMeters_RPSChallengedByList) == 1) then
				local index, struct;
				for index, struct in pairs( DamageMeters_RPSChallengedByList ) do
					player = struct.player;
					break;
				end
			else
				DMPrint(#(DamageMeters_RPSChallengedByList));
				for _player, unused in pairs( 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 pairs( 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.gmatch(patternString,"%%(%d)%$.") do
			field[idx] = tonumber(i)
			idx = idx + 1
		end

		for k in pairs( field ) do
			for j in pairs( 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 pairs( 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 pairs( 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 pairs( 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 pairs( DM_eventCaseTableDebug ) do
			for event, eventTable in pairs( msgTypeTable ) do
				for caseIx, case in pairs( 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, info in ipairs( DM_Spellometer_Patterns ) do
			local caster, spell, target;
			local matches = {};
			local found;

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

			if found then
				local casterRelationship = DamageMeters_Relation_FRIENDLY;

				caster = matches[info.caster];
				spell = matches[info.spell];
				if (info.target) then
					target = matches[info.target];
				else
					target = UnitName("player");
				end
				if (caster == YOU or caster == DM_YOU) then
					caster = UnitName("player");
					casterRelationship = DamageMeters_Relation_SELF;
				end

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

				if (spellFound) 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 pairs( 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 ipairs( 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 (#(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.gmatch(arg1, "(%d+)") do

				-- Eliminate numeric messages we aren't interested in:
				-- Wierd (#) messages (buffs)
				for player, amount, type in string.gmatch(arg1, "%(%d+%)") do	return;	end
				-- GENERICPOWERGAIN_OTHER
				-- GENERICPOWERGAIN_SELF
				for player, amount, type in string.gmatch(arg1, "(.+) gains (%d+) (.+)%.") do	return;	end
				for player, amount, type in string.gmatch(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.gmatch(arg1, "(.+) casts (.+) on (.+)'s (.+)%.") do return; end
				for player in string.gmatch(arg1, "(.+) casts (.+) on your (.+)%.") do return; end
				for player in string.gmatch(arg1, "You cast (.+) on (.+)'s (.+)%.") do return; end
				for player in string.gmatch(arg1, "You cast (.+) on your (.+)%.") do return; end
				-- VSENVIRONMENTALDAMAGE_DROWNING_OTHER etc.
				for player in string.gmatch(arg1, "You are drowning and lose (%d+) health.") do return; end
				for player in string.gmatch(arg1, "(.+) is exhausted and and loses (%d+) health.") do return; end
				for player in string.gmatch(arg1, "You are exhausted and lose (%d+) health.") do return; end
				for player in string.gmatch(arg1, "(.+) suffers (%d+) points of fire damage.") do return; end
				for player in string.gmatch(arg1, "You suffer (%d+) points of fire damage.") do return; end
				for player in string.gmatch(arg1, "(.+) loses (%d+) health for swimming in (.+)%.") do return; end
				for player in string.gmatch(arg1, "You lose (%d+) health for swimming in (.+)%.") do return; end
				-- SPELLPOWERDRAINOTHEROTHER etc
				for player in string.gmatch(arg1, "(.+) drains (%d+) Mana from you.") do return; end
				for player in string.gmatch(arg1, "(.+) drains (%d+) Mana from (.+)%.") do return; end
				for player in string.gmatch(arg1, "You drain (%d+) Mana from (.+)%.") do return; end
				for player in string.gmatch(arg1, "(.+) drains (%d+) Mana from you and gains (%d+)") do return; end
				for player in string.gmatch(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 (#(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.gmatch(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.gmatch(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.gmatch(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.gmatch(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;
	elseif (event == "PLAYER_LOGIN") then
		if (not DamageMetersFrame:IsUserPlaced()) then
			DMPrintD("Not user placed: resetting pos.");
			DamageMeters_ResetPos();
		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 pairs( 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_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 (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 = #(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 pairs( 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 pairs( 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 pairs( 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 pairs( DamageMeters_PLUGINS ) do
		if v.pfnClear ~= nil then
			v.pfnClear();
		end
	end
end


---------------------------
function DamageMetersPlugin_DropDownInitialize()
	for k, v in pairs( DamageMeters_PLUGINS ) do
		if v.pfnDropDownInitialize ~= nil then
			v.pfnDropDownInitialize();
		end
	end
end

