[General] Reimplement weather synchronization to allow soft transitions

Although weather sync was added by Koncord to the rewrite in fd721143e2 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.
fix/skillcap
David Cernat 6 years ago
parent b6db570d9c
commit 892d71ce71

@ -34,6 +34,31 @@ unsigned int WorldstateFunctions::GetMapChangesSize() noexcept
return readWorldstate->mapTiles.size();
}
const char *WorldstateFunctions::GetWeatherRegion() noexcept
{
return readWorldstate->weather.region.c_str();
}
int WorldstateFunctions::GetWeatherCurrent() noexcept
{
return readWorldstate->weather.currentWeather;
}
int WorldstateFunctions::GetWeatherNext() noexcept
{
return readWorldstate->weather.nextWeather;
}
int WorldstateFunctions::GetWeatherQueued() noexcept
{
return readWorldstate->weather.queuedWeather;
}
double WorldstateFunctions::GetWeatherTransitionFactor() noexcept
{
return readWorldstate->weather.transitionFactor;
}
int WorldstateFunctions::GetMapTileCellX(unsigned int index) noexcept
{
return readWorldstate->mapTiles.at(index).x;
@ -44,6 +69,41 @@ int WorldstateFunctions::GetMapTileCellY(unsigned int index) noexcept
return readWorldstate->mapTiles.at(index).y;
}
void WorldstateFunctions::SetAuthorityRegion(const char* authorityRegion) noexcept
{
writeWorldstate.authorityRegion = authorityRegion;
}
void WorldstateFunctions::SetWeatherRegion(const char* region) noexcept
{
writeWorldstate.weather.region = region;
}
void WorldstateFunctions::SetWeatherForceState(bool forceState) noexcept
{
writeWorldstate.forceWeather = forceState;
}
void WorldstateFunctions::SetWeatherCurrent(int currentWeather) noexcept
{
writeWorldstate.weather.currentWeather = currentWeather;
}
void WorldstateFunctions::SetWeatherNext(int nextWeather) noexcept
{
writeWorldstate.weather.nextWeather = nextWeather;
}
void WorldstateFunctions::SetWeatherQueued(int queuedWeather) noexcept
{
writeWorldstate.weather.queuedWeather = queuedWeather;
}
void WorldstateFunctions::SetWeatherTransitionFactor(double transitionFactor) noexcept
{
writeWorldstate.weather.transitionFactor = transitionFactor;
}
void WorldstateFunctions::SetHour(double hour) noexcept
{
writeWorldstate.hour = hour;
@ -169,6 +229,22 @@ void WorldstateFunctions::SendWorldTime(unsigned short pid, bool sendToOtherPlay
packet->Send(true);
}
void WorldstateFunctions::SendWorldWeather(unsigned short pid, bool sendToOtherPlayers, bool skipAttachedPlayer) noexcept
{
Player *player;
GET_PLAYER(pid, player, );
writeWorldstate.guid = player->guid;
mwmp::WorldstatePacket *packet = mwmp::Networking::get().getWorldstatePacketController()->GetPacket(ID_WORLD_WEATHER);
packet->setWorldstate(&writeWorldstate);
if (!skipAttachedPlayer)
packet->Send(false);
if (sendToOtherPlayers)
packet->Send(true);
}
void WorldstateFunctions::SendWorldCollisionOverride(unsigned short pid, bool sendToOtherPlayers, bool skipAttachedPlayer) noexcept
{
Player *player;
@ -185,6 +261,22 @@ void WorldstateFunctions::SendWorldCollisionOverride(unsigned short pid, bool se
packet->Send(true);
}
void WorldstateFunctions::SendWorldRegionAuthority(unsigned short pid) noexcept
{
Player *player;
GET_PLAYER(pid, player, );
writeWorldstate.guid = player->guid;
mwmp::WorldstatePacket *packet = mwmp::Networking::get().getWorldstatePacketController()->GetPacket(ID_WORLD_REGION_AUTHORITY);
packet->setWorldstate(&writeWorldstate);
packet->Send(false);
// This packet should always be sent to all other players
packet->Send(true);
}
// All methods below are deprecated versions of methods from above

@ -12,9 +12,24 @@
\
{"GetMapChangesSize", WorldstateFunctions::GetMapChangesSize},\
\
{"GetWeatherRegion", WorldstateFunctions::GetWeatherRegion},\
{"GetWeatherCurrent", WorldstateFunctions::GetWeatherCurrent},\
{"GetWeatherNext", WorldstateFunctions::GetWeatherNext},\
{"GetWeatherQueued", WorldstateFunctions::GetWeatherQueued},\
{"GetWeatherTransitionFactor", WorldstateFunctions::GetWeatherTransitionFactor},\
\
{"GetMapTileCellX", WorldstateFunctions::GetMapTileCellX},\
{"GetMapTileCellY", WorldstateFunctions::GetMapTileCellY},\
\
{"SetAuthorityRegion", WorldstateFunctions::SetAuthorityRegion},\
\
{"SetWeatherRegion", WorldstateFunctions::SetWeatherRegion},\
{"SetWeatherForceState", WorldstateFunctions::SetWeatherForceState},\
{"SetWeatherCurrent", WorldstateFunctions::SetWeatherCurrent},\
{"SetWeatherNext", WorldstateFunctions::SetWeatherNext},\
{"SetWeatherQueued", WorldstateFunctions::SetWeatherQueued},\
{"SetWeatherTransitionFactor", WorldstateFunctions::SetWeatherTransitionFactor},\
\
{"SetHour", WorldstateFunctions::SetHour},\
{"SetDay", WorldstateFunctions::SetDay},\
{"SetMonth", WorldstateFunctions::SetMonth},\
@ -35,7 +50,9 @@
\
{"SendWorldMap", WorldstateFunctions::SendWorldMap},\
{"SendWorldTime", WorldstateFunctions::SendWorldTime},\
{"SendWorldWeather", WorldstateFunctions::SendWorldWeather},\
{"SendWorldCollisionOverride", WorldstateFunctions::SendWorldCollisionOverride},\
{"SendWorldRegionAuthority", WorldstateFunctions::SendWorldRegionAuthority},\
\
{"ReadLastWorldstate", WorldstateFunctions::ReadLastWorldstate},\
{"CopyLastWorldstateToStore", WorldstateFunctions::CopyLastWorldstateToStore}
@ -76,9 +93,44 @@ public:
*/
static unsigned int GetMapChangesSize() noexcept;
/**
* \brief Get the weather region in the read worldstate.
*
* \return The weather region.
*/
static const char *GetWeatherRegion() noexcept;
/**
* \brief Get the current weather in the read worldstate.
*
* \return The current weather.
*/
static int GetWeatherCurrent() noexcept;
/**
* \brief Get the next weather in the read worldstate.
*
* \return The next weather.
*/
static int GetWeatherNext() noexcept;
/**
* \brief Get the queued weather in the read worldstate.
*
* \return The queued weather.
*/
static int GetWeatherQueued() noexcept;
/**
* \brief Get the transition factor of the weather in the read worldstate.
*
* \return The transition factor of the weather.
*/
static double GetWeatherTransitionFactor() noexcept;
/**
* \brief Get the X coordinate of the cell corresponding to the map tile at a certain index in
* the read worldstate's map changes.
* the read worldstate's map tiles.
*
* \param i The index of the map tile.
* \return The X coordinate of the cell.
@ -87,13 +139,71 @@ public:
/**
* \brief Get the Y coordinate of the cell corresponding to the map tile at a certain index in
* the read worldstate's map changes.
* the read worldstate's map tiles.
*
* \param i The index of the map tile.
* \return The Y coordinate of the cell.
*/
static int GetMapTileCellY(unsigned int index) noexcept;
/**
* \brief Set the region affected by the next WorldRegionAuthority packet sent.
*
* \param region The region.
* \return void
*/
static void SetAuthorityRegion(const char* authorityRegion) noexcept;
/**
* \brief Set the weather region in the write-only worldstate stored on the server.
*
* \param region The region.
* \return void
*/
static void SetWeatherRegion(const char* region) noexcept;
/**
* \brief Set the weather forcing state in the write-only worldstate stored on the server.
*
* Players who receive a packet with forced weather will switch to that weather immediately.
*
* \param forceState The weather forcing state.
* \return void
*/
static void SetWeatherForceState(bool forceState) noexcept;
/**
* \brief Set the current weather in the write-only worldstate stored on the server.
*
* \param currentWeather The current weather.
* \return void
*/
static void SetWeatherCurrent(int currentWeather) noexcept;
/**
* \brief Set the next weather in the write-only worldstate stored on the server.
*
* \param nextWeather The next weather.
* \return void
*/
static void SetWeatherNext(int nextWeather) noexcept;
/**
* \brief Set the queued weather in the write-only worldstate stored on the server.
*
* \param queuedWeather The queued weather.
* \return void
*/
static void SetWeatherQueued(int queuedWeather) noexcept;
/**
* \brief Set the transition factor for the weather in the write-only worldstate stored on the server.
*
* \param transitionFactor The transition factor.
* \return void
*/
static void SetWeatherTransitionFactor(double transitionFactor) noexcept;
/**
* \brief Set the world's hour in the write-only worldstate stored on the server.
*
@ -217,6 +327,17 @@ public:
*/
static void LoadMapTileImageFile(int cellX, int cellY, const char* filePath) noexcept;
/**
* \brief Send a WorldRegionAuthority packet establishing a certain player as the only one who
* should process certain region-specific events (such as weather changes).
*
* It is always sent to all players.
*
* \param pid The player ID attached to the packet.
* \return void
*/
static void SendWorldRegionAuthority(unsigned short pid) noexcept;
/**
* \brief Send a WorldMap packet with the current set of map changes in the write-only
* worldstate.
@ -241,6 +362,18 @@ public:
*/
static void SendWorldTime(unsigned short pid, bool sendToOtherPlayers, bool skipAttachedPlayer) noexcept;
/**
* \brief Send a WorldWeather packet with the current weather in the write-only worldstate.
*
* \param pid The player ID attached to the packet.
* \param sendToOtherPlayers Whether this packet should be sent to players other than the
* player attached to the packet (false by default).
* \param skipAttachedPlayer Whether the packet should skip being sent to the player attached
* to the packet (false by default).
* \return void
*/
static void SendWorldWeather(unsigned short pid, bool sendToOtherPlayers, bool skipAttachedPlayer) noexcept;
/**
* \brief Send a WorldCollisionOverride packet with the current collision overrides in
* the write-only worldstate.

@ -201,6 +201,7 @@ public:
{"OnGUIAction", Function<void, unsigned short, int, const char*>()},
{"OnWorldKillCount", Function<void, unsigned short>()},
{"OnWorldMap", Function<void, unsigned short>()},
{"OnWorldWeather", Function<void, unsigned short>() },
{"OnMpNumIncrement", Function<void, int>()},
{"OnRequestPluginList", Function<const char *, unsigned int, unsigned int>()}
};

@ -1,7 +1,7 @@
#ifndef OPENMW_PROCESSORWORLDWEATHER_HPP
#define OPENMW_PROCESSORWORLDWEATHER_HPP
#include "../PlayerProcessor.hpp"
#include "../WorldstateProcessor.hpp"
namespace mwmp
{
@ -15,7 +15,9 @@ namespace mwmp
void Do(WorldstatePacket &packet, Player &player, BaseWorldstate &worldstate) override
{
// Placeholder to be filled in later
DEBUG_PRINTF(strPacketID.c_str());
Script::Call<Script::CallbackIdentity("OnWorldWeather")>(player.getId());
}
};
}

@ -259,6 +259,51 @@ namespace MWBase
virtual void changeWeather(const std::string& region, const unsigned int id) = 0;
/*
Start of tes3mp addition
Make it possible to set a specific weather state for a region from elsewhere
in the code
*/
virtual void setRegionWeather(const std::string& region, const unsigned int currentWeather, const unsigned int nextWeather,
const unsigned int queuedWeather, const float transitionFactor, bool force) = 0;
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to check whether the local WeatherManager has the
ability to create weather changes
*/
virtual bool getWeatherCreationState() = 0;
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to enable and disable the local WeatherManager's ability
to create weather changes
*/
virtual void setWeatherCreationState(bool state) = 0;
/*
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
*/
virtual void sendWeather() = 0;
/*
End of tes3mp addition
*/
virtual int getCurrentWeather() const = 0;
virtual int getMasserPhase() const = 0;
@ -290,6 +335,17 @@ namespace MWBase
virtual MWWorld::Ptr getFacedObject() = 0;
///< Return pointer to the object the player is looking at, if it is within activation range
/*
Start of tes3mp addition
This has been declared here so it can be accessed from places
other than MWWorld::World
*/
virtual void updateWeather(float duration, bool paused = false) = 0;
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition

@ -61,6 +61,8 @@ DedicatedPlayer::DedicatedPlayer(RakNet::RakNetGUID guid) : BasePlayer(guid)
npc = *world->getPlayerPtr().get<ESM::NPC>()->mBase;
npc.mId = "Dedicated Player";
previousRace = npc.mRace;
hasFinishedInitialTeleportation = false;
}
DedicatedPlayer::~DedicatedPlayer()
{
@ -381,6 +383,19 @@ void DedicatedPlayer::setCell()
// NPC data in that cell
if (Main::get().getCellController()->hasLocalAuthority(cell))
Main::get().getCellController()->getCell(cell)->updateLocal(true);
// If this player is a new player or is now in a region that we are the weather authority over,
// or is a new player, we should send our latest weather data to the server
if (world->getWeatherCreationState())
{
if (!hasFinishedInitialTeleportation || Misc::StringUtils::ciEqual(getPtr().getCell()->getCell()->mRegion,
world->getPlayerPtr().getCell()->getCell()->mRegion))
{
world->sendWeather();
}
}
hasFinishedInitialTeleportation = true;
}
void DedicatedPlayer::updateMarker()

@ -78,6 +78,8 @@ namespace mwmp
bool previousDisplayCreatureName;
std::string creatureRecordId;
bool hasFinishedInitialTeleportation;
};
}
#endif //OPENMW_DEDICATEDPLAYER_HPP

@ -4,6 +4,7 @@
#include "../mwgui/windowmanagerimp.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/worldimp.hpp"
#include "Worldstate.hpp"
@ -50,6 +51,40 @@ void Worldstate::markExploredMapTile(int cellX, int cellY)
exploredMapTiles.push_back(exploredTile);
}
void Worldstate::setMapExplored()
{
for (const auto &mapTile : mapTiles)
{
const MWWorld::CellStore *cellStore = MWBase::Environment::get().getWorld()->getExterior(mapTile.x, mapTile.y);
if (!cellStore->getCell()->mName.empty())
MWBase::Environment::get().getWindowManager()->addVisitedLocation(cellStore->getCell()->mName, mapTile.x, mapTile.y);
MWBase::Environment::get().getWindowManager()->setGlobalMapImage(mapTile.x, mapTile.y, mapTile.imageData);
// Keep this tile marked as explored so we don't send any more packets for it
markExploredMapTile(mapTile.x, mapTile.y);
}
}
void Worldstate::setWeather()
{
MWBase::World *world = MWBase::Environment::get().getWorld();
// There's a chance we've been sent the weather for a region right after a teleportation
// that hasn't been registered in the WeatherManager yet, meaning the WeatherManager
// doesn't have the correct new region set for us, so make sure we update it
world->updateWeather(0);
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Setting weather for region: %s, currentWeather: %i, "
"nextWeather: %i, queuedWeather: %i, transitionFactor: %f, forceWeather is %s",
weather.region.c_str(), weather.currentWeather, weather.nextWeather,
weather.queuedWeather, weather.transitionFactor, forceWeather ? "true" : "false");
world->setRegionWeather(weather.region.c_str(), weather.currentWeather, weather.nextWeather,
weather.queuedWeather, weather.transitionFactor, forceWeather);
}
void Worldstate::sendMapExplored(int cellX, int cellY, const std::vector<char>& imageData)
{
mapTiles.clear();
@ -59,7 +94,7 @@ void Worldstate::sendMapExplored(int cellX, int cellY, const std::vector<char>&
mapTile.y = cellY;
mapTile.imageData = imageData;
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_PLAYER_MAP with x: %i, y: %i", cellX, cellY);
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_MAP with x: %i, y: %i", cellX, cellY);
mapTiles.push_back(mapTile);
@ -67,18 +102,18 @@ void Worldstate::sendMapExplored(int cellX, int cellY, const std::vector<char>&
getNetworking()->getWorldstatePacket(ID_WORLD_MAP)->Send();
}
void Worldstate::setMapExplored()
void Worldstate::sendWeather(std::string region, int currentWeather, int nextWeather, int queuedWeather, float transitionFactor)
{
for (const auto &mapTile : mapTiles)
{
const MWWorld::CellStore *cellStore = MWBase::Environment::get().getWorld()->getExterior(mapTile.x, mapTile.y);
if (!cellStore->getCell()->mName.empty())
MWBase::Environment::get().getWindowManager()->addVisitedLocation(cellStore->getCell()->mName, mapTile.x, mapTile.y);
MWBase::Environment::get().getWindowManager()->setGlobalMapImage(mapTile.x, mapTile.y, mapTile.imageData);
// Keep this tile marked as explored so we don't send any more packets for it
markExploredMapTile(mapTile.x, mapTile.y);
}
weather.region = region;
weather.currentWeather = currentWeather;
weather.nextWeather = nextWeather;
weather.queuedWeather = queuedWeather;
weather.transitionFactor = transitionFactor;
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Sending ID_PLAYER_WEATHER with region: %s, currentWeather: %i, "
"nextWeather: %i, queuedWeather, %i, transitionFactor: %f",
region.c_str(), currentWeather, nextWeather, queuedWeather, transitionFactor);
getNetworking()->getWorldstatePacket(ID_WORLD_WEATHER)->setWorldstate(this);
getNetworking()->getWorldstatePacket(ID_WORLD_WEATHER)->Send();
}

@ -18,8 +18,12 @@ namespace mwmp
void markExploredMapTile(int cellX, int cellY);
void setMapExplored();
void setWeather();
void sendMapExplored(int cellX, int cellY, const std::vector<char>& imageData);
void sendWeather(std::string region, int currentWeather, int nextWeather, int queuedWeather, float transitionFactor);
mwmp::Weather localWeather;
private:

@ -1,6 +1,8 @@
#ifndef OPENMW_PROCESSORWORLDREGIONAUTHORITY_HPP
#define OPENMW_PROCESSORWORLDREGIONAUTHORITY_HPP
#include <apps/openmw/mwbase/world.hpp>
#include "../PlayerProcessor.hpp"
namespace mwmp
@ -15,14 +17,31 @@ namespace mwmp
virtual void Do(WorldstatePacket &packet, Worldstate &worldstate)
{
if (isLocal())
{
LOG_APPEND(Log::LOG_INFO, "- The new region authority for %s is me");
}
else
MWBase::World *world = MWBase::Environment::get().getWorld();
if (Misc::StringUtils::ciEqual(worldstate.authorityRegion, world->getPlayerPtr().getCell()->getCell()->mRegion))
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Received %s about %s", strPacketID.c_str(), worldstate.authorityRegion.c_str());
if (isLocal())
{
LOG_APPEND(Log::LOG_INFO, "- The new region authority is me");
// There's a chance we've been made the region authority right after a teleportation that hasn't
// been registered in the WeatherManager yet, so make sure we update it
world->updateWeather(0);
world->setWeatherCreationState(true);
world->sendWeather();
}
else
{
BasePlayer *player = PlayerList::getPlayer(guid);
if (player != 0)
LOG_APPEND(Log::LOG_INFO, "- The new region authority is %s", player->npc.mName.c_str());
world->setWeatherCreationState(false);
}
}
}
};

@ -1,5 +1,5 @@
#ifndef OPENMW_PROCESSORGAMEWEATHER_HPP
#define OPENMW_PROCESSORGAMEWEATHER_HPP
#ifndef OPENMW_PROCESSORWORLDWEATHER_HPP
#define OPENMW_PROCESSORWORLDWEATHER_HPP
#include "../PlayerProcessor.hpp"
@ -15,9 +15,9 @@ namespace mwmp
virtual void Do(WorldstatePacket &packet, Worldstate &worldstate)
{
// Placeholder to be filled in later
worldstate.setWeather();
}
};
}
#endif //OPENMW_PROCESSORGAMEWEATHER_HPP
#endif //OPENMW_PROCESSORWORLDWEATHER_HPP

@ -7,6 +7,19 @@
#include <components/esm/savedgame.hpp>
#include <components/esm/weatherstate.hpp>
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include <components/openmw-mp/Log.hpp>
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/Worldstate.hpp"
/*
End of tes3mp addition
*/
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
@ -657,12 +670,72 @@ void WeatherManager::playerTeleported(const std::string& playerRegion, bool isEx
std::map<std::string, RegionWeather>::iterator it = mRegions.find(playerRegion);
if(it != mRegions.end() && playerRegion != mCurrentRegion)
{
/*
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
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 unsigned int currentWeather, const unsigned int nextWeather,
const unsigned int queuedWeather, const float transitionFactor, bool force)
{
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
*/
void WeatherManager::update(float duration, bool paused, const TimeStamp& time, bool isExterior)
{
MWWorld::ConstPtr player = MWMechanics::getPlayer();
@ -906,6 +979,50 @@ void WeatherManager::clear()
importRegions();
}
/*
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,
const Fallback::Map& fallback,
float dlFactor, float dlOffset,
@ -943,7 +1060,19 @@ inline void WeatherManager::regionalWeatherChanged(const std::string& regionID,
inline bool WeatherManager::updateWeatherTime()
{
/*
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;
mTimePassed = 0.0f;
if(mWeatherUpdateTime <= 0.0f)
{
@ -968,6 +1097,17 @@ inline bool WeatherManager::updateWeatherRegion(const std::string& playerRegion)
{
mCurrentRegion = playerRegion;
/*
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;
}
@ -976,6 +1116,16 @@ inline bool WeatherManager::updateWeatherRegion(const std::string& playerRegion)
inline void WeatherManager::updateWeatherTransitions(const float elapsedRealSeconds)
{
/*
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())
@ -999,10 +1149,31 @@ inline void WeatherManager::updateWeatherTransitions(const float elapsedRealSeco
{
mTransitionFactor = 0.0f;
}
/*
Start of tes3mp addition
The weather is changing, so decide to send an ID_WORLD_WEATHER packet
*/
shouldSendPacket = true;
/*
End of tes3mp addition
*/
}
}
else
{
/*
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;
@ -1016,6 +1187,20 @@ inline void WeatherManager::updateWeatherTransitions(const float elapsedRealSeco
mQueuedWeather = invalidWeatherID;
mFastForward = false;
}
/*
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)
@ -1024,6 +1209,20 @@ inline void WeatherManager::forceWeather(const int weatherID)
mCurrentWeather = weatherID;
mNextWeather = invalidWeatherID;
mQueuedWeather = invalidWeatherID;
/*
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()
@ -1047,6 +1246,20 @@ inline void WeatherManager::addWeatherTransition(const int weatherID)
{
mQueuedWeather = weatherID;
}
/*
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,

@ -267,6 +267,18 @@ namespace MWWorld
void modRegion(const std::string& regionID, const std::vector<char>& chances);
void playerTeleported(const std::string& playerRegion, bool isExterior);
/*
Start of tes3mp addition
Make it possible to set a specific weather state for a region from elsewhere
in the code
*/
void setRegionWeather(const std::string& region, const unsigned int currentWeather, const unsigned int nextWeather,
const unsigned int queuedWeather, const float transitionFactor, bool force);
/*
End of tes3mp addition
*/
/**
* Per-frame update
* @param duration
@ -295,6 +307,39 @@ namespace MWWorld
void clear();
/*
Start of tes3mp addition
Make it possible to check whether the local WeatherManager has the
ability to create weather changes
*/
bool getWeatherCreationState();
/*
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 setWeatherCreationState(bool 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 sendWeather();
/*
End of tes3mp addition
*/
private:
MWWorld::ESMStore& mStore;
MWRender::RenderingManager& mRendering;
@ -338,6 +383,17 @@ namespace MWWorld
MWBase::Sound *mAmbientSound;
std::string mPlayingSoundID;
/*
Start of tes3mp addition
Track whether the local WeatherManager should be creating any weather changes
by itself; when set to false, only weather changes sent by the server are used
*/
bool mWeatherCreationState = false;
/*
End of tes3mp addition
*/
void addWeather(const std::string& name,
const Fallback::Map& fallback,
float dlFactor, float dlOffset,

@ -2031,6 +2031,63 @@ namespace MWWorld
mWeatherManager->changeWeather(region, id);
}
/*
Start of tes3mp addition
Make it possible to set a specific weather state for a region from elsewhere
in the code
*/
void World::setRegionWeather(const std::string& region, const unsigned int currentWeather, const unsigned int nextWeather,
const unsigned int queuedWeather, const float transitionFactor, bool force)
{
mWeatherManager->setRegionWeather(region, currentWeather, nextWeather, queuedWeather, transitionFactor, force);
}
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to check whether the local WeatherManager has the
ability to create weather changes
*/
bool World::getWeatherCreationState()
{
return mWeatherManager->getWeatherCreationState();
}
/*
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 World::setWeatherCreationState(bool state)
{
mWeatherManager->setWeatherCreationState(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 World::sendWeather()
{
mWeatherManager->sendWeather();
}
/*
End of tes3mp addition
*/
void World::modRegion(const std::string &regionid, const std::vector<char> &chances)
{
mWeatherManager->modRegion(regionid, chances);

@ -373,6 +373,51 @@ namespace MWWorld
void changeWeather (const std::string& region, const unsigned int id) override;
/*
Start of tes3mp addition
Make it possible to set a specific weather state for a region from elsewhere
in the code
*/
void setRegionWeather(const std::string& region, const unsigned int currentWeather, const unsigned int nextWeather,
const unsigned int queuedWeather, const float transitionFactor, bool force) override;
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to check whether the local WeatherManager has the
ability to create weather changes
*/
bool getWeatherCreationState() override;
/*
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 setWeatherCreationState(bool state) override;
/*
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 sendWeather() override;
/*
End of tes3mp addition
*/
int getCurrentWeather() const override;
int getMasserPhase() const override;

@ -18,6 +18,15 @@ namespace mwmp
std::vector<char> imageData;
};
struct Weather
{
std::string region;
unsigned int currentWeather;
unsigned int nextWeather;
unsigned int queuedWeather;
float transitionFactor;
};
class BaseWorldstate
{
public:
@ -53,6 +62,9 @@ namespace mwmp
std::vector<MapTile> mapTiles;
bool forceWeather;
Weather weather;
bool isValid;
};
}

@ -13,5 +13,10 @@ void PacketWorldWeather::Packet(RakNet::BitStream *bs, bool send)
{
WorldstatePacket::Packet(bs, send);
// Placeholder to be filled in later
RW(worldstate->forceWeather, send);
RW(worldstate->weather.region, send, true);
RW(worldstate->weather.currentWeather, send);
RW(worldstate->weather.nextWeather, send);
RW(worldstate->weather.queuedWeather, send);
RW(worldstate->weather.transitionFactor, send);
}

Loading…
Cancel
Save