From baa865d85711cb15e1bb856847da2952ecdee7f6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko <saven.ivan@gmail.com> Date: Wed, 12 Jul 2023 21:13:17 +0300 Subject: [PATCH] Extracted message-related functionality of CGameHandler to separate file --- lib/GameConstants.cpp | 1 + lib/GameConstants.h | 2 + server/CGameHandler.cpp | 393 +---------------------- server/CGameHandler.h | 19 +- server/CMakeLists.txt | 2 + server/CVCMIServer.cpp | 5 +- server/NetPacksServer.cpp | 3 +- server/PlayerMessageProcessor.cpp | 508 ++++++++++++++++++++++++++++++ server/PlayerMessageProcessor.h | 63 ++++ 9 files changed, 606 insertions(+), 390 deletions(-) create mode 100644 server/PlayerMessageProcessor.cpp create mode 100644 server/PlayerMessageProcessor.h diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 76dcc54ed..c440277de 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -40,6 +40,7 @@ VCMI_LIB_NAMESPACE_BEGIN const HeroTypeID HeroTypeID::NONE = HeroTypeID(-1); +const ObjectInstanceID ObjectInstanceID::NONE = ObjectInstanceID(-1); const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2); const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 9cc315f6b..93a3085a3 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -313,6 +313,8 @@ class ObjectInstanceID : public BaseForID<ObjectInstanceID, si32> { INSTID_LIKE_CLASS_COMMON(ObjectInstanceID, si32) + DLL_LINKAGE static const ObjectInstanceID NONE; + friend class CGameInfoCallback; friend class CNonConstInfoCallback; }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c96f8d808..00412911a 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -15,6 +15,8 @@ #include "ServerSpellCastEnvironment.h" #include "CVCMIServer.h" +#include "PlayerMessageProcessor.h" + #include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/FileInfo.h" #include "../lib/int3.h" @@ -296,6 +298,11 @@ events::EventBus * CGameHandler::eventBus() const return serverEventBus.get(); } +CVCMIServer * CGameHandler::gameLobby() const +{ + return lobby; +} + void CGameHandler::levelUpHero(const CGHeroInstance * hero, SecondarySkill skill) { changeSecSkill(hero, skill, 1, 0); @@ -1217,7 +1224,7 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c) if(playerConnection != playerConnections.second.end()) { std::string messageText = boost::str(boost::format("%s (cid %d) was disconnected") % playerSettings->name % c->connectionID); - playerMessage(playerId, messageText, ObjectInstanceID{}); + playerMessages->broadcastMessage(playerId, messageText); } } } @@ -1554,6 +1561,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest) CGameHandler::CGameHandler(CVCMIServer * lobby) : lobby(lobby) , heroPool(std::make_unique<HeroPoolProcessor>(this)) + , playerMessages(std::make_unique<PlayerMessageProcessor>(this)) , complainNoCreatures("No creatures to split") , complainNotEnoughCreatures("Cannot split that stack, not enough creatures!") , complainInvalidSlot("Invalid slot accessed!") @@ -2644,14 +2652,6 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st sendAndApply(&cs); } -void CGameHandler::sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message) -{ - SystemMessage sm; - sm.text = message; - boost::unique_lock<boost::mutex> lock(*c->mutexWrite); - *(c.get()) << &sm; -} - void CGameHandler::giveHeroBonus(GiveBonus * bonus) { sendAndApply(bonus); @@ -2862,10 +2862,8 @@ bool CGameHandler::isPlayerOwns(CPackForServer * pack, ObjectInstanceID id) void CGameHandler::throwNotAllowedAction(CPackForServer * pack) { if(pack->c) - { - SystemMessage temp_message("You are not allowed to perform this action!"); - pack->c->sendPack(&temp_message); - } + playerMessages->sendSystemMessage(pack->c, "You are not allowed to perform this action!"); + logNetwork->error("Player is not allowed to perform this action!"); throw ExceptionNotAllowedAction(); } @@ -2875,11 +2873,9 @@ void CGameHandler::wrongPlayerMessage(CPackForServer * pack, PlayerColor expecte std::ostringstream oss; oss << "You were identified as player " << getPlayerAt(pack->c) << " while expecting " << expectedplayer; logNetwork->error(oss.str()); + if(pack->c) - { - SystemMessage temp_message(oss.str()); - pack->c->sendPack(&temp_message); - } + playerMessages->sendSystemMessage(pack->c, oss.str()); } void CGameHandler::throwOnWrongOwner(CPackForServer * pack, ObjectInstanceID id) @@ -3594,13 +3590,6 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) return true; } -void CGameHandler::sendMessageToAll(const std::string &message) -{ - SystemMessage sm; - sm.text = message; - sendToAllClients(&sm); -} - bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl) { const CGDwelling * dw = static_cast<const CGDwelling *>(getObj(objid)); @@ -4825,140 +4814,6 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) return ok; } -void CGameHandler::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj) -{ - bool cheated = false; - - std::vector<std::string> words; - boost::split(words, message, boost::is_any_of(" ")); - - bool isHost = false; - for(auto & c : connections[player]) - if(lobby->isClientHost(c->connectionID)) - isHost = true; - - if(isHost && words.size() >= 2 && words[0] == "game") - { - if(words[1] == "exit" || words[1] == "quit" || words[1] == "end") - { - SystemMessage temp_message("game was terminated"); - sendAndApply(&temp_message); - lobby->state = EServerState::SHUTDOWN; - return; - } - if(words.size() == 3 && words[1] == "save") - { - save("Saves/" + words[2]); - SystemMessage temp_message("game saved as " + words[2]); - sendAndApply(&temp_message); - return; - } - if(words.size() == 3 && words[1] == "kick") - { - auto playername = words[2]; - PlayerColor playerToKick(PlayerColor::CANNOT_DETERMINE); - if(std::all_of(playername.begin(), playername.end(), ::isdigit)) - playerToKick = PlayerColor(std::stoi(playername)); - else - { - for(auto & c : connections) - { - if(c.first.getStr(false) == playername) - playerToKick = c.first; - } - } - - if(playerToKick != PlayerColor::CANNOT_DETERMINE) - { - PlayerCheated pc; - pc.player = playerToKick; - pc.losingCheatCode = true; - sendAndApply(&pc); - checkVictoryLossConditionsForPlayer(playerToKick); - } - return; - } - } - - int obj = 0; - if (words.size() == 2 && words[0] != "vcmiexp" && words[0] != "vcmiolorin") - { - obj = std::atoi(words[1].c_str()); - if (obj) - currObj = ObjectInstanceID(obj); - } - - const CGHeroInstance * hero = getHero(currObj); - const CGTownInstance * town = getTown(currObj); - if (!town && hero) - town = hero->visitedTown; - - if(words.size() > 1 && (words[0] == "vcmiarmy" || words[0] == "vcminissi" || words[0] == "vcmiexp" || words[0] == "vcmiolorin")) - { - std::string cheatCodeWithOneParameter = std::string(words[0]) + " " + words[1]; - handleCheatCode(cheatCodeWithOneParameter, player, hero, town, cheated); - } - else if (words.size() == 1 || obj) - { - handleCheatCode(words[0], player, hero, town, cheated); - } - else - { - for (const auto & i : gs->players) - { - if (i.first == PlayerColor::NEUTRAL) - continue; - if (words[1] == "ai") - { - if (i.second.human) - continue; - } - else if (words[1] != "all" && words[1] != i.first.getStr()) - continue; - - if (words[0] == "vcmiformenos" || words[0] == "vcmieagles" || words[0] == "vcmiungoliant" - || words[0] == "vcmiresources" || words[0] == "vcmimap" || words[0] == "vcmihidemap") - { - handleCheatCode(words[0], i.first, nullptr, nullptr, cheated); - } - else if (words[0] == "vcmiarmenelos" || words[0] == "vcmibuild") - { - for (const auto & t : i.second.towns) - { - handleCheatCode(words[0], i.first, nullptr, t, cheated); - } - } - else - { - for (const auto & h : i.second.heroes) - { - handleCheatCode(words[0], i.first, h, nullptr, cheated); - } - } - } - } - - if (cheated) - { - if(!getPlayerSettings(player)->isControlledByAI()) - { - SystemMessage temp_message(VLC->generaltexth->allTexts[260]); - sendAndApply(&temp_message); - } - - if(!player.isSpectator()) - checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature - } - else - { - if(!getPlayerSettings(player)->isControlledByAI()) - { - PlayerMessageClient temp_message(player, message); - sendAndApply(&temp_message); - } - } -} - bool CGameHandler::makeCustomAction(BattleAction & ba) { switch(ba.actionType) @@ -5321,7 +5176,7 @@ void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n) bool CGameHandler::complain(const std::string &problem) { - sendMessageToAll("Server encountered a problem: " + problem); + playerMessages->broadcastSystemMessage("Server encountered a problem: " + problem); logGlobal->error(problem); return true; } @@ -6713,225 +6568,6 @@ void CGameHandler::spawnWanderingMonsters(CreatureID creatureID) } } -void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, const CGHeroInstance * hero, const CGTownInstance * town, bool & cheated) -{ - //Make cheat case-insensitive - std::transform(cheat.begin(), cheat.end(), cheat.begin(), [](unsigned char c){ return std::tolower(c); }); - - if (cheat == "vcmiistari" || cheat == "vcmispells") - { - cheated = true; - if (!hero) return; - ///Give hero spellbook - if (!hero->hasSpellbook()) - giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); - - ///Give all spells with bonus (to allow banned spells) - GiveBonus giveBonus(GiveBonus::ETarget::HERO); - giveBonus.id = hero->id.getNum(); - giveBonus.bonus = Bonus(BonusDuration::PERMANENT, BonusType::SPELLS_OF_LEVEL, BonusSource::OTHER, 0, 0); - //start with level 0 to skip abilities - for (int level = 1; level <= GameConstants::SPELL_LEVELS; level++) - { - giveBonus.bonus.subtype = level; - sendAndApply(&giveBonus); - } - - ///Give mana - SetMana sm; - sm.hid = hero->id; - sm.val = 999; - sm.absolute = true; - sendAndApply(&sm); - } - else if (cheat == "vcmiarmenelos" || cheat == "vcmibuild") - { - cheated = true; - if (!town) return; - ///Build all buildings in selected town - for (auto & build : town->town->buildings) - { - if (!town->hasBuilt(build.first) - && !build.second->getNameTranslated().empty() - && build.first != BuildingID::SHIP) - { - buildStructure(town->id, build.first, true); - } - } - } - else if (cheat == "vcmiainur" || cheat == "vcmiangband" || cheat == "vcmiglaurung" || cheat == "vcmiarchangel" - || cheat == "vcmiblackknight" || cheat == "vcmicrystal" || cheat == "vcmiazure" || cheat == "vcmifaerie") - { - cheated = true; - if (!hero) return; - ///Gives N creatures into each slot - std::map<std::string, std::pair<std::string, int>> creatures; - creatures.insert(std::make_pair("vcmiainur", std::make_pair("archangel", 5))); //5 archangels - creatures.insert(std::make_pair("vcmiangband", std::make_pair("blackKnight", 10))); //10 black knights - creatures.insert(std::make_pair("vcmiglaurung", std::make_pair("crystalDragon", 5000))); //5000 crystal dragons - creatures.insert(std::make_pair("vcmiarchangel", std::make_pair("archangel", 5))); //5 archangels - creatures.insert(std::make_pair("vcmiblackknight", std::make_pair("blackKnight", 10))); //10 black knights - creatures.insert(std::make_pair("vcmicrystal", std::make_pair("crystalDragon", 5000))); //5000 crystal dragons - creatures.insert(std::make_pair("vcmiazure", std::make_pair("azureDragon", 5000))); //5000 azure dragons - creatures.insert(std::make_pair("vcmifaerie", std::make_pair("fairieDragon", 5000))); //5000 faerie dragons - - const int32_t creatureIdentifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", creatures[cheat].first, false).value(); - const CCreature * creature = VLC->creh->objects.at(creatureIdentifier); - for (int i = 0; i < GameConstants::ARMY_SIZE; i++) - if (!hero->hasStackAtSlot(SlotID(i))) - insertNewStack(StackLocation(hero, SlotID(i)), creature, creatures[cheat].second); - } - else if (boost::starts_with(cheat, "vcmiarmy") || boost::starts_with(cheat, "vcminissi")) - { - cheated = true; - if (!hero) return; - - std::vector<std::string> words; - boost::split(words, cheat, boost::is_any_of(" ")); - - if(words.size() < 2) - return; - - std::string creatureIdentifier = words[1]; - - std::optional<int32_t> creatureId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", creatureIdentifier, false); - - if(creatureId.has_value()) - { - const auto * creature = CreatureID(creatureId.value()).toCreature(); - - for (int i = 0; i < GameConstants::ARMY_SIZE; i++) - if (!hero->hasStackAtSlot(SlotID(i))) - insertNewStack(StackLocation(hero, SlotID(i)), creature, 5 * std::pow(10, i)); - } - } - else if (cheat == "vcminoldor" || cheat == "vcmimachines") - { - cheated = true; - if (!hero) return; - ///Give all war machines to hero - if (!hero->getArt(ArtifactPosition::MACH1)) - giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::BALLISTA], ArtifactPosition::MACH1); - if (!hero->getArt(ArtifactPosition::MACH2)) - giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::AMMO_CART], ArtifactPosition::MACH2); - if (!hero->getArt(ArtifactPosition::MACH3)) - giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3); - } - else if (cheat == "vcmiforgeofnoldorking" || cheat == "vcmiartifacts") - { - cheated = true; - if (!hero) return; - ///Give hero all artifacts except war machines, spell scrolls and spell book - for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods - { - if(VLC->arth->objects[g]->canBePutAt(hero)) - giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE); - } - } - else if (cheat == "vcmiglorfindel" || cheat == "vcmilevel") - { - cheated = true; - if (!hero) return; - ///selected hero gains a new level - changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level + 1) - VLC->heroh->reqExp(hero->level)); - } - else if (boost::starts_with(cheat, "vcmiexp") || boost::starts_with(cheat, "vcmiolorin")) - { - cheated = true; - if (!hero) return; - - std::vector<std::string> words; - boost::split(words, cheat, boost::is_any_of(" ")); - - if(words.size() < 2) - return; - - std::string expAmount = words[1]; - long expAmountProcessed = 0; - - try - { - expAmountProcessed = std::stol(expAmount); - } - catch(std::exception&) - { - logGlobal->error("Could not parse experience amount for vcmiexp cheat"); - } - - if(expAmountProcessed > 1) - { - changePrimSkill(hero, PrimarySkill::EXPERIENCE, expAmountProcessed); - } - } - else if (cheat == "vcminahar" || cheat == "vcmimove") - { - cheated = true; - if (!hero) return; - ///Give 1000000 movement points to hero - SetMovePoints smp; - smp.hid = hero->id; - smp.val = 1000000; - sendAndApply(&smp); - - GiveBonus gb(GiveBonus::ETarget::HERO); - gb.bonus.type = BonusType::FREE_SHIP_BOARDING; - gb.bonus.duration = BonusDuration::ONE_DAY; - gb.bonus.source = BonusSource::OTHER; - gb.id = hero->id.getNum(); - giveHeroBonus(&gb); - } - else if (cheat == "vcmiformenos" || cheat == "vcmiresources") - { - cheated = true; - ///Give resources to player - TResources resources; - resources[EGameResID::GOLD] = 100000; - for (GameResID i = EGameResID::WOOD; i < EGameResID::GOLD; ++i) - resources[i] = 100; - - giveResources(player, resources); - } - else if (cheat == "vcmisilmaril" || cheat == "vcmiwin") - { - cheated = true; - ///Player wins - PlayerCheated pc; - pc.player = player; - pc.winningCheatCode = true; - sendAndApply(&pc); - } - else if (cheat == "vcmimelkor" || cheat == "vcmilose") - { - cheated = true; - ///Player looses - PlayerCheated pc; - pc.player = player; - pc.losingCheatCode = true; - sendAndApply(&pc); - } - else if (cheat == "vcmieagles" || cheat == "vcmiungoliant" || cheat == "vcmimap" || cheat == "vcmihidemap") - { - cheated = true; - ///Reveal or conceal FoW - FoWChange fc; - fc.mode = ((cheat == "vcmieagles" || cheat == "vcmimap") ? 1 : 0); - fc.player = player; - const auto & fowMap = gs->getPlayerTeam(player)->fogOfWarMap; - auto hlp_tab = new int3[gs->map->width * gs->map->height * (gs->map->levels())]; - int lastUnc = 0; - - for(int z = 0; z < gs->map->levels(); z++) - for(int x = 0; x < gs->map->width; x++) - for(int y = 0; y < gs->map->height; y++) - if(!(*fowMap)[z][x][y] || !fc.mode) - hlp_tab[lastUnc++] = int3(x, y, z); - - fc.tiles.insert(hlp_tab, hlp_tab + lastUnc); - delete [] hlp_tab; - sendAndApply(&fc); - } -} - void CGameHandler::removeObstacle(const CObstacleInstance & obstacle) { BattleObstaclesChanged obsRem; @@ -7248,4 +6884,5 @@ void CGameHandler::deserializationFix() //FIXME: pointer to GameHandler itself can't be deserialized at the moment since GameHandler is top-level entity in serialization // restore any places that requires such pointer manually heroPool->gameHandler = this; + playerMessages->gameHandler = this; } diff --git a/server/CGameHandler.h b/server/CGameHandler.h index af01e43c1..771ba90f1 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -50,6 +50,7 @@ class HeroPoolProcessor; class CGameHandler; class CVCMIServer; class CBaseForGHApply; +class PlayerMessageProcessor; struct PlayerStatus { @@ -108,6 +109,8 @@ public: enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST}; enum ELEaveTile {LEAVING_TILE, REMAINING_ON_TILE}; + std::unique_ptr<PlayerMessageProcessor> playerMessages; + std::map<PlayerColor, std::set<std::shared_ptr<CConnection>>> connections; //player color -> connection to client with interface of that player PlayerStatuses states; //player color -> player state @@ -123,6 +126,7 @@ public: const GameCb * game() const override; vstd::CLoggerBase * logger() const override; events::EventBus * eventBus() const override; + CVCMIServer * gameLobby() const; bool isValidObject(const CGObjectInstance *obj) const; bool isBlockedByQueries(const CPack *pack, PlayerColor player); @@ -235,7 +239,6 @@ public: PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const; bool hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> c) const; - void playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj); void updateGateState(); bool makeBattleAction(BattleAction &ba); bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) @@ -289,6 +292,7 @@ public: h & finishingBattle; h & heroPool; h & getRandomGenerator(); + h & playerMessages; if (!h.saving) deserializationFix(); @@ -303,8 +307,6 @@ public: #endif } - void sendMessageToAll(const std::string &message); - void sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message); void sendToAllClients(CPackForClient * pack); void sendAndApply(CPackForClient * pack) override; void applyAndSend(CPackForClient * pack); @@ -354,7 +356,11 @@ public: void attackCasting(bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender); bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector<ArtifactPosition> & slot); void spawnWanderingMonsters(CreatureID creatureID); - void handleCheatCode(std::string & cheat, PlayerColor player, const CGHeroInstance * hero, const CGTownInstance * town, bool & cheated); + + // Check for victory and loss conditions + void checkVictoryLossConditionsForPlayer(PlayerColor player); + void checkVictoryLossConditions(const std::set<PlayerColor> & playerColors); + void checkVictoryLossConditionsForAll(); CRandomGenerator & getRandomGenerator(); @@ -379,11 +385,6 @@ private: void makeStackDoNothing(const CStack * next); void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const; - // Check for victory and loss conditions - void checkVictoryLossConditionsForPlayer(PlayerColor player); - void checkVictoryLossConditions(const std::set<PlayerColor> & playerColors); - void checkVictoryLossConditionsForAll(); - const std::string complainNoCreatures; const std::string complainNotEnoughCreatures; const std::string complainInvalidSlot; diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 76030afd0..4239d07ba 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -3,6 +3,7 @@ set(server_SRCS CGameHandler.cpp HeroPoolProcessor.cpp + PlayerMessageProcessor.cpp ServerSpellCastEnvironment.cpp CQuery.cpp CVCMIServer.cpp @@ -15,6 +16,7 @@ set(server_HEADERS CGameHandler.h HeroPoolProcessor.h + PlayerMessageProcessor.h ServerSpellCastEnvironment.h CQuery.h CVCMIServer.h diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 892869efd..d8ce1fc34 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -39,6 +39,7 @@ #include "../lib/VCMI_Lib.h" #include "../lib/VCMIDirs.h" #include "CGameHandler.h" +#include "PlayerMessageProcessor.h" #include "../lib/mapping/CMapInfo.h" #include "../lib/GameConstants.h" #include "../lib/logging/CBasicLogConfigurator.h" @@ -605,7 +606,7 @@ void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c) if(gh && si && state == EServerState::GAMEPLAY) { - gh->playerMessage(playerSettings->color, playerLeftMsgText, ObjectInstanceID{}); + gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText); gh->connections[playerSettings->color].insert(hostClient); startAiPack.players.push_back(playerSettings->color); } @@ -633,7 +634,7 @@ void CVCMIServer::reconnectPlayer(int connId) continue; std::string messageText = boost::str(boost::format("%s (cid %d) is connected") % playerSettings->name % connId); - gh->playerMessage(playerSettings->color, messageText, ObjectInstanceID{}); + gh->playerMessages->broadcastMessage(playerSettings->color, messageText); startAiPack.players.push_back(playerSettings->color); } diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index dc3fd7ef1..d6812f1ef 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -12,6 +12,7 @@ #include "CGameHandler.h" #include "HeroPoolProcessor.h" +#include "PlayerMessageProcessor.h" #include "../lib/IGameCallback.h" #include "../lib/mapObjects/CGTownInstance.h" @@ -352,6 +353,6 @@ void ApplyGhNetPackVisitor::visitPlayerMessage(PlayerMessage & pack) if(!pack.player.isSpectator()) // TODO: clearly not a great way to verify permissions gh.throwOnWrongPlayer(&pack, pack.player); - gh.playerMessage(pack.player, pack.text, pack.currObj); + gh.playerMessages->playerMessage(pack.player, pack.text, pack.currObj); result = true; } diff --git a/server/PlayerMessageProcessor.cpp b/server/PlayerMessageProcessor.cpp new file mode 100644 index 000000000..0a161d64f --- /dev/null +++ b/server/PlayerMessageProcessor.cpp @@ -0,0 +1,508 @@ +/* + * CGameHandler.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 "PlayerMessageProcessor.h" + +#include "CGameHandler.h" +#include "CVCMIServer.h" + +#include "../lib/serializer/Connection.h" +#include "../lib/CGeneralTextHandler.h" +#include "../lib/CHeroHandler.h" +#include "../lib/CModHandler.h" +#include "../lib/CPlayerState.h" +#include "../lib/GameConstants.h" +#include "../lib/NetPacks.h" +#include "../lib/StartInfo.h" +#include "../lib/gameState/CGameState.h" +#include "../lib/mapObjects/CGTownInstance.h" + +PlayerMessageProcessor::PlayerMessageProcessor() + :gameHandler(nullptr) +{ +} + +PlayerMessageProcessor::PlayerMessageProcessor(CGameHandler * gameHandler) + :gameHandler(gameHandler) +{ +} + +void PlayerMessageProcessor::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj) +{ + if (handleHostCommand(player, message)) + return; + + if (handleCheatCode(message, player, currObj)) + { + if(!gameHandler->getPlayerSettings(player)->isControlledByAI()) + broadcastSystemMessage(VLC->generaltexth->allTexts[260]); + + if(!player.isSpectator()) + gameHandler->checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature + + return; + } + + broadcastMessage(player, message); +} + +bool PlayerMessageProcessor::handleHostCommand(PlayerColor player, const std::string &message) +{ + std::vector<std::string> words; + boost::split(words, message, boost::is_any_of(" ")); + + bool isHost = false; + for(auto & c : gameHandler->connections[player]) + if(gameHandler->gameLobby()->isClientHost(c->connectionID)) + isHost = true; + + if(!isHost || words.size() < 2 || words[0] != "game") + return false; + + if(words[1] == "exit" || words[1] == "quit" || words[1] == "end") + { + broadcastSystemMessage("game was terminated"); + gameHandler->gameLobby()->state = EServerState::SHUTDOWN; + + return true; + } + if(words.size() == 3 && words[1] == "save") + { + gameHandler->save("Saves/" + words[2]); + broadcastSystemMessage("game saved as " + words[2]); + + return true; + } + if(words.size() == 3 && words[1] == "kick") + { + auto playername = words[2]; + PlayerColor playerToKick(PlayerColor::CANNOT_DETERMINE); + if(std::all_of(playername.begin(), playername.end(), ::isdigit)) + playerToKick = PlayerColor(std::stoi(playername)); + else + { + for(auto & c : gameHandler->connections) + { + if(c.first.getStr(false) == playername) + playerToKick = c.first; + } + } + + if(playerToKick != PlayerColor::CANNOT_DETERMINE) + { + PlayerCheated pc; + pc.player = playerToKick; + pc.losingCheatCode = true; + gameHandler->sendAndApply(&pc); + gameHandler->checkVictoryLossConditionsForPlayer(playerToKick); + } + return true; + } + + return false; +} + +void PlayerMessageProcessor::cheatGiveSpells(PlayerColor player, const CGHeroInstance * hero) +{ + if (!hero) + return; + + ///Give hero spellbook + if (!hero->hasSpellbook()) + gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); + + ///Give all spells with bonus (to allow banned spells) + GiveBonus giveBonus(GiveBonus::ETarget::HERO); + giveBonus.id = hero->id.getNum(); + giveBonus.bonus = Bonus(BonusDuration::PERMANENT, BonusType::SPELLS_OF_LEVEL, BonusSource::OTHER, 0, 0); + //start with level 0 to skip abilities + for (int level = 1; level <= GameConstants::SPELL_LEVELS; level++) + { + giveBonus.bonus.subtype = level; + gameHandler->sendAndApply(&giveBonus); + } + + ///Give mana + SetMana sm; + sm.hid = hero->id; + sm.val = 999; + sm.absolute = true; + gameHandler->sendAndApply(&sm); +} + +void PlayerMessageProcessor::cheatBuildTown(PlayerColor player, const CGTownInstance * town) +{ + if (!town) + return; + + for (auto & build : town->town->buildings) + { + if (!town->hasBuilt(build.first) + && !build.second->getNameTranslated().empty() + && build.first != BuildingID::SHIP) + { + gameHandler->buildStructure(town->id, build.first, true); + } + } +} + +void PlayerMessageProcessor::cheatGiveArmy(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words) +{ + if (!hero) + return; + + std::string creatureIdentifier = words.empty() ? "archangel" : words[0]; + std::optional<int> amountPerSlot; + + try + { + amountPerSlot = std::stol(words.at(1)); + } + catch(std::exception&) + { + } + + std::optional<int32_t> creatureId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", creatureIdentifier, false); + + if(creatureId.has_value()) + { + const auto * creature = CreatureID(creatureId.value()).toCreature(); + + for (int i = 0; i < GameConstants::ARMY_SIZE; i++) + { + if (!hero->hasStackAtSlot(SlotID(i))) + { + if (amountPerSlot.has_value()) + gameHandler->insertNewStack(StackLocation(hero, SlotID(i)), creature, *amountPerSlot); + else + gameHandler->insertNewStack(StackLocation(hero, SlotID(i)), creature, 5 * std::pow(10, i)); + } + } + } +} + +void PlayerMessageProcessor::cheatGiveMachines(PlayerColor player, const CGHeroInstance * hero) +{ + if (!hero) + return; + + if (!hero->getArt(ArtifactPosition::MACH1)) + gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::BALLISTA], ArtifactPosition::MACH1); + if (!hero->getArt(ArtifactPosition::MACH2)) + gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::AMMO_CART], ArtifactPosition::MACH2); + if (!hero->getArt(ArtifactPosition::MACH3)) + gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3); +} + +void PlayerMessageProcessor::cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero) +{ + if (!hero) + return; + + for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods + { + if(VLC->arth->objects[g]->canBePutAt(hero)) + gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE); + } +} + +void PlayerMessageProcessor::cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words) +{ + if (!hero) + return; + + int levelsToGain; + try + { + levelsToGain = std::stol(words.at(0)); + } + catch(std::exception&) + { + levelsToGain = 1; + } + + gameHandler->changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level + levelsToGain) - VLC->heroh->reqExp(hero->level)); +} + +void PlayerMessageProcessor::cheatExperience(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words) +{ + if (!hero) + return; + + int expAmountProcessed; + + try + { + expAmountProcessed = std::stol(words.at(0)); + } + catch(std::exception&) + { + expAmountProcessed = 10000; + } + + gameHandler->changePrimSkill(hero, PrimarySkill::EXPERIENCE, expAmountProcessed); +} + +void PlayerMessageProcessor::cheatMovement(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words) +{ + if (!hero) + return; + + SetMovePoints smp; + smp.hid = hero->id; + try + { + smp.val = std::stol(words.at(0));; + } + catch(std::exception&) + { + smp.val = 1000000; + } + + gameHandler->sendAndApply(&smp); + + GiveBonus gb(GiveBonus::ETarget::HERO); + gb.bonus.type = BonusType::FREE_SHIP_BOARDING; + gb.bonus.duration = BonusDuration::ONE_DAY; + gb.bonus.source = BonusSource::OTHER; + gb.id = hero->id.getNum(); + gameHandler->giveHeroBonus(&gb); +} + +void PlayerMessageProcessor::cheatResources(PlayerColor player, std::vector<std::string> words) +{ + int baseResourceAmount; + try + { + baseResourceAmount = std::stol(words.at(0));; + } + catch(std::exception&) + { + baseResourceAmount = 100; + } + + TResources resources; + resources[EGameResID::GOLD] = baseResourceAmount * 100; + for (GameResID i = EGameResID::WOOD; i < EGameResID::GOLD; ++i) + resources[i] = baseResourceAmount; + + gameHandler->giveResources(player, resources); +} + +void PlayerMessageProcessor::cheatVictory(PlayerColor player) +{ + PlayerCheated pc; + pc.player = player; + pc.winningCheatCode = true; + gameHandler->sendAndApply(&pc); +} + +void PlayerMessageProcessor::cheatDefeat(PlayerColor player) +{ + PlayerCheated pc; + pc.player = player; + pc.losingCheatCode = true; + gameHandler->sendAndApply(&pc); +} + +void PlayerMessageProcessor::cheatMapReveal(PlayerColor player, bool reveal) +{ + FoWChange fc; + fc.mode = reveal; + fc.player = player; + const auto & fowMap = gameHandler->gameState()->getPlayerTeam(player)->fogOfWarMap; + const auto & mapSize = gameHandler->gameState()->getMapSize(); + auto hlp_tab = new int3[mapSize.x * mapSize.y * mapSize.z]; + int lastUnc = 0; + + for(int z = 0; z < mapSize.z; z++) + for(int x = 0; x < mapSize.x; x++) + for(int y = 0; y < mapSize.y; y++) + if(!(*fowMap)[z][x][y] || !fc.mode) + hlp_tab[lastUnc++] = int3(x, y, z); + + fc.tiles.insert(hlp_tab, hlp_tab + lastUnc); + delete [] hlp_tab; + gameHandler->sendAndApply(&fc); +} + +bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerColor player, ObjectInstanceID currObj) +{ + std::vector<std::string> words; + boost::split(words, cheat, boost::is_any_of("\t\r\n ")); + + if (words.empty()) + return false; + + //Make cheat name case-insensitive, but keep words/parameters (e.g. creature name) as it + std::string cheatName = boost::to_lower_copy(words[0]); + words.erase(words.begin()); + + std::vector<std::string> townTargetedCheats = { "vcmiarmenelos", "vcmibuild", "nwczion" }; + std::vector<std::string> playerTargetedCheats = { + "vcmiformenos", "vcmiresources", "nwctheconstruct", + "vcmimelkor", "vcmilose", "nwcbluepill", + "vcmisilmaril", "vcmiwin", "nwcredpill", + "vcmieagles", "vcmimap", "nwcwhatisthematrix", + "vcmiungoliant", "vcmihidemap", "nwcignoranceisbliss" + }; + std::vector<std::string> heroTargetedCheats = { + "vcmiainur", "vcmiarchangel", "nwctrinity", + "vcmiangband", "vcmiblackknight", "nwcagents", + "vcmiglaurung", "vcmicrystal", "vcmiazure", + "vcmifaerie", "vcmiarmy", "vcminissi", + "vcmiistari", "vcmispells", "nwcthereisnospoon", + "vcminoldor", "vcmimachines", "nwclotsofguns", + "vcmiglorfindel", "vcmilevel", "nwcneo", + "vcminahar", "vcmimove", "nwcnebuchadnezzar", + "vcmiforgeofnoldorking", "vcmiartifacts", + "vcmiolorin", "vcmiexp", + }; + + if (!vstd::contains(townTargetedCheats, cheatName) && !vstd::contains(playerTargetedCheats, cheatName) && !vstd::contains(heroTargetedCheats, cheatName)) + return false; + + bool playerTargetedCheat = false; + + for (const auto & i : gameHandler->gameState()->players) + { + if (i.first == PlayerColor::NEUTRAL) + continue; + + if (words.front() == "ai" && i.second.human) + continue; + + if (words.front() != "all" && words.front() != i.first.getStr()) + continue; + + std::vector<std::string> parameters = words; + + playerTargetedCheat = true; + parameters.erase(parameters.begin()); + + if (vstd::contains(playerTargetedCheats, cheatName)) + executeCheatCode(cheatName, i.first, ObjectInstanceID::NONE, parameters); + + if (vstd::contains(townTargetedCheats, cheatName)) + for (const auto & t : i.second.towns) + executeCheatCode(cheatName, i.first, t->id, parameters); + + if (vstd::contains(heroTargetedCheats, cheatName)) + for (const auto & h : i.second.heroes) + executeCheatCode(cheatName, i.first, h->id, parameters); + } + + if (!playerTargetedCheat) + executeCheatCode(cheatName, player, currObj, words); + + return true; +} + +void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, PlayerColor player, ObjectInstanceID currObj, const std::vector<std::string> & words) +{ + const CGHeroInstance * hero = gameHandler->getHero(currObj); + const CGTownInstance * town = gameHandler->getTown(currObj); + if (!town && hero) + town = hero->visitedTown; + + const auto & doCheatGiveSpells = [&]() { cheatGiveSpells(player, hero); }; + const auto & doCheatBuildTown = [&]() { cheatBuildTown(player, town); }; + const auto & doCheatGiveArmyCustom = [&]() { cheatGiveArmy(player, hero, words); }; + const auto & doCheatGiveArmyFixed = [&](std::vector<std::string> customWords) { cheatGiveArmy(player, hero, customWords); }; + const auto & doCheatGiveMachines = [&]() { cheatGiveMachines(player, hero); }; + const auto & doCheatGiveArtifacts = [&]() { cheatGiveArtifacts(player, hero); }; + const auto & doCheatLevelup = [&]() { cheatLevelup(player, hero, words); }; + const auto & doCheatExperience = [&]() { cheatExperience(player, hero, words); }; + const auto & doCheatMovement = [&]() { cheatMovement(player, hero, words); }; + const auto & doCheatResources = [&]() { cheatResources(player, words); }; + const auto & doCheatVictory = [&]() { cheatVictory(player); }; + const auto & doCheatDefeat = [&]() { cheatDefeat(player); }; + const auto & doCheatMapReveal = [&]() { cheatMapReveal(player, true); }; + const auto & doCheatMapHide = [&]() { cheatMapReveal(player, false); }; + + // Unimplemented H3 cheats: + // nwcfollowthewhiterabbit - The currently selected hero permanently gains maximum luck. + // nwcmorpheus - The currently selected hero permanently gains maximum morale. + // nwcoracle - The puzzle map is permanently revealed. + // nwcphisherprice - Changes and brightens the game colors. + + std::map<std::string, std::function<void()>> callbacks = { + {"vcmiainur", [&] () {doCheatGiveArmyFixed({ "archangel", "5" });} }, + {"nwctrinity", [&] () {doCheatGiveArmyFixed({ "archangel", "5" });} }, + {"vcmiangband", [&] () {doCheatGiveArmyFixed({ "blackKnight", "10" });} }, + {"vcmiglaurung", [&] () {doCheatGiveArmyFixed({ "crystalDragon", "5000" });} }, + {"vcmiarchangel", [&] () {doCheatGiveArmyFixed({ "archangel", "5" });} }, + {"nwcagents", [&] () {doCheatGiveArmyFixed({ "blackKnight", "10" });} }, + {"vcmiblackknight", [&] () {doCheatGiveArmyFixed({ "blackKnight", "10" });} }, + {"vcmicrystal", [&] () {doCheatGiveArmyFixed({ "crystalDragon", "5000" });} }, + {"vcmiazure", [&] () {doCheatGiveArmyFixed({ "azureDragon", "5000" });} }, + {"vcmifaerie", [&] () {doCheatGiveArmyFixed({ "fairieDragon", "5000" });} }, + {"vcmiarmy", doCheatGiveArmyCustom }, + {"vcminissi", doCheatGiveArmyCustom }, + {"vcmiistari", doCheatGiveSpells }, + {"vcmispells", doCheatGiveSpells }, + {"nwcthereisnospoon", doCheatGiveSpells }, + {"vcmiarmenelos", doCheatBuildTown }, + {"vcmibuild", doCheatBuildTown }, + {"nwczion", doCheatBuildTown }, + {"vcminoldor", doCheatGiveMachines }, + {"vcmimachines", doCheatGiveMachines }, + {"nwclotsofguns", doCheatGiveMachines }, + {"vcmiforgeofnoldorking", doCheatGiveArtifacts }, + {"vcmiartifacts", doCheatGiveArtifacts }, + {"vcmiglorfindel", doCheatLevelup }, + {"vcmilevel", doCheatLevelup }, + {"nwcneo", doCheatLevelup }, + {"vcmiolorin", doCheatExperience }, + {"vcmiexp", doCheatExperience }, + {"vcminahar", doCheatMovement }, + {"vcmimove", doCheatMovement }, + {"nwcnebuchadnezzar", doCheatMovement }, + {"vcmiformenos", doCheatResources }, + {"vcmiresources", doCheatResources }, + {"nwctheconstruct", doCheatResources }, + {"nwcbluepill", doCheatDefeat }, + {"vcmimelkor", doCheatDefeat }, + {"vcmilose", doCheatDefeat }, + {"nwcredpill", doCheatVictory }, + {"vcmisilmaril", doCheatVictory }, + {"vcmiwin", doCheatVictory }, + {"nwcwhatisthematrix", doCheatMapReveal }, + {"vcmieagles", doCheatMapReveal }, + {"vcmimap", doCheatMapReveal }, + {"vcmiungoliant", doCheatMapHide }, + {"vcmihidemap", doCheatMapHide }, + {"nwcignoranceisbliss", doCheatMapHide }, + }; + + assert(callbacks.count(cheatName)); + if (callbacks.count(cheatName)) + callbacks.at(cheatName)(); +} + +void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message) +{ + SystemMessage sm; + sm.text = message; + connection->sendPack(&sm); +} + +void PlayerMessageProcessor::broadcastSystemMessage(const std::string & message) +{ + SystemMessage sm; + sm.text = message; + gameHandler->sendToAllClients(&sm); +} + +void PlayerMessageProcessor::broadcastMessage(PlayerColor playerSender, const std::string & message) +{ + PlayerMessageClient temp_message(playerSender, message); + gameHandler->sendAndApply(&temp_message); +} diff --git a/server/PlayerMessageProcessor.h b/server/PlayerMessageProcessor.h new file mode 100644 index 000000000..357bd675c --- /dev/null +++ b/server/PlayerMessageProcessor.h @@ -0,0 +1,63 @@ +/* + * CGameHandler.h, 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 + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN +class PlayerColor; +class ObjectInstanceID; +class CGHeroInstance; +class CGTownInstance; +class CConnection; +VCMI_LIB_NAMESPACE_END + +class CGameHandler; + +class PlayerMessageProcessor +{ + void executeCheatCode(const std::string & cheatName, PlayerColor player, ObjectInstanceID currObj, const std::vector<std::string> & arguments ); + bool handleCheatCode(const std::string & cheatFullCommand, PlayerColor player, ObjectInstanceID currObj); + bool handleHostCommand(PlayerColor player, const std::string & message); + + void cheatGiveSpells(PlayerColor player, const CGHeroInstance * hero); + void cheatBuildTown(PlayerColor player, const CGTownInstance * town); + void cheatGiveArmy(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words); + void cheatGiveMachines(PlayerColor player, const CGHeroInstance * hero); + void cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero); + void cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words); + void cheatExperience(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words); + void cheatMovement(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words); + void cheatResources(PlayerColor player, std::vector<std::string> words); + void cheatVictory(PlayerColor player); + void cheatDefeat(PlayerColor player); + void cheatMapReveal(PlayerColor player, bool reveal); + +public: + CGameHandler * gameHandler; + + PlayerMessageProcessor(); + PlayerMessageProcessor(CGameHandler * gameHandler); + + /// incoming NetPack handling + void playerMessage(PlayerColor player, const std::string & message, ObjectInstanceID currObj); + + /// Send message to specific client with "System" as sender + void sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message); + + /// Send message to all players with "System" as sender + void broadcastSystemMessage(const std::string & message); + + /// Send message from specific player to all other players + void broadcastMessage(PlayerColor playerSender, const std::string & message); + + template <typename Handler> void serialize(Handler &h, const int version) + { + + } +};