Skip to content

Commit

Permalink
Blood moon and Ashenvale: fix handling time zones for start time
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
emmericp committed Feb 27, 2024
1 parent 97e729d commit 86565a5
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 26 deletions.
1 change: 1 addition & 0 deletions .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ globals = {
"C_AreaPoiInfo",
"C_ChatInfo",
"C_CurrencyInfo",
"C_DateAndTime",
"C_DeathInfo",
"C_GossipInfo",
"C_Map",
Expand Down
11 changes: 3 additions & 8 deletions DBM-PvP/Ashenvale.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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)
Expand Down
23 changes: 5 additions & 18 deletions DBM-PvP/BloodMoon.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions DBM-PvP/PvPGeneral.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 86565a5

Please sign in to comment.