diff --git a/apps/openmw-mp/Script/Functions/Worldstate.cpp b/apps/openmw-mp/Script/Functions/Worldstate.cpp index 85fcf7870..3638cb155 100644 --- a/apps/openmw-mp/Script/Functions/Worldstate.cpp +++ b/apps/openmw-mp/Script/Functions/Worldstate.cpp @@ -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 diff --git a/apps/openmw-mp/Script/Functions/Worldstate.hpp b/apps/openmw-mp/Script/Functions/Worldstate.hpp index 682380468..bc7af01c2 100644 --- a/apps/openmw-mp/Script/Functions/Worldstate.hpp +++ b/apps/openmw-mp/Script/Functions/Worldstate.hpp @@ -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. diff --git a/apps/openmw-mp/Script/ScriptFunctions.hpp b/apps/openmw-mp/Script/ScriptFunctions.hpp index f8b92482a..f69b2ff77 100644 --- a/apps/openmw-mp/Script/ScriptFunctions.hpp +++ b/apps/openmw-mp/Script/ScriptFunctions.hpp @@ -201,6 +201,7 @@ public: {"OnGUIAction", Function()}, {"OnWorldKillCount", Function()}, {"OnWorldMap", Function()}, + {"OnWorldWeather", Function() }, {"OnMpNumIncrement", Function()}, {"OnRequestPluginList", Function()} }; diff --git a/apps/openmw-mp/processors/worldstate/ProcessorWorldWeather.hpp b/apps/openmw-mp/processors/worldstate/ProcessorWorldWeather.hpp index f815a557e..001838fca 100644 --- a/apps/openmw-mp/processors/worldstate/ProcessorWorldWeather.hpp +++ b/apps/openmw-mp/processors/worldstate/ProcessorWorldWeather.hpp @@ -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(player.getId()); } }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 115c6afe7..9ed5e64fe 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -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 diff --git a/apps/openmw/mwmp/DedicatedPlayer.cpp b/apps/openmw/mwmp/DedicatedPlayer.cpp index d5f40ea42..c5bb3ab4a 100644 --- a/apps/openmw/mwmp/DedicatedPlayer.cpp +++ b/apps/openmw/mwmp/DedicatedPlayer.cpp @@ -61,6 +61,8 @@ DedicatedPlayer::DedicatedPlayer(RakNet::RakNetGUID guid) : BasePlayer(guid) npc = *world->getPlayerPtr().get()->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() diff --git a/apps/openmw/mwmp/DedicatedPlayer.hpp b/apps/openmw/mwmp/DedicatedPlayer.hpp index 783153a00..4b0cd1dc4 100644 --- a/apps/openmw/mwmp/DedicatedPlayer.hpp +++ b/apps/openmw/mwmp/DedicatedPlayer.hpp @@ -78,6 +78,8 @@ namespace mwmp bool previousDisplayCreatureName; std::string creatureRecordId; + + bool hasFinishedInitialTeleportation; }; } #endif //OPENMW_DEDICATEDPLAYER_HPP diff --git a/apps/openmw/mwmp/Worldstate.cpp b/apps/openmw/mwmp/Worldstate.cpp index db9ff265e..0c4560745 100644 --- a/apps/openmw/mwmp/Worldstate.cpp +++ b/apps/openmw/mwmp/Worldstate.cpp @@ -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& imageData) { mapTiles.clear(); @@ -59,7 +94,7 @@ void Worldstate::sendMapExplored(int cellX, int cellY, const std::vector& 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& 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(); } diff --git a/apps/openmw/mwmp/Worldstate.hpp b/apps/openmw/mwmp/Worldstate.hpp index e1f7a0d0d..bf64db706 100644 --- a/apps/openmw/mwmp/Worldstate.hpp +++ b/apps/openmw/mwmp/Worldstate.hpp @@ -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& imageData); + void sendWeather(std::string region, int currentWeather, int nextWeather, int queuedWeather, float transitionFactor); + + mwmp::Weather localWeather; private: diff --git a/apps/openmw/mwmp/processors/worldstate/ProcessorWorldRegionAuthority.hpp b/apps/openmw/mwmp/processors/worldstate/ProcessorWorldRegionAuthority.hpp index f36b3ee20..f61d74a96 100644 --- a/apps/openmw/mwmp/processors/worldstate/ProcessorWorldRegionAuthority.hpp +++ b/apps/openmw/mwmp/processors/worldstate/ProcessorWorldRegionAuthority.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_PROCESSORWORLDREGIONAUTHORITY_HPP #define OPENMW_PROCESSORWORLDREGIONAUTHORITY_HPP +#include + #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); + } } } }; diff --git a/apps/openmw/mwmp/processors/worldstate/ProcessorWorldWeather.hpp b/apps/openmw/mwmp/processors/worldstate/ProcessorWorldWeather.hpp index 2cdf0e705..001383d8f 100644 --- a/apps/openmw/mwmp/processors/worldstate/ProcessorWorldWeather.hpp +++ b/apps/openmw/mwmp/processors/worldstate/ProcessorWorldWeather.hpp @@ -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 diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index d12e633be..ae2454647 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -7,6 +7,19 @@ #include #include +/* + Start of tes3mp addition + + Include additional headers for multiplayer purposes +*/ +#include +#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::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, diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index cf6868356..cff3da226 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -267,6 +267,18 @@ namespace MWWorld void modRegion(const std::string& regionID, const std::vector& 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, diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 2cafb1af5..95fe12371 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -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 ®ionid, const std::vector &chances) { mWeatherManager->modRegion(regionid, chances); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 06cfb162c..6a55c1be1 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -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; diff --git a/components/openmw-mp/Base/BaseWorldstate.hpp b/components/openmw-mp/Base/BaseWorldstate.hpp index 9c31e67d6..5bdf170c9 100644 --- a/components/openmw-mp/Base/BaseWorldstate.hpp +++ b/components/openmw-mp/Base/BaseWorldstate.hpp @@ -18,6 +18,15 @@ namespace mwmp std::vector 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 mapTiles; + bool forceWeather; + Weather weather; + bool isValid; }; } diff --git a/components/openmw-mp/Packets/Worldstate/PacketWorldWeather.cpp b/components/openmw-mp/Packets/Worldstate/PacketWorldWeather.cpp index b99dda037..75887d4ee 100644 --- a/components/openmw-mp/Packets/Worldstate/PacketWorldWeather.cpp +++ b/components/openmw-mp/Packets/Worldstate/PacketWorldWeather.cpp @@ -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); }