/* * ClientCommandManager.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 "ClientCommandManager.h" #include "Client.h" #include "adventureMap/CInGameConsole.h" #include "CPlayerInterface.h" #include "PlayerLocalState.h" #include "CServerHandler.h" #include "gui/CGuiHandler.h" #include "gui/WindowHandler.h" #include "render/IRenderHandler.h" #include "ClientNetPackVisitors.h" #include "../lib/CConfigHandler.h" #include "../lib/gameState/CGameState.h" #include "../lib/CPlayerState.h" #include "../lib/constants/StringConstants.h" #include "../lib/campaign/CampaignHandler.h" #include "../lib/mapping/CMapService.h" #include "../lib/mapping/CMap.h" #include "windows/CCastleInterface.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "render/CAnimation.h" #include "../CCallback.h" #include "../lib/texts/CGeneralTextHandler.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/modding/CModHandler.h" #include "../lib/modding/ContentTypeHandler.h" #include "../lib/modding/ModUtility.h" #include "../lib/CHeroHandler.h" #include "../lib/VCMIDirs.h" #include "../lib/logging/VisualLogger.h" #include "CMT.h" #include "../lib/serializer/Connection.h" #ifdef SCRIPTING_ENABLED #include "../lib/ScriptHandler.h" #endif void ClientCommandManager::handleQuitCommand() { exit(EXIT_SUCCESS); } void ClientCommandManager::handleSaveCommand(std::istringstream & singleWordBuffer) { if(!CSH->client) { printCommandMessage("Game is not in playing state"); return; } std::string saveFilename; singleWordBuffer >> saveFilename; CSH->client->save(saveFilename); printCommandMessage("Game saved as: " + saveFilename); } void ClientCommandManager::handleLoadCommand(std::istringstream& singleWordBuffer) { // TODO: this code should end the running game and manage to call startGame instead //std::string fname; //singleWordBuffer >> fname; //CSH->client->loadGame(fname); } void ClientCommandManager::handleGoSoloCommand() { Settings session = settings.write["session"]; boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); if(!CSH->client) { printCommandMessage("Game is not in playing state"); return; } if(session["aiSolo"].Bool()) { // unlikely it will work but just in case to be consistent for(auto & color : CSH->getAllClientPlayers(CSH->logicConnection->connectionID)) { if(color.isValidPlayer() && CSH->client->getStartInfo()->playerInfos.at(color).isControlledByHuman()) { CSH->client->installNewPlayerInterface(std::make_shared(color), color); } } } else { PlayerColor currentColor = LOCPLINT->playerID; CSH->client->removeGUI(); for(auto & color : CSH->getAllClientPlayers(CSH->logicConnection->connectionID)) { if(color.isValidPlayer() && CSH->client->getStartInfo()->playerInfos.at(color).isControlledByHuman()) { auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(color), false, false); printCommandMessage("Player " + color.toString() + " will be lead by " + AiToGive, ELogLevel::INFO); CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color); } } GH.windows().totalRedraw(); giveTurn(currentColor); } session["aiSolo"].Bool() = !session["aiSolo"].Bool(); } void ClientCommandManager::handleAutoskipCommand() { Settings session = settings.write["session"]; session["autoSkip"].Bool() = !session["autoSkip"].Bool(); } void ClientCommandManager::handleControlaiCommand(std::istringstream& singleWordBuffer) { std::string colorName; singleWordBuffer >> colorName; boost::to_lower(colorName); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); if(!CSH->client) { printCommandMessage("Game is not in playing state"); return; } PlayerColor color; if(LOCPLINT) color = LOCPLINT->playerID; for(auto & elem : CSH->client->gameState()->players) { if(!elem.first.isValidPlayer() || elem.second.human || (colorName.length() && elem.first.getNum() != vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, colorName))) { continue; } CSH->client->removeGUI(); CSH->client->installNewPlayerInterface(std::make_shared(elem.first), elem.first); } GH.windows().totalRedraw(); if(color != PlayerColor::NEUTRAL) giveTurn(color); } void ClientCommandManager::handleSetBattleAICommand(std::istringstream& singleWordBuffer) { std::string aiName; singleWordBuffer >> aiName; printCommandMessage("Will try loading that AI to see if it is correct name...\n"); try { if(auto ai = CDynLibHandler::getNewBattleAI(aiName)) //test that given AI is indeed available... heavy but it is easy to make a typo and break the game { Settings neutralAI = settings.write["server"]["neutralAI"]; neutralAI->String() = aiName; printCommandMessage("Setting changed, from now the battle ai will be " + aiName + "!\n"); } } catch(std::exception &e) { printCommandMessage("Failed opening " + aiName + ": " + e.what(), ELogLevel::WARN); printCommandMessage("Setting not changed, AI not found or invalid!", ELogLevel::WARN); } } void ClientCommandManager::handleRedrawCommand() { GH.windows().totalRedraw(); } void ClientCommandManager::handleTranslateGameCommand() { std::map> textsByMod; VLC->generaltexth->exportAllTexts(textsByMod); const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "translation"; boost::filesystem::create_directories(outPath); for(const auto & modEntry : textsByMod) { JsonNode output; for(const auto & stringEntry : modEntry.second) { if(boost::algorithm::starts_with(stringEntry.first, "map.")) continue; if(boost::algorithm::starts_with(stringEntry.first, "campaign.")) continue; output[stringEntry.first].String() = stringEntry.second; } if (!output.isNull()) { const boost::filesystem::path filePath = outPath / (modEntry.first + ".json"); std::ofstream file(filePath.c_str()); file << output.toString(); } } printCommandMessage("Translation export complete"); } void ClientCommandManager::handleTranslateMapsCommand() { CMapService mapService; printCommandMessage("Searching for available maps"); std::unordered_set mapList = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident) { return ident.getType() == EResType::MAP; }); std::vector> loadedMaps; std::vector> loadedCampaigns; printCommandMessage("Loading maps for export"); for (auto const & mapName : mapList) { try { // load and drop loaded map - we only need loader to run over all maps loadedMaps.push_back(mapService.loadMap(mapName, nullptr)); } catch(std::exception & e) { logGlobal->warn("Map %s is invalid. Message: %s", mapName.getName(), e.what()); } } printCommandMessage("Searching for available campaigns"); std::unordered_set campaignList = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident) { return ident.getType() == EResType::CAMPAIGN; }); logGlobal->info("Loading campaigns for export"); for (auto const & campaignName : campaignList) { loadedCampaigns.push_back(CampaignHandler::getCampaign(campaignName.getName())); for (auto const & part : loadedCampaigns.back()->allScenarios()) loadedCampaigns.back()->getMap(part, nullptr); } std::map> textsByMod; VLC->generaltexth->exportAllTexts(textsByMod); const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "translation"; boost::filesystem::create_directories(outPath); for(const auto & modEntry : textsByMod) { JsonNode output; for(const auto & stringEntry : modEntry.second) { if(boost::algorithm::starts_with(stringEntry.first, "map.")) output[stringEntry.first].String() = stringEntry.second; if(boost::algorithm::starts_with(stringEntry.first, "campaign.")) output[stringEntry.first].String() = stringEntry.second; } if (!output.isNull()) { const boost::filesystem::path filePath = outPath / (modEntry.first + ".json"); std::ofstream file(filePath.c_str()); file << output.toString(); } } printCommandMessage("Translation export complete"); } void ClientCommandManager::handleGetConfigCommand() { printCommandMessage("Command accepted.\t"); const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "configuration"; boost::filesystem::create_directories(outPath); const std::vector contentNames = { "heroClasses", "artifacts", "creatures", "factions", "objects", "heroes", "spells", "skills" }; for(auto contentName : contentNames) { auto const & handler = *VLC->modh->content; auto const & content = handler[contentName]; auto contentOutPath = outPath / contentName; boost::filesystem::create_directories(contentOutPath); for(auto& iter : content.modData) { const JsonNode& modData = iter.second.modData; for(auto& nameAndObject : modData.Struct()) { const JsonNode& object = nameAndObject.second; std::string name = ModUtility::makeFullIdentifier(object.getModScope(), contentName, nameAndObject.first); boost::algorithm::replace_all(name, ":", "_"); const boost::filesystem::path filePath = contentOutPath / (name + ".json"); std::ofstream file(filePath.c_str()); file << object.toString(); } } } printCommandMessage("\rExtracting done :)\n"); printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n"); } void ClientCommandManager::handleGetScriptsCommand() { #if SCRIPTING_ENABLED printCommandMessage("Command accepted.\t"); const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "scripts"; boost::filesystem::create_directories(outPath); for(const auto & kv : VLC->scriptHandler->objects) { std::string name = kv.first; boost::algorithm::replace_all(name,":","_"); const scripting::ScriptImpl * script = kv.second.get(); boost::filesystem::path filePath = outPath / (name + ".lua"); std::ofstream file(filePath.c_str()); file << script->getSource(); } printCommandMessage("\rExtracting done :)\n"); printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n"); #endif } void ClientCommandManager::handleGetTextCommand() { printCommandMessage("Command accepted.\t"); const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath(); auto list = CResourceHandler::get()->getFilteredFiles([](const ResourcePath & ident) { return ident.getType() == EResType::TEXT && boost::algorithm::starts_with(ident.getName(), "DATA/"); }); for (auto & filename : list) { const boost::filesystem::path filePath = outPath / (filename.getName() + ".TXT"); boost::filesystem::create_directories(filePath.parent_path()); std::ofstream file(filePath.c_str()); auto text = CResourceHandler::get()->load(filename)->readAll(); file.write((char*)text.first.get(), text.second); } printCommandMessage("\rExtracting done :)\n"); printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n"); } void ClientCommandManager::handleDef2bmpCommand(std::istringstream& singleWordBuffer) { std::string URI; singleWordBuffer >> URI; auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI), EImageBlitMode::ALPHA); anim->exportBitmaps(VCMIDirs::get().userExtractedPath()); } void ClientCommandManager::handleExtractCommand(std::istringstream& singleWordBuffer) { std::string URI; singleWordBuffer >> URI; if(CResourceHandler::get()->existsResource(ResourcePath(URI))) { const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / URI; auto data = CResourceHandler::get()->load(ResourcePath(URI))->readAll(); boost::filesystem::create_directories(outPath.parent_path()); std::ofstream outFile(outPath.c_str(), std::ofstream::binary); outFile.write((char*)data.first.get(), data.second); } else printCommandMessage("File not found!", ELogLevel::ERROR); } void ClientCommandManager::handleBonusesCommand(std::istringstream & singleWordBuffer) { if(currentCallFromIngameConsole) { printCommandMessage("Output for this command is too large for ingame chat! Please run it from client console.\n"); return; } std::string outputFormat; singleWordBuffer >> outputFormat; auto format = [outputFormat](const BonusList & b) -> std::string { if(outputFormat == "json") return b.toJsonNode().toCompactString(); std::ostringstream ss; ss << b; return ss.str(); }; printCommandMessage("Bonuses of " + LOCPLINT->localState->getCurrentArmy()->getObjectName() + "\n"); printCommandMessage(format(*LOCPLINT->localState->getCurrentArmy()->getAllBonuses(Selector::all, Selector::all)) + "\n"); printCommandMessage("\nInherited bonuses:\n"); TCNodes parents; LOCPLINT->localState->getCurrentArmy()->getParents(parents); for(const CBonusSystemNode *parent : parents) { printCommandMessage(std::string("\nBonuses from ") + typeid(*parent).name() + "\n" + format(*parent->getAllBonuses(Selector::all, Selector::all)) + "\n"); } } void ClientCommandManager::handleTellCommand(std::istringstream& singleWordBuffer) { std::string what; int id1; int id2; singleWordBuffer >> what >> id1 >> id2; if(what == "hs") { for(const CGHeroInstance* h : LOCPLINT->cb->getHeroesInfo()) if(h->type->getIndex() == id1) if(const CArtifactInstance* a = h->getArt(ArtifactPosition(id2))) printCommandMessage(a->nodeName()); } } void ClientCommandManager::handleMpCommand() { if(const CGHeroInstance* h = LOCPLINT->localState->getCurrentHero()) printCommandMessage(std::to_string(h->movementPointsRemaining()) + "; max: " + std::to_string(h->movementPointsLimit(true)) + "/" + std::to_string(h->movementPointsLimit(false)) + "\n"); } void ClientCommandManager::handleSetCommand(std::istringstream& singleWordBuffer) { std::string what; std::string value; singleWordBuffer >> what; Settings config = settings.write["session"][what]; singleWordBuffer >> value; if(value == "on") { config->Bool() = true; printCommandMessage("Option " + what + " enabled!", ELogLevel::INFO); } else if(value == "off") { config->Bool() = false; printCommandMessage("Option " + what + " disabled!", ELogLevel::INFO); } } void ClientCommandManager::handleCrashCommand() { int* ptr = nullptr; *ptr = 666; //disaster! } void ClientCommandManager::handleVsLog(std::istringstream & singleWordBuffer) { std::string key; singleWordBuffer >> key; logVisual->setKey(key); } void ClientCommandManager::printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType) { switch(messageType) { case ELogLevel::NOT_SET: std::cout << commandMessage; break; case ELogLevel::TRACE: logGlobal->trace(commandMessage); break; case ELogLevel::DEBUG: logGlobal->debug(commandMessage); break; case ELogLevel::INFO: logGlobal->info(commandMessage); break; case ELogLevel::WARN: logGlobal->warn(commandMessage); break; case ELogLevel::ERROR: logGlobal->error(commandMessage); break; default: std::cout << commandMessage; break; } if(currentCallFromIngameConsole) { boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); if(LOCPLINT && LOCPLINT->cingconsole) { LOCPLINT->cingconsole->addMessage("", "System", commandMessage); } } } void ClientCommandManager::giveTurn(const PlayerColor &colorIdentifier) { PlayerStartsTurn yt; yt.player = colorIdentifier; yt.queryID = QueryID::NONE; ApplyClientNetPackVisitor visitor(*CSH->client, *CSH->client->gameState()); yt.visit(visitor); } void ClientCommandManager::processCommand(const std::string & message, bool calledFromIngameConsole) { // split the message into individual words std::istringstream singleWordBuffer; singleWordBuffer.str(message); // get command name, to be used for single word commands std::string commandName; singleWordBuffer >> commandName; currentCallFromIngameConsole = calledFromIngameConsole; if(message == std::string("die, fool")) handleQuitCommand(); else if(commandName == "save") handleSaveCommand(singleWordBuffer); else if(commandName=="load") handleLoadCommand(singleWordBuffer); // not implemented else if(commandName == "gosolo") handleGoSoloCommand(); else if(commandName == "autoskip") handleAutoskipCommand(); else if(commandName == "controlai") handleControlaiCommand(singleWordBuffer); else if(commandName == "setBattleAI") handleSetBattleAICommand(singleWordBuffer); else if(commandName == "redraw") handleRedrawCommand(); else if(message=="translate" || message=="translate game") handleTranslateGameCommand(); else if(message=="translate maps") handleTranslateMapsCommand(); else if(message=="get config") handleGetConfigCommand(); else if(message=="get scripts") handleGetScriptsCommand(); else if(message=="get txt") handleGetTextCommand(); else if(commandName == "def2bmp") handleDef2bmpCommand(singleWordBuffer); else if(commandName == "extract") handleExtractCommand(singleWordBuffer); else if(commandName == "bonuses") handleBonusesCommand(singleWordBuffer); else if(commandName == "tell") handleTellCommand(singleWordBuffer); else if(commandName == "mp" && LOCPLINT) handleMpCommand(); else if (commandName == "set") handleSetCommand(singleWordBuffer); else if(commandName == "crash") handleCrashCommand(); else if(commandName == "vslog") handleVsLog(singleWordBuffer); else { if (!commandName.empty() && !vstd::iswithin(commandName[0], 0, ' ')) // filter-out debugger/IDE noise printCommandMessage("Command not found :(", ELogLevel::ERROR); } }