цикл день-ночь, кролики, кровавая луна

This commit is contained in:
azykov@mail.ru
2026-09-04 20:31:53 +03:00
parent 982bbce813
commit 396a604671
17 changed files with 1061 additions and 171 deletions
+86
View File
@@ -0,0 +1,86 @@
/** 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 20s day-night cycle with a 10s night. */
export const NIGHT_SPEED = NIGHT_HOURS / 10;
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)})`;
}