only1yzerman@0: only1yzerman@0: -------- SINGLE OBJECT IN GLOBAL NAMESPACE only1yzerman@0: only1yzerman@0: Bloodhound2 = {}; only1yzerman@0: only1yzerman@0: only1yzerman@0: -------- CREATE OUR INVISIBLE FRAME TO RECEIVE EVENTS AND UPDATES only1yzerman@0: only1yzerman@0: local frame = CreateFrame("Frame", nil, UIParent); only1yzerman@0: Bloodhound2.Frame = frame; only1yzerman@0: only1yzerman@0: only1yzerman@0: -------- DEFINE AND REGISTER EVENT HANDLERS only1yzerman@0: only1yzerman@0: local events = {}; only1yzerman@0: only1yzerman@0: function events:MINIMAP_UPDATE_TRACKING(...) only1yzerman@0: if player and player.Z then only1yzerman@0: Bloodhound2.UpdateMinimap(); only1yzerman@0: end only1yzerman@0: end only1yzerman@0: only1yzerman@0: frame:SetScript("OnEvent", only1yzerman@0: function(self, event, ...) only1yzerman@0: events[event](self, ...); only1yzerman@0: end); only1yzerman@0: only1yzerman@0: local k, v; only1yzerman@0: only1yzerman@0: for k, v in pairs(events) do only1yzerman@0: frame:RegisterEvent(k); only1yzerman@0: end only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: ---------------------------------------------------------------------------------------------------- only1yzerman@0: -------- STORES A POINTER TO THE MINIMAP LOCALLY SO IT CAN BE CHANGED BY A RE-PARENTING FUNCTION only1yzerman@0: -------- - Added by Zasurus(www.curseforge.com/profiles/Zasurus/) for MiniHugeHUD only1yzerman@0: ---------------------------------------------------------------------------------------------------- only1yzerman@0: local Minimap = _G.Minimap; only1yzerman@0: Minimap.MinimapName = "Minimap"; only1yzerman@0: ---------------------------------------------------------------------------------------------------- only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: -------- WORK AROUND WOW BUG THAT CAUSES GetPlayerMapPosition TO RETURN 0,0 only1yzerman@0: only1yzerman@0: WorldMapFrame:Show() only1yzerman@0: WorldMapFrame:Hide() only1yzerman@0: only1yzerman@0: only1yzerman@0: -------- HOOK INTO PER-FRAME UPDATE HANDLER only1yzerman@0: only1yzerman@0: local player = {}; -- player position only1yzerman@0: local totalElapsed = 0.0; -- time elapsed since last update only1yzerman@0: local updatePeriod = 0.1; -- sec. interval for each minimap update only1yzerman@0: local cpuThrottle = 0.05; -- throttle to 5% usage while moving only1yzerman@0: local minUpdatePeriod = 0.04; -- sec. don't update more often than this only1yzerman@0: local lastzoom = 0; -- previous minimap zoom level only1yzerman@0: local updateTotalTime = 0; -- time spent updating minimap only1yzerman@0: local updateCount = 0; -- number of times minimap updated only1yzerman@0: only1yzerman@0: ---------------------------------------------------------------------------------------------------- only1yzerman@0: -------- MOVED OnUpdate Script into seperate function so it could be called by re-parent function only1yzerman@0: -------- - Added by Zasurus(www.curseforge.com/profiles/Zasurus/) for MiniHugeHUD only1yzerman@0: ---------------------------------------------------------------------------------------------------- only1yzerman@0: frame:SetScript("OnUpdate", function(self, elapsed) Bloodhound2.OnUpdateScript(self, elapsed) end); only1yzerman@0: function Bloodhound2.OnUpdateScript(self, elapsed) only1yzerman@0: totalElapsed = totalElapsed + elapsed; -- measure total elapsed time only1yzerman@0: only1yzerman@0: if (totalElapsed >= updatePeriod) then -- if enough time has passed only1yzerman@0: totalElapsed = mod(totalElapsed, updatePeriod); only1yzerman@0: local isInInstance, instanceType = IsInInstance() only1yzerman@0: only1yzerman@0: if isInInstance ~= nil then only1yzerman@0: Bloodhound2.DrawArrow(0, 0, 0, 0) only1yzerman@0: return only1yzerman@0: end only1yzerman@0: only1yzerman@0: local cmap = GetCurrentMapContinent(); only1yzerman@0: local zmap = GetCurrentMapZone(); only1yzerman@0: SetMapToCurrentZone(); only1yzerman@0: local c = GetCurrentMapContinent(); -- get current player position only1yzerman@0: local z = GetCurrentMapZone(); only1yzerman@0: local x, y = GetPlayerMapPosition("player"); only1yzerman@0: local f = GetPlayerFacing(); only1yzerman@0: local m = Minimap:GetZoom(); only1yzerman@6: local ztxt = GetZoneText(); only1yzerman@6: local zstxt = GetSubZoneText(); only1yzerman@6: local badzones = { "","The Star's Bazaar", "The Emperor's Step", "The Golden Lantern", "Chamber of Reflection", "The Imperial Exchange", "Path of Serenity", "Ethereal Corridor", "The Celestial Vault", "Chamber of Enlightenment" } only1yzerman@0: SetMapZoom(cmap, zmap); only1yzerman@6: only1yzerman@5: if (c ~= player.C and c ~= 0 and c ~= -1) then -- reload continent data if continent has changed only1yzerman@0: Bloodhound2.HerbNodes, Bloodhound2.ContinentHerbs = Bloodhound2.ReloadNodes(Bloodhound2.HerbDatabase[c], Bloodhound2.ZoneDatum[c]); only1yzerman@0: Bloodhound2.OreNodes, Bloodhound2.ContinentOre = Bloodhound2.ReloadNodes(Bloodhound2.OreDatabase[c], Bloodhound2.ZoneDatum[c]); only1yzerman@2: Bloodhound2.MiscNodes, Bloodhound2.ContinentNode = Bloodhound2.ReloadNodes(Bloodhound2.MiscDatabase[c], Bloodhound2.ZoneDatum[c]); only1yzerman@0: end only1yzerman@6: only1yzerman@0: only1yzerman@0: if (c ~= player.C or z ~= player.Z or x ~= player.X or y ~= player.Y or f ~= player.F or m ~= lastzoom) then only1yzerman@0: player.C = c; only1yzerman@0: player.Z = z; only1yzerman@0: player.X = x; only1yzerman@0: player.Y = y; only1yzerman@0: player.F = f; only1yzerman@0: lastzoom = m; only1yzerman@0: local t0 = GetTime(); only1yzerman@0: Bloodhound2.UpdateMinimap(); -- player has moved or minimap scale has changed only1yzerman@0: local t1 = GetTime() - t0; only1yzerman@0: only1yzerman@0: -- calculate and throttle CPU usage to 5% only1yzerman@0: updateTotalTime = updateTotalTime + t1 only1yzerman@0: only1yzerman@0: if updateCount > 10 then only1yzerman@0: local avgTime = updateTotalTime / updateCount only1yzerman@0: updatePeriod = avgTime / cpuThrottle only1yzerman@0: Print(updateCount); only1yzerman@0: only1yzerman@0: if updatePeriod < minUpdatePeriod then only1yzerman@0: updatePeriod = minUpdatePeriod only1yzerman@0: end only1yzerman@0: only1yzerman@0: if updateCount >= 100 then only1yzerman@0: updateTotalTime = updateTotalTime / 2 only1yzerman@0: updateCount = updateCount / 2 only1yzerman@0: end only1yzerman@0: end only1yzerman@0: end only1yzerman@0: end only1yzerman@0: end only1yzerman@6: function Bloodhound2.BadZone(ztxt, zstxt, badzones) only1yzerman@6: for tbl, value in pairs(badzones) do only1yzerman@6: if (ztxt == "Shrine of Seven Stars" or zstxt == value) then return false only1yzerman@6: end only1yzerman@6: end only1yzerman@6: return true only1yzerman@6: end only1yzerman@0: only1yzerman@0: function Bloodhound2.UpdateMinimap() only1yzerman@0: local facing = 0 only1yzerman@0: only1yzerman@0: if GetCVar("rotateMinimap") ~= "0" then only1yzerman@0: facing = GetPlayerFacing() * 57.2957795; only1yzerman@0: end only1yzerman@0: only1yzerman@0: local cf = cos(facing); only1yzerman@0: local sf = sin(facing); only1yzerman@0: local gx, gy = Bloodhound2.CalculateForce(cf, sf); only1yzerman@0: Bloodhound2.DrawArrow(gx, gy, cf, sf); only1yzerman@0: end only1yzerman@0: only1yzerman@0: only1yzerman@0: -------- DATABASE LOADING only1yzerman@0: only1yzerman@0: Bloodhound2.HerbNodes = {}; only1yzerman@0: Bloodhound2.OreNodes = {}; only1yzerman@2: Bloodhound2.MiscNodes = {}; only1yzerman@0: Bloodhound2.ContinentHerbs = {}; only1yzerman@0: Bloodhound2.ContinentOre = {}; only1yzerman@0: only1yzerman@0: function Bloodhound2.ReloadNodes(database, zoneDatum) only1yzerman@0: only1yzerman@0: if (database == nil) then return {}; end only1yzerman@0: only1yzerman@0: local nodes = {}; only1yzerman@0: local db = {}; only1yzerman@0: only1yzerman@0: local zoneNode, positions; only1yzerman@0: for zoneNode, positions in pairs(database) do only1yzerman@0: local _, _, zone, node = string.find(zoneNode, "Z(%d+)N(%d+)"); only1yzerman@0: local z = tonumber(zone); only1yzerman@0: local datum = zoneDatum[z]; only1yzerman@0: local n = tonumber(node); only1yzerman@0: db[n] = true; only1yzerman@0: local pxy = 0 only1yzerman@0: only1yzerman@0: for _,pos in ipairs(positions) do only1yzerman@0: local xy = pos + pxy only1yzerman@0: pxy = xy only1yzerman@0: local x = floor(xy / 1000); only1yzerman@0: local y = xy - 1000 * x; only1yzerman@0: x, y = Bloodhound2.ZoneDatum.LocalToGlobal(x / 1000.0, y / 1000.0, datum); only1yzerman@0: tinsert(nodes, {Z = z, N = n, X = x, Y = y, TimeStamp = time() - 60 - 3600 * math.random() }); only1yzerman@0: end only1yzerman@0: end only1yzerman@0: only1yzerman@0: return nodes, db; only1yzerman@0: end only1yzerman@0: only1yzerman@0: only1yzerman@0: -------- UTILITY FUNCTIONS only1yzerman@0: only1yzerman@0: function Print(message) only1yzerman@0: DEFAULT_CHAT_FRAME:AddMessage(message); only1yzerman@0: end only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: -------- ARROW LOADING AND DISPLAY only1yzerman@0: only1yzerman@0: local function GetArrowTexture() only1yzerman@0: only1yzerman@0: local tex = Bloodhound2.GravityTexture only1yzerman@0: only1yzerman@0: if not ( tex ) then only1yzerman@1: tex = Minimap:CreateTexture("Bloodhound2_Arrow", "OVERLAY"); only1yzerman@0: tex:SetTexture("Interface\\AddOns\\Bloodhound2\\MinimapArrow"); only1yzerman@0: Bloodhound2.GravityTexture = tex only1yzerman@0: end only1yzerman@0: only1yzerman@0: return tex only1yzerman@0: end only1yzerman@0: only1yzerman@0: function Bloodhound2.DrawArrow(grx, gry, cf, sf) only1yzerman@0: only1yzerman@0: local gravityX = grx * cf - gry * sf; only1yzerman@0: local gravityY = gry * cf + grx * sf; only1yzerman@0: local gravityTexture = GetArrowTexture() only1yzerman@0: only1yzerman@0: if (gravityX == 0 and gravityY == 0) then only1yzerman@0: gravityTexture:Hide(); only1yzerman@0: return; only1yzerman@0: end only1yzerman@0: only1yzerman@0: local gravityScale = sqrt(gravityX * gravityX + gravityY * gravityY) only1yzerman@0: gravityX = gravityX / gravityScale only1yzerman@0: gravityY = gravityY / gravityScale only1yzerman@0: only1yzerman@0: -- determine rotated and scaled texture coordinates only1yzerman@0: local gy = (gravityX + gravityY) / 2.82843 only1yzerman@0: local gx = (gravityX - gravityY) / 2.82843 only1yzerman@0: only1yzerman@0: gravityTexture:SetTexCoord( only1yzerman@0: 0.5 - gx, 0.5 + gy, only1yzerman@0: 0.5 + gy, 0.5 + gx, only1yzerman@0: 0.5 - gy, 0.5 - gx, only1yzerman@0: 0.5 + gx, 0.5 - gy); only1yzerman@0: only1yzerman@0: gravityTexture:SetPoint("CENTER", Minimap, "CENTER", 40 * gravityX, -40 * gravityY) only1yzerman@0: gravityTexture:Show() only1yzerman@0: end only1yzerman@0: only1yzerman@0: only1yzerman@0: -------- "RUBY" MANAGEMENT (REUSE RUBIES AND ONLY CREATE NEW ONES WHEN NEEDED) only1yzerman@0: only1yzerman@0: local circles = {}; only1yzerman@0: local nextCircle = 1; only1yzerman@0: local circleScale = 1; only1yzerman@0: only1yzerman@0: function Bloodhound2.ResetCircles() only1yzerman@0: nextCircle = 1; only1yzerman@0: circleScale = Minimap:GetHeight() * 0.015 / (7 - Minimap:GetZoom()); only1yzerman@0: end only1yzerman@0: only1yzerman@0: function Bloodhound2.SetCircle(dx, dy, alpha) only1yzerman@0: local circle; only1yzerman@0: if (#circles < nextCircle) then only1yzerman@1: circle = Minimap:CreateTexture("Bloodhound_Circle", "ARTWORK", nil, -7); only1yzerman@0: circles[nextCircle] = circle; only1yzerman@0: circle:SetTexture("Interface\\AddOns\\Bloodhound2\\Circle"); only1yzerman@0: circle:SetWidth(12); only1yzerman@0: circle:SetHeight(12); only1yzerman@0: circle:SetAlpha(1); only1yzerman@0: else only1yzerman@0: circle = circles[nextCircle]; only1yzerman@0: end only1yzerman@0: only1yzerman@0: nextCircle = nextCircle + 1; only1yzerman@0: circle:SetPoint("CENTER", Minimap, "CENTER", dx * circleScale, -dy * circleScale); only1yzerman@0: circle:SetAlpha(alpha); only1yzerman@0: circle:Show(); only1yzerman@0: end only1yzerman@0: only1yzerman@0: function Bloodhound2.HideExtraCircles() only1yzerman@0: for i=nextCircle,#circles,1 do only1yzerman@0: circles[i]:Hide(); only1yzerman@0: end only1yzerman@0: end only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: -------- MAIN FUNCTION FOR CALCULATING RECOMMENDED DIRECTION OF TRAVEL only1yzerman@0: only1yzerman@0: function Bloodhound2.CalculateForce(cf, sf) only1yzerman@0: only1yzerman@0: local c = player.C; -- get player position only1yzerman@0: local z = player.Z; only1yzerman@0: local x = player.X; only1yzerman@0: local y = player.Y; only1yzerman@6: local ztxt = GetZoneText(); only1yzerman@6: local zstxt = GetSubZoneText(); only1yzerman@6: local badzones = { "The Star's Bazaar", "The Emperor's Step", "The Golden Lantern", "Chamber of Reflection", "The Imperial Exchange", "Path of Serenity", "Ethereal Corridor", "The Celestial Vault", "Chamber of Enlightenment" } only1yzerman@6: only1yzerman@6: if (Bloodhound2.BadZone(ztxt, zstxt, badzones) == false) then return 0,0 end -- don't draw the arrow when inside Shrine of Seven Stars only1yzerman@5: if c == 0 or z == 0 or c == -1 then return 0,0 end -- don't draw arrow if using world coordinates only1yzerman@0: if not Bloodhound2.ZoneDatum then return 0,0 end only1yzerman@0: only1yzerman@0: local datum = Bloodhound2.ZoneDatum[c][z]; -- get scale and offset for current zone map only1yzerman@0: if datum == nil then return 0,0 end only1yzerman@0: only1yzerman@0: local gx, gy = Bloodhound2.ZoneDatum.LocalToGlobal(x, y, datum); -- convert player position to world coordinates only1yzerman@0: local now = time(); -- value to use for time-stamping visited nodes only1yzerman@0: local fx = 0; -- cumulative force, X-component only1yzerman@0: local fy = 0; -- cumulative force, Y-component only1yzerman@0: local multiZone = true; -- whether to include more than the current zone only1yzerman@0: local inspectionRadius = 60; only1yzerman@0: only1yzerman@0: if Settings then only1yzerman@0: if Settings.MultiZoneMode==0 then only1yzerman@0: multiZone = IsFlying(); only1yzerman@0: elseif Settings.MultiZoneMode==1 then only1yzerman@0: multiZone = true; only1yzerman@0: else only1yzerman@0: multiZone = false; only1yzerman@0: end only1yzerman@0: only1yzerman@0: if Settings.InspectionRadius then only1yzerman@0: inspectionRadius = Settings.InspectionRadius; only1yzerman@0: end only1yzerman@0: end only1yzerman@0: only1yzerman@0: local settingsFilter = {}; -- which node types to ignore only1yzerman@0: local nodes = {}; -- which nodes to track only1yzerman@0: only1yzerman@0: for i=1,GetNumTrackingTypes(),1 do only1yzerman@0: local name, _, active, _ = GetTrackingInfo(i); only1yzerman@0: if (active == 1) then only1yzerman@0: if (name == L["Find Herbs"]) then only1yzerman@0: for k,v in pairs(Settings.HerbFilter) do settingsFilter[k] = v end only1yzerman@0: for k,v in pairs(Bloodhound2.HerbNodes) do tinsert(nodes, v) end only1yzerman@2: if (IsQuestFlaggedCompleted(32609) ~= 1) then only1yzerman@2: for k,v in pairs(Bloodhound2.MiscNodes) do tinsert(nodes, v) end only1yzerman@2: end only1yzerman@0: elseif (name == L["Find Minerals"]) then only1yzerman@0: for k,v in pairs(Settings.OreFilter) do settingsFilter[k] = v end only1yzerman@0: for k,v in pairs(Bloodhound2.OreNodes) do tinsert(nodes, v) end only1yzerman@2: if (IsQuestFlaggedCompleted(32609) ~= 1) then only1yzerman@2: for k,v in pairs(Bloodhound2.MiscNodes) do tinsert(nodes, v) end only1yzerman@2: end only1yzerman@0: end only1yzerman@0: end only1yzerman@0: end only1yzerman@0: only1yzerman@0: local avoidZones = {}; -- which zones to avoid only1yzerman@0: only1yzerman@0: if multiZone then only1yzerman@0: local cmc = GetCurrentMapContinent() only1yzerman@0: only1yzerman@0: if not Settings.ZoneFilter[cmc] then only1yzerman@0: Settings.ZoneFilter[cmc] = {} only1yzerman@0: end only1yzerman@0: only1yzerman@0: for k,v in pairs(Settings.ZoneFilter[GetCurrentMapContinent()]) do only1yzerman@0: avoidZones[k] = true; only1yzerman@0: end only1yzerman@0: end only1yzerman@0: only1yzerman@0: local avoidNodes = {}; -- which node types to ignore only1yzerman@0: only1yzerman@0: if settingsFilter then only1yzerman@0: for k,v in pairs(settingsFilter) do only1yzerman@0: avoidNodes[k] = true; only1yzerman@0: end only1yzerman@0: end only1yzerman@0: only1yzerman@0: local minimapSize = Minimap:GetWidth() / 2 - 2; only1yzerman@0: Bloodhound2.ResetCircles(); only1yzerman@0: only1yzerman@0: for key, node in pairs(nodes) do only1yzerman@0: if (avoidNodes[node.N] ~= true) and (avoidZones[node.Z] ~= true) and (multiZone or (node.Z == z)) then only1yzerman@0: local dx = node.X - gx; only1yzerman@0: local dy = node.Y - gy; only1yzerman@0: local rsqrd = dx * dx + dy * dy; only1yzerman@0: local r = sqrt(rsqrd); -- distance to node only1yzerman@0: local age = now - node.TimeStamp; -- time since last inspected only1yzerman@0: only1yzerman@0: if (r < inspectionRadius) then only1yzerman@0: node.TimeStamp = now; only1yzerman@0: else only1yzerman@0: --[[ only1yzerman@0: The magnitude of the force is proportional to the age and inversely only1yzerman@0: proportional to the square of the distance, and the direction is towards only1yzerman@0: the node. The math for this starts out as only1yzerman@0: only1yzerman@0: local force = age / rsqrd only1yzerman@0: local angle = math.atan2(dy, dx) only1yzerman@0: fx = fx + force * math.cos(angle) only1yzerman@0: fy = fy + force * math.sin(angle) only1yzerman@0: only1yzerman@0: But cos(angle) is just dx/r and sin(angle) is just dy/r, so we don't only1yzerman@0: actually need atan2, cos, and sin and instead we can write this as only1yzerman@0: ]] only1yzerman@0: local force = age / (r * rsqrd) only1yzerman@0: fx = fx + force * dx only1yzerman@0: fy = fy + force * dy only1yzerman@0: end only1yzerman@0: only1yzerman@0: if (r * circleScale < minimapSize) and (age > 60) then -- draw ruby only1yzerman@0: local alpha = r / (minimapSize / circleScale) / 0.7 - 0.3; only1yzerman@0: alpha = max(0, alpha); only1yzerman@0: Bloodhound2.SetCircle(dx * cf - dy * sf, dy * cf + dx * sf, alpha); only1yzerman@0: end only1yzerman@0: end only1yzerman@0: end only1yzerman@0: only1yzerman@0: Bloodhound2.HideExtraCircles(); only1yzerman@0: return fx, fy; only1yzerman@0: end only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: only1yzerman@0: ---------------------------------------------------------------------------------------------------- only1yzerman@0: -------- REPARENT THE MINIMAP FOR 3RD PARTY MINIMAPS AND HUDS only1yzerman@0: -------- - Added by Zasurus(www.curseforge.com/profiles/Zasurus/) for MiniHugeHUD only1yzerman@0: ---------------------------------------------------------------------------------------------------- only1yzerman@0: function Bloodhound2.ReparentMinimap(NewMinimap, MinimapName) only1yzerman@0: Bloodhound2.OnUpdateScript(nil, 1000000) only1yzerman@0: -- Hide the circles currently on the minimap only1yzerman@0: Bloodhound2.ResetCircles(); only1yzerman@0: Bloodhound2.HideExtraCircles(); only1yzerman@0: -- Hide this minimap's copy of the arrow only1yzerman@0: if Bloodhound2.GravityTexture then only1yzerman@0: Bloodhound2.GravityTexture:Hide(); only1yzerman@0: end; only1yzerman@0: -- Store the current set of circles and arrow on this minimap for later only1yzerman@0: Minimap.Bloodhound2Circles = circles; only1yzerman@0: Minimap.Bloodhound2Arrow = Bloodhound2.GravityTexture; only1yzerman@0: -- Pull back (or create for circles) the arrow and circles for this minimap only1yzerman@0: circles = NewMinimap.Bloodhound2Circles or {}; only1yzerman@0: ZasCircles = circles; only1yzerman@0: Bloodhound2.GravityTexture = NewMinimap.Bloodhound2Arrow; only1yzerman@0: -- Move Bloodhound2's Local Minimap pointer to the new minimap only1yzerman@0: Minimap = NewMinimap; only1yzerman@0: Minimap.MinimapName = MinimapName; only1yzerman@0: -- Update all points only1yzerman@0: Bloodhound2.UpdateMinimap(); only1yzerman@0: end only1yzerman@0: ----------------------------------------------------------------------------------------------------