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.

554 lines
22 KiB

4 years ago
-- Redefine often used functions locally.
local CreateFrame = CreateFrame
local IsLeftControlKeyDown = IsLeftControlKeyDown
local IsRightControlKeyDown = IsRightControlKeyDown
local UnitInRaid = UnitInRaid
local IsLeftAltKeyDown = IsLeftAltKeyDown
local IsRightAltKeyDown = IsRightAltKeyDown
local GetServerTime = GetServerTime
local GetTime = GetTime
local C_QuestLog = C_QuestLog
local IsQuestFlaggedCompleted = C_QuestLog.IsQuestFlaggedCompleted
local pairs = pairs
local print = print
local wipe = wipe
-- Redefine global variables locally.
local string = string
local UIParent = UIParent
-- Width and height variables used to customize the window.
local entity_name_width = 208
local entity_status_width = 50
local frame_padding = 4
local favorite_rares_width = 10
local shard_id_frame_height = 16
-- Values for the opacity of the background and foreground.
local background_opacity = 0.4
local foreground_opacity = 0.6
-- The list of currently tracked npcs. Used for cleanup.
local target_npc_ids = {}
-- ####################################################################
-- ## Localization Support ##
-- ####################################################################
-- Get an object we can use for the localization of the addon.
local L = LibStub("AceLocale-3.0"):GetLocale("RareTracker", true)
-- ####################################################################
-- ## Interface Control ##
-- ####################################################################
-- Prepare the window's data and show it on the screen.
function RareTracker:OpenWindow()
-- Update all the kill marks. Also make a delayed call in case the quest marks aren't loaded.
self:UpdateAllDailyKillMarks()
self:DelayedExecution(3, function() self:UpdateAllDailyKillMarks() end)
-- Show the window if it is not hidden.
if not self.db.global.window.hide then
self.gui:Show()
end
end
-- Close the window and do cleanup.
function RareTracker:CloseWindow()
-- Simply hide the interface.
self.gui:Hide()
end
-- ####################################################################
-- ## Rare Entity Window Update Functions ##
-- ####################################################################
-- Update the shard number in the shard number display.
function RareTracker:UpdateShardNumber()
if self.shard_id then
self.gui.shard_id_frame.status_text:SetText(string.format(L["Shard ID: %s"], self.shard_id))
else
self.gui.shard_id_frame.status_text:SetText(string.format(L["Shard ID: %s"], L["Unknown"]))
end
end
-- Update the status for the given entity.
function RareTracker:UpdateStatus(npc_id)
local f = self.gui.entities_frame.entities[npc_id]
if self.current_health[npc_id] then
f.status:SetText(self.current_health[npc_id].."%")
f.status:SetFontObject("GameFontGreen")
f.announce.texture:SetColorTexture(0, 1, 0, 1)
elseif self.is_alive[npc_id] then
f.status:SetText("N/A")
f.status:SetFontObject("GameFontGreen")
f.announce.texture:SetColorTexture(0, 1, 0, 1)
elseif self.last_recorded_death[npc_id] then
local last_death, _ = unpack(self.last_recorded_death[npc_id])
f.status:SetText(math.floor((GetServerTime() - last_death) / 60).."m")
f.status:SetFontObject("GameFontNormal")
f.announce.texture:SetColorTexture(0, 0, 1, foreground_opacity)
else
f.status:SetText("--")
f.status:SetFontObject("GameFontNormal")
f.announce.texture:SetColorTexture(0, 0, 0, foreground_opacity)
end
end
-- Update the daily kill mark of the given entity.
function RareTracker:UpdateDailyKillMark(npc_id, primary_id)
-- Not all npcs have a completion quest.
local quest_id = self.primary_id_to_data[primary_id].entities[npc_id].quest_id
if not quest_id then return end
-- Multiple NPCs might share the same quest id.
local npc_ids = self.completion_quest_to_npc_ids[quest_id]
local is_completed = IsQuestFlaggedCompleted(quest_id)
for _, target_npc_id in pairs(npc_ids) do
local f = self.gui.entities_frame.entities[target_npc_id].name
f:SetFontObject((is_completed and "GameFontRed") or "GameFontNormal")
end
end
-- Update the daily kill marks of all the currently tracked entities.
function RareTracker:UpdateAllDailyKillMarks()
local primary_id = self.zone_id
if primary_id then
for npc_id, _ in pairs(self.primary_id_to_data[primary_id].entities) do
self:UpdateDailyKillMark(npc_id, primary_id)
end
-- Update the visibility of entities.
self:UpdateEntityVisibility()
end
end
-- Update the visibility of rares in the entity frame based on the used settings.
function RareTracker:UpdateEntityVisibility()
local f = self.gui.entities_frame
-- Do not continue if the required information is unavailable.
local primary_id = self.zone_id
if not primary_id or not self.primary_id_to_data[primary_id] then return end
-- Show and hide entities based on the used settings.
local n = 0
for _, npc_id in pairs(self.primary_id_to_data[primary_id].ordering) do
-- Note: ignored rares are already removed from the list by the update display list function.
if target_npc_ids[npc_id] then
if self.db.global.window.hide_killed_entities then
-- Find the quest id and hide the entry if already killed today.
local quest_id = self.primary_id_to_data[primary_id].entities[npc_id].quest_id
if quest_id and IsQuestFlaggedCompleted(quest_id) then
-- Hide the entity, since it has been killed already.
f.entities[npc_id]:Hide()
else
-- Display the entity.
f.entities[npc_id]:SetPoint("TOPLEFT", f, 0, -n * 12 - 5)
f.entities[npc_id]:Show()
n = n + 1
end
else
-- Display the entity.
f.entities[npc_id]:SetPoint("TOPLEFT", f, 0, -n * 12 - 5)
f.entities[npc_id]:Show()
n = n + 1
end
end
end
-- Resize the appropriate frames.
self:UpdateEntityFrameDimensions(n, f)
end
-- Update the data that is displayed to apply to the current zone and other parameters.
function RareTracker:UpdateDisplayList()
-- Hide all frames that are currently marked as visible.
local f = self.gui.entities_frame
for npc_id, _ in pairs(target_npc_ids) do
f.entities[npc_id]:Hide()
end
-- If no data is present for the given zone, then there are no targets.
wipe(target_npc_ids)
local primary_id = self.zone_id
if primary_id and self.primary_id_to_data[primary_id] then
-- Gather the candidate npc ids, using the plugin provider when defined.
if self.primary_id_to_data[primary_id].SelectTargetEntities then
self.primary_id_to_data[primary_id].SelectTargetEntities(self, target_npc_ids)
else
for npc_id, _ in pairs(self.primary_id_to_data[primary_id].entities) do
target_npc_ids[npc_id] = true
end
end
-- Filter out all ignored entities and count the number of entries we will have in total.
-- Give all of the table entries their new positions and show them when appropriate.
local n = 0
for _, npc_id in pairs(self.primary_id_to_data[primary_id].ordering) do
if target_npc_ids[npc_id] then
if self.db.global.ignored_rares[npc_id] then
target_npc_ids[npc_id] = nil
else
f.entities[npc_id]:SetPoint("TOPLEFT", f, 0, -n * 12 - 5)
f.entities[npc_id]:Show()
n = n + 1
end
end
end
-- Resize the appropriate frames.
self:UpdateEntityFrameDimensions(n, f)
end
end
-- Resize the entities portion of the tracking window.
function RareTracker:UpdateEntityFrameDimensions(n, f)
self.gui:SetSize(
entity_name_width + entity_status_width + 2 * favorite_rares_width + 5 * frame_padding,
shard_id_frame_height + 3 * frame_padding + n * 12 + 8
)
f:SetSize(
entity_name_width + entity_status_width + 2 * favorite_rares_width + 3 * frame_padding,
n * 12 + 8
)
f.entity_name_backdrop:SetSize(entity_name_width, f:GetHeight())
f.entity_status_backdrop:SetSize(entity_status_width, f:GetHeight())
end
-- Ensure that all the favorite marks of the entities are set correctly.
function RareTracker:CorrectFavoriteMarks()
for npc_id, _ in pairs(self.tracked_npc_ids) do
if self.db.global.favorite_rares[npc_id] then
self.gui.entities_frame.entities[npc_id].favorite.texture:SetColorTexture(0, 1, 0, 1)
else
self.gui.entities_frame.entities[npc_id].favorite.texture:SetColorTexture(0, 0, 0, foreground_opacity)
end
end
end
-- Update the status of all npcs that are currently being tracked.
function RareTracker:UpdateStatusTrackedEntities()
for npc_id, _ in pairs(target_npc_ids) do
-- It might occur that the rare is marked as alive, but no health is known.
-- If two minutes pass without a health value, the alive tag will be reset.
if self.is_alive[npc_id] and GetServerTime() - self.is_alive[npc_id] > 120 then
self.is_alive[npc_id] = nil
self.current_health[npc_id] = nil
self.reported_spawn_uids[npc_id] = nil
self.current_coordinates[npc_id] = nil
end
self:UpdateStatus(npc_id)
end
end
-- Switch between the report and waypoint icons.
function RareTracker.CycleReportWaypointIcon(f)
f.icon_state = not f.icon_state
if f.icon_state then
f.texture:SetTexture("Interface\\AddOns\\RareTrackerCore\\Icons\\Broadcast.tga")
else
f.texture:SetTexture("Interface\\AddOns\\RareTrackerCore\\Icons\\Waypoint.tga")
end
end
-- ####################################################################
-- ## Initialization ##
-- ####################################################################
-- Create an entry within the entity frame for the given entity.
function RareTracker:InitializeRareTableEntry(npc_id, rare_data, parent)
local f = CreateFrame("Frame", string.format("RT.entities_frame.entities[%s]", npc_id), parent)
f:SetSize(entity_name_width + entity_status_width + 3 * frame_padding + 2 * favorite_rares_width, 12)
-- Create the favorite button.
f.favorite = CreateFrame("CheckButton", string.format("RT.entities_frame.entities[%s].favorite", npc_id), f)
f.favorite:SetSize(10, 10)
f.favorite:SetPoint("TOPLEFT", 1, 0)
f.favorite.texture = f.favorite:CreateTexture(nil, "BACKGROUND")
f.favorite.texture:SetColorTexture(0, 0, 0, foreground_opacity)
f.favorite.texture:SetAllPoints(f.favorite)
-- Add an action listener.
f.favorite:SetScript("OnClick", function()
if self.db.global.favorite_rares[npc_id] then
self.db.global.favorite_rares[npc_id] = nil
f.favorite.texture:SetColorTexture(0, 0, 0, foreground_opacity)
else
self.db.global.favorite_rares[npc_id] = true
f.favorite.texture:SetColorTexture(0, 1, 0, 1)
end
self.NotifyOptionsChange()
end)
-- Add a button that announces the rare/adds a waypoint when applicable.
-- Add the announce/waypoint button.
f.announce = CreateFrame("Button", string.format("RT.entities_frame.entities[%s].announce", npc_id), f)
f.announce:SetSize(10, 10)
f.announce:SetPoint("TOPLEFT", frame_padding + favorite_rares_width + 1, 0)
f.announce.texture = f.announce:CreateTexture(nil, "BACKGROUND")
f.announce.texture:SetColorTexture(0, 0, 0, foreground_opacity)
f.announce.texture:SetAllPoints(f.announce)
f.announce:RegisterForClicks("LeftButtonDown", "RightButtonDown")
f.announce:SetScript("OnClick", function(_, button)
local name = rare_data.name
local health = self.current_health[npc_id]
local last_death = nil
if self.last_recorded_death[npc_id] then
last_death, _ = unpack(self.last_recorded_death[npc_id])
end
local loc = self.current_coordinates[npc_id]
if button == "LeftButton" then
-- The default target is general chat.
local target = "CHANNEL"
if IsLeftControlKeyDown() or IsRightControlKeyDown() then
if UnitInRaid("player") then
target = "RAID"
else
target = "PARTY"
end
elseif IsLeftAltKeyDown() or IsRightAltKeyDown() then
target = "SAY"
end
-- Send the message.
self:ReportRareInChat(npc_id, target, name, health, last_death, loc)
else
-- Put down a waypoint.
local loc = self.current_coordinates[npc_id] or self.primary_id_to_data[self.zone_id].entities[npc_id].coordinates
if loc then
if IsLeftAltKeyDown() or IsRightAltKeyDown() then
self:ReportRareCoordinatesInChat(npc_id, "CHANNEL", name, loc)
elseif IsLeftControlKeyDown() or IsRightControlKeyDown() then
local target = "PARTY"
if UnitInRaid("player") then
target = "RAID"
end
self:ReportRareCoordinatesInChat(npc_id, target, name, loc)
else
local x, y = unpack(loc)
C_Map.SetUserWaypoint(UiMapPoint.CreateFromCoordinates(self.zone_id, x/100, y/100))
C_SuperTrack.SetSuperTrackedUserWaypoint(true)
end
end
end
end)
-- Add the entities name.
f.name = f:CreateFontString(nil, nil, "GameFontNormal")
f.name:SetSize(entity_name_width, 12)
f.name:SetPoint("TOPLEFT", 2 * frame_padding + 2 * favorite_rares_width + 10, 0)
f.name:SetJustifyH("LEFT")
f.name:SetJustifyV("TOP")
f.name:SetText(rare_data.name)
-- Add the timer/health entry.
f.status = f:CreateFontString(nil, nil, "GameFontNormal")
f.status:SetSize(entity_status_width, 12)
f.status:SetPoint("TOPRIGHT", 0, 0)
f.status:SetText("--")
f.status:SetJustifyH("MIDDLE")
f.status:SetJustifyV("TOP")
parent.entities[npc_id] = f
end
-- Initialize the rare table frame.
function RareTracker:InitializeRareTableFrame(parent)
-- First, add the frames for the backdrop and make sure that the hierarchy is created.
local f = CreateFrame("Frame", "RT.entities_frame", parent)
f:SetPoint("TOPLEFT", frame_padding, -(2 * frame_padding + shard_id_frame_height))
f.entity_name_backdrop = CreateFrame("Frame", "RT.entities_frame.entity_name_backdrop", f)
f.entity_name_backdrop:SetPoint("TOPLEFT", f, 2 * frame_padding + 2 * favorite_rares_width, 0)
f.entity_name_backdrop.texture = f.entity_name_backdrop:CreateTexture(nil, "BACKGROUND")
f.entity_name_backdrop.texture:SetColorTexture(0, 0, 0, foreground_opacity)
f.entity_name_backdrop.texture:SetAllPoints(f.entity_name_backdrop)
f.entity_status_backdrop = CreateFrame("Frame", "RT.entities_frame.entity_status_backdrop", f)
f.entity_status_backdrop:SetPoint("TOPRIGHT", f, 0, 0)
f.entity_status_backdrop.texture = f.entity_status_backdrop:CreateTexture(nil, "BACKGROUND")
f.entity_status_backdrop.texture:SetColorTexture(0, 0, 0, foreground_opacity)
f.entity_status_backdrop.texture:SetAllPoints(f.entity_status_backdrop)
-- Next, create frames for all rares in the table.
f.entities = {}
-- Create a frame entry for all of the NPC ids, even the ignored ones.
-- The ordering and hiding of rares will be done later.
for _, data in pairs(self.primary_id_to_data) do
for npc_id, rare_data in pairs(data.entities) do
self:InitializeRareTableEntry(npc_id, rare_data, f)
end
end
-- Ensure that the data in the window updates periodically.
f.last_display_update = GetTime()
f:SetScript("OnUpdate", function()
if f.last_display_update + 1 < GetTime() then
f.last_display_update = GetTime()
self:UpdateStatusTrackedEntities()
end
end)
parent.entities_frame = f
end
-- Display the current shard number at the top of the frame.
function RareTracker.InitializeShardNumberFrame(parent)
local f = CreateFrame("Frame", "RT.shard_id_frame", parent)
local width = entity_name_width + entity_status_width + 3 * frame_padding + 2 * favorite_rares_width
local height = shard_id_frame_height
f:SetSize(width, height)
f:SetPoint("TOPLEFT", parent, frame_padding, -frame_padding)
f.texture = f:CreateTexture(nil, "BACKGROUND")
f.texture:SetColorTexture(0, 0, 0, foreground_opacity)
f.texture:SetAllPoints(f)
f.status_text = f:CreateFontString(nil, nil, "GameFontNormal")
f.status_text:SetPoint("TOPLEFT", 10 + 2 * favorite_rares_width + 2 * frame_padding, -3)
f.status_text:SetText(string.format(L["Shard ID: %s"], L["Unknown"]))
parent.shard_id_frame = f
end
-- Initialize the favorite icon in the rare entities frame.
function RareTracker.InitializeFavoriteIconFrame(parent)
local f = CreateFrame("Frame", "RT.favorite_icon", parent)
f:SetSize(10, 10)
f:SetPoint("TOPLEFT", parent, frame_padding + 1, -(frame_padding + 3))
f.texture = f:CreateTexture(nil, "OVERLAY")
f.texture:SetSize(10, 10)
f.texture:SetPoint("CENTER", f)
f.texture:SetTexture("Interface\\AddOns\\RareTrackerCore\\Icons\\Favorite.tga")
parent.favorite_icon = f
end
-- Initialize the alternating announce icon in the rare entities frame.
function RareTracker:InitializeAnnounceIconFrame(parent)
local f = CreateFrame("Frame", "RT.broadcast_icon", parent)
f:SetSize(10, 10)
f:SetPoint("TOPLEFT", parent, 2 * frame_padding + favorite_rares_width + 1, -(frame_padding + 3))
f.texture = f:CreateTexture(nil, "OVERLAY")
f.texture:SetSize(10, 10)
f.texture:SetPoint("CENTER", f)
f.texture:SetTexture("Interface\\AddOns\\RareTrackerCore\\Icons\\Broadcast.tga")
-- Make the icon swap periodically by adding an update handler.
f.icon_state = false
f.last_icon_change = GetTime()
f:SetScript("OnUpdate", function()
if f.last_icon_change + 2 < GetTime() then
f.last_icon_change = GetTime()
self.CycleReportWaypointIcon(f)
end
end)
parent.broadcast_icon = f
end
-- Initialize the close button in the rare entities frame.
function RareTracker:InitializeInfoButton(parent)
local f = CreateFrame("Button", "RT.info_button", parent)
f:SetSize(10, 10)
f:SetPoint("TOPRIGHT", parent, -3 * frame_padding - favorite_rares_width, -(frame_padding + 3))
f.texture = f:CreateTexture(nil, "OVERLAY")
f.texture:SetTexture("Interface\\AddOns\\RareTrackerCore\\Icons\\Info.tga")
f.texture:SetSize(10, 10)
f.texture:SetPoint("CENTER", f)
f:SetScript("OnClick", function()
InterfaceOptionsFrame_Show()
InterfaceOptionsFrame_OpenToCategory(self.info_frame)
end)
parent.info_button = f
end
-- Initialize the close button in the rare entities frame.
function RareTracker:InitializeCloseButton(parent)
local f = CreateFrame("Button", "RT.close_button", parent)
f:SetSize(10, 10)
f:SetPoint("TOPRIGHT", parent, -2 * frame_padding, -(frame_padding + 3))
f.texture = f:CreateTexture(nil, "OVERLAY")
f.texture:SetTexture("Interface\\AddOns\\RareTrackerCore\\Icons\\Cross.tga")
f.texture:SetSize(10, 10)
f.texture:SetPoint("CENTER", f)
f:SetScript("OnClick", function()
parent:Hide()
self.db.global.window.hide = true
end)
parent.close_button = f
end
-- Initialize the addon's entity frame.
function RareTracker:InitializeInterface()
local f = self.gui
f:SetSize(
entity_name_width + entity_status_width + 2 * favorite_rares_width + 5 * frame_padding,
shard_id_frame_height + 2 * frame_padding
)
if self.db.global.window.position then
local anchor, x, y = unpack(self.db.global.window.position)
f:SetPoint(anchor, x, y)
else
f:SetPoint("CENTER")
end
f.texture = f:CreateTexture(nil, "BACKGROUND")
f.texture:SetColorTexture(0, 0, 0, background_opacity)
f.texture:SetAllPoints(f)
-- Create a sub-frame for the entity names.
self.InitializeShardNumberFrame(f)
self:InitializeRareTableFrame(f)
-- Add icons for the favorite and broadcast columns.
self.InitializeFavoriteIconFrame(f)
self:InitializeAnnounceIconFrame(f)
-- Create an info and close button.
self:InitializeInfoButton(f)
self:InitializeCloseButton(f)
-- Make the window moveable and ensure that the window stays where the user puts it.
f:SetClampedToScreen(true)
f:RegisterForDrag("LeftButton")
f:SetMovable(true)
f:SetUserPlaced(true)
f:EnableMouse(true)
f:SetScript("OnDragStart", f.StartMoving)
f:SetScript("OnDragStop", function(_f)
_f:StopMovingOrSizing()
local _, _, anchor, x, y = _f:GetPoint()
self.db.global.window.position = {anchor, x, y}
self:Debug("New frame position", anchor, x, y)
end)
-- Enforce the user-defined scale of the window.
f:SetScale(self.db.global.window.scale)
-- The default state of the window is hidden.
f:Hide()
end