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

663 lines
21 KiB

local _, addon = ...
local API = addon.API;
local L = addon.L;
local SetDBValue = addon.SetDBValue;
local GetDBValue = addon.GetDBValue;
local Mixin = API.Mixin;
local MapFrame = WorldMapFrame;
local TooltipFrame = GameTooltip;
local InCombatLockdown = InCombatLockdown;
local C_UIWidgetManager = C_UIWidgetManager;
local PIN_TEMPLATE_NAME = "PlumberWorldMapPinTemplate";
local PinController = CreateFrame("Frame"); --Events driven, updates Pin
addon.MapPinController = PinController;
local MapTracker = CreateFrame("Frame"); --Attach to WorldMapFrame to monitor changes to map, scale...
local WorldMapDataProvider = CreateFromMixins(MapCanvasDataProviderMixin);
local BlizzardUIUtil = {};
do --BlizzardUIUtil
local QuestDetailsFrame = QuestMapFrame and QuestMapFrame.DetailsFrame;
function BlizzardUIUtil:IsViewingQuestDetails()
return QuestDetailsFrame and QuestDetailsFrame:IsVisible();
end
--Map Filter Menu
function BlizzardUIUtil:HookIntoMenu()
if not self.isMenuHooked then
self.isMenuHooked = true;
else
return
end
self.isMenuHooked = true;
if Menu and Menu.ModifyMenu then
Menu.ModifyMenu("MENU_WORLD_MAP_TRACKING", function(owner, rootDescription, contextData)
if not PinController.isEnabled then return end;
local options = PinController:GetFilterOptionsForCurrentMap();
if options then
rootDescription:CreateDivider();
rootDescription:CreateTitle("Plumber");
for _, option in ipairs(options) do
--rootDescription:CreateButton("Button", function() print("Text here!" end);
local function IsSelected()
return GetDBValue(option.dbKey);
end
local function SetSelected()
local newState = not GetDBValue(option.dbKey);
SetDBValue(option.dbKey, newState);
PinController:EnableMapDataProvider(option.dbKey, newState);
PinController:RequestUpdate();
end
local checkbox = rootDescription:CreateCheckbox(option.name, IsSelected, SetSelected);
checkbox:AddInitializer(function(button, description, menu)
local rightTexture = button:AttachTexture();
rightTexture:SetPoint("RIGHT");
option.iconSetupFunc(rightTexture);
local fontString = button.fontString;
fontString:SetPoint("RIGHT", rightTexture, "LEFT");
fontString:SetTextColor(1, 1, 1);
local pad = 20;
local width = pad + fontString:GetUnboundedStringWidth() + rightTexture:GetWidth();
local height = 20;
return width, height;
end);
end
local submenu = rootDescription:CreateButton(L["Pin Size"]);
local function IsSelected(index)
return index == PinController:GetPinSizeIndex()
end
local function SetSelected(index)
SetDBValue("WorldMapPin_Size", index, true);
return (MenuResponse and MenuResponse.Refresh) or 2
end
for index, text in ipairs(PinController:GetPinSizeChoices()) do
submenu:CreateRadio(text, IsSelected, SetSelected, index);
end
end
end);
end
end
end
do --PinController
PinController.pins = {};
PinController.mapDataProviders = {};
PinController.dbKeyXdataprovider = {};
local tinsert = table.insert;
local ipairs = ipairs;
function PinController:AddPin(pin)
tinsert(self.pins, pin);
end
function PinController:AddMapDataProvider(mapID, dataProvider)
if not self.mapDataProviders[mapID] then
self.mapDataProviders[mapID] = {};
end
tinsert(self.mapDataProviders[mapID], dataProvider);
if not self.dbKeyXdataprovider[dataProvider.OptionData.dbKey] then
self.dbKeyXdataprovider[dataProvider.OptionData.dbKey] = dataProvider;
else
print(string.format("Plumber: Duplicated MapDataProvider Key %s", dataProvider.OptionData.dbKey));
end
end
function PinController:InitializeMapDataProvider()
if self.mapDataProviders then
for mapID, dataProviders in pairs(self.mapDataProviders) do
for _, dataProvider in ipairs(dataProviders) do
dataProvider.enabled = GetDBValue(dataProvider.OptionData.dbKey);
end
end
end
end
function PinController:EnableMapDataProvider(dbKey, state)
local dataProvider = self.dbKeyXdataprovider[dbKey];
if dataProvider then
dataProvider.enabled = state == true or state == nil;
end
end
function PinController:DoesMapHaveData(mapID)
if mapID and self.mapDataProviders[mapID] then
for _, dataProvider in ipairs(self.mapDataProviders[mapID]) do
if dataProvider.enabled then
return true
end
end
end
end
function PinController:UpdatePinForMap(mapID)
local pin, pinData;
local anyPins = false;
local pinSizeScale = self:GetPinSizeScale();
for i, dataProvider in ipairs(self.mapDataProviders[mapID]) do
if dataProvider.enabled then
pinData = dataProvider:GetPinDataForMap(mapID);
if pinData then
for _, data in ipairs(pinData) do
pin = MapFrame:AcquirePin(PIN_TEMPLATE_NAME, data);
if pin then
anyPins = true;
pin:SetSizeScale(pinSizeScale);
end
end
end
end
end
return anyPins
end
function PinController:GetFilterOptionsForCurrentMap()
local mapID = MapTracker:GetMapID();
if mapID and self.mapDataProviders[mapID] then
local options;
local n = 0;
for _, dataProvider in ipairs(self.mapDataProviders[mapID]) do
if not options then
options = {};
end
n = n + 1;
options[n] = dataProvider.OptionData;
--option = {name=, iconSetupFunc=, enabled=}
end
return options
end
end
function PinController:OnUpdate(elapsed)
self.t = self.t + elapsed;
if self.t >= 0.2 then
self.t = nil;
self:SetScript("OnUpdate", nil);
MapTracker:OnMapChanged();
end
end
function PinController:RequestUpdate()
--Called by sub module when data is recieved
self.t = 0;
self:SetScript("OnUpdate", self.OnUpdate);
end
function PinController:ListenEvents(state)
if state and not self.listening then
self.listening = true;
self:RegisterEvent("AREA_POIS_UPDATED");
self:SetScript("OnEvent", self.OnEvent);
elseif (not state) and self.listening then
self.listening = false;
self:UnregisterEvent("AREA_POIS_UPDATED");
self:SetScript("OnEvent", nil);
end
end
function PinController:OnEvent(event, ...)
if event == "AREA_POIS_UPDATED" then
self:RequestUpdate();
end
end
function PinController:UpdatePinScale()
end
--Custom Pin Size
local PinSizeIndexSizeScale = {
--[pinSizeIndex] = scale,
[1] = 1.0,
[2] = 1.25,
[3] = 1.5,
};
function PinController:GetPinSizeChoices()
local tbl = {};
for i, scale in ipairs(PinSizeIndexSizeScale) do
tbl[i] = string.format("%.0f%%", scale * 100);
end
return tbl
end
function PinController:GetPinSizeIndex()
return self.pinSizeIndex or 1
end
function PinController:GetPinSizeScale()
return self.pinSizeScale or 1
end
function PinController:SetPinSizeIndex(index, useInput)
if not (index and PinSizeIndexSizeScale[index]) then
index = 1;
end
self.pinSizeIndex = index;
self.pinSizeScale = PinSizeIndexSizeScale[index];
if useInput then
MapTracker:OnMapChanged();
end
end
addon.CallbackRegistry:Register("SettingChanged.WorldMapPin_Size", PinController.SetPinSizeIndex, PinController);
end
do --WorldMapDataProvider
function WorldMapDataProvider:GetPinTemplate()
return PIN_TEMPLATE_NAME
end
function WorldMapDataProvider:OnShow()
end
function WorldMapDataProvider:OnHide()
PinController:ListenEvents(false);
end
function WorldMapDataProvider:OnEvent(event, ...)
--This significantly increase RAM usage count
--So we monitor Events using another frame (PinController)
end
function WorldMapDataProvider:RemoveAllIfNeeded()
if self.anyPins then
self:RemoveAllData();
end
end
function WorldMapDataProvider:RemoveAllData()
self.anyPins = false;
MapFrame:RemoveAllPinsByTemplate(PIN_TEMPLATE_NAME);
end
function WorldMapDataProvider:RefreshAllData(fromOnShow)
self:RemoveAllIfNeeded();
self:ShowAllPins();
end
function WorldMapDataProvider:RefreshAllDataIfPossible()
self:RemoveAllIfNeeded();
if not BlizzardUIUtil:IsViewingQuestDetails() then
self:ShowAllPins();
end
end
function WorldMapDataProvider:ShowAllPins()
local mapID = MapTracker:GetMapID();
self.anyPins = PinController:UpdatePinForMap(mapID);
end
function WorldMapDataProvider:OnCanvasScaleChanged()
--Fires multiple times when opening WorldMapFrame
PinController:UpdatePinScale();
end
end
do
local MapScrollContainer = WorldMapFrame.ScrollContainer;
function MapTracker:Attach()
if not self.attached then
self.attached = true;
self:SetParent(MapFrame);
self:EnableScripts();
self:Show();
end
end
function MapTracker:Detach()
if self.attached then
self.attached = nil;
self.mapID = nil;
self:SetParent(nil);
self:DisableScripts();
self:Hide();
WorldMapDataProvider:RemoveAllIfNeeded();
WorldMapDataProvider:OnHide();
end
end
function MapTracker:OnUpdate(elapsed)
self.t1 = self.t1 + elapsed;
self.t2 = self.t2 + elapsed;
if self.t1 > 0.016 then
self.t1 = 0;
self.newMapID = MapFrame.mapID;
if self.newMapID ~= self.mapID then
self.mapID = self.newMapID;
self:OnMapChanged(self.mapID);
end
self.newScale = MapScrollContainer.targetScale;
if self.newScale ~= self.mapScale then
self.mapScale = self.newScale;
self:OnCanvasScaleChanged();
end
end
if self.t2 > 0.1 then
self.t2 = 0;
self.detailsVisiblity = BlizzardUIUtil:IsViewingQuestDetails();
if self.detailsVisiblity ~= self.isViewingDetails then
self.isViewingDetails = self.detailsVisiblity;
self:OnViewingQuestDetailsChanged();
end
end
end
function MapTracker:OnShow()
self.mapID = nil;
self.mapScale = nil;
self.isViewingDetails = BlizzardUIUtil:IsViewingQuestDetails();
self.t1 = 1;
end
function MapTracker:OnHide()
WorldMapDataProvider:OnHide();
end
function MapTracker:EnableScripts()
self.t1 = 0;
self.t2 = 0;
self:SetScript("OnShow", self.OnShow);
self:SetScript("OnHide", self.OnHide);
self:SetScript("OnUpdate", self.OnUpdate);
end
function MapTracker:DisableScripts()
self:SetScript("OnShow", nil);
self:SetScript("OnHide", nil);
self:SetScript("OnUpdate", nil);
end
function MapTracker:GetMapID()
return self.mapID
end
function MapTracker:OnMapChanged(mapID)
if not mapID then
mapID = self:GetMapID();
end
if not mapID then return end;
if PinController:DoesMapHaveData(mapID) then
PinController:ListenEvents(true);
WorldMapDataProvider:RefreshAllDataIfPossible();
else
PinController:ListenEvents(false);
WorldMapDataProvider:RemoveAllIfNeeded();
end
end
function MapTracker:OnCanvasScaleChanged()
WorldMapDataProvider:OnCanvasScaleChanged();
end
function MapTracker:OnViewingQuestDetailsChanged()
if self.isViewingDetails then
else
self:OnMapChanged();
end
end
end
do
PlumberWorldMapPinMixin = CreateFromMixins(MapCanvasPinMixin);
local function Dummy_SetPassThroughButtons()
end
function PlumberWorldMapPinMixin:OnCreated()
--When frame being created
self.originalSetPassThroughButtons = self.SetPassThroughButtons;
self.SetPassThroughButtons = Dummy_SetPassThroughButtons;
self:AllowPassThroughRightButton(true);
PinController:AddPin(self);
end
function PlumberWorldMapPinMixin:OnLoad()
--newPin (see MapCanvasMixin:AcquirePin)
self:SetScalingLimits(1, 1.0, 1.2);
self.pinFrameLevelType = "PIN_FRAME_LEVEL_GROUP_MEMBER"; --PIN_FRAME_LEVEL_VIGNETTE PIN_FRAME_LEVEL_AREA_POI PIN_FRAME_LEVEL_WAYPOINT_LOCATION PIN_FRAME_LEVEL_GROUP_MEMBER
self.pinFrameLevelIndex = 1;
self:SetTexture("Interface/AddOns/Plumber/Art/MapPin/SeedPlanting-Empty-Distant");
end
function PlumberWorldMapPinMixin:AllowPassThroughRightButton(unpackedPrimitiveType)
--Original "SetPassThroughButtons" (see SimpleScriptRegionAPI for details) has chance to taint when called
--So we overwrite it
if (not self.isRightButtonAllowed) and (not InCombatLockdown()) then
self.isRightButtonAllowed = true;
if self.originalSetPassThroughButtons then
self.originalSetPassThroughButtons(self, "RightButton");
end
end
end
function PlumberWorldMapPinMixin:SetTexture(texture, sampling)
sampling = sampling or "LINEAR";
self.Texture:SetTexture(texture, nil, nil, sampling);
self.HighlightTexture:SetTexture(texture, nil, nil, sampling);
self.HighlightTexture:SetVertexColor(0.4, 0.4, 0.4);
end
function PlumberWorldMapPinMixin:SetTexCoord(l, r, t, b)
self.Texture:SetTexCoord(l, r, t, b);
self.HighlightTexture:SetTexCoord(l, r, t, b);
end
function PlumberWorldMapPinMixin:OnMouseLeave()
--BaseMapPoiPinMixin.OnMouseLeave(self);
TooltipFrame:Hide();
end
function PlumberWorldMapPinMixin:SetClickable(state)
if state ~= self.isClickable then
self.isClickable = state;
end
self:SetMouseClickEnabled(state);
end
function PlumberWorldMapPinMixin:OnAcquired(data)
if self.mixin ~= data.mixin then
self.mixin = data.mixin;
Mixin(self, data.mixin);
end
self:SetClickable(data.clickable);
self.data = data;
self:UpdatePosition();
self:Update();
end
function PlumberWorldMapPinMixin:UpdatePosition()
if self.data then
self:SetPosition(self.data.x, self.data.y);
end
end
function PlumberWorldMapPinMixin:OnMouseEnter()
self.UpdateTooltip = nil;
self:AllowPassThroughRightButton(true);
self:PostMouseEnter();
end
function PlumberWorldMapPinMixin:TriggerMouseReEnter()
self.UpdateTooltip = self.OnMouseEnter;
end
local function GetTextColorForEnabledState(enabledState)
local Stats = Enum.WidgetEnabledState;
if enabledState == Stats.Disabled then
return DISABLED_FONT_COLOR;
elseif enabledState == Stats.Red then
return RED_FONT_COLOR;
elseif enabledState == Stats.White then
return HIGHLIGHT_FONT_COLOR;
elseif enabledState == Stats.Green then
return GREEN_FONT_COLOR;
elseif enabledState == Stats.Artifact then
return ARTIFACT_GOLD_COLOR;
elseif enabledState == Stats.Black then
return BLACK_FONT_COLOR;
elseif enabledState == Stats.BrightBlue then
return BRIGHTBLUE_FONT_COLOR;
else
return NORMAL_FONT_COLOR;
end
end
function PlumberWorldMapPinMixin:AttachWidgetSetToTooltip(tooltip, widgetSetID, textRule)
local widgets = C_UIWidgetManager.GetAllWidgetsBySetID(widgetSetID);
--We only show TextWithState
if widgets then
for _, info in ipairs(widgets) do
if info.widgetType == 8 then
local widgetInfo = C_UIWidgetManager.GetTextWithStateWidgetVisualizationInfo(info.widgetID);
if widgetInfo and widgetInfo.shownState ~= 0 and widgetInfo.text then
if (not textRule) or (textRule(widgetInfo.text)) then
local color = GetTextColorForEnabledState(widgetInfo.enabledState);
local r, g, b = color:GetRGB();
tooltip:AddLine(widgetInfo.text, r, g, b, true);
end
end
end
end
end
end
function PlumberWorldMapPinMixin:AddQuestTimeToTooltip(tooltip, questID)
if not questID then return end;
local formattedTime, color, secondsRemaining = WorldMap_GetQuestTimeForTooltip(questID);
if formattedTime and color then
GameTooltip_AddColoredLine(tooltip, formattedTime, color);
end
end
--Overwrite by module
function PlumberWorldMapPinMixin:PostMouseEnter()
end
function PlumberWorldMapPinMixin:IsMouseClickEnabled()
return true
end
function PlumberWorldMapPinMixin:OnMouseClickAction(mouseButton)
end
function PlumberWorldMapPinMixin:Update()
end
end
do --Master Switch
local function EnableMapPinSystem(state)
if state then
MapTracker:Attach();
PinController:InitializeMapDataProvider();
PinController.isEnabled = true;
BlizzardUIUtil:HookIntoMenu();
else
MapTracker:Detach();
PinController.isEnabled = false;
PinController:ListenEvents(false);
end
end
local moduleData = {
name = addon.L["ModuleName WorldMapPin_TWW"],
dbKey = "WorldMapPin_TWW",
description = string.format(L["ModuleDescription WorldMapPin_TWW"], L["Bountiful Delve"], L["Special Assignment"]);
toggleFunc = EnableMapPinSystem,
categoryID = 1,
uiOrder = 1101,
moduleAddedTime = 1721730000,
};
addon.ControlCenter:AddModule(moduleData);
end
do --Dev Tool
local DevTool;
local function SavePOIPosition(poiData)
if not DevTool then
DevTool = CreateFrame("Frame");
end
DevTool.poiData = poiData;
DevTool.index = 0;
DevTool.t = 0;
DevTool:SetScript("OnUpdate", function(self, elapsed)
self.t = self.t + elapsed;
if self.t > 0.25 then
self.t = 0;
else
return
end
self.index = self.index + 1;
local data = self.poiData[self.index];
if not data then
self:SetScript("OnUpdate", nil);
return
end
local mapID = data[1];
local poiID = data[2];
local poiInfo = C_AreaPoiInfo.GetAreaPOIInfo(mapID, poiID);
if poiInfo then
if not data.name then
data.name = poiInfo.name;
end
local x, y = poiInfo.position:GetXY();
API.ConvertMapPositionToContinentPosition(mapID, x, y, poiID);
else
print("Missing POI", poiID);
end
end);
end
addon.SavePOIPosition = SavePOIPosition;
end