From 9995af537f3cd593b8f48efe94fd0abeb0048a39 Mon Sep 17 00:00:00 2001
From: konaka <>
Date: Fri, 26 Sep 2025 13:38:30 +0800
Subject: [PATCH] Move house
---
Bindings.xml | 71 +++
README.md | 2 +
UnitXP_SP3_Addon.lua | 842 +++++++++++++++++++++++++++++++++++
UnitXP_SP3_Addon.toc | 14 +
UnitXP_SP3_Addon.xml | 658 +++++++++++++++++++++++++++
libs/AceCore-3.0.lua | 209 +++++++++
libs/AceHook-3.0.lua | 540 ++++++++++++++++++++++
libs/CallbackHandler-1.0.lua | 280 ++++++++++++
libs/LibDBIcon-1.0.lua | 487 ++++++++++++++++++++
libs/LibDataBroker-1.1.lua | 90 ++++
libs/LibStub.lua | 33 ++
11 files changed, 3226 insertions(+)
create mode 100644 Bindings.xml
create mode 100644 README.md
create mode 100755 UnitXP_SP3_Addon.lua
create mode 100755 UnitXP_SP3_Addon.toc
create mode 100755 UnitXP_SP3_Addon.xml
create mode 100644 libs/AceCore-3.0.lua
create mode 100644 libs/AceHook-3.0.lua
create mode 100644 libs/CallbackHandler-1.0.lua
create mode 100644 libs/LibDBIcon-1.0.lua
create mode 100644 libs/LibDataBroker-1.1.lua
create mode 100644 libs/LibStub.lua
diff --git a/Bindings.xml b/Bindings.xml
new file mode 100644
index 0000000..8982949
--- /dev/null
+++ b/Bindings.xml
@@ -0,0 +1,71 @@
+
+
+
+ UnitXP("target", "nearestEnemy");
+
+
+
+ UnitXP("target", "mostHP");
+
+
+
+ UnitXP("target", "worldBoss");
+
+
+
+ UnitXP("target", "nextMarkedEnemyInCycle");
+
+
+
+ UnitXP("target", "previousMarkedEnemyInCycle");
+
+
+
+ UnitXP("target", "nextEnemyInCycle");
+
+
+
+ UnitXP("target", "previousEnemyInCycle");
+
+
+
+ UnitXP("target", "nextEnemyConsideringDistance");
+
+
+
+ UnitXP("target", "previousEnemyConsideringDistance");
+
+
+
+ UnitXP_SP3_raiseCameraHeight();
+
+
+
+ UnitXP_SP3_lowerCameraHeight();
+
+
+
+ UnitXP_SP3_rightPlayer();
+
+
+
+ UnitXP_SP3_leftPlayer();
+
+
+
+ UnitXP_SP3_resetCamera();
+
+
+
+ UnitXP_SP3_toggleModernNameplateDistance();
+
+
+
+ UnitXP_SP3_togglePrioritizeTargetNameplate();
+
+
+
+ UnitXP_SP3_togglePrioritizeMarkedNameplate();
+
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..55a8785
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# UnitXP_SP3_Addon
+Vanilla 1.12 Lua Addon for supporting [UnitXP SP3 mod](https://codeberg.org/konaka/UnitXP_SP3)
diff --git a/UnitXP_SP3_Addon.lua b/UnitXP_SP3_Addon.lua
new file mode 100755
index 0000000..57e9d31
--- /dev/null
+++ b/UnitXP_SP3_Addon.lua
@@ -0,0 +1,842 @@
+--[[
+ UnitXP Service Pack 3 Lua Addon
+
+ By allfox, and the thankful community
+]] --
+UnitXP_SP3_Addon = nil; -- It's a SavedVariable, not local
+UnitXP_SP3_Icon = nil; -- It's a SavedVariable, not local
+
+local function UnitXP_SP3_Print(msg)
+ if not DEFAULT_CHAT_FRAME then
+ return;
+ end
+ DEFAULT_CHAT_FRAME:AddMessage(tostring(msg));
+end
+
+-- Commands
+SLASH_UNITXP1 = "/unitxp";
+SlashCmdList["UNITXP"] = function()
+ if pcall(UnitXP, "nop", "nop") == true then
+ if xpsp3Frame:IsShown() then
+ PlaySound("igMainMenuContinue");
+ xpsp3Frame:Hide();
+ else
+ PlaySound("igMainMenuOpen");
+ xpsp3Frame:Show();
+ end
+ else
+ UnitXP_SP3_Print("UnitXP Service Pack 3 didn't load properly.");
+ end
+end
+
+-- Bindings
+BINDING_HEADER_UNITXPSP3TARGETING = "UnitXP SP3 Targeting Functions";
+BINDING_NAME_UNITXPSP3NEARESTENEMY = "Nearest Enemy";
+BINDING_NAME_UNITXPSP3TARGETMOSTHP = "The enemy with most HP";
+BINDING_NAME_UNITXPSP3WORLDBOSS = "World Boss";
+BINDING_NAME_UNITXPSP3NEXTMARKER = "Next Target Marker";
+BINDING_NAME_UNITXPSP3PREVIOUSMARKER = "Previous Target Marker";
+BINDING_NAME_UNITXPSP3NEXTENEMY = "Next Enemy";
+BINDING_NAME_UNITXPSP3PREVIOUSENEMY = "Previous Enemy";
+BINDING_NAME_UNITXPSP3NEXTMELEE = "Next Enemy Prioritizing Melee";
+BINDING_NAME_UNITXPSP3PREVIOUSMELEE = "Previous Enemy Prioritizing Melee";
+BINDING_HEADER_UNITXPSP3UTILITIES = "UnitXP SP3 Utilities";
+BINDING_NAME_UNITXPSP3RAISECAMERA = "Raise Camera";
+BINDING_NAME_UNITXPSP3LOWERCAMERA = "Lower Camera";
+BINDING_NAME_UNITXPSP3LEFTCAMERA = "Left Camera";
+BINDING_NAME_UNITXPSP3RIGHTCAMERA = "Right Camera";
+BINDING_NAME_UNITXPSP3RESETCAMERA = "Reset Camera";
+BINDING_NAME_UNITXPSP3TOGGLEMODERNNAMEPLATEDISTANCE = "Toggle Proper Nameplates Behavior";
+BINDING_NAME_UNITXPSP3TOGGLEPRIORITIZETARGETNAMEPLATE = "Toggle Prioritize Target Nameplate";
+BINDING_NAME_UNITXPSP3TOGGLEPRIORITIZEMARKEDNAMEPLATE = "Toggle Prioritize Marked Nameplate";
+
+local UNITXPSP3TOOLTIP = "UnitXP SP3 is running";
+
+local libIcon = LibStub("LibDBIcon-1.0");
+local libData = LibStub("LibDataBroker-1.1");
+local skipUpdateMessage = false;
+local original_CombatText_AddMessage = nil;
+
+function UnitXP_SP3_OnLoad()
+ xpsp3Frame:RegisterEvent("ADDON_LOADED");
+end
+
+local function UnitXP_SP3_flashTaskbarIcon()
+ local test, result = pcall(UnitXP, "notify", "taskbarIcon");
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"notify taskbarIcon\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return nil;
+ end
+
+ return result;
+end
+
+local function UnitXP_SP3_playSystemDefaultSound()
+ local test, result = pcall(UnitXP, "notify", "systemSound", "SystemDefault");
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"notify systemSound\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return nil;
+ end
+
+ return result;
+end
+
+local function UnitXP_SP3_FPScap(cap)
+ local test, result = pcall(UnitXP, "FPScap", cap);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"FPScap\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return UnitXP_SP3_Addon["FPScap"];
+ end
+
+ UnitXP_SP3_Addon["FPScap"] = result;
+ return UnitXP_SP3_Addon["FPScap"];
+end
+
+local function UnitXP_SP3_setTargetingRangeConeFactor(factor)
+ local test, result = pcall(UnitXP, "target", "rangeCone", factor);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"target rangeCone\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return UnitXP_SP3_Addon["targetingRangeConeFactor"];
+ end
+
+ UnitXP_SP3_Addon["targetingRangeConeFactor"] = result;
+ return UnitXP_SP3_Addon["targetingRangeConeFactor"];
+end
+
+local function UnitXP_SP3_setModernNameplateDistance(enable)
+ local test, result = pcall(UnitXP, "modernNameplateDistance", enable);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"modernNameplateDistance\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return false;
+ end
+ return result;
+end
+
+function UnitXP_SP3_detoured_CombatText_AddMessage(message, scrollFunction, r, g, b, displayType, isStaggered)
+ if UnitXP_SP3_Addon["combatTextSP3"] then
+ if displayType == "crit" then
+ pcall(UnitXP, "addCombatText", "crit", message, r, g, b);
+ else
+ pcall(UnitXP, "addCombatText", "normal", message, r, g, b);
+ end
+ return nil;
+ else
+ if original_CombatText_AddMessage ~= nil then
+ return original_CombatText_AddMessage(message, scrollFunction, r, g, b, displayType, isStaggered);
+ end
+ end
+end
+
+local function UnitXP_SP3_combatTextSP3(enable)
+ local test, result = pcall(UnitXP, "combatTextSP3", enable);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"combatTextSP3\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return false;
+ end
+
+ return result;
+end
+
+local function UnitXP_SP3_combatTextSP3_fontSize(size)
+ local test, result, reloadResult = pcall(UnitXP, "combatTextSP3", "setFontSize", size);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"combatTextSP3 setFontSize\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return UnitXP_SP3_Addon["combatTextSP3_fontSize"];
+ end
+
+ if not reloadResult then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3 failed to reload combat message font. This could because of missing d3dx9_43.dll on your system. Please try installing DirectX End-User Runtimes.");
+ end
+ return UnitXP_SP3_Addon["combatTextSP3_fontSize"];
+ end
+
+ UnitXP_SP3_Addon["combatTextSP3_fontSize"] = result;
+ return UnitXP_SP3_Addon["combatTextSP3_fontSize"];
+end
+
+local function UnitXP_SP3_setPrioritizeTargetNameplate(enable)
+ local test, result = pcall(UnitXP, "prioritizeTargetNameplate", enable);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"prioritizeTargetNameplate\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return false;
+ end
+ return result;
+end
+
+local function UnitXP_SP3_setPrioritizeMarkedNameplate(enable)
+ local test, result = pcall(UnitXP, "prioritizeMarkedNameplate", enable);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"prioritizeMarkedNameplate\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return false;
+ end
+ return result;
+end
+
+local function UnitXP_SP3_setNameplateCombatFilter(enable)
+ local test, result = pcall(UnitXP, "nameplateCombatFilter", enable);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"nameplateCombatFilter\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return false;
+ end
+ return result;
+end
+
+local function UnitXP_SP3_setHideCritterNameplate(enable)
+ local test, result = pcall(UnitXP, "hideCritterNameplate", enable);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"hideCritterNameplate\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return false;
+ end
+ return result;
+end
+
+local function UnitXP_SP3_setShowInCombatNameplatesNearPlayer(enable)
+ local test, result = pcall(UnitXP, "showInCombatNameplatesNearPlayer", enable);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"showInCombatNameplatesNearPlayer\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return false;
+ end
+ return result;
+end
+
+local function UnitXP_SP3_setWeatherAlwaysClear(enable)
+ local test, result = pcall(UnitXP, "weatherAlwaysClear", enable);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"weatherAlwaysClear\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return false;
+ end
+ return result;
+end
+
+local function UnitXP_SP3_setCameraPinHeight(enable)
+ local test, result = pcall(UnitXP, "cameraPinHeight", enable);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"cameraPinHeight\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return false;
+ end
+ return result;
+end
+
+local function UnitXP_SP3_setCameraHeight(value)
+ local test, result = pcall(UnitXP, "cameraVerticalDisplacement", "set", value);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"cameraVerticalDisplacement set\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return UnitXP_SP3_Addon["cameraHeight"];
+ end
+
+ UnitXP_SP3_Addon["cameraHeight"] = result;
+ return UnitXP_SP3_Addon["cameraHeight"];
+end
+
+local function UnitXP_SP3_setCameraPitch(value)
+ local test, result = pcall(UnitXP, "cameraPitch", "set", value);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"cameraPitch set\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return UnitXP_SP3_Addon["cameraPitch"];
+ end
+
+ UnitXP_SP3_Addon["cameraPitch"] = result;
+ return UnitXP_SP3_Addon["cameraPitch"];
+end
+
+local function UnitXP_SP3_setCameraHorizontalDisplacement(value)
+ local test, result = pcall(UnitXP, "cameraHorizontalDisplacement", "set", value);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"cameraHorizontalDisplacement set\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return UnitXP_SP3_Addon["cameraHorizontalDisplacement"];
+ end
+
+ UnitXP_SP3_Addon["cameraHorizontalDisplacement"] = result;
+ return UnitXP_SP3_Addon["cameraHorizontalDisplacement"];
+end
+
+function UnitXP_SP3_resetCamera()
+ UnitXP_SP3_setCameraHorizontalDisplacement(0);
+ UnitXP_SP3_setCameraHeight(0);
+ UnitXP_SP3_setCameraPitch(0);
+ return 0;
+end
+
+function UnitXP_SP3_leftPlayer()
+ return UnitXP_SP3_setCameraHorizontalDisplacement(UnitXP_SP3_Addon["cameraHorizontalDisplacement"] + 0.11);
+end
+
+function UnitXP_SP3_rightPlayer()
+ return UnitXP_SP3_setCameraHorizontalDisplacement(UnitXP_SP3_Addon["cameraHorizontalDisplacement"] - 0.11);
+end
+
+function UnitXP_SP3_raiseCameraHeight()
+ return UnitXP_SP3_setCameraHeight(UnitXP_SP3_Addon["cameraHeight"] + 0.11);
+end
+
+function UnitXP_SP3_lowerCameraHeight()
+ return UnitXP_SP3_setCameraHeight(UnitXP_SP3_Addon["cameraHeight"] - 0.11);
+end
+
+function UnitXP_SP3_cameraPitchUp()
+ return UnitXP_SP3_setCameraPitch(UnitXP_SP3_Addon["cameraPitch"] + 0.011);
+end
+
+function UnitXP_SP3_cameraPitchDown()
+ return UnitXP_SP3_setCameraPitch(UnitXP_SP3_Addon["cameraPitch"] - 0.011);
+end
+
+local function UnitXP_SP3_reloadConfig()
+ UnitXP_SP3_FPScap(UnitXP_SP3_Addon["FPScap"]);
+ xpsp3_editBox_FPScap:SetNumber(UnitXP_SP3_Addon["FPScap"]);
+
+ UnitXP_SP3_combatTextSP3_fontSize(UnitXP_SP3_Addon["combatTextSP3_fontSize"]);
+ xpsp3_editBox_combatTextSP3_fontSize:SetNumber(UnitXP_SP3_Addon["combatTextSP3_fontSize"]);
+
+ UnitXP_SP3_setTargetingRangeConeFactor(UnitXP_SP3_Addon["targetingRangeConeFactor"]);
+
+ UnitXP_SP3_setCameraHeight(UnitXP_SP3_Addon["cameraHeight"]);
+ UnitXP_SP3_setCameraPitch(UnitXP_SP3_Addon["cameraPitch"]);
+ UnitXP_SP3_setCameraHorizontalDisplacement(UnitXP_SP3_Addon["cameraHorizontalDisplacement"]);
+
+ if UnitXP_SP3_Icon.hide then
+ xpsp3_checkButton_minimapButton:SetChecked(false);
+ else
+ xpsp3_checkButton_minimapButton:SetChecked(true);
+ end
+
+ if UnitXP_SP3_Addon["modernNameplateDistance"] then
+ UnitXP_SP3_setModernNameplateDistance("enable");
+ xpsp3_checkButton_modernNameplate:SetChecked(true);
+ else
+ UnitXP_SP3_setModernNameplateDistance("disable");
+ xpsp3_checkButton_modernNameplate:SetChecked(false);
+ end
+
+ if UnitXP_SP3_Addon["combatTextSP3"] then
+ UnitXP_SP3_combatTextSP3("enable");
+ xpsp3_checkButton_combatTextSP3:SetChecked(true);
+ else
+ UnitXP_SP3_combatTextSP3("disable");
+ xpsp3_checkButton_combatTextSP3:SetChecked(false);
+ end
+
+ if UnitXP_SP3_Addon["cameraPinHeight"] then
+ UnitXP_SP3_setCameraPinHeight("enable");
+ xpsp3_checkButton_cameraPinHeight:SetChecked(true);
+ else
+ UnitXP_SP3_setCameraPinHeight("disable");
+ xpsp3_checkButton_cameraPinHeight:SetChecked(false);
+ end
+
+ if UnitXP_SP3_Addon["prioritizeTargetNameplate"] then
+ UnitXP_SP3_setPrioritizeTargetNameplate("enable");
+ xpsp3_checkButton_prioritizeTargetNameplate:SetChecked(true);
+ else
+ UnitXP_SP3_setPrioritizeTargetNameplate("disable");
+ xpsp3_checkButton_prioritizeTargetNameplate:SetChecked(false);
+ end
+
+ if UnitXP_SP3_Addon["prioritizeMarkedNameplate"] then
+ UnitXP_SP3_setPrioritizeMarkedNameplate("enable");
+ xpsp3_checkButton_prioritizeMarkedNameplate:SetChecked(true);
+ else
+ UnitXP_SP3_setPrioritizeMarkedNameplate("disable");
+ xpsp3_checkButton_prioritizeMarkedNameplate:SetChecked(false);
+ end
+
+ if UnitXP_SP3_Addon["nameplateCombatFilter"] then
+ UnitXP_SP3_setNameplateCombatFilter("enable");
+ xpsp3_checkButton_nameplateCombatFilter:SetChecked(true);
+ else
+ UnitXP_SP3_setNameplateCombatFilter("disable");
+ xpsp3_checkButton_nameplateCombatFilter:SetChecked(false);
+ end
+
+ if UnitXP_SP3_Addon["hideCritterNameplate"] then
+ UnitXP_SP3_setHideCritterNameplate("enable");
+ xpsp3_checkButton_hideCritterNameplate:SetChecked(true);
+ else
+ UnitXP_SP3_setHideCritterNameplate("disable");
+ xpsp3_checkButton_hideCritterNameplate:SetChecked(false);
+ end
+
+ if UnitXP_SP3_Addon["showInCombatNameplatesNearPlayer"] then
+ UnitXP_SP3_setShowInCombatNameplatesNearPlayer("enable");
+ xpsp3_checkButton_showInCombatNameplatesNearPlayer:SetChecked(true);
+ else
+ UnitXP_SP3_setShowInCombatNameplatesNearPlayer("disable");
+ xpsp3_checkButton_showInCombatNameplatesNearPlayer:SetChecked(false);
+ end
+
+ if UnitXP_SP3_Addon["weatherAlwaysClear"] then
+ UnitXP_SP3_setWeatherAlwaysClear("enable");
+ xpsp3_checkButton_weatherAlwaysClear:SetChecked(true);
+ else
+ UnitXP_SP3_setWeatherAlwaysClear("disable");
+ xpsp3_checkButton_weatherAlwaysClear:SetChecked(false);
+ end
+
+ for ev, v in pairs(UnitXP_SP3_Addon["notify_flashTaskbarIcon"]) do
+ xpsp3Frame:UnregisterEvent(ev);
+ end
+ for ev, v in pairs(UnitXP_SP3_Addon["notify_playSystemDefaultSound"]) do
+ xpsp3Frame:UnregisterEvent(ev);
+ end
+
+ for ev, v in pairs(UnitXP_SP3_Addon["notify_flashTaskbarIcon"]) do
+ if v == true then
+ xpsp3Frame:RegisterEvent(ev);
+ xpsp3_checkButton_notify_flashTaskbarIcon:SetChecked(true);
+ else
+ xpsp3_checkButton_notify_flashTaskbarIcon:SetChecked(false);
+ end
+ end
+ for ev, v in pairs(UnitXP_SP3_Addon["notify_playSystemDefaultSound"]) do
+ if v == true then
+ xpsp3Frame:RegisterEvent(ev);
+ xpsp3_checkButton_notify_playSystemDefaultSound:SetChecked(true);
+ else
+ xpsp3_checkButton_notify_playSystemDefaultSound:SetChecked(false);
+ end
+ end
+
+end
+
+function UnitXP_SP3_UI_OnClick(widget)
+ if widget == nil or string.find(widget:GetName(), "xpsp3") == nil then
+ return
+ end
+
+ xpsp3_editBox_FPScap:ClearFocus();
+ xpsp3_editBox_combatTextSP3_fontSize:ClearFocus();
+
+ skipUpdateMessage = false;
+
+ if string.find(widget:GetName(), "_editBox_") then
+ PlaySound("igMainMenuContinue");
+ if string.find(widget:GetName(), "_FPScap") then
+ UnitXP_SP3_FPScap(widget:GetNumber());
+ end
+ if string.find(widget:GetName(), "_combatTextSP3_fontSize") then
+ UnitXP_SP3_combatTextSP3_fontSize(widget:GetNumber());
+ end
+ end
+
+ if string.find(widget:GetName(), "_button_") then
+ PlaySound("igMainMenuContinue");
+ if string.find(widget:GetName(), "_cameraHeight_raise") then
+ UnitXP_SP3_raiseCameraHeight();
+ end
+ if string.find(widget:GetName(), "_cameraHeight_lower") then
+ UnitXP_SP3_lowerCameraHeight();
+ end
+ if string.find(widget:GetName(), "_cameraPitch_up") then
+ UnitXP_SP3_cameraPitchUp();
+ end
+ if string.find(widget:GetName(), "_cameraPitch_down") then
+ UnitXP_SP3_cameraPitchDown();
+ end
+ if string.find(widget:GetName(), "_cameraHorizontalDisplacement_leftPlayer") then
+ UnitXP_SP3_leftPlayer();
+ end
+ if string.find(widget:GetName(), "_cameraHorizontalDisplacement_rightPlayer") then
+ UnitXP_SP3_rightPlayer();
+ end
+ end
+
+ if string.find(widget:GetName(), "_buttonCancel_") then
+ PlaySound("gsTitleOptionExit");
+ if string.find(widget:GetName(), "_close") then
+ xpsp3Frame:Hide();
+ end
+ if string.find(widget:GetName(), "_resetCamera") then
+ UnitXP_SP3_resetCamera();
+ end
+ end
+
+ if string.find(widget:GetName(), "_checkButton_") then
+ if widget:GetChecked() then
+ PlaySound("igMainMenuOptionCheckBoxOn");
+ else
+ PlaySound("igMainMenuOptionCheckBoxOff");
+ end
+
+ if string.find(widget:GetName(), "_minimapButton") then
+ UnitXP_SP3_Icon.hide = not widget:GetChecked();
+ libIcon:Refresh("UnitXP SP3 icon", UnitXP_SP3_Icon);
+ end
+
+ if string.find(widget:GetName(), "_modernNameplate") then
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["modernNameplateDistance"] = true;
+ else
+ UnitXP_SP3_Addon["modernNameplateDistance"] = false;
+ end
+ end
+
+ if string.find(widget:GetName(), "_combatTextSP3") then
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["combatTextSP3"] = true;
+ else
+ UnitXP_SP3_Addon["combatTextSP3"] = false;
+ end
+ end
+
+ if string.find(widget:GetName(), "_cameraPinHeight") then
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["cameraPinHeight"] = true;
+ else
+ UnitXP_SP3_Addon["cameraPinHeight"] = false;
+ end
+ end
+
+ if string.find(widget:GetName(), "_prioritizeTargetNameplate") then
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["prioritizeTargetNameplate"] = true;
+ else
+ UnitXP_SP3_Addon["prioritizeTargetNameplate"] = false;
+ end
+ end
+
+ if string.find(widget:GetName(), "_prioritizeMarkedNameplate") then
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["prioritizeMarkedNameplate"] = true;
+ else
+ UnitXP_SP3_Addon["prioritizeMarkedNameplate"] = false;
+ end
+ end
+
+ if string.find(widget:GetName(), "_nameplateCombatFilter") then
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["nameplateCombatFilter"] = true;
+ else
+ UnitXP_SP3_Addon["nameplateCombatFilter"] = false;
+ end
+ end
+
+ if string.find(widget:GetName(), "_weatherAlwaysClear") then
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["weatherAlwaysClear"] = true;
+ else
+ UnitXP_SP3_Addon["weatherAlwaysClear"] = false;
+ end
+ end
+
+ if string.find(widget:GetName(), "_showInCombatNameplatesNearPlayer") then
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["showInCombatNameplatesNearPlayer"] = true;
+ else
+ UnitXP_SP3_Addon["showInCombatNameplatesNearPlayer"] = false;
+ end
+ end
+
+ if string.find(widget:GetName(), "_hideCritterNameplate") then
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["hideCritterNameplate"] = true;
+ else
+ UnitXP_SP3_Addon["hideCritterNameplate"] = false;
+ end
+ end
+
+ if string.find(widget:GetName(), "_notify_flashTaskbarIcon") then
+ for ev, v in pairs(UnitXP_SP3_Addon["notify_flashTaskbarIcon"]) do
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"][ev] = true;
+ else
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"][ev] = false;
+ end
+ end
+ end
+
+ if string.find(widget:GetName(), "_notify_playSystemDefaultSound") then
+ for ev, v in pairs(UnitXP_SP3_Addon["notify_playSystemDefaultSound"]) do
+ if widget:GetChecked() then
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"][ev] = true;
+ else
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"][ev] = false;
+ end
+ end
+ end
+ end
+
+ UnitXP_SP3_reloadConfig();
+end
+
+function UnitXP_SP3_toggleModernNameplateDistance()
+ UnitXP_SP3_Addon["modernNameplateDistance"] = not UnitXP_SP3_Addon["modernNameplateDistance"];
+ UnitXP_SP3_reloadConfig();
+end
+
+function UnitXP_SP3_togglePrioritizeTargetNameplate()
+ UnitXP_SP3_Addon["prioritizeTargetNameplate"] = not UnitXP_SP3_Addon["prioritizeTargetNameplate"];
+ UnitXP_SP3_reloadConfig();
+end
+
+function UnitXP_SP3_togglePrioritizeMarkedNameplate()
+ UnitXP_SP3_Addon["prioritizeMarkedNameplate"] = not UnitXP_SP3_Addon["prioritizeMarkedNameplate"];
+ UnitXP_SP3_reloadConfig();
+end
+
+-- Recording party members from previous PARTY_MEMBERS_CHANGED events so that we can verify if the party just became full
+local lastRecordedPartyMembers = 0
+
+local function checkEvent(listenedEvents, actionFunction)
+ if listenedEvents[event] then
+ if event == "PARTY_MEMBERS_CHANGED" then
+ -- Party full
+ if GetNumRaidMembers() == 0 and GetNumPartyMembers() == 4 and lastRecordedPartyMembers ~= 4 then
+ actionFunction();
+ end
+ elseif event == "UPDATE_BATTLEFIELD_STATUS" then
+ for i = 1, MAX_BATTLEFIELD_QUEUES do
+ local s = GetBattlefieldStatus(i);
+ -- Battlefield is ready
+ if s == "confirm" then
+ actionFunction();
+ break
+ end
+ end
+ elseif event == "CHAT_MSG_ADDON" then
+ if arg1 == (LFT_ADDON_PREFIX or "TW_LFG") then
+ if string.find(arg2, "S2C_OFFER_NEW") or string.find(arg2, "S2C_ROLECHECK_START") or
+ string.find(arg2, "S2C_QUEUE_LEFT") then
+ -- LFT found group or role check start or somehow player left queue
+ actionFunction();
+ end
+ end
+ else
+ actionFunction();
+ end
+ end
+end
+
+function UnitXP_SP3_OnEvent(event)
+ if event == "ADDON_LOADED" and arg1 == "UnitXP_SP3_Addon" then
+ local dataVersion = 28;
+ if UnitXP_SP3_Addon == nil or UnitXP_SP3_Addon["dataVersion"] ~= dataVersion then
+ UnitXP_SP3_Addon = {};
+ UnitXP_SP3_Addon["dataVersion"] = dataVersion;
+ UnitXP_SP3_Addon["targetRangeConeFactor"] = 2.2;
+ UnitXP_SP3_Addon["cameraHeight"] = 0.0;
+ UnitXP_SP3_Addon["cameraPitch"] = 0.0;
+ UnitXP_SP3_Addon["cameraHorizontalDisplacement"] = 0.0;
+ UnitXP_SP3_Addon["cameraPinHeight"] = false;
+ UnitXP_SP3_Addon["modernNameplateDistance"] = true;
+ UnitXP_SP3_Addon["prioritizeTargetNameplate"] = false;
+ UnitXP_SP3_Addon["prioritizeMarkedNameplate"] = false;
+ UnitXP_SP3_Addon["nameplateCombatFilter"] = false;
+ UnitXP_SP3_Addon["showInCombatNameplatesNearPlayer"] = false;
+ UnitXP_SP3_Addon["FPScap"] = 0;
+ UnitXP_SP3_Addon["weatherAlwaysClear"] = false;
+ UnitXP_SP3_Addon["hideCritterNameplate"] = true;
+ UnitXP_SP3_Addon["combatTextSP3"] = false;
+ UnitXP_SP3_Addon["combatTextSP3_fontSize"] = 36;
+
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"] = {};
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["PLAYER_REGEN_DISABLED"] = true;
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["CHAT_MSG_WHISPER"] = true;
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["CHAT_MSG_RAID_WARNING"] = true;
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["TRADE_SHOW"] = true;
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["PARTY_INVITE_REQUEST"] = true;
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["READY_CHECK"] = true;
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["GUILD_INVITE_REQUEST"] = true;
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["UPDATE_BATTLEFIELD_STATUS"] = true;
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["PARTY_MEMBERS_CHANGED"] = true;
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["CHAT_MSG_ADDON"] = true; -- For LFT
+
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"] = {};
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["PLAYER_REGEN_DISABLED"] = false;
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["CHAT_MSG_WHISPER"] = false;
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["CHAT_MSG_RAID_WARNING"] = false;
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["TRADE_SHOW"] = false;
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["PARTY_INVITE_REQUEST"] = false;
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["READY_CHECK"] = false;
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["GUILD_INVITE_REQUEST"] = false;
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["UPDATE_BATTLEFIELD_STATUS"] = false;
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["PARTY_MEMBERS_CHANGED"] = false;
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["CHAT_MSG_ADDON"] = false; -- For LFT
+
+ UnitXP_SP3_Print("UnitXP Service Pack 3 configuration is reset due to AddOn update.")
+ end
+ if UnitXP_SP3_Icon == nil then
+ UnitXP_SP3_Icon = {
+ hide = false
+ };
+ end
+ xpsp3Frame:UnregisterEvent("ADDON_LOADED");
+ xpsp3Frame:RegisterEvent("PLAYER_LOGIN");
+ return;
+ elseif event == "PLAYER_LOGIN" then
+ if pcall(UnitXP, "nop", "nop") == true then
+ UnitXP_SP3_reloadConfig();
+ xpsp3Frame:RegisterEvent("PLAYER_ENTERING_WORLD");
+ xpsp3Frame:RegisterEvent("PLAYER_LEAVING_WORLD");
+ original_CombatText_AddMessage = CombatText_AddMessage;
+ CombatText_AddMessage = UnitXP_SP3_detoured_CombatText_AddMessage;
+
+ local message = "UnitXP Service Pack 3 is loaded.";
+ local hasCOFFtimestamp, coffTimestamp = pcall(UnitXP, "version", "coffTimeDateStamp");
+ if hasCOFFtimestamp then
+ message = message .. " It was built on " .. date("%d %b %Y", coffTimestamp) .. ".";
+ end
+ UnitXP_SP3_Print(message);
+ else
+ UnitXP_SP3_Print("UnitXP Service Pack 3 didn't load properly.");
+ return;
+ end
+
+ if UnitXP_SP3_Addon["notify_flashTaskbarIcon"] ~= nil and
+ UnitXP_SP3_Addon["notify_flashTaskbarIcon"]["PLAYER_REGEN_DISABLED"] == true and
+ UnitAffectingCombat("player") then
+ UnitXP_SP3_flashTaskbarIcon();
+ end
+
+ if UnitXP_SP3_Addon["notify_playSystemDefaultSound"] ~= nil and
+ UnitXP_SP3_Addon["notify_playSystemDefaultSound"]["PLAYER_REGEN_DISABLED"] == true and
+ UnitAffectingCombat("player") then
+ UnitXP_SP3_playSystemDefaultSound();
+ end
+
+ local iconData = libData:NewDataObject("UnitXP SP3 icon data", {
+ OnClick = function()
+ if pcall(UnitXP, "nop", "nop") == true then
+ if xpsp3Frame:IsShown() then
+ PlaySound("igMainMenuContinue");
+ xpsp3Frame:Hide();
+ else
+ PlaySound("igMainMenuOpen");
+ xpsp3Frame:Show();
+ end
+ else
+ UnitXP_SP3_Print("UnitXP Service Pack 3 didn't load properly.");
+ end
+ end,
+ OnTooltipShow = function(tooltip)
+ tooltip:SetText(UNITXPSP3TOOLTIP);
+ end,
+ icon = "Interface\\Icons\\INV_Misc_Gem_Pearl_05"
+ });
+
+ libIcon:Register("UnitXP SP3 icon", iconData, UnitXP_SP3_Icon);
+
+ return;
+ elseif event == "PLAYER_ENTERING_WORLD" then
+ local test, result = pcall(UnitXP, "FPScap", UnitXP_SP3_Addon["FPScap"]);
+
+ if not test then
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"FPScap\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ return;
+ elseif event == "PLAYER_LEAVING_WORLD" then
+ -- Some user report a faster loading time when FPS cap turns off
+ local test, result = pcall(UnitXP, "FPScap", 0);
+
+ if not test then
+ if not skipUpdateMessage then
+ skipUpdateMessage = true;
+ UnitXP_SP3_Print(
+ "UnitXP_SP3.dll failed to execute \"FPScap\". You might need to update UnitXP_SP3.dll to support this method.");
+ end
+ end
+ return;
+ end
+
+ if UnitXP_SP3_Addon then
+ if UnitXP_SP3_Addon["notify_flashTaskbarIcon"] then
+ checkEvent(UnitXP_SP3_Addon["notify_flashTaskbarIcon"], UnitXP_SP3_flashTaskbarIcon)
+ end
+
+ if UnitXP_SP3_Addon["notify_playSystemDefaultSound"] then
+ checkEvent(UnitXP_SP3_Addon["notify_playSystemDefaultSound"], UnitXP_SP3_playSystemDefaultSound)
+ end
+
+ if event == "PARTY_MEMBERS_CHANGED" then
+ lastRecordedPartyMembers = GetNumPartyMembers()
+ end
+ end
+end
diff --git a/UnitXP_SP3_Addon.toc b/UnitXP_SP3_Addon.toc
new file mode 100755
index 0000000..2982ff6
--- /dev/null
+++ b/UnitXP_SP3_Addon.toc
@@ -0,0 +1,14 @@
+## Interface: 11200
+## Title: UnitXP SP3 Lua Addon
+## Notes: Menu to control UnitXP SP3 mod
+## Author: allfox and thankful community
+## DefaultState: enabled
+## OptionalDeps: none
+## SavedVariables: UnitXP_SP3_Addon, UnitXP_SP3_Icon
+libs\LibStub.lua
+libs\AceCore-3.0.lua
+libs\AceHook-3.0.lua
+libs\CallbackHandler-1.0.lua
+libs\LibDataBroker-1.1.lua
+libs\LibDBIcon-1.0.lua
+UnitXP_SP3_Addon.xml
diff --git a/UnitXP_SP3_Addon.xml b/UnitXP_SP3_Addon.xml
new file mode 100755
index 0000000..4d50f70
--- /dev/null
+++ b/UnitXP_SP3_Addon.xml
@@ -0,0 +1,658 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Show Minimap Button");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("Toggle visibility of the minimap button");
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Proper Nameplates Occlusion");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("Nameplates would also check camera's line of sight in addition to distance");
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Prioritize Target Nameplate");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("Other nameplates would disappear when a target is selected");
+ xpsp3tooltip:AddLine("Need Proper Nameplates Occlusion to be also enabled",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Prioritize Marked Nameplate");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("Other nameplates would disappear when some nameplates are raid-marked");
+ xpsp3tooltip:AddLine("Need Proper Nameplates Occlusion to be also enabled",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Hide Healthy Nameplates");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("Aside from prioritized nameplates, only those in-combat/damaged/PvP-flagged nameplates would be shown");
+ xpsp3tooltip:AddLine("Need Proper Nameplates Occlusion to be also enabled",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Hide Critter Nameplates");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("Aside from prioritized nameplates, only those in-combat critters would have nameplates");
+ xpsp3tooltip:AddLine("Need Proper Nameplates Occlusion to be also enabled",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Show In-combat Nameplates Nearby");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("In-combat nameplates in small range would be shown regardless of occlusion");
+ xpsp3tooltip:AddLine("Need Proper Nameplates Occlusion to be also enabled",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Taskbar Notification");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("Flash operating system's taskbar icon when the game requires attention");
+ xpsp3tooltip:AddLine("When the game is in background/minimized",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("System Sound Notification");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("Play operating system's default sound when the game requires attention");
+ xpsp3tooltip:AddLine("When the game is in background/minimized",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Pin Camera Height");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("Camera would keep its height during shapeshifting");
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Keep Weather Clear");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("This would fix FPS-drop during bad weather at the cost of losing weather particles visual");
+ xpsp3tooltip:AddLine("Toggling the switch would influence the next weather event but not the current one",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Anti-aliased Combat Text");
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_RIGHT");
+ xpsp3tooltip:SetText("Alternative style for floating combat text");
+ xpsp3tooltip:AddLine("It requires d3dx9_43.dll which is from DirectX End-User Runtimes",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_LEFT");
+ xpsp3tooltip:SetText("Adjust combat text font size");
+ xpsp3tooltip:AddLine("Range from 10 to 99",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xpsp3tooltip:SetOwner(this, "ANCHOR_LEFT");
+ xpsp3tooltip:SetText("Limit frames-per-second to the specific value");
+ xpsp3tooltip:AddLine("Zero means no limit",1,1,1);
+ xpsp3tooltip:Show();
+
+
+ xpsp3tooltip:Hide();
+
+
+ UnitXP_SP3_UI_OnClick(this);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UnitXP_SP3_OnLoad();
+
+
+ UnitXP_SP3_OnEvent(event);
+
+
+
+
+
+
diff --git a/libs/AceCore-3.0.lua b/libs/AceCore-3.0.lua
new file mode 100644
index 0000000..69be3d1
--- /dev/null
+++ b/libs/AceCore-3.0.lua
@@ -0,0 +1,209 @@
+local ACECORE_MAJOR, ACECORE_MINOR = "AceCore-3.0", 2
+local AceCore, oldminor = LibStub:NewLibrary(ACECORE_MAJOR, ACECORE_MINOR)
+
+if not AceCore then return end -- No upgrade needed
+
+AceCore._G = AceCore._G or getfenv()
+local _G = AceCore._G
+local strsub, strgsub, strfind = string.sub, string.gsub, string.find
+local tremove, tconcat = table.remove, table.concat
+local tgetn, tsetn = table.getn, table.setn
+
+local new, del
+do
+local list = setmetatable({}, {__mode = "k"})
+function new()
+ local t = next(list)
+ if not t then
+ return {}
+ end
+ list[t] = nil
+ return t
+end
+
+function del(t)
+ setmetatable(t, nil)
+ for k in pairs(t) do
+ t[k] = nil
+ end
+ tsetn(t,0)
+ list[t] = true
+end
+
+print = print or function(text)
+ DEFAULT_CHAT_FRAME:AddMessage(text)
+end
+
+-- debug
+function AceCore.listcount()
+ local count = 0
+ for k in list do
+ count = count + 1
+ end
+ return count
+end
+end -- AceCore.new, AceCore.del
+AceCore.new, AceCore.del = new, del
+
+local function errorhandler(err)
+ return geterrorhandler()(err)
+end
+AceCore.errorhandler = errorhandler
+
+local function CreateSafeDispatcher(argCount)
+ local code = [[
+ local errorhandler = LibStub("AceCore-3.0").errorhandler
+ local method, UP_ARGS
+ local function call()
+ local func, ARGS = method, UP_ARGS
+ method, UP_ARGS = nil, NILS
+ return func(ARGS)
+ end
+ return function(func, ARGS)
+ method, UP_ARGS = func, ARGS
+ return xpcall(call, errorhandler)
+ end
+ ]]
+ local c = 4*argCount-1
+ local s = "b01,b02,b03,b04,b05,b06,b07,b08,b09,b10,b11,b12,b13,b14,b15,b16,b17,b18,b19,b20"
+ code = strgsub(code, "UP_ARGS", string.sub(s,1,c))
+ s = "a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20"
+ code = strgsub(code, "ARGS", string.sub(s,1,c))
+ s = "nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil"
+ code = strgsub(code, "NILS", string.sub(s,1,c))
+ return assert(loadstring(code, "safecall SafeDispatcher["..tostring(argCount).."]"))()
+end
+
+local SafeDispatchers = setmetatable({}, {__index=function(self, argCount)
+ local dispatcher
+ if not tonumber(argCount) then dbg(debugstack()) end
+ if argCount > 0 then
+ dispatcher = CreateSafeDispatcher(argCount)
+ else
+ dispatcher = function(func) return xpcall(func,errorhandler) end
+ end
+ rawset(self, argCount, dispatcher)
+ return dispatcher
+end})
+
+local function safecall(func,argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
+ -- we check to see if the func is passed is actually a function here and don't error when it isn't
+ -- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not
+ -- present execution should continue without hinderance
+ if type(func) == "function" then
+ return SafeDispatchers[argc](func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
+ end
+end
+AceCore.safecall = safecall
+
+local function CreateDispatcher(argCount)
+ local code = [[
+ return function(func,ARGS)
+ return func(ARGS)
+ end
+ ]]
+ local s = "a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20"
+ code = strgsub(code, "ARGS", string.sub(s,1,4*argCount-1))
+ return assert(loadstring(code, "call Dispatcher["..tostring(argCount).."]"))()
+end
+
+AceCore.Dispatchers = setmetatable({}, {__index=function(self, argCount)
+ local dispatcher
+ if argCount > 0 then
+ dispatcher = CreateDispatcher(argCount)
+ else
+ dispatcher = function(func) return func() end
+ end
+ rawset(self, argCount, dispatcher)
+ return dispatcher
+end})
+
+-- some string functions
+-- vanilla available string operations:
+-- sub, gfind, rep, gsub, char, dump, find, upper, len, format, byte, lower
+-- we will just replace every string.match with string.find in the code
+function AceCore.strtrim(s)
+ return strgsub(s, "^%s*(.-)%s*$", "%1")
+end
+
+local function strsplit(delim, s, n)
+ if n and n < 2 then return s end
+ beg = beg or 1
+ local i,j = string.find(s,delim,beg)
+ if not i then
+ return s, nil
+ end
+ return string.sub(s,1,j-1), strsplit(delim, string.sub(s,j+1), n and n-1 or nil)
+end
+AceCore.strsplit = strsplit
+
+-- Ace3v: fonctions copied from AceHook-2.1
+local protFuncs = {
+ CameraOrSelectOrMoveStart = true, CameraOrSelectOrMoveStop = true,
+ TurnOrActionStart = true, TurnOrActionStop = true,
+ PitchUpStart = true, PitchUpStop = true,
+ PitchDownStart = true, PitchDownStop = true,
+ MoveBackwardStart = true, MoveBackwardStop = true,
+ MoveForwardStart = true, MoveForwardStop = true,
+ Jump = true, StrafeLeftStart = true,
+ StrafeLeftStop = true, StrafeRightStart = true,
+ StrafeRightStop = true, ToggleMouseMove = true,
+ ToggleRun = true, TurnLeftStart = true,
+ TurnLeftStop = true, TurnRightStart = true,
+ TurnRightStop = true,
+}
+
+local function issecurevariable(x)
+ return protFuncs[x] and 1 or nil
+end
+AceCore.issecurevariable = issecurevariable
+
+local function hooksecurefunc(arg1, arg2, arg3)
+ if type(arg1) == "string" then
+ arg1, arg2, arg3 = _G, arg1, arg2
+ end
+ local orig = arg1[arg2]
+ if type(orig) ~= "function" then
+ error("The function "..arg2.." does not exist", 2)
+ end
+ arg1[arg2] = function(...)
+ local tmp = {orig(unpack(arg))}
+ arg3(unpack(arg))
+ return unpack(tmp)
+ end
+end
+AceCore.hooksecurefunc = hooksecurefunc
+
+-- pickfirstset() - picks the first non-nil value and returns it
+local function pickfirstset(argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
+ if (argc <= 1) or (a1 ~= nil) then
+ return a1
+ else
+ return pickfirstset(argc-1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
+ end
+end
+AceCore.pickfirstset = pickfirstset
+
+local function countargs(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
+ if (a1 == nil) then return 0 end
+ return 1 + countargs(a2,a3,a4,a5,a6,a7,a8,a9,a10)
+end
+AceCore.countargs = countargs
+
+-- wipe preserves metatable
+function AceCore.wipe(t)
+ for k,v in pairs(t) do t[k] = nil end
+ tsetn(t,0)
+ return t
+end
+
+function AceCore.truncate(t,e)
+ e = e or tgetn(t)
+ for i=1,e do
+ if t[i] == nil then
+ tsetn(t,i-1)
+ return
+ end
+ end
+ tsetn(t,e)
+end
diff --git a/libs/AceHook-3.0.lua b/libs/AceHook-3.0.lua
new file mode 100644
index 0000000..7714848
--- /dev/null
+++ b/libs/AceHook-3.0.lua
@@ -0,0 +1,540 @@
+--- **AceHook-3.0** offers safe Hooking/Unhooking of functions, methods and frame scripts.
+-- Using AceHook-3.0 is recommended when you need to unhook your hooks again, so the hook chain isn't broken
+-- when you manually restore the original function.
+--
+-- **AceHook-3.0** can be embeded into your addon, either explicitly by calling AceHook:Embed(MyAddon) or by
+-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
+-- and can be accessed directly, without having to explicitly call AceHook itself.\\
+-- It is recommended to embed AceHook, otherwise you'll have to specify a custom `self` on all calls you
+-- make into AceHook.
+-- @class file
+-- @name AceHook-3.0
+-- @release $Id: AceHook-3.0.lua 1118 2014-10-12 08:21:54Z nevcairiel $
+local ACEHOOK_MAJOR, ACEHOOK_MINOR = "AceHook-3.0", 8
+local AceHook, oldminor = LibStub:NewLibrary(ACEHOOK_MAJOR, ACEHOOK_MINOR)
+
+if not AceHook then return end -- No upgrade needed
+
+local AceCore = LibStub("AceCore-3.0")
+local new, del = AceCore.new, AceCore.del
+
+AceHook.embeded = AceHook.embeded or {}
+AceHook.registry = AceHook.registry or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end })
+AceHook.handlers = AceHook.handlers or {}
+AceHook.actives = AceHook.actives or {}
+AceHook.scripts = AceHook.scripts or {}
+AceHook.onceSecure = AceHook.onceSecure or {}
+AceHook.hooks = AceHook.hooks or {}
+
+-- local upvalues
+local registry = AceHook.registry
+local handlers = AceHook.handlers
+local actives = AceHook.actives
+local scripts = AceHook.scripts
+local onceSecure = AceHook.onceSecure
+
+-- Lua APIs
+local pairs, next, type = pairs, next, type
+local format = string.format
+local assert, error = assert, error
+
+-- WoW APIs
+local hooksecurefunc, issecurevariable = AceCore.hooksecurefunc, AceCore.issecurevariable
+
+local _G = AceCore._G
+
+local protectedScripts = {
+ OnClick = true,
+}
+
+-- upgrading of embeded is done at the bottom of the file
+
+local mixins = {
+ "Hook", "SecureHook",
+ "HookScript", "SecureHookScript",
+ "Unhook", "UnhookAll",
+ "IsHooked",
+ "RawHook", "RawHookScript"
+}
+
+-- AceHook:Embed( target )
+-- target (object) - target object to embed AceHook in
+--
+-- Embeds AceEevent into the target object making the functions from the mixins list available on target:..
+function AceHook:Embed( target )
+ for k, v in pairs( mixins ) do
+ target[v] = self[v]
+ end
+ self.embeded[target] = true
+ -- inject the hooks table safely
+ target.hooks = target.hooks or {}
+ return target
+end
+
+-- AceHook:OnEmbedDisable( target )
+-- target (object) - target object that is being disabled
+--
+-- Unhooks all hooks when the target disables.
+-- this method should be called by the target manually or by an addon framework
+function AceHook:OnEmbedDisable( target )
+ target:UnhookAll()
+end
+
+-- failsafe means even the hooking method fails, the original method will
+-- always be called
+local function createHook(self, handler, orig, secure, failsafe)
+ local uid
+ local method = type(handler) == "string"
+ -- Ace3v: when this function called in the "hook" function, we have
+ -- failsafe = not (raw or secure), so the first check condition
+ -- is same as "if failsafe" or "if not raw and not secure"
+ if failsafe and not secure then
+ -- failsafe hook creation
+ uid = function(...)
+ if actives[uid] then
+ if method then
+ self[handler](self,unpack(arg))
+ else
+ handler(unpack(arg))
+ end
+ end
+ return orig(unpack(arg))
+ end
+ -- /failsafe hook
+ else
+ -- all other hooks
+ uid = function(...)
+ if actives[uid] then
+ if method then
+ return self[handler](self,unpack(arg))
+ else
+ return handler(unpack(arg))
+ end
+ elseif not secure then -- backup on non secure
+ return orig(unpack(arg))
+ end
+ end
+ -- /hook
+ end
+ return uid
+end
+
+local function donothing() end
+
+-- @param self
+-- @param obj nil or a frame object, use with script = true
+-- @param method string, the name of a global function or the name of an object method
+-- @param handler function, or string when it's a method of 'self', if nil then will use the same value as method
+-- @param script boolean, must be true, if the hooked object is a frame
+-- @param secure boolean, if hooking a secure script, if true the original script will be called first, else later
+-- @param raw boolean, if raw hooking (replacing the original method)
+-- @param forceSecure boolean, if forcing to hook a secure method
+-- @param usage
+local function hook(self, obj, method, handler, script, secure, raw, forceSecure, usage)
+ if not handler then handler = method end
+
+ -- These asserts make sure AceHooks's devs play by the rules.
+ assert(not script or type(script) == "boolean")
+ assert(not secure or type(secure) == "boolean")
+ assert(not raw or type(raw) == "boolean")
+ assert(not forceSecure or type(forceSecure) == "boolean")
+ assert(usage)
+
+ -- Error checking Battery!
+ if obj and type(obj) ~= "table" then
+ error(format("%s: 'object' - nil or table expected got %s", usage, type(obj)), 3)
+ end
+ if type(method) ~= "string" then
+ error(format("%s: 'method' - string expected got %s", usage, type(method)), 3)
+ end
+ if type(handler) ~= "string" and type(handler) ~= "function" then
+ error(format("%s: 'handler' - nil, string, or function expected got %s", usage, type(handler)), 3)
+ end
+ if type(handler) == "string" and type(self[handler]) ~= "function" then
+ error(format("%s: 'handler' - Handler specified does not exist at self[handler]", usage), 3)
+ end
+ if script then
+ if not obj or not obj.GetScript or not obj:HasScript(method) then
+ error(format("%s: You can only hook a script on a frame object", usage), 3)
+ end
+ if not secure and obj.IsProtected and obj:IsProtected() and protectedScripts[method] then
+ error(format("Cannot hook secure script %q; Use SecureHookScript(obj, method, [handler]) instead.", method), 3)
+ end
+ else
+ local issecure
+ if obj then
+ issecure = onceSecure[obj] and onceSecure[obj][method] or issecurevariable(obj, method)
+ else
+ issecure = onceSecure[method] or issecurevariable(method)
+ end
+ if issecure then
+ if forceSecure then
+ if obj then
+ onceSecure[obj] = onceSecure[obj] or {}
+ onceSecure[obj][method] = true
+ else
+ onceSecure[method] = true
+ end
+ elseif not secure then
+ error(format("%s: Attempt to hook secure function %s. Use `SecureHook' or add `true' to the argument list to override.", usage, method), 3)
+ end
+ end
+ end
+
+ local uid
+ if obj then
+ uid = registry[self][obj] and registry[self][obj][method]
+ else
+ uid = registry[self][method]
+ end
+
+ if uid then
+ if actives[uid] then
+ -- Only two sane choices exist here. We either a) error 100% of the time or b) always unhook and then hook
+ -- choice b would likely lead to odd debuging conditions or other mysteries so we're going with a.
+ error(format("Attempting to rehook already active hook %s.", method))
+ end
+
+ if handlers[uid] == handler then -- turn on a decative hook, note enclosures break this ability, small memory leak
+ actives[uid] = true
+ return
+ elseif obj then -- is there any reason not to call unhook instead of doing the following several lines?
+ if self.hooks and self.hooks[obj] then
+ self.hooks[obj][method] = nil
+ end
+ registry[self][obj][method] = nil
+ else
+ if self.hooks then
+ self.hooks[method] = nil
+ end
+ registry[self][method] = nil
+ end
+ handlers[uid], actives[uid], scripts[uid] = nil, nil, nil
+ uid = nil
+ end
+
+ local orig
+ if script then
+ -- Sometimes there is not a original function for a script.
+ orig = obj:GetScript(method) or donothing
+ elseif obj then
+ orig = obj[method]
+ else
+ orig = _G[method]
+ end
+
+ if not orig then
+ error(format("%s: Attempting to hook a non existing target", usage), 3)
+ end
+
+ uid = createHook(self, handler, orig, secure, not (raw or secure))
+
+ if obj then
+ registry[self][obj] = registry[self][obj] or new()
+ registry[self][obj][method] = uid
+
+ if not secure then
+ self.hooks[obj] = self.hooks[obj] or new()
+ self.hooks[obj][method] = orig
+ end
+
+ if script then
+ if not secure then
+ obj:SetScript(method, uid)
+ else
+ obj:SetScript(method, function(...)
+ local tmp = {orig(unpack(arg))}
+ uid(unpack(arg))
+ return unpack(tmp)
+ end)
+ --obj:HookScript(method, uid) -- Ace3v: vanilla frame has no HookScript
+ end
+ else
+ if not secure then
+ obj[method] = uid
+ else
+ hooksecurefunc(obj, method, uid)
+ end
+ end
+ else
+ registry[self][method] = uid
+
+ if not secure then
+ _G[method] = uid
+ self.hooks[method] = orig
+ else
+ hooksecurefunc(method, uid)
+ end
+ end
+
+ actives[uid], handlers[uid], scripts[uid] = true, handler, script and true or nil
+end
+
+--- Hook a function or a method on an object.
+-- The hook created will be a "safe hook", that means that your handler will be called
+-- before the hooked function ("Pre-Hook"), and you don't have to call the original function yourself,
+-- however you cannot stop the execution of the function, or modify any of the arguments/return values.\\
+-- This type of hook is typically used if you need to know if some function got called, and don't want to modify it.
+-- @paramsig [object], method, [handler], [hookSecure]
+-- @param object The object to hook a method from
+-- @param method If object was specified, the name of the method, or the name of the function to hook.
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
+-- @param hookSecure If true, AceHook will allow hooking of secure functions.
+-- @usage
+-- -- create an addon with AceHook embeded
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
+--
+-- function MyAddon:OnEnable()
+-- -- Hook ActionButton_UpdateHotkeys, overwriting the secure status
+-- self:Hook("ActionButton_UpdateHotkeys", true)
+-- end
+--
+-- function MyAddon:ActionButton_UpdateHotkeys(button, type)
+-- print(button:GetName() .. " is updating its HotKey")
+-- end
+function AceHook:Hook(object, method, handler, hookSecure)
+ if type(object) == "string" then
+ method, handler, hookSecure, object = object, method, handler, nil
+ end
+
+ if handler == true then
+ handler, hookSecure = nil, true
+ end
+
+ hook(self, object, method, handler, false, false, false, hookSecure or false, "Usage: Hook([object], method, [handler], [hookSecure])")
+end
+
+--- RawHook a function or a method on an object.
+-- The hook created will be a "raw hook", that means that your handler will completly replace
+-- the original function, and your handler has to call the original function (or not, depending on your intentions).\\
+-- The original function will be stored in `self.hooks[object][method]` or `self.hooks[functionName]` respectively.\\
+-- This type of hook can be used for all purposes, and is usually the most common case when you need to modify arguments
+-- or want to control execution of the original function.
+-- @paramsig [object], method, [handler], [hookSecure]
+-- @param object The object to hook a method from
+-- @param method If object was specified, the name of the method, or the name of the function to hook.
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
+-- @param hookSecure If true, AceHook will allow hooking of secure functions.
+-- @usage
+-- -- create an addon with AceHook embeded
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
+--
+-- function MyAddon:OnEnable()
+-- -- Hook ActionButton_UpdateHotkeys, overwriting the secure status
+-- self:RawHook("ActionButton_UpdateHotkeys", true)
+-- end
+--
+-- function MyAddon:ActionButton_UpdateHotkeys(button, type)
+-- if button:GetName() == "MyButton" then
+-- -- do stuff here
+-- else
+-- self.hooks.ActionButton_UpdateHotkeys(button, type)
+-- end
+-- end
+function AceHook:RawHook(object, method, handler, hookSecure)
+ if type(object) == "string" then
+ method, handler, hookSecure, object = object, method, handler, nil
+ end
+
+ if handler == true then
+ handler, hookSecure = nil, true
+ end
+
+ hook(self, object, method, handler, false, false, true, hookSecure or false, "Usage: RawHook([object], method, [handler], [hookSecure])")
+end
+
+--- SecureHook a function or a method on an object.
+-- This function is a wrapper around the `hooksecurefunc` function in the WoW API. Using AceHook
+-- extends the functionality of secure hooks, and adds the ability to unhook once the hook isn't
+-- required anymore, or the addon is being disabled.\\
+-- Secure Hooks should be used if the secure-status of the function is vital to its function,
+-- and taint would block execution. Secure Hooks are always called after the original function was called
+-- ("Post Hook"), and you cannot modify the arguments, return values or control the execution.
+-- @paramsig [object], method, [handler]
+-- @param object The object to hook a method from
+-- @param method If object was specified, the name of the method, or the name of the function to hook.
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
+function AceHook:SecureHook(object, method, handler)
+ if type(object) == "string" then
+ method, handler, object = object, method, nil
+ end
+
+ hook(self, object, method, handler, false, true, false, false, "Usage: SecureHook([object], method, [handler])")
+end
+
+--- Hook a script handler on a frame.
+-- The hook created will be a "safe hook", that means that your handler will be called
+-- before the hooked script ("Pre-Hook"), and you don't have to call the original function yourself,
+-- however you cannot stop the execution of the function, or modify any of the arguments/return values.\\
+-- This is the frame script equivalent of the :Hook safe-hook. It would typically be used to be notified
+-- when a certain event happens to a frame.
+-- @paramsig frame, script, [handler]
+-- @param frame The Frame to hook the script on
+-- @param script The script to hook
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
+-- @usage
+-- -- create an addon with AceHook embeded
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
+--
+-- function MyAddon:OnEnable()
+-- -- Hook the OnShow of FriendsFrame
+-- self:HookScript(FriendsFrame, "OnShow", "FriendsFrameOnShow")
+-- end
+--
+-- function MyAddon:FriendsFrameOnShow(frame)
+-- print("The FriendsFrame was shown!")
+-- end
+function AceHook:HookScript(frame, script, handler)
+ hook(self, frame, script, handler, true, false, false, false, "Usage: HookScript(object, method, [handler])")
+end
+
+--- RawHook a script handler on a frame.
+-- The hook created will be a "raw hook", that means that your handler will completly replace
+-- the original script, and your handler has to call the original script (or not, depending on your intentions).\\
+-- The original script will be stored in `self.hooks[frame][script]`.\\
+-- This type of hook can be used for all purposes, and is usually the most common case when you need to modify arguments
+-- or want to control execution of the original script.
+-- @paramsig frame, script, [handler]
+-- @param frame The Frame to hook the script on
+-- @param script The script to hook
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
+-- @usage
+-- -- create an addon with AceHook embeded
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
+--
+-- function MyAddon:OnEnable()
+-- -- Hook the OnShow of FriendsFrame
+-- self:RawHookScript(FriendsFrame, "OnShow", "FriendsFrameOnShow")
+-- end
+--
+-- function MyAddon:FriendsFrameOnShow(frame)
+-- -- Call the original function
+-- self.hooks[frame].OnShow(frame)
+-- -- Do our processing
+-- -- .. stuff
+-- end
+function AceHook:RawHookScript(frame, script, handler)
+ hook(self, frame, script, handler, true, false, true, false, "Usage: RawHookScript(object, method, [handler])")
+end
+
+--- SecureHook a script handler on a frame.
+-- This function is a wrapper around the `frame:HookScript` function in the WoW API. Using AceHook
+-- extends the functionality of secure hooks, and adds the ability to unhook once the hook isn't
+-- required anymore, or the addon is being disabled.\\
+-- Secure Hooks should be used if the secure-status of the function is vital to its function,
+-- and taint would block execution. Secure Hooks are always called after the original function was called
+-- ("Post Hook"), and you cannot modify the arguments, return values or control the execution.
+-- @paramsig frame, script, [handler]
+-- @param frame The Frame to hook the script on
+-- @param script The script to hook
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
+function AceHook:SecureHookScript(frame, script, handler)
+ hook(self, frame, script, handler, true, true, false, false, "Usage: SecureHookScript(object, method, [handler])")
+end
+
+--- Unhook from the specified function, method or script.
+-- @paramsig [obj], method
+-- @param obj The object or frame to unhook from
+-- @param method The name of the method, function or script to unhook from.
+function AceHook:Unhook(obj, method)
+ local usage = "Usage: Unhook([obj], method)"
+ if type(obj) == "string" then
+ method, obj = obj, nil
+ end
+
+ if obj and type(obj) ~= "table" then
+ error(format("%s: 'obj' - expecting nil or table got %s", usage, type(obj)), 2)
+ end
+ if type(method) ~= "string" then
+ error(format("%s: 'method' - expeting string got %s", usage, type(method)), 2)
+ end
+
+ local uid
+ if obj then
+ uid = registry[self][obj] and registry[self][obj][method]
+ else
+ uid = registry[self][method]
+ end
+
+ if not uid or not actives[uid] then
+ -- Declining to error on an unneeded unhook since the end effect is the same and this would just be annoying.
+ return false
+ end
+
+ actives[uid], handlers[uid] = nil, nil
+
+ if obj then
+ local tmp = registry[self][obj]
+ tmp[method] = nil
+ if not next(tmp) then
+ del(tmp)
+ registry[self][obj] = nil
+ end
+
+ -- if the hook reference doesnt exist, then its a secure hook, just bail out and dont do any unhooking
+ if not self.hooks[obj] or not self.hooks[obj][method] then return true end
+
+ if scripts[uid] and obj:GetScript(method) == uid then -- unhooks scripts
+ obj:SetScript(method, self.hooks[obj][method] ~= donothing and self.hooks[obj][method] or nil)
+ scripts[uid] = nil
+ elseif obj and self.hooks[obj] and self.hooks[obj][method] and obj[method] == uid then -- unhooks methods
+ obj[method] = self.hooks[obj][method]
+ end
+
+ tmp = self.hooks[obj]
+ tmp[method] = nil
+ if not next(tmp) then
+ del(tmp)
+ self.hooks[obj] = nil
+ end
+ else
+ registry[self][method] = nil
+
+ -- if self.hooks[method] doesn't exist, then this is a SecureHook, just bail out
+ if not self.hooks[method] then return true end
+
+ if self.hooks[method] and _G[method] == uid then -- unhooks functions
+ _G[method] = self.hooks[method]
+ end
+
+ self.hooks[method] = nil
+ end
+ return true
+end
+
+--- Unhook all existing hooks for this addon.
+function AceHook:UnhookAll()
+ for key, value in pairs(registry[self]) do
+ if type(key) == "table" then
+ for method in pairs(value) do
+ self:Unhook(key, method)
+ end
+ else
+ self:Unhook(key)
+ end
+ end
+end
+
+--- Check if the specific function, method or script is already hooked.
+-- @paramsig [obj], method
+-- @param obj The object or frame to unhook from
+-- @param method The name of the method, function or script to unhook from.
+function AceHook:IsHooked(obj, method)
+ -- we don't check if registry[self] exists, this is done by evil magicks in the metatable
+ if type(obj) == "string" then
+ if registry[self][obj] and actives[registry[self][obj]] then
+ return true, handlers[registry[self][obj]]
+ end
+ else
+ if registry[self][obj] and registry[self][obj][method] and actives[registry[self][obj][method]] then
+ return true, handlers[registry[self][obj][method]]
+ end
+ end
+
+ return false, nil
+end
+
+--- Upgrade our old embeded
+for target, v in pairs( AceHook.embeded ) do
+ AceHook:Embed( target )
+end
diff --git a/libs/CallbackHandler-1.0.lua b/libs/CallbackHandler-1.0.lua
new file mode 100644
index 0000000..e4677cc
--- /dev/null
+++ b/libs/CallbackHandler-1.0.lua
@@ -0,0 +1,280 @@
+--[[ $Id: CallbackHandler-1.0.lua 1131 2015-06-04 07:29:24Z nevcairiel $ ]]
+local MAJOR, MINOR = "CallbackHandler-1.0", 6
+local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not CallbackHandler then return end -- No upgrade needed
+
+-- Lua APIs
+local tconcat, tinsert, tgetn, tsetn = table.concat, table.insert, table.getn, table.setn
+local assert, error, loadstring = assert, error, loadstring
+local setmetatable, rawset, rawget = setmetatable, rawset, rawget
+local next, pairs, type, tostring = next, pairs, type, tostring
+local strgsub = string.gsub
+
+local new, del
+do
+local list = setmetatable({}, {__mode = "k"})
+function new()
+ local t = next(list)
+ if not t then
+ return {}
+ end
+ list[t] = nil
+ return t
+end
+
+function del(t)
+ setmetatable(t, nil)
+ for k in pairs(t) do
+ t[k] = nil
+ end
+ tsetn(t,0)
+ list[t] = true
+end
+end
+
+local meta = {__index = function(tbl, key) rawset(tbl, key, new()) return tbl[key] end}
+
+-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
+-- List them here for Mikk's FindGlobals script
+-- GLOBALS: geterrorhandler
+
+local function errorhandler(err)
+ return geterrorhandler()(err)
+end
+CallbackHandler.errorhandler = errorhandler
+
+local function CreateDispatcher(argCount)
+ local code = [[
+ local xpcall, errorhandler = xpcall, LibStub("CallbackHandler-1.0").errorhandler
+ local method, UP_ARGS
+ local function call()
+ local func, ARGS = method, UP_ARGS
+ method, UP_ARGS = nil, NILS
+ return func(ARGS)
+ end
+ return function(handlers, ARGS)
+ local index
+ index, method = next(handlers)
+ if not method then return end
+ repeat
+ UP_ARGS = ARGS
+ xpcall(call, errorhandler)
+ index, method = next(handlers, index)
+ until not method
+ end
+ ]]
+ local c = 4*argCount-1
+ local s = "b01,b02,b03,b04,b05,b06,b07,b08,b09,b10"
+ code = strgsub(code, "UP_ARGS", string.sub(s,1,c))
+ s = "a01,a02,a03,a04,a05,a06,a07,a08,a09,a10"
+ code = strgsub(code, "ARGS", string.sub(s,1,c))
+ s = "nil,nil,nil,nil,nil,nil,nil,nil,nil,nil"
+ code = strgsub(code, "NILS", string.sub(s,1,c))
+ return assert(loadstring(code, "safecall Dispatcher["..tostring(argCount).."]"))()
+end
+
+local Dispatchers = setmetatable({}, {__index=function(self, argCount)
+ local dispatcher = CreateDispatcher(argCount)
+ rawset(self, argCount, dispatcher)
+ return dispatcher
+end})
+
+--------------------------------------------------------------------------
+-- CallbackHandler:New
+--
+-- target - target object to embed public APIs in
+-- RegisterName - name of the callback registration API, default "RegisterCallback"
+-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
+-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
+function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName)
+
+ RegisterName = RegisterName or "RegisterCallback"
+ UnregisterName = UnregisterName or "UnregisterCallback"
+ if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
+ UnregisterAllName = "UnregisterAllCallbacks"
+ end
+
+ -- we declare all objects and exported APIs inside this closure to quickly gain access
+ -- to e.g. function names, the "target" parameter, etc
+
+
+ -- Create the registry object
+ local events = setmetatable({}, meta)
+ local registry = { recurse=0, events=events }
+
+ -- registry:Fire() - fires the given event/message into the registry
+ function registry:Fire(eventname, argc, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)
+ if not rawget(events, eventname) or not next(events[eventname]) then return end
+ local oldrecurse = registry.recurse
+ registry.recurse = oldrecurse + 1
+
+ argc = argc or 0
+ Dispatchers[argc+1](events[eventname], eventname, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)
+
+ registry.recurse = oldrecurse
+
+ if registry.insertQueue and oldrecurse==0 then
+ -- Something in one of our callbacks wanted to register more callbacks; they got queued
+ for eventname,callbacks in pairs(registry.insertQueue) do
+ local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
+ for self,func in pairs(callbacks) do
+ events[eventname][self] = func
+ -- fire OnUsed callback?
+ if first and registry.OnUsed then
+ registry.OnUsed(registry, target, eventname)
+ first = nil
+ end
+ end
+ del(callbacks)
+ end
+ del(registry.insertQueue)
+ registry.insertQueue = nil
+ end
+ end
+
+ -- Registration of a callback, handles:
+ -- self["method"], leads to self["method"](self, ...)
+ -- self with function ref, leads to functionref(...)
+ -- "addonId" (instead of self) with function ref, leads to functionref(...)
+ -- all with an optional arg, which, if present, gets passed as first argument (after self if present)
+ target[RegisterName] = function(self, eventname, method, ...)
+ if type(eventname) ~= "string" then
+ error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
+ end
+
+ method = method or eventname
+
+ local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
+
+ if type(method) ~= "string" and type(method) ~= "function" then
+ error("Usage: "..RegisterName.."(eventname, method[, arg]): 'method' - string or function expected.", 2)
+ end
+
+ local regfunc
+ local a1 = arg[1]
+
+ if type(method) == "string" then
+ -- self["method"] calling style
+ if type(self) ~= "table" then
+ error("Usage: "..RegisterName.."(eventname, method[, arg]): self was not a table?", 2)
+ elseif self==target then
+ error("Usage: "..RegisterName.."(eventname, method[, arg]): do not use Library:"..RegisterName.."(), use your own 'self'.", 2)
+ elseif type(self[method]) ~= "function" then
+ error("Usage: "..RegisterName.."(eventname, method[, arg]): 'method' - method '"..tostring(method).."' not found on 'self'.", 2)
+ end
+
+ if tgetn(arg) >= 1 then
+ regfunc = function (...) return self[method](self,a1,unpack(arg)) end
+ else
+ regfunc = function (...) return self[method](self,unpack(arg)) end
+ end
+ else
+ -- function ref with self=object or self="addonId"
+ if type(self)~="table" and type(self)~="string" then
+ error("Usage: "..RegisterName.."(self or addonId, eventname, method[, arg]): 'self or addonId': table or string expected.", 2)
+ end
+
+ if tgetn(arg) >= 1 then
+ regfunc = function (...) return method(a1, unpack(arg)) end
+ else
+ regfunc = method
+ end
+ end
+
+
+ if events[eventname][self] or registry.recurse<1 then
+ -- if registry.recurse<1 then
+ -- we're overwriting an existing entry, or not currently recursing. just set it.
+ events[eventname][self] = regfunc
+ -- fire OnUsed callback?
+ if registry.OnUsed and first then
+ registry.OnUsed(registry, target, eventname)
+ end
+ else
+ -- we're currently processing a callback in this registry, so delay the registration of this new entry!
+ -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
+ registry.insertQueue = registry.insertQueue or setmetatable(new(),meta)
+ registry.insertQueue[eventname][self] = regfunc
+ end
+ end
+
+ -- Unregister a callback
+ target[UnregisterName] = function(self, eventname)
+ if not self or self==target then
+ error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
+ end
+ if type(eventname) ~= "string" then
+ error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
+ end
+ if rawget(events, eventname) and events[eventname][self] then
+ events[eventname][self] = nil
+
+ -- Fire OnUnused callback?
+ if registry.OnUnused and not next(events[eventname]) then
+ registry.OnUnused(registry, target, eventname)
+ end
+
+ if rawget(events, eventname) and not next(events[eventname]) then
+ del(events[eventname])
+ events[eventname] = nil
+ end
+ end
+ if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
+ registry.insertQueue[eventname][self] = nil
+ end
+ end
+
+ -- OPTIONAL: Unregister all callbacks for given selfs/addonIds
+ if UnregisterAllName then
+ target[UnregisterAllName] = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
+ if not a1 then
+ error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or 'addonId' to unregister events for.", 2)
+ end
+ if a1 == target then
+ error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or 'addonId'", 2)
+ end
+
+ -- use our registry table as argument table
+ registry[1] = a1
+ registry[2] = a2
+ registry[3] = a3
+ registry[4] = a4
+ registry[5] = a5
+ registry[6] = a6
+ registry[7] = a7
+ registry[8] = a8
+ registry[9] = a9
+ registry[10] = a10
+ for i=1,10 do
+ local self = registry[i]
+ registry[i] = nil
+ if self then
+ if registry.insertQueue then
+ for eventname, callbacks in pairs(registry.insertQueue) do
+ if callbacks[self] then
+ callbacks[self] = nil
+ end
+ end
+ end
+ for eventname, callbacks in pairs(events) do
+ if callbacks[self] then
+ callbacks[self] = nil
+ -- Fire OnUnused callback?
+ if registry.OnUnused and not next(callbacks) then
+ registry.OnUnused(registry, target, eventname)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ return registry
+end
+
+-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
+-- try to upgrade old implicit embeds since the system is selfcontained and
+-- relies on closures to work.
+
diff --git a/libs/LibDBIcon-1.0.lua b/libs/LibDBIcon-1.0.lua
new file mode 100644
index 0000000..b877bce
--- /dev/null
+++ b/libs/LibDBIcon-1.0.lua
@@ -0,0 +1,487 @@
+
+-----------------------------------------------------------------------
+-- LibDBIcon-1.0
+--
+-- Allows addons to easily create a lightweight minimap icon as an alternative to heavier LDB displays.
+--
+
+local DBICON10 = "LibDBIcon-1.0"
+local DBICON10_MINOR = 44 -- Bump on changes
+if not LibStub then error(DBICON10 .. " requires LibStub.") end
+local ldb = LibStub("LibDataBroker-1.1", true)
+if not ldb then error(DBICON10 .. " requires LibDataBroker-1.1.") end
+local lib = LibStub:NewLibrary(DBICON10, DBICON10_MINOR)
+if not lib then return end
+
+LibStub("AceHook-3.0"):Embed(lib)
+lib.objects = lib.objects or {}
+lib.callbackRegistered = lib.callbackRegistered or nil
+lib.callbacks = lib.callbacks or LibStub("CallbackHandler-1.0"):New(lib)
+lib.notCreated = lib.notCreated or {}
+lib.radius = lib.radius or 5
+lib.tooltip = lib.tooltip or CreateFrame("GameTooltip", "LibDBIconTooltip", UIParent, "GameTooltipTemplate")
+local next, Minimap = next, Minimap
+local tgetn = table.getn
+local isDraggingButton = false
+
+function lib:IconCallback(event, name, key, value)
+ if lib.objects[name] then
+ if key == "icon" then
+ lib.objects[name].icon:SetTexture(value)
+ elseif key == "iconCoords" then
+ lib.objects[name].icon:UpdateCoord()
+ elseif key == "iconR" then
+ local _, g, b = lib.objects[name].icon:GetVertexColor()
+ lib.objects[name].icon:SetVertexColor(value, g, b)
+ elseif key == "iconG" then
+ local r, _, b = lib.objects[name].icon:GetVertexColor()
+ lib.objects[name].icon:SetVertexColor(r, value, b)
+ elseif key == "iconB" then
+ local r, g = lib.objects[name].icon:GetVertexColor()
+ lib.objects[name].icon:SetVertexColor(r, g, value)
+ end
+ end
+end
+if not lib.callbackRegistered then
+ ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__icon", "IconCallback")
+ ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconCoords", "IconCallback")
+ ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconR", "IconCallback")
+ ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconG", "IconCallback")
+ ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconB", "IconCallback")
+ lib.callbackRegistered = true
+end
+
+local function getAnchors(frame)
+ local x, y = frame:GetCenter()
+ if not x or not y then return "CENTER" end
+ local hhalf = (x > UIParent:GetWidth()*2/3) and "RIGHT" or (x < UIParent:GetWidth()/3) and "LEFT" or ""
+ local vhalf = (y > UIParent:GetHeight()/2) and "TOP" or "BOTTOM"
+ return vhalf..hhalf, frame, (vhalf == "TOP" and "BOTTOM" or "TOP")..hhalf
+end
+
+local function onEnter()
+ if isDraggingButton then return end
+
+ for _, button in next, lib.objects do
+ if button.showOnMouseover then
+ --button.fadeOut:Stop()
+ button:SetAlpha(1)
+ end
+ end
+
+ local obj = this.dataObject
+ if obj.OnTooltipShow then
+ lib.tooltip:SetOwner(this, "ANCHOR_NONE")
+ lib.tooltip:SetPoint(getAnchors(this))
+ obj.OnTooltipShow(lib.tooltip)
+ lib.tooltip:Show()
+ elseif obj.OnEnter then
+ obj.OnEnter()
+ end
+end
+
+local function onLeave()
+ lib.tooltip:Hide()
+
+ if not isDraggingButton then
+ for _, button in next, lib.objects do
+ if button.showOnMouseover then
+ --button.fadeOut:Play()
+ end
+ end
+ end
+
+ local obj = this.dataObject
+ if obj.OnLeave then
+ obj.OnLeave()
+ end
+end
+
+--------------------------------------------------------------------------------
+
+local onDragStart, updatePosition
+
+do
+ local minimapShapes = {
+ ["ROUND"] = {true, true, true, true},
+ ["SQUARE"] = {false, false, false, false},
+ ["CORNER-TOPLEFT"] = {false, false, false, true},
+ ["CORNER-TOPRIGHT"] = {false, false, true, false},
+ ["CORNER-BOTTOMLEFT"] = {false, true, false, false},
+ ["CORNER-BOTTOMRIGHT"] = {true, false, false, false},
+ ["SIDE-LEFT"] = {false, true, false, true},
+ ["SIDE-RIGHT"] = {true, false, true, false},
+ ["SIDE-TOP"] = {false, false, true, true},
+ ["SIDE-BOTTOM"] = {true, true, false, false},
+ ["TRICORNER-TOPLEFT"] = {false, true, true, true},
+ ["TRICORNER-TOPRIGHT"] = {true, false, true, true},
+ ["TRICORNER-BOTTOMLEFT"] = {true, true, false, true},
+ ["TRICORNER-BOTTOMRIGHT"] = {true, true, true, false},
+ }
+
+ local rad, cos, sin, sqrt, max, min = math.rad, math.cos, math.sin, math.sqrt, math.max, math.min
+ function updatePosition(button, position)
+ local angle = rad(position or 225)
+ local x, y, q = cos(angle), sin(angle), 1
+ if x < 0 then q = q + 1 end
+ if y > 0 then q = q + 2 end
+ local minimapShape = GetMinimapShape and GetMinimapShape() or "ROUND"
+ local quadTable = minimapShapes[minimapShape]
+ local w = (Minimap:GetWidth() / 2) + lib.radius
+ local h = (Minimap:GetHeight() / 2) + lib.radius
+ if quadTable[q] then
+ x, y = x*w, y*h
+ else
+ local diagRadiusW = sqrt(2*(w)^2)-10
+ local diagRadiusH = sqrt(2*(h)^2)-10
+ x = max(-w, min(x*diagRadiusW, w))
+ y = max(-h, min(y*diagRadiusH, h))
+ end
+ button:SetPoint("CENTER", Minimap, "CENTER", x, y)
+ end
+end
+
+local function onClick( )
+ if this.dataObject.OnClick then
+ this.dataObject.OnClick( this, arg1)
+ end
+end
+
+local function onMouseDown()
+ this.isMouseDown = true
+ this.icon:UpdateCoord()
+end
+
+local function onMouseUp()
+ this.isMouseDown = false
+ this.icon:UpdateCoord()
+end
+
+local fmod = function(x, y)
+ return x - math.floor(x / y) * y
+end
+
+do
+ local deg, atan2 = math.deg, math.atan2
+ local function onUpdate()
+ local mx, my = Minimap:GetCenter()
+ local px, py = GetCursorPosition()
+ local scale = Minimap:GetEffectiveScale()
+ px, py = px / scale, py / scale
+ local pos = 225
+ if this.db then
+ pos = fmod(deg(atan2(py - my, px - mx)) , 360)
+ this.db.minimapPos = pos
+ else
+ pos = fmod(deg(atan2(py - my, px - mx)) , 360)
+ this.minimapPos = pos
+ end
+ updatePosition(this, pos)
+ end
+
+ function onDragStart()
+ this:LockHighlight()
+ this.isMouseDown = true
+ this.icon:UpdateCoord()
+ this:SetScript("OnUpdate", onUpdate)
+ isDraggingButton = true
+ lib.tooltip:Hide()
+ for _, button in next, lib.objects do
+ if button.showOnMouseover then
+ --button.fadeOut:Stop()
+ button:SetAlpha(1)
+ end
+ end
+ end
+end
+
+local function onDragStop()
+ this:SetScript("OnUpdate", nil)
+ this.isMouseDown = false
+ this.icon:UpdateCoord()
+ this:UnlockHighlight()
+ isDraggingButton = false
+ for _, button in next, lib.objects do
+ if button.showOnMouseover then
+ --button.fadeOut:Play()
+ end
+ end
+end
+
+local defaultCoords = {0, 1, 0, 1}
+local function updateCoord(self)
+ local coords = self:GetParent().dataObject.iconCoords or defaultCoords
+ local deltaX, deltaY = 0, 0
+ if not self:GetParent().isMouseDown then
+ deltaX = (coords[2] - coords[1]) * 0.05
+ deltaY = (coords[4] - coords[3]) * 0.05
+ end
+ self:SetTexCoord(coords[1] + deltaX, coords[2] - deltaX, coords[3] + deltaY, coords[4] - deltaY)
+end
+
+local function createButton(name, object, db)
+ local button = CreateFrame("Button", "LibDBIcon10_"..name, Minimap)
+ button.dataObject = object
+ button.db = db
+ button:SetFrameStrata("MEDIUM")
+ button:SetWidth(31)
+ button:SetHeight(31)
+ button:SetFrameLevel(8)
+ button:SetFrameStrata("HIGH")
+ button:SetFrameLevel(7)
+ button:EnableMouse(true)
+ --button:EnableMouseWheel(true)
+ button:SetMovable(true)
+ button:RegisterForClicks("LeftButtonUp","RightButtonUp")
+ button:RegisterForDrag("LeftButton")
+ button:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
+ local overlay = button:CreateTexture(nil, "OVERLAY")
+ overlay:SetWidth(53)
+ overlay:SetHeight(53)
+ overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
+ overlay:SetPoint("TOPLEFT",0,0)
+ local background = button:CreateTexture(nil, "BACKGROUND")
+ background:SetWidth(20)
+ background:SetHeight(20)
+ background:SetTexture("Interface\\Minimap\\UI-Minimap-Background")
+ background:SetPoint("TOPLEFT", 7, -5)
+ local icon = button:CreateTexture(nil, "ARTWORK")
+ icon:SetWidth(17)
+ icon:SetHeight(17)
+ icon:SetTexture(object.icon)
+ icon:SetPoint("TOPLEFT", 7, -6)
+ button.icon = icon
+ icon.parent = button
+ button.isMouseDown = false
+
+ local r, g, b = icon:GetVertexColor()
+ icon:SetVertexColor(object.iconR or r, object.iconG or g, object.iconB or b)
+
+ icon.UpdateCoord = updateCoord
+ icon:UpdateCoord()
+
+ button:SetScript("OnEnter", onEnter)
+ button:SetScript("OnLeave", onLeave)
+ button:SetScript("OnClick", onClick)
+ if not db or not db.lock then
+ button:SetScript("OnDragStart", onDragStart)
+ button:SetScript("OnDragStop", onDragStop)
+ end
+ button:SetScript("OnMouseDown", onMouseDown)
+ button:SetScript("OnMouseUp", onMouseUp)
+--[[
+ button.fadeOut = button:CreateAnimationGroup()
+ local animOut = button.fadeOut:CreateAnimation("Alpha")
+ animOut:SetOrder(1)
+ animOut:SetDuration(0.2)
+ animOut:SetFromAlpha(1)
+ animOut:SetToAlpha(0)
+ animOut:SetStartDelay(1)
+ button.fadeOut:SetToFinalAlpha(true)
+]]
+ lib.objects[name] = button
+
+ if lib.loggedIn then
+ updatePosition(button, db and db.minimapPos)
+ if not db or not db.hide then
+ button:Show()
+ else
+ button:Hide()
+ end
+ end
+ lib.callbacks:Fire("LibDBIcon_IconCreated", button, name) -- Fire 'Icon Created' callback
+end
+
+-- We could use a metatable.__index on lib.objects, but then we'd create
+-- the icons when checking things like :IsRegistered, which is not necessary.
+local function check(name)
+ if lib.notCreated[name] then
+ createButton(name, lib.notCreated[name][1], lib.notCreated[name][2])
+ lib.notCreated[name] = nil
+ end
+end
+
+-- Wait a bit with the initial positioning to let any GetMinimapShape addons
+-- load up.
+if not lib.loggedIn then
+ local f = CreateFrame("Frame")
+ f:SetScript("OnEvent", function()
+ for _, button in next, lib.objects do
+ updatePosition(button, button.db and button.db.minimapPos)
+ if not button.db or not button.db.hide then
+ button:Show()
+ else
+ button:Hide()
+ end
+ end
+ lib.loggedIn = true
+ this:SetScript("OnEvent", nil)
+ end)
+ f:RegisterEvent("PLAYER_LOGIN")
+end
+
+local function getDatabase(name)
+ return lib.notCreated[name] and lib.notCreated[name][2] or lib.objects[name].db
+end
+
+function lib:Register(name, object, db)
+ if not object.icon then error("Can't register LDB objects without icons set!") end
+ if lib.objects[name] or lib.notCreated[name] then error(DBICON10.. ": Object '".. name .."' is already registered.") end
+ if not db or not db.hide then
+ createButton(name, object, db)
+ else
+ lib.notCreated[name] = {object, db}
+ end
+end
+
+function lib:Lock(name)
+ if not lib:IsRegistered(name) then return end
+ if lib.objects[name] then
+ lib.objects[name]:SetScript("OnDragStart", nil)
+ lib.objects[name]:SetScript("OnDragStop", nil)
+ end
+ local db = getDatabase(name)
+ if db then
+ db.lock = true
+ end
+end
+
+function lib:Unlock(name)
+ if not lib:IsRegistered(name) then return end
+ if lib.objects[name] then
+ lib.objects[name]:SetScript("OnDragStart", onDragStart)
+ lib.objects[name]:SetScript("OnDragStop", onDragStop)
+ end
+ local db = getDatabase(name)
+ if db then
+ db.lock = nil
+ end
+end
+
+function lib:Hide(name)
+ if not lib.objects[name] then return end
+ lib.objects[name]:Hide()
+end
+
+function lib:Show(name)
+ check(name)
+ local button = lib.objects[name]
+ if button then
+ button:Show()
+ updatePosition(button, button.db and button.db.minimapPos or button.minimapPos)
+ end
+end
+
+function lib:IsRegistered(name)
+ return (lib.objects[name] or lib.notCreated[name]) and true or false
+end
+
+function lib:Refresh(name, db)
+ check(name)
+ local button = lib.objects[name]
+ if db then
+ button.db = db
+ end
+ updatePosition(button, button.db and button.db.minimapPos or button.minimapPos)
+ if not button.db or not button.db.hide then
+ button:Show()
+ else
+ button:Hide()
+ end
+ if not button.db or not button.db.lock then
+ button:SetScript("OnDragStart", onDragStart)
+ button:SetScript("OnDragStop", onDragStop)
+ else
+ button:SetScript("OnDragStart", nil)
+ button:SetScript("OnDragStop", nil)
+ end
+end
+
+function lib:GetMinimapButton(name)
+ return lib.objects[name]
+end
+
+do
+ local function OnMinimapEnter()
+ if isDraggingButton then return end
+ for _, button in next, lib.objects do
+ if button.showOnMouseover then
+ -- button.fadeOut:Stop()
+ button:SetAlpha(1)
+ end
+ end
+ end
+ local function OnMinimapLeave()
+ if isDraggingButton then return end
+ for _, button in next, lib.objects do
+ if button.showOnMouseover then
+ -- button.fadeOut:Play()
+ end
+ end
+ end
+ lib:HookScript(Minimap, "OnEnter", OnMinimapEnter)
+ lib:HookScript(Minimap, "OnLeave", OnMinimapLeave)
+
+ function lib:ShowOnEnter(name, value)
+ local button = lib.objects[name]
+ if button then
+ if value then
+ button.showOnMouseover = true
+ -- button.fadeOut:Stop()
+ button:SetAlpha(0)
+ else
+ button.showOnMouseover = false
+ -- button.fadeOut:Stop()
+ button:SetAlpha(1)
+ end
+ end
+ end
+end
+
+function lib:GetButtonList()
+ local t = {}
+ for name in next, lib.objects do
+ tinsert(t, name)
+ end
+ return t
+end
+
+function lib:SetButtonRadius(radius)
+ if type(radius) == "number" then
+ lib.radius = radius
+ for _, button in next, lib.objects do
+ updatePosition(button, button.db and button.db.minimapPos or button.minimapPos)
+ end
+ end
+end
+
+function lib:SetButtonToPosition(button, position)
+ updatePosition(lib.objects[button] or button, position)
+end
+
+-- Upgrade!
+for name, button in next, lib.objects do
+ local db = getDatabase(name)
+ if not db or not db.lock then
+ button:SetScript("OnDragStart", onDragStart)
+ button:SetScript("OnDragStop", onDragStop)
+ end
+ button:SetScript("OnEnter", onEnter)
+ button:SetScript("OnLeave", onLeave)
+ button:SetScript("OnClick", onClick)
+ button:SetScript("OnMouseDown", onMouseDown)
+ button:SetScript("OnMouseUp", onMouseUp)
+--[[
+ if not button.fadeOut then -- Upgrade to 39
+ button.fadeOut = button:CreateAnimationGroup()
+ local animOut = button.fadeOut:CreateAnimation("Alpha")
+ animOut:SetOrder(1)
+ animOut:SetDuration(0.2)
+ animOut:SetFromAlpha(1)
+ animOut:SetToAlpha(0)
+ animOut:SetStartDelay(1)
+ button.fadeOut:SetToFinalAlpha(true)
+ end
+ ]]
+end
+lib:SetButtonRadius(lib.radius) -- Upgrade to 40
diff --git a/libs/LibDataBroker-1.1.lua b/libs/LibDataBroker-1.1.lua
new file mode 100644
index 0000000..f47c0cd
--- /dev/null
+++ b/libs/LibDataBroker-1.1.lua
@@ -0,0 +1,90 @@
+
+assert(LibStub, "LibDataBroker-1.1 requires LibStub")
+assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0")
+
+local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4)
+if not lib then return end
+oldminor = oldminor or 0
+
+
+lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib)
+lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {}
+local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks
+
+if oldminor < 2 then
+ lib.domt = {
+ __metatable = "access denied",
+ __index = function(self, key) return attributestorage[self] and attributestorage[self][key] end,
+ }
+end
+
+if oldminor < 3 then
+ lib.domt.__newindex = function(self, key, value)
+ if not attributestorage[self] then attributestorage[self] = {} end
+ if attributestorage[self][key] == value then return end
+ attributestorage[self][key] = value
+ local name = namestorage[self]
+ if not name then return end
+ callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self)
+ callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self)
+ callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self)
+ callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self)
+ end
+end
+
+if oldminor < 2 then
+ function lib:NewDataObject(name, dataobj)
+ if self.proxystorage[name] then return end
+
+ if dataobj then
+ assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table")
+ self.attributestorage[dataobj] = {}
+ for i,v in pairs(dataobj) do
+ self.attributestorage[dataobj][i] = v
+ dataobj[i] = nil
+ end
+ end
+ dataobj = setmetatable(dataobj or {}, self.domt)
+ self.proxystorage[name], self.namestorage[dataobj] = dataobj, name
+ self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj)
+ return dataobj
+ end
+end
+
+if oldminor < 1 then
+ function lib:DataObjectIterator()
+ return pairs(self.proxystorage)
+ end
+
+ function lib:GetDataObjectByName(dataobjectname)
+ return self.proxystorage[dataobjectname]
+ end
+
+ function lib:GetNameByDataObject(dataobject)
+ return self.namestorage[dataobject]
+ end
+end
+
+if oldminor < 4 then
+ local next = pairs(attributestorage)
+ function lib:pairs(dataobject_or_name)
+ local t = type(dataobject_or_name)
+ assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)")
+
+ local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name
+ assert(attributestorage[dataobj], "Data object not found")
+
+ return next, attributestorage[dataobj], nil
+ end
+
+ local ipairs_iter = ipairs(attributestorage)
+ function lib:ipairs(dataobject_or_name)
+ local t = type(dataobject_or_name)
+ assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)")
+
+ local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name
+ assert(attributestorage[dataobj], "Data object not found")
+
+ return ipairs_iter, attributestorage[dataobj], 0
+ end
+end
diff --git a/libs/LibStub.lua b/libs/LibStub.lua
new file mode 100644
index 0000000..a71512f
--- /dev/null
+++ b/libs/LibStub.lua
@@ -0,0 +1,33 @@
+-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
+-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
+local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
+local _G = getfenv()
+local strfind, strfmt = string.find, string.format
+local LibStub = _G[LIBSTUB_MAJOR]
+
+if not LibStub or LibStub.minor < LIBSTUB_MINOR then
+ LibStub = LibStub or { libs = {}, minors = {} }
+ _G[LIBSTUB_MAJOR] = LibStub
+ LibStub.minor = LIBSTUB_MINOR
+
+ function LibStub:NewLibrary(major, minor)
+ assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
+ local _,_,num = strfind(minor, "(%d+)")
+ minor = assert(tonumber(num), "Minor version must either be a number or contain a number.")
+
+ local oldminor = self.minors[major]
+ if oldminor and oldminor >= minor then return nil end
+ self.minors[major], self.libs[major] = minor, self.libs[major] or {}
+ return self.libs[major], oldminor
+ end
+
+ function LibStub:GetLibrary(major, silent)
+ if not self.libs[major] and not silent then
+ error(strfmt("Cannot find a library instance of %q.", tostring(major)), 2)
+ end
+ return self.libs[major], self.minors[major]
+ end
+
+ function LibStub:IterateLibraries() return pairs(self.libs) end
+ setmetatable(LibStub, { __call = LibStub.GetLibrary })
+end