/* * NetPacksLobbyServer.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */ #include "StdInc.h" #include "LobbyNetPackVisitors.h" #include "CVCMIServer.h" #include "CGameHandler.h" #include "../lib/StartInfo.h" #include "../lib/CRandomGenerator.h" #include "../lib/campaign/CampaignState.h" #include "../lib/entities/faction/CTownHandler.h" #include "../lib/entities/faction/CFaction.h" #include "../lib/serializer/Connection.h" #include "../lib/mapping/CMapInfo.h" #include "../lib/mapping/CMapHeader.h" #include "../lib/filesystem/Filesystem.h" void ClientPermissionsCheckerNetPackVisitor::visitForLobby(CPackForLobby & pack) { if(pack.isForServer()) { result = srv.isClientHost(pack.c->connectionID); } } void ApplyOnServerAfterAnnounceNetPackVisitor::visitForLobby(CPackForLobby & pack) { // Propagate options after every CLobbyPackToServer if(pack.isForServer()) { srv.updateAndPropagateLobbyState(); } } void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack) { result = srv.getState() == EServerState::LOBBY; } void ApplyOnServerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack) { auto compatibleVersion = std::min(pack.version, ESerializationVersion::CURRENT); pack.c->setSerializationVersion(compatibleVersion); srv.clientConnected(pack.c, pack.names, pack.uuid, pack.mode); // Server need to pass some data to newly connected client pack.clientId = pack.c->connectionID; pack.mode = srv.si->mode; pack.hostClientId = srv.hostClientId; pack.version = compatibleVersion; result = true; } void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack) { // FIXME: we need to avoid senting something to client that not yet get answer for LobbyClientConnected // Until UUID set we only pass LobbyClientConnected to this client pack.c->uuid = pack.uuid; srv.updateAndPropagateLobbyState(); // FIXME: what is this??? We do NOT support reconnection into ongoing game - at the very least queries and battles are NOT serialized // if(srv.getState() == EServerState::GAMEPLAY) // { // //immediately start game // std::unique_ptr startGameForReconnectedPlayer(new LobbyStartGame); // startGameForReconnectedPlayer->initializedStartInfo = srv.si; // startGameForReconnectedPlayer->initializedGameState = srv.gh->gameState(); // startGameForReconnectedPlayer->clientId = pack.c->connectionID; // srv.announcePack(std::move(startGameForReconnectedPlayer)); // } } void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) { if(pack.clientId != pack.c->connectionID) { result = false; return; } if(pack.shutdownServer) { if(!srv.wasStartedByClient()) { result = false; return; } if(pack.c->connectionID != srv.hostClientId) { result = false; return; } } result = true; } void ApplyOnServerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) { pack.c->getConnection()->close(); srv.clientDisconnected(pack.c); result = true; } void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) { if(pack.shutdownServer) { logNetwork->info("Client requested shutdown, server will close itself..."); srv.setState(EServerState::SHUTDOWN); return; } else if(srv.activeConnections.empty()) { logNetwork->error("Last connection lost, server will close itself..."); srv.setState(EServerState::SHUTDOWN); } else if(pack.c->connectionID == srv.hostClientId) { LobbyChangeHost ph; auto newHost = srv.activeConnections.front(); ph.newHostConnectionId = newHost->connectionID; srv.announcePack(ph); } srv.updateAndPropagateLobbyState(); // if(srv.getState() != EServerState::SHUTDOWN && srv.remoteConnections.count(pack.c)) // { // srv.remoteConnections -= pack.c; // srv.connectToRemote(); // } } void ClientPermissionsCheckerNetPackVisitor::visitLobbyChatMessage(LobbyChatMessage & pack) { result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetMap(LobbySetMap & pack) { if(srv.getState() != EServerState::LOBBY) { result = false; return; } srv.updateStartInfoOnMapChange(pack.mapInfo, pack.mapGenOpts); result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetCampaign(LobbySetCampaign & pack) { srv.si->mapname = pack.ourCampaign->getFilename(); srv.si->mode = EStartMode::CAMPAIGN; srv.si->campState = pack.ourCampaign; srv.si->turnTimerInfo = TurnTimerInfo{}; bool isCurrentMapConquerable = pack.ourCampaign->currentScenario() && pack.ourCampaign->isAvailable(*pack.ourCampaign->currentScenario()); for(auto scenarioID : pack.ourCampaign->allScenarios()) { if(pack.ourCampaign->isAvailable(scenarioID)) { if(!isCurrentMapConquerable || (isCurrentMapConquerable && scenarioID == *pack.ourCampaign->currentScenario())) { srv.setCampaignMap(scenarioID); } } } result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetCampaignMap(LobbySetCampaignMap & pack) { srv.setCampaignMap(pack.mapId); result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetCampaignBonus(LobbySetCampaignBonus & pack) { srv.setCampaignBonus(pack.bonusId); result = true; } void ClientPermissionsCheckerNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack) { result = srv.isClientHost(pack.c->connectionID); } void ClientPermissionsCheckerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) { result = srv.isClientHost(pack.c->connectionID); } void ApplyOnServerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) { srv.prepareToRestart(); result = true; } void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) { for(const auto & connection : srv.activeConnections) connection->enterLobbyConnectionMode(); } void ClientPermissionsCheckerNetPackVisitor::visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) { result = srv.isClientHost(pack.c->connectionID); } void ClientPermissionsCheckerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) { result = srv.isClientHost(pack.c->connectionID); } void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) { try { srv.verifyStateBeforeStart(true); } catch(...) { result = false; return; } // Server will prepare gamestate and we announce StartInfo to clients if(!srv.prepareToStartGame()) { result = false; return; } pack.initializedStartInfo = std::make_shared(*srv.gh->getStartInfo(true)); pack.initializedGameState = srv.gh->gameState(); result = true; } void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) { if(pack.clientId == -1) //do not restart game for single client only srv.startGameImmediately(); else { for(const auto & connection : srv.activeConnections) { if(connection->connectionID == pack.clientId) { connection->enterGameplayConnectionMode(srv.gh->gameState()); srv.reconnectPlayer(pack.clientId); } } } } void ClientPermissionsCheckerNetPackVisitor::visitLobbyChangeHost(LobbyChangeHost & pack) { result = srv.isClientHost(pack.c->connectionID); } void ApplyOnServerNetPackVisitor::visitLobbyChangeHost(LobbyChangeHost & pack) { result = true; } void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyChangeHost(LobbyChangeHost & pack) { auto result = srv.passHost(pack.newHostConnectionId); if(!result) { logGlobal->error("passHost returned false. What does it mean?"); } } void ClientPermissionsCheckerNetPackVisitor::visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) { if(srv.isClientHost(pack.c->connectionID)) { result = true; return; } if(vstd::contains(srv.getAllClientPlayers(pack.c->connectionID), pack.color)) { result = true; return; } result = false; } void ApplyOnServerNetPackVisitor::visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) { switch(pack.what) { case LobbyChangePlayerOption::TOWN_ID: srv.optionSetCastle(pack.color, FactionID(pack.value)); break; case LobbyChangePlayerOption::TOWN: srv.optionNextCastle(pack.color, pack.value); break; case LobbyChangePlayerOption::HERO_ID: srv.optionSetHero(pack.color, HeroTypeID(pack.value)); break; case LobbyChangePlayerOption::HERO: srv.optionNextHero(pack.color, pack.value); break; case LobbyChangePlayerOption::BONUS_ID: srv.optionSetBonus(pack.color, PlayerStartingBonus(pack.value)); break; case LobbyChangePlayerOption::BONUS: srv.optionNextBonus(pack.color, pack.value); break; } result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetPlayer(LobbySetPlayer & pack) { srv.setPlayer(pack.clickedColor); result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetPlayerName(LobbySetPlayerName & pack) { srv.setPlayerName(pack.color, pack.name); result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetPlayerHandicap(LobbySetPlayerHandicap & pack) { srv.setPlayerHandicap(pack.color, pack.handicap); result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetSimturns(LobbySetSimturns & pack) { srv.si->simturnsInfo = pack.simturnsInfo; result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetTurnTime(LobbySetTurnTime & pack) { srv.si->turnTimerInfo = pack.turnTimerInfo; result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetExtraOptions(LobbySetExtraOptions & pack) { srv.si->extraOptionsInfo = pack.extraOptionsInfo; result = true; } void ApplyOnServerNetPackVisitor::visitLobbySetDifficulty(LobbySetDifficulty & pack) { srv.si->difficulty = std::clamp(pack.difficulty, 0, 4); result = true; } void ApplyOnServerNetPackVisitor::visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) { srv.si->playerInfos[pack.targetPlayerColor].connectedPlayerIDs.insert(pack.targetConnectedPlayer); result = true; } void ClientPermissionsCheckerNetPackVisitor::visitLobbyPvPAction(LobbyPvPAction & pack) { result = true; } void ApplyOnServerNetPackVisitor::visitLobbyPvPAction(LobbyPvPAction & pack) { std::vector allowedTowns; for (auto const & factionID : VLC->townh->getDefaultAllowed()) if(std::find(pack.bannedTowns.begin(), pack.bannedTowns.end(), factionID) == pack.bannedTowns.end()) allowedTowns.push_back(factionID); std::vector randomFaction1; std::sample(allowedTowns.begin(), allowedTowns.end(), std::back_inserter(randomFaction1), 1, std::mt19937{std::random_device{}()}); std::vector randomFaction2; std::sample(allowedTowns.begin(), allowedTowns.end(), std::back_inserter(randomFaction2), 1, std::mt19937{std::random_device{}()}); MetaString txt; switch(pack.action) { case LobbyPvPAction::COIN: txt.appendTextID("vcmi.lobby.pvp.coin.hover"); txt.appendRawString(" - " + std::to_string(std::rand()%2)); srv.announceTxt(txt); break; case LobbyPvPAction::RANDOM_TOWN: if(!allowedTowns.size()) break; txt.appendTextID("core.overview.3"); txt.appendRawString(" - "); txt.appendTextID(VLC->townh->getById(randomFaction1[0])->getNameTextID()); srv.announceTxt(txt); break; case LobbyPvPAction::RANDOM_TOWN_VS: if(!allowedTowns.size()) break; txt.appendTextID("core.overview.3"); txt.appendRawString(" - "); txt.appendTextID(VLC->townh->getById(randomFaction1[0])->getNameTextID()); txt.appendRawString(" "); txt.appendTextID("vcmi.lobby.pvp.versus"); txt.appendRawString(" "); txt.appendTextID(VLC->townh->getById(randomFaction2[0])->getNameTextID()); srv.announceTxt(txt); break; } result = true; } void ClientPermissionsCheckerNetPackVisitor::visitLobbyDelete(LobbyDelete & pack) { result = srv.isClientHost(pack.c->connectionID); } void ApplyOnServerNetPackVisitor::visitLobbyDelete(LobbyDelete & pack) { if(pack.type == LobbyDelete::SAVEGAME || pack.type == LobbyDelete::RANDOMMAP) { auto res = ResourcePath(pack.name, pack.type == LobbyDelete::SAVEGAME ? EResType::SAVEGAME : EResType::MAP); auto file = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(res)); boost::filesystem::remove(file); if(boost::filesystem::is_empty(file.parent_path())) boost::filesystem::remove(file.parent_path()); } else if(pack.type == LobbyDelete::SAVEGAME_FOLDER) { auto res = ResourcePath("Saves/" + pack.name, EResType::DIRECTORY); auto folder = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(res)); boost::filesystem::remove_all(folder); } LobbyUpdateState lus; lus.state = srv; lus.refreshList = true; srv.announcePack(lus); }