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