1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-14 10:12:59 +02:00
vcmi/client/ClientCommandManager.cpp
Ivan Savenko 3dd4fa2528 Reduce usage of pointers to VLC entities
Final goal (of multiple PR's) is to remove all remaining pointers from
serializeable game state, and replace them with either identifiers or
with shared/unique pointers.

CGTownInstance::town and CGHeroInstance::type members have been removed.
Now this data is computed dynamically using subID member.

VLC entity of a town can now be accessed via following methods:
- getFactionID() returns ID of a faction
- getFaction() returns pointer to a faction
- getTown() returns pointer to a town

VLC entity of a hero can now be accessed via following methods:
- getHeroTypeID() returns ID of a hero
- getHeroClassID() returns ID of a hero class
- getHeroType() returns pointer to a hero
- getHeroClass() returns pointer to a hero class
2024-10-10 12:28:08 +00:00

642 lines
18 KiB
C++

/*
* 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 "render/AssetGenerator.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 "../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<CPlayerInterface>(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<CPlayerInterface>(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<std::string, std::map<std::string, std::string>> 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<ResourcePath> mapList = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident)
{
return ident.getType() == EResType::MAP;
});
std::vector<std::unique_ptr<CMap>> loadedMaps;
std::vector<std::shared_ptr<CampaignState>> 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<ResourcePath> 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<std::string, std::map<std::string, std::string>> 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<std::string> 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->getHeroTypeID().getNum() == 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::handleGenerateAssets()
{
AssetGenerator::generateAll();
printCommandMessage("All assets generated");
}
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(message=="generate assets")
handleGenerateAssets();
else
{
if (!commandName.empty() && !vstd::iswithin(commandName[0], 0, ' ')) // filter-out debugger/IDE noise
printCommandMessage("Command not found :(", ELogLevel::ERROR);
}
}