From 86565a59a3d07ac5ab311eb7ad397f4a26934ea2 Mon Sep 17 00:00:00 2001 From: Paul Emmerich Date: Tue, 27 Feb 2024 19:50:19 +0100 Subject: [PATCH] Blood moon and Ashenvale: fix handling time zones for start time Turns out GetServerTime() does not return server time but local time. Which is fine because synchronized clocks are a thing. But we need to adjust for server time zone. The previous logic just relying on GetGameTime() got this right, but it was unreliable because GetGameTime() sucks in other ways. --- .luacheckrc | 1 + DBM-PvP/Ashenvale.lua | 11 +++-------- DBM-PvP/BloodMoon.lua | 23 +++++------------------ DBM-PvP/PvPGeneral.lua | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 663a8bb..3e47b84 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -42,6 +42,7 @@ globals = { "C_AreaPoiInfo", "C_ChatInfo", "C_CurrencyInfo", + "C_DateAndTime", "C_DeathInfo", "C_GossipInfo", "C_Map", diff --git a/DBM-PvP/Ashenvale.lua b/DBM-PvP/Ashenvale.lua index e3206cb..63fbb66 100644 --- a/DBM-PvP/Ashenvale.lua +++ b/DBM-PvP/Ashenvale.lua @@ -4,6 +4,8 @@ end local MAP_ASHENVALE = 1440 local mod = DBM:NewMod("m" .. MAP_ASHENVALE, "DBM-PvP") +local pvpMod = DBM:GetModByName("PvPGeneral") + mod:SetRevision("@file-date-integer@") -- TODO: we could teach this thing to handle outdoor zones instead of only instances -- when implementing this make sure that the stop functions are called properly, i.e., that ZONE_CHANGED_NEW_AREA still fires when leaving @@ -27,13 +29,6 @@ local widgetIDs = { -- Observed start times: -- 16:00:12 --- See BloodMoon.lua for some comments on how timing works. -local function getTimeUntilNextEvent() - local time = date("*t", GetServerTime()) - local hour = time.hour + time.min / 60 + time.sec / 60 / 60 - return (3 - ((hour - 1) % 3)) * 60 * 60 + 20 -end - local function debugTimeString() local time = date("*t", GetServerTime()) local gameHour, gameMin = GetGameTime() @@ -46,7 +41,7 @@ function mod:updateStartTimer() -- (yes, this happened) return end - local remaining = getTimeUntilNextEvent() + local remaining = pvpMod:GetTimeUntilWorldPvpEvent(1) local total = 3 * 60 * 60 if remaining < 2.75 * 60 * 60 then startTimer:Update(total - remaining, total) diff --git a/DBM-PvP/BloodMoon.lua b/DBM-PvP/BloodMoon.lua index 5cebf8a..f1022b7 100644 --- a/DBM-PvP/BloodMoon.lua +++ b/DBM-PvP/BloodMoon.lua @@ -5,6 +5,8 @@ local MAP_STRANGLETHORN = 1434 local mod = DBM:NewMod("m" .. MAP_STRANGLETHORN, "DBM-PvP") local L = mod:GetLocalizedStrings() +local pvpMod = DBM:GetModByName("PvPGeneral") + mod:SetRevision("@file-date-integer@") mod:SetZone(DBM_DISABLE_ZONE_DETECTION) mod:RegisterEvents( @@ -23,7 +25,7 @@ local widgetIDs = { [5609] = true, -- Event not active } --- Observed start and end times (GetServerTime()), seems to be exactly 30 minutes +-- Observed start and end times (GetServerTime()), seems to be exactly 30 minutes but start/end is a bit random -- 18:00:58 to 18:30:58 -- 21:00:48 to 21:30:47 -- 12:00:?? to 12:30:36 @@ -32,23 +34,8 @@ local widgetIDs = { -- 12:00:16 to 12:30:17 -- 15:00:12 to 15:30:12 --- Note on game time and server time. --- Contrary to popular opinion the event start time is not synced to GetGameTime(), it seems a bit random. --- Also, GetGameTime() is only available with minute granularity and the updates of minutes on game time as visible by the API does not seem to be synchronized to actual time. --- This GetGameTime() randomness seems to be just be a weird effect due to how the time between client and server are synchronized. --- The exact time at which the minute for GetGameTime updates changes between relogs, so there doesn't seem to be any meaning to the exact point in time when this happens. - -local function getTimeUntilNextEvent() - -- C_DateAndTime.GetServerTimeLocal() returns a time zone that is neither the server's time nor my time? - -- GetServerTime() returns something that looks like local time and is 0.5-1.5 minutes ahead of GetGameTime() (but GetGameTime() is somewhat random between relogs) - -- So we just show a timer targeting xx:00:30 ServerTime (whatever ServerTime means) because that seems to be close enough (+/- 30 seconds) - local time = date("*t", GetServerTime()) - local hour = time.hour + time.min / 60 + time.sec / 60 / 60 - return (3 - (hour % 3)) * 60 * 60 + 30 -end - function mod:updateStartTimer() - local remaining = getTimeUntilNextEvent() + local remaining = pvpMod:GetTimeUntilWorldPvpEvent() local total = 3 * 60 * 60 if remaining < 2.5 * 60 * 60 then startTimer:Update(total - remaining, total) @@ -79,7 +66,7 @@ function mod:startEvent(timeRemaining) -- For example, event triggers like this are common: -- 3 minute at 28:35 server time, 1 minute at 29:38 server time, ended at 30:36 (2 min update was just skipped) -- 3 minute at 28:10 server time, 2 minute at 29:13 server time, 1 minute at 30:15 server time, ended at 30:17 - local remaining = getTimeUntilNextEvent() - 2.5 * 60 * 60 + local remaining = pvpMod:GetTimeUntilWorldPvpEvent() - 2.5 * 60 * 60 eventRunningTimer:Update(30 * 60 - remaining, 30 * 60) end end diff --git a/DBM-PvP/PvPGeneral.lua b/DBM-PvP/PvPGeneral.lua index 4d2675a..e9647da 100644 --- a/DBM-PvP/PvPGeneral.lua +++ b/DBM-PvP/PvPGeneral.lua @@ -681,3 +681,45 @@ do end mod.UPDATE_UI_WIDGET = mod.AREA_POIS_UPDATED end + +-- Note on game time and server time. +-- Contrary to popular opinion the event start time is not synced to GetGameTime(), it seems a bit random. +-- Also, GetGameTime() is only available with minute granularity and the updates of minutes on game time as visible by the API does not seem to be synchronized to actual time. +-- This GetGameTime() randomness seems to be just be a weird effect due to how the time between client and server are synchronized. +-- The exact time at which the minute for GetGameTime updates changes between relogs, so there doesn't seem to be any meaning to the exact point in time when this happens. +-- Earlier versions of this mod just used GetGameTime() and attempted to adjust for seconds from local but it was often off by a whole minute, +-- this implementation is only off by at most 30 seconds, but usually at most 15 seconds (if your clock is synchronized) + +-- Get current time in server time zone +function mod:GetServerTime() + -- C_DateAndTime.GetServerTimeLocal() returns a time zone that is neither the server's time nor my time? + -- GetGameTime() returns server time but is updated once per minute and the update interval is synchronized to actual server time, i.e., it will be off by up to a minute and the update time differs between relogs + -- Also there is GetLocalGameTime() which seems to be identical to GetGameTime()? + -- GetServerTime() looks like it returns local time, but good thing everyone has synchronized clocks nowadays, so this is fine to use + -- We just need to handle time zones, i.e., find the diff between what GetGameTime() says and what is local time + local gameHours, gameMinutes = GetGameTime() + -- The whole date logic could probably be avoided with some clever modular arithmetic, but whatever, we know the date + local gameDate = C_DateAndTime.GetTodaysDate() -- Yes, this is server date + local localSeconds = GetServerTime() -- Yes, that is local time + local gameSeconds = time({ + year = gameDate.year, + month = gameDate.month, + day = gameDate.day, + hour = gameHours, + min = gameMinutes + }) + local timeDiff = localSeconds - gameSeconds + -- Time zones can be in 15 minute increments, so round to that + return localSeconds - math.floor(timeDiff / (15 * 60) + 0.5) * 15 * 60 +end + +-- Time until world pvp events (Season of Discovery) that ocur every `interval` hours at an offset of `offet` hours +---@return number +function mod:GetTimeUntilWorldPvpEvent(offset, interval) + offset = offset or 0 + interval = interval or 3 + local time = date("*t", self:GetServerTime()) + local hour = time.hour + time.min / 60 + time.sec / 60 / 60 + return (interval - ((hour - offset) % interval)) * 60 * 60 + 30 +end +