1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "Climate.h"
11 
12 #include "../Cheats.h"
13 #include "../Context.h"
14 #include "../Game.h"
15 #include "../OpenRCT2.h"
16 #include "../audio/AudioMixer.h"
17 #include "../audio/audio.h"
18 #include "../config/Config.h"
19 #include "../drawing/Drawing.h"
20 #include "../interface/Window.h"
21 #include "../localisation/Date.h"
22 #include "../scenario/Scenario.h"
23 #include "../sprites.h"
24 #include "../util/Util.h"
25 #include "../windows/Intent.h"
26 
27 #include <algorithm>
28 #include <iterator>
29 
30 constexpr int32_t MAX_THUNDER_INSTANCES = 2;
31 
32 enum class THUNDER_STATUS
33 {
34     NONE,
35     PLAYING,
36 };
37 
38 struct WeatherTransition
39 {
40     int8_t BaseTemperature;
41     int8_t DistributionSize;
42     WeatherType Distribution[24];
43 };
44 
45 extern const WeatherTransition* ClimateTransitions[4];
46 extern const WeatherState ClimateWeatherData[EnumValue(WeatherType::Count)];
47 extern const FilterPaletteID ClimateWeatherGloomColours[4];
48 
49 // Climate data
50 ClimateType gClimate;
51 ClimateState gClimateCurrent;
52 ClimateState gClimateNext;
53 uint16_t gClimateUpdateTimer;
54 uint16_t gClimateLightningFlash;
55 
56 // Sound data
57 static int32_t _weatherVolume = 1;
58 static uint32_t _lightningTimer;
59 static uint32_t _thunderTimer;
60 static void* _thunderSoundChannels[MAX_THUNDER_INSTANCES];
61 static THUNDER_STATUS _thunderStatus[MAX_THUNDER_INSTANCES] = {
62     THUNDER_STATUS::NONE,
63     THUNDER_STATUS::NONE,
64 };
65 static OpenRCT2::Audio::SoundId _thunderSoundId;
66 static int32_t _thunderVolume;
67 static int32_t _thunderStereoEcho = 0;
68 
69 static int8_t climate_step_weather_level(int8_t currentWeatherLevel, int8_t nextWeatherLevel);
70 static void climate_determine_future_weather(int32_t randomDistribution);
71 static void climate_update_weather_sound();
72 static void climate_update_thunder_sound();
73 static void climate_update_lightning();
74 static void climate_update_thunder();
75 static void climate_play_thunder(int32_t instanceIndex, OpenRCT2::Audio::SoundId soundId, int32_t volume, int32_t pan);
76 
climate_celsius_to_fahrenheit(int32_t celsius)77 int32_t climate_celsius_to_fahrenheit(int32_t celsius)
78 {
79     return (celsius * 29) / 16 + 32;
80 }
81 
82 /**
83  * Set climate and determine start weather.
84  */
climate_reset(ClimateType climate)85 void climate_reset(ClimateType climate)
86 {
87     auto weather = WeatherType::PartiallyCloudy;
88     int32_t month = date_get_month(gDateMonthsElapsed);
89     const WeatherTransition* transition = &ClimateTransitions[static_cast<uint8_t>(climate)][month];
90     const WeatherState* weatherState = &ClimateWeatherData[EnumValue(weather)];
91 
92     gClimate = climate;
93     gClimateCurrent.Weather = weather;
94     gClimateCurrent.Temperature = transition->BaseTemperature + weatherState->TemperatureDelta;
95     gClimateCurrent.WeatherEffect = weatherState->EffectLevel;
96     gClimateCurrent.WeatherGloom = weatherState->GloomLevel;
97     gClimateCurrent.Level = weatherState->Level;
98 
99     _lightningTimer = 0;
100     _thunderTimer = 0;
101     if (_weatherVolume != 1)
102     {
103         OpenRCT2::Audio::StopWeatherSound();
104         _weatherVolume = 1;
105     }
106 
107     climate_determine_future_weather(scenario_rand());
108 }
109 
110 /**
111  * Weather & climate update iteration.
112  * Gradually changes the weather parameters towards their determined next values.
113  */
climate_update()114 void climate_update()
115 {
116     // Only do climate logic if playing (not in scenario editor or title screen)
117     if (gScreenFlags & (~SCREEN_FLAGS_PLAYING))
118         return;
119 
120     if (!gCheatsFreezeWeather)
121     {
122         if (gClimateUpdateTimer)
123         {
124             if (gClimateUpdateTimer == 960)
125             {
126                 auto intent = Intent(INTENT_ACTION_UPDATE_CLIMATE);
127                 context_broadcast_intent(&intent);
128             }
129             gClimateUpdateTimer--;
130         }
131         else if (!(gCurrentTicks & 0x7F))
132         {
133             if (gClimateCurrent.Temperature == gClimateNext.Temperature)
134             {
135                 if (gClimateCurrent.WeatherGloom == gClimateNext.WeatherGloom)
136                 {
137                     gClimateCurrent.WeatherEffect = gClimateNext.WeatherEffect;
138                     _thunderTimer = 0;
139                     _lightningTimer = 0;
140 
141                     if (gClimateCurrent.Level == gClimateNext.Level)
142                     {
143                         gClimateCurrent.Weather = gClimateNext.Weather;
144                         climate_determine_future_weather(scenario_rand());
145                         auto intent = Intent(INTENT_ACTION_UPDATE_CLIMATE);
146                         context_broadcast_intent(&intent);
147                     }
148                     else if (gClimateNext.Level <= WeatherLevel::Heavy)
149                     {
150                         gClimateCurrent.Level = static_cast<WeatherLevel>(climate_step_weather_level(
151                             static_cast<int8_t>(gClimateCurrent.Level), static_cast<int8_t>(gClimateNext.Level)));
152                     }
153                 }
154                 else
155                 {
156                     gClimateCurrent.WeatherGloom = climate_step_weather_level(
157                         gClimateCurrent.WeatherGloom, gClimateNext.WeatherGloom);
158                     gfx_invalidate_screen();
159                 }
160             }
161             else
162             {
163                 gClimateCurrent.Temperature = climate_step_weather_level(gClimateCurrent.Temperature, gClimateNext.Temperature);
164                 auto intent = Intent(INTENT_ACTION_UPDATE_CLIMATE);
165                 context_broadcast_intent(&intent);
166             }
167         }
168     }
169 
170     if (_thunderTimer != 0)
171     {
172         climate_update_lightning();
173         climate_update_thunder();
174     }
175     else if (
176         gClimateCurrent.WeatherEffect == WeatherEffectType::Storm
177         || gClimateCurrent.WeatherEffect == WeatherEffectType::Blizzard)
178     {
179         // Create new thunder and lightning
180         uint32_t randomNumber = util_rand();
181         if ((randomNumber & 0xFFFF) <= 0x1B4)
182         {
183             randomNumber >>= 16;
184             _thunderTimer = 43 + (randomNumber % 64);
185             _lightningTimer = randomNumber % 32;
186         }
187     }
188 }
189 
climate_force_weather(WeatherType weather)190 void climate_force_weather(WeatherType weather)
191 {
192     int32_t month = date_get_month(gDateMonthsElapsed);
193     const WeatherTransition* transition = &ClimateTransitions[static_cast<uint8_t>(gClimate)][month];
194     const auto weatherState = &ClimateWeatherData[EnumValue(weather)];
195 
196     gClimateCurrent.Weather = weather;
197     gClimateCurrent.WeatherGloom = weatherState->GloomLevel;
198     gClimateCurrent.Level = weatherState->Level;
199     gClimateCurrent.WeatherEffect = weatherState->EffectLevel;
200     gClimateCurrent.Temperature = transition->BaseTemperature + weatherState->TemperatureDelta;
201     gClimateUpdateTimer = 1920;
202 
203     climate_update();
204 
205     // In case of change in gloom level force a complete redraw
206     gfx_invalidate_screen();
207 }
208 
climate_update_sound()209 void climate_update_sound()
210 {
211     if (!OpenRCT2::Audio::IsAvailable())
212         return;
213 
214     if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)
215         return;
216 
217     climate_update_weather_sound();
218     climate_update_thunder_sound();
219 }
220 
climate_is_raining()221 bool climate_is_raining()
222 {
223     if (gClimateCurrent.Weather == WeatherType::Rain || gClimateCurrent.Weather == WeatherType::HeavyRain
224         || gClimateCurrent.Weather == WeatherType::Thunder)
225     {
226         return true;
227     }
228 
229     return false;
230 }
231 
climate_is_snowing()232 bool climate_is_snowing()
233 {
234     if (gClimateCurrent.Weather == WeatherType::Snow || gClimateCurrent.Weather == WeatherType::HeavySnow
235         || gClimateCurrent.Weather == WeatherType::Blizzard)
236     {
237         return true;
238     }
239 
240     return false;
241 }
242 
WeatherIsDry(WeatherType weatherType)243 bool WeatherIsDry(WeatherType weatherType)
244 {
245     return weatherType == WeatherType::Sunny || weatherType == WeatherType::PartiallyCloudy
246         || weatherType == WeatherType::Cloudy;
247 }
248 
climate_get_weather_gloom_palette_id(const ClimateState & state)249 FilterPaletteID climate_get_weather_gloom_palette_id(const ClimateState& state)
250 {
251     auto paletteId = FilterPaletteID::PaletteNull;
252     auto gloom = state.WeatherGloom;
253     if (gloom < std::size(ClimateWeatherGloomColours))
254     {
255         paletteId = ClimateWeatherGloomColours[gloom];
256     }
257     return paletteId;
258 }
259 
climate_get_weather_sprite_id(const ClimateState & state)260 uint32_t climate_get_weather_sprite_id(const ClimateState& state)
261 {
262     uint32_t spriteId = SPR_WEATHER_SUN;
263     if (EnumValue(state.Weather) < std::size(ClimateWeatherData))
264     {
265         spriteId = ClimateWeatherData[EnumValue(state.Weather)].SpriteId;
266     }
267     return spriteId;
268 }
269 
climate_step_weather_level(int8_t currentWeatherLevel,int8_t nextWeatherLevel)270 static int8_t climate_step_weather_level(int8_t currentWeatherLevel, int8_t nextWeatherLevel)
271 {
272     if (nextWeatherLevel > currentWeatherLevel)
273     {
274         return currentWeatherLevel + 1;
275     }
276 
277     return currentWeatherLevel - 1;
278 }
279 
280 /**
281  * Calculates future weather development.
282  * RCT2 implements this as discrete probability distributions dependent on month and climate
283  * for nextWeather. The other weather parameters are then looked up depending only on the
284  * next weather.
285  */
climate_determine_future_weather(int32_t randomDistribution)286 static void climate_determine_future_weather(int32_t randomDistribution)
287 {
288     int32_t month = date_get_month(gDateMonthsElapsed);
289 
290     // Generate a random variable with values 0 up to DistributionSize-1 and chose weather from the distribution table
291     // accordingly
292     const WeatherTransition* transition = &ClimateTransitions[static_cast<uint8_t>(gClimate)][month];
293     WeatherType nextWeather = (transition->Distribution[((randomDistribution & 0xFF) * transition->DistributionSize) >> 8]);
294     gClimateNext.Weather = nextWeather;
295 
296     const auto nextWeatherState = &ClimateWeatherData[EnumValue(nextWeather)];
297     gClimateNext.Temperature = transition->BaseTemperature + nextWeatherState->TemperatureDelta;
298     gClimateNext.WeatherEffect = nextWeatherState->EffectLevel;
299     gClimateNext.WeatherGloom = nextWeatherState->GloomLevel;
300     gClimateNext.Level = nextWeatherState->Level;
301 
302     gClimateUpdateTimer = 1920;
303 }
304 
climate_update_weather_sound()305 static void climate_update_weather_sound()
306 {
307     if (gClimateCurrent.WeatherEffect == WeatherEffectType::Rain || gClimateCurrent.WeatherEffect == WeatherEffectType::Storm)
308     {
309         // Start playing the weather sound
310         if (OpenRCT2::Audio::gWeatherSoundChannel == nullptr)
311         {
312             OpenRCT2::Audio::gWeatherSoundChannel = Mixer_Play_Effect(
313                 OpenRCT2::Audio::SoundId::Rain, MIXER_LOOP_INFINITE, DStoMixerVolume(-4000), 0.5f, 1, 0);
314         }
315         if (_weatherVolume == 1)
316         {
317             _weatherVolume = -4000;
318         }
319         else
320         {
321             // Increase weather sound
322             _weatherVolume = std::min(-1400, _weatherVolume + 80);
323             if (OpenRCT2::Audio::gWeatherSoundChannel != nullptr)
324             {
325                 Mixer_Channel_Volume(OpenRCT2::Audio::gWeatherSoundChannel, DStoMixerVolume(_weatherVolume));
326             }
327         }
328     }
329     else if (_weatherVolume != 1)
330     {
331         // Decrease weather sound
332         _weatherVolume -= 80;
333         if (_weatherVolume > -4000)
334         {
335             if (OpenRCT2::Audio::gWeatherSoundChannel != nullptr)
336             {
337                 Mixer_Channel_Volume(OpenRCT2::Audio::gWeatherSoundChannel, DStoMixerVolume(_weatherVolume));
338             }
339         }
340         else
341         {
342             OpenRCT2::Audio::StopWeatherSound();
343             _weatherVolume = 1;
344         }
345     }
346 }
347 
climate_update_thunder_sound()348 static void climate_update_thunder_sound()
349 {
350     if (_thunderStereoEcho)
351     {
352         // Play thunder on right side
353         _thunderStereoEcho = 0;
354         climate_play_thunder(1, _thunderSoundId, _thunderVolume, 10000);
355     }
356 
357     // Stop thunder sounds if they have finished
358     for (int32_t i = 0; i < MAX_THUNDER_INSTANCES; i++)
359     {
360         if (_thunderStatus[i] != THUNDER_STATUS::NONE)
361         {
362             void* channel = _thunderSoundChannels[i];
363             if (!Mixer_Channel_IsPlaying(channel))
364             {
365                 Mixer_Stop_Channel(channel);
366                 _thunderStatus[i] = THUNDER_STATUS::NONE;
367             }
368         }
369     }
370 }
371 
climate_update_lightning()372 static void climate_update_lightning()
373 {
374     if (_lightningTimer == 0)
375         return;
376     if (gConfigGeneral.disable_lightning_effect)
377         return;
378     if (!gConfigGeneral.render_weather_effects && !gConfigGeneral.render_weather_gloom)
379         return;
380 
381     _lightningTimer--;
382     if (gClimateLightningFlash == 0)
383     {
384         if ((util_rand() & 0xFFFF) <= 0x2000)
385         {
386             gClimateLightningFlash = 1;
387         }
388     }
389 }
390 
climate_update_thunder()391 static void climate_update_thunder()
392 {
393     _thunderTimer--;
394     if (_thunderTimer == 0)
395     {
396         uint32_t randomNumber = util_rand();
397         if (randomNumber & 0x10000)
398         {
399             if (_thunderStatus[0] == THUNDER_STATUS::NONE && _thunderStatus[1] == THUNDER_STATUS::NONE)
400             {
401                 // Play thunder on left side
402                 _thunderSoundId = (randomNumber & 0x20000) ? OpenRCT2::Audio::SoundId::Thunder1
403                                                            : OpenRCT2::Audio::SoundId::Thunder2;
404                 _thunderVolume = (-(static_cast<int32_t>((randomNumber >> 18) & 0xFF))) * 8;
405                 climate_play_thunder(0, _thunderSoundId, _thunderVolume, -10000);
406 
407                 // Let thunder play on right side
408                 _thunderStereoEcho = 1;
409             }
410         }
411         else
412         {
413             if (_thunderStatus[0] == THUNDER_STATUS::NONE)
414             {
415                 _thunderSoundId = (randomNumber & 0x20000) ? OpenRCT2::Audio::SoundId::Thunder1
416                                                            : OpenRCT2::Audio::SoundId::Thunder2;
417                 int32_t pan = (((randomNumber >> 18) & 0xFF) - 128) * 16;
418                 climate_play_thunder(0, _thunderSoundId, 0, pan);
419             }
420         }
421     }
422 }
423 
climate_play_thunder(int32_t instanceIndex,OpenRCT2::Audio::SoundId soundId,int32_t volume,int32_t pan)424 static void climate_play_thunder(int32_t instanceIndex, OpenRCT2::Audio::SoundId soundId, int32_t volume, int32_t pan)
425 {
426     _thunderSoundChannels[instanceIndex] = Mixer_Play_Effect(
427         soundId, MIXER_LOOP_NONE, DStoMixerVolume(volume), DStoMixerPan(pan), 1, 0);
428     if (_thunderSoundChannels[instanceIndex] != nullptr)
429     {
430         _thunderStatus[instanceIndex] = THUNDER_STATUS::PLAYING;
431     }
432 }
433 
434 #pragma region Climate / Weather data tables
435 
436 const FilterPaletteID ClimateWeatherGloomColours[4] = {
437     FilterPaletteID::PaletteNull,
438     FilterPaletteID::PaletteDarken1,
439     FilterPaletteID::PaletteDarken2,
440     FilterPaletteID::PaletteDarken3,
441 };
442 
443 // There is actually a sprite at 0x5A9C for snow but only these weather types seem to be fully implemented
444 const WeatherState ClimateWeatherData[EnumValue(WeatherType::Count)] = {
445     { 10, WeatherEffectType::None, 0, WeatherLevel::None, SPR_WEATHER_SUN },         // Sunny
446     { 5, WeatherEffectType::None, 0, WeatherLevel::None, SPR_WEATHER_SUN_CLOUD },    // Partially Cloudy
447     { 0, WeatherEffectType::None, 0, WeatherLevel::None, SPR_WEATHER_CLOUD },        // Cloudy
448     { -2, WeatherEffectType::Rain, 1, WeatherLevel::Light, SPR_WEATHER_LIGHT_RAIN }, // Rain
449     { -4, WeatherEffectType::Rain, 2, WeatherLevel::Heavy, SPR_WEATHER_HEAVY_RAIN }, // Heavy Rain
450     { 2, WeatherEffectType::Storm, 2, WeatherLevel::Heavy, SPR_WEATHER_STORM },      // Thunderstorm
451     { -10, WeatherEffectType::Snow, 1, WeatherLevel::Light, SPR_WEATHER_SNOW },      // Snow
452     { -15, WeatherEffectType::Snow, 2, WeatherLevel::Heavy, SPR_WEATHER_SNOW },      // Heavy Snow
453     { -20, WeatherEffectType::Blizzard, 2, WeatherLevel::Heavy, SPR_WEATHER_SNOW },  // Blizzard
454 };
455 
456 constexpr auto S = WeatherType::Sunny;
457 constexpr auto P = WeatherType::PartiallyCloudy;
458 constexpr auto C = WeatherType::Cloudy;
459 constexpr auto R = WeatherType::Rain;
460 constexpr auto H = WeatherType::HeavyRain;
461 constexpr auto T = WeatherType::Thunder;
462 
463 static constexpr const WeatherTransition ClimateTransitionsCoolAndWet[] = {
464     { 8, 18, { S, P, P, P, P, P, C, C, C, C, C, C, C, R, R, R, H, H, S, S, S, S, S } },
465     { 10, 21, { P, P, P, P, P, C, C, C, C, C, C, C, C, C, R, R, R, H, H, H, T, S, S } },
466     { 14, 17, { S, S, S, P, P, P, P, P, P, C, C, C, C, R, R, R, H, S, S, S, S, S, S } },
467     { 17, 17, { S, S, S, S, P, P, P, P, P, P, P, C, C, C, C, R, R, S, S, S, S, S, S } },
468     { 19, 23, { S, S, S, S, S, S, S, S, S, S, P, P, P, P, P, P, C, C, C, C, C, R, H } },
469     { 20, 23, { S, S, S, S, S, S, P, P, P, P, P, P, P, P, C, C, C, C, R, H, H, H, T } },
470     { 16, 19, { S, S, S, P, P, P, P, P, C, C, C, C, C, C, R, R, H, H, T, S, S, S, S } },
471     { 13, 16, { S, S, P, P, P, P, C, C, C, C, C, C, R, R, H, T, S, S, S, S, S, S, S } },
472 };
473 static constexpr const WeatherTransition ClimateTransitionsWarm[] = {
474     { 12, 21, { S, S, S, S, S, P, P, P, P, P, P, P, P, C, C, C, C, C, C, C, H, S, S } },
475     { 13, 22, { S, S, S, S, S, P, P, P, P, P, P, C, C, C, C, C, C, C, C, C, R, T, S } },
476     { 16, 17, { S, S, S, S, S, S, P, P, P, P, P, P, C, C, C, C, R, S, S, S, S, S, S } },
477     { 19, 18, { S, S, S, S, S, S, P, P, P, P, P, P, P, C, C, C, C, R, S, S, S, S, S } },
478     { 21, 22, { S, S, S, S, S, S, S, S, S, S, P, P, P, P, P, P, P, P, P, C, C, C, S } },
479     { 22, 17, { S, S, S, S, S, S, S, S, S, P, P, P, P, P, C, C, T, S, S, S, S, S, S } },
480     { 19, 17, { S, S, S, S, S, P, P, P, P, P, C, C, C, C, C, C, R, S, S, S, S, S, S } },
481     { 16, 17, { S, S, P, P, P, P, P, C, C, C, C, C, C, C, C, C, H, S, S, S, S, S, S } },
482 };
483 static constexpr const WeatherTransition ClimateTransitionsHotAndDry[] = {
484     { 12, 15, { S, S, S, S, P, P, P, P, P, P, P, P, C, C, R, S, S, S, S, S, S, S, S } },
485     { 14, 12, { S, S, S, S, S, P, P, P, P, P, C, C, S, S, S, S, S, S, S, S, S, S, S } },
486     { 16, 11, { S, S, S, S, S, S, P, P, P, P, C, S, S, S, S, S, S, S, S, S, S, S, S } },
487     { 19, 9, { S, S, S, S, S, S, P, P, P, S, S, S, S, S, S, S, S, S, S, S, S, S, S } },
488     { 21, 13, { S, S, S, S, S, S, S, S, S, S, P, P, P, S, S, S, S, S, S, S, S, S, S } },
489     { 22, 11, { S, S, S, S, S, S, S, S, S, P, P, S, S, S, S, S, S, S, S, S, S, S, S } },
490     { 21, 12, { S, S, S, S, S, S, S, P, P, P, C, T, S, S, S, S, S, S, S, S, S, S, S } },
491     { 16, 13, { S, S, S, S, S, S, S, S, P, P, P, C, R, S, S, S, S, S, S, S, S, S, S } },
492 };
493 static constexpr const WeatherTransition ClimateTransitionsCold[] = {
494     { 4, 18, { S, S, S, S, P, P, P, P, P, C, C, C, C, C, C, C, R, H, S, S, S, S, S } },
495     { 5, 21, { S, S, S, S, P, P, P, P, P, C, C, C, C, C, C, C, C, C, R, H, T, S, S } },
496     { 7, 17, { S, S, S, S, P, P, P, P, P, P, P, C, C, C, C, R, H, S, S, S, S, S, S } },
497     { 9, 17, { S, S, S, S, P, P, P, P, P, P, P, C, C, C, C, R, R, S, S, S, S, S, S } },
498     { 10, 23, { S, S, S, S, S, S, S, S, S, S, P, P, P, P, P, P, C, C, C, C, C, R, H } },
499     { 11, 23, { S, S, S, S, S, S, P, P, P, P, P, P, P, P, P, P, C, C, C, C, R, H, T } },
500     { 9, 19, { S, S, S, S, S, P, P, P, P, P, C, C, C, C, C, C, R, H, T, S, S, S, S } },
501     { 6, 16, { S, S, P, P, P, P, C, C, C, C, C, C, R, R, H, T, S, S, S, S, S, S, S } },
502 };
503 
504 const WeatherTransition* ClimateTransitions[] = {
505     ClimateTransitionsCoolAndWet,
506     ClimateTransitionsWarm,
507     ClimateTransitionsHotAndDry,
508     ClimateTransitionsCold,
509 };
510 
511 #pragma endregion
512