Quantcast

Refactoring of corpse and objective code

James Whitehead II [01-09-11 - 16:08]
Refactoring of corpse and objective code

Waypoints should be provided by a waypoint source, which will eventually
be filtered. Priorities should be used to deteremine which waypoints
should be shown, allowing the user to control this themselves.
Filename
AddonCore.lua
DatabaseDefaults.lua
MapUtils.lua
TomTomLite.lua
TomTomLite.toc
sources/ArchaeologyDigSites.lua
sources/Corpse.lua
sources/QuestObjectives.lua
diff --git a/AddonCore.lua b/AddonCore.lua
index a765133..aa37464 100644
--- a/AddonCore.lua
+++ b/AddonCore.lua
@@ -127,6 +127,16 @@ end
 --  Setup Initialize/Enable support
 -------------------------------------------------------------------------]]--

+local initFuncs = {}
+function addon:RunAtInitialize(func)
+    initFuncs[#initFuncs + 1] = func
+end
+
+local enableFuncs = {}
+function addon:RunAtEnable(func)
+    enableFuncs[#enableFuncs + 1] = func
+end
+
 addon:RegisterEvent("PLAYER_LOGIN", "Enable")
 addon:RegisterEvent("ADDON_LOADED", function(event, ...)
     if ... == addonName then
@@ -135,10 +145,20 @@ addon:RegisterEvent("ADDON_LOADED", function(event, ...)
             addon["Initialize"](addon)
         end

+        -- Run any registered init functions
+        for idx, func in ipairs(initFuncs) do
+            func()
+        end
+
         -- If this addon was loaded-on-demand, trigger 'Enable' as well
         if IsLoggedIn() and type(addon["Enable"]) == "function" then
             addon["Enable"](addon)
         end
+
+        -- Run any registered enable functions
+        for idx, func in ipairs(enableFuncs) do
+            func()
+        end
     end
 end)

diff --git a/DatabaseDefaults.lua b/DatabaseDefaults.lua
index 20c08a0..d9b3eaf 100644
--- a/DatabaseDefaults.lua
+++ b/DatabaseDefaults.lua
@@ -10,6 +10,8 @@ addon.defaults = {
         showMapIconsZone = false,
         showMapIconsContinent = false,

+        corpseArrow = true,
+
         goodcolor = {0, 1, 0},
         badcolor = {1, 0, 0},
         middlecolor = {1, 1, 0},
diff --git a/MapUtils.lua b/MapUtils.lua
index 1edecb5..cc68db7 100644
--- a/MapUtils.lua
+++ b/MapUtils.lua
@@ -34,7 +34,7 @@ end
 -- return nil

 function addon:GetVector(sm, sf, sx, sy, dm, df, dx, dy)
-    if smap == dmap and sfloor == dfloor then
+    if sm == dm and sf == df then
         -- The waypoints are on the same map, so calculate directly using map data

         if mapdata then
@@ -74,17 +74,45 @@ function addon:GetVector(sm, sf, sx, sy, dm, df, dx, dy)
     end
 end

+-- Returns the player's current map, floor and position. This information isn't
+-- directly available if the world map is open and the player has navigated to
+-- another map.
+
+function addon:GetPlayerPosition()
+    -- Attempt to get the player's position on the current map
+    local x, y = GetPlayerMapPosition("player")
+    if x and y and x > 0 and y > 0 then
+        local map, floor = GetCurrentMapAreaID()
+        return map, floor, x, y
+    end
+
+    -- At this point, we were unable to get the position information
+    if WorldMapFrame:IsVisible() then
+        -- The map is open and we cannot change the map being displayed
+        return
+    end
+
+    -- Flip the map to the current zone and get the position. We do not change
+    -- the map zoom back at the end of this function to avoid getting into any
+    -- nasty race conditions, it's bad enough that we need to set it currently.
+    SetMapToCurrentZone()
+    local x, y = GetPlayerMapPosition("player")
+
+    if x <= 0 and y <= 0 then
+        -- Coordinate information not available for wherever the player is
+        return
+    end
+
+    -- Fetch the map and floor and return the information that we have
+    local map, floor = GetCurrentMapAreaID()
+    return map, floor, x, y
+end
+
 -- Get the distance (in yards) and angle (in radians) from the player's current
 -- position to a position on the map.

 function addon:GetVectorFromCurrent(map, floor, x, y)
-
-    -- First we need to obtain the player's current position. Attempt, at all
-    -- costs to do this without changing the map zoom, as it shouldn't be
-    -- necessary.
-
-    local cmap, cfloor = GetCurrentMapAreaID()
-    local cx, cy = GetPlayerMapPosition("player")
+    local cmap, cfloor, cx, cy = self:GetPlayerPosition()

     if map and floor and x and y then
         return self:GetVector(cmap, cfloor, cx, cy, map, floor, x, y)
@@ -115,11 +143,11 @@ local continents = {
     [751] = 5,                   -- Maelstrom (5)

     -- Map from continentIndex to mapId
-    [1] = 13,
-    [2] = 14,
-    [3] = 466,
-    [4] = 485,
-    [5] = 751,
+    [1] = 13,                    -- Kalimdor (13)
+    [2] = 14,                    -- Eastern Kingdoms (14)
+    [3] = 466,                   -- Outland (466)
+    [4] = 485,                   -- Northrend (485)
+    [5] = 751,                   -- Maelstrom (751)
 }

 -- Returns if a given map file is a continent map
@@ -137,3 +165,13 @@ function addon:GetMapContinentMap(map)
     elseif astrolabe then
     end
 end
+
+function addon:GetNumMapFloors(map)
+    if mapdata then
+        local floors = mapdata:MapFloors(map) == 0 and 0 or 1
+        return floors
+    elseif astrolabe then
+        local floors = astrolabe:GetNumFloors(map) == 0 and 0 or 1
+        return floors
+    end
+end
diff --git a/TomTomLite.lua b/TomTomLite.lua
index 110d7fa..1939222 100644
--- a/TomTomLite.lua
+++ b/TomTomLite.lua
@@ -57,15 +57,8 @@ function addon:Initialize()
     self.arrow:SetPoint("CENTER", 0, 0)
     self.arrow:Hide()

-    -- Events for objective tracking
-    self:RegisterEvent("QUEST_POI_UPDATE", "OBJECTIVES_CHANGED")
-    self:RegisterEvent("QUEST_LOG_UPDATE", "OBJECTIVES_CHANGED")
-    self:RegisterMessage("OBJECTIVES_CHANGED")
-    hooksecurefunc("WatchFrame_Update", function(self)
-        addon:FireMessage("OBJECTIVES_CHANGED")
-    end)
-
     self:RegisterMessage("TOMTOMLITE_WAYPOINT_ADDED")
+    self:RegisterMessage("TOMTOMLITE_WAYPOINTS_CHANGED")

     -- Events for world map overlays
     self:RegisterEvent("WORLD_MAP_UPDATE")
@@ -76,10 +69,15 @@ function addon:CreateCrazyArrow(name, parent)
     local frame = CreateFrame("Button", name, parent)

     frame:SetSize(128, 128)
-    frame.arrow = frame:CreateTexture("OVERLAY")
+    frame.arrow = frame:CreateTexture(name .. "Icon", "BACKGROUND")
     frame.arrow:SetAllPoints()
     frame.arrow:SetTexture("Interface\\Addons\\TomTomLite\\images\\BevArrow")

+    frame.glow = frame:CreateTexture(name .. "Glow", "OVERLAY")
+    frame.glow:SetAllPoints()
+    frame.glow:SetTexture("Interface\\Addons\\TomTomLite\\images\\BevArrowGlow")
+    frame.glow:SetVertexColor(1.0, 0.8, 0.0)
+
     frame.title = frame:CreateFontString("OVERLAY", name .. "Title", "GameFontHighlight")
     frame.info = frame:CreateFontString("OVERLAY", name .. "Info", "GameFontHighlight")
     frame.subtitle = frame:CreateFontString("OVERLAY", name .. "Subtitle", "GameFontHighlight")
@@ -109,6 +107,31 @@ function addon:CreateCrazyArrow(name, parent)
         self.arrow:SetVertexColor(r,g,b)
         self.arrow:SetRotation(faceangle)

+        -- This code is not quite correct, needs to be 'fixed'
+
+        local lowlimit = 20.0
+        local highlimit = 360.0 - lowlimit
+
+        local angle = math.abs(deg(faceangle)) % 360
+
+        if angle <= lowlimit then
+            -- Determine what alpha to show
+            local perc = angle / lowlimit
+
+            self.glow:Show()
+            self.glow:SetRotation(faceangle)
+            self.glow:SetAlpha(1.0 - perc)
+        elseif angle >= highlimit then
+            -- Determine what alpha to show
+            local perc = angle / (360 - lowlimit)
+
+            self.glow:Show()
+            self.glow:SetRotation(faceangle)
+            self.glow:SetAlpha(1.0 - perc)
+        else
+            self.glow:Hide()
+        end
+
         self.subtitle:SetFormattedText("%.1f yards", distance)
     end)

@@ -127,10 +150,14 @@ end

 function addon:AddWaypoint(map, floor, x, y, opt)
     assert(type(map) == "number")
-    assert(type(floor) == "number")
+    assert(type(floor) == "number" or floor == nil)
     assert(type(x) == "number")
     assert(type(y) == "number")

+    if floor == nil then
+        floor = addon:GetNumMapFloors(map)
+    end
+
     local waypoint = {map, floor, x, y}
     if type(opt) == "table" then
         for k, v in pairs(opt) do
@@ -146,10 +173,10 @@ function addon:AddWaypoint(map, floor, x, y, opt)
     return waypoint
 end

-function addon:DeleteWaypoint(waypoint)
+function addon:RemoveWaypoint(waypoint)
     for idx, entry in ipairs(self.waypoints) do
         if entry == waypoint then
-            table.remove(self.waypoints, waypoint)
+            table.remove(self.waypoints, idx)
             break
         end
     end
@@ -161,42 +188,37 @@ end
 --  Private implementation
 -------------------------------------------------------------------------]]--
 function addon:TOMTOMLITE_WAYPOINT_ADDED(msg, waypoint, ...)
-    self:UpdateArrow()
+    self:FireMessage("TOMTOMLITE_WAYPOINTS_CHANGED")
+    self:Printf("Waypoint '%s' added at %.2f, %.2f", waypoint.title, waypoint[3], waypoint[4])
+end
+
+function addon:TOMTOMLITE_WAYPOINT_DELETED(msg, waypoint, ...)
+    self:FireMessage("TOMTOMLITE_WAYPOINTS_CHANGED")
+    self:Printf("Waypoint '%s' REMOVED at %.2f, %.2f", waypoint.title, waypoint[3], waypoint[4])
 end

-function addon:OBJECTIVES_CHANGED()
-    self:UpdateQuestObjectives()
+function addon:TOMTOMLITE_WAYPOINTS_CHANGED(msg, ...)
     self:UpdateArrow()
 end

 function addon:UpdateArrow()
-    -- local cmap, cfloor = GetCurrentMapAreaID()
-    -- local cx, cy = GetPlayerMapPosition("player")
-
-    -- -- Scan the current waypoints and determine which one is closest, regardless
-    -- -- of which zone the waypoint is in. This could be altered to only consider
-    -- -- waypoints in the current zone, quite easily.
-    -- local mindist = math.huge
-    -- local closest
-
-    -- for idx, waypoint in ipairs(self.waypoints) do
-    --     local map, floor, x, y = unpack(waypoint)
-    --     local distance = addon.mapdata:DistanceWithinContinent(cmap, cfloor or 0, cx, cy, map, floor, x, y)
-    --     if distance < mindist then
-    --         mindist = distance
-    --         closest = waypoint
-    --     end
-    -- end
-
-    local closest = self.arrow.waypoint
-
-    if closest then
-        -- Set the crazy arrow to display this waypoint
-        local zone, floor, x, y = unpack(closest)
+    -- This naive sort function will sort all waypoints so the highest
+    -- priority waypoint is first. This is the waypoint that will be
+    -- displayed on the arrow.
+
+    table.sort(self.waypoints, function(a, b)
+        local apri = a.priority or 0
+        local bpri = b.priority or 0
+        return bpri < apri
+    end)
+
+    local highest = self.waypoints[1]
+    if highest then
+        local zone, floor, x, y = unpack(highest)
         local lzone = self:GetMapDisplayName(zone)

-        self.arrow.waypoint = closest
-        self.arrow.title:SetText(closest.title or L["Unknown waypoint"])
+        self.arrow.waypoint = highest
+        self.arrow.title:SetText(highest.title or L["Unknown waypoint"])
         self.arrow.info:SetFormattedText("%.2f, %.2f - %s", x * 100, y * 100, lzone)
         self.arrow:Show()
     else
@@ -205,70 +227,6 @@ function addon:UpdateArrow()
     end
 end

-local objectiveWaypoints = {}
-function addon:UpdateQuestObjectives()
-    local map, floor = GetCurrentMapAreaID()
-    floor = floor or 0
-
-    -- Do not set any NEW waypoints if we're on the continent map
-    if self:IsContinentMap(map) then
-        return
-    end
-
-    local cvar = GetCVarBool("questPOI")
-    SetCVar("questPOI", 1)
-
-    -- Only do an objective scan if the option is enabled
-    if not self.db.profile.trackQuestObjectives then
-        return
-    end
-
-    QuestPOIUpdateIcons()
-
-    -- Scan through every quest that is being tracked, and create a waypoint
-    -- for each of the objectives that are being tracked. These waypoints will
-    -- be unordered, and will be sorted or ordered by the user/algorithm
-    local closest
-    local watchIndex = 1
-    while true do
-        local questIndex = GetQuestIndexForWatch(watchIndex)
-        if not questIndex then
-            break
-        end
-
-        local title = GetQuestLogTitle(questIndex)
-        local qid = select(9, GetQuestLogTitle(questIndex))
-        local completed, x, y, objective = QuestPOIGetIconInfo(qid)
-
-        if x and y then
-            -- For two waypoints to be equal, their map, floor, x, y should all be
-            -- the same, as well as the title and completion set.
-            local key = qid + (x * 100) * (y * 100) * map * (floor + 1)
-            if not objectiveWaypoints[key] then
-                if completed then
-                    title = "Turn in: " .. title
-                end
-
-                local waypoint = self:AddWaypoint(map, floor, x, y, {title = title})
-                objectiveWaypoints[key] = waypoint
-                if not closest then
-                    closest = objectiveWaypoints[key]
-                end
-            else
-                if not closest then
-                    closest = objectiveWaypoints[key]
-                end
-            end
-        end
-
-        watchIndex = watchIndex + 1
-    end
-
-    self.arrow.waypoint = closest
-
-    SetCVar("questPOI", cvar and 1 or 0)
-end
-
 --[[-------------------------------------------------------------------------
 --  World map support, displaying waypoint overlays
 -------------------------------------------------------------------------]]--
@@ -323,9 +281,6 @@ function addon:WORLD_MAP_UPDATE()
         return
     end

-    -- TODO: Handle 'showMapIconsContinent' option
-
-    local continent = GetCurrentMapContinent()
     local map, floor = GetCurrentMapAreaID()
     local waypoints = addon.waypointsByMap[map]

diff --git a/TomTomLite.toc b/TomTomLite.toc
index ae036a0..7449561 100755
--- a/TomTomLite.toc
+++ b/TomTomLite.toc
@@ -22,3 +22,7 @@ Utils.lua
 DatabaseDefaults.lua

 TomTomLite.lua
+
+sources\ArchaeologyDigSites.lua
+sources\Corpse.lua
+sources\QuestObjectives.lua
diff --git a/sources/ArchaeologyDigSites.lua b/sources/ArchaeologyDigSites.lua
new file mode 100644
index 0000000..e69de29
diff --git a/sources/Corpse.lua b/sources/Corpse.lua
new file mode 100644
index 0000000..3d28b02
--- /dev/null
+++ b/sources/Corpse.lua
@@ -0,0 +1,133 @@
+local addonName, addon = ...
+local L = addon.L
+
+--[[-------------------------------------------------------------------
+--  Corpse arrow module for TomTomLite, example reference module
+-------------------------------------------------------------------]]--
+
+local eventFrame = CreateFrame("Frame")
+eventFrame:RegisterEvent("ADDON_LOADED")
+eventFrame:RegisterEvent("PLAYER_ALIVE")
+eventFrame:RegisterEvent("PLAYER_DEAD")
+eventFrame:RegisterEvent("PLAYER_UNGHOST")
+eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
+eventFrame:Hide()
+
+local waypoint
+local map, floor, x, y
+
+local function GetCorpseLocation()
+    if map and x and y then
+        return map, floor, x, y
+    end
+
+    local oldmap, oldfloor = GetCurrentMapAreaID()
+
+    local cont
+    -- Scan the all of the continent maps in order to find the corpse arrow
+    for i=1, select("#", GetMapContinents()) do
+        SetMapZoom(i)
+        local cx, cy = GetCorpseMapPosition()
+        if cx ~= 0 and cy ~= 0 then
+            cont = i
+            break
+        end
+    end
+
+	-- If we found the corpse on a continent, find out which zone it is in
+	if cont and cont ~= -1 then
+		for i = 1, select("#", GetMapZones(cont)) do
+			SetMapZoom(cont, i)
+			local cx, cy = GetCorpseMapPosition()
+			if cx > 0 and cy > 0 then
+                map, floor = GetCurrentMapAreaID()
+                x, y = cx, cy
+                break
+			end
+		end
+	end
+
+	-- Restore the map to its previous map
+    SetMapByID(oldmap, oldfloor)
+
+    if map and x and y then
+        return map, floor, x, y
+    else
+        -- Now handle the case where the corpse is on the current map
+        local cx, cy = GetCorpseMapPosition()
+        if cx ~= 0 and cy ~= 0 then
+            map, floor = oldmap, oldfloor
+            x, y = cx, cy
+            return map, floor, x, y
+        end
+    end
+end
+
+local function SetCorpseArrow()
+    if map and x and y and x > 0 and y > 0 then
+        waypoint = addon:AddWaypoint(map, floor, x, y, {
+            title = "Your corpse",
+            priority = 100,
+        })
+        return waypoint
+	end
+end
+
+local function StartCorpseSearch()
+	if not IsInInstance() then
+		eventFrame:Show()
+	end
+end
+
+local function ClearCorpseArrow()
+	if waypoint then
+		addon:RemoveWaypoint(waypoint)
+
+        waypoint = nil
+        map, floor, x, y = nil, nil, nil, nil
+	end
+end
+
+local counter, throttle = 0, 0.5
+eventFrame:SetScript("OnUpdate", function(self, elapsed)
+	counter = counter + elapsed
+	if counter < throttle then
+		return
+	else
+		counter = 0
+		if addon.db.profile.corpseArrow then
+			if GetCorpseLocation() then
+				if SetCorpseArrow() then
+					self:Hide()
+				end
+			end
+		else
+			self:Hide()
+		end
+	end
+end)
+
+eventFrame:SetScript("OnEvent", function(self, event, arg1, ...)
+	if event == "ADDON_LOADED" and arg1 == addonName then
+		self:UnregisterEvent("ADDON_LOADED")
+		if UnitIsDeadOrGhost("player") then
+			StartCorpseSearch()
+		end
+	end
+
+	if event == "PLAYER_ALIVE" or event == "PLAYER_ENTERING_WORLD" then
+		if UnitIsDeadOrGhost("player") then
+			StartCorpseSearch()
+		else
+			ClearCorpseArrow()
+		end
+	elseif event == "PLAYER_DEAD" then
+		StartCorpseSearch()
+	elseif event == "PLAYER_UNGHOST" then
+		ClearCorpseArrow()
+	end
+end)
+
+if IsLoggedIn() then
+	eventFrame:GetScript("OnEvent")(eventFrame, "ADDON_LOADED", addonName)
+end
diff --git a/sources/QuestObjectives.lua b/sources/QuestObjectives.lua
new file mode 100644
index 0000000..f670c71
--- /dev/null
+++ b/sources/QuestObjectives.lua
@@ -0,0 +1,134 @@
+local addonName, addon = ...
+local L = addon.L
+
+--[[-------------------------------------------------------------------
+--  Quest objective waypoint source for TomTomLite
+--
+--  Anytime the quest log or objective POI information changes, the
+--  objective tracker is scanned for all objectives and waypoints are
+--  created for each of them. The priority of the first waypoint, i.e.
+--  the first one in the objectives tracker, is set higher than the
+--  rest. This of course can be overridden by the user
+-------------------------------------------------------------------]]--
+
+local PRI_FIRST = 15
+local PRI_OTHER = 0
+
+local eventFrame = CreateFrame("Frame")
+eventFrame:RegisterEvent("QUEST_POI_UPDATE")
+eventFrame:RegisterEvent("QUEST_LOG_UPDATE")
+hooksecurefunc("WatchFrame_Update", function(self)
+    addon:OBJECTIVES_CHANGED()
+end)
+
+eventFrame:SetScript("OnEvent", function(self, event, ...)
+    if event == "QUEST_POI_UPDATE" then
+        addon:OBJECTIVES_CHANGED()
+    elseif event == "QUEST_LOG_UPDATE" and (...) == "player" then
+        addon:OBJECTIVES_CHANGED()
+    end
+end)
+
+-- This function scans the current set of objectives, adding and removing
+-- waypoints to reflect the current state of the tracker.
+
+local waypoints = {}
+function addon:OBJECTIVES_CHANGED()
+    local map, floor = GetCurrentMapAreaID()
+    floor = floor or 0
+
+    -- Do not set any NEW waypoints if we're on the continent map
+    if self:IsContinentMap(map) then
+        return
+    end
+
+    local cvar = GetCVarBool("questPOI")
+    SetCVar("questPOI", 1)
+
+    -- Only do an objective scan if the option is enabled
+    if not self.db.profile.trackQuestObjectives then
+        return
+    end
+
+    local newWaypoints = {}
+    local changed = false
+
+    -- This function relies on the above CVar being set, and updates the icon
+    -- position information so it can be queries via the API
+    QuestPOIUpdateIcons()
+
+    -- Scan through every quest that is being tracked, and create a waypoint
+    -- for each of the objectives that are being tracked. These waypoints will
+    -- be unordered, and will be sorted or ordered by the user/algorithm
+    local watchIndex = 1
+    while true do
+        local questIndex = GetQuestIndexForWatch(watchIndex)
+        if not questIndex then
+            break
+        end
+
+        local title = GetQuestLogTitle(questIndex)
+        local qid = select(9, GetQuestLogTitle(questIndex))
+        local completed, x, y, objective = QuestPOIGetIconInfo(qid)
+
+        if x and y then
+            -- Try to uniquely identify objective waypoints using qid, x, y along
+            -- with map and floor. This may not always work, but its the best we
+            -- can do right now. This allows us to ensure we don't double-set a
+            -- waypoint.
+
+            if completed then
+                title = "Turn in: " .. title
+            end
+
+            local key = title .. tostring(qid + (x * 100) * (y * 100) * map * (floor + 1))
+
+            if waypoints[key] then
+                -- This waypoint already exists, no need to do anything, except
+                -- possibly change the priority
+
+                local waypoint = waypoints[key]
+                local newpri = (watchIndex == 1) and PRI_FIRST or PRI_OTHER
+                if waypoint.priority ~= newpri then
+                    changed = true
+                    waypoint.priority = newpri
+                end
+
+                newWaypoints[key] = waypoint
+            else
+                -- Create the waypoint, setting priority
+                local waypoint = self:AddWaypoint(map, floor, x, y, {
+                    title = title,
+                    priority = (watchIndex == 1) and PRI_FIRST or PRI_OTHER,
+                })
+
+                newWaypoints[key] = waypoint
+            end
+        end
+
+        watchIndex = watchIndex + 1
+    end
+
+    SetCVar("questPOI", cvar and 1 or 0)
+
+    -- Check to see if there are any waypoints that are in 'waypoints' but
+    -- not in 'newWaypoints' so we can remove them. Additionally, we may have
+    -- changed a waypoint's priority during the scan, so trigger an update in
+    -- that case.
+
+    for k, waypoint in pairs(newWaypoints) do
+        waypoints[k] = nil
+    end
+
+    for k, waypoint in pairs(waypoints) do
+        -- This waypoint is no longer being tracked
+        addon:RemoveWaypoint(waypoint)
+    end
+
+    -- Swap the arrays
+    waypoints = newWaypoints
+
+    if changed then
+        addon:FireMessage("TOMTOMLITE_WAYPOINTS_CHANGED")
+    end
+end