/** The RTC's day counter wraps every 20 weeks (FixDays keeps wCurDay below * 140), and day differences wrap with it (_CalcDaysSince). */ export const RTC_DAYS = 20 * 7; /** A day countdown (InitNDaysCountdown's two bytes): the days remaining * and the wCurDay it was last checked on. */ export interface DayTimer { remaining: number; day: number; } /** wCurDay for a native day number. */ export const rtcDay = (day: number) => ((day % RTC_DAYS) + RTC_DAYS) % RTC_DAYS; /** InitNDaysCountdown. */ export function initCountdown(days: number, today: number): DayTimer { return { remaining: days, day: rtcDay(today) }; } /** CheckDayDependentEventHL: CalcDaysSince (which also moves the stored * day to today) and UpdateTimeRemaining. True (carry) once the countdown * has run out; a timer never started (`undefined`, all zeros in the * original) is already out. */ export function checkCountdown(timer: DayTimer | undefined, today: number) { const current = rtcDay(today); const start = timer ?? { remaining: 0, day: 0 }; const elapsed = (current - start.day + RTC_DAYS) % RTC_DAYS; const remaining = Math.max(0, start.remaining - elapsed); return { done: remaining === 0, timer: { remaining, day: current } }; } /** The days until next Friday (RestartLuckyNumberCountdown's * .GetDaysUntilNextFriday): 7 on Fridays and 6 on Saturdays. */ export function daysUntilNextFriday(weekday: number) { const FRIDAY = 5; const days = FRIDAY - weekday; return days > 0 ? days : days + 7; } export function validDayTimer(value: unknown): value is DayTimer { const timer = value as DayTimer; return ( Boolean(timer) && typeof timer === "object" && Number.isInteger(timer.remaining) && timer.remaining >= 0 && timer.remaining <= 255 && Number.isInteger(timer.day) && timer.day >= 0 && timer.day < RTC_DAYS ); }