#include "weather.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwrender/renderingmanager.hpp" #include "player.hpp" #include "esmstore.hpp" #include "fallback.hpp" using namespace Ogre; using namespace MWWorld; using namespace MWSound; namespace { float lerp (float x, float y, float factor) { return x * (1-factor) + y * factor; } Ogre::ColourValue lerp (const Ogre::ColourValue& x, const Ogre::ColourValue& y, float factor) { return x * (1-factor) + y * factor; } } void WeatherManager::setFallbackWeather(Weather& weather,const std::string& name) { std::string upper=name; upper[0]=toupper(name[0]); weather.mCloudsMaximumPercent = mFallback->getFallbackFloat("Weather_"+upper+"_Clouds_Maximum_Percent"); weather.mTransitionDelta = mFallback->getFallbackFloat("Weather_"+upper+"_Transition_Delta"); weather.mSkySunriseColor=mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunrise_Color"); weather.mSkyDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Day_Color"); weather.mSkySunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunset_Color"); weather.mSkyNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Night_Color"); weather.mFogSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunrise_Color"); weather.mFogDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Day_Color"); weather.mFogSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunset_Color"); weather.mFogNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Night_Color"); weather.mAmbientSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunrise_Color"); weather.mAmbientDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Day_Color"); weather.mAmbientSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunset_Color"); weather.mAmbientNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Night_Color"); weather.mSunSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunrise_Color"); weather.mSunDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Day_Color"); weather.mSunSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunset_Color"); weather.mSunNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Night_Color"); weather.mSunDiscSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Disc_Sunset_Color"); weather.mLandFogDayDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Day_Depth"); weather.mLandFogNightDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Night_Depth"); weather.mWindSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Wind_Speed"); weather.mCloudSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Cloud_Speed"); weather.mGlareView = mFallback->getFallbackFloat("Weather_"+upper+"_Glare_View"); mWeatherSettings[name] = weather; } float WeatherManager::calculateHourFade (const std::string& moonName) const { float fadeOutStart=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Out_Start"); float fadeOutFinish=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Out_Finish"); float fadeInStart=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_In_Start"); float fadeInFinish=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_In_Finish"); if (mHour >= fadeOutStart && mHour <= fadeOutFinish) return (1 - ((mHour - fadeOutStart) / (fadeOutFinish - fadeOutStart))); if (mHour >= fadeInStart && mHour <= fadeInFinish) return (1 - ((mHour - fadeInStart) / (fadeInFinish - fadeInStart))); else return 1; } float WeatherManager::calculateAngleFade (const std::string& moonName, float angle) const { float endAngle=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_End_Angle"); float startAngle=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Start_Angle"); if (angle <= startAngle && angle >= endAngle) return (1 - ((angle - endAngle)/(startAngle-endAngle))); else if (angle > startAngle) return 0.f; else return 1.f; } WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fallback* fallback) : mHour(14), mCurrentWeather("clear"), mNextWeather(""), mFirstUpdate(true), mWeatherUpdateTime(0), mThunderFlash(0), mThunderChance(0), mThunderChanceNeeded(50), mThunderSoundDelay(0), mRemainingTransitionTime(0), mMonth(0), mDay(0), mTimePassed(0), mFallback(fallback), mWindSpeed(0.f), mRendering(rendering) { //Globals mThunderSoundID0 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_0"); mThunderSoundID1 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_1"); mThunderSoundID2 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_2"); mThunderSoundID3 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_3"); mSunriseTime = mFallback->getFallbackFloat("Weather_Sunrise_Time"); mSunsetTime = mFallback->getFallbackFloat("Weather_Sunset_Time"); mSunriseDuration = mFallback->getFallbackFloat("Weather_Sunrise_Duration"); mSunsetDuration = mFallback->getFallbackFloat("Weather_Sunset_Duration"); mHoursBetweenWeatherChanges = mFallback->getFallbackFloat("Weather_Hours_Between_Weather_Changes"); mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600; mThunderFrequency = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Frequency"); mThunderThreshold = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Threshold"); mThunderSoundDelay = 0.25; //Some useful values /* TODO: Use pre-sunrise_time, pre-sunset_time, * post-sunrise_time, and post-sunset_time to better * describe sunrise/sunset time. * These values are fallbacks attached to weather. */ mNightStart = mSunsetTime + mSunsetDuration; mNightEnd = mSunriseTime - 0.5; mDayStart = mSunriseTime + mSunriseDuration; mDayEnd = mSunsetTime; //Weather Weather clear; clear.mCloudTexture = "tx_sky_clear.dds"; setFallbackWeather(clear,"clear"); Weather cloudy; cloudy.mCloudTexture = "tx_sky_cloudy.dds"; setFallbackWeather(cloudy,"cloudy"); Weather foggy; foggy.mCloudTexture = "tx_sky_foggy.dds"; setFallbackWeather(foggy,"foggy"); Weather thunderstorm; thunderstorm.mCloudTexture = "tx_sky_thunder.dds"; thunderstorm.mRainLoopSoundID = "rain heavy"; setFallbackWeather(thunderstorm,"thunderstorm"); Weather rain; rain.mCloudTexture = "tx_sky_rainy.dds"; rain.mRainLoopSoundID = "rain"; setFallbackWeather(rain,"rain"); Weather overcast; overcast.mCloudTexture = "tx_sky_overcast.dds"; setFallbackWeather(overcast,"overcast"); Weather ashstorm; ashstorm.mCloudTexture = "tx_sky_ashstorm.dds"; ashstorm.mAmbientLoopSoundID = "ashstorm"; setFallbackWeather(ashstorm,"ashstorm"); Weather blight; blight.mCloudTexture = "tx_sky_blight.dds"; blight.mAmbientLoopSoundID = "blight"; setFallbackWeather(blight,"blight"); Weather snow; snow.mCloudTexture = "tx_bm_sky_snow.dds"; setFallbackWeather(snow, "snow"); Weather blizzard; blizzard.mCloudTexture = "tx_bm_sky_blizzard.dds"; blizzard.mAmbientLoopSoundID = "BM Blizzard"; setFallbackWeather(blizzard,"blizzard"); } void WeatherManager::setWeather(const String& weather, bool instant) { if (weather == mCurrentWeather && mNextWeather == "") { mFirstUpdate = false; return; } if (instant || mFirstUpdate) { mNextWeather = ""; mCurrentWeather = weather; } else { if (mNextWeather != "") { // transition more than 50% finished? if (mRemainingTransitionTime/(mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600) <= 0.5) mCurrentWeather = mNextWeather; } mNextWeather = weather; mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600; } mFirstUpdate = false; } void WeatherManager::setResult(const String& weatherType) { const Weather& current = mWeatherSettings[weatherType]; mResult.mCloudTexture = current.mCloudTexture; mResult.mCloudBlendFactor = 0; mResult.mCloudOpacity = current.mCloudsMaximumPercent; mResult.mWindSpeed = current.mWindSpeed; mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mSunColor = current.mSunDiscSunsetColor; mResult.mNight = (mHour < mSunriseTime || mHour > mNightStart - 1); mResult.mFogDepth = mResult.mNight ? current.mLandFogNightDepth : current.mLandFogDayDepth; // night if (mHour <= mNightEnd || mHour >= mNightStart + 1) { mResult.mFogColor = current.mFogNightColor; mResult.mAmbientColor = current.mAmbientNightColor; mResult.mSunColor = current.mSunNightColor; mResult.mSkyColor = current.mSkyNightColor; mResult.mNightFade = 1.f; } // sunrise else if (mHour >= mNightEnd && mHour <= mDayStart + 1) { if (mHour <= mSunriseTime) { // fade in float advance = mSunriseTime - mHour; float factor = advance / 0.5f; mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogNightColor, factor); mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientNightColor, factor); mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunNightColor, factor); mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyNightColor, factor); mResult.mNightFade = factor; } else //if (mHour >= 6) { // fade out float advance = mHour - mSunriseTime; float factor = advance / 3.f; mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogDayColor, factor); mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientDayColor, factor); mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunDayColor, factor); mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyDayColor, factor); } } // day else if (mHour >= mDayStart + 1 && mHour <= mDayEnd - 1) { mResult.mFogColor = current.mFogDayColor; mResult.mAmbientColor = current.mAmbientDayColor; mResult.mSunColor = current.mSunDayColor; mResult.mSkyColor = current.mSkyDayColor; } // sunset else if (mHour >= mDayEnd - 1 && mHour <= mNightStart + 1) { if (mHour <= mDayEnd + 1) { // fade in float advance = (mDayEnd + 1) - mHour; float factor = (advance / 2); mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogDayColor, factor); mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientDayColor, factor); mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunDayColor, factor); mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyDayColor, factor); } else //if (mHour >= 19) { // fade out float advance = mHour - (mDayEnd + 1); float factor = advance / 2.f; mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogNightColor, factor); mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientNightColor, factor); mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunNightColor, factor); mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyNightColor, factor); mResult.mNightFade = factor; } } } void WeatherManager::transition(float factor) { setResult(mCurrentWeather); const WeatherResult current = mResult; setResult(mNextWeather); const WeatherResult other = mResult; mResult.mCloudTexture = current.mCloudTexture; mResult.mNextCloudTexture = other.mCloudTexture; mResult.mCloudBlendFactor = factor; mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, factor); mResult.mFogColor = lerp(current.mFogColor, other.mFogColor, factor); mResult.mSunColor = lerp(current.mSunColor, other.mSunColor, factor); mResult.mSkyColor = lerp(current.mSkyColor, other.mSkyColor, factor); mResult.mAmbientColor = lerp(current.mAmbientColor, other.mAmbientColor, factor); mResult.mSunDiscColor = lerp(current.mSunDiscColor, other.mSunDiscColor, factor); mResult.mFogDepth = lerp(current.mFogDepth, other.mFogDepth, factor); mResult.mWindSpeed = lerp(current.mWindSpeed, other.mWindSpeed, factor); mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor); mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, factor); mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor); mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor); mResult.mNight = current.mNight; } void WeatherManager::update(float duration) { float timePassed = mTimePassed; mTimePassed = 0; mWeatherUpdateTime -= timePassed; const bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); if (!exterior) { mRendering->sunDisable(false); mRendering->skyDisable(); mRendering->getSkyManager()->setLightningStrength(0.f); stopSounds(true); return; } // Exterior std::string regionstr = Misc::StringUtils::lowerCase(MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->mRegion); if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion) { mCurrentRegion = regionstr; mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600; std::string weatherType = "clear"; if (mRegionOverrides.find(regionstr) != mRegionOverrides.end()) weatherType = mRegionOverrides[regionstr]; else { // get weather probabilities for the current region const ESM::Region *region = MWBase::Environment::get().getWorld()->getStore().get().search (regionstr); if (region != 0) { weatherType = nextWeather(region); } } setWeather(weatherType, false); } if (mNextWeather != "") { mRemainingTransitionTime -= timePassed; if (mRemainingTransitionTime < 0) { mCurrentWeather = mNextWeather; mNextWeather = ""; } } if (mNextWeather != "") transition(1 - (mRemainingTransitionTime / (mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600))); else setResult(mCurrentWeather); mWindSpeed = mResult.mWindSpeed; mRendering->configureFog(mResult.mFogDepth, mResult.mFogColor); // disable sun during night if (mHour >= mNightStart || mHour <= mSunriseTime) mRendering->getSkyManager()->sunDisable(); else mRendering->getSkyManager()->sunEnable(); // sun angle float height; //Day duration float dayDuration = (mNightStart - 1) - mSunriseTime; // rise at 6, set at 20 if (mHour >= mSunriseTime && mHour <= mNightStart) height = 1 - std::abs(((mHour - dayDuration) / 7.f)); else if (mHour > mNightStart) height = (mHour - mNightStart) / 4.f; else //if (mHour > 0 && mHour < 6) height = 1 - (mHour / mSunriseTime); int facing = (mHour > 13.f) ? 1 : -1; Vector3 final( (height - 1) * facing, (height - 1) * facing, height); mRendering->setSunDirection(final); /* * TODO: import separated fadeInStart/Finish, fadeOutStart/Finish * for masser and secunda */ float fadeOutFinish=mFallback->getFallbackFloat("Moons_Masser_Fade_Out_Finish"); float fadeInStart=mFallback->getFallbackFloat("Moons_Masser_Fade_In_Start"); //moon calculations float moonHeight; if (mHour >= fadeInStart) moonHeight = mHour - fadeInStart; else if (mHour <= fadeOutFinish) moonHeight = mHour + fadeOutFinish; else moonHeight = 0; moonHeight /= (24.f - (fadeInStart - fadeOutFinish)); if (moonHeight != 0) { int facing = (moonHeight <= 1) ? 1 : -1; Vector3 masser( (moonHeight - 1) * facing, (1 - moonHeight) * facing, moonHeight); Vector3 secunda( (moonHeight - 1) * facing * 1.25, (1 - moonHeight) * facing * 0.8, moonHeight); mRendering->getSkyManager()->setMasserDirection(masser); mRendering->getSkyManager()->setSecundaDirection(secunda); mRendering->getSkyManager()->masserEnable(); mRendering->getSkyManager()->secundaEnable(); float angle = (1-moonHeight) * 90.f * facing; float masserHourFade = calculateHourFade("Masser"); float secundaHourFade = calculateHourFade("Secunda"); float masserAngleFade = calculateAngleFade("Masser", angle); float secundaAngleFade = calculateAngleFade("Secunda", angle); masserAngleFade *= masserHourFade; secundaAngleFade *= secundaHourFade; mRendering->getSkyManager()->setMasserFade(masserAngleFade); mRendering->getSkyManager()->setSecundaFade(secundaAngleFade); } else { mRendering->getSkyManager()->masserDisable(); mRendering->getSkyManager()->secundaDisable(); } if (mCurrentWeather == "thunderstorm" && mNextWeather == "") { if (mThunderFlash > 0) { // play the sound after a delay mThunderSoundDelay -= duration; if (mThunderSoundDelay <= 0) { // pick a random sound int sound = rand() % 4; std::string* soundName; if (sound == 0) soundName = &mThunderSoundID0; else if (sound == 1) soundName = &mThunderSoundID1; else if (sound == 2) soundName = &mThunderSoundID2; else if (sound == 3) soundName = &mThunderSoundID3; MWBase::Environment::get().getSoundManager()->playSound(*soundName, 1.0, 1.0); mThunderSoundDelay = 1000; } mThunderFlash -= duration; if (mThunderFlash > 0) mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold ); else { mThunderChanceNeeded = rand() % 100; mThunderChance = 0; mRendering->getSkyManager()->setLightningStrength( 0.f ); } } else { // no thunder active mThunderChance += duration*4; // chance increases by 4 percent every second if (mThunderChance >= mThunderChanceNeeded) { mThunderFlash = mThunderThreshold; mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold ); mThunderSoundDelay = 0.25; } } } else mRendering->getSkyManager()->setLightningStrength(0.f); mRendering->setAmbientColour(mResult.mAmbientColor); mRendering->sunEnable(false); mRendering->setSunColour(mResult.mSunColor); mRendering->getSkyManager()->setWeather(mResult); // Play sounds if (mNextWeather == "") { std::string ambientSnd = mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID; if (!ambientSnd.empty() && std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end()) { mSoundsPlaying.push_back(ambientSnd); MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, MWBase::SoundManager::Play_Loop); } std::string rainSnd = mWeatherSettings[mCurrentWeather].mRainLoopSoundID; if (!rainSnd.empty() && std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end()) { mSoundsPlaying.push_back(rainSnd); MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, MWBase::SoundManager::Play_Loop); } } stopSounds(false); } void WeatherManager::stopSounds(bool stopAll) { std::vector::iterator it = mSoundsPlaying.begin(); while (it!=mSoundsPlaying.end()) { if (stopAll || \ !((*it == mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID) || \ (*it == mWeatherSettings[mCurrentWeather].mRainLoopSoundID))) { MWBase::Environment::get().getSoundManager()->stopSound(*it); it = mSoundsPlaying.erase(it); } else ++it; } } Ogre::String WeatherManager::nextWeather(const ESM::Region* region) const { /* * All probabilities must add to 100 (responsibility of the user). * If chances A and B has values 30 and 70 then by generating * 100 numbers 1..100, 30% will be lesser or equal 30 and * 70% will be greater than 30 (in theory). */ const int probability[] = { region->mData.mClear, region->mData.mCloudy, region->mData.mFoggy, region->mData.mOvercast, region->mData.mRain, region->mData.mThunder, region->mData.mAsh, region->mData.mBlight, region->mData.mA, region->mData.mB }; // 10 elements int chance = (rand() % 100) + 1; // 1..100 int sum = 0; int i = 0; for (; i < 10; ++i) { sum += probability[i]; if (chance < sum) break; } switch (i) { case 1: return "cloudy"; case 2: return "foggy"; case 3: return "overcast"; case 4: return "rain"; case 5: return "thunderstorm"; case 6: return "ashstorm"; case 7: return "blight"; case 8: return "snow"; case 9: return "blizzard"; default: // case 0 return "clear"; } } void WeatherManager::setHour(const float hour) { mHour = hour; } void WeatherManager::setDate(const int day, const int month) { mDay = day; mMonth = month; } unsigned int WeatherManager::getWeatherID() const { // Source: http://www.uesp.net/wiki/Tes3Mod:GetCurrentWeather if (mCurrentWeather == "clear") return 0; else if (mCurrentWeather == "cloudy") return 1; else if (mCurrentWeather == "foggy") return 2; else if (mCurrentWeather == "overcast") return 3; else if (mCurrentWeather == "rain") return 4; else if (mCurrentWeather == "thunderstorm") return 5; else if (mCurrentWeather == "ashstorm") return 6; else if (mCurrentWeather == "blight") return 7; else if (mCurrentWeather == "snow") return 8; else if (mCurrentWeather == "blizzard") return 9; else return 0; } void WeatherManager::changeWeather(const std::string& region, const unsigned int id) { // make sure this region exists MWBase::Environment::get().getWorld()->getStore().get().find(region); std::string weather; if (id==0) weather = "clear"; else if (id==1) weather = "cloudy"; else if (id==2) weather = "foggy"; else if (id==3) weather = "overcast"; else if (id==4) weather = "rain"; else if (id==5) weather = "thunderstorm"; else if (id==6) weather = "ashstorm"; else if (id==7) weather = "blight"; else if (id==8) weather = "snow"; else if (id==9) weather = "blizzard"; else weather = "clear"; mRegionOverrides[Misc::StringUtils::lowerCase(region)] = weather; std::string playerRegion = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->mRegion; if (Misc::StringUtils::ciEqual(region, playerRegion)) setWeather(weather); } float WeatherManager::getWindSpeed() const { return mWindSpeed; }