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.
298 lines
9.5 KiB
298 lines
9.5 KiB
-- ----------------------------------------------------------------------------
|
|
-- KillInfo: Data structure for holding data about previous kills. (And update-timers.)
|
|
-- ----------------------------------------------------------------------------
|
|
|
|
local _, WBT = ...;
|
|
|
|
local Util = WBT.Util;
|
|
local Options = WBT.Options;
|
|
|
|
local KillInfo = {};
|
|
WBT.KillInfo = KillInfo;
|
|
|
|
KillInfo.CURRENT_VERSION = "v1.12";
|
|
KillInfo.UNKNOWN_SHARD = -1;
|
|
|
|
local RANDOM_DELIM = "-";
|
|
|
|
local ID_DELIM = ";";
|
|
local ID_PART_UNKNOWN = "_";
|
|
|
|
|
|
function KillInfo.CompareTo(t, a, b)
|
|
-- "if I'm comparing 'a' and 'b', return true when 'a' should come first"
|
|
local k1 = t[a];
|
|
local k2 = t[b];
|
|
|
|
if k1:IsExpired() and not k2:IsExpired() then
|
|
return false;
|
|
elseif not k1:IsExpired() and k2:IsExpired() then
|
|
return true;
|
|
end
|
|
|
|
return k1:GetSecondsUntilLatestRespawn() < k2:GetSecondsUntilLatestRespawn();
|
|
end
|
|
|
|
function KillInfo.IsValidID(id)
|
|
return KillInfo.ParseID(id) and true;
|
|
end
|
|
|
|
-- Returns nil if the parsing fails.
|
|
function KillInfo.ParseID(id)
|
|
local boss_name, shard_id, map_id = strsplit(ID_DELIM, id);
|
|
|
|
if not boss_name then
|
|
return nil;
|
|
end
|
|
|
|
return {
|
|
boss_name = boss_name,
|
|
shard_id = shard_id,
|
|
map_id = map_id,
|
|
};
|
|
end
|
|
|
|
function KillInfo.CreateID(boss_name, shard_id, map_id)
|
|
-- Unique ID used as key in the global table of tracked KillInfos and GUI labels.
|
|
--
|
|
-- Note on map_id: It's necessary to make Zandalari Warbringers unique.
|
|
|
|
if shard_id == nil or shard_id == KillInfo.UNKNOWN_SHARD then
|
|
shard_id = ID_PART_UNKNOWN;
|
|
end
|
|
|
|
local map_id = map_id or WBT.GetCurrentMapId();
|
|
|
|
return table.concat({boss_name, shard_id, map_id}, ID_DELIM);
|
|
end
|
|
|
|
function KillInfo:ID()
|
|
return self.CreateID(self.boss_name, self.shard_id, self.map_id);
|
|
end
|
|
|
|
function KillInfo:HasShardID()
|
|
return self.shard_id ~= nil;
|
|
end
|
|
|
|
-- Needed in order to verify that all fields are present.
|
|
function KillInfo:IsValidVersion()
|
|
return self.version and self.version == KillInfo.CURRENT_VERSION;
|
|
end
|
|
|
|
function KillInfo:SetInitialValues()
|
|
self.version = KillInfo.CURRENT_VERSION;
|
|
self.realm_name = GetRealmName(); -- Only use for printing!
|
|
self.realm_name_normalized = GetNormalizedRealmName();
|
|
self.map_id = WBT.GetCurrentMapId();
|
|
self.announce_times = {1, 2, 3, 10, 30, 1*60, 5*60, 10*60};
|
|
self.has_triggered_respawn = false;
|
|
end
|
|
|
|
-- NOTE:
|
|
-- This function is a reminder that the design is to update KillInfo.CURRENT_VERSION (and thereby clear
|
|
-- all user KillInfos), rather than try to upgrade existing ones. The reason is that it makes the code simpler,
|
|
-- and should not impact users too much if it only happens once in a while.
|
|
function KillInfo:Upgrade()
|
|
-- Don't implement this function.
|
|
end
|
|
|
|
function KillInfo:Print(indent)
|
|
print(indent .. "boss_name: " .. self.boss_name);
|
|
print(indent .. "version: " .. self.version);
|
|
print(indent .. "realm_name: " .. self.realm_name);
|
|
print(indent .. "realm_name_normalized: " .. self.realm_name_normalized);
|
|
print(indent .. "shard_id: " .. self.shard_id);
|
|
print(indent .. "map_id: " .. self.map_id);
|
|
print(indent .. "has_triggered_respawn: " .. tostring(self.has_triggered_respawn));
|
|
end
|
|
|
|
function KillInfo:SetNewDeath(t_death)
|
|
-- FIXME: It doesn't make sense to call this function from here. I think it's
|
|
-- a remnant from the time when the addon tried to upgrade KillInfos.
|
|
self:SetInitialValues();
|
|
|
|
self.t_death = t_death;
|
|
end
|
|
|
|
function KillInfo:New(boss_name, t_death, shard_id)
|
|
local ki = {};
|
|
|
|
setmetatable(ki, self);
|
|
self.__index = self;
|
|
|
|
ki.boss_name = boss_name;
|
|
ki.db = WBT.BossData.Get(boss_name); -- FIXME: Convert to fcn, and fix calls to WBT.BossData.Get(self.boss_name)
|
|
ki.shard_id = shard_id or KillInfo.UNKNOWN_SHARD;
|
|
|
|
ki:SetNewDeath(t_death);
|
|
|
|
return ki;
|
|
end
|
|
|
|
function KillInfo:Deserialize(serialized)
|
|
local ki = serialized;
|
|
setmetatable(ki, self);
|
|
self.__index = self;
|
|
return ki;
|
|
end
|
|
|
|
function KillInfo:HasRandomSpawnTime()
|
|
return self.db.min_respawn ~= self.db.max_respawn;
|
|
end
|
|
|
|
function KillInfo:IsSafeToShare(error_msgs)
|
|
|
|
if not self:IsValidVersion() then
|
|
table.insert(error_msgs, "Timer was created with an old version of WBT and is now outdated.");
|
|
end
|
|
if self:IsExpired() then
|
|
table.insert(error_msgs, "Timer has expired.");
|
|
end
|
|
if self:HasUnknownShard() then
|
|
-- It's impossible to tell where it comes from. Player may have received it when server-jumping or
|
|
-- what not. To avoid complexity, just don't allow sharing it.
|
|
table.insert(error_msgs, "Timer doesn't have a shard ID. (This means that it was shared to you by a "
|
|
.. "player with an old version of WBT.)"); -- WBT v.1.9 or less.
|
|
else
|
|
local shard_id = WBT.GetCurrentShardID();
|
|
if WBT.IsUnknownShard(shard_id) then
|
|
table.insert(error_msgs, "Current shard ID is unknown. It will automatically be detected when "
|
|
.. "mousing over an NPC.");
|
|
elseif self.shard_id ~= shard_id then
|
|
table.insert(error_msgs, "Kill was made on shard ID " .. self.shard_id
|
|
.. ", but you are on " .. shard_id .. ".");
|
|
end
|
|
end
|
|
|
|
if Util.TableIsEmpty(error_msgs) then
|
|
return true;
|
|
end
|
|
return false;
|
|
end
|
|
|
|
function KillInfo:GetServerDeathTime()
|
|
return self.t_death;
|
|
end
|
|
|
|
function KillInfo:GetSecondsSinceDeath()
|
|
return GetServerTime() - self.t_death;
|
|
end
|
|
|
|
function KillInfo:GetEarliestRespawnTimePoint()
|
|
return self.t_death + self.db.min_respawn;
|
|
end
|
|
|
|
function KillInfo:GetLatestRespawnTimePoint()
|
|
return self.t_death + self.db.max_respawn;
|
|
end
|
|
|
|
|
|
function KillInfo:GetNumCycles()
|
|
local t = self:GetLatestRespawnTimePoint() - GetServerTime();
|
|
local n = 0;
|
|
while t < 0 do
|
|
t = t + self.db.max_respawn;
|
|
n = n + 1;
|
|
end
|
|
return n;
|
|
end
|
|
|
|
function KillInfo:GetSecondsUntilEarliestRespawn(opt_cyclic)
|
|
return self:GetSecondsUntilLatestRespawn(opt_cyclic) - (self.db.max_respawn - self.db.min_respawn);
|
|
end
|
|
|
|
function KillInfo:GetSecondsUntilLatestRespawn(opt_cyclic)
|
|
local t = self:GetLatestRespawnTimePoint() - GetServerTime();
|
|
if opt_cyclic and t < 0 then
|
|
t = t + (self:GetNumCycles() * self.db.max_respawn);
|
|
end
|
|
return t;
|
|
end
|
|
|
|
function KillInfo:GetSpawnTimeAsText()
|
|
if not self:IsValidVersion() then
|
|
return "--outdated--";
|
|
end
|
|
|
|
if self:HasRandomSpawnTime() then
|
|
local t_lower = self:GetSecondsUntilEarliestRespawn(true);
|
|
local t_upper = self:GetSecondsUntilLatestRespawn(true);
|
|
if t_lower == nil or t_upper == nil then
|
|
return "--invalid1--";
|
|
elseif t_lower < 0 then
|
|
return "0s" .. RANDOM_DELIM .. Util.FormatTimeSeconds(t_upper)
|
|
else
|
|
return Util.FormatTimeSeconds(t_lower) .. RANDOM_DELIM .. Util.FormatTimeSeconds(t_upper)
|
|
end
|
|
else
|
|
return Util.FormatTimeSeconds(self:GetSecondsUntilEarliestRespawn(true));
|
|
end
|
|
end
|
|
|
|
function KillInfo:ShouldAutoAnnounce()
|
|
return WBT.db.global.auto_announce
|
|
and Util.SetUtil.ContainsValue(self.announce_times, self:GetSecondsUntilLatestRespawn())
|
|
and WBT.PlayerIsInBossPerimiter(self.boss_name)
|
|
and WBT.BossData.Get(self.boss_name).auto_announce
|
|
and self:IsSafeToShare({});
|
|
end
|
|
|
|
function KillInfo:InTimeWindow(from, to)
|
|
local t_now = GetServerTime();
|
|
return from <= t_now and t_now <= to;
|
|
end
|
|
|
|
function KillInfo:ShouldRespawnAlertPlayNow(offset)
|
|
local t_before_respawn = self:GetLatestRespawnTimePoint() - offset;
|
|
|
|
local trigger = self:InTimeWindow(t_before_respawn, t_before_respawn + 2)
|
|
and WBT.InZoneAndShardForTimer(self)
|
|
and WBT.PlayerIsInBossPerimiter(self.boss_name)
|
|
and self:IsValidVersion()
|
|
and not self:IsExpired()
|
|
and not self.has_triggered_respawn;
|
|
if trigger then
|
|
self.has_triggered_respawn = true;
|
|
end
|
|
|
|
return trigger;
|
|
end
|
|
|
|
function KillInfo:IsExpired()
|
|
return self:GetSecondsUntilLatestRespawn() < 0;
|
|
end
|
|
|
|
function KillInfo:IsCyclicExpired()
|
|
if not Options.cyclic.get() then
|
|
return self:IsExpired();
|
|
elseif Options.num_cycles_to_show.get() == Options.NUM_CYCLES_TO_SHOW_MAX then
|
|
-- Special case. Allows timers to always be shown.
|
|
return false;
|
|
end
|
|
return Options.num_cycles_to_show.get() < self:GetNumCycles();
|
|
end
|
|
|
|
function KillInfo:HasUnknownShard()
|
|
return self.shard_id == KillInfo.UNKNOWN_SHARD;
|
|
end
|
|
|
|
function KillInfo:IsOnCurrentShard()
|
|
if self:HasUnknownShard() then
|
|
return false;
|
|
elseif Options.assume_realm_keeps_shard.get() and (self.shard_id == WBT.GetSavedShardID(WBT.GetCurrentMapId())) then
|
|
return true;
|
|
else
|
|
return self.shard_id == WBT.GetCurrentShardID();
|
|
end
|
|
end
|
|
|
|
-- Returns true if the KillInfo comes from the last known shard for the
|
|
-- zone its boss belongs to.
|
|
function KillInfo:IsOnSavedRealmShard()
|
|
if self:HasUnknownShard() then
|
|
return false;
|
|
end
|
|
|
|
local zone_id = WBT.BossData.Get(self.boss_name).map_id;
|
|
return WBT.GetSavedShardID(zone_id) == self.shard_id;
|
|
end
|