1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-26 03:52:01 +02:00

Merge pull request #1381 from dydzio0614/client-commands-improvements

Client commands code improvements + ingame support
This commit is contained in:
Nordsoft91 2023-01-18 02:04:38 +04:00 committed by GitHub
commit 672bcb488b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 578 additions and 453 deletions

View File

@ -13,8 +13,6 @@
#include <boost/program_options.hpp>
#include <vcmi/scripting/Service.h>
#include "gui/SDL_Extensions.h"
#include "CGameInfo.h"
#include "mapHandler.h"
@ -33,32 +31,26 @@
#include "../lib/CBuildingHandler.h"
#include "CVideoHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CCreatureHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "CMusicHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "Graphics.h"
#include "Client.h"
#include "../lib/CConfigHandler.h"
#include "../lib/serializer/BinaryDeserializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/VCMIDirs.h"
#include "../lib/NetPacks.h"
#include "CMessage.h"
#include "../lib/CModHandler.h"
#include "../lib/ScriptHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/CArtHandler.h"
#include "../lib/GameConstants.h"
#include "gui/CGuiHandler.h"
#include "../lib/logging/CBasicLogConfigurator.h"
#include "../lib/StringConstants.h"
#include "../lib/CPlayerState.h"
#include "gui/CAnimation.h"
#include "../lib/serializer/Connection.h"
#include "CServerHandler.h"
#include "gui/NotificationHandler.h"
#include "ClientCommandManager.h"
#include <boost/asio.hpp>
@ -71,7 +63,7 @@
#ifdef VCMI_ANDROID
#include "lib/CAndroidVMHelper.h"
#endif
#include "../lib/UnlockGuard.h"
#include "CMT.h"
#if __MINGW32__
@ -249,7 +241,14 @@ int main(int argc, char * argv[])
std::cout.flags(std::ios::unitbuf);
#ifndef VCMI_IOS
console = new CConsoleHandler();
*console->cb = processCommand;
auto callbackFunction = [](std::string buffer, bool calledFromIngameConsole)
{
ClientCommandManager commandController;
commandController.processCommand(buffer, calledFromIngameConsole);
};
*console->cb = callbackFunction;
console->start();
#endif
@ -566,437 +565,6 @@ int main(int argc, char * argv[])
return 0;
}
void printInfoAboutIntObject(const CIntObject *obj, int level)
{
std::stringstream sbuffer;
sbuffer << std::string(level, '\t');
sbuffer << typeid(*obj).name() << " *** ";
if (obj->active)
{
#define PRINT(check, text) if (obj->active & CIntObject::check) sbuffer << text
PRINT(LCLICK, 'L');
PRINT(RCLICK, 'R');
PRINT(HOVER, 'H');
PRINT(MOVE, 'M');
PRINT(KEYBOARD, 'K');
PRINT(TIME, 'T');
PRINT(GENERAL, 'A');
PRINT(WHEEL, 'W');
PRINT(DOUBLECLICK, 'D');
#undef PRINT
}
else
sbuffer << "inactive";
sbuffer << " at " << obj->pos.x <<"x"<< obj->pos.y;
sbuffer << " (" << obj->pos.w <<"x"<< obj->pos.h << ")";
logGlobal->info(sbuffer.str());
for(const CIntObject *child : obj->children)
printInfoAboutIntObject(child, level+1);
}
void removeGUI()
{
// CClient::endGame
GH.curInt = nullptr;
if(GH.topInt())
GH.topInt()->deactivate();
adventureInt = nullptr;
GH.listInt.clear();
GH.objsToBlit.clear();
GH.statusbar = nullptr;
logGlobal->info("Removed GUI.");
LOCPLINT = nullptr;
}
#ifndef VCMI_IOS
void processCommand(const std::string &message)
{
std::istringstream readed;
readed.str(message);
std::string cn; //command name
readed >> cn;
// Check mantis issue 2292 for details
// if(LOCPLINT && LOCPLINT->cingconsole)
// LOCPLINT->cingconsole->print(message);
if(message==std::string("die, fool"))
{
exit(EXIT_SUCCESS);
}
else if(cn==std::string("activate"))
{
int what;
readed >> what;
switch (what)
{
case 0:
GH.topInt()->activate();
break;
case 1:
adventureInt->activate();
break;
case 2:
LOCPLINT->castleInt->activate();
break;
}
}
else if(cn=="redraw")
{
GH.totalRedraw();
}
else if(cn=="screen")
{
std::cout << "Screenbuf points to ";
if(screenBuf == screen)
logGlobal->error("screen");
else if(screenBuf == screen2)
logGlobal->error("screen2");
else
logGlobal->error("?!?");
SDL_SaveBMP(screen, "Screen_c.bmp");
SDL_SaveBMP(screen2, "Screen2_c.bmp");
}
else if(cn=="save")
{
if(!CSH->client)
{
std::cout << "Game in not active";
return;
}
std::string fname;
readed >> fname;
CSH->client->save(fname);
}
// else if(cn=="load")
// {
// // TODO: this code should end the running game and manage to call startGame instead
// std::string fname;
// readed >> fname;
// CSH->client->loadGame(fname);
// }
else if(message=="convert txt")
{
VLC->generaltexth->dumpAllTexts();
}
else if(message=="get config")
{
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userExtractedPath() / "configuration";
bfs::create_directories(outPath);
const std::vector<std::string> contentNames = {"heroClasses", "artifacts", "creatures", "factions", "objects", "heroes", "spells", "skills"};
for(auto contentName : contentNames)
{
auto & content = (*VLC->modh->content)[contentName];
auto contentOutPath = outPath / contentName;
bfs::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 = CModHandler::normalizeIdentifier(object.meta, CModHandler::scopeBuiltin(), nameAndObject.first);
boost::algorithm::replace_all(name,":","_");
const bfs::path filePath = contentOutPath / (name + ".json");
bfs::ofstream file(filePath);
file << object.toJson();
}
}
}
std::cout << "\rExtracting done :)\n";
std::cout << " Extracted files can be found in " << outPath << " directory\n";
}
#if SCRIPTING_ENABLED
else if(message=="get scripts")
{
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userExtractedPath() / "scripts";
bfs::create_directories(outPath);
for(auto & kv : VLC->scriptHandler->objects)
{
std::string name = kv.first;
boost::algorithm::replace_all(name,":","_");
const scripting::ScriptImpl * script = kv.second.get();
bfs::path filePath = outPath / (name + ".lua");
bfs::ofstream file(filePath);
file << script->getSource();
}
std::cout << "\rExtracting done :)\n";
std::cout << " Extracted files can be found in " << outPath << " directory\n";
}
#endif
else if(message=="get txt")
{
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userExtractedPath();
auto list = CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident)
{
return ident.getType() == EResType::TEXT && boost::algorithm::starts_with(ident.getName(), "DATA/");
});
for (auto & filename : list)
{
const bfs::path filePath = outPath / (filename.getName() + ".TXT");
bfs::create_directories(filePath.parent_path());
bfs::ofstream file(filePath);
auto text = CResourceHandler::get()->load(filename)->readAll();
file.write((char*)text.first.get(), text.second);
}
std::cout << "\rExtracting done :)\n";
std::cout << " Extracted files can be found in " << outPath << " directory\n";
}
else if(cn=="crash")
{
int *ptr = nullptr;
*ptr = 666;
//disaster!
}
else if(cn == "mp" && adventureInt)
{
if(const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(adventureInt->selection))
std::cout << h->movement << "; max: " << h->maxMovePoints(true) << "/" << h->maxMovePoints(false) << std::endl;
}
else if(cn == "bonuses")
{
bool jsonFormat = (message == "bonuses json");
auto format = [jsonFormat](const BonusList & b) -> std::string
{
if(jsonFormat)
return b.toJsonNode().toJson(true);
std::ostringstream ss;
ss << b;
return ss.str();
};
std::cout << "Bonuses of " << adventureInt->selection->getObjectName() << std::endl
<< format(adventureInt->selection->getBonusList()) << std::endl;
std::cout << "\nInherited bonuses:\n";
TCNodes parents;
adventureInt->selection->getParents(parents);
for(const CBonusSystemNode *parent : parents)
{
std::cout << "\nBonuses from " << typeid(*parent).name() << std::endl << format(*parent->getAllBonuses(Selector::all, Selector::all)) << std::endl;
}
}
else if(cn == "not dialog")
{
LOCPLINT->showingDialog->setn(false);
}
else if(cn == "gui")
{
for(auto & child : GH.listInt)
{
const auto childPtr = child.get();
if(const CIntObject * obj = dynamic_cast<const CIntObject *>(childPtr))
printInfoAboutIntObject(obj, 0);
else
std::cout << typeid(childPtr).name() << std::endl;
}
}
else if(cn=="tell")
{
std::string what;
int id1, id2;
readed >> what >> id1 >> id2;
if(what == "hs")
{
for(const CGHeroInstance *h : LOCPLINT->cb->getHeroesInfo())
if(h->type->ID.getNum() == id1)
if(const CArtifactInstance *a = h->getArt(ArtifactPosition(id2)))
std::cout << a->nodeName();
}
}
else if (cn == "set")
{
std::string what, value;
readed >> what;
Settings conf = settings.write["session"][what];
readed >> value;
if (value == "on")
{
conf->Bool() = true;
logGlobal->info("Option %s enabled!", what);
}
else if (value == "off")
{
conf->Bool() = false;
logGlobal->info("Option %s disabled!", what);
}
}
else if(cn == "unlock")
{
std::string mxname;
readed >> mxname;
if(mxname == "pim" && LOCPLINT)
LOCPLINT->pim->unlock();
}
else if(cn == "def2bmp")
{
std::string URI;
readed >> URI;
std::unique_ptr<CAnimation> anim = std::make_unique<CAnimation>(URI);
anim->preload();
anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
}
else if(cn == "extract")
{
std::string URI;
readed >> URI;
if (CResourceHandler::get()->existsResource(ResourceID(URI)))
{
const bfs::path outPath = VCMIDirs::get().userExtractedPath() / URI;
auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll();
bfs::create_directories(outPath.parent_path());
bfs::ofstream outFile(outPath, bfs::ofstream::binary);
outFile.write((char*)data.first.get(), data.second);
}
else
logGlobal->error("File not found!");
}
else if(cn == "setBattleAI")
{
std::string fname;
readed >> fname;
std::cout << "Will try loading that AI to see if it is correct name...\n";
try
{
if(auto ai = CDynLibHandler::getNewBattleAI(fname)) //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() = fname;
std::cout << "Setting changed, from now the battle ai will be " << fname << "!\n";
}
}
catch(std::exception &e)
{
logGlobal->warn("Failed opening %s: %s", fname, e.what());
logGlobal->warn("Setting not changes, AI not found or invalid!");
}
}
auto giveTurn = [&](PlayerColor player)
{
YourTurn yt;
yt.player = player;
yt.daysWithoutCastle = CSH->client->getPlayerState(player)->daysWithoutCastle;
yt.applyCl(CSH->client);
};
Settings session = settings.write["session"];
if(cn == "autoskip")
{
session["autoSkip"].Bool() = !session["autoSkip"].Bool();
}
else if(cn == "gosolo")
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
if(!CSH->client)
{
std::cout << "Game in not active";
return;
}
PlayerColor color;
if(session["aiSolo"].Bool())
{
for(auto & elem : CSH->client->gameState()->players)
{
if(elem.second.human)
CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
}
}
else
{
color = LOCPLINT->playerID;
removeGUI();
for(auto & elem : CSH->client->gameState()->players)
{
if(elem.second.human)
{
auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false);
logNetwork->info("Player %s will be lead by %s", elem.first, AiToGive);
CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
}
}
GH.totalRedraw();
giveTurn(color);
}
session["aiSolo"].Bool() = !session["aiSolo"].Bool();
}
else if(cn == "controlai")
{
std::string colorName;
readed >> colorName;
boost::to_lower(colorName);
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
if(!CSH->client)
{
std::cout << "Game in not active";
return;
}
PlayerColor color;
if(LOCPLINT)
color = LOCPLINT->playerID;
for(auto & elem : CSH->client->gameState()->players)
{
if(elem.second.human || (colorName.length() &&
elem.first.getNum() != vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, colorName)))
{
continue;
}
removeGUI();
CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
}
GH.totalRedraw();
if(color != PlayerColor::NEUTRAL)
giveTurn(color);
}
// Check mantis issue 2292 for details
/* else if(client && client->serv && client->serv->connected && LOCPLINT) //send to server
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
LOCPLINT->cb->sendMessage(message);
}*/
}
#endif
//plays intro, ends when intro is over or button has been pressed (handles events)
void playIntro()
{

View File

@ -19,5 +19,4 @@ extern SDL_Surface *screen; // main screen surface
extern SDL_Surface *screen2; // and hlp surface (used to store not-active interfaces layer)
extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
void removeGUI();
void handleQuit(bool ask = true);

View File

@ -82,6 +82,7 @@ set(client_SRCS
NetPacksClient.cpp
NetPacksLobbyClient.cpp
SDLRWwrapper.cpp
ClientCommandManager.cpp
)
set(client_HEADERS
@ -168,6 +169,7 @@ set(client_HEADERS
mapHandler.h
resource.h
SDLRWwrapper.h
ClientCommandManager.h
)
if(APPLE_IOS)

View File

@ -46,9 +46,9 @@
#include "../lib/CThreadHelper.h"
#include "../lib/registerTypes/RegisterTypes.h"
#include "gui/CGuiHandler.h"
#include "CMT.h"
#include "CServerHandler.h"
#include "../lib/ScriptHandler.h"
#include "windows/CAdvmapInterface.h"
#include <vcmi/events/EventBus.h>
#ifdef VCMI_ANDROID
@ -761,6 +761,21 @@ void CClient::reinitScripting()
#endif
}
void CClient::removeGUI()
{
// CClient::endGame
GH.curInt = nullptr;
if(GH.topInt())
GH.topInt()->deactivate();
adventureInt.reset();
GH.listInt.clear();
GH.objsToBlit.clear();
GH.statusbar.reset();
logGlobal->info("Removed GUI.");
LOCPLINT = nullptr;
}
#ifdef VCMI_ANDROID
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_clientSetupJNI(JNIEnv * env, jobject cls)
{

View File

@ -240,6 +240,7 @@ public:
void showInfoDialog(InfoWindow * iw) override {};
void showInfoDialog(const std::string & msg, PlayerColor player) override {};
void removeGUI();
#if SCRIPTING_ENABLED
scripting::Pool * getGlobalContextPool() const override;

View File

@ -0,0 +1,494 @@
/*
* 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 "CPlayerInterface.h"
#include "CServerHandler.h"
#include "gui/CGuiHandler.h"
#include "../lib/NetPacks.h"
#include "../lib/CConfigHandler.h"
#include "../lib/CGameState.h"
#include "../lib/CPlayerState.h"
#include "../lib/StringConstants.h"
#include "gui/CAnimation.h"
#include "windows/CAdvmapInterface.h"
#include "windows/CCastleInterface.h"
#include "../CCallback.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CModHandler.h"
#include "../lib/VCMIDirs.h"
#ifdef SCRIPTING_ENABLED
#include "../lib/ScriptHandler.h"
#endif
void ClientCommandManager::handleGoSolo()
{
Settings session = settings.write["session"];
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
if(!CSH->client)
{
printCommandMessage("Game is not in playing state");
return;
}
PlayerColor color;
if(session["aiSolo"].Bool())
{
for(auto & elem : CSH->client->gameState()->players)
{
if(elem.second.human)
CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
}
}
else
{
color = LOCPLINT->playerID;
CSH->client->removeGUI();
for(auto & elem : CSH->client->gameState()->players)
{
if(elem.second.human)
{
auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false);
printCommandMessage("Player " + elem.first.getStr() + " will be lead by " + AiToGive, ELogLevel::INFO);
CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
}
}
GH.totalRedraw();
giveTurn(color);
}
session["aiSolo"].Bool() = !session["aiSolo"].Bool();
}
void ClientCommandManager::handleControlAi(const std::string &colorName)
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
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.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.totalRedraw();
if(color != PlayerColor::NEUTRAL)
giveTurn(color);
}
void ClientCommandManager::processCommand(const std::string &message, bool calledFromIngameConsole)
{
std::istringstream singleWordBuffer;
singleWordBuffer.str(message);
std::string commandName;
singleWordBuffer >> commandName;
currentCallFromIngameConsole = calledFromIngameConsole;
if(message==std::string("die, fool"))
{
exit(EXIT_SUCCESS);
}
else if(commandName == std::string("activate"))
{
int what;
singleWordBuffer >> what;
switch (what)
{
case 0:
GH.topInt()->activate();
break;
case 1:
adventureInt->activate();
break;
case 2:
LOCPLINT->castleInt->activate();
break;
default:
printCommandMessage("Wrong argument specified!", ELogLevel::ERROR);
}
}
else if(commandName == "redraw")
{
GH.totalRedraw();
}
else if(commandName == "screen")
{
printCommandMessage("Screenbuf points to ");
if(screenBuf == screen)
printCommandMessage("screen", ELogLevel::ERROR);
else if(screenBuf == screen2)
printCommandMessage("screen2", ELogLevel::ERROR);
else
printCommandMessage("?!?", ELogLevel::ERROR);
SDL_SaveBMP(screen, "Screen_c.bmp");
SDL_SaveBMP(screen2, "Screen2_c.bmp");
}
else if(commandName == "save")
{
if(!CSH->client)
{
printCommandMessage("Game is not in playing state");
return;
}
std::string fname;
singleWordBuffer >> fname;
CSH->client->save(fname);
}
// else if(commandName=="load")
// {
// // TODO: this code should end the running game and manage to call startGame instead
// std::string fname;
// singleWordBuffer >> fname;
// CSH->client->loadGame(fname);
// }
else if(message=="convert txt")
{
VLC->generaltexth->dumpAllTexts();
}
else if(message=="get config")
{
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 & content = (*VLC->modh->content)[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 = CModHandler::normalizeIdentifier(object.meta, CModHandler::scopeBuiltin(), nameAndObject.first);
boost::algorithm::replace_all(name,":","_");
const boost::filesystem::path filePath = contentOutPath / (name + ".json");
boost::filesystem::ofstream file(filePath);
file << object.toJson();
}
}
}
printCommandMessage("\rExtracting done :)\n");
printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n");
}
#if SCRIPTING_ENABLED
else if(message=="get scripts")
{
printCommandMessage("Command accepted.\t");
const boost::filesystem::path outPath =
VCMIDirs::get().userExtractedPath() / "scripts";
boost::filesystem::create_directories(outPath);
for(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");
boost::filesystem::ofstream file(filePath);
file << script->getSource();
}
printCommandMessage("\rExtracting done :)\n");
printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n");
}
#endif
else if(message=="get txt")
{
printCommandMessage("Command accepted.\t");
const boost::filesystem::path outPath =
VCMIDirs::get().userExtractedPath();
auto list =
CResourceHandler::get()->getFilteredFiles([](const ResourceID & 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());
boost::filesystem::ofstream file(filePath);
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");
}
else if(commandName == "crash")
{
int *ptr = nullptr;
*ptr = 666;
//disaster!
}
else if(commandName == "mp" && adventureInt)
{
if(const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(adventureInt->selection))
printCommandMessage(std::to_string(h->movement) + "; max: " + std::to_string(h->maxMovePoints(true)) + "/" + std::to_string(h->maxMovePoints(false)) + "\n");
}
else if(commandName == "bonuses")
{
bool jsonFormat = (message == "bonuses json");
auto format = [jsonFormat](const BonusList & b) -> std::string
{
if(jsonFormat)
return b.toJsonNode().toJson(true);
std::ostringstream ss;
ss << b;
return ss.str();
};
printCommandMessage("Bonuses of " + adventureInt->selection->getObjectName() + "\n");
printCommandMessage(format(adventureInt->selection->getBonusList()) + "\n");
printCommandMessage("\nInherited bonuses:\n");
TCNodes parents;
adventureInt->selection->getParents(parents);
for(const CBonusSystemNode *parent : parents)
{
printCommandMessage(std::string("\nBonuses from ") + typeid(*parent).name() + "\n" + format(*parent->getAllBonuses(Selector::all, Selector::all)) + "\n");
}
}
else if(commandName == "not dialog")
{
LOCPLINT->showingDialog->setn(false);
}
else if(commandName == "gui")
{
for(auto & child : GH.listInt)
{
const auto childPtr = child.get();
if(const CIntObject * obj = dynamic_cast<const CIntObject *>(childPtr))
printInfoAboutInterfaceObject(obj, 0);
else
printCommandMessage(std::string(typeid(childPtr).name()) + "\n");
}
}
else if(commandName == "tell")
{
std::string what;
int id1, id2;
singleWordBuffer >> what >> id1 >> id2;
if(what == "hs")
{
for(const CGHeroInstance *h : LOCPLINT->cb->getHeroesInfo())
if(h->type->ID.getNum() == id1)
if(const CArtifactInstance *a = h->getArt(ArtifactPosition(id2)))
printCommandMessage(a->nodeName());
}
}
else if (commandName == "set")
{
std::string what, 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);
}
}
else if(commandName == "unlock")
{
std::string mxname;
singleWordBuffer >> mxname;
if(mxname == "pim" && LOCPLINT)
LOCPLINT->pim->unlock();
}
else if(commandName == "def2bmp")
{
std::string URI;
singleWordBuffer >> URI;
std::unique_ptr<CAnimation> anim = std::make_unique<CAnimation>(URI);
anim->preload();
anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
}
else if(commandName == "extract")
{
std::string URI;
singleWordBuffer >> URI;
if (CResourceHandler::get()->existsResource(ResourceID(URI)))
{
const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / URI;
auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll();
boost::filesystem::create_directories(outPath.parent_path());
boost::filesystem::ofstream outFile(outPath, boost::filesystem::ofstream::binary);
outFile.write((char*)data.first.get(), data.second);
}
else
printCommandMessage("File not found!", ELogLevel::ERROR);
}
else if(commandName == "setBattleAI")
{
std::string fname;
singleWordBuffer >> fname;
printCommandMessage("Will try loading that AI to see if it is correct name...\n");
try
{
if(auto ai = CDynLibHandler::getNewBattleAI(fname)) //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() = fname;
printCommandMessage("Setting changed, from now the battle ai will be " + fname + "!\n");
}
}
catch(std::exception &e)
{
printCommandMessage("Failed opening " + fname + ": " + e.what(), ELogLevel::WARN);
printCommandMessage("Setting not changed, AI not found or invalid!", ELogLevel::WARN);
}
}
else if(commandName == "autoskip")
{
Settings session = settings.write["session"];
session["autoSkip"].Bool() = !session["autoSkip"].Bool();
}
else if(commandName == "gosolo")
{
ClientCommandManager::handleGoSolo();
}
else if(commandName == "controlai")
{
std::string colorName;
singleWordBuffer >> colorName;
boost::to_lower(colorName);
ClientCommandManager::handleControlAi(colorName);
}
else
{
printCommandMessage("Command not found :(", ELogLevel::ERROR);
}
}
void ClientCommandManager::giveTurn(const PlayerColor &colorIdentifier)
{
YourTurn yt;
yt.player = colorIdentifier;
yt.daysWithoutCastle = CSH->client->getPlayerState(colorIdentifier)->daysWithoutCastle;
yt.applyCl(CSH->client);
}
void ClientCommandManager::printInfoAboutInterfaceObject(const CIntObject *obj, int level)
{
std::stringstream sbuffer;
sbuffer << std::string(level, '\t');
sbuffer << typeid(*obj).name() << " *** ";
if (obj->active)
{
#define PRINT(check, text) if (obj->active & CIntObject::check) sbuffer << text
PRINT(LCLICK, 'L');
PRINT(RCLICK, 'R');
PRINT(HOVER, 'H');
PRINT(MOVE, 'M');
PRINT(KEYBOARD, 'K');
PRINT(TIME, 'T');
PRINT(GENERAL, 'A');
PRINT(WHEEL, 'W');
PRINT(DOUBLECLICK, 'D');
#undef PRINT
}
else
sbuffer << "inactive";
sbuffer << " at " << obj->pos.x <<"x"<< obj->pos.y;
sbuffer << " (" << obj->pos.w <<"x"<< obj->pos.h << ")";
printCommandMessage(sbuffer.str(), ELogLevel::INFO);
for(const CIntObject *child : obj->children)
printInfoAboutInterfaceObject(child, level+1);
}
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::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
if(LOCPLINT && LOCPLINT->cingconsole)
{
LOCPLINT->cingconsole->print(commandMessage);
}
}
}

View File

@ -0,0 +1,31 @@
/*
* ClientCommandManager.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;
VCMI_LIB_NAMESPACE_END
class CIntObject;
class ClientCommandManager //take mantis #2292 issue about account if thinking about handling cheats from command-line
{
bool currentCallFromIngameConsole;
void giveTurn(const PlayerColor &color);
void printInfoAboutInterfaceObject(const CIntObject *obj, int level);
void printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType = ELogLevel::NOT_SET);
void handleGoSolo();
void handleControlAi(const std::string &colorName);
public:
ClientCommandManager() = default;
void processCommand(const std::string &message, bool calledFromIngameConsole);
};

View File

@ -48,6 +48,7 @@
#include "../../lib/mapping/CMap.h"
#include "../../lib/NetPacksBase.h"
#include "../../lib/StringConstants.h"
#include "ClientCommandManager.h"
CList::CListItem::CListItem(CList * Parent)
: CIntObject(LCLICK | RCLICK | HOVER),
@ -1141,15 +1142,29 @@ void CInGameConsole::startEnteringText()
GH.statusbar->setEnteredText(enteredText);
}
void CInGameConsole::endEnteringText(bool printEnteredText)
void CInGameConsole::endEnteringText(bool processEnteredText)
{
captureAllKeys = false;
prevEntDisp = -1;
if(printEnteredText)
if(processEnteredText)
{
std::string txt = enteredText.substr(0, enteredText.size()-1);
LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection());
previouslyEntered.push_back(txt);
if(txt.at(0) == '/')
{
//some commands like gosolo don't work when executed from GUI thread
auto threadFunction = [=]()
{
ClientCommandManager commandController;
commandController.processCommand(txt.substr(1), true);
};
boost::thread clientCommandThread(threadFunction);
clientCommandThread.detach();
}
else
LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection());
}
enteredText.clear();

View File

@ -424,7 +424,7 @@ public:
void textEdited(const SDL_TextEditingEvent & event) override;
void startEnteringText();
void endEnteringText(bool printEnteredText);
void endEnteringText(bool processEnteredText);
void refreshEnteredText();
CInGameConsole();

View File

@ -230,7 +230,7 @@ int CConsoleHandler::run()
{
if ( getline(std::cin, buffer).good() )
if ( cb && *cb )
(*cb)(buffer);
(*cb)(buffer, false);
}
else
boost::this_thread::sleep(boost::posix_time::millisec(100));
@ -239,7 +239,7 @@ int CConsoleHandler::run()
#else
std::getline(std::cin, buffer);
if ( cb && *cb )
(*cb)(buffer);
(*cb)(buffer, false);
#endif
}
return -1;
@ -263,7 +263,7 @@ CConsoleHandler::CConsoleHandler() : thread(nullptr)
#else
defColor = "\x1b[0m";
#endif
cb = new std::function<void(const std::string &)>;
cb = new std::function<void(const std::string &, bool)>;
}
CConsoleHandler::~CConsoleHandler()
{

View File

@ -75,8 +75,8 @@ public:
funlockfile(stdout);
#endif
}
std::function<void(const std::string &)> *cb; //function to be called when message is received
//function to be called when message is received - string: message, bool: whether call was made from in-game console
std::function<void(const std::string &, bool)> *cb;
private:
int run();