#pragma region Copyright (c) 2014-2016 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/mixer.h" #include "../cheats.h" #include "../config.h" #include "../drawing/drawing.h" #include "../game.h" #include "../interface/window.h" #include "../localisation/date.h" #include "../scenario.h" #include "../util/util.h" #include "climate.h" enum { THUNDER_STATUS_NULL = 0, THUNDER_STATUS_PLAYING = 1, MAX_THUNDER_INSTANCES = 2 }; typedef struct rct_weather_transition { sint8 base_temperature; sint8 distribution_size; sint8 distribution[24]; } rct_weather_transition; static const rct_weather_transition* climate_transitions[4]; uint16 gClimateLightningFlash; // Sound data static int _rainVolume = 1; static unsigned int _lightningTimer, _thunderTimer; static void* _thunderSoundChannels[MAX_THUNDER_INSTANCES]; static int _thunderStatus[MAX_THUNDER_INSTANCES] = { THUNDER_STATUS_NULL, THUNDER_STATUS_NULL }; static unsigned int _thunderSoundId; static int _thunderVolume; static int _thunderStereoEcho = 0; static void climate_determine_future_weather(int randomDistribution); static void climate_update_rain_sound(); static void climate_update_thunder_sound(); static void climate_update_lightning(); static void climate_update_thunder(); static int climate_play_thunder(int instanceIndex, int soundId, int volume, int pan); int climate_celsius_to_fahrenheit(int celsius) { return (celsius * 29) / 16 + 32; } /** * Set climate and determine start weather. * rct2: 0x006C45ED */ void climate_reset(int climate) { gClimate = climate; sint8 month = gDateMonthsElapsed & 7; const rct_weather_transition* climate_table = climate_transitions[climate]; rct_weather_transition transition = climate_table[month]; sint8 weather = WEATHER_PARTIALLY_CLOUDY; gClimateCurrentWeather = weather; gClimateCurrentTemperature = transition.base_temperature + climate_weather_data[weather].temp_delta; gClimateCurrentWeatherEffect = climate_weather_data[weather].effect_level; gClimateCurrentWeatherGloom = climate_weather_data[weather].gloom_level; gClimateCurrentRainLevel = climate_weather_data[weather].rain_level; _lightningTimer = 0; _thunderTimer = 0; if (_rainVolume != 1){ audio_stop_rain_sound(); _rainVolume = 1; } climate_determine_future_weather(util_rand()); } sint8 step_weather_level(sint8 cur_weather_level, sint8 next_weather_level) { if (next_weather_level > cur_weather_level) { return cur_weather_level + 1; } else { return cur_weather_level - 1; } } /** * Weather & climate update iteration. * Gradually changes the weather parameters towards their determined next values. * * rct2: 0x006C46B1 */ void climate_update() { uint8 screen_flags = gScreenFlags; sint8 temperature = gClimateCurrentTemperature, target_temperature = gClimateNextTemperature, cur_gloom = gClimateCurrentWeatherGloom, next_gloom = gClimateNextWeatherGloom, cur_rain = gClimateCurrentRainLevel, next_rain = gClimateNextRainLevel; if (gCheatsFreezeClimate) //for cheats return; if (screen_flags & (~SCREEN_FLAGS_PLAYING)) // only normal play mode gets climate return; if (gClimateUpdateTimer) { if (gClimateUpdateTimer == 960) { gToolbarDirtyFlags |= BTM_TB_DIRTY_FLAG_CLIMATE; } gClimateUpdateTimer--; } else if (!(gCurrentTicks & 0x7F)) { if (temperature == target_temperature) { if (cur_gloom == next_gloom) { gClimateCurrentWeatherEffect = gClimateNextWeatherEffect; _thunderTimer = 0; _lightningTimer = 0; if (cur_rain == next_rain) { gClimateCurrentWeather = gClimateNextWeather; climate_determine_future_weather(scenario_rand()); gToolbarDirtyFlags |= BTM_TB_DIRTY_FLAG_CLIMATE; } else if (next_rain <= 2) { // Safe-guard gClimateCurrentRainLevel = step_weather_level(cur_rain, next_rain); } } else { gClimateCurrentWeatherGloom = step_weather_level(cur_gloom, next_gloom); gfx_invalidate_screen(); } } else { gClimateCurrentTemperature = step_weather_level(temperature, target_temperature); gToolbarDirtyFlags |= BTM_TB_DIRTY_FLAG_CLIMATE; } } if (_thunderTimer != 0) { climate_update_lightning(); climate_update_thunder(); } else if (gClimateCurrentWeatherEffect == 2) { // Create new thunder and lightning unsigned int randomNumber = util_rand(); if ((randomNumber & 0xFFFF) <= 0x1B4) { randomNumber >>= 16; _thunderTimer = 43 + (randomNumber % 64); _lightningTimer = randomNumber % 32; } } } void climate_force_weather(uint8 weather){ gClimateCurrentWeather = weather; gClimateCurrentWeatherGloom = climate_weather_data[weather].gloom_level; gClimateCurrentRainLevel = climate_weather_data[weather].rain_level; gClimateCurrentWeatherEffect = climate_weather_data[weather].effect_level; gClimateUpdateTimer = 1920; climate_update(); // Incase of change in gloom level force a complete redraw gfx_invalidate_screen(); } /** * Calculates future weather development. * RCT2 implements this as discrete probability distributions dependant on month and climate * for next_weather. The other weather parameters are then looked up depending only on the * next weather. * * rct2: 0x006C461C */ static void climate_determine_future_weather(int randomDistribution) { sint8 climate = gClimate; const rct_weather_transition* climate_table = climate_transitions[climate]; sint8 month = gDateMonthsElapsed & 7; rct_weather_transition transition = climate_table[month]; // Generate a random variable with values 0 upto distribution_size-1 and chose weather from the distribution table accordingly sint8 next_weather = transition.distribution[ ((randomDistribution & 0xFF) * transition.distribution_size) >> 8 ]; gClimateNextWeather = next_weather; gClimateNextTemperature = transition.base_temperature + climate_weather_data[next_weather].temp_delta; gClimateNextWeatherEffect = climate_weather_data[next_weather].effect_level; gClimateNextWeatherGloom = climate_weather_data[next_weather].gloom_level; gClimateNextRainLevel = climate_weather_data[next_weather].rain_level; gClimateUpdateTimer = 1920; } /** * * rct2: 0x006BCB91 */ 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(); } static void climate_update_rain_sound() { if (gClimateCurrentWeatherEffect == 1 || gClimateCurrentWeatherEffect == 2) { // Start playing the rain sound if (!gRainSoundChannel) { 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 = min(-1400, _rainVolume + 80); if (gRainSoundChannel) { Mixer_Channel_Volume(gRainSoundChannel, DStoMixerVolume(_rainVolume)); } } } else if (_rainVolume != 1) { // Decrease rain sound _rainVolume -= 80; if (_rainVolume > -4000) { if (gRainSoundChannel) { 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 (int i = 0; i < MAX_THUNDER_INSTANCES; i++) { if (_thunderStatus[i] == THUNDER_STATUS_NULL) continue; if (!Mixer_Channel_IsPlaying(_thunderSoundChannels[i])) { Mixer_Stop_Channel(_thunderSoundChannels[i]); _thunderStatus[i] = THUNDER_STATUS_NULL; } } } static void climate_update_lightning() { if (_lightningTimer == 0) return; if (!gConfigGeneral.disable_lightning_effect) { _lightningTimer--; if (gClimateLightningFlash == 0) if ((util_rand() & 0xFFFF) <= 0x2000) gClimateLightningFlash = 1; } } static void climate_update_thunder() { _thunderTimer--; if (_thunderTimer != 0) return; unsigned int randomNumber = util_rand(); if (randomNumber & 0x10000) { if (_thunderStatus[0] == THUNDER_STATUS_NULL && _thunderStatus[1] == THUNDER_STATUS_NULL) { // Play thunder on left side _thunderSoundId = (randomNumber & 0x20000) ? SOUND_THUNDER_1 : SOUND_THUNDER_2; _thunderVolume = (-((int)((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_NULL){ _thunderSoundId = (randomNumber & 0x20000) ? SOUND_THUNDER_1 : SOUND_THUNDER_2; int pan = (((randomNumber >> 18) & 0xFF) - 128) * 16; climate_play_thunder(0, _thunderSoundId, 0, pan); } } } static int climate_play_thunder(int instanceIndex, int soundId, int volume, int pan) { _thunderSoundChannels[instanceIndex] = Mixer_Play_Effect(soundId, MIXER_LOOP_NONE, DStoMixerVolume(volume), DStoMixerPan(pan), 1, 0); if (_thunderSoundChannels[instanceIndex]) { _thunderStatus[instanceIndex] = THUNDER_STATUS_PLAYING; return 1; } return 0; } #pragma region Climate / Weather data tables // rct2: 0x00993C94 // There is actually a sprite at 0x5A9C for snow but only these weather types seem to be fully implemented const rct_weather climate_weather_data[6] = { { .temp_delta = 10, .effect_level = 0, .gloom_level = 0, .rain_level = 0, .sprite_id = 0x5A96 }, // Sunny { .temp_delta = 5, .effect_level = 0, .gloom_level = 0, .rain_level = 0, .sprite_id = 0x5A97 }, // Partially Cloudy { .temp_delta = 0, .effect_level = 0, .gloom_level = 0, .rain_level = 0, .sprite_id = 0x5A98 }, // Cloudy { .temp_delta = -2, .effect_level = 1, .gloom_level = 1, .rain_level = 1, .sprite_id = 0x5A99 }, // Rain { .temp_delta = -4, .effect_level = 1, .gloom_level = 2, .rain_level = 2, .sprite_id = 0x5A9A }, // Heavy Rain { .temp_delta = 2, .effect_level = 2, .gloom_level = 2, .rain_level = 2, .sprite_id = 0x5A9B }, // Thunderstorm }; // rct2: 00993998 static const rct_weather_transition climate_cool_and_wet_transitions[] = { { .base_temperature = 8, .distribution_size = 18, .distribution = { 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 0, 0, 0, 0, 0 } }, { .base_temperature = 10, .distribution_size = 21, .distribution = { 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 0, 0 } }, { .base_temperature = 14, .distribution_size = 17, .distribution = { 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 17, .distribution_size = 17, .distribution = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 19, .distribution_size = 23, .distribution = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 4 } }, { .base_temperature = 20, .distribution_size = 23, .distribution = { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 } }, { .base_temperature = 16, .distribution_size = 19, .distribution = { 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 5, 0, 0, 0, 0 } }, { .base_temperature = 13, .distribution_size = 16, .distribution = { 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0 } } }; static const rct_weather_transition climate_warm_transitions[] = { { .base_temperature = 12, .distribution_size = 21, .distribution = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 4, 0, 0 } }, { .base_temperature = 13, .distribution_size = 22, .distribution = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 5, 0 } }, { .base_temperature = 16, .distribution_size = 17, .distribution = { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 19, .distribution_size = 18, .distribution = { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 0, 0, 0, 0, 0 } }, { .base_temperature = 21, .distribution_size = 22, .distribution = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 0 } }, { .base_temperature = 22, .distribution_size = 17, .distribution = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 5, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 19, .distribution_size = 17, .distribution = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 16, .distribution_size = 17, .distribution = { 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0, 0 } } }; static const rct_weather_transition climate_hot_and_dry_transitions[] = { { .base_temperature = 12, .distribution_size = 15, .distribution = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 14, .distribution_size = 12, .distribution = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 16, .distribution_size = 11, .distribution = { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 19, .distribution_size = 9, .distribution = { 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 21, .distribution_size = 13, .distribution = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 22, .distribution_size = 11, .distribution = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 21, .distribution_size = 12, .distribution = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 16, .distribution_size = 13, .distribution = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } }; static const rct_weather_transition climate_cold_transitions[] = { { .base_temperature = 4, .distribution_size = 18, .distribution = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 4, 0, 0, 0, 0, 0 } }, { .base_temperature = 5, .distribution_size = 21, .distribution = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 0, 0 } }, { .base_temperature = 7, .distribution_size = 17, .distribution = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 9, .distribution_size = 17, .distribution = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 0, 0, 0, 0, 0, 0 } }, { .base_temperature = 10, .distribution_size = 23, .distribution = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 4 } }, { .base_temperature = 11, .distribution_size = 23, .distribution = { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 5 } }, { .base_temperature = 9, .distribution_size = 19, .distribution = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 4, 5, 0, 0, 0, 0 } }, { .base_temperature = 6, .distribution_size = 16, .distribution = { 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0 } } }; static const rct_weather_transition* climate_transitions[] = { climate_cool_and_wet_transitions, climate_warm_transitions, climate_hot_and_dry_transitions, climate_cold_transitions }; #pragma endregion