From 57fff5d56b6f59e45c5dd4f163bd3a9295a8c5fe Mon Sep 17 00:00:00 2001 From: "nikosvandenbroek@hotmail.com" Date: Fri, 14 Feb 2020 16:35:06 +0100 Subject: [PATCH] 8.3.03.1.3 Changed pin nudging and placement --- Data.lua | 15 ++- Debug.lua | 2 +- MapPinProvider.lua | 241 ++++++++++++++++++++++++++++++++------------- Settings.lua | 1 + Templates.xml | 110 +++++++++++++++++++++ WorldQuestTab.xml | 110 +-------------------- 6 files changed, 298 insertions(+), 181 deletions(-) diff --git a/Data.lua b/Data.lua index 5125cdd..0d9f9d0 100644 --- a/Data.lua +++ b/Data.lua @@ -4,7 +4,7 @@ addon.WQT = LibStub("AceAddon-3.0"):NewAddon("WorldQuestTab"); addon.externals = {}; addon.variables = {}; -addon.debug = false; +addon.debug = true; addon.WQT_Utils = {}; local WQT_Utils = addon.WQT_Utils; local _L = addon.L; @@ -439,12 +439,14 @@ _V["SETTING_TYPES"] = { ------------------------------- -- This list gets turned into a settings menu based on the data provided. -- GENERAL --- type (SETTING_TYPES): Defines the type of setting +-- (either) template: A frame template which inherits the base mixin WQT_SettingsBaseMixin; +-- (or) frameName: The name of a specific frame using the mixin WQT_SettingsBaseMixin; -- label (string): The text the label should have -- tooltip (string): Text displayed in the tooltip -- valueChangedFunc (function(value)): what actions should be taken when the value is changed. Value is nil for buttons -- isDisabled (boolean|function()): Boolean or function returning if the setting should be disabled -- getValueFunc (function()): Function returning the current value of the setting +-- isNew (boolean): Mark the setting as new by adding an exclamantion mark to the label -- SLIDER SPECIFIC -- min (number): min value -- max (number): max value @@ -453,7 +455,8 @@ _V["SETTING_TYPES"] = { -- options (table): a list for options in following format {[id] = {["label"] = "Displayed label", ["tooltip"] = "additional tooltip info (optional)"}, ...} _V["SETTING_CATEGORIES"] = { - {["id"]="GENERAL", ["label"] = GENERAL} + {["id"]="DEBUG", ["label"] = "Debug"} + ,{["id"]="GENERAL", ["label"] = GENERAL} ,{["id"]="QUESTLIST", ["label"] = _L["QUEST_LIST"]} ,{["id"]="MAPPINS", ["label"] = _L["MAP_PINS"]} ,{["id"]="WQTU", ["label"] = "Utilities"} @@ -674,7 +677,7 @@ _V["SETTING_LIST"] = { end ,["getValueFunc"] = function() return WQT.settings.pin.rewardTypeIcon; end ,["isDisabled"] = function() return WQT.settings.pin.disablePoI; end - } + } } _V["SETTING_UTILITIES_LIST"] = { @@ -691,7 +694,6 @@ _V["SETTING_UTILITIES_LIST"] = { } } - _V["TIME_REMAINING_CATEGORY"] = { ["none"] = 0 ,["expired"] = 1 @@ -1035,6 +1037,9 @@ local _patchNotes = { ,["new"] = { "New Setting: Include dailies (default on). Found under General settings. Treat certain dailies as world quests. Only affects dailies which Blizzard themselves treats as world quests." } + ,["changes"] = { + "Made some improvements to map pins to reduce the chance of one completely overlapping another." + } ,["fixes"] = { "Fixed WQTU 'load' setting not disabling when it is not enabled in the add-on list." } diff --git a/Debug.lua b/Debug.lua index e1a5135..57ab51b 100644 --- a/Debug.lua +++ b/Debug.lua @@ -25,7 +25,7 @@ local function bts(bool) end local function GetQuestDump() - local counted, limit = WQT_Utils:GetQuestLogInfo(hiddenList) + local counted, limit = WQT_Utils:GetQuestLogInfo() local output = FORMAT_QUEST_HEADER:format(counted, limit); local title, level, suggestedGroup, isHeader, isCollapsed, isComplete, frequency, questID, startEvent, displayQuestID, isOnMap, hasLocalPOI, isTask, isBounty, isStory, isHidden, isScaling; diff --git a/MapPinProvider.lua b/MapPinProvider.lua index 7508fdc..2be21fc 100644 --- a/MapPinProvider.lua +++ b/MapPinProvider.lua @@ -20,11 +20,30 @@ local ICON_ANGLE_START = 270; local ICON_ANGLE_DISTANCE = 50; local ICON_CENTER_DISTANCE = 13; local ICON_MAX_AMOUNT = floor(360/ICON_ANGLE_DISTANCE); +local PIN_FRAME_LEVEL_BASE = 2200; +local PIN_FRAME_LEVEL_FOCUS = 3000; ------------------------------------ -- Locals ------------------------------------ - + +local function SortPinsByMapPos(a, b) + local aX, aY = a:GetNudgedPosition(); + local bX, bY = b:GetNudgedPosition(); + if (aX and bX) then + -- If 2 pins are close with the left being slightly higher, we still want left to be in front + if (aY ~= bY) then + return aY < bY; + else + if (aX ~= bY) then + return aX > bX; + end + end + end + + return a.questId < b.questId; +end + local function OnPinIconRelease(pool, iconFrame) iconFrame:Hide(); @@ -41,7 +60,7 @@ local function OnPinIconRelease(pool, iconFrame) end local function OnPinRelease(pool, pin) - pin.questID = nil; + pin.questId = nil; pin.nudgeX = 0; pin.nudgeY = 0; pin.updateTime = 0; @@ -85,7 +104,7 @@ end -- FixOverlaps(canvasRatio, isFlightMap) -- UpdateAllPlacements() -- UpdateQuestPings() --- SetQuestIDPinged(questID, shouldPing) +-- SetQuestIDPinged(questId, shouldPing) WQT_PinDataProvider = {}; @@ -104,6 +123,12 @@ function WQT_PinDataProvider:Init() wipe(self.pingedQuests); self:UpdateQuestPings(); end); + + self.clusterDistance = 0.5; + self.clusterSpread = 0.20; + self.enableNudging = true; + self.pinClusters = {}; + self.pinClusterLookup = {}; end function WQT_PinDataProvider:RemoveAllData() @@ -116,12 +141,10 @@ function WQT_PinDataProvider:RefreshAllData() WQT_WorldQuestFrame:HideOfficialMapPins(); local parentMapFrame; - local isFlightMap = false; if (WorldMapFrame:IsShown()) then parentMapFrame = WorldMapFrame; elseif (FlightMapFrame and FlightMapFrame:IsShown()) then parentMapFrame = FlightMapFrame; - isFlightMap = true; end -- If the Quest details are shown, keep all pins hidden. @@ -130,16 +153,16 @@ function WQT_PinDataProvider:RefreshAllData() end if (not parentMapFrame) then return; end + local mapID = parentMapFrame:GetMapID(); local settingsContinentPins = WQT_Utils:GetSetting("pin", "continentPins"); local settingsFilterPoI = WQT_Utils:GetSetting("pin", "filterPoI"); local mapInfo = WQT_Utils:GetCachedMapInfo(mapID); - local questList = WQT_WorldQuestFrame.dataProvider:GetIterativeList(); local canvas = parentMapFrame:GetCanvas(); wipe(self.activePins); - for k, questInfo in ipairs(questList) do - if (not settingsFilterPoI or questInfo.passedFilter) then + for k, questInfo in ipairs(WQT_WorldQuestFrame.dataProvider:GetIterativeList()) do + if (settingsFilterPoI and questInfo.passedFilter or (not settingsFilterPoI and questInfo.isValid)) then local pinType = GetPinType(parentMapFrame, mapInfo.mapType, questInfo, settingsContinentPins); if (pinType) then local posX, posY = C_TaskQuest.GetQuestLocation(questInfo.questId, mapID); @@ -148,7 +171,7 @@ function WQT_PinDataProvider:RefreshAllData() pin:SetParent(canvas); tinsert(self.activePins, pin); pin:Setup(questInfo, #self.activePins, posX, posY, pinType, parentMapFrame); - if (self.pingedQuests[pin.questID]) then + if (self.pingedQuests[pin.questId]) then pin:Focus(); else pin:ClearFocus(); @@ -159,49 +182,119 @@ function WQT_PinDataProvider:RefreshAllData() end -- Slightly spread out overlapping pins - self:FixOverlaps(canvas:GetWidth()/canvas:GetHeight(), isFlightMap); + self:FixOverlaps(canvas); self:UpdateAllPlacements(); self:UpdateQuestPings(); if (not self.hookedCanvasChanges[parentMapFrame]) then - hooksecurefunc(parentMapFrame, "OnCanvasScaleChanged", function() self:UpdateAllPlacements(); end); + hooksecurefunc(parentMapFrame, "OnCanvasScaleChanged", function() + self:FixOverlaps(canvas) + self:UpdateAllPlacements(); + end); self.hookedCanvasChanges[parentMapFrame] = true; end end -function WQT_PinDataProvider:FixOverlaps(canvasRatio, isFlightMap) - local maxOverlap = 0.01 * (isFlightMap and 0.5 or 1); +function WQT_PinDataProvider:FixOverlaps(canvas) + if (not self.enableNudging or not canvas) then return; end + local canvasScale = 1/canvas:GetScale(); + local scaling = 25/(canvas:GetWidth() * canvas:GetScale()); + local canvasRatio = canvas:GetWidth() /canvas:GetHeight(); + local clusterDistance = self.clusterDistance * scaling; + local clusterSpread = self.clusterSpread * scaling + local clusters = self.pinClusters; + local clusterdLookup = self.pinClusterLookup; + local cluster; + + for k, pin in ipairs(self.activePins) do + pin:ResetNudge() + end + + -- Put close proximity quests in a cluster. for k1, pinA in ipairs(self.activePins) do - for k2, pinB in ipairs(self.activePins) do - if (pinA ~= pinB) then - local aX, aY = pinA:GetNudgedPosition(); - local bX, bY = pinB:GetNudgedPosition(); - local distanceSquared = SquaredDistanceBetweenPoints(aX, aY, bX, bY); - if (distanceSquared < maxOverlap * maxOverlap) then - - local distance = math.sqrt(distanceSquared); - local halfDifference = (maxOverlap - distance)/2; - local vec; - if (distance == 0) then - -- Same position, have to just push them somewhere - vec = CreateVector2D(1, 0); - else - -- Push them away from the other pin - vec = CreateVector2D(aX, aY, bX, bY); - vec:Normalize(); + if (not clusterdLookup[k1]) then + for k2, pinB in ipairs(self.activePins) do + if (pinA ~= pinB) then + local aX, aY = pinA:GetNudgedPosition(); + local bX, bY = pinB:GetNudgedPosition(); + local distanceSquared = SquaredDistanceBetweenPoints(aX, aY, bX, bY); + if (distanceSquared < clusterDistance * clusterDistance) then + if (not cluster) then + cluster = {pinA, pinB} + else + tinsert(cluster, pinB); + end + clusterdLookup[k1] = true; + clusterdLookup[k2] = true; + + local centerX, centerY = 0, 0; + for k, pin in ipairs(cluster) do + local pinX, pinY = pin:GetPosition(); + centerX = centerX + pinX; + centerY = centerY + pinY; + end + centerX = centerX / #cluster; + centerY = centerY / #cluster; + + for k, pin in ipairs(cluster) do + pin:SetNudge(centerX, centerY); + end + end - vec:ScaleBy(halfDifference); - pinA:SetNudge(vec:GetXY()) - -- Push the second one in the opposite direction - vec:ScaleBy(-1); - pinB:SetNudge(vec:GetXY()); end end + if (cluster) then + tinsert(clusters, cluster); + cluster = nil; + end end end + + -- Spread out the quests in each cluster in a circle around the center point + local mapID = canvas:GetParent().mapID; + for kC, pins in ipairs(clusters) do + local centerX, centerY = pins[1]:GetNudgedPosition(); + -- Keep pins in relatively the same localtion. This will make it so 2 pins don't switch positions once clustered + table.sort(pins, function(a, b) + local aX, aY = a:GetPosition(); + local bX, bY = b:GetPosition(); + -- Don't calculate same position or missing position + if (not aX or not bX or (aX == bX and aY == bY)) then + return a.questId < b.questId; + end + + -- Keep in mind Y axis is inverse + local degA = math.deg(math.atan2((centerY - aY), (aX-centerX))); + local degB = math.deg(math.atan2((centerY - bY), (bX-centerX))); + degA = degA < 0 and degA+360 or degA; + degB = degB < 0 and degB+360 or degB; + return degA < degB; + end); + -- Get the rotation of the first pin. This is where we start placing them on the circle + local firstX, firstY = pins[1]:GetPosition(); + local startAngle = math.deg(math.atan2((centerY - firstY), (firstX-centerX))); + + -- Place every pin at aqual distance + for kP, pin in ipairs(pins) do + local angle = -startAngle - (kP-1) * (360 / #pins); + local offsetX = cos(angle) * clusterSpread; + local offsetY = sin(angle) * clusterSpread * canvasRatio; + pin:SetNudge(centerX + offsetX, centerY + offsetY); + end + end + + -- Sort pins to place them like dragon scales (lower is more in front) + table.sort(self.activePins, SortPinsByMapPos); + for k, pin in ipairs(self.activePins) do + pin.index = k; + pin:UpdatePlacement(); + end + + wipe(self.pinClusters); + wipe(self.pinClusterLookup); end function WQT_PinDataProvider:UpdateAllPlacements() @@ -223,7 +316,7 @@ function WQT_PinDataProvider:UpdateQuestPings() if (fadeOthers) then for pin in self.pinPool:EnumerateActive() do - if (not self.pingedQuests[pin.questID])then + if (not self.pingedQuests[pin.questId])then pin:FadeOut(); end end @@ -240,7 +333,7 @@ function WQT_PinDataProvider:UpdateQuestPings() end for pin in self.pinPool:EnumerateActive() do - if (not self.pingedQuests[pin.questID])then + if (not self.pingedQuests[pin.questId])then if (pin.isFaded) then pin:FadeIn(); end @@ -251,9 +344,9 @@ function WQT_PinDataProvider:UpdateQuestPings() end end -function WQT_PinDataProvider:SetQuestIDPinged(questID, shouldPing) - if (not questID) then return; end - self.pingedQuests[questID] = shouldPing or nil; +function WQT_PinDataProvider:SetQuestIDPinged(questId, shouldPing) + if (not questId) then return; end + self.pingedQuests[questId] = shouldPing or nil; -- Official pins if (WQT_Utils:GetSetting("pin", "disablePoI")) then @@ -261,13 +354,13 @@ function WQT_PinDataProvider:SetQuestIDPinged(questID, shouldPing) if (WorldMapFrame:IsShown()) then local WQProvider = WQT_Utils:GetMapWQProvider(); if (WQProvider) then - WQProvider:PingQuestID(questID); + WQProvider:PingQuestID(questId); end end if (FlightMapFrame and FlightMapFrame:IsShown()) then local FlightWQProvider = WQT_Utils:GetFlightWQProvider(); if (FlightWQProvider) then - FlightWQProvider:PingQuestID(questID); + FlightWQProvider:PingQuestID(questId); end end @@ -276,7 +369,7 @@ function WQT_PinDataProvider:SetQuestIDPinged(questID, shouldPing) -- Custom pins for pin in self.pinPool:EnumerateActive() do - if (pin.questID == questID) then + if (pin.questId == questId) then if (shouldPing) then pin:Focus(true); else @@ -366,7 +459,7 @@ function WQT_PinMixin:Setup(questInfo, index, x, y, pinType, parentMapFrame) self.index = index; self.questInfo = questInfo; - self.questID = questInfo.questId; + self.questId = questInfo.questId; local scale = WQT_Utils:GetSetting("pin", "scale") local _, _, _, timeStringShort = WQT_Utils:GetQuestTimeString(questInfo); @@ -520,9 +613,10 @@ function WQT_PinMixin:Setup(questInfo, index, x, y, pinType, parentMapFrame) end self.Time:SetPoint("TOP", self, "BOTTOM", 1, timeOffset); + self:ResetNudge(); self.posX = x; self.posY = y; - self.baseFrameLevel = 2200 + (self.index or 0); + self.baseFrameLevel = PIN_FRAME_LEVEL_BASE; self:UpdatePinTime(); self:UpdatePlacement(); @@ -535,14 +629,13 @@ function WQT_PinMixin:OnUpdate(elapsed) self.updateTime = self.updateTime - self.updateInterval; local timeLeft = self:UpdatePinTime(); - -- For the last minute we want to update every second for the time label self.updateInterval = timeLeft > SECONDS_PER_MIN * 16 and 60 or 1; end function WQT_PinMixin:UpdatePinTime() local start, total, timeLeft, seconds, color, timeStringShort, timeCategory = WQT_Utils:GetPinTime(self.questInfo); - + if (WQT_Utils:GetSetting("pin", "ringType") == _V["RING_TYPES"].time) then local r, g, b = color:GetRGB(); local now = time(); @@ -601,7 +694,7 @@ function WQT_PinMixin:UpdatePlacement() self.currentScale = parentScaleFactor; self:ApplyScaledPosition(parentScaleFactor); - self:SetFrameLevel(self.baseFrameLevel); + self:SetFrameLevel(self.baseFrameLevel + self.index); end function WQT_PinMixin:OnEnter() @@ -610,8 +703,8 @@ function WQT_PinMixin:OnEnter() WQT_Utils:ShowQuestTooltip(self, self.questInfo); -- Highlight quest in list - if (self.questID ~= WQT_QuestScrollFrame.PoIHoverId) then - WQT_QuestScrollFrame.PoIHoverId = self.questID; + if (self.questId ~= WQT_QuestScrollFrame.PoIHoverId) then + WQT_QuestScrollFrame.PoIHoverId = self.questId; WQT_QuestScrollFrame:DisplayQuestList(); end end @@ -628,20 +721,20 @@ end function WQT_PinMixin:OnClick(button) if (button == "LeftButton") then - if ( not ChatEdit_TryInsertQuestLinkForQuestID(self.questID) ) then + if ( not ChatEdit_TryInsertQuestLinkForQuestID(self.questId) ) then PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON); if (IsShiftKeyDown()) then - if (IsWorldQuestHardWatched(self.questID) or (IsWorldQuestWatched(self.questID) and GetSuperTrackedQuestID() == self.questID)) then - BonusObjectiveTracker_UntrackWorldQuest(self.questID); + if (IsWorldQuestHardWatched(self.questId) or (IsWorldQuestWatched(self.questId) and GetSuperTrackedQuestID() == self.questId)) then + BonusObjectiveTracker_UntrackWorldQuest(self.questId); else - BonusObjectiveTracker_TrackWorldQuest(self.questID, true); + BonusObjectiveTracker_TrackWorldQuest(self.questId, true); end else - if (IsWorldQuestHardWatched(self.questID)) then - SetSuperTrackedQuestID(self.questID); + if (IsWorldQuestHardWatched(self.questId)) then + SetSuperTrackedQuestID(self.questId); else - BonusObjectiveTracker_TrackWorldQuest(self.questID); + BonusObjectiveTracker_TrackWorldQuest(self.questId); end end end @@ -658,14 +751,15 @@ end function WQT_PinMixin:ApplyScaledPosition(manualScale) local canvas = self:GetParent(); local scale = manualScale or self.scale / canvas:GetScale(); - local posX = (canvas:GetWidth() * (self.posX + self.nudgeX))/scale; - local posY = -(canvas:GetHeight() * (self.posY + self.nudgeY))/scale; + local posX, posY = self:GetNudgedPosition(); + posX = (canvas:GetWidth() * posX)/scale; + posY = -(canvas:GetHeight() * posY)/scale; self:ClearAllPoints(); self:SetPoint("CENTER", canvas, "TOPLEFT", posX, posY); end function WQT_PinMixin:Focus(playPing) - if (not self.questID) then return; end + if (not self.questId) then return; end local canvas = self:GetParent(); local parentScaleFactor = self.scale / canvas:GetScale(); @@ -687,11 +781,12 @@ function WQT_PinMixin:Focus(playPing) self.ringAnim2:Play(); end - self:SetFrameLevel(3000 + self.index); + self.baseFrameLevel = PIN_FRAME_LEVEL_FOCUS; + self:UpdatePlacement(); end function WQT_PinMixin:ClearFocus() - if (not self.questID) then return; end + if (not self.questId) then return; end self:SetAlpha(self.currentAlpha); self:SetScale(self.currentScale); self:SetShown(self.currentAlpha > 0.05); @@ -704,7 +799,8 @@ function WQT_PinMixin:ClearFocus() self.ringAnim:Stop(); self.ringAnim2:Stop(); end - self:SetFrameLevel(self.baseFrameLevel); + self.baseFrameLevel = PIN_FRAME_LEVEL_BASE; + self:UpdatePlacement(); end function WQT_PinMixin:FadeIn() @@ -729,11 +825,24 @@ function WQT_PinMixin:FadeOut() end end +function WQT_PinMixin:ResetNudge() + self.nudgeX = nil; + self.nudgeY = nil; +end + +function WQT_PinMixin:GetPosition() + return self.posX, self.posY; +end + function WQT_PinMixin:GetNudgedPosition() - return self.posX + self.nudgeX, self.posY + self.nudgeY; + if (self.nudgeX and self.nudgeY)then + + return self.nudgeX, self.nudgeY; + end + return self:GetPosition(); end function WQT_PinMixin:SetNudge(x, y) - self.nudgeX = self.nudgeX + x; - self.nudgeY = self.nudgeY + y; + self.nudgeX = x; + self.nudgeY = y; end diff --git a/Settings.lua b/Settings.lua index 3467138..d1ac41e 100644 --- a/Settings.lua +++ b/Settings.lua @@ -275,6 +275,7 @@ function WQT_SettingsSliderMixin:OnValueChanged(value, userInput) self:UpdateState(); return; end + value = Round(value*100)/100; value = min(self.max, max(self.min, value)); if (userInput and value ~= self.current) then diff --git a/Templates.xml b/Templates.xml index 6030be8..9bb7a02 100644 --- a/Templates.xml +++ b/Templates.xml @@ -4,6 +4,116 @@