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:
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)
{
mGame.load(esm);
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)
{
if (!mHasGame)
return;
esm.startRecord(ESM::REC_WTHR);
ESM::WeatherState weather;
weather.mCurrentWeather = toString(mGame.mGMDT.mCurrentWeather);
weather.mNextWeather = toString(mGame.mGMDT.mNextWeather);
weather.mRemainingTransitionTime = mGame.mGMDT.mWeatherTransition/100.f*(0.015f*24*3600);
weather.mHour = mContext->mHour;
weather.mWindSpeed = 0.f;
weather.mTimePassed = 0.0;
weather.mFirstUpdate = false;
weather.mTimePassed = 0.0f;
weather.mFastForward = false;
weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour;
weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f);
weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather);
weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather);
weather.mQueuedWeather = -1;
// TODO: Determine how ModRegion modifiers are saved in Morrowind.
weather.save(esm);
esm.endRecord(ESM::REC_WTHR);
}

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

@ -188,6 +188,9 @@ namespace MWBase
virtual void advanceTime (double hours) = 0;
///< 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;
///< Set in-game time hour.

File diff suppressed because it is too large Load Diff

@ -14,6 +14,7 @@
namespace ESM
{
struct Region;
struct RegionWeatherState;
class ESMWriter;
class ESMReader;
}
@ -31,6 +32,7 @@ namespace Loading
namespace MWWorld
{
class Fallback;
class TimeStamp;
/// Defines a single weather setting (according to INI)
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
// is broken in the vanilla game and was disabled.
float transitionSeconds() const;
float transitionDelta() const;
float cloudBlendFactor(float transitionRatio) const;
private:
@ -118,12 +120,35 @@ namespace MWWorld
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
{
public:
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:
float mFadeInStart;
@ -137,23 +162,23 @@ namespace MWWorld
float mFadeEndAngle;
float mMoonShadowEarlyFadeAngle;
float angle(unsigned int daysPassed, float gameHour) const;
float angle(const TimeStamp& gameTime) const;
float moonRiseHour(unsigned int daysPassed) 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 hourlyAlpha(float gameHour) const;
float earlyMoonShadowAlpha(float angle) const;
};
///
/// Interface for weather settings
///
class WeatherManager
{
public:
// 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();
/**
@ -161,8 +186,9 @@ namespace MWWorld
* @param region that should be changed
* @param ID of the weather setting to shift to
*/
void changeWeather(const std::string& region, const unsigned int id);
void switchToNextWeather(bool instantly = true);
void changeWeather(const std::string& regionID, const unsigned int weatherID);
void modRegion(const std::string& regionID, const std::vector<char>& chances);
void playerTeleported();
/**
* Per-frame update
@ -173,8 +199,6 @@ namespace MWWorld
void stopSounds();
void setHour(const float hour);
float getWindSpeed() const;
/// Are we in an ash or blight storm?
@ -183,11 +207,10 @@ namespace MWWorld
osg::Vec3f getStormDirection() const;
void advanceTime(double hours);
void advanceTimeByFrame(double hours);
unsigned int getWeatherID() const;
void modRegion(const std::string &regionid, const std::vector<char> &chances);
/// @see World::isDark
bool isDark() const;
@ -198,73 +221,77 @@ namespace MWWorld
void clear();
private:
float mHour;
float mWindSpeed;
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;
MWWorld::ESMStore& mStore;
MWRender::RenderingManager& mRendering;
float mSunriseTime;
float mSunsetTime;
float mSunriseDuration;
float mSunsetDuration;
float mWeatherUpdateTime;
float mHoursBetweenWeatherChanges;
float mThunderFrequency;
float mThunderThreshold;
float mThunderSoundDelay;
// Some useful values
/* TODO: Use pre-sunrise_time, pre-sunset_time,
* post-sunrise_time, and post-sunset_time to better
* describe sunrise/sunset time.
* These values are fallbacks attached to weather.
*/
float mNightStart;
float mNightEnd;
float mDayStart;
float mDayEnd;
float mHoursBetweenWeatherChanges;
float mRainSpeed;
std::vector<Weather> mWeatherSettings;
MoonModel mMasser;
MoonModel mSecunda;
float mThunderFrequency;
float mThunderThreshold;
std::string mThunderSoundID0;
std::string mThunderSoundID1;
std::string mThunderSoundID2;
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,
const MWWorld::Fallback& fallback,
const std::string& ambientLoopSoundID = "",
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);
mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback,&mStore);
mWeatherManager = new MWWorld::WeatherManager(*mRendering, mFallback, mStore);
mWorldScene = new Scene(*mRendering, mPhysics);
}
@ -212,6 +212,11 @@ namespace MWWorld
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)
{
// set new game mark
@ -265,11 +270,6 @@ namespace MWWorld
if (!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())
MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript);
}
@ -804,6 +804,25 @@ namespace MWWorld
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)
{
if (hour<0)
@ -815,8 +834,6 @@ namespace MWWorld
mGlobalVariables["gamehour"].setFloat(static_cast<float>(hour));
mWeatherManager->setHour(static_cast<float>(hour));
if (days>0)
setDay (days + mGlobalVariables["day"].getInteger());
}
@ -2888,15 +2905,15 @@ namespace MWWorld
MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false);
action.execute(ptr);
}
void World::updateWeather(float duration, bool paused)
{
if (mPlayer->wasTeleported())
{
mPlayer->setTeleported(false);
mWeatherManager->switchToNextWeather(true);
mWeatherManager->playerTeleported();
}
mWeatherManager->update(duration, paused);
}

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

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

Loading…
Cancel
Save