# HG changeset patch # User Farmbuyer of US-Kilrogg # Date 1288636606 0 # Node ID f7c747904387ff2f92caeb24ed437d5e5ba3dba5 # Parent a3b67ffe00a0a885013271969951abaf6daab127 adding pkgmeta diff -r a3b67ffe00a0 -r f7c747904387 .pkgmeta --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.pkgmeta Mon Nov 01 18:36:46 2010 +0000 @@ -0,0 +1,19 @@ +package-as: GuildDelta + +externals: + libs/LibStub: + url: svn://svn.wowace.com/wow/libstub/mainline/trunk + tag: latest + libs/CallbackHandler-1.0: + url: svn://svn.wowace.com/wow/callbackhandler/mainline/trunk/CallbackHandler-1.0 + tag: latest + libs/AceAddon-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceAddon-3.0 + libs/AceConfig-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceConfig-3.0 + libs/AceConsole-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceConsole-3.0 + libs/AceGUI-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceGUI-3.0 + +# vim: et diff -r a3b67ffe00a0 -r f7c747904387 GuildDelta.toc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GuildDelta.toc Mon Nov 01 18:36:46 2010 +0000 @@ -0,0 +1,18 @@ +## Interface: 40000 +## Title: Guild Deltas +## Version: 4.0 +## Notes: On login, displays changes to your guild since the last time you logged in. +## Author: Farmbuyer of Kilrogg +## SavedVariables: GuildDeltaSV +## OptionalDeps: Ace3 + +#@no-lib-strip@ +libs\LibStub\LibStub.lua +libs\CallbackHandler-1.0\CallbackHandler-1.0.xml +libs\AceAddon-3.0\AceAddon-3.0.xml +libs\AceConsole-3.0\AceConsole-3.0.xml +libs\AceGUI-3.0\AceGUI-3.0.xml +libs\AceConfig-3.0\AceConfig-3.0.xml +#@end-no-lib-strip@ + +gd.lua diff -r a3b67ffe00a0 -r f7c747904387 gd.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gd.lua Mon Nov 01 18:36:46 2010 +0000 @@ -0,0 +1,514 @@ + +local addon = CreateFrame("Frame") --select(2,...) +local SV + +local DEFAULT_CHAT = 2 -- combat log (no constant predefined for that) + +local l10n +--if GetLocale() == "enUS" then + l10n= { + ["LEFT"] = "Players who have left the guild: ", + ["JOINED"] = "Players who have joined the guild: ", + ["RANK"] = "Players whose rank has changed:", + ["LEVEL"] = "Players whose level has changed:", + ["NOTES"] = "Players whose notes have changed:", + + ["Oabbrev"] = "O", -- abbreviation for "Officer", specifically the non-public note + ["FIELD_rank"] = "Rank", + ["FIELD_level"] = "Level", + ["FIELD_notes"] = "Player/Officer notes", + + -- Something needs to be done about the walls of description text below. + } +--elseif .... end + +local guild_selection, log_ever_shown +addon.options = { + name = "Guild Delta", + type = 'group', + handler = addon, -- functions listed as strings called as addon:func + args = { + version = { + --name = filled in during OnEnable + type = 'description', + fontSize = "large", + --image = "Interface\\AddOns\\GuildDelta\\test2", + cmdHidden = true, + order = 1, + }, + note = { + --name = filled in locals section + type = 'description', + cmdHidden = true, + width = 'full', + order = 2, + }, + reset = { + name = "Reset Output", + desc = "Restores default output settings", + type = 'execute', + order = 5, + func = "SetChat", + arg = DEFAULT_CHAT, + }, + spacer2 = { + name = '', + type = 'description', + cmdHidden = true, + width = 'full', + order = 6, + }, + print_chatframes = { + name = "Print Chatframe Numbers", + desc = "Print each chat window number in its frame, for easy reference in the next slider option", + type = 'execute', + --func = print_chatframes filled in below + order = 10, + }, + chatframe_num = { + name = "Output Chatframe", + desc = "Which chat window to use for printing all the output during login", + type = 'range', + min = 1, + max = NUM_CHAT_WINDOWS, + step = 1, + get = function() return tonumber(SV.chatframe) or --[[in case of custom name]]DEFAULT_CHAT end, + set = "SetChat", + order = 15, + }, + chatframe_name = { + name = "Chatframe Override", + desc = " If blank, uses the numerical slider. If set, it is the NAME of a frame with AddMessage capability to use for output.", + type = 'input', + get = function() + return type(SV.chatframe) == 'string' and SV.chatframe or nil + end, + set = "SetChat", + order = 16, + }, + fields = { + name = "Fields", + desc = "Track changes to these player fields", + type = 'multiselect', + order = 20, + -- these need to be of function type rather than string keys of members + values = function(info) return addon:MakeFieldList() end, + get = function(info,x) return SV.fields[x] end, + set = function(info,x,val) SV.fields[x] = val end, + }, + spacer1 = { + name = '', + type = 'description', + cmdHidden = true, + width = 'full', + order = 29, + }, + guilds = { + name = "Guilds", + desc = "Guilds for which a roster is known", + type = 'select', + order = 30, + width = 'double', + values = function(info) return addon:MakeGuildList() end, + get = function(info) return guild_selection end, + set = function(info,val) guild_selection = val end, + }, + clearguild = { + name = "Reset Guild", + desc = "Erase stored data for selected guild; information will be scanned from scratch on next login.", + type = 'execute', + order = 32, + disabled = function() return not guild_selection end, + func = function() + assert(type(guild_selection)=='string') + local g,r = guild_selection:match("<([^>]+)> %- (.*)") + local m = SV.members[r] + if m then + m[g] = nil + else + addon:Print("Hm, error.", r, "can't be matched as a realm name. Please report this as a bug, including the name of the realm and guild.") + end + end, + }, + persist = { + name = "Logging", + type = 'group', + inline = true, + order = 60, + args = { + note = { + --name = filled in locals section + type = 'description', + cmdHidden = true, + width = 'full', + order = 1, + }, + enable = { + name = "Enable Logging", + desc = [[Accumulate all deltas, including a timestamp. |cffFF0000WARNING|r: if logging was enabled and you turn it off, the log itself will not be saved when exiting the game.]], + type = 'toggle', + order = 2, + get = function() return SV.logging end, + set = function(i,v) + SV.logging = v + log_ever_shown = SV.logging or log_ever_shown + end, + }, + clearlog = { + name = "Reset Log", + desc = "Erase accumulated deltas.", + type = 'execute', + order = 3, + hidden = function() return not (SV.logging or log_ever_shown) end, + disabled = function() return not SV.logging end, + func = function() + SV.logtext = nil + end, + }, + log = { + name = "Log", + desc = "If you make changes, don't forget to click 'Accept' to save them.", + type = 'input', + order = 10, + multiline = 15, + width = 'full', + hidden = function() return not (SV.logging or log_ever_shown) end, + disabled = function() return not SV.logging end, + get = function() return SV.logtext end, + set = function(i,t) SV.logtext = t end, + }, + }, + }, + } +} + + +----------------------------------------------------------------------------- +-- other locals +local tinsert, GetGuildRosterInfo = _G.table.insert, _G.GetGuildRosterInfo +local chatframe, fieldlist +local function prt (...) + return chatframe:AddMessage(...) +end +local function cprt (txt) + if SV.logging then addon:AddLogNote(txt) end + return prt(txt, 255/255, 26/255, 160/255) +end + +function addon:current_guild_info (N) + local ret = {} + for i = 1, N do + local name,rank_as_string,_,level,_,_,publicnote,officernote = GetGuildRosterInfo(i) + -- This technically does not work out to the same as A?B:C, combined + -- with the logic below. It does, however, still result in the entry + -- not appearing in the returned table. + publicnote = publicnote ~= "" and publicnote or nil + officernote = officernote ~= "" and officernote or nil + if name then -- redundant, but apparently happens on extreme lag + tinsert(ret, + {name = name, + rank = SV.fields.rank and rank_as_string or nil, + level = SV.fields.level and level or nil, + pnote = SV.fields.notes and publicnote or nil, + onote = SV.fields.notes and officernote or nil, + }) + end + end + table.sort(ret, function (l,r) return l.name < r.name end) + return ret +end + +function addon.options.args.print_chatframes.func() + for i = 1, NUM_CHAT_WINDOWS do + local cf = _G["ChatFrame"..i] + if not cf then break end + addon:Print(cf, "This is frame number", i) + end +end + +addon.options.args.note.name = +"You can use the '/guilddelta' command to open the options window.\n\n".. +"The guild roster has already been scanned by the time you see this. Therefore, ".. +"if you make any changes to the Fields section below, you should probably relog ".. +"immediately to begin tracking the changed fields. Changes to the contents *OF* ".. +"those fields will not be noticed until the first login after *that*.\n\n" + +addon.options.args.persist.args.note.name = +"Enabling logging will accumulate the text of the 'deltas' as you see them. ".. +"This can grow large over time, depending on the activity of your guilds, so you ".. +"should use the Reset Log button below from time to time.\n\n".. +"If you click inside the text area below, you can use Control-A to select all ".. +"the text, and Control-C to copy it to your computer's clipboard. If you make ".. +"any changes to the text, they will be preserved. (You can remove uninteresting ".. +"changes, add reminders to yourself, and so forth.)\n\n" + + +----------------------------------------------------------------------------- +addon = LibStub("AceAddon-3.0"):NewAddon(addon, "GuildDelta", + "AceConsole-3.0") + +function addon:OnInitialize() + if _G.GuildDeltaSV == nil then + -- Defaults need to transition from potential older savedvars + _G.GuildDeltaSV = { + chatframe = _G.GuildDelta_chatframe or DEFAULT_CHAT, + fields = _G.GuildDelta_fields or { rank = true, level = false, notes = true }, + members = _G.GuildDelta_memberdata or {} + } + end + SV = _G.GuildDeltaSV +end + +-- Remove anything that normal operation doesn't need after finishing its work. +function addon:cleanup() + prt = nil; cprt = nil + self.current_guild_info = nil + self.cleanup = nil + self.unload = nil + self.logquay = nil + self.AddLogNote = nil + self.FinishLog = nil +end +function addon:unload() + self:cleanup() + LibStub("AceAddon-3.0").addons["GuildDelta"] = nil + l10n = nil; addon = nil; + -- put the userdata back so it counts as a Frame object again + local ud = self[0] + table.wipe(self) + self[0] = ud +end + +function addon:OnEnable() + if not IsInGuild() then + self:Print("You are not in a guild, not loading.") + return self:unload() + end + + AutoCompleteInfoDelayer:HookScript("OnFinished", + function() self:RegisterEvent("GUILD_ROSTER_UPDATE") end) + self:SetScript("OnEvent", self.GuildUpdate) + + self.options.args.version.name = + "|cff30adffVersion " .. (GetAddOnMetadata("GuildDelta", "Version") or "?") .. "|r" + LibStub("AceConfig-3.0"):RegisterOptionsTable("GuildDelta", self.options) + --[[self.optionsFrame =]] LibStub("AceConfigDialog-3.0"):AddToBlizOptions("GuildDelta", "Guild Delta") + self:RegisterChatCommand("guilddelta", "OnChatCommand") + self:SetChat(false) + log_ever_shown = SV.logging + self.OnEnable = nil +end + + +function addon:OnChatCommand (input) + if not input or input:trim() == "" then + LibStub("AceConfigDialog-3.0"):Open("GuildDelta") + else + LibStub("AceConfigCmd-3.0").HandleCommand(self, "guilddelta", "GuildDelta", input) + end +end + + +-- 0 args: called during startup to initialize +-- 1 arg: resetting via menus to defaults (see arg field) +-- 2 args: setting via menus to new value, number or custom name +function addon:SetChat (info, value) + local n, nframe + if info then -- coming via menu + n = info.arg or value + else + n = SV.chatframe + end + if type(n) == 'number' then + nframe = _G["ChatFrame"..n] + else + nframe = _G[n] + end + if type(nframe) == 'table' and type(nframe.AddMessage) == 'function' then + if type(info) ~= 'boolean' then + self:Print("Now printing to chat frame", n, + (type(nframe.name)=='string' and ("(".. nframe.name .. ")") or "")) + end + SV.chatframe = n + chatframe = nframe + else + self:Printf("EEEEEEEK! '%s' was not a valid chat frame number/name, no change has been made.", n) + end +end + + +-- Not a "normal" PLAYER_LOGOUT handler; this only fires if the player is in +-- a guild and the update has already run. +function addon:PLAYER_LOGOUT() + if not SV.logging then + SV.logtext = nil + end +end + + +function addon:GuildUpdate() + local current_n = GetNumGuildMembers(true) + if current_n <= 0 then + -- catch the hell up, servers... + return GuildRoster() + end + self:UnregisterEvent("GUILD_ROSTER_UPDATE") + self:SetScript("OnEvent", self.PLAYER_LOGOUT) -- keepin' it real^H^H^H^Hsmall and kludgey + self:RegisterEvent("PLAYER_LOGOUT") + self.GuildUpdate = nil + + local guild, realm = (GetGuildInfo("player")), GetRealmName() + local members = SV.members + if members[realm] + and members[realm][guild] + and #(members[realm][guild]) > 0 + then + -- moved the normal case below + else + -- new user, or new guild, or any number of things + self:Print("GuildDelta initializing roster...") + members[realm] = members[realm] or {} + members[realm][guild] = self:current_guild_info(current_n) + return + end + + -- table.insert with notes if available + -- concatentation of all strings faster than string.format + local function tins (t, x) + local s = x.name + if x.onote and (x.onote ~= "") then + s = s .. "(" .. l10n.Oabbrev .. ": " .. x.onote .. ")" + end + if x.pnote and (x.pnote ~= "") then + s = s .. "(" .. x.pnote .. ")" + end + tinsert(t, s) + end + + -- build the current list + local previous, current = members[realm][guild], + self:current_guild_info(current_n) + local previous_n = #previous + + -- walk both and do equivalence comparison + local joined, left, rank, level, notes = {}, {}, {}, {}, {} + local p, c = 1, 1 + while p <= previous_n and c <= current_n do + local P, C = previous[p], current[c] + + if P.name == C.name then + -- normal case + p = p + 1 + c = c + 1 + -- but can now compare details + if C.rank and P.rank and (P.rank ~= C.rank) then + tinsert(rank, {C.name, P.rank.." --> "..C.rank}) + end + if C.level and P.level and (P.level ~= C.level) then + tinsert(level, {C.name, P.level.." --> "..C.level}) + end + if C.pnote and (P.pnote ~= C.pnote) then + tinsert(notes, {C.name, C.pnote}) + end + if C.onote and (P.onote ~= C.onote) then + tinsert(notes, {C.name, "["..l10n.Oabbrev.."]: "..C.onote}) + end + + elseif P.name < C.name then + -- entry at index p not at c -> somebody has left + tins (left, P) + p = p + 1 + + else + -- entry at index c not at p -> somebody has joined + tins (joined, C) + c = c + 1 + end + end + + -- leftovers + for i = p, previous_n do + tins (left, previous[i]) + end + for i = c, current_n do + tins (joined, current[i]) + end + + -- show results + if SV.logging then self.logquay = {} end + local m + if #left > 0 then + m = l10n.LEFT .. table.concat(left, ", ") + cprt(m) + end + + if #joined > 0 then + m = l10n.JOINED .. table.concat(joined, ", ") + cprt(m) + end + + if #rank > 0 then + cprt(l10n.RANK) + for i = 1, #rank do + cprt(rank[i][1]..': '..rank[i][2]) + end + end + + if #level > 0 then + cprt(l10n.LEVEL) + for i = 1, #level do + cprt(level[i][1]..': '..level[i][2]) + end + end + + if #notes > 0 then + cprt(l10n.NOTES) + for i = 1, #notes do + cprt(notes[i][1]..': "'..notes[i][2]..'"') + end + end + + if SV.logging then self:FinishLog(guild,realm) end + members[realm][guild] = current +end + + +function addon:MakeFieldList() + if not fieldlist then + fieldlist = {} + for name in pairs(SV.fields) do + fieldlist[name] = l10n["FIELD_"..name] + end + end + return fieldlist +end + +function addon:MakeGuildList() + local list = {} + local K + for rname,rdata in pairs(SV.members) do + for g in pairs(rdata) do + K = ("<%s> - %s"):format(g,rname) + list[K] = K + end + end + return list +end + +function addon:AddLogNote (txt) + tinsert(self.logquay,txt) +end + +function addon:FinishLog(g,r) + if #self.logquay > 0 then + Calendar_LoadUI() + local _,M,D,Y = CalendarGetDate() + local h,m = GetGameTime() + local timestamp = ("%.4d/%.2d/%.2d %.2d:%.2d <%s> - %s\n"):format(Y,M,D,h,m,g,r) + SV.logtext = timestamp + .. table.concat(self.logquay, '\n') + .. (SV.logtext and ('\n\n'..SV.logtext) or '') + end +end + +-- vim:noet