farmbuyer@10: -- This file is one gigantic exercise in abusing the garbage collector via
farmbuyer@10: -- string manipulation. A real XML-handling library would be cleaner, alas,
farmbuyer@10: -- we can't load third-party .so/.dll inside the WoW client. Life is hard.
farmbuyer@10:
farmbuyer@1: local addon = select(2,...)
farmbuyer@10: local pairs, ipairs, tinsert, tremove, tconcat = pairs, ipairs, table.insert, table.remove, table.concat
farmbuyer@10: local tostring, tonumber = tostring, tonumber
farmbuyer@10:
farmbuyer@10: local banner_formatted = "Formatted version (scroll down for unformatted):"
farmbuyer@10: local banner_unformatted = "Unformatted version:"
farmbuyer@10: local banner_sep = "==========================="
farmbuyer@1:
farmbuyer@1: -- We keep some local state bundled up rather than trying to pass it around
farmbuyer@1: -- as paramters (which would have entailed creating a ton of closures).
farmbuyer@1: local state = {}
farmbuyer@1: local tag_lookup_handlers = {}
farmbuyer@1: local do_tag_lookup_handler
farmbuyer@1:
farmbuyer@1:
farmbuyer@1: --[[
farmbuyer@10: This is based on CT_RaidTracker 1.7.32, reconstructing the output from
farmbuyer@1: code inspection. No official format documents are available on the web
farmbuyer@1: without downloading and installing the EQDKP webserver package. Bah.
farmbuyer@1:
farmbuyer@1: Case of tag names shouldn't matter, but is preserved here from CT_RaidTracker
farmbuyer@1: code for comparison of generated output.
farmbuyer@1:
farmbuyer@1: There is some waste adding newlines between elements here only to strip them
farmbuyer@1: out later, but it's worth the extra cycles for debugging and verification.
farmbuyer@1:
farmbuyer@1: $TIMESTAMP,$ENDTIME MM/DD/YY HH:mm:ss except we don't have seconds available
farmbuyer@1: $REALM GetRealmName()
farmbuyer@1: $ZONE raw name, not snarky
farmbuyer@1: $RAIDNOTE arbitrary text for the raid event
farmbuyer@1: $PHAT_LEWTS all accumulated loot entries
farmbuyer@1: ]]
farmbuyer@1: local XML = ([====[
farmbuyer@1:
farmbuyer@1: 1.4 {In live output, this is missing due to scoping bug in ct_raidtracker.lua:3471}
farmbuyer@1: $TIMESTAMP
farmbuyer@1: $REALM
farmbuyer@1: $TIMESTAMP {Same as the key, apparently?}
farmbuyer@10: $ENDTIME {Set by the "end the raid" command in CTRT, here it is just the final entry time}
farmbuyer@1: $ZONE {may be optional. first one on the list in case of multiple zones?}
farmbuyer@1: {$DIFFICULTY {this scales badly in places like ICC. may be optional?}}
farmbuyer@1:
farmbuyer@10:
farmbuyer@10: $PLAYER_INFOS
farmbuyer@10:
farmbuyer@1:
farmbuyer@1:
farmbuyer@1: $BOSS_KILLS
farmbuyer@1:
farmbuyer@1:
farmbuyer@10:
farmbuyer@10: $WIPES
farmbuyer@10:
farmbuyer@10: {Baron Steamroller {only one "next boss" for the whole
farmbuyer@10: raid event? presumably where to pick up next time?}}
farmbuyer@1:
farmbuyer@1:
farmbuyer@1:
farmbuyer@10:
farmbuyer@10: $JOINS
farmbuyer@10:
farmbuyer@10:
farmbuyer@10: $LEAVES
farmbuyer@10:
farmbuyer@1:
farmbuyer@1:
farmbuyer@1: $PHAT_LEWTS
farmbuyer@1:
farmbuyer@1: ]====]):gsub('%b{}', "")
farmbuyer@1:
farmbuyer@1: --[[
farmbuyer@10: See the loot markup below.
farmbuyer@1: ]]
farmbuyer@1: local boss_kills_xml = ([====[
farmbuyer@1:
farmbuyer@1: $BOSS_NAME
farmbuyer@1:
farmbuyer@1: {this is actually empty in the working example...}
farmbuyer@1: $DIFFICULTY
farmbuyer@1:
farmbuyer@1: ]====]):gsub('%b{}', "")
farmbuyer@1:
farmbuyer@1: local function boss_kills_tag_lookup (tag)
farmbuyer@1: if tag == 'N' then
farmbuyer@1: return tostring(state.key)
farmbuyer@1: elseif tag == 'BOSS_NAME' then
farmbuyer@1: return state.entry.bosskill
farmbuyer@1: elseif tag == 'BOSS_TIME' then
farmbuyer@1: return do_tag_lookup_handler (state.index, state.entry, 'TIME')
farmbuyer@1: else
farmbuyer@1: return do_tag_lookup_handler (state.index, state.entry, tag) or 'NYI'
farmbuyer@1: end
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: --[[
farmbuyer@10: Handles the PlayerInfo, Join, and Leave tags.
farmbuyer@10: ]]
farmbuyer@10: local joinleave_xml
farmbuyer@10: local player_info_xml = ([====[
farmbuyer@10:
farmbuyer@10: $PLAYER_GRUNTWORK
farmbuyer@10:
farmbuyer@10: ]====]):gsub('%b{}', "")
farmbuyer@10:
farmbuyer@10: local function player_info_tag_lookup (tag)
farmbuyer@10: if tag == 'N' then
farmbuyer@10: return tostring(state.key)
farmbuyer@10: elseif tag == 'NAME' then
farmbuyer@10: return state.index
farmbuyer@10: elseif tag == 'TIME' then
farmbuyer@10: return state.time
farmbuyer@10: end
farmbuyer@10: local ltag = tag:lower()
farmbuyer@10: if state.entry[ltag] then
farmbuyer@10: -- handles race, guild, sex, class, level
farmbuyer@10: return state.entry[ltag]
farmbuyer@10: end
farmbuyer@10: return "?"
farmbuyer@10: end
farmbuyer@10:
farmbuyer@10: do
farmbuyer@10: local pi_xml_save = player_info_xml
farmbuyer@10: local gruntwork_tags = {
farmbuyer@10: "name", "race", "guild", "sex", "class", "level"
farmbuyer@10: }
farmbuyer@10: for i,tag in ipairs(gruntwork_tags) do
farmbuyer@10: gruntwork_tags[i] = (" <%s>$%s%s>"):format(tag,tag:upper(),tag)
farmbuyer@10: end
farmbuyer@10: player_info_xml = player_info_xml:gsub('$PLAYER_GRUNTWORK', table.concat(gruntwork_tags,'\n'))
farmbuyer@10:
farmbuyer@10: -- The join/leave blocks use "player" instead of "name". They don't have a
farmbuyer@10: -- guild tag, but they do have a time tag.
farmbuyer@10: gruntwork_tags[1] = " $NAME"
farmbuyer@10: gruntwork_tags[3] = " "
farmbuyer@10: joinleave_xml = pi_xml_save:gsub('$PLAYER_GRUNTWORK', table.concat(gruntwork_tags,'\n'))
farmbuyer@10: end
farmbuyer@10:
farmbuyer@10: --[[
farmbuyer@1: $N 1-based loop variable for key element
farmbuyer@10: $ITEMNAME Without The Square Brackets of the Whale
farmbuyer@10: $ITEMID Not the numeric ID, actually a full itemstring without the leading "item:"
farmbuyer@1: $ICON Last component of texture path?
farmbuyer@1: $CLASS,$SUBCLASS ItemType
farmbuyer@1: $COLOR GetItemQualityColor, full 8-digit string
farmbuyer@1: $COUNT,$BOSS,$ZONE,
farmbuyer@1: $PLAYER all self-explanatory
farmbuyer@1: $COSTS in DKP points... hmmm
farmbuyer@1: $ITEMNOTE take the notes field for this one
farmbuyer@1: $TIME another formatted timestamp
farmbuyer@1: ]]
farmbuyer@1: local phat_lewt_xml = ([====[
farmbuyer@1:
farmbuyer@1: $LEWT_GRUNTWORK
farmbuyer@1: $ZONE {may be optional}
farmbuyer@1: $DIFFICULTY {this scales badly in places like ICC. may be optional?}
farmbuyer@1: {zone can be followed by difficulty}
farmbuyer@1:
farmbuyer@1: ]====]):gsub('%b{}', "")
farmbuyer@1:
farmbuyer@1: local function phat_lewt_tag_lookup (tag)
farmbuyer@1: if tag == 'N' then
farmbuyer@1: return tostring(state.key)
farmbuyer@1: elseif tag == 'COSTS'
farmbuyer@1: then return '1'
farmbuyer@1: else
farmbuyer@1: return do_tag_lookup_handler (state.index, state.entry, tag) or 'NYI'
farmbuyer@1: end
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: do
farmbuyer@1: local gruntwork_tags = {
farmbuyer@1: "ItemName", "ItemID", "Icon", "Class", "SubClass", "Color", "Count",
farmbuyer@1: "Player", "Costs", "Boss", "Time",
farmbuyer@1: }
farmbuyer@1: for i,tag in ipairs(gruntwork_tags) do
farmbuyer@1: gruntwork_tags[i] = (" <%s>$%s%s>"):format(tag,tag:upper(),tag)
farmbuyer@1: end
farmbuyer@1: phat_lewt_xml = phat_lewt_xml:gsub('$LEWT_GRUNTWORK', table.concat(gruntwork_tags,'\n'))
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1:
farmbuyer@1: local function format_EQDKP_timestamp (day_entry, time_entry)
farmbuyer@1: --assert(day_entry.kind == 'time', day_entry.kind .. " passed to MLEQDKP timestamp")
farmbuyer@1: return addon:format_timestamp ("$M/$D/$Y $h:$m:00", day_entry, time_entry)
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1:
farmbuyer@1: -- Look up tag strings for a particular item, given index and entry table.
farmbuyer@1: tag_lookup_handlers.ITEMNAME =
farmbuyer@1: function (i, e)
farmbuyer@1: return e.itemname
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.ITEMID =
farmbuyer@1: function (i, e)
farmbuyer@1: return e.itemlink:match("^|c%x+|H(item[%d:]+)|h%[")
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.ICON =
farmbuyer@1: function (i, e)
farmbuyer@1: local str = e.itexture
farmbuyer@1: repeat
farmbuyer@1: local s = str:find('\\')
farmbuyer@1: if s then str = str:sub(s+1) end
farmbuyer@1: until not s
farmbuyer@1: return str
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.CLASS =
farmbuyer@1: function (i, e)
farmbuyer@1: return state.class
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.SUBCLASS =
farmbuyer@1: function (i, e)
farmbuyer@1: return state.subclass
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.COLOR =
farmbuyer@1: function (i, e)
farmbuyer@1: local q = select(4, GetItemQualityColor(e.quality))
farmbuyer@12: return q -- skip leading |c (no longer returned by GIQC after 4.2)
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.COUNT =
farmbuyer@1: function (i, e)
farmbuyer@1: return e.count and e.count:sub(2) or "1" -- skip the leading "x"
farmbuyer@1: end
farmbuyer@1:
farmbuyer@10: -- should combine these next two
farmbuyer@1: tag_lookup_handlers.BOSS =
farmbuyer@1: function (i, e)
farmbuyer@1: while i > 0 and state.loot[i].kind ~= 'boss' do
farmbuyer@1: i = i - 1
farmbuyer@1: end
farmbuyer@1: if i == 0 then return "No Boss Entry Found, Unknown Boss" end
farmbuyer@1: return state.loot[i].bosskill
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.ZONE =
farmbuyer@1: function (i, e)
farmbuyer@1: while i > 0 and state.loot[i].kind ~= 'boss' do
farmbuyer@1: i = i - 1
farmbuyer@1: end
farmbuyer@1: if i == 0 then return "No Boss Entry Found, Unknown Zone" end
farmbuyer@1: return state.loot[i].instance
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.DIFFICULTY =
farmbuyer@1: function (i, e)
farmbuyer@1: local tag = tag_lookup_handlers.ZONE(i,e)
farmbuyer@1: local N,h = tag:match("%((%d+)(h?)%)")
farmbuyer@1: if not N then return "1" end -- maybe signal an error instead?
farmbuyer@1: N = tonumber(N)
farmbuyer@1: N = ( (N==10) and 1 or 2 ) + ( (h=='h') and 2 or 0 )
farmbuyer@1: return tostring(N)
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.PLAYER =
farmbuyer@1: function (i, e)
farmbuyer@1: return state.player
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.ITEMNOTE =
farmbuyer@1: function (i, e)
farmbuyer@1: return state.itemnote
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: tag_lookup_handlers.TIME =
farmbuyer@1: function (i, e)
farmbuyer@1: local ti,tl = addon:find_previous_time_entry(i)
farmbuyer@1: return format_EQDKP_timestamp(tl,e)
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1:
farmbuyer@1: function do_tag_lookup_handler (index, entry, tag)
farmbuyer@1: local h = tag_lookup_handlers[tag]
farmbuyer@1: if h then
farmbuyer@1: return h(index,entry)
farmbuyer@1: else
farmbuyer@1: error(("MLDKP tag lookup (index %d) on tag %s with no handler"):format(index,tag))
farmbuyer@1: end
farmbuyer@1: end
farmbuyer@1:
farmbuyer@10:
farmbuyer@1: local function generator (ttype, loot, last_printed, generated, cache)
farmbuyer@1: -- Because it's XML, generated text is "grown" by shoving more crap into
farmbuyer@1: -- the middle instead of appending to the end. Only easy way of doing that
farmbuyer@1: -- here is regenerating it from scratch each time.
farmbuyer@1: generated[ttype] = nil
farmbuyer@1:
farmbuyer@1: local _
farmbuyer@1: local text = XML
farmbuyer@1: state.loot = loot
farmbuyer@1:
farmbuyer@1: -- TIMESTAMPs
farmbuyer@1: do
farmbuyer@1: local f,l -- first and last timestamps in the table
farmbuyer@1: for i = 1, #loot do
farmbuyer@1: if loot[i].kind == 'time' then
farmbuyer@1: f = format_EQDKP_timestamp(loot[i])
farmbuyer@1: break
farmbuyer@1: end
farmbuyer@1: end
farmbuyer@1: _,l = addon:find_previous_time_entry(#loot) -- latest timestamp
farmbuyer@1: l = format_EQDKP_timestamp(l,loot[#loot])
farmbuyer@1: text = text:gsub('$TIMESTAMP', f):gsub('$ENDTIME', l)
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: -- Loot
farmbuyer@1: do
farmbuyer@1: local all_lewts = {}
farmbuyer@1: local lewt_template = phat_lewt_xml
farmbuyer@1:
farmbuyer@1: state.key = 1
farmbuyer@1: for i,e in addon:filtered_loot_iter('loot') do
farmbuyer@1: state.index, state.entry = i, e
farmbuyer@1: -- no sense doing repeated getiteminfo calls
farmbuyer@1: state.class, state.subclass = select(6, GetItemInfo(e.id))
farmbuyer@1:
farmbuyer@1: -- similar logic as text_tabs.lua:
farmbuyer@1: -- assuming nobody names a toon "offspec" or "gvault"
farmbuyer@1: local P, N
farmbuyer@1: local disp = e.disposition or e.person
farmbuyer@1: if disp == 'offspec' then
farmbuyer@1: P,N = e.person, "offspec"
farmbuyer@1: elseif disp == 'gvault' then
farmbuyer@1: P,N = "guild vault", e.person
farmbuyer@1: else
farmbuyer@1: P,N = disp, ""
farmbuyer@1: end
farmbuyer@1: if e.extratext_byhand then
farmbuyer@1: N = N .. " -- " .. e.extratext
farmbuyer@1: end
farmbuyer@1: state.player, state.itemnote = P, N
farmbuyer@1:
farmbuyer@1: all_lewts[#all_lewts+1] = lewt_template:gsub('%$([%w_]+)',
farmbuyer@1: phat_lewt_tag_lookup)
farmbuyer@1: state.key = state.key + 1
farmbuyer@1: end
farmbuyer@1:
farmbuyer@10: text = text:gsub('$PHAT_LEWTS', tconcat(all_lewts, '\n'))
farmbuyer@1: end
farmbuyer@1:
farmbuyer@10: -- Player info, join times, leave times
farmbuyer@1: do
farmbuyer@10: local all_players, all_joins, all_leaves = {}, {}, {}
farmbuyer@10: local player_template, joinleave_template = player_info_xml, joinleave_xml
farmbuyer@10: local date = date
farmbuyer@10:
farmbuyer@10: state.key = 1
farmbuyer@10: if type(loot.raiders) == 'table' then for name,r in pairs(loot.raiders) do
farmbuyer@10: state.index, state.entry = name, r
farmbuyer@10: all_players[#all_players+1] = player_template:gsub('%$([%w_]+)', player_info_tag_lookup)
farmbuyer@10: state.time = date ("%m/%d/%y %H:%M:00", r.join)
farmbuyer@10: all_joins[#all_joins+1] = joinleave_template:gsub('%$([%w_]+)', player_info_tag_lookup)
farmbuyer@10: state.time = date ("%m/%d/%y %H:%M:00", r.leave)
farmbuyer@10: all_leaves[#all_leaves+1] = joinleave_template:gsub('%$([%w_]+)', player_info_tag_lookup)
farmbuyer@10: state.key = state.key + 1
farmbuyer@10: end end
farmbuyer@10: text = text:gsub('$PLAYER_INFOS', tconcat(all_players, '\n'))
farmbuyer@10: :gsub('$JOINS', tconcat(all_joins, '\n'))
farmbuyer@10: :gsub('$LEAVES', tconcat(all_leaves, '\n'))
farmbuyer@10: end
farmbuyer@10:
farmbuyer@10: -- Bosses and wipes (does anybody really use the latter?)
farmbuyer@10: do
farmbuyer@10: local all_bosses, all_wipes = {}, {}
farmbuyer@1: local boss_template = boss_kills_xml
farmbuyer@1:
farmbuyer@1: state.key = 1
farmbuyer@1: for i,e in addon:filtered_loot_iter('boss') do
farmbuyer@10: if e.reason == 'kill' then
farmbuyer@1: state.index, state.entry = i, e
farmbuyer@1: all_bosses[#all_bosses+1] = boss_template:gsub('%$([%w_]+)',
farmbuyer@1: boss_kills_tag_lookup)
farmbuyer@1: state.key = state.key + 1
farmbuyer@10: elseif e.reason == 'wipe' then
farmbuyer@10: all_wipes[#all_wipes+1] = ('%d'):format(e.stamp)
farmbuyer@1: end
farmbuyer@1: end
farmbuyer@1:
farmbuyer@10: text = text:gsub('$BOSS_KILLS', tconcat(all_bosses, '\n'))
farmbuyer@10: :gsub('$WIPES', tconcat(all_wipes, '\n'))
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: -- In addition to doing the top-level zone, this will also catch any
farmbuyer@1: -- leftover $ZONE tags. There could be multiple places in the raid, so
farmbuyer@1: -- we default to the first one we saw.
farmbuyer@1: do
farmbuyer@1: local iter = addon:filtered_loot_iter() -- HACK
farmbuyer@1: local first_boss = iter('boss',0)
farmbuyer@1: local zone = first_boss and loot[first_boss].instance or "Unknown"
farmbuyer@1: text = text:gsub('$ZONE', zone)
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: -- Misc
farmbuyer@1: text = text:gsub('$REALM', (GetRealmName()))
farmbuyer@1: --text = text:gsub('$DIFFICULTY', )
farmbuyer@1: text = text:gsub('$RAIDNOTE', "")
farmbuyer@1:
farmbuyer@10: cache[#cache+1] = banner_formatted
farmbuyer@10: cache[#cache+1] = banner_sep
farmbuyer@1: cache[#cache+1] = text
farmbuyer@1: cache[#cache+1] = '\n'
farmbuyer@1:
farmbuyer@10: cache[#cache+1] = banner_unformatted
farmbuyer@10: cache[#cache+1] = banner_sep
farmbuyer@1: text = text:gsub('>%s+<', "><")
farmbuyer@1: cache[#cache+1] = text
farmbuyer@1: cache[#cache+1] = '\n'
farmbuyer@1:
farmbuyer@1: wipe(state)
farmbuyer@1: return true
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: local function specials (_, editbox, container, mkbutton)
farmbuyer@10: local b = mkbutton("Highlight",
farmbuyer@1: [[Highlight the unformatted copy for copy-and-pasting.]])
farmbuyer@10: b:SetFullWidth(true)
farmbuyer@10: b:SetCallback("OnClick", function(_b)
farmbuyer@10: local _,start,finish
farmbuyer@1: local txt = editbox:GetText()
farmbuyer@10: _,start = txt:find(banner_unformatted..'\n'..banner_sep..'\n')
farmbuyer@10: _,finish = txt:find("", start)
farmbuyer@1: editbox.editBox:HighlightText(start,finish)
farmbuyer@1: editbox.editBox:SetCursorPosition(start)
farmbuyer@1: end)
farmbuyer@10: container:AddChild(b)
farmbuyer@10:
farmbuyer@10: local b = mkbutton("Re-Unformat",
farmbuyer@10: [[Regenerate only the unformatted copy at the bottom <*from*> the formatted copy at the top.]])
farmbuyer@10: b:SetFullWidth(true)
farmbuyer@10: b:SetCallback("OnClick", function(_b)
farmbuyer@10: local _,start,finish
farmbuyer@10: local txt = editbox:GetText()
farmbuyer@10: _,start = txt:find(banner_formatted..'\n'..banner_sep..'\n', --[[init=]]1, --[[plain=]]true)
farmbuyer@10: _,finish = txt:find("", start, true)
farmbuyer@10: txt = txt:sub(start+1,finish)
farmbuyer@10: txt = banner_formatted .. '\n'
farmbuyer@10: .. banner_sep .. '\n'
farmbuyer@10: .. txt .. '\n\n\n'
farmbuyer@10: .. banner_unformatted .. '\n'
farmbuyer@10: .. banner_sep .. '\n'
farmbuyer@10: .. txt:gsub('>%s+<', "><") .. '\n'
farmbuyer@10: -- This would normally screw up the cached version, but we're regenerating
farmbuyer@10: -- everything on each new display for this tab anyhow.
farmbuyer@10: editbox.editBox:SetText(txt)
farmbuyer@10: _,start = txt:find(banner_unformatted..'\n'..banner_sep..'\n', --[[init=]]1, --[[plain=]]true)
farmbuyer@10: editbox.editBox:SetCursorPosition(start)
farmbuyer@10: end)
farmbuyer@10: container:AddChild(b)
farmbuyer@1: end
farmbuyer@1:
farmbuyer@1: addon:register_text_generator ("mleqdkp", [[ML/EQ-DKP]], [[MLdkp 1.1 EQDKP format]], generator, specials)
farmbuyer@1:
farmbuyer@1: -- vim:noet