Implement weather effects and day/night cycle mechanics in 2D&D
Two interrelated systems that affect encounters, combat, visuals, and audio.
src/systems/daynight.ts)| Period | Steps | Duration | Tint Color |
|---|---|---|---|
| Dawn | 0–44 | 45 steps | 0xffd5a0 (warm orange) |
| Day | 45–219 | 175 steps | 0xffffff (no tint) |
| Dusk | 220–264 | 45 steps | 0xffa570 (deep orange) |
| Night | 265–359 | 95 steps | (cool blue) |
0x6688ccimport {
TimePeriod, // enum: Dawn, Day, Dusk, Night
CYCLE_LENGTH, // 360
getTimePeriod, // (step: number) → TimePeriod
getEncounterMultiplier, // Dawn/Day=1.0, Dusk=1.25, Night=1.5
isNightTime, // true during Dusk and Night
PERIOD_TINT, // Record<TimePeriod, 0xRRGGBB>
PERIOD_LABEL, // Record<TimePeriod, "🌅 Dawn" etc.>
} from "../systems/daynight";
applyDayNightTint()timeStep is persisted in save data and passed through all scene transitionssrc/systems/weather.ts)| Type | Visual | Audio | Combat Effect |
|---|---|---|---|
| Clear | None | None | None |
| Rain | Blue rain particles | Lowpass filtered noise | Accuracy penalty |
| Snow | White slow particles | Soft highpass noise | Accuracy penalty |
| Sandstorm | Tan fast horizontal | Bandpass midrange noise | Accuracy penalty |
| Storm | Heavy rain + lightning | Heavy noise + thunder rumble | Accuracy + monster boost |
| Fog | Large slow blobs | Low sine drone | Accuracy penalty |
interface WeatherState {
current: WeatherType;
stepsUntilChange: number; // Countdown to next weather check
}
// Create fresh state (starts Clear, 40 steps until first check)
const state = createWeatherState();
Each chunk name maps to weather weights. Examples:
import {
WeatherType,
createWeatherState,
advanceWeather, // Step-based countdown, may change weather
changeZoneWeather, // Called on chunk transition
getWeatherAccuracyPenalty, // Accuracy penalty for combat
getWeatherEncounterMultiplier, // Encounter rate modifier
getMonsterWeatherBoost, // Per-monster AC/ATK/DMG bonuses
WEATHER_TINT, // Tint colors per weather type
WEATHER_LABEL, // HUD labels per weather type
} from "../systems/weather";
// In combat, apply weather penalties:
const weatherPenalty = getWeatherAccuracyPenalty(weatherState.current);
const boost = getMonsterWeatherBoost(monster.id, weatherState.current);
// boost.acBonus, boost.attackBonus, boost.damageBonus
WeatherType enumBIOME_WEATHER recordsWEATHER_ACCURACY_PENALTYWEATHER_ENCOUNTER_MULTWEATHER_TINTWEATHER_LABELupdateWeatherParticles() (in renderers/map.ts) and Battle.createWeatherParticles()audioEngine.playWeatherSFX()Both systems' state must be passed through every scene transition:
this.scene.start("NextScene", {
player: this.player,
defeatedBosses: this.defeatedBosses,
codex: this.codex,
timeStep: this.timeStep, // Day/night position
weatherState: this.weatherState, // Current weather + countdown
});
weatherState in scene transitions — it resets to Clear otherwiseWeatherType.ClearrerollWeather() on chunk transitionsgetTimePeriod() and constants