#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * * OpenRCT2 is the work of many authors, a full list can be found in contributors.md * For more information, visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * A full copy of the GNU General Public License can be found in licence.txt *****************************************************************************/ #pragma endregion #include "../audio/audio.h" #include "../audio/AudioMixer.h" #include "../Cheats.h" #include "../config/Config.h" #include "../core/Math.hpp" #include "../core/Util.hpp" #include "../drawing/Drawing.h" #include "../Game.h" #include "../interface/Window.h" #include "../localisation/Date.h" #include "../OpenRCT2.h" #include "../scenario/Scenario.h" #include "../sprites.h" #include "../util/Util.h" #include "Climate.h" #include "../windows/Intent.h" #include "../Context.h" constexpr sint32 MAX_THUNDER_INSTANCES = 2; enum class THUNDER_STATUS { NONE, PLAYING, }; struct WeatherTransition { sint8 BaseTemperature; sint8 DistributionSize; sint8 Distribution[24]; }; extern const WeatherTransition * ClimateTransitions[4]; extern const WeatherState ClimateWeatherData[6]; extern const FILTER_PALETTE_ID ClimateWeatherGloomColours[4]; // Climate data uint8 gClimate; ClimateState gClimateCurrent; ClimateState gClimateNext; uint16 gClimateUpdateTimer; uint16 gClimateLightningFlash; // Sound data static sint32 _rainVolume = 1; static uint32 _lightningTimer; static uint32 _thunderTimer; static void * _thunderSoundChannels[MAX_THUNDER_INSTANCES]; static THUNDER_STATUS _thunderStatus[MAX_THUNDER_INSTANCES] = { THUNDER_STATUS::NONE, THUNDER_STATUS::NONE }; static uint32 _thunderSoundId; static sint32 _thunderVolume; static sint32 _thunderStereoEcho = 0; static sint8 climate_step_weather_level(sint8 currentWeatherLevel, sint8 nextWeatherLevel); static void climate_determine_future_weather(sint32 randomDistribution); static void climate_update_rain_sound(); static void climate_update_thunder_sound(); static void climate_update_lightning(); static void climate_update_thunder(); static void climate_play_thunder(sint32 instanceIndex, sint32 soundId, sint32 volume, sint32 pan); sint32 climate_celsius_to_fahrenheit(sint32 celsius) { return (celsius * 29) / 16 + 32; } /** * Set climate and determine start weather. */ void climate_reset(sint32 climate) { uint8 weather = WEATHER_PARTIALLY_CLOUDY; sint32 month = date_get_month(gDateMonthsElapsed); const WeatherTransition * transition = &ClimateTransitions[climate][month]; const WeatherState * weatherState = &ClimateWeatherData[weather]; gClimate = climate; gClimateCurrent.Weather = weather; gClimateCurrent.Temperature = transition->BaseTemperature + weatherState->TemperatureDelta; gClimateCurrent.WeatherEffect = weatherState->EffectLevel; gClimateCurrent.WeatherGloom = weatherState->GloomLevel; gClimateCurrent.RainLevel = weatherState->RainLevel; _lightningTimer = 0; _thunderTimer = 0; if (_rainVolume != 1) { audio_stop_rain_sound(); _rainVolume = 1; } climate_determine_future_weather(scenario_rand()); } /** * Weather & climate update iteration. * Gradually changes the weather parameters towards their determined next values. */ void climate_update() { // Only do climate logic if playing (not in scenario editor or title screen) if (gScreenFlags & (~SCREEN_FLAGS_PLAYING)) return; if (!gCheatsFreezeClimate) { if (gClimateUpdateTimer) { if (gClimateUpdateTimer == 960) { auto intent = Intent(INTENT_ACTION_UPDATE_CLIMATE); context_broadcast_intent(&intent); } gClimateUpdateTimer--; } else if (!(gCurrentTicks & 0x7F)) { if (gClimateCurrent.Temperature == gClimateNext.Temperature) { if (gClimateCurrent.WeatherGloom == gClimateNext.WeatherGloom) { gClimateCurrent.WeatherEffect = gClimateNext.WeatherEffect; _thunderTimer = 0; _lightningTimer = 0; if (gClimateCurrent.RainLevel == gClimateNext.RainLevel) { gClimateCurrent.Weather = gClimateNext.Weather; climate_determine_future_weather(scenario_rand()); auto intent = Intent(INTENT_ACTION_UPDATE_CLIMATE); context_broadcast_intent(&intent); } else if (gClimateNext.RainLevel <= RAIN_LEVEL_HEAVY) { gClimateCurrent.RainLevel = climate_step_weather_level(gClimateCurrent.RainLevel, gClimateNext.RainLevel); } } else { gClimateCurrent.WeatherGloom = climate_step_weather_level(gClimateCurrent.WeatherGloom, gClimateNext.WeatherGloom); gfx_invalidate_screen(); } } else { gClimateCurrent.Temperature = climate_step_weather_level(gClimateCurrent.Temperature, gClimateNext.Temperature); auto intent = Intent(INTENT_ACTION_UPDATE_CLIMATE); context_broadcast_intent(&intent); } } } if (_thunderTimer != 0) { climate_update_lightning(); climate_update_thunder(); } else if (gClimateCurrent.WeatherEffect == WEATHER_EFFECT_STORM) { // Create new thunder and lightning uint32 randomNumber = util_rand(); if ((randomNumber & 0xFFFF) <= 0x1B4) { randomNumber >>= 16; _thunderTimer = 43 + (randomNumber % 64); _lightningTimer = randomNumber % 32; } } } void climate_force_weather(uint8 weather) { const auto weatherState = &ClimateWeatherData[weather]; gClimateCurrent.Weather = weather; gClimateCurrent.WeatherGloom = weatherState->GloomLevel; gClimateCurrent.RainLevel = weatherState->RainLevel; gClimateCurrent.WeatherEffect = weatherState->EffectLevel; gClimateUpdateTimer = 1920; climate_update(); // In case of change in gloom level force a complete redraw gfx_invalidate_screen(); } void climate_update_sound() { if (gAudioCurrentDevice == -1) return; if (gGameSoundsOff) return; if (!gConfigSound.sound_enabled) return; if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) return; climate_update_rain_sound(); climate_update_thunder_sound(); } bool climate_is_raining() { return gClimateCurrent.RainLevel != RAIN_LEVEL_NONE; } FILTER_PALETTE_ID climate_get_weather_gloom_palette_id(const ClimateState &state) { auto paletteId = PALETTE_NULL; auto gloom = state.WeatherGloom; if (gloom < Util::CountOf(ClimateWeatherGloomColours)) { paletteId = ClimateWeatherGloomColours[gloom]; } return paletteId; } uint32 climate_get_weather_sprite_id(const ClimateState &state) { uint32 spriteId = SPR_WEATHER_SUN; if (state.Weather < Util::CountOf(ClimateWeatherData)) { spriteId = ClimateWeatherData[state.Weather].SpriteId; } return spriteId; } static sint8 climate_step_weather_level(sint8 currentWeatherLevel, sint8 nextWeatherLevel) { if (nextWeatherLevel > currentWeatherLevel) { return currentWeatherLevel + 1; } else { return currentWeatherLevel - 1; } } /** * Calculates future weather development. * RCT2 implements this as discrete probability distributions dependant on month and climate * for nextWeather. The other weather parameters are then looked up depending only on the * next weather. */ static void climate_determine_future_weather(sint32 randomDistribution) { sint8 month = date_get_month(gDateMonthsElapsed); // Generate a random variable with values 0 up to DistributionSize-1 and chose weather from the distribution table accordingly const WeatherTransition * transition = &ClimateTransitions[gClimate][month]; sint8 nextWeather = transition->Distribution[((randomDistribution & 0xFF) * transition->DistributionSize) >> 8]; gClimateNext.Weather = nextWeather; const auto nextWeatherState = &ClimateWeatherData[nextWeather]; gClimateNext.Temperature = transition->BaseTemperature + nextWeatherState->TemperatureDelta; gClimateNext.WeatherEffect = nextWeatherState->EffectLevel; gClimateNext.WeatherGloom = nextWeatherState->GloomLevel; gClimateNext.RainLevel = nextWeatherState->RainLevel; gClimateUpdateTimer = 1920; } static void climate_update_rain_sound() { if (gClimateCurrent.WeatherEffect == WEATHER_EFFECT_RAIN || gClimateCurrent.WeatherEffect == WEATHER_EFFECT_STORM) { // Start playing the rain sound if (gRainSoundChannel == nullptr) { gRainSoundChannel = Mixer_Play_Effect(SOUND_RAIN_1, MIXER_LOOP_INFINITE, DStoMixerVolume(-4000), 0.5f, 1, 0); } if (_rainVolume == 1) { _rainVolume = -4000; } else { // Increase rain sound _rainVolume = Math::Min(-1400, _rainVolume + 80); if (gRainSoundChannel != nullptr) { Mixer_Channel_Volume(gRainSoundChannel, DStoMixerVolume(_rainVolume)); } } } else if (_rainVolume != 1) { // Decrease rain sound _rainVolume -= 80; if (_rainVolume > -4000) { if (gRainSoundChannel != nullptr) { Mixer_Channel_Volume(gRainSoundChannel, DStoMixerVolume(_rainVolume)); } } else { audio_stop_rain_sound(); _rainVolume = 1; } } } static void climate_update_thunder_sound() { if (_thunderStereoEcho) { // Play thunder on right side _thunderStereoEcho = 0; climate_play_thunder(1, _thunderSoundId, _thunderVolume, 10000); } // Stop thunder sounds if they have finished for (sint32 i = 0; i < MAX_THUNDER_INSTANCES; i++) { if (_thunderStatus[i] != THUNDER_STATUS::NONE) { void * channel = _thunderSoundChannels[i]; if (!Mixer_Channel_IsPlaying(channel)) { Mixer_Stop_Channel(channel); _thunderStatus[i] = THUNDER_STATUS::NONE; } } } } static void climate_update_lightning() { if (_lightningTimer == 0) return; if (gConfigGeneral.disable_lightning_effect) return; if (!gConfigGeneral.render_weather_effects && !gConfigGeneral.render_weather_gloom) return; _lightningTimer--; if (gClimateLightningFlash == 0) { if ((util_rand() & 0xFFFF) <= 0x2000) { gClimateLightningFlash = 1; } } } static void climate_update_thunder() { _thunderTimer--; if (_thunderTimer == 0) { uint32 randomNumber = util_rand(); if (randomNumber & 0x10000) { if (_thunderStatus[0] == THUNDER_STATUS::NONE && _thunderStatus[1] == THUNDER_STATUS::NONE) { // Play thunder on left side _thunderSoundId = (randomNumber & 0x20000) ? SOUND_THUNDER_1 : SOUND_THUNDER_2; _thunderVolume = (-((sint32)((randomNumber >> 18) & 0xFF))) * 8; climate_play_thunder(0, _thunderSoundId, _thunderVolume, -10000); // Let thunder play on right side _thunderStereoEcho = 1; } } else { if (_thunderStatus[0] == THUNDER_STATUS::NONE) { _thunderSoundId = (randomNumber & 0x20000) ? SOUND_THUNDER_1 : SOUND_THUNDER_2; sint32 pan = (((randomNumber >> 18) & 0xFF) - 128) * 16; climate_play_thunder(0, _thunderSoundId, 0, pan); } } } } static void climate_play_thunder(sint32 instanceIndex, sint32 soundId, sint32 volume, sint32 pan) { _thunderSoundChannels[instanceIndex] = Mixer_Play_Effect(soundId, MIXER_LOOP_NONE, DStoMixerVolume(volume), DStoMixerPan(pan), 1, 0); if (_thunderSoundChannels[instanceIndex] != nullptr) { _thunderStatus[instanceIndex] = THUNDER_STATUS::PLAYING; } } #pragma region Climate / Weather data tables const FILTER_PALETTE_ID ClimateWeatherGloomColours[4] = { PALETTE_NULL, PALETTE_DARKEN_1, PALETTE_DARKEN_2, PALETTE_DARKEN_3, }; // There is actually a sprite at 0x5A9C for snow but only these weather types seem to be fully implemented const WeatherState ClimateWeatherData[6] = { { 10, WEATHER_EFFECT_NONE, 0, RAIN_LEVEL_NONE, SPR_WEATHER_SUN }, // Sunny { 5, WEATHER_EFFECT_NONE, 0, RAIN_LEVEL_NONE, SPR_WEATHER_SUN_CLOUD }, // Partially Cloudy { 0, WEATHER_EFFECT_NONE, 0, RAIN_LEVEL_NONE, SPR_WEATHER_CLOUD }, // Cloudy { -2, WEATHER_EFFECT_RAIN, 1, RAIN_LEVEL_LIGHT, SPR_WEATHER_LIGHT_RAIN }, // Rain { -4, WEATHER_EFFECT_RAIN, 2, RAIN_LEVEL_HEAVY, SPR_WEATHER_HEAVY_RAIN }, // Heavy Rain { 2, WEATHER_EFFECT_STORM, 2, RAIN_LEVEL_HEAVY, SPR_WEATHER_STORM }, // Thunderstorm }; static constexpr const WeatherTransition ClimateTransitionsCoolAndWet[] = { { 8, 18, { 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 0, 0, 0, 0, 0 } }, { 10, 21, { 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 0, 0 } }, { 14, 17, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 0, 0, 0, 0, 0, 0 } }, { 17, 17, { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 0, 0, 0, 0, 0, 0 } }, { 19, 23, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 4 } }, { 20, 23, { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 } }, { 16, 19, { 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 5, 0, 0, 0, 0 } }, { 13, 16, { 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0 } }, }; static constexpr const WeatherTransition ClimateTransitionsWarm[] = { { 12, 21, { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 4, 0, 0 } }, { 13, 22, { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 5, 0 } }, { 16, 17, { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0 } }, { 19, 18, { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 0, 0, 0, 0, 0 } }, { 21, 22, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 0 } }, { 22, 17, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 5, 0, 0, 0, 0, 0, 0 } }, { 19, 17, { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0 } }, { 16, 17, { 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0, 0 } }, }; static constexpr const WeatherTransition ClimateTransitionsHotAndDry[] = { { 12, 15, { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 14, 12, { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 16, 11, { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 19, 9, { 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 21, 13, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 22, 11, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 21, 12, { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 16, 13, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, }; static constexpr const WeatherTransition ClimateTransitionsCold[] = { { 4, 18, { 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 4, 0, 0, 0, 0, 0 } }, { 5, 21, { 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 0, 0 } }, { 7, 17, { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 0, 0, 0, 0, 0, 0 } }, { 9, 17, { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 0, 0, 0, 0, 0, 0 } }, { 10, 23, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 4 } }, { 11, 23, { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 5 } }, { 9, 19, { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 4, 5, 0, 0, 0, 0 } }, { 6, 16, { 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0 } }, }; const WeatherTransition * ClimateTransitions[] = { ClimateTransitionsCoolAndWet, ClimateTransitionsWarm, ClimateTransitionsHotAndDry, ClimateTransitionsCold, }; #pragma endregion