Files
game-forest/src/game/daynight.ts
T

87 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Night spans 22:00–06:00 (8 game-hours); the rest of the clock ("day", covering morning/midday/
* evening) is the other time-speed regime — see GameStore.updateClock. Lighting still varies
* continuously across all of these (see overlayColorForHour) even though only two speeds exist. */
export const NIGHT_START_HOUR = 22;
export const NIGHT_END_HOUR = 6;
export const NIGHT_HOURS = 24 - NIGHT_START_HOUR + NIGHT_END_HOUR;
export const DAY_HOURS = 24 - NIGHT_HOURS;
/** Fixed (non-upgradable) night time-flow speed, in game-hours per real second — derived from the
* default 10s night. */
export const NIGHT_SPEED = NIGHT_HOURS / 20;
export function isNightHour(hour: number): boolean {
return hour >= NIGHT_START_HOUR || hour < NIGHT_END_HOUR;
}
export const BLOOD_MOON_INTERVAL_DAYS = 5;
/** Seconds the "КРОВАВАЯ ЛУНА!" banner stays on screen once a blood moon night begins. */
export const BLOOD_MOON_BANNER_SECONDS = 4;
/** A single night spans a day-counter rollover (22:00 of day N through 06:00 of day N+1) — this
* says which day that night belongs to throughout, for period-based night events. */
export function nightBelongsToDay(day: number, hour: number): number {
return hour >= NIGHT_START_HOUR ? day : day - 1;
}
export function isBloodMoonNight(day: number, hour: number): boolean {
if (!isNightHour(hour))
return false;
const nightDay = nightBelongsToDay(day, hour);
return nightDay > 0 && nightDay % BLOOD_MOON_INTERVAL_DAYS === 0;
}
/** Formats an in-game clock hour (0-24, fractional) as "HH:MM". */
export function formatClock(hour: number): string {
const totalMinutes = Math.round(hour * 60);
const wrapped = ((totalMinutes % (24 * 60)) + 24 * 60) % (24 * 60);
const h = Math.floor(wrapped / 60);
const m = wrapped % 60;
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
}
interface OverlayKeyframe {
hour: number;
r: number;
g: number;
b: number;
a: number;
}
// Night (dark) -> dawn (warm glow) -> day (clear) -> dusk (warm glow) -> night, keyed by hour.
const OVERLAY_KEYFRAMES: OverlayKeyframe[] = [
{ hour: 0, r: 10, g: 10, b: 35, a: 0.55 },
{ hour: 5, r: 10, g: 10, b: 35, a: 0.55 },
{ hour: 6, r: 255, g: 170, b: 110, a: 0.3 },
{ hour: 9, r: 0, g: 0, b: 0, a: 0 },
{ hour: 18, r: 0, g: 0, b: 0, a: 0 },
{ hour: 19, r: 255, g: 140, b: 70, a: 0.3 },
{ hour: 22, r: 10, g: 10, b: 35, a: 0.55 },
{ hour: 24, r: 10, g: 10, b: 35, a: 0.55 },
];
/** The ambient darkness/tint overlay for the given time of day, as a CSS rgba() string —
* interpolated piecewise-linearly between OVERLAY_KEYFRAMES. */
export function overlayColorForHour(hour: number): string {
const h = ((hour % 24) + 24) % 24;
let prev = OVERLAY_KEYFRAMES[0];
let next = OVERLAY_KEYFRAMES[OVERLAY_KEYFRAMES.length - 1];
for (let i = 0; i < OVERLAY_KEYFRAMES.length - 1; i++) {
if (h >= OVERLAY_KEYFRAMES[i].hour && h <= OVERLAY_KEYFRAMES[i + 1].hour) {
prev = OVERLAY_KEYFRAMES[i];
next = OVERLAY_KEYFRAMES[i + 1];
break;
}
}
const span = next.hour - prev.hour;
const t = span === 0 ? 0 : (h - prev.hour) / span;
const lerp = (a: number, b: number) => a + (b - a) * t;
const r = lerp(prev.r, next.r);
const g = lerp(prev.g, next.g);
const b = lerp(prev.b, next.b);
const a = lerp(prev.a, next.a);
return `rgba(${r.toFixed(0)}, ${g.toFixed(0)}, ${b.toFixed(0)}, ${a.toFixed(3)})`;
}