You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

644 lines
23 KiB

5 years ago
-- imports
local WIM = WIM;
local _G = _G;
local CreateFrame = CreateFrame;
local select = select;
local type = type;
local table = table;
local pairs = pairs;
local string = string;
local next = next;
local Ambiguate = Ambiguate;
-- set name space
setfenv(1, WIM);
-- Core information
addonTocName = "WIM";
version = "3.9.1";
beta = false; -- flags current version as beta.
debug = false; -- turn debugging on and off.
useProtocol2 = true; -- test switch for new W2W Protocol. (Dev use only)
local buildNumber = select(4, _G.GetBuildInfo())
isShadowlands = buildNumber >= 90001;
isTBC = buildNumber >= 20501
-- is Private Server?
--[[isPrivateServer = not (string.match(_G.GetCVar("realmList"), "worldofwarcraft.com$")
or string.match(_G.GetCVar("realmList"), "battle.net$")
or string.match(_G.GetCVar("realmListbn"), "battle.net$")) and true or false;--]]
constants = {}; -- constants such as class colors will be stored here. (includes female class names).
modules = {}; -- module table. consists of all registerd WIM modules/plugins/skins. (treated the same).
windows = {active = {whisper = {}, chat = {}, w2w = {}}}; -- table of WIM windows.
libs = {}; -- table of loaded library references.
stats = {};
-- default options. live data is found in WIM.db
-- modules may insert fields into this table to
-- respect their option contributions.
db_defaults = {
enabled = true,
showToolTips = true,
modules = {},
alertedPrivateServer = false,
};
-- WIM.env is an evironmental reference for the current instance of WIM.
-- Information is stored here such as .realm and .character.
-- View table dump for more available information.
env = {};
-- default lists - This will store lists such as friends, guildies, raid members etc.
lists = {};
-- list of all the events registered from attached modules.
local Events = {};
-- create a frame to moderate events and frame updates.
local workerFrame = CreateFrame("Frame", "WIM_workerFrame");
workerFrame:SetScript("OnEvent", function(self, event, ...) WIM:CoreEventHandler(event, ...); end);
-- some events we always want to listen to so data is ready upon WIM being enabled.
workerFrame:RegisterEvent("VARIABLES_LOADED");
workerFrame:RegisterEvent("ADDON_LOADED");
-- called when WIM is first loaded into memory but after variables are loaded.
local function initialize()
--load cached information from the WIM_Cache saved variable.
env.cache[env.realm] = env.cache[env.realm] or {};
env.cache[env.realm][env.character] = env.cache[env.realm][env.character] or {};
lists.friends = env.cache[env.realm][env.character].friendList;
lists.guild = env.cache[env.realm][env.character].guildList;
if(type(lists.friends) ~= "table") then lists.friends = {}; end
if(type(lists.guild) ~= "table") then lists.guild = {}; end
workerFrame:RegisterEvent("GUILD_ROSTER_UPDATE");
workerFrame:RegisterEvent("FRIENDLIST_UPDATE");
workerFrame:RegisterEvent("BN_FRIEND_LIST_SIZE_CHANGED");
workerFrame:RegisterEvent("BN_FRIEND_INFO_CHANGED");
--querie guild roster
if( _G.IsInGuild() ) then
-- H.Sch. - ReglohPri - this is deprecated -> GuildRoster() - changed to C_GuildInfo.GuildRoster()
if not isShadowlands then
--for classic
_G.GuildRoster();
else
_G.C_GuildInfo.GuildRoster();
end
end
-- import libraries.
libs.SML = _G.LibStub:GetLibrary("LibSharedMedia-3.0");
libs.ChatHandler = _G.LibStub:GetLibrary("LibChatHandler-1.0");
isInitialized = true;
RegisterPrematureSkins();
--enableModules
local moduleName, tData;
for moduleName, tData in pairs(modules) do
modules[moduleName].db = db;
if(modules[moduleName].canDisable ~= false) then
local modDB = db.modules[moduleName];
if(modDB) then
if(modDB.enabled == nil) then
modDB.enabled = modules[moduleName].enableByDefault;
end
EnableModule(moduleName, modDB.enabled);
else
if(modules[moduleName].enableByDefault) then
EnableModule(moduleName, true);
end
end
else
EnableModule(moduleName, true);
end
end
for mName, module in pairs(modules) do
if(db.enabled) then
if(type(module.OnEnableWIM) == "function") then
module:OnEnableWIM();
end
else
if(type(module.OnDisableWIM) == "function") then
module:OnDisableWIM();
end
end
end
-- notify all modules of current state.
CallModuleFunction("OnStateChange", WIM.curState);
RegisterSlashCommand("enable", function() SetEnabled(not db.enabled) end, L["Toggle WIM 'On' and 'Off'."]);
RegisterSlashCommand("debug", function() debug = not debug; end, L["Toggle Debugging Mode 'On' and 'Off'."]);
FRIENDLIST_UPDATE(); -- pretend event has been fired in order to get cache loaded.
CallModuleFunction("OnInitialized");
WindowParent:Show();
dPrint("WIM initialized...");
end
--Begin Compat wrappers for retail and classic to access same functions and expect same returns
--These should work in all files that use them since they are written to WIMs global namespace
--Retail kind of has these for now, but won't forever, and classic is not expected to make same API restructuring, so this ugly mess is probably required forever
function GetBNGetFriendInfo(friendIndex)
if not isShadowlands then--Classic
return _G.BNGetFriendInfo(friendIndex)
else
local accountInfo = _G.C_BattleNet.GetFriendAccountInfo(friendIndex);
if accountInfo then
local wowProjectID = accountInfo.gameAccountInfo.wowProjectID or 0;
local clientProgram = accountInfo.gameAccountInfo.clientProgram ~= "" and accountInfo.gameAccountInfo.clientProgram or nil;
return accountInfo.bnetAccountID, accountInfo.accountName, accountInfo.battleTag, accountInfo.isBattleTagFriend,
accountInfo.gameAccountInfo.characterName, accountInfo.gameAccountInfo.gameAccountID, clientProgram,
accountInfo.gameAccountInfo.isOnline, accountInfo.lastOnlineTime, accountInfo.isAFK, accountInfo.isDND, accountInfo.customMessage, accountInfo.note, accountInfo.isFriend,
accountInfo.customMessageTime, wowProjectID, accountInfo.rafLinkType == _G.Enum.RafLinkType.Recruit, accountInfo.gameAccountInfo.canSummon, accountInfo.isFavorite, accountInfo.gameAccountInfo.isWowMobile;
end
end
end
function GetBNGetFriendInfoByID(id)
if not isShadowlands then--Classic/TBC
return _G.BNGetFriendInfoByID(id)
else--Retail
local accountInfo = _G.C_BattleNet.GetAccountInfoByID(id) or {};
if accountInfo and accountInfo.gameAccountInfo then
local wowProjectID = accountInfo.gameAccountInfo.wowProjectID or 0;
local clientProgram = accountInfo.gameAccountInfo.clientProgram ~= "" and accountInfo.gameAccountInfo.clientProgram or nil;
return accountInfo.bnetAccountID, accountInfo.accountName, accountInfo.battleTag, accountInfo.isBattleTagFriend,
accountInfo.gameAccountInfo.characterName, accountInfo.gameAccountInfo.gameAccountID, clientProgram,
accountInfo.gameAccountInfo.isOnline, accountInfo.lastOnlineTime, accountInfo.isAFK, accountInfo.isDND, accountInfo.customMessage, accountInfo.note, accountInfo.isFriend,
accountInfo.customMessageTime, wowProjectID, accountInfo.rafLinkType == _G.Enum.RafLinkType.Recruit, accountInfo.gameAccountInfo.canSummon, accountInfo.isFavorite, accountInfo.gameAccountInfo.isWowMobile;
end
end
end
function GetBNGetGameAccountInfo(toonId)
if not isShadowlands then--Classic/TBC
return _G.BNGetGameAccountInfo(toonId)
else--Retail
local gameAccountInfo = _G.C_BattleNet.GetGameAccountInfoByID(toonId)
if gameAccountInfo then
local wowProjectID = gameAccountInfo.wowProjectID or 0;
local characterName = gameAccountInfo.characterName or "";
local realmName = gameAccountInfo.realmName or "";
local realmID = gameAccountInfo.realmID or 0;
local factionName = gameAccountInfo.factionName or "";
local raceName = gameAccountInfo.raceName or "";
local className = gameAccountInfo.className or "";
local areaName = gameAccountInfo.areaName or "";
local characterLevel = gameAccountInfo.characterLevel or "";
local richPresence = gameAccountInfo.richPresence or "";
local gameAccountID = gameAccountInfo.gameAccountID or 0;
local playerGuid = gameAccountInfo.playerGuid or 0;
return gameAccountInfo.hasFocus, characterName, gameAccountInfo.clientProgram,
realmName, realmID, factionName, raceName, className, "", areaName, characterLevel,
richPresence, nil, nil,
gameAccountInfo.isOnline, gameAccountID, nil, gameAccountInfo.isGameAFK, gameAccountInfo.isGameBusy,
playerGuid, wowProjectID, gameAccountInfo.isWowMobile
end
end
end
--End Compat wrappers for retail and classic to access same functions and expect same returns
-- called when WIM is enabled.
-- WIM will not be enabled until WIM is initialized event is fired.
local function onEnable()
db.enabled = true;
local tEvent;
for tEvent, _ in pairs(Events) do
workerFrame:RegisterEvent(tEvent);
end
if(isInitialized) then
for mName, module in pairs(modules) do
if(type(module.OnEnableWIM) == "function") then
module:OnEnableWIM();
end
if(db.modules[mName] and db.modules[mName].enabled and type(module.OnEnable) == "function") then
module:OnEnable();
end
end
end
-- DisplayTutorial(L["WIM (WoW Instant Messenger)"], L["WIM is currently running. To access WIM's wide array of options type:"].." |cff69ccf0/wim|r");
dPrint("WIM is now enabled.");
--[[ --Private Server Check
if(isPrivateServer and not db.alertedPrivateServer) then
_G.StaticPopupDialogs["WIM_PRIVATE_SERVER"] = {
preferredIndex = STATICPOPUP_NUMDIALOGS,
text = L["WIM has detected that you are playing on a private server. Some servers can not process ChatAddonMessages. Would you like to enable them anyway?"],
button1 = _G.TEXT(_G.YES),
button2 = _G.TEXT(_G.NO),
OnShow = function(self) end,
OnHide = function() end,
OnAccept = function() db.disableAddonMessages = false; db.alertedPrivateServer = true; end,
OnCancel = function() db.disableAddonMessages = true; db.alertedPrivateServer = true; end,
timeout = 0,
whileDead = 1,
hideOnEscape = 1
};
_G.StaticPopup_Show ("WIM_PRIVATE_SERVER", theLink);
end--]]
end
-- called when WIM is disabled.
local function onDisable()
db.enabled = false;
local tEvent;
for tEvent, _ in pairs(Events) do
workerFrame:UnregisterEvent(tEvent);
end
if(isInitialized) then
for _, module in pairs(modules) do
if(type(module.OnDisableWIM) == "function") then
module:OnDisableWIM();
end
if(type(module.OnDisable) == "function") then
module:OnDisable();
end
end
end
dPrint("WIM is now disabled.");
end
function SetEnabled(enabled)
if( enabled ) then
onEnable();
else
onDisable();
end
end
-- events are passed to modules. Events do not need to be
-- unregistered. A disabled module will not receive events.
local function RegisterEvent(event)
Events[event] = true;
if( db and db.enabled ) then
workerFrame:RegisterEvent(event);
end
end
-- create a new WIM module. Will return module object.
function CreateModule(moduleName, enableByDefault)
if(type(moduleName) == "string") then
modules[moduleName] = {
title = moduleName,
enabled = false,
enableByDefault = enableByDefault or false,
canDisable = true,
resources = {
lists = lists,
windows = windows,
env = env,
constants = constants,
libs = libs,
},
db = db,
db_defaults = db_defaults,
RegisterEvent = function(self, event) RegisterEvent(event); end,
Enable = function() EnableModule(moduleName, true) end,
Disable = function() EnableModule(moduleName, false) end,
dPrint = function(self, t) dPrint(t); end,
hasWidget = false,
RegisterWidget = function(widgetName, createFunction) RegisterWidget(widgetName, createFunction, moduleName); end
}
return modules[moduleName];
else
return nil;
end
end
function EnableModule(moduleName, enabled)
if(enabled == nil) then enabled = false; end
local module = modules[moduleName];
if(module) then
if(module.canDisable == false and enabled == false) then
dPrint("Module '"..moduleName.."' can not be disabled!");
return;
end
if(db) then
db.modules[moduleName] = WIM.db.modules[moduleName] or {};
db.modules[moduleName].enabled = enabled;
end
if(enabled) then
module.enabled = enabled;
if(enabled and type(module.OnEnable) == "function") then
module:OnEnable();
elseif(not enabled and type(module.OnDisable) == "function") then
module:OnDisable();
end
dPrint("Module '"..moduleName.."' Enabled");
else
if(module.hasWidget) then
dPrint("Module '"..moduleName.."' will be disabled after restart.");
else
module.enabled = enabled;
if(enabled and type(module.OnEnable) == "function") then
module:OnEnable();
elseif(not enabled and type(module.OnDisable) == "function") then
module:OnDisable();
end
dPrint("Module '"..moduleName.."' Disabled");
end
end
end
end
function CallModuleFunction(funName, ...)
-- notify all enabled modules.
dPrint("Calling Module Function: "..funName);
local module, tData, fun;
for module, tData in pairs(WIM.modules) do
fun = tData[funName];
if(type(fun) == "function" and tData.enabled) then
dPrint(" +--"..module);
fun(tData, ...);
end
end
end
--------------------------------------
-- Event Handlers --
--------------------------------------
function WIM.honorChatFrameEventFilter(event, ...)
local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15 = ...;
local chatFilters = _G.ChatFrame_GetMessageEventFilters(event);
local filter = false;
if chatFilters then
local narg1, narg2, narg3, narg4, narg5, narg6, narg7, narg8, narg9, narg10, narg11, narg12, narg13, narg14, narg15;
for _, filterFunc in next, chatFilters do
filter, narg1, narg2, narg3, narg4, narg5, narg6, narg7, narg8, narg9, narg10, narg11, narg12, narg13, narg14, narg15 = filterFunc(workerFrame, event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15);
if filter then
return true, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15;
elseif(narg1) then
arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15 = narg1, narg2, narg3, narg4, narg5, narg6, narg7, narg8, narg9, narg10, narg11, narg12, narg13, narg14, narg15;
end
end
end
return filter, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15;
end
function WIM:EventHandler(event,...)
-- depricated - here for compatibility only
end
-- This is WIM's core event controler.
function WIM:CoreEventHandler(event, ...)
-- Core WIM Event Handlers.
dPrint("Event '"..event.."' received.");
local fun = WIM[event];
if(type(fun) == "function") then
dPrint(" +-- WIM:"..event);
fun(WIM, ...);
end
-- Module Event Handlers
if(db and db.enabled) then
local module, tData;
for module, tData in pairs(modules) do
fun = tData[event];
if(type(fun) == "function" and tData.enabled) then
dPrint(" +-- "..module..":"..event);
fun(modules[module], ...);
end
end
end
end
function WIM:VARIABLES_LOADED()
_G.WIM3_Data = _G.WIM3_Data or {};
db = _G.WIM3_Data;
_G.WIM3_Cache = _G.WIM3_Cache or {};
env.cache = _G.WIM3_Cache;
_G.WIM3_Filters = _G.WIM3_Filters or GetDefaultFilters();
_G.WIM3_ChatFilters = _G.WIM3_ChatFilters or {};
if(#_G.WIM3_Filters == 0) then
_G.WIM3_Filters = GetDefaultFilters();
end
filters = _G.WIM3_Filters;
chatFilters = _G.WIM3_ChatFilters;
_G.WIM3_History = _G.WIM3_History or {};
history = _G.WIM3_History;
-- load some environment data.
env.realm = _G.GetRealmName();
env.character = _G.UnitName("player");
-- inherrit any new default options which wheren't shown in previous releases.
inherritTable(db_defaults, db);
lists.gm = {};
-- load previous state into memory
curState = db.lastState;
SetEnabled(db.enabled);
initialize();
--_G.print("WIM Notice: Since 7.0 there is a new bug where first whisper is not visible until you get a 2nd whisper, or you scroll up and then back down. That's work around. Scroll up, then scroll down.")
end
function WIM:FRIENDLIST_UPDATE()
env.cache[env.realm][env.character].friendList = env.cache[env.realm][env.character].friendList or {};
for key, d in pairs(env.cache[env.realm][env.character].friendList) do
if(d == 1) then
env.cache[env.realm][env.character].friendList[key] = nil;
end
end
if _G.C_FriendList then
for i=1, _G.C_FriendList.GetNumFriends() do
local name = _G.C_FriendList.GetFriendInfoByIndex(i).name;
if(name) then
env.cache[env.realm][env.character].friendList[name] = 1; --[set place holder for quick lookup
end
end
else
for i=1, _G.GetNumFriends() do
local name = _G.GetFriendInfo(i);
if(name) then
env.cache[env.realm][env.character].friendList[name] = 1; --[set place holder for quick lookup
end
end
end
lists.friends = env.cache[env.realm][env.character].friendList;
dPrint("Friends list updated...");
end
function WIM:BN_FRIEND_LIST_SIZE_CHANGED()
env.cache[env.realm][env.character].friendList = env.cache[env.realm][env.character].friendList or {};
for key, d in pairs(env.cache[env.realm][env.character].friendList) do
if(d == 2) then
env.cache[env.realm][env.character].friendList[key] = nil;
end
end
for i=1, _G.BNGetNumFriends() do
local id, name = GetBNGetFriendInfo(i);
if(name) then
env.cache[env.realm][env.character].friendList[name] = 2; --[set place holder for quick lookup
if(windows.active.whisper[name]) then
windows.active.whisper[name]:SendWho();
end
end
end
lists.friends = env.cache[env.realm][env.character].friendList;
dPrint("RealID list updated...");
end
WIM.BN_FRIEND_INFO_CHANGED = WIM.BN_FRIEND_LIST_SIZE_CHANGED;
function WIM:GUILD_ROSTER_UPDATE()
env.cache[env.realm][env.character].guildList = env.cache[env.realm][env.character].guildList or {};
for key, _ in pairs(env.cache[env.realm][env.character].guildList) do
env.cache[env.realm][env.character].guildList[key] = nil;
end
if(_G.IsInGuild()) then
for i=1, _G.GetNumGuildMembers(true) do
local name = _G.GetGuildRosterInfo(i);
if(name) then
name = Ambiguate(name, "none")
env.cache[env.realm][env.character].guildList[name] = i; --[set place holder for quick lookup
end
end
end
lists.guild = env.cache[env.realm][env.character].guildList;
dPrint("Guild list updated...");
end
function IsGM(name)
if(name == nil or name == "") then
return false;
end
-- Blizz gave us a new tool. Lets use it.
if(_G.GMChatFrame_IsGM and _G.GMChatFrame_IsGM(name)) then
lists.gm[name] = 1;
return true;
end
if(lists.gm[name]) then
return true;
else
return false;
end
end
function IsInParty(user)
for i=1, 4 do
if(_G.GetUnitName("party"..i, true) == user) then
return true;
end
end
return false;
end
function IsInRaid(user)
for i=1, _G.GetNumGroupMembers() do
if(_G.GetUnitName("raid"..i, true) == user) then
return true;
end
end
return false;
end
function CompareVersion(v, withV)
withV = withV or version;
local M, m, r = string.match(v, "(%d+).(%d+).(%d+)");
local cM, cm, cr = string.match(withV, "(%d+).(%d+).(%d+)");
M, m = M*100000, m*1000;
cM, cm = cM*100000, cm*1000;
local this, that = cM+cm+cr, M+m+r;
return that - this;
end
local talentOrder = {};
function TalentsToString(talents, class)
--passed talents in format of "#/#/#";
-- first check that all required information is passed.
local t1, t2, t3 = string.match(talents or "", "(%d+)/(%d+)/(%d+)");
if(not t1 or not t2 or not t3 or not class) then
return talents;
end
-- next check if we even have information to show.
if(talents == "0/0/0") then return L["None"]; end
local classTbl = constants.classes[class];
if(not classTbl) then
return talents;
end
-- clear talentOrder
for k, _ in pairs(talentOrder) do
talentOrder[k] = nil;
end
--calculate which order the tabs should be in; in relation to spec.
table.insert(talentOrder, t1.."1");
table.insert(talentOrder, t2.."2");
table.insert(talentOrder, t3.."3");
table.sort(talentOrder);
local fVal, f = string.match(_G.tostring(talentOrder[3]), "^(%d+)(%d)$");
local sVal, s = string.match(_G.tostring(talentOrder[2]), "^(%d+)(%d)$");
local tVal, t = string.match(_G.tostring(talentOrder[1]), "^(%d+)(%d)$");
if(_G.tonumber(fVal)*.75 <= _G.tonumber(sVal)) then
if(_G.tonumber(fVal)*.75 <= _G.tonumber(tVal)) then
return L["Hybrid"]..": "..talents;
else
return classTbl.talent[_G.tonumber(f)].."/"..classTbl.talent[_G.tonumber(s)]..": "..talents;
end
else
return classTbl.talent[_G.tonumber(f)]..": "..talents;
end
end
function GetTalentSpec()
local talents, tabs = "", _G.GetNumTalentTabs();
for i=1, tabs do
local name, _, _, _, pointsSpent = _G.GetTalentTabInfo(i);
talents = i==tabs and talents..pointsSpent or talents..pointsSpent.."/";
end
return talents ~= "" and talents or "0/0/0";
end
-- list of PreSendFilterText(text)
local preSendFilterTextFunctions = {};
function PreSendFilterText(text)
for i=1, #preSendFilterTextFunctions do
text = preSendFilterTextFunctions[i](text);
end
return text;
end
function RegisterPreSendFilterText(func)
if(type(func) == "function") then
table.insert(preSendFilterTextFunctions, func);
end
end
--[[ Example usage
RegisterPreSendFilterText(
function(text)
return "john";
end
);
]]