diff --git a/apps/openmw-mp/CMakeLists.txt b/apps/openmw-mp/CMakeLists.txt index 11863815f..061b57fac 100644 --- a/apps/openmw-mp/CMakeLists.txt +++ b/apps/openmw-mp/CMakeLists.txt @@ -69,6 +69,7 @@ set(SERVER Networking.cpp Utils.cpp MasterClient.cpp + Cell.cpp Script/Script.cpp Script/ScriptFunction.cpp Script/ScriptFunctions.cpp diff --git a/apps/openmw-mp/Cell.cpp b/apps/openmw-mp/Cell.cpp new file mode 100644 index 000000000..fb28d9ef4 --- /dev/null +++ b/apps/openmw-mp/Cell.cpp @@ -0,0 +1,221 @@ +// +// Created by koncord on 18.02.17. +// + +#include "Cell.hpp" + +#include +#include "Player.hpp" + +using namespace std; + +void Cell::addPlayer(Player *player) +{ + auto it = find(player->cells.begin(), player->cells.end(), this); + if (it == player->cells.end()) + player->cells.push_back(this); + players.push_back(player); +} + +void Cell::removePlayer(Player *player) +{ + for (Iterator it = begin(); it != end(); it++) + { + if (*it == player) + { + auto it2 = find(player->cells.begin(), player->cells.end(), this); + if (it2 != player->cells.end()) + player->cells.erase(it2); + players.erase(it); + return; + } + } +} + +Cell::TPlayers Cell::getPlayers() +{ + return players; +} + +void Cell::sendToLoaded(mwmp::WorldPacket *worldPacket, mwmp::BaseEvent *baseEvent) +{ + std::list plList; + + for (auto pl :getPlayers()) + plList.push_back(pl); + + plList.sort(); + plList.unique(); + + for (auto pl : plList) + { + if (pl->guid == baseEvent->guid) continue; + worldPacket->Send(baseEvent, pl->guid); + } +} + +std::string Cell::getDescription() const +{ + return cell.getDescription(); +} + +CellController::CellController() +{ + +} + +CellController::~CellController() +{ + +} + +CellController *CellController::sThis = nullptr; + +void CellController::create() +{ + sThis = new CellController; +} + +void CellController::destroy() +{ + assert(sThis); + delete sThis; + sThis = nullptr; +} + +CellController *CellController::get() +{ + return sThis; +} + +Cell *CellController::getCell(ESM::Cell *esmCell) +{ + if (esmCell->isExterior()) + return getCellByXY(esmCell->mData.mX, esmCell->mData.mY); + else + return getCellByName(esmCell->mName); +} + + +Cell *CellController::getCellByXY(int x, int y) +{ + auto it = find_if(cells.begin(), cells.end(), [x, y](const Cell *c) { + return c->cell.mData.mX == x && c->cell.mData.mY == y; + }); + if (it == cells.end()) + return nullptr; + return *it; +} + +Cell *CellController::getCellByName(std::string cellName) +{ + auto it = find_if(cells.begin(), cells.end(), [cellName](const Cell *c) { + return c->cell.mName == cellName; + }); + if (it == cells.end()) + return nullptr; + return *it; +} + +Cell *CellController::addCell(ESM::Cell cellData) +{ + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Loaded cells: %d", cells.size()); + auto it = find_if(cells.begin(), cells.end(), [cellData](const Cell *c) { + //return c->cell.sRecordId == cellData.sRecordId; // Currently we cannot compare because plugin lists can be loaded in different order + return c->cell.mData.mX == cellData.mData.mX && c->cell.mData.mY == cellData.mData.mY && + c->cell.mCellId.mWorldspace == cellData.mCellId.mWorldspace; + }); + Cell *cell; + if (it == cells.end()) + { + cell = new Cell(cellData); + cells.push_back(cell); + } + else + cell = *it; + return cell; + + +} + +void CellController::removeCell(Cell *cell) +{ + if (cell == nullptr) + return; + for (auto it = cells.begin(); it != cells.end();) + { + if (*it != nullptr && *it == cell) + { + delete *it; + it = cells.erase(it); + } + else + ++it; + } +} + +void CellController::removePlayer(Cell *cell, Player *player) +{ + cell->removePlayer(player); + + if (cell->players.empty()) + { + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Deleting empty cell from memory: %s", player->npc.mName, player->getId(), cell->cell.getDescription().c_str()); + auto it = find(cells.begin(), cells.end(), cell); + delete *it; + cells.erase(it); + } +} + +void CellController::deletePlayer(Player *player) +{ + for_each (player->getCells().begin(), player->getCells().end(), [&player](Cell *cell) { + for (auto it = cell->begin(); it != cell->end(); ++it) + { + if (*it == player) + { + cell->players.erase(it); + break; + } + } + }); +} + +void CellController::update(Player *player) +{ + for (auto cell : player->cellStateChanges.cellStates) + { + if (cell.type == mwmp::CellState::LOAD) + { + Cell *c = addCell(cell.cell); + c->addPlayer(player); + } + else + { + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Player %s (%d) unloaded cell: %s", player->npc.mName, player->getId(), cell.cell.getDescription().c_str()); + Cell *c; + if (!cell.cell.isExterior()) + c = getCellByName(cell.cell.mName); + else + c = getCellByXY(cell.cell.getGridX(), cell.cell.getGridY()); + + if (c != nullptr) + removePlayer(c, player); + } + } +} + +Cell::Cell(ESM::Cell cell): cell(cell) +{ + +} + +Cell::Iterator Cell::begin() +{ + return players.begin(); +} + +Cell::Iterator Cell::end() +{ + return players.end(); +} diff --git a/apps/openmw-mp/Cell.hpp b/apps/openmw-mp/Cell.hpp new file mode 100644 index 000000000..938aa0a55 --- /dev/null +++ b/apps/openmw-mp/Cell.hpp @@ -0,0 +1,76 @@ +// +// Created by koncord on 18.02.17. +// + +#ifndef OPENMW_CELL_HPP +#define OPENMW_CELL_HPP + +#include +#include +#include +#include +#include + +class Player; +class Cell; + + +class CellController +{ +private: + CellController(); + ~CellController(); + + CellController(CellController&); // not used +public: + static void create(); + static void destroy(); + static CellController *get(); +public: + typedef std::deque TContainer; + typedef TContainer::iterator TIter; + + Cell * addCell(ESM::Cell cell); + void removeCell(Cell *); + + void removePlayer(Cell *cell, Player *player); + void deletePlayer(Player *player); + + Cell *getCell(ESM::Cell *esmCell); + Cell *getCellByXY(int x, int y); + Cell *getCellByName(std::string cellName); + + void update(Player *player); + +private: + static CellController *sThis; + TContainer cells; +}; + +class Cell +{ + friend class CellController; +public: + Cell(ESM::Cell cell); + typedef std::deque TPlayers; + typedef TPlayers::iterator Iterator; + + Iterator begin(); + Iterator end(); + + void addPlayer(Player *player); + void removePlayer(Player *player); + + TPlayers getPlayers(); + void sendToLoaded(mwmp::WorldPacket *worldPacket, mwmp::BaseEvent *baseEvent); + + std::string getDescription() const; + + +private: + TPlayers players; + ESM::Cell cell; +}; + + +#endif //OPENMW_CELL_HPP diff --git a/apps/openmw-mp/MasterClient.cpp b/apps/openmw-mp/MasterClient.cpp index bdd915123..dcb787e69 100644 --- a/apps/openmw-mp/MasterClient.cpp +++ b/apps/openmw-mp/MasterClient.cpp @@ -152,7 +152,7 @@ void MasterClient::Update() { LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Update rate is too low, and the master server has deleted information about" " the server. Trying low rate..."); - if((timeout - step_rate) >= step_rate) + if ((timeout - step_rate) >= step_rate) SetUpdateRate(timeout - step_rate); update = false; } @@ -177,10 +177,10 @@ void MasterClient::Start() void MasterClient::Stop() { - if(!sRun) + if (!sRun) return; sRun = false; - if(thrQuery.joinable()) + if (thrQuery.joinable()) thrQuery.join(); } diff --git a/apps/openmw-mp/Networking.cpp b/apps/openmw-mp/Networking.cpp index 0d3948df5..352756f53 100644 --- a/apps/openmw-mp/Networking.cpp +++ b/apps/openmw-mp/Networking.cpp @@ -15,6 +15,7 @@ #include "Networking.hpp" #include "MasterClient.hpp" +#include "Cell.hpp" using namespace mwmp; using namespace std; @@ -29,6 +30,8 @@ Networking::Networking(RakNet::RakPeerInterface *peer) this->peer = peer; players = Players::getPlayers(); + CellController::create(); + playerController = new PlayerPacketController(peer); worldController = new WorldPacketController(peer); @@ -46,6 +49,8 @@ Networking::~Networking() { Script::Call(false); + CellController::destroy(); + sThis = 0; delete playerController; LOG_QUIT(); @@ -145,7 +150,10 @@ void Networking::processPlayerPacket(RakNet::Packet *packet) if (!player->creatureStats.mDead) { myPacket->Read(player); - myPacket->Send(player, true); //send to other clients + //myPacket->Send(player, true); //send to other clients + + player->sendToLoaded(myPacket); + } break; @@ -162,7 +170,26 @@ void Networking::processPlayerPacket(RakNet::Packet *packet) LOG_APPEND(Log::LOG_INFO, "- Moved to %s", player->cell.getDescription().c_str()); + player->forEachLoaded([this](Player *pl, Player *other) { + playerController->GetPacket(ID_PLAYER_DYNAMICSTATS)->Send(other, pl->guid); + playerController->GetPacket(ID_PLAYER_ATTRIBUTE)->Send(other, pl->guid); + playerController->GetPacket(ID_PLAYER_SKILL)->Send(other, pl->guid); + playerController->GetPacket(ID_PLAYER_POS)->Send(other, pl->guid); + playerController->GetPacket(ID_PLAYER_EQUIPMENT)->Send(other, pl->guid); + playerController->GetPacket(ID_PLAYER_ATTACK)->Send(other, pl->guid); + playerController->GetPacket(ID_PLAYER_DRAWSTATE)->Send(other, pl->guid); + + playerController->GetPacket(ID_PLAYER_DYNAMICSTATS)->Send(pl, other->guid); + playerController->GetPacket(ID_PLAYER_ATTRIBUTE)->Send(pl, other->guid); + playerController->GetPacket(ID_PLAYER_SKILL)->Send(pl, other->guid); + playerController->GetPacket(ID_PLAYER_POS)->Send(pl, other->guid); + playerController->GetPacket(ID_PLAYER_EQUIPMENT)->Send(pl, other->guid); + playerController->GetPacket(ID_PLAYER_ATTACK)->Send(pl, other->guid); + playerController->GetPacket(ID_PLAYER_DRAWSTATE)->Send(pl, other->guid); + }); + myPacket->Send(player, true); //send to other clients + Script::Call(player->getId()); } else @@ -180,6 +207,8 @@ void Networking::processPlayerPacket(RakNet::Packet *packet) myPacket->Read(player); + CellController::get()->update(player); + Script::Call(player->getId()); break; @@ -190,7 +219,9 @@ void Networking::processPlayerPacket(RakNet::Packet *packet) if (!player->creatureStats.mDead) { myPacket->Read(player); - myPacket->Send(player, true); + //myPacket->Send(player, true); + + player->sendToLoaded(myPacket); Script::Call(player->getId()); } @@ -203,7 +234,8 @@ void Networking::processPlayerPacket(RakNet::Packet *packet) if (!player->creatureStats.mDead) { myPacket->Read(player); - myPacket->Send(player, true); + //myPacket->Send(player, true); + player->sendToLoaded(myPacket); Script::Call(player->getId()); } @@ -216,7 +248,7 @@ void Networking::processPlayerPacket(RakNet::Packet *packet) if (!player->creatureStats.mDead) { myPacket->Read(player); - myPacket->Send(player, true); + //myPacket->Send(player, true); Script::Call(player->getId()); } @@ -228,7 +260,9 @@ void Networking::processPlayerPacket(RakNet::Packet *packet) DEBUG_PRINTF("ID_PLAYER_EQUIPMENT\n"); myPacket->Read(player); - myPacket->Send(player, true); + //myPacket->Send(player, true); + + player->sendToLoaded(myPacket); Script::Call(player->getId()); @@ -291,7 +325,8 @@ void Networking::processPlayerPacket(RakNet::Packet *packet) } } - myPacket->Send(player, true); + //myPacket->Send(player, true); + player->sendToLoaded(myPacket); playerController->GetPacket(ID_PLAYER_DYNAMICSTATS)->RequestData(player->attack.target); } break; @@ -301,7 +336,10 @@ void Networking::processPlayerPacket(RakNet::Packet *packet) { DEBUG_PRINTF("ID_PLAYER_DYNAMICSTATS\n"); myPacket->Read(player); - myPacket->Send(player, true); + //myPacket->Send(player, true); + + player->sendToLoaded(myPacket); + break; } @@ -352,7 +390,10 @@ void Networking::processPlayerPacket(RakNet::Packet *packet) { DEBUG_PRINTF("ID_PLAYER_DRAWSTATE\n"); myPacket->Read(player); - myPacket->Send(player, true); + //myPacket->Send(player, true); + + player->sendToLoaded(myPacket); + break; } @@ -558,7 +599,16 @@ void Networking::processWorldPacket(RakNet::Packet *packet) player->npc.mName.c_str()); myPacket->Read(baseEvent); - myPacket->Send(baseEvent, true); + + LOG_APPEND(Log::LOG_WARN, "- action: %i", baseEvent->action); + + // Until we have a timestamp-based system, send packets pertaining to more + // than one container (i.e. replies to server requests for container contents) + // only to players who have the container's cell loaded + if (baseEvent->action == BaseEvent::SET && baseEvent->objectChanges.count > 1) + CellController::get()->getCell(&baseEvent->cell)->sendToLoaded(myPacket, baseEvent); + else + myPacket->Send(baseEvent, true); Script::Call( player->getId(), @@ -817,7 +867,7 @@ int Networking::mainLoop() RakNet::BitStream bs; bs.Write((unsigned char) ID_MASTER_QUERY); bs.Write(Players::getPlayers()->size()); - for(auto player : *Players::getPlayers()) + for (auto player : *Players::getPlayers()) bs.Write(RakNet::RakString(player.second->npc.mName.c_str())); bs.Write(0); // plugins peer->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); diff --git a/apps/openmw-mp/Player.cpp b/apps/openmw-mp/Player.cpp index 666ed5282..6f9b5ee3e 100644 --- a/apps/openmw-mp/Player.cpp +++ b/apps/openmw-mp/Player.cpp @@ -15,6 +15,8 @@ void Players::deletePlayer(RakNet::RakNetGUID guid) if (players[guid] != 0) { + CellController::get()->deletePlayer(players[guid]); + LOG_APPEND(Log::LOG_INFO, "- Emptying slot %i", players[guid]->getId()); @@ -52,7 +54,7 @@ void Players::newPlayer(RakNet::RakNetGUID guid) Player *Players::getPlayer(RakNet::RakNetGUID guid) { - if(players.count(guid) == 0) + if (players.count(guid) == 0) return nullptr; return players[guid]; } @@ -135,3 +137,44 @@ std::chrono::steady_clock::time_point Player::getLastAttackerTime() { return lastAttackerTime; } + +CellController::TContainer Player::getCells() +{ + return cells; +} + +void Player::sendToLoaded(mwmp::PlayerPacket *myPacket) +{ + std::list plList; + + for (auto cell : getCells()) + for (auto pl : *cell) + plList.push_back(pl); + + plList.sort(); + plList.unique(); + + for (auto pl : plList) + { + if (pl == this) continue; + myPacket->Send(this, pl->guid); + } +} + +void Player::forEachLoaded(std::function func) +{ + std::list plList; + + for (auto cell : getCells()) + for (auto pl : *cell) + plList.push_back(pl); + + plList.sort(); + plList.unique(); + + for (auto pl : plList) + { + if (pl == this) continue; + func(this, pl); + } +} diff --git a/apps/openmw-mp/Player.hpp b/apps/openmw-mp/Player.hpp index 3fdc0b2e3..f9fdbb878 100644 --- a/apps/openmw-mp/Player.hpp +++ b/apps/openmw-mp/Player.hpp @@ -17,6 +17,8 @@ #include #include +#include +#include "Cell.hpp" struct Player; typedef std::map TPlayers; @@ -38,6 +40,7 @@ private: class Player : public mwmp::BasePlayer { + friend class Cell; unsigned short id; public: @@ -67,12 +70,18 @@ public: virtual ~Player(); + CellController::TContainer getCells(); + void sendToLoaded(mwmp::PlayerPacket *myPacket); + + void forEachLoaded(std::function func); + public: mwmp::InventoryChanges inventoryChangesBuffer; mwmp::SpellbookChanges spellbookChangesBuffer; mwmp::JournalChanges journalChangesBuffer; private: + CellController::TContainer cells; bool handshakeState; int loadState; unsigned short lastAttacker; diff --git a/apps/openmw/mwmp/Networking.cpp b/apps/openmw/mwmp/Networking.cpp index b592f9fc3..9fdeb8565 100644 --- a/apps/openmw/mwmp/Networking.cpp +++ b/apps/openmw/mwmp/Networking.cpp @@ -747,6 +747,7 @@ void Networking::processWorldPacket(RakNet::Packet *packet) if (!ptrCellStore) return; LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Received ID_CONTAINER"); + LOG_APPEND(Log::LOG_WARN, "- action: %i", event->action); // If we've received a request for information, comply with it if (event->action == mwmp::BaseEvent::REQUEST)