acdream/src/AcDream.Core/World/DerethCalendar.cs

103 lines
3.4 KiB
C#

namespace AcDream.Core.World;
/// <summary>
/// Instance-scoped Dereth calendar projection. Retail stores the Region
/// <c>GameTime.ZeroTimeOfYear</c> beside the active world rather than in a
/// process-global calendar. Keeping that origin on this object lets multiple
/// Runtime sessions use different Region definitions without corrupting one
/// another.
/// </summary>
public sealed class DerethCalendar
{
public DerethCalendar(
double originOffsetTicks = DerethDateTime.DayFractionOriginOffsetTicks)
{
SetOriginOffset(originOffsetTicks);
}
public double OriginOffsetTicks { get; private set; }
/// <summary>
/// Installs the Region DAT origin for this world lifetime.
/// </summary>
public void SetOriginOffset(double originOffsetTicks)
{
if (!double.IsFinite(originOffsetTicks))
throw new ArgumentOutOfRangeException(nameof(originOffsetTicks));
OriginOffsetTicks = originOffsetTicks;
}
public double DayFraction(double ticks)
{
double shifted = Shift(ticks);
double rem = shifted
- Math.Floor(shifted / DerethDateTime.DayTicks)
* DerethDateTime.DayTicks;
return rem / DerethDateTime.DayTicks;
}
public DerethDateTime.HourName CurrentHour(double ticks)
{
int slot = (int)Math.Floor(
DayFraction(ticks) * DerethDateTime.HoursInADay);
slot = Math.Clamp(slot, 0, DerethDateTime.HoursInADay - 1);
return (DerethDateTime.HourName)slot;
}
public bool IsDaytime(double ticks)
{
int hour = (int)CurrentHour(ticks);
return hour >= (int)DerethDateTime.HourName.Dawnsong
&& hour <= (int)DerethDateTime.HourName.WarmtideAndHalf;
}
public DerethDateTime.Calendar ToCalendar(double ticks)
{
double shifted = Shift(ticks);
int relativeYear = (int)(shifted / DerethDateTime.YearTicks);
double withinYear =
shifted - relativeYear * DerethDateTime.YearTicks;
int month = Math.Min(
DerethDateTime.MonthsInAYear - 1,
(int)(withinYear / DerethDateTime.MonthTicks));
double withinMonth =
withinYear - month * DerethDateTime.MonthTicks;
int day = Math.Min(
DerethDateTime.DaysInAMonth,
(int)(withinMonth / DerethDateTime.DayTicks) + 1);
return new DerethDateTime.Calendar(
relativeYear + DerethDateTime.ZeroYear,
(DerethDateTime.MonthName)month,
day,
CurrentHour(ticks));
}
public int Year(double ticks) =>
(int)(Shift(ticks) / DerethDateTime.YearTicks);
public int AbsoluteYear(double ticks) =>
Year(ticks) + DerethDateTime.ZeroYear;
public int DayOfYear(double ticks)
{
double shifted = Shift(ticks);
int year = (int)(shifted / DerethDateTime.YearTicks);
double withinYear =
shifted - year * DerethDateTime.YearTicks;
return Math.Clamp(
(int)(withinYear / DerethDateTime.DayTicks),
0,
DerethDateTime.DaysInAMonth
* DerethDateTime.MonthsInAYear
- 1);
}
private double Shift(double ticks)
{
if (!double.IsFinite(ticks))
throw new ArgumentOutOfRangeException(nameof(ticks));
return Math.Max(0d, ticks) + OriginOffsetTicks;
}
}