Refactor weather transitions to act more like MW

Fixed several issues:
* Waiting/jail time/training all now properly skip remaining transitions
* ChangeWeather no longer permanently sets the region's weather
* ChangeWeather being called during a transition now correctly queues up
another transition
* Corrected transition delta and factor calculations
* ModRegion settings are now saved
sceneinput
slothlife 9 years ago
parent e76401d5ea
commit 54fa5273dc

@ -507,60 +507,40 @@ class ConvertGAME : public Converter
public: public:
ConvertGAME() : mHasGame(false) {} ConvertGAME() : mHasGame(false) {}
std::string toString(int weatherId)
{
switch (weatherId)
{
case 0:
return "clear";
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";
case -1:
return "";
default:
{
std::stringstream error;
error << "unknown weather id: " << weatherId;
throw std::runtime_error(error.str());
}
}
}
virtual void read(ESM::ESMReader &esm) virtual void read(ESM::ESMReader &esm)
{ {
mGame.load(esm); mGame.load(esm);
mHasGame = true; mHasGame = true;
} }
int validateWeatherID(int weatherID)
{
if(weatherID >= -1 && weatherID < 10)
{
return weatherID;
}
else
{
std::stringstream error;
error << "Invalid weather ID:" << weatherID << std::endl;
throw std::runtime_error(error.str());
}
}
virtual void write(ESM::ESMWriter &esm) virtual void write(ESM::ESMWriter &esm)
{ {
if (!mHasGame) if (!mHasGame)
return; return;
esm.startRecord(ESM::REC_WTHR); esm.startRecord(ESM::REC_WTHR);
ESM::WeatherState weather; ESM::WeatherState weather;
weather.mCurrentWeather = toString(mGame.mGMDT.mCurrentWeather); weather.mTimePassed = 0.0f;
weather.mNextWeather = toString(mGame.mGMDT.mNextWeather); weather.mFastForward = false;
weather.mRemainingTransitionTime = mGame.mGMDT.mWeatherTransition/100.f*(0.015f*24*3600); weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour;
weather.mHour = mContext->mHour; weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f);
weather.mWindSpeed = 0.f; weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather);
weather.mTimePassed = 0.0; weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather);
weather.mFirstUpdate = false; weather.mQueuedWeather = -1;
// TODO: Determine how ModRegion modifiers are saved in Morrowind.
weather.save(esm); weather.save(esm);
esm.endRecord(ESM::REC_WTHR); esm.endRecord(ESM::REC_WTHR);
} }

@ -128,8 +128,7 @@ void OMW::Engine::frame(float frametime)
} }
if (!guiActive) if (!guiActive)
mEnvironment.getWorld()->advanceTime( mEnvironment.getWorld()->advanceTimeByFrame(frametime);
frametime*mEnvironment.getWorld()->getTimeScaleFactor()/3600);
} }
osg::Timer_t afterScriptTick = osg::Timer::instance()->tick(); osg::Timer_t afterScriptTick = osg::Timer::instance()->tick();

@ -188,6 +188,9 @@ namespace MWBase
virtual void advanceTime (double hours) = 0; virtual void advanceTime (double hours) = 0;
///< Advance in-game time. ///< Advance in-game time.
virtual void advanceTimeByFrame (double frametime) = 0;
///< Advance in-game time by the duration of a frame.
virtual void setHour (double hour) = 0; virtual void setHour (double hour) = 0;
///< Set in-game time hour. ///< Set in-game time hour.

File diff suppressed because it is too large Load Diff

@ -14,6 +14,7 @@
namespace ESM namespace ESM
{ {
struct Region; struct Region;
struct RegionWeatherState;
class ESMWriter; class ESMWriter;
class ESMReader; class ESMReader;
} }
@ -31,6 +32,7 @@ namespace Loading
namespace MWWorld namespace MWWorld
{ {
class Fallback; class Fallback;
class TimeStamp;
/// Defines a single weather setting (according to INI) /// Defines a single weather setting (according to INI)
class Weather class Weather
@ -110,7 +112,7 @@ namespace MWWorld
// Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature // Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature
// is broken in the vanilla game and was disabled. // is broken in the vanilla game and was disabled.
float transitionSeconds() const; float transitionDelta() const;
float cloudBlendFactor(float transitionRatio) const; float cloudBlendFactor(float transitionRatio) const;
private: private:
@ -118,12 +120,35 @@ namespace MWWorld
float mCloudsMaximumPercent; float mCloudsMaximumPercent;
}; };
/// A class for storing a region's weather.
class RegionWeather
{
public:
explicit RegionWeather(const ESM::Region& region);
explicit RegionWeather(const ESM::RegionWeatherState& state);
explicit operator ESM::RegionWeatherState() const;
void setChances(const std::vector<char>& chances);
void setWeather(int weatherID);
int getWeather();
private:
int mWeather;
std::vector<char> mChances;
void chooseNewWeather();
};
/// A class that acts as a model for the moons.
class MoonModel class MoonModel
{ {
public: public:
MoonModel(const std::string& name, const MWWorld::Fallback& fallback); MoonModel(const std::string& name, const MWWorld::Fallback& fallback);
MWRender::MoonState calculateState(unsigned int daysPassed, float gameHour) const; MWRender::MoonState calculateState(const TimeStamp& gameTime) const;
private: private:
float mFadeInStart; float mFadeInStart;
@ -137,23 +162,23 @@ namespace MWWorld
float mFadeEndAngle; float mFadeEndAngle;
float mMoonShadowEarlyFadeAngle; float mMoonShadowEarlyFadeAngle;
float angle(unsigned int daysPassed, float gameHour) const; float angle(const TimeStamp& gameTime) const;
float moonRiseHour(unsigned int daysPassed) const; float moonRiseHour(unsigned int daysPassed) const;
float rotation(float hours) const; float rotation(float hours) const;
unsigned int phase(unsigned int daysPassed, float gameHour) const; unsigned int phase(const TimeStamp& gameTime) const;
float shadowBlend(float angle) const; float shadowBlend(float angle) const;
float hourlyAlpha(float gameHour) const; float hourlyAlpha(float gameHour) const;
float earlyMoonShadowAlpha(float angle) const; float earlyMoonShadowAlpha(float angle) const;
}; };
///
/// Interface for weather settings /// Interface for weather settings
///
class WeatherManager class WeatherManager
{ {
public: public:
// Have to pass fallback and Store, can't use singleton since World isn't fully constructed yet at the time // Have to pass fallback and Store, can't use singleton since World isn't fully constructed yet at the time
WeatherManager(MWRender::RenderingManager*, MWWorld::Fallback* fallback, MWWorld::ESMStore* store); WeatherManager(MWRender::RenderingManager& rendering,
const MWWorld::Fallback& fallback,
MWWorld::ESMStore& store);
~WeatherManager(); ~WeatherManager();
/** /**
@ -161,8 +186,9 @@ namespace MWWorld
* @param region that should be changed * @param region that should be changed
* @param ID of the weather setting to shift to * @param ID of the weather setting to shift to
*/ */
void changeWeather(const std::string& region, const unsigned int id); void changeWeather(const std::string& regionID, const unsigned int weatherID);
void switchToNextWeather(bool instantly = true); void modRegion(const std::string& regionID, const std::vector<char>& chances);
void playerTeleported();
/** /**
* Per-frame update * Per-frame update
@ -173,8 +199,6 @@ namespace MWWorld
void stopSounds(); void stopSounds();
void setHour(const float hour);
float getWindSpeed() const; float getWindSpeed() const;
/// Are we in an ash or blight storm? /// Are we in an ash or blight storm?
@ -183,11 +207,10 @@ namespace MWWorld
osg::Vec3f getStormDirection() const; osg::Vec3f getStormDirection() const;
void advanceTime(double hours); void advanceTime(double hours);
void advanceTimeByFrame(double hours);
unsigned int getWeatherID() const; unsigned int getWeatherID() const;
void modRegion(const std::string &regionid, const std::vector<char> &chances);
/// @see World::isDark /// @see World::isDark
bool isDark() const; bool isDark() const;
@ -198,73 +221,77 @@ namespace MWWorld
void clear(); void clear();
private: private:
float mHour; MWWorld::ESMStore& mStore;
float mWindSpeed; MWRender::RenderingManager& mRendering;
bool mIsStorm;
osg::Vec3f mStormDirection;
MWBase::SoundPtr mAmbientSound;
std::string mPlayingSoundID;
MWWorld::ESMStore* mStore;
MWRender::RenderingManager* mRendering;
std::map<std::string, Weather> mWeatherSettings;
std::map<std::string, std::string> mRegionOverrides;
std::string mCurrentWeather;
std::string mNextWeather;
std::string mCurrentRegion;
bool mFirstUpdate;
float mRemainingTransitionTime;
float mThunderFlash;
float mThunderChance;
float mThunderChanceNeeded;
double mTimePassed; // time passed since last update
void transition(const float factor);
void setResult(const std::string& weatherType);
void setWeather(const std::string& weatherType, bool instant=false);
std::string nextWeather(const ESM::Region* region) const;
MWRender::WeatherResult mResult;
typedef std::map<std::string,std::vector<char> > RegionModMap;
RegionModMap mRegionMods;
float mRainSpeed;
float mSunriseTime; float mSunriseTime;
float mSunsetTime; float mSunsetTime;
float mSunriseDuration; float mSunriseDuration;
float mSunsetDuration; float mSunsetDuration;
float mWeatherUpdateTime; // Some useful values
float mHoursBetweenWeatherChanges; /* TODO: Use pre-sunrise_time, pre-sunset_time,
float mThunderFrequency; * post-sunrise_time, and post-sunset_time to better
float mThunderThreshold; * describe sunrise/sunset time.
float mThunderSoundDelay; * These values are fallbacks attached to weather.
*/
float mNightStart; float mNightStart;
float mNightEnd; float mNightEnd;
float mDayStart; float mDayStart;
float mDayEnd; float mDayEnd;
float mHoursBetweenWeatherChanges;
float mRainSpeed;
std::vector<Weather> mWeatherSettings;
MoonModel mMasser;
MoonModel mSecunda;
float mThunderFrequency;
float mThunderThreshold;
std::string mThunderSoundID0; std::string mThunderSoundID0;
std::string mThunderSoundID1; std::string mThunderSoundID1;
std::string mThunderSoundID2; std::string mThunderSoundID2;
std::string mThunderSoundID3; std::string mThunderSoundID3;
MoonModel mMasser;
MoonModel mSecunda; float mWindSpeed;
bool mIsStorm;
osg::Vec3f mStormDirection;
float mThunderSoundDelay;
float mThunderFlash;
float mThunderChance;
float mThunderChanceNeeded;
std::string mCurrentRegion;
float mTimePassed;
bool mFastForward;
float mWeatherUpdateTime;
float mTransitionFactor;
int mCurrentWeather;
int mNextWeather;
int mQueuedWeather;
std::map<std::string, RegionWeather> mRegions;
MWRender::WeatherResult mResult;
MWBase::SoundPtr mAmbientSound;
std::string mPlayingSoundID;
void addWeather(const std::string& name, void addWeather(const std::string& name,
const MWWorld::Fallback& fallback, const MWWorld::Fallback& fallback,
const std::string& ambientLoopSoundID = "", const std::string& ambientLoopSoundID = "",
const std::string& particleEffect = ""); const std::string& particleEffect = "");
Weather& findWeather(const std::string& name); void importRegions();
void regionalWeatherChanged(const std::string& regionID, RegionWeather& region);
bool updateWeatherTime();
bool updateWeatherRegion(const std::string& playerRegion);
void updateWeatherTransitions(const float elapsedRealSeconds);
void forceWeather(const int weatherID);
bool inTransition();
void addWeatherTransition(const int weatherID);
void calculateWeatherResult(const float gameHour);
void calculateResult(const int weatherID, const float gameHour);
void calculateTransitionResult(const float factor, const float gameHour);
}; };
} }

@ -193,7 +193,7 @@ namespace MWWorld
mGlobalVariables.fill (mStore); mGlobalVariables.fill (mStore);
mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback,&mStore); mWeatherManager = new MWWorld::WeatherManager(*mRendering, mFallback, mStore);
mWorldScene = new Scene(*mRendering, mPhysics); mWorldScene = new Scene(*mRendering, mPhysics);
} }
@ -212,6 +212,11 @@ namespace MWWorld
MWBase::Environment::get().getWindowManager()->updatePlayer(); MWBase::Environment::get().getWindowManager()->updatePlayer();
// we don't want old weather to persist on a new game
// Note that if reset later, the initial ChangeWeather that the chargen script calls will be lost.
delete mWeatherManager;
mWeatherManager = new MWWorld::WeatherManager(*mRendering, mFallback, mStore);
if (!bypass) if (!bypass)
{ {
// set new game mark // set new game mark
@ -265,11 +270,6 @@ namespace MWWorld
if (!mPhysics->toggleCollisionMode()) if (!mPhysics->toggleCollisionMode())
mPhysics->toggleCollisionMode(); mPhysics->toggleCollisionMode();
// we don't want old weather to persist on a new game
delete mWeatherManager;
mWeatherManager = 0;
mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback,&mStore);
if (!mStartupScript.empty()) if (!mStartupScript.empty())
MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript); MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript);
} }
@ -804,6 +804,25 @@ namespace MWWorld
days + mGlobalVariables["dayspassed"].getInteger()); days + mGlobalVariables["dayspassed"].getInteger());
} }
void World::advanceTimeByFrame (double frametime)
{
double hours = (frametime * getTimeScaleFactor()) / 3600.0;
MWBase::Environment::get().getMechanicsManager()->advanceTime(static_cast<float>(hours * 3600));
mWeatherManager->advanceTimeByFrame (hours);
hours += mGlobalVariables["gamehour"].getFloat();
setHour (hours);
int days = static_cast<int>(hours / 24);
if (days>0)
mGlobalVariables["dayspassed"].setInteger (
days + mGlobalVariables["dayspassed"].getInteger());
}
void World::setHour (double hour) void World::setHour (double hour)
{ {
if (hour<0) if (hour<0)
@ -815,8 +834,6 @@ namespace MWWorld
mGlobalVariables["gamehour"].setFloat(static_cast<float>(hour)); mGlobalVariables["gamehour"].setFloat(static_cast<float>(hour));
mWeatherManager->setHour(static_cast<float>(hour));
if (days>0) if (days>0)
setDay (days + mGlobalVariables["day"].getInteger()); setDay (days + mGlobalVariables["day"].getInteger());
} }
@ -2888,15 +2905,15 @@ namespace MWWorld
MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false); MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false);
action.execute(ptr); action.execute(ptr);
} }
void World::updateWeather(float duration, bool paused) void World::updateWeather(float duration, bool paused)
{ {
if (mPlayer->wasTeleported()) if (mPlayer->wasTeleported())
{ {
mPlayer->setTeleported(false); mPlayer->setTeleported(false);
mWeatherManager->switchToNextWeather(true); mWeatherManager->playerTeleported();
} }
mWeatherManager->update(duration, paused); mWeatherManager->update(duration, paused);
} }

@ -277,6 +277,9 @@ namespace MWWorld
virtual void advanceTime (double hours); virtual void advanceTime (double hours);
///< Advance in-game time. ///< Advance in-game time.
virtual void advanceTimeByFrame (double frametime);
///< Advance in-game time by the duration of a frame.
virtual void setHour (double hour); virtual void setHour (double hour);
///< Set in-game time hour. ///< Set in-game time hour.

@ -1,59 +1,72 @@
#include "weatherstate.hpp" #include "weatherstate.hpp"
#include "esmreader.hpp" #include "esmreader.hpp"
#include "esmwriter.hpp" #include "esmwriter.hpp"
namespace namespace
{ {
const char* hourRecord = "HOUR"; const char* currentRegionRecord = "CREG";
const char* windSpeedRecord = "WNSP"; const char* timePassedRecord = "TMPS";
const char* currentWeatherRecord = "CWTH"; const char* fastForwardRecord = "FAST";
const char* nextWeatherRecord = "NWTH"; const char* weatherUpdateTimeRecord = "WUPD";
const char* currentRegionRecord = "CREG"; const char* transitionFactorRecord = "TRFC";
const char* firstUpdateRecord = "FUPD"; const char* currentWeatherRecord = "CWTH";
const char* remainingTransitionTimeRecord = "RTTM"; const char* nextWeatherRecord = "NWTH";
const char* timePassedRecord = "TMPS"; const char* queuedWeatherRecord = "QWTH";
} const char* regionNameRecord = "RGNN";
const char* regionWeatherRecord = "RGNW";
namespace ESM const char* regionChanceRecord = "RGNC";
{ }
void WeatherState::load(ESMReader& esm)
{ namespace ESM
// store values locally so that a failed load can't leave the state half set {
float newHour = 0.0; void WeatherState::load(ESMReader& esm)
esm.getHNT(newHour, hourRecord); {
float newWindSpeed = 0.0; mCurrentRegion = esm.getHNString(currentRegionRecord);
esm.getHNT(newWindSpeed, windSpeedRecord); esm.getHNT(mTimePassed, timePassedRecord);
std::string newCurrentWeather = esm.getHNString(currentWeatherRecord); esm.getHNT(mFastForward, fastForwardRecord);
std::string newNextWeather = esm.getHNString(nextWeatherRecord); esm.getHNT(mWeatherUpdateTime, weatherUpdateTimeRecord);
std::string newCurrentRegion = esm.getHNString(currentRegionRecord); esm.getHNT(mTransitionFactor, transitionFactorRecord);
bool newFirstUpdate = false; esm.getHNT(mCurrentWeather, currentWeatherRecord);
esm.getHNT(newFirstUpdate, firstUpdateRecord); esm.getHNT(mNextWeather, nextWeatherRecord);
float newRemainingTransitionTime = 0.0; esm.getHNT(mQueuedWeather, queuedWeatherRecord);
esm.getHNT(newRemainingTransitionTime, remainingTransitionTimeRecord);
double newTimePassed = 0.0; while(esm.peekNextSub(regionNameRecord))
esm.getHNT(newTimePassed, timePassedRecord); {
std::string regionID = esm.getHNString(regionNameRecord);
// swap values now that it is safe to do so RegionWeatherState region;
mHour = newHour; esm.getHNT(region.mWeather, regionWeatherRecord);
mWindSpeed = newWindSpeed; while(esm.peekNextSub(regionChanceRecord))
mCurrentWeather.swap(newCurrentWeather); {
mNextWeather.swap(newNextWeather); char chance;
mCurrentRegion.swap(newCurrentRegion); esm.getHNT(chance, regionChanceRecord);
mFirstUpdate = newFirstUpdate; region.mChances.push_back(chance);
mRemainingTransitionTime = newRemainingTransitionTime; }
mTimePassed = newTimePassed;
} mRegions.insert(std::make_pair(regionID, region));
}
void WeatherState::save(ESMWriter& esm) const }
{
esm.writeHNT(hourRecord, mHour); void WeatherState::save(ESMWriter& esm) const
esm.writeHNT(windSpeedRecord, mWindSpeed); {
esm.writeHNCString(currentWeatherRecord, mCurrentWeather.c_str()); esm.writeHNCString(currentRegionRecord, mCurrentRegion.c_str());
esm.writeHNCString(nextWeatherRecord, mNextWeather.c_str()); esm.writeHNT(timePassedRecord, mTimePassed);
esm.writeHNCString(currentRegionRecord, mCurrentRegion.c_str()); esm.writeHNT(fastForwardRecord, mFastForward);
esm.writeHNT(firstUpdateRecord, mFirstUpdate); esm.writeHNT(weatherUpdateTimeRecord, mWeatherUpdateTime);
esm.writeHNT(remainingTransitionTimeRecord, mRemainingTransitionTime); esm.writeHNT(transitionFactorRecord, mTransitionFactor);
esm.writeHNT(timePassedRecord, mTimePassed); esm.writeHNT(currentWeatherRecord, mCurrentWeather);
} esm.writeHNT(nextWeatherRecord, mNextWeather);
} esm.writeHNT(queuedWeatherRecord, mQueuedWeather);
std::map<std::string, RegionWeatherState>::const_iterator it = mRegions.begin();
for(; it != mRegions.end(); ++it)
{
esm.writeHNCString(regionNameRecord, it->first.c_str());
esm.writeHNT(regionWeatherRecord, it->second.mWeather);
for(size_t i = 0; i < it->second.mChances.size(); ++i)
{
esm.writeHNT(regionChanceRecord, it->second.mChances[i]);
}
}
}
}

@ -1,27 +1,36 @@
#ifndef OPENMW_ESM_WEATHERSTATE_H #ifndef OPENMW_ESM_WEATHERSTATE_H
#define OPENMW_ESM_WEATHERSTATE_H #define OPENMW_ESM_WEATHERSTATE_H
#include <string> #include <map>
#include <string>
namespace ESM #include <vector>
{
class ESMReader; namespace ESM
class ESMWriter; {
class ESMReader;
struct WeatherState class ESMWriter;
{
float mHour; struct RegionWeatherState
float mWindSpeed; {
std::string mCurrentWeather; int mWeather;
std::string mNextWeather; std::vector<char> mChances;
std::string mCurrentRegion; };
bool mFirstUpdate;
float mRemainingTransitionTime; struct WeatherState
double mTimePassed; {
std::string mCurrentRegion;
void load(ESMReader& esm); float mTimePassed;
void save(ESMWriter& esm) const; bool mFastForward;
}; float mWeatherUpdateTime;
} float mTransitionFactor;
int mCurrentWeather;
#endif int mNextWeather;
int mQueuedWeather;
std::map<std::string, RegionWeatherState> mRegions;
void load(ESMReader& esm);
void save(ESMWriter& esm) const;
};
}
#endif

Loading…
Cancel
Save