fish_lib/game/systems/
weather_system.rs

1use crate::game::systems::weather_system::attributes::WeatherAttributes;
2use crate::game::systems::weather_system::config::WeatherSystemConfig;
3use crate::game::systems::weather_system::weather::Weather;
4use crate::utils::math::float_interpolate;
5use chrono::{DateTime, Timelike};
6use chrono_tz::Tz;
7use noise::{NoiseFn, Perlin};
8
9pub mod attributes;
10pub mod config;
11pub mod weather;
12
13pub struct WeatherSystem {
14    cloudiness: Perlin,
15    cloud_brightness: Perlin,
16    moisture: Perlin,
17    wind_presence: Perlin,
18    wind_strength: Perlin,
19    temperature: Perlin,
20    rain_intensity: Perlin,
21    config: WeatherSystemConfig,
22}
23
24impl WeatherSystem {
25    pub fn new(config: WeatherSystemConfig) -> Self {
26        let seed = config.location_data.weather_seed;
27        Self {
28            cloudiness: Perlin::new(seed),
29            cloud_brightness: Perlin::new(seed * 2),
30            moisture: Perlin::new(seed * 3),
31            wind_presence: Perlin::new(seed * 4),
32            wind_strength: Perlin::new(seed * 5),
33            temperature: Perlin::new(seed * 6),
34            rain_intensity: Perlin::new(seed * 7),
35            config,
36        }
37    }
38
39    fn time_to_noise_input(time: DateTime<Tz>) -> f64 {
40        time.timestamp() as f64 / 1_000_000.0
41    }
42
43    fn normalize_noise(noise: f64) -> f32 {
44        (noise as f32 + 1.0) / 2.0
45    }
46
47    /// Ensures its hottest at the middle of the day
48    pub fn light_level(time: DateTime<Tz>) -> f32 {
49        let hour = time.hour() as f32 + (time.minute() as f32 / 60.0);
50        let multiplier = (((hour - 6.0) * std::f32::consts::PI / 12.0).sin() * 0.45) + 0.55;
51        multiplier.clamp(0.1, 1.0)
52    }
53
54    pub fn get_weather_attributes(&self, time: DateTime<Tz>) -> WeatherAttributes {
55        let t = Self::time_to_noise_input(time);
56
57        let cloudiness_noise = self.cloudiness.get([t * 5.5, 0.0]);
58        let cloud_brightness_noise = self.cloud_brightness.get([t * 2.5, 1.0]);
59        let moisture_noise = self.moisture.get([t * 3.25, 1_000_000.0]);
60        let wind_presence_noise = self.wind_presence.get([t * 40.0, t * 50.0]);
61        let wind_strength_noise = self.wind_strength.get([t * 4.5, 5_000_000.0]);
62        let temperature_noise = self.temperature.get([t, 2_000_000.0]);
63        let rain_intensity_noise = self.rain_intensity.get([t, 0.0]);
64
65        let moisture = Self::normalize_noise(moisture_noise);
66        let rain_intensity = Self::normalize_noise(rain_intensity_noise);
67
68        let wind_strength = Self::normalize_noise(wind_strength_noise);
69        let wind_presence = Self::normalize_noise(wind_presence_noise);
70
71        let cloudiness = Self::normalize_noise(cloudiness_noise);
72        let cloud_brightness_raw = Self::normalize_noise(cloud_brightness_noise);
73
74        // Ensure that lower cloud brightness only occurs with high cloudiness
75        let cloud_brightness =
76            (cloud_brightness_raw * (1.0 - cloudiness) + 1.0 * (1.0 - cloudiness)).clamp(0.0, 1.0);
77
78        let raw_light = Self::light_level(time);
79
80        let cloud_light_blocking =
81            cloudiness * (1.0 - cloud_brightness * self.config.cloud_brightness_light_block_factor);
82        let light = raw_light * (1.0 - cloud_light_blocking);
83        let temperature = Self::normalize_noise(temperature_noise) * raw_light;
84
85        WeatherAttributes {
86            cloudiness,
87            cloud_brightness,
88            moisture,
89            wind_presence,
90            wind_strength,
91            temperature,
92            light,
93            rain_intensity,
94        }
95    }
96
97    pub fn get_weather(&self, time: DateTime<Tz>, time_multiplier: f32) -> Weather {
98        let attributes = self.get_weather_attributes(time);
99        let (season_data, season, season_progress) = self
100            .config
101            .location_data
102            .full_season_information(time, time_multiplier);
103
104        let raining_rain_intensity_met =
105            attributes.rain_intensity > season_data.rain_intensity_raining_threshold;
106        let raining_moisture_met = attributes.moisture > season_data.moisture_raining_threshold;
107        let raining_cloudiness_met =
108            attributes.cloudiness > season_data.cloudiness_raining_threshold;
109        let is_raining =
110            raining_cloudiness_met && raining_moisture_met && raining_rain_intensity_met;
111        let rain_strength = if is_raining {
112            (attributes.rain_intensity - season_data.rain_intensity_raining_threshold)
113                / (1.0 - season_data.rain_intensity_raining_threshold)
114        } else {
115            0.0
116        };
117
118        Weather {
119            location_name: self.config.location_data.name.clone(),
120            time,
121            season,
122            season_progress,
123            temperature_c: float_interpolate(
124                season_data.min_temp_c,
125                season_data.max_temp_c,
126                attributes.temperature,
127            ),
128            min_possible_temp_c: season_data.min_temp_c,
129            max_possible_temp_c: season_data.max_temp_c,
130            humidity: attributes.moisture,
131            light_level: attributes.light,
132            cloudiness: attributes.cloudiness,
133            cloud_brightness: attributes.cloud_brightness,
134            is_raining,
135            rain_strength,
136        }
137    }
138}