You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw-tes3mp/apps/openmw/mwworld/weather.cpp

1488 lines
56 KiB
C++

#include "weather.hpp"
#include <components/misc/rng.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/weatherstate.hpp>
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include <components/openmw-mp/TimedLog.hpp>
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/Worldstate.hpp"
/*
End of tes3mp addition
*/
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwsound/sound.hpp"
#include "../mwrender/renderingmanager.hpp"
#include "../mwrender/sky.hpp"
#include "player.hpp"
#include "esmstore.hpp"
#include "cellstore.hpp"
#include <cmath>
using namespace MWWorld;
namespace
{
static const int invalidWeatherID = -1;
// linear interpolate between x and y based on factor.
float lerp (float x, float y, float factor)
{
return x * (1-factor) + y * factor;
}
// linear interpolate between x and y based on factor.
osg::Vec4f lerp (const osg::Vec4f& x, const osg::Vec4f& y, float factor)
{
return x * (1-factor) + y * factor;
}
}
template <typename T>
T TimeOfDayInterpolator<T>::getValue(const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const
{
WeatherSetting setting = timeSettings.getSetting(prefix);
float preSunriseTime = setting.mPreSunriseTime;
float postSunriseTime = setting.mPostSunriseTime;
float preSunsetTime = setting.mPreSunsetTime;
float postSunsetTime = setting.mPostSunsetTime;
// night
if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime)
return mNightValue;
// sunrise
else if (gameHour >= timeSettings.mNightEnd - preSunriseTime && gameHour <= timeSettings.mDayStart + postSunriseTime)
{
float duration = timeSettings.mDayStart + postSunriseTime - timeSettings.mNightEnd + preSunriseTime;
float middle = timeSettings.mNightEnd - preSunriseTime + duration / 2.f;
if (gameHour <= middle)
{
// fade in
float advance = middle - gameHour;
float factor = 0.f;
if (duration > 0)
factor = advance / duration * 2;
return lerp(mSunriseValue, mNightValue, factor);
}
else
{
// fade out
float advance = gameHour - middle;
float factor = 1.f;
if (duration > 0)
factor = advance / duration * 2;
return lerp(mSunriseValue, mDayValue, factor);
}
}
// day
else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime)
return mDayValue;
// sunset
else if (gameHour >= timeSettings.mDayEnd - preSunsetTime && gameHour <= timeSettings.mNightStart + postSunsetTime)
{
float duration = timeSettings.mNightStart + postSunsetTime - timeSettings.mDayEnd + preSunsetTime;
float middle = timeSettings.mDayEnd - preSunsetTime + duration / 2.f;
if (gameHour <= middle)
{
// fade in
float advance = middle - gameHour;
float factor = 0.f;
if (duration > 0)
factor = advance / duration * 2;
return lerp(mSunsetValue, mDayValue, factor);
}
else
{
// fade out
float advance = gameHour - middle;
float factor = 1.f;
if (duration > 0)
factor = advance / duration * 2;
return lerp(mSunsetValue, mNightValue, factor);
}
}
// shut up compiler
return T();
}
template class MWWorld::TimeOfDayInterpolator<float>;
template class MWWorld::TimeOfDayInterpolator<osg::Vec4f>;
Weather::Weather(const std::string& name,
float stormWindSpeed,
float rainSpeed,
float dlFactor,
float dlOffset,
const std::string& particleEffect)
: mCloudTexture(Fallback::Map::getString("Weather_" + name + "_Cloud_Texture"))
, mSkyColor(Fallback::Map::getColour("Weather_" + name +"_Sky_Sunrise_Color"),
Fallback::Map::getColour("Weather_" + name + "_Sky_Day_Color"),
Fallback::Map::getColour("Weather_" + name + "_Sky_Sunset_Color"),
Fallback::Map::getColour("Weather_" + name + "_Sky_Night_Color"))
, mFogColor(Fallback::Map::getColour("Weather_" + name + "_Fog_Sunrise_Color"),
Fallback::Map::getColour("Weather_" + name + "_Fog_Day_Color"),
Fallback::Map::getColour("Weather_" + name + "_Fog_Sunset_Color"),
Fallback::Map::getColour("Weather_" + name + "_Fog_Night_Color"))
, mAmbientColor(Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunrise_Color"),
Fallback::Map::getColour("Weather_" + name + "_Ambient_Day_Color"),
Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunset_Color"),
Fallback::Map::getColour("Weather_" + name + "_Ambient_Night_Color"))
, mSunColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Sunrise_Color"),
Fallback::Map::getColour("Weather_" + name + "_Sun_Day_Color"),
Fallback::Map::getColour("Weather_" + name + "_Sun_Sunset_Color"),
Fallback::Map::getColour("Weather_" + name + "_Sun_Night_Color"))
, mLandFogDepth(Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"),
Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"),
Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"),
Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Night_Depth"))
, mSunDiscSunsetColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Disc_Sunset_Color"))
, mWindSpeed(Fallback::Map::getFloat("Weather_" + name + "_Wind_Speed"))
, mCloudSpeed(Fallback::Map::getFloat("Weather_" + name + "_Cloud_Speed"))
, mGlareView(Fallback::Map::getFloat("Weather_" + name + "_Glare_View"))
, mIsStorm(mWindSpeed > stormWindSpeed)
, mRainSpeed(rainSpeed)
, mRainEntranceSpeed(Fallback::Map::getFloat("Weather_" + name + "_Rain_Entrance_Speed"))
, mRainMaxRaindrops(Fallback::Map::getFloat("Weather_" + name + "_Max_Raindrops"))
, mRainDiameter(Fallback::Map::getFloat("Weather_" + name + "_Rain_Diameter"))
, mRainThreshold(Fallback::Map::getFloat("Weather_" + name + "_Rain_Threshold"))
, mRainMinHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Min"))
, mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max"))
, mParticleEffect(particleEffect)
, mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "")
, mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta"))
, mCloudsMaximumPercent(Fallback::Map::getFloat("Weather_" + name + "_Clouds_Maximum_Percent"))
, mThunderFrequency(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Frequency"))
, mThunderThreshold(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Threshold"))
, mThunderSoundID()
, mFlashDecrement(Fallback::Map::getFloat("Weather_" + name + "_Flash_Decrement"))
, mFlashBrightness(0.0f)
{
mDL.FogFactor = dlFactor;
mDL.FogOffset = dlOffset;
mThunderSoundID[0] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_0");
mThunderSoundID[1] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_1");
mThunderSoundID[2] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_2");
mThunderSoundID[3] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_3");
// TODO: support weathers that have both "Ambient Loop Sound ID" and "Rain Loop Sound ID", need to play both sounds at the same time.
if (!mRainEffect.empty()) // NOTE: in vanilla, the weathers with rain seem to be hardcoded; changing Using_Precip has no effect
{
mAmbientLoopSoundID = Fallback::Map::getString("Weather_" + name + "_Rain_Loop_Sound_ID");
if (mAmbientLoopSoundID.empty()) // default to "rain" if not set
mAmbientLoopSoundID = "rain";
}
else
mAmbientLoopSoundID = Fallback::Map::getString("Weather_" + name + "_Ambient_Loop_Sound_ID");
if (Misc::StringUtils::ciEqual(mAmbientLoopSoundID, "None"))
mAmbientLoopSoundID.clear();
}
float Weather::transitionDelta() const
{
// Transition Delta describes how quickly transitioning to the weather in question will take, in Hz. Note that the
// measurement is in real time, not in-game time.
return mTransitionDelta;
}
float Weather::cloudBlendFactor(const float transitionRatio) const
{
// Clouds Maximum Percent affects how quickly the sky transitions from one sky texture to the next.
return transitionRatio / mCloudsMaximumPercent;
}
float Weather::calculateThunder(const float transitionRatio, const float elapsedSeconds, const bool isPaused)
{
// When paused, the flash brightness remains the same and no new strikes can occur.
if(!isPaused)
{
// Morrowind doesn't appear to do any calculations unless the transition ratio is higher than the Thunder Threshold.
if(transitionRatio >= mThunderThreshold && mThunderFrequency > 0.0f)
{
flashDecrement(elapsedSeconds);
if(Misc::Rng::rollProbability() <= thunderChance(transitionRatio, elapsedSeconds))
{
lightningAndThunder();
}
}
else
{
mFlashBrightness = 0.0f;
}
}
return mFlashBrightness;
}
inline void Weather::flashDecrement(const float elapsedSeconds)
{
// The Flash Decrement is measured in whole units per second. This means that if the flash brightness was
// currently 1.0, then it should take approximately 0.25 seconds to decay to 0.0 (the minimum).
float decrement = mFlashDecrement * elapsedSeconds;
mFlashBrightness = decrement > mFlashBrightness ? 0.0f : mFlashBrightness - decrement;
}
inline float Weather::thunderChance(const float transitionRatio, const float elapsedSeconds) const
{
// This formula is reversed from the observation that with Thunder Frequency set to 1, there are roughly 10 strikes
// per minute. It doesn't appear to be tied to in game time as Timescale doesn't affect it. Various values of
// Thunder Frequency seem to change the average number of strikes in a linear fashion.. During a transition, it appears to
// scaled based on how far past it is past the Thunder Threshold.
float scaleFactor = (transitionRatio - mThunderThreshold) / (1.0f - mThunderThreshold);
return ((mThunderFrequency * 10.0f) / 60.0f) * elapsedSeconds * scaleFactor;
}
inline void Weather::lightningAndThunder(void)
{
// Morrowind seems to vary the intensity of the brightness based on which of the four sound IDs it selects.
// They appear to go from 0 (brightest, closest) to 3 (faintest, farthest). The value of 0.25 per distance
// was derived by setting the Flash Decrement to 0.1 and measuring how long each value took to decay to 0.
// TODO: Determine the distribution of each distance to see if it's evenly weighted.
unsigned int distance = Misc::Rng::rollDice(4);
// Flash brightness appears additive, since if multiple strikes occur, it takes longer for it to decay to 0.
mFlashBrightness += 1 - (distance * 0.25f);
MWBase::Environment::get().getSoundManager()->playSound(mThunderSoundID[distance], 1.0, 1.0);
}
RegionWeather::RegionWeather(const ESM::Region& region)
: mWeather(invalidWeatherID)
, mChances()
{
mChances.reserve(10);
mChances.push_back(region.mData.mClear);
mChances.push_back(region.mData.mCloudy);
mChances.push_back(region.mData.mFoggy);
mChances.push_back(region.mData.mOvercast);
mChances.push_back(region.mData.mRain);
mChances.push_back(region.mData.mThunder);
mChances.push_back(region.mData.mAsh);
mChances.push_back(region.mData.mBlight);
mChances.push_back(region.mData.mA);
mChances.push_back(region.mData.mB);
}
RegionWeather::RegionWeather(const ESM::RegionWeatherState& state)
: mWeather(state.mWeather)
, mChances(state.mChances)
{
}
RegionWeather::operator ESM::RegionWeatherState() const
{
ESM::RegionWeatherState state =
{
mWeather,
mChances
};
return state;
}
void RegionWeather::setChances(const std::vector<char>& chances)
{
if(mChances.size() < chances.size())
{
mChances.reserve(chances.size());
}
int i = 0;
for(char chance : chances)
{
mChances[i] = chance;
i++;
}
// Regional weather no longer supports the current type, select a new weather pattern.
if((static_cast<size_t>(mWeather) >= mChances.size()) || (mChances[mWeather] == 0))
{
chooseNewWeather();
}
}
void RegionWeather::setWeather(int weatherID)
{
mWeather = weatherID;
}
int RegionWeather::getWeather()
{
// If the region weather was already set (by ChangeWeather, or by a previous call) then just return that value.
// Note that the region weather will be expired periodically when the weather update timer expires.
if(mWeather == invalidWeatherID)
{
chooseNewWeather();
}
return mWeather;
}
void RegionWeather::chooseNewWeather()
{
// 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).
int chance = Misc::Rng::rollDice(100) + 1; // 1..100
int sum = 0;
int i = 0;
for(; static_cast<size_t>(i) < mChances.size(); ++i)
{
sum += mChances[i];
if(chance <= sum)
{
mWeather = i;
return;
}
}
// if we hit this path then the chances don't add to 100, choose a default weather instead
mWeather = 0;
}
MoonModel::MoonModel(const std::string& name)
: mFadeInStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Start"))
, mFadeInFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Finish"))
, mFadeOutStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Start"))
, mFadeOutFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Finish"))
, mAxisOffset(Fallback::Map::getFloat("Moons_" + name + "_Axis_Offset"))
, mSpeed(Fallback::Map::getFloat("Moons_" + name + "_Speed"))
, mDailyIncrement(Fallback::Map::getFloat("Moons_" + name + "_Daily_Increment"))
, mFadeStartAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_Start_Angle"))
, mFadeEndAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_End_Angle"))
, mMoonShadowEarlyFadeAngle(Fallback::Map::getFloat("Moons_" + name + "_Moon_Shadow_Early_Fade_Angle"))
{
// Morrowind appears to have a minimum speed in order to avoid situations where the moon couldn't conceivably
// complete a rotation in a single 24 hour period. The value of 180/23 was deduced from reverse engineering.
mSpeed = std::min(mSpeed, 180.0f / 23.0f);
}
MWRender::MoonState MoonModel::calculateState(const TimeStamp& gameTime) const
{
float rotationFromHorizon = angle(gameTime);
MWRender::MoonState state =
{
rotationFromHorizon,
mAxisOffset, // Reverse engineered from Morrowind's scene graph rotation matrices.
phase(gameTime),
shadowBlend(rotationFromHorizon),
earlyMoonShadowAlpha(rotationFromHorizon) * hourlyAlpha(gameTime.getHour())
};
return state;
}
inline float MoonModel::angle(const TimeStamp& gameTime) const
{
// Morrowind's moons start travel on one side of the horizon (let's call it H-rise) and travel 180 degrees to the
// opposite horizon (let's call it H-set). Upon reaching H-set, they reset to H-rise until the next moon rise.
// When calculating the angle of the moon, several cases have to be taken into account:
// 1. Moon rises and then sets in one day.
// 2. Moon sets and doesn't rise in one day (occurs when the moon rise hour is >= 24).
// 3. Moon sets and then rises in one day.
float moonRiseHourToday = moonRiseHour(gameTime.getDay());
float moonRiseAngleToday = 0;
if(gameTime.getHour() < moonRiseHourToday)
{
float moonRiseHourYesterday = moonRiseHour(gameTime.getDay() - 1);
if(moonRiseHourYesterday < 24)
{
float moonRiseAngleYesterday = rotation(24 - moonRiseHourYesterday);
if(moonRiseAngleYesterday < 180)
{
// The moon rose but did not set yesterday, so accumulate yesterday's angle with how much we've travelled today.
moonRiseAngleToday = rotation(gameTime.getHour()) + moonRiseAngleYesterday;
}
}
}
else
{
moonRiseAngleToday = rotation(gameTime.getHour() - moonRiseHourToday);
}
if(moonRiseAngleToday >= 180)
{
// The moon set today, reset the angle to the horizon.
moonRiseAngleToday = 0;
}
return moonRiseAngleToday;
}
inline float MoonModel::moonRiseHour(unsigned int daysPassed) const
{
// This arises from the start date of 16 Last Seed, 427
// TODO: Find an alternate formula that doesn't rely on this day being fixed.
static const unsigned int startDay = 16;
// This odd formula arises from the fact that on 16 Last Seed, 17 increments have occurred, meaning
// that upon starting a new game, it must only calculate the moon phase as far back as 1 Last Seed.
// Note that we don't modulo after adding the latest daily increment because other calculations need to
// know if doing so would cause the moon rise to be postponed until the next day (which happens when
// the moon rise hour is >= 24 in Morrowind).
return mDailyIncrement + std::fmod((daysPassed - 1 + startDay) * mDailyIncrement, 24.0f);
}
inline float MoonModel::rotation(float hours) const
{
// 15 degrees per hour was reverse engineered from the rotation matrices of the Morrowind scene graph.
// Note that this correlates to 360 / 24, which is a full rotation every 24 hours, so speed is a measure
// of whole rotations that could be completed in a day.
return 15.0f * mSpeed * hours;
}
MWRender::MoonState::Phase MoonModel::phase(const TimeStamp& gameTime) const
{
// Morrowind starts with a full moon on 16 Last Seed and then begins to wane 17 Last Seed, working on 3 day phase cycle.
// If the moon didn't rise yet today, use yesterday's moon phase.
if(gameTime.getHour() < moonRiseHour(gameTime.getDay()))
return static_cast<MWRender::MoonState::Phase>((gameTime.getDay() / 3) % 8);
else
return static_cast<MWRender::MoonState::Phase>(((gameTime.getDay() + 1) / 3) % 8);
}
inline float MoonModel::shadowBlend(float angle) const
{
// The Fade End Angle and Fade Start Angle describe a region where the moon transitions from a solid disk
// that is roughly the color of the sky, to a textured surface.
// Depending on the current angle, the following values describe the ratio between the textured moon
// and the solid disk:
// 1. From Fade End Angle 1 to Fade Start Angle 1 (during moon rise): 0..1
// 2. From Fade Start Angle 1 to Fade Start Angle 2 (between moon rise and moon set): 1 (textured)
// 3. From Fade Start Angle 2 to Fade End Angle 2 (during moon set): 1..0
// 4. From Fade End Angle 2 to Fade End Angle 1 (between moon set and moon rise): 0 (solid disk)
float fadeAngle = mFadeStartAngle - mFadeEndAngle;
float fadeEndAngle2 = 180.0f - mFadeEndAngle;
float fadeStartAngle2 = 180.0f - mFadeStartAngle;
if((angle >= mFadeEndAngle) && (angle < mFadeStartAngle))
return (angle - mFadeEndAngle) / fadeAngle;
else if((angle >= mFadeStartAngle) && (angle < fadeStartAngle2))
return 1.0f;
else if((angle >= fadeStartAngle2) && (angle < fadeEndAngle2))
return (fadeEndAngle2 - angle) / fadeAngle;
else
return 0.0f;
}
inline float MoonModel::hourlyAlpha(float gameHour) const
{
// The Fade Out Start / Finish and Fade In Start / Finish describe the hours at which the moon
// appears and disappears.
// Depending on the current hour, the following values describe how transparent the moon is.
// 1. From Fade Out Start to Fade Out Finish: 1..0
// 2. From Fade Out Finish to Fade In Start: 0 (transparent)
// 3. From Fade In Start to Fade In Finish: 0..1
// 4. From Fade In Finish to Fade Out Start: 1 (solid)
if((gameHour >= mFadeOutStart) && (gameHour < mFadeOutFinish))
return (mFadeOutFinish - gameHour) / (mFadeOutFinish - mFadeOutStart);
else if((gameHour >= mFadeOutFinish) && (gameHour < mFadeInStart))
return 0.0f;
else if((gameHour >= mFadeInStart) && (gameHour < mFadeInFinish))
return (gameHour - mFadeInStart) / (mFadeInFinish - mFadeInStart);
else
return 1.0f;
}
inline float MoonModel::earlyMoonShadowAlpha(float angle) const
{
// The Moon Shadow Early Fade Angle describes an arc relative to Fade End Angle.
// Depending on the current angle, the following values describe how transparent the moon is.
// 1. From Moon Shadow Early Fade Angle 1 to Fade End Angle 1 (during moon rise): 0..1
// 2. From Fade End Angle 1 to Fade End Angle 2 (between moon rise and moon set): 1 (solid)
// 3. From Fade End Angle 2 to Moon Shadow Early Fade Angle 2 (during moon set): 1..0
// 4. From Moon Shadow Early Fade Angle 2 to Moon Shadow Early Fade Angle 1: 0 (transparent)
float moonShadowEarlyFadeAngle1 = mFadeEndAngle - mMoonShadowEarlyFadeAngle;
float fadeEndAngle2 = 180.0f - mFadeEndAngle;
float moonShadowEarlyFadeAngle2 = fadeEndAngle2 + mMoonShadowEarlyFadeAngle;
if((angle >= moonShadowEarlyFadeAngle1) && (angle < mFadeEndAngle))
return (angle - moonShadowEarlyFadeAngle1) / mMoonShadowEarlyFadeAngle;
else if((angle >= mFadeEndAngle) && (angle < fadeEndAngle2))
return 1.0f;
else if((angle >= fadeEndAngle2) && (angle < moonShadowEarlyFadeAngle2))
return (moonShadowEarlyFadeAngle2 - angle) / mMoonShadowEarlyFadeAngle;
else
return 0.0f;
}
WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, MWWorld::ESMStore& store)
: mStore(store)
, mRendering(rendering)
, mSunriseTime(Fallback::Map::getFloat("Weather_Sunrise_Time"))
, mSunsetTime(Fallback::Map::getFloat("Weather_Sunset_Time"))
, mSunriseDuration(Fallback::Map::getFloat("Weather_Sunrise_Duration"))
, mSunsetDuration(Fallback::Map::getFloat("Weather_Sunset_Duration"))
, mSunPreSunsetTime(Fallback::Map::getFloat("Weather_Sun_Pre-Sunset_Time"))
, mNightFade(0, 0, 0, 1)
, mHoursBetweenWeatherChanges(Fallback::Map::getFloat("Weather_Hours_Between_Weather_Changes"))
, mRainSpeed(Fallback::Map::getFloat("Weather_Precip_Gravity"))
, mUnderwaterFog(Fallback::Map::getFloat("Water_UnderwaterSunriseFog"),
Fallback::Map::getFloat("Water_UnderwaterDayFog"),
Fallback::Map::getFloat("Water_UnderwaterSunsetFog"),
Fallback::Map::getFloat("Water_UnderwaterNightFog"))
, mWeatherSettings()
, mMasser("Masser")
, mSecunda("Secunda")
, mWindSpeed(0.f)
, mCurrentWindSpeed(0.f)
, mNextWindSpeed(0.f)
, mIsStorm(false)
, mPrecipitation(false)
, mStormDirection(0,1,0)
, mCurrentRegion()
, mTimePassed(0)
, mFastForward(false)
, mWeatherUpdateTime(mHoursBetweenWeatherChanges)
, mTransitionFactor(0)
, mNightDayMode(Default)
, mCurrentWeather(0)
, mNextWeather(0)
, mQueuedWeather(0)
, mRegions()
, mResult()
, mAmbientSound(nullptr)
, mPlayingSoundID()
{
mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration;
mTimeSettings.mNightEnd = mSunriseTime;
mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration;
mTimeSettings.mDayEnd = mSunsetTime;
mTimeSettings.addSetting("Sky");
mTimeSettings.addSetting("Ambient");
mTimeSettings.addSetting("Fog");
mTimeSettings.addSetting("Sun");
// Morrowind handles stars settings differently for other ones
mTimeSettings.mStarsPostSunsetStart = Fallback::Map::getFloat("Weather_Stars_Post-Sunset_Start");
mTimeSettings.mStarsPreSunriseFinish = Fallback::Map::getFloat("Weather_Stars_Pre-Sunrise_Finish");
mTimeSettings.mStarsFadingDuration = Fallback::Map::getFloat("Weather_Stars_Fading_Duration");
WeatherSetting starSetting = {
mTimeSettings.mStarsPreSunriseFinish,
mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPreSunriseFinish,
mTimeSettings.mStarsPostSunsetStart,
mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPostSunsetStart
};
mTimeSettings.mSunriseTransitions["Stars"] = starSetting;
mWeatherSettings.reserve(10);
// These distant land fog factor and offset values are the defaults MGE XE provides. Should be
// provided by settings somewhere?
addWeather("Clear", 1.0f, 0.0f); // 0
addWeather("Cloudy", 0.9f, 0.0f); // 1
addWeather("Foggy", 0.2f, 30.0f); // 2
addWeather("Overcast", 0.7f, 0.0f); // 3
addWeather("Rain", 0.5f, 10.0f); // 4
addWeather("Thunderstorm", 0.5f, 20.0f); // 5
addWeather("Ashstorm", 0.2f, 50.0f, "meshes\\ashcloud.nif"); // 6
addWeather("Blight", 0.2f, 60.0f, "meshes\\blightcloud.nif"); // 7
addWeather("Snow", 0.5f, 40.0f, "meshes\\snow.nif"); // 8
addWeather("Blizzard", 0.16f, 70.0f, "meshes\\blizzard.nif"); // 9
Store<ESM::Region>::iterator it = store.get<ESM::Region>().begin();
for(; it != store.get<ESM::Region>().end(); ++it)
{
std::string regionID = Misc::StringUtils::lowerCase(it->mId);
mRegions.insert(std::make_pair(regionID, RegionWeather(*it)));
}
forceWeather(0);
}
WeatherManager::~WeatherManager()
{
stopSounds();
}
void WeatherManager::changeWeather(const std::string& regionID, const unsigned int weatherID)
{
// In Morrowind, this seems to have the following behavior, when applied to the current region:
// - When there is no transition in progress, start transitioning to the new weather.
// - If there is a transition in progress, queue up the transition and process it when the current one completes.
// - If there is a transition in progress, and a queued transition, overwrite the queued transition.
// - If multiple calls to ChangeWeather are made while paused (console up), only the last call will be used,
// meaning that if there was no transition in progress, only the last ChangeWeather will be processed.
// If the region isn't current, Morrowind will store the new weather for the region in question.
if(weatherID < mWeatherSettings.size())
{
std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID);
std::map<std::string, RegionWeather>::iterator it = mRegions.find(lowerCaseRegionID);
if(it != mRegions.end())
13 years ago
{
it->second.setWeather(weatherID);
regionalWeatherChanged(it->first, it->second);
13 years ago
}
}
}
void WeatherManager::modRegion(const std::string& regionID, const std::vector<char>& chances)
{
// Sets the region's probability for various weather patterns. Note that this appears to be saved permanently.
// In Morrowind, this seems to have the following behavior when applied to the current region:
// - If the region supports the current weather, no change in current weather occurs.
// - If the region no longer supports the current weather, and there is no transition in progress, begin to
// transition to a new supported weather type.
// - If the region no longer supports the current weather, and there is a transition in progress, queue a
// transition to a new supported weather type.
std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID);
std::map<std::string, RegionWeather>::iterator it = mRegions.find(lowerCaseRegionID);
if(it != mRegions.end())
{
it->second.setChances(chances);
regionalWeatherChanged(it->first, it->second);
}
}
13 years ago
void WeatherManager::playerTeleported(const std::string& playerRegion, bool isExterior)
{
// If the player teleports to an outdoors cell in a new region (for instance, by travelling), the weather needs to
// be changed immediately, and any transitions for the previous region discarded.
{
std::map<std::string, RegionWeather>::iterator it = mRegions.find(playerRegion);
if(it != mRegions.end() && playerRegion != mCurrentRegion)
{
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
If we've moved to another region, set our weather creation ability to false;
the server will set it to true if it wants us creating weather here
*/
setWeatherCreationState(false);
/*
End of tes3mp addition
*/
mCurrentRegion = playerRegion;
forceWeather(it->second.getWeather());
}
/*
Start of tes3mp addition
There's no scenario where we want our weather creation ability to be true in
an interior, so set it to false
*/
else if (!isExterior)
setWeatherCreationState(false);
/*
End of tes3mp addition
*/
}
}
13 years ago
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
Make it possible to set a specific weather state for a region from elsewhere
in the code
*/
void WeatherManager::setRegionWeather(const std::string& region, const int currentWeather, const int nextWeather,
const int queuedWeather, const float transitionFactor, bool force)
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
{
bool isSameRegion = Misc::StringUtils::ciEqual(region, mCurrentRegion);
// Only ever force weather if we are in the correct region for it
if (isSameRegion)
{
if (force)
{
mCurrentWeather = currentWeather;
mNextWeather = nextWeather;
mQueuedWeather = queuedWeather;
mTransitionFactor = transitionFactor;
}
else
{
// Keep the queued weather in sync if everything else already is
if (mCurrentWeather == currentWeather && mNextWeather == nextWeather)
{
mQueuedWeather = queuedWeather;
}
// Start moving towards the next weather immediately if it's different from the one we have
else if (mNextWeather != nextWeather && nextWeather != -1)
{
changeWeather(region, nextWeather);
}
// Otherwise, if the current weather is different from the one we should have, move towards it
else if (mCurrentWeather != currentWeather)
{
changeWeather(region, currentWeather);
}
}
}
else
{
changeWeather(region, currentWeather);
}
}
/*
End of tes3mp addition
*/
float WeatherManager::calculateWindSpeed(int weatherId, float currentSpeed)
{
float targetSpeed = std::min(8.0f * mWeatherSettings[weatherId].mWindSpeed, 70.f);
if (currentSpeed == 0.f)
currentSpeed = targetSpeed;
float multiplier = mWeatherSettings[weatherId].mRainEffect.empty() ? 1.f : 0.5f;
float updatedSpeed = (Misc::Rng::rollClosedProbability() - 0.5f) * multiplier * targetSpeed + currentSpeed;
if (updatedSpeed > 0.5f * targetSpeed && updatedSpeed < 2.f * targetSpeed)
currentSpeed = updatedSpeed;
return currentSpeed;
}
void WeatherManager::update(float duration, bool paused, const TimeStamp& time, bool isExterior)
{
MWWorld::ConstPtr player = MWMechanics::getPlayer();
13 years ago
if(!paused || mFastForward)
{
// Add new transitions when either the player's current external region changes.
std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion);
if(updateWeatherTime() || updateWeatherRegion(playerRegion))
{
std::map<std::string, RegionWeather>::iterator it = mRegions.find(mCurrentRegion);
if(it != mRegions.end())
{
addWeatherTransition(it->second.getWeather());
}
}
updateWeatherTransitions(duration);
}
bool isDay = time.getHour() >= mSunriseTime && time.getHour() <= mTimeSettings.mNightStart;
if (isExterior && !isDay)
mNightDayMode = ExteriorNight;
else if (!isExterior && isDay && mWeatherSettings[mCurrentWeather].mGlareView >= 0.5f)
mNightDayMode = InteriorDay;
else
mNightDayMode = Default;
if(!isExterior)
{
mRendering.setSkyEnabled(false);
stopSounds();
mWindSpeed = 0.f;
mCurrentWindSpeed = 0.f;
mNextWindSpeed = 0.f;
return;
}
calculateWeatherResult(time.getHour(), duration, paused);
if (!paused)
{
mWindSpeed = mResult.mWindSpeed;
mCurrentWindSpeed = mResult.mCurrentWindSpeed;
mNextWindSpeed = mResult.mNextWindSpeed;
}
mIsStorm = mResult.mIsStorm;
12 years ago
// For some reason Ash Storm is not considered as a precipitation weather in game
mPrecipitation = !(mResult.mParticleEffect.empty() && mResult.mRainEffect.empty())
&& mResult.mParticleEffect != "meshes\\ashcloud.nif";
if (mIsStorm)
{
osg::Vec3f stormDirection(0, 1, 0);
if (mResult.mParticleEffect == "meshes\\ashcloud.nif" || mResult.mParticleEffect == "meshes\\blightcloud.nif")
{
osg::Vec3f playerPos (MWMechanics::getPlayer().getRefData().getPosition().asVec3());
playerPos.z() = 0;
osg::Vec3f redMountainPos (25000, 70000, 0);
stormDirection = (playerPos - redMountainPos);
stormDirection.normalize();
}
mStormDirection = stormDirection;
mRendering.getSkyManager()->setStormDirection(mStormDirection);
}
// disable sun during night
if (time.getHour() >= mTimeSettings.mNightStart || time.getHour() <= mSunriseTime)
mRendering.getSkyManager()->sunDisable();
10 years ago
else
mRendering.getSkyManager()->sunEnable();
// Update the sun direction. Run it east to west at a fixed angle from overhead.
// The sun's speed at day and night may differ, since mSunriseTime and mNightStart
// mark when the sun is level with the horizon.
{
// Shift times into a 24-hour window beginning at mSunriseTime...
float adjustedHour = time.getHour();
float adjustedNightStart = mTimeSettings.mNightStart;
if ( time.getHour() < mSunriseTime )
adjustedHour += 24.f;
if ( mTimeSettings.mNightStart < mSunriseTime )
adjustedNightStart += 24.f;
const bool is_night = adjustedHour >= adjustedNightStart;
const float dayDuration = adjustedNightStart - mSunriseTime;
const float nightDuration = 24.f - dayDuration;
double theta;
if ( !is_night )
{
theta = static_cast<float>(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration;
}
else
{
theta = static_cast<float>(osg::PI) - static_cast<float>(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration;
}
osg::Vec3f final(
static_cast<float>(cos(theta)),
-0.268f, // approx tan( -15 degrees )
static_cast<float>(sin(theta)));
mRendering.setSunDirection( final * -1 );
}
13 years ago
float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog");
float peakHour = mSunriseTime + (mTimeSettings.mNightStart - mSunriseTime) / 2;
float glareFade = 1.f;
if (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart)
glareFade = 0.f;
else if (time.getHour() < peakHour)
glareFade = 1.f - (peakHour - time.getHour()) / (peakHour - mSunriseTime);
else
glareFade = 1.f - (time.getHour() - peakHour) / (mTimeSettings.mNightStart - peakHour);
mRendering.getSkyManager()->setGlareTimeOfDayFade(glareFade);
mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time));
mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time));
13 years ago
mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor,
mResult.mDLFogOffset/100.0f, mResult.mFogColor);
mRendering.setAmbientColour(mResult.mAmbientColor);
mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade);
mRendering.getSkyManager()->setWeather(mResult);
// Play sounds
if (mPlayingSoundID != mResult.mAmbientLoopSoundID)
{
stopSounds();
if (!mResult.mAmbientLoopSoundID.empty())
mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(
mResult.mAmbientLoopSoundID, mResult.mAmbientSoundVolume, 1.0,
MWSound::Type::Sfx, MWSound::PlayMode::Loop
);
mPlayingSoundID = mResult.mAmbientLoopSoundID;
}
else if (mAmbientSound)
mAmbientSound->setVolume(mResult.mAmbientSoundVolume);
}
void WeatherManager::stopSounds()
{
if (mAmbientSound)
MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound);
mAmbientSound = nullptr;
mPlayingSoundID.clear();
}
float WeatherManager::getWindSpeed() const
{
return mWindSpeed;
}
bool WeatherManager::isInStorm() const
{
return mIsStorm;
}
osg::Vec3f WeatherManager::getStormDirection() const
{
return mStormDirection;
}
12 years ago
void WeatherManager::advanceTime(double hours, bool incremental)
{
// In Morrowind, when the player sleeps/waits, serves jail time, travels, or trains, all weather transitions are
// immediately applied, regardless of whatever transition time might have been remaining.
mTimePassed += hours;
mFastForward = !incremental ? true : mFastForward;
}
unsigned int WeatherManager::getWeatherID() const
12 years ago
{
return mCurrentWeather;
12 years ago
}
NightDayMode WeatherManager::getNightDayMode() const
{
return mNightDayMode;
}
bool WeatherManager::useTorches(float hour) const
{
bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart;
return isDark && !mPrecipitation;
}
void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress)
{
ESM::WeatherState state;
state.mCurrentRegion = mCurrentRegion;
state.mTimePassed = mTimePassed;
state.mFastForward = mFastForward;
state.mWeatherUpdateTime = mWeatherUpdateTime;
state.mTransitionFactor = mTransitionFactor;
state.mCurrentWeather = mCurrentWeather;
state.mNextWeather = mNextWeather;
state.mQueuedWeather = mQueuedWeather;
std::map<std::string, RegionWeather>::iterator it = mRegions.begin();
for(; it != mRegions.end(); ++it)
{
state.mRegions.insert(std::make_pair(it->first, it->second));
}
writer.startRecord(ESM::REC_WTHR);
state.save(writer);
writer.endRecord(ESM::REC_WTHR);
}
bool WeatherManager::readRecord(ESM::ESMReader& reader, uint32_t type)
{
if(ESM::REC_WTHR == type)
{
static const int oldestCompatibleSaveFormat = 2;
if(reader.getFormat() < oldestCompatibleSaveFormat)
{
// Weather state isn't really all that important, so to preserve older save games, we'll just discard the
// older weather records, rather than fail to handle the record.
reader.skipRecord();
}
else
{
ESM::WeatherState state;
state.load(reader);
mCurrentRegion.swap(state.mCurrentRegion);
mTimePassed = state.mTimePassed;
mFastForward = state.mFastForward;
mWeatherUpdateTime = state.mWeatherUpdateTime;
mTransitionFactor = state.mTransitionFactor;
mCurrentWeather = state.mCurrentWeather;
mNextWeather = state.mNextWeather;
mQueuedWeather = state.mQueuedWeather;
mRegions.clear();
importRegions();
for(std::map<std::string, ESM::RegionWeatherState>::iterator it = state.mRegions.begin(); it != state.mRegions.end(); ++it)
{
std::map<std::string, RegionWeather>::iterator found = mRegions.find(it->first);
if (found != mRegions.end())
{
found->second = RegionWeather(it->second);
}
}
}
return true;
}
return false;
}
void WeatherManager::clear()
{
stopSounds();
mCurrentRegion = "";
mTimePassed = 0.0f;
mWeatherUpdateTime = 0.0f;
forceWeather(0);
mRegions.clear();
importRegions();
}
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
Make it possible to check whether the local WeatherManager has the
ability to create weather changes
*/
bool WeatherManager::getWeatherCreationState()
{
return mWeatherCreationState;
}
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to enable and disable the local WeatherManager's ability
to create weather changes
*/
void WeatherManager::setWeatherCreationState(bool state)
{
mWeatherCreationState = state;
}
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to send the current weather in a WorldWeather packet
when requested from elsewhere in the code
*/
void WeatherManager::sendWeather()
{
mwmp::Worldstate *worldstate = mwmp::Main::get().getNetworking()->getWorldstate();
worldstate->sendWeather(mCurrentRegion, mCurrentWeather, mNextWeather, mQueuedWeather, mTransitionFactor);
}
/*
End of tes3mp addition
*/
inline void WeatherManager::addWeather(const std::string& name,
float dlFactor, float dlOffset,
const std::string& particleEffect)
{
static const float fStromWindSpeed = mStore.get<ESM::GameSetting>().find("fStromWindSpeed")->mValue.getFloat();
Weather weather(name, fStromWindSpeed, mRainSpeed, dlFactor, dlOffset, particleEffect);
mWeatherSettings.push_back(weather);
}
inline void WeatherManager::importRegions()
{
for(const ESM::Region& region : mStore.get<ESM::Region>())
{
std::string regionID = Misc::StringUtils::lowerCase(region.mId);
mRegions.insert(std::make_pair(regionID, RegionWeather(region)));
}
}
inline void WeatherManager::regionalWeatherChanged(const std::string& regionID, RegionWeather& region)
{
// If the region is current, then add a weather transition for it.
MWWorld::ConstPtr player = MWMechanics::getPlayer();
if(player.isInCell())
{
if(Misc::StringUtils::ciEqual(regionID, mCurrentRegion))
{
addWeatherTransition(region.getWeather());
}
}
}
inline bool WeatherManager::updateWeatherTime()
{
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp change (major)
Avoid creating any weather changes on this client unless approved by the server
*/
if (!mWeatherCreationState)
return false;
/*
End of tes3mp change (major)
*/
mWeatherUpdateTime -= mTimePassed;
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
mTimePassed = 0.0f;
if(mWeatherUpdateTime <= 0.0f)
{
// Expire all regional weather, so that any call to getWeather() will return a new weather ID.
std::map<std::string, RegionWeather>::iterator it = mRegions.begin();
for(; it != mRegions.end(); ++it)
{
it->second.setWeather(invalidWeatherID);
}
mWeatherUpdateTime += mHoursBetweenWeatherChanges;
return true;
}
return false;
}
inline bool WeatherManager::updateWeatherRegion(const std::string& playerRegion)
{
if(!playerRegion.empty() && playerRegion != mCurrentRegion)
{
mCurrentRegion = playerRegion;
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
If we've moved to another region, set our weather creation ability to false;
the server will set it to true if it wants us creating weather here
*/
setWeatherCreationState(false);
/*
End of tes3mp addition
*/
return true;
}
return false;
}
inline void WeatherManager::updateWeatherTransitions(const float elapsedRealSeconds)
{
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
Track whether an ID_WORLD_WEATHER packet should be sent or not
*/
bool shouldSendPacket = false;
/*
End of tes3mp addition
*/
// When a player chooses to train, wait, or serves jail time, any transitions will be fast forwarded to the last
// weather type set, regardless of the remaining transition time.
if(!mFastForward && inTransition())
{
const float delta = mWeatherSettings[mNextWeather].transitionDelta();
mTransitionFactor -= elapsedRealSeconds * delta;
if(mTransitionFactor <= 0.0f)
{
mCurrentWeather = mNextWeather;
mNextWeather = mQueuedWeather;
mQueuedWeather = invalidWeatherID;
// We may have begun processing the queued transition, so we need to apply the remaining time towards it.
if(inTransition())
{
const float newDelta = mWeatherSettings[mNextWeather].transitionDelta();
const float remainingSeconds = -(mTransitionFactor / delta);
mTransitionFactor = 1.0f - (remainingSeconds * newDelta);
}
else
{
mTransitionFactor = 0.0f;
}
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
The weather is changing, so decide to send an ID_WORLD_WEATHER packet
*/
shouldSendPacket = true;
/*
End of tes3mp addition
*/
}
}
else
{
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
If the weather is changing, decide to send an ID_WORLD_WEATHER packet
*/
if (mQueuedWeather != invalidWeatherID || mNextWeather != invalidWeatherID)
shouldSendPacket = true;
/*
End of tes3mp addition
*/
if(mQueuedWeather != invalidWeatherID)
{
mCurrentWeather = mQueuedWeather;
}
else if(mNextWeather != invalidWeatherID)
{
mCurrentWeather = mNextWeather;
}
mNextWeather = invalidWeatherID;
mQueuedWeather = invalidWeatherID;
mFastForward = false;
}
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
Send an ID_WORLD_WEATHER packet every time the weather changes here, but only
if we are allowed to create weather changes on this client
*/
if (shouldSendPacket && mWeatherCreationState && !mCurrentRegion.empty())
{
sendWeather();
}
/*
End of tes3mp addition
*/
}
inline void WeatherManager::forceWeather(const int weatherID)
{
mTransitionFactor = 0.0f;
mCurrentWeather = weatherID;
mNextWeather = invalidWeatherID;
mQueuedWeather = invalidWeatherID;
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
Send an ID_WORLD_WEATHER packet every time the weather changes here, but only
if we are allowed to create weather changes on this client
*/
if (!mCurrentRegion.empty() && mWeatherCreationState)
{
sendWeather();
}
/*
End of tes3mp addition
*/
}
inline bool WeatherManager::inTransition()
{
return mNextWeather != invalidWeatherID;
}
inline void WeatherManager::addWeatherTransition(const int weatherID)
{
// In order to work like ChangeWeather expects, this method begins transitioning to the new weather immediately if
// no transition is in progress, otherwise it queues it to be transitioned.
assert(weatherID >= 0 && static_cast<size_t>(weatherID) < mWeatherSettings.size());
if(!inTransition() && (weatherID != mCurrentWeather))
{
mNextWeather = weatherID;
mTransitionFactor = 1.0f;
}
else if(inTransition() && (weatherID != mNextWeather))
{
mQueuedWeather = weatherID;
}
[General] Reimplement weather synchronization to allow soft transitions Although weather sync was added by Koncord to the rewrite in fd721143e226061d72f7b8931b8d4192a5327f47 in a way that used surprisingly few lines of code, it relied on the server requesting weather states every second from authority players and sending them to non-authority players, while also allowing only very sudden weather transitions across regions, i.e. if there was one player in the Ascadian Isles who had stormy weather, and another player with clear weather in the Bitter Coast Region walked across to the Ascadian Isles, that player was instantly made to have stormy weather with no kind of transition at all. My approach solves both of those problems. It solves the packet spam by only sending weather updates to the server when weather changes happen or when there are new arrivals to a weather authority's region, and it allows for both sudden weather transitions when players teleport to a region and for soft, gradual transitions when players walk across to a region. It is inspired by my previous actor sync, and uses a WorldRegionAuthority packet to set players as region authorities in a similar way to how ActorAuthority sets players as cell AI authorities. Weather changes are created only by the region authority for a given region, and weather packets are also only sent by that authority. However, it should be noted that gradual weather transitions are used by default in this implementation. To use sudden weather transitions, the serverside Lua scripts need to forward WorldWeather packets with the forceWeather boolean set to true. That is, however, already handled by our default Lua scripts in situations where it makes sense.
7 years ago
/*
Start of tes3mp addition
Send an ID_WORLD_WEATHER packet every time the weather changes here, but only
if we are allowed to create weather changes on this client
*/
if (mWeatherCreationState)
{
sendWeather();
}
/*
End of tes3mp addition
*/
}
inline void WeatherManager::calculateWeatherResult(const float gameHour,
const float elapsedSeconds,
const bool isPaused)
{
float flash = 0.0f;
if(!inTransition())
{
calculateResult(mCurrentWeather, gameHour);
flash = mWeatherSettings[mCurrentWeather].calculateThunder(1.0f, elapsedSeconds, isPaused);
}
else
{
calculateTransitionResult(1 - mTransitionFactor, gameHour);
float currentFlash = mWeatherSettings[mCurrentWeather].calculateThunder(mTransitionFactor,
elapsedSeconds,
isPaused);
float nextFlash = mWeatherSettings[mNextWeather].calculateThunder(1 - mTransitionFactor,
elapsedSeconds,
isPaused);
flash = currentFlash + nextFlash;
}
osg::Vec4f flashColor(flash, flash, flash, 0.0f);
mResult.mFogColor += flashColor;
mResult.mAmbientColor += flashColor;
mResult.mSunColor += flashColor;
}
inline void WeatherManager::calculateResult(const int weatherID, const float gameHour)
{
const Weather& current = mWeatherSettings[weatherID];
mResult.mCloudTexture = current.mCloudTexture;
mResult.mCloudBlendFactor = 0;
mResult.mNextWindSpeed = 0;
mResult.mWindSpeed = mResult.mCurrentWindSpeed = calculateWindSpeed(weatherID, mWindSpeed);
mResult.mBaseWindSpeed = mWeatherSettings[weatherID].mWindSpeed;
mResult.mCloudSpeed = current.mCloudSpeed;
mResult.mGlareView = current.mGlareView;
mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID;
mResult.mAmbientSoundVolume = 1.f;
mResult.mPrecipitationAlpha = 1.f;
mResult.mIsStorm = current.mIsStorm;
mResult.mRainSpeed = current.mRainSpeed;
mResult.mRainEntranceSpeed = current.mRainEntranceSpeed;
mResult.mRainDiameter = current.mRainDiameter;
mResult.mRainMinHeight = current.mRainMinHeight;
mResult.mRainMaxHeight = current.mRainMaxHeight;
mResult.mRainMaxRaindrops = current.mRainMaxRaindrops;
mResult.mParticleEffect = current.mParticleEffect;
mResult.mRainEffect = current.mRainEffect;
mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart + mTimeSettings.mStarsPostSunsetStart - mTimeSettings.mStarsFadingDuration);
mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings, "Fog");
mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings, "Fog");
mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings, "Ambient");
mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings, "Sun");
mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings, "Sky");
mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings, "Stars");
mResult.mDLFogFactor = current.mDL.FogFactor;
mResult.mDLFogOffset = current.mDL.FogOffset;
WeatherSetting setting = mTimeSettings.getSetting("Sun");
float preSunsetTime = setting.mPreSunsetTime;
if (gameHour >= mTimeSettings.mDayEnd - preSunsetTime)
{
float factor = 1.f;
if (preSunsetTime > 0)
factor = (gameHour - (mTimeSettings.mDayEnd - preSunsetTime)) / preSunsetTime;
factor = std::min(1.f, factor);
mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor);
// The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because
// MW applied the color to the ambient term as well. After the ambient and emissive terms are added together, the fixed pipeline
// would then clamp the total lighting to (1,1,1). A noticeable change in color tone can be observed when only one of the color components gets clamped.
// Unfortunately that means we can't use the INI color as is, have to replicate the above nonsense.
mResult.mSunDiscColor = mResult.mSunDiscColor + osg::componentMultiply(mResult.mSunDiscColor, mResult.mAmbientColor);
for (int i=0; i<3; ++i)
mResult.mSunDiscColor[i] = std::min(1.f, mResult.mSunDiscColor[i]);
}
else
mResult.mSunDiscColor = osg::Vec4f(1,1,1,1);
if (gameHour >= mTimeSettings.mDayEnd)
{
// sunset
float fade = std::min(1.f, (gameHour - mTimeSettings.mDayEnd) / (mTimeSettings.mNightStart - mTimeSettings.mDayEnd));
fade = fade*fade;
mResult.mSunDiscColor.a() = 1.f - fade;
}
else if (gameHour >= mTimeSettings.mNightEnd && gameHour <= mTimeSettings.mNightEnd + mSunriseDuration / 2.f)
{
// sunrise
mResult.mSunDiscColor.a() = gameHour - mTimeSettings.mNightEnd;
}
else
mResult.mSunDiscColor.a() = 1;
}
inline void WeatherManager::calculateTransitionResult(const float factor, const float gameHour)
{
calculateResult(mCurrentWeather, gameHour);
const MWRender::WeatherResult current = mResult;
calculateResult(mNextWeather, gameHour);
const MWRender::WeatherResult other = mResult;
mResult.mCloudTexture = current.mCloudTexture;
mResult.mNextCloudTexture = other.mCloudTexture;
mResult.mCloudBlendFactor = mWeatherSettings[mNextWeather].cloudBlendFactor(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.mDLFogFactor = lerp(current.mDLFogFactor, other.mDLFogFactor, factor);
mResult.mDLFogOffset = lerp(current.mDLFogOffset, other.mDLFogOffset, factor);
mResult.mCurrentWindSpeed = calculateWindSpeed(mCurrentWeather, mCurrentWindSpeed);
mResult.mNextWindSpeed = calculateWindSpeed(mNextWeather, mNextWindSpeed);
mResult.mBaseWindSpeed = lerp(current.mBaseWindSpeed, other.mBaseWindSpeed, factor);
mResult.mWindSpeed = lerp(mResult.mCurrentWindSpeed, mResult.mNextWindSpeed, factor);
mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor);
mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor);
mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor);
mResult.mNight = current.mNight;
float threshold = mWeatherSettings[mNextWeather].mRainThreshold;
if (threshold <= 0)
threshold = 0.5f;
if(factor < threshold)
{
mResult.mIsStorm = current.mIsStorm;
mResult.mParticleEffect = current.mParticleEffect;
mResult.mRainEffect = current.mRainEffect;
mResult.mRainSpeed = current.mRainSpeed;
mResult.mRainEntranceSpeed = current.mRainEntranceSpeed;
mResult.mAmbientSoundVolume = 1 - factor / threshold;
mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume;
mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID;
mResult.mRainDiameter = current.mRainDiameter;
mResult.mRainMinHeight = current.mRainMinHeight;
mResult.mRainMaxHeight = current.mRainMaxHeight;
mResult.mRainMaxRaindrops = current.mRainMaxRaindrops;
}
else
{
mResult.mIsStorm = other.mIsStorm;
mResult.mParticleEffect = other.mParticleEffect;
mResult.mRainEffect = other.mRainEffect;
mResult.mRainSpeed = other.mRainSpeed;
mResult.mRainEntranceSpeed = other.mRainEntranceSpeed;
mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold);
mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume;
mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID;
mResult.mRainDiameter = other.mRainDiameter;
mResult.mRainMinHeight = other.mRainMinHeight;
mResult.mRainMaxHeight = other.mRainMaxHeight;
mResult.mRainMaxRaindrops = other.mRainMaxRaindrops;
}
}