1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Merge remote-tracking branch 'upstream/develop' into enet

# Conflicts:
#	CI/mac/before_install.sh
This commit is contained in:
nordsoft 2023-01-18 02:16:15 +04:00
commit f6ae0c74ce
206 changed files with 7544 additions and 3057 deletions

View File

@ -202,7 +202,6 @@ void ObjectClusterizer::clusterize()
Obj::WHIRLPOOL,
Obj::BUOY,
Obj::SIGN,
Obj::SIGN,
Obj::GARRISON,
Obj::MONSTER,
Obj::GARRISON2,

12
CI/conan/base/apple Normal file
View File

@ -0,0 +1,12 @@
[settings]
compiler=apple-clang
compiler.version=14
compiler.libcxx=libc++
build_type=Release
# required for Boost.Locale in versions >= 1.81
compiler.cppstd=11
[conf]
tools.apple:enable_bitcode = False
tools.cmake.cmaketoolchain:generator = Ninja

5
CI/conan/base/ios Normal file
View File

@ -0,0 +1,5 @@
include(apple)
[settings]
os=iOS
os.sdk=iphoneos

4
CI/conan/base/macos Normal file
View File

@ -0,0 +1,4 @@
include(apple)
[settings]
os=Macos

View File

@ -1,14 +1,5 @@
include(base/ios)
[settings]
os=iOS
os.version=12.0
os.sdk=iphoneos
arch=armv8
compiler=apple-clang
compiler.version=13
compiler.libcxx=libc++
build_type=Release
[options]
[build_requires]
[env]
[conf]
tools.cmake.cmaketoolchain:generator = Ninja

View File

@ -1,14 +1,8 @@
include(base/ios)
[settings]
os=iOS
os.version=10.0
os.sdk=iphoneos
arch=armv7
compiler=apple-clang
# Xcode 13.x is the last version that can build for armv7
compiler.version=13
compiler.libcxx=libc++
build_type=Release
[options]
[build_requires]
[env]
[conf]
tools.cmake.cmaketoolchain:generator = Ninja

View File

@ -1,13 +1,5 @@
include(base/macos)
[settings]
os=Macos
os.version=11.0
arch=armv8
compiler=apple-clang
compiler.version=13
compiler.libcxx=libc++
build_type=Release
[options]
[build_requires]
[env]
[conf]
tools.cmake.cmaketoolchain:generator = Ninja

View File

@ -1,13 +1,5 @@
include(base/macos)
[settings]
os=Macos
os.version=10.13
arch=x86_64
compiler=apple-clang
compiler.version=13
compiler.libcxx=libc++
build_type=Release
[options]
[build_requires]
[env]
[conf]
tools.cmake.cmaketoolchain:generator = Ninja

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
echo DEVELOPER_DIR=/Applications/Xcode_13.4.1.app >> $GITHUB_ENV
echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
mkdir ~/.conan ; cd ~/.conan
curl -L 'https://github.com/vcmi/vcmi-ios-deps/releases/download/1.1/ios-arm64.xz' \
curl -L 'https://github.com/vcmi/vcmi-ios-deps/releases/download/1.2/ios-arm64.txz' \
| tar -xf -

View File

@ -1,9 +1,9 @@
#!/usr/bin/env bash
echo DEVELOPER_DIR=/Applications/Xcode_13.4.1.app >> $GITHUB_ENV
echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
brew install ninja
mkdir ~/.conan ; cd ~/.conan
curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/download/1.1/$DEPS_FILENAME.txz" \
| tar -xf -
curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/download/1.2/$DEPS_FILENAME.txz" \
| tar -xf -

View File

@ -1,10 +1,10 @@
curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" \
"https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.5/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z"
7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z"
curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" \
"https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.6/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
rm -r -f vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug
mkdir -p vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
cp vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/bin/* vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
#rm -r -f vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug
#mkdir -p vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
#cp vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/bin/* vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
DUMPBIN_DIR=$(vswhere -latest -find **/dumpbin.exe | head -n 1)
dirname "$DUMPBIN_DIR" > $GITHUB_PATH
dirname "$DUMPBIN_DIR" > $GITHUB_PATH

View File

@ -90,6 +90,11 @@ if(APPLE_IOS AND COPY_CONFIG_ON_BUILD)
set(COPY_CONFIG_ON_BUILD OFF)
endif()
# No QT Linguist on MXE
if((MINGW) AND (${CMAKE_CROSSCOMPILING}))
set(ENABLE_TRANSLATIONS OFF)
endif()
############################################
# Miscellaneous options #
############################################
@ -181,6 +186,14 @@ set(CMAKE_XCODE_ATTRIBUTE_MARKETING_VERSION ${APP_SHORT_VERSION})
set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO)
set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] YES)
if(ENABLE_LAUNCHER)
add_definitions(-DENABLE_LAUNCHER)
endif()
if(ENABLE_EDITOR)
add_definitions(-DENABLE_EDITOR)
endif()
if(ENABLE_SINGLE_APP_BUILD)
add_definitions(-DSINGLE_PROCESS_APP=1)
endif()
@ -232,7 +245,7 @@ if(MINGW OR MSVC)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 4800: implicit conversion from 'xxx' to bool. Possible information loss
if(ENABLE_STRICT_COMPILATION)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wx") # Treats all compiler warnings as errors
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") # Treats all compiler warnings as errors
endif()
if(ENABLE_MULTI_PROCESS_BUILDS)
@ -361,12 +374,8 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network)
find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools)
if(NOT Qt${QT_VERSION_MAJOR}LinguistTools_DIR)
set(ENABLE_TRANSLATIONS OFF)
endif()
if(ENABLE_TRANSLATIONS)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS LinguistTools)
add_definitions(-DENABLE_QT_TRANSLATIONS)
endif()
endif()
@ -564,6 +573,9 @@ if(WIN32)
FILES ${integration_loc}
DESTINATION ${BIN_DIR}/platforms
)
install(
FILES "$<TARGET_FILE:Qt${QT_VERSION_MAJOR}::QWindowsVistaStylePlugin>"
DESTINATION ${BIN_DIR}/styles)
endif()
endif()

View File

@ -1,6 +1,20 @@
{
"name" : "VCMI essential files",
"description" : "Essential files required for VCMI to run correctly",
"german" : {
"name" : "VCMI - grundlegende Dateien",
"description" : "Grundlegende Dateien, die für die korrekte Ausführung von VCMI erforderlich sind",
"author" : "VCMI-Team",
"modType" : "Grafik",
},
"ukrainian" : {
"name" : "VCMI - ключові файли",
"description" : "Ключові файли необхідні для повноцінної роботи VCMI",
"author" : "Команда VCMI",
"modType" : "Графіка",
},
"version" : "1.1",
"author" : "VCMI Team",

View File

@ -38,7 +38,7 @@ VCMI_LIB_NAMESPACE_END
class CMapHandler;
class CSoundHandler;
class CMusicHandler;
class CCursorHandler;
class CursorHandler;
class IMainVideoPlayer;
class CServerHandler;
@ -49,7 +49,7 @@ public:
CSoundHandler * soundh;
CMusicHandler * musich;
CConsoleHandler * consoleh;
CCursorHandler * curh;
CursorHandler * curh;
IMainVideoPlayer * videoh;
};
extern CClientState * CCS;

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"
@ -25,7 +23,7 @@
#include "lobby/CSelectionBase.h"
#include "windows/CCastleInterface.h"
#include "../lib/CConsoleHandler.h"
#include "gui/CCursorHandler.h"
#include "gui/CursorHandler.h"
#include "../lib/CGameState.h"
#include "../CCallback.h"
#include "CPlayerInterface.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__
@ -208,6 +200,8 @@ int main(int argc, char * argv[])
("lobby-host", "if this client hosts session")
("lobby-uuid", po::value<std::string>(), "uuid to the server")
("lobby-connections", po::value<ui16>(), "connections of server")
("lobby-username", po::value<std::string>(), "player name")
("lobby-gamemode", po::value<ui16>(), "use 0 for new game and 1 for load game")
("uuid", po::value<std::string>(), "uuid for the client");
if(argc > 1)
@ -247,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
@ -470,7 +471,7 @@ int main(int argc, char * argv[])
pomtime.getDiff();
graphics = new Graphics(); // should be before curh
CCS->curh = new CCursorHandler();
CCS->curh = new CursorHandler();
logGlobal->info("Screen handler: %d ms", pomtime.getDiff());
pomtime.getDiff();
@ -489,29 +490,8 @@ int main(int argc, char * argv[])
session["autoSkip"].Bool() = vm.count("autoSkip");
session["oneGoodAI"].Bool() = vm.count("oneGoodAI");
session["aiSolo"].Bool() = false;
std::shared_ptr<CMainMenu> mmenu;
session["lobby"].Bool() = false;
if(vm.count("lobby"))
{
session["lobby"].Bool() = true;
session["host"].Bool() = false;
session["address"].String() = vm["lobby-address"].as<std::string>();
CSH->uuid = vm["uuid"].as<std::string>();
session["port"].Integer() = vm["lobby-port"].as<ui16>();
logGlobal->info("Remote lobby mode at %s:%d, uuid is %s", session["address"].String(), session["port"].Integer(), CSH->uuid);
if(vm.count("lobby-host"))
{
session["host"].Bool() = true;
session["hostConnections"].String() = std::to_string(vm["lobby-connections"].as<ui16>());
session["hostUuid"].String() = vm["lobby-uuid"].as<std::string>();
logGlobal->info("This client will host session, server uuid is %s", session["hostUuid"].String());
}
//we should not reconnect to previous game in online mode
Settings saveSession = settings.write["server"]["reconnect"];
saveSession->Bool() = false;
}
if(vm.count("testmap"))
{
session["testmap"].String() = vm["testmap"].as<std::string>();
@ -526,7 +506,44 @@ int main(int argc, char * argv[])
}
else
{
GH.curInt = CMainMenu::create().get();
mmenu = CMainMenu::create();
GH.curInt = mmenu.get();
}
std::vector<std::string> names;
session["lobby"].Bool() = false;
if(vm.count("lobby"))
{
session["lobby"].Bool() = true;
session["host"].Bool() = false;
session["address"].String() = vm["lobby-address"].as<std::string>();
if(vm.count("lobby-username"))
session["username"].String() = vm["lobby-username"].as<std::string>();
else
session["username"].String() = settings["launcher"]["lobbyUsername"].String();
if(vm.count("lobby-gamemode"))
session["gamemode"].Integer() = vm["lobby-gamemode"].as<ui16>();
else
session["gamemode"].Integer() = 0;
CSH->uuid = vm["uuid"].as<std::string>();
session["port"].Integer() = vm["lobby-port"].as<ui16>();
logGlobal->info("Remote lobby mode at %s:%d, uuid is %s", session["address"].String(), session["port"].Integer(), CSH->uuid);
if(vm.count("lobby-host"))
{
session["host"].Bool() = true;
session["hostConnections"].String() = std::to_string(vm["lobby-connections"].as<ui16>());
session["hostUuid"].String() = vm["lobby-uuid"].as<std::string>();
logGlobal->info("This client will host session, server uuid is %s", session["hostUuid"].String());
}
//we should not reconnect to previous game in online mode
Settings saveSession = settings.write["server"]["reconnect"];
saveSession->Bool() = false;
//start lobby immediately
names.push_back(session["username"].String());
ESelectionScreen sscreen = session["gamemode"].Integer() == 0 ? ESelectionScreen::newGame : ESelectionScreen::loadGame;
mmenu->openLobby(sscreen, session["host"].Bool(), &names, ELoadMode::MULTI);
}
// Restore remote session - start game immediately
@ -548,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

@ -18,7 +18,7 @@ set(client_SRCS
gui/CAnimation.cpp
gui/Canvas.cpp
gui/CCursorHandler.cpp
gui/CursorHandler.cpp
gui/CGuiHandler.cpp
gui/CIntObject.cpp
gui/ColorFilter.cpp
@ -82,6 +82,7 @@ set(client_SRCS
NetPacksClient.cpp
NetPacksLobbyClient.cpp
SDLRWwrapper.cpp
ClientCommandManager.cpp
)
set(client_HEADERS
@ -104,7 +105,7 @@ set(client_HEADERS
gui/CAnimation.h
gui/Canvas.h
gui/CCursorHandler.h
gui/CursorHandler.h
gui/CGuiHandler.h
gui/ColorFilter.h
gui/CIntObject.h
@ -168,6 +169,7 @@ set(client_HEADERS
mapHandler.h
resource.h
SDLRWwrapper.h
ClientCommandManager.h
)
if(APPLE_IOS)
@ -232,7 +234,7 @@ if(WIN32)
add_custom_command(TARGET vcmiclient POST_BUILD
WORKING_DIRECTORY "$<TARGET_FILE_DIR:vcmiclient>"
COMMAND ${CMAKE_COMMAND} -E copy AI/fuzzylite.dll fuzzylite.dll
COMMAND ${CMAKE_COMMAND} -E copy AI/tbb.dll tbb.dll
COMMAND ${CMAKE_COMMAND} -E copy AI/tbb12.dll tbb12.dll
)
endif()
elseif(APPLE_IOS)

View File

@ -20,7 +20,7 @@
#include "../lib/StringConstants.h"
#include "../lib/CRandomGenerator.h"
#include "../lib/VCMIDirs.h"
#include "../lib/Terrain.h"
#include "../lib/TerrainHandler.h"
#define VCMI_SOUND_NAME(x)
#define VCMI_SOUND_FILE(y) #y,
@ -89,36 +89,6 @@ CSoundHandler::CSoundHandler():
soundBase::battle02, soundBase::battle03, soundBase::battle04,
soundBase::battle05, soundBase::battle06, soundBase::battle07
};
//predefine terrain set
//TODO: support custom sounds for new terrains and load from json
horseSounds =
{
{Terrain::DIRT, soundBase::horseDirt},
{Terrain::SAND, soundBase::horseSand},
{Terrain::GRASS, soundBase::horseGrass},
{Terrain::SNOW, soundBase::horseSnow},
{Terrain::SWAMP, soundBase::horseSwamp},
{Terrain::ROUGH, soundBase::horseRough},
{Terrain::SUBTERRANEAN, soundBase::horseSubterranean},
{Terrain::LAVA, soundBase::horseLava},
{Terrain::WATER, soundBase::horseWater},
{Terrain::ROCK, soundBase::horseRock}
};
}
void CSoundHandler::loadHorseSounds()
{
const auto & terrains = CGI->terrainTypeHandler->terrains();
for(const auto & terrain : terrains)
{
//since all sounds are hardcoded, let's keep it
if(vstd::contains(horseSounds, terrain.id))
continue;
//Use already existing horse sound
horseSounds[terrain.id] = horseSounds.at(terrains[terrain.id].horseSoundId);
}
}
void CSoundHandler::init()
@ -368,9 +338,9 @@ CMusicHandler::CMusicHandler():
void CMusicHandler::loadTerrainMusicThemes()
{
for (const auto & terrain : CGI->terrainTypeHandler->terrains())
for (const auto & terrain : CGI->terrainTypeHandler->objects)
{
addEntryToSet("terrain_" + terrain.name, "Music/" + terrain.musicFilename);
addEntryToSet("terrain_" + terrain->getJsonKey(), "Music/" + terrain->musicFilename);
}
}
@ -542,6 +512,20 @@ MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string mu
}
MusicEntry::~MusicEntry()
{
if (playing)
{
assert(0);
logGlobal->error("Attempt to delete music while playing!");
Mix_HaltMusic();
}
if (loop == 0 && Mix_FadingMusic() != MIX_NO_FADING)
{
assert(0);
logGlobal->error("Attempt to delete music while fading out!");
Mix_HaltMusic();
}
logGlobal->trace("Del-ing music file %s", currentName);
if (music)
Mix_FreeMusic(music);
@ -619,7 +603,7 @@ bool MusicEntry::play()
bool MusicEntry::stop(int fade_ms)
{
if (playing)
if (Mix_PlayingMusic())
{
playing = false;
loop = 0;

View File

@ -11,7 +11,6 @@
#include "../lib/CConfigHandler.h"
#include "../lib/CSoundBase.h"
#include "../lib/Terrain.h"
struct _Mix_Music;
struct SDL_RWops;
@ -61,7 +60,6 @@ public:
CSoundHandler();
void init() override;
void loadHorseSounds();
void release() override;
void setVolume(ui32 percent) override;
@ -84,7 +82,6 @@ public:
// Sets
std::vector<soundBase::soundID> pickupSounds;
std::vector<soundBase::soundID> battleIntroSounds;
std::map<TerrainId, soundBase::soundID> horseSounds;
};
// Helper //now it looks somewhat useless

View File

@ -19,7 +19,7 @@
#include "battle/BattleWindow.h"
#include "../CCallback.h"
#include "windows/CCastleInterface.h"
#include "gui/CCursorHandler.h"
#include "gui/CursorHandler.h"
#include "windows/CKingdomInterface.h"
#include "CGameInfo.h"
#include "windows/CHeroWindow.h"
@ -61,6 +61,8 @@
#include "windows/InfoWindows.h"
#include "../lib/UnlockGuard.h"
#include "../lib/CPathfinder.h"
#include "../lib/RoadHandler.h"
#include "../lib/TerrainHandler.h"
#include <SDL.h>
#include "CServerHandler.h"
// FIXME: only needed for CGameState::mutex
@ -156,7 +158,6 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
cb = CB;
env = ENV;
CCS->soundh->loadHorseSounds();
CCS->musich->loadTerrainMusicThemes();
initializeHeroTownList();
@ -260,7 +261,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
{
updateAmbientSounds();
//We may need to change music - select new track, music handler will change it if needed
CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->name, true, false);
CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->getJsonKey(), true, false);
if(details.result == TryMoveHero::TELEPORTATION)
{
@ -436,7 +437,7 @@ void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
adventureInt->select(newSelection, true);
else if (adventureInt->selection == hero)
adventureInt->selection = nullptr;
if (vstd::contains(paths, hero))
paths.erase(hero);
}
@ -1623,7 +1624,7 @@ int CPlayerInterface::getLastIndex( std::string namePrefix)
else
for (directory_iterator dir(gamesDir); dir != enddir; ++dir)
{
if (is_regular(dir->status()))
if (is_regular_file(dir->status()))
{
std::string name = dir->path().filename().string();
if (starts_with(name, namePrefix) && ends_with(name, ".vcgm1"))
@ -2372,8 +2373,9 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
for (auto & elem : path.nodes)
elem.coord = h->convertFromVisitablePos(elem.coord);
TerrainId currentTerrain = Terrain::BORDER; // not init yet
TerrainId currentTerrain = ETerrainId::NONE;
TerrainId newTerrain;
bool wasOnRoad = true;
int sh = -1;
auto canStop = [&](CGPathNode * node) -> bool
@ -2389,13 +2391,18 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
for (i=(int)path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE || !canStop(&path.nodes[i])); i--)
{
int3 currentCoord = path.nodes[i].coord;
int3 prevCoord = path.nodes[i].coord;
int3 nextCoord = path.nodes[i-1].coord;
auto currentObject = getObj(currentCoord, currentCoord == h->pos);
auto prevRoad = cb->getTile(h->convertToVisitablePos(prevCoord))->roadType;
auto nextRoad = cb->getTile(h->convertToVisitablePos(nextCoord))->roadType;
bool movingOnRoad = prevRoad->getId() != Road::NO_ROAD && nextRoad->getId() != Road::NO_ROAD;
auto prevObject = getObj(prevCoord, prevCoord == h->pos);
auto nextObjectTop = getObj(nextCoord, false);
auto nextObject = getObj(nextCoord, true);
auto destTeleportObj = getDestTeleportObj(currentObject, nextObjectTop, nextObject);
auto destTeleportObj = getDestTeleportObj(prevObject, nextObjectTop, nextObject);
if (isTeleportAction(path.nodes[i-1].action) && destTeleportObj != nullptr)
{
CCS->soundh->stopSound(sh);
@ -2410,7 +2417,10 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
}
if(i != path.nodes.size() - 1)
{
sh = CCS->soundh->playSound(CCS->soundh->horseSounds[currentTerrain], -1);
if (movingOnRoad)
sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(currentTerrain)->horseSound, -1);
else
sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(currentTerrain)->horseSoundPenalty, -1);
}
continue;
}
@ -2428,12 +2438,16 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
sh = CCS->soundh->playSound(soundBase::horseFlying, -1);
#endif
{
newTerrain = cb->getTile(h->convertToVisitablePos(currentCoord))->terType->id;
if(newTerrain != currentTerrain)
newTerrain = cb->getTile(h->convertToVisitablePos(prevCoord))->terType->getId();
if(newTerrain != currentTerrain || wasOnRoad != movingOnRoad)
{
CCS->soundh->stopSound(sh);
sh = CCS->soundh->playSound(CCS->soundh->horseSounds[newTerrain], -1);
if (movingOnRoad)
sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(newTerrain)->horseSound, -1);
else
sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(newTerrain)->horseSoundPenalty, -1);
currentTerrain = newTerrain;
wasOnRoad = movingOnRoad;
}
}
@ -2473,6 +2487,9 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
// (i == 0) means hero went through all the path
adventureInt->updateMoveHero(h, (i != 0));
adventureInt->updateNextHero(h);
// ugly workaround to force instant update of adventure map
adventureInt->animValHitCount = 8;
}
setMovementStatus(false);

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,8 +761,29 @@ 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)
{
logNetwork->info("Received clientSetupJNI");
CAndroidVMHelper::cacheVM(env);
}
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls)
{
logNetwork->info("Received server closed signal");

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

@ -63,7 +63,7 @@ void LobbyClientDisconnected::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHa
void LobbyChatMessage::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
{
if(lobby)
if(lobby && lobby->card)
{
lobby->card->chat->addNewMessage(playerName + ": " + message);
lobby->card->setChat(true);

View File

@ -19,7 +19,7 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CIntObject.h"
#include "../windows/CCreatureWindow.h"

View File

@ -22,7 +22,7 @@
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../../CCallback.h"

View File

@ -26,7 +26,7 @@
#include "../gui/CAnimation.h"
#include "../gui/Canvas.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../../CCallback.h"
#include "../../lib/BattleFieldHandler.h"

View File

@ -28,7 +28,7 @@
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../gui/Canvas.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../windows/CAdvmapInterface.h"
@ -41,6 +41,7 @@
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/NetPacks.h"
#include "../../lib/UnlockGuard.h"
#include "../../lib/TerrainHandler.h"
CondSh<BattleAction *> BattleInterface::givenCommand(nullptr);
@ -136,8 +137,8 @@ BattleInterface::~BattleInterface()
if (adventureInt && adventureInt->selection)
{
//FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player
const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
const auto * terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType;
CCS->musich->playMusicFromSet("terrain", terrain->getJsonKey(), true, false);
}
// may happen if user decided to close game while in battle

View File

@ -26,7 +26,7 @@
#include "../Graphics.h"
#include "../gui/CAnimation.h"
#include "../gui/Canvas.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/AdventureMapClasses.h"
#include "../widgets/Buttons.h"

View File

@ -83,10 +83,10 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
amountNegative = IImage::createFromFile("CMNUMWIN.BMP");
amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP");
static const auto shifterNormal = ColorFilter::genRangeShifter( 0,0,0, 0.6, 0.2, 1.0 );
static const auto shifterPositive = ColorFilter::genRangeShifter( 0,0,0, 0.2, 1.0, 0.2 );
static const auto shifterNegative = ColorFilter::genRangeShifter( 0,0,0, 1.0, 0.2, 0.2 );
static const auto shifterNeutral = ColorFilter::genRangeShifter( 0,0,0, 1.0, 1.0, 0.2 );
static const auto shifterNormal = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.6f, 0.2f, 1.0f );
static const auto shifterPositive = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.2f, 1.0f, 0.2f );
static const auto shifterNegative = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 0.2f, 0.2f );
static const auto shifterNeutral = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 1.0f, 0.2f );
amountNormal->adjustPalette(shifterNormal);
amountPositive->adjustPalette(shifterPositive);

View File

@ -21,7 +21,7 @@
#include "../CPlayerInterface.h"
#include "../CMusicHandler.h"
#include "../gui/Canvas.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CAnimation.h"
#include "../windows/CSpellWindow.h"

View File

@ -94,8 +94,8 @@ public:
// Keep the original palette, in order to do color switching operation
void savePalette();
void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr, ui8 alpha=255) const override;
void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha=255) const override;
void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr) const override;
void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src) const override;
std::shared_ptr<IImage> scaleFast(float scale) const override;
void exportBitmap(const boost::filesystem::path & path) const override;
void playerColored(PlayerColor player) override;
@ -642,17 +642,16 @@ SDLImage::SDLImage(std::string filename)
}
}
void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src, ui8 alpha) const
void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src) const
{
if(!surf)
return;
Rect destRect(posX, posY, surf->w, surf->h);
draw(where, &destRect, src);
}
void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src, ui8 alpha) const
void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src) const
{
if (!surf)
return;

View File

@ -40,8 +40,8 @@ public:
using SpecialPalette = std::array<SDL_Color, 7>;
//draws image on surface "where" at position
virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr, ui8 alpha = 255) const=0;
virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha = 255) const = 0;
virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr) const = 0;
virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src) const = 0;
virtual std::shared_ptr<IImage> scaleFast(float scale) const = 0;

View File

@ -1,317 +0,0 @@
/*
* CCursorHandler.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 "CCursorHandler.h"
#include <SDL.h>
#include "SDL_Extensions.h"
#include "CGuiHandler.h"
#include "../widgets/Images.h"
#include "../CMT.h"
void CCursorHandler::clearBuffer()
{
Uint32 fillColor = SDL_MapRGBA(buffer->format, 0, 0, 0, 0);
CSDL_Ext::fillRect(buffer, nullptr, fillColor);
}
void CCursorHandler::updateBuffer(CIntObject * payload)
{
payload->moveTo(Point(0,0));
payload->showAll(buffer);
needUpdate = true;
}
void CCursorHandler::replaceBuffer(CIntObject * payload)
{
clearBuffer();
updateBuffer(payload);
}
CCursorHandler::CCursorHandler()
: needUpdate(true)
, buffer(nullptr)
, cursorLayer(nullptr)
, frameTime(0.f)
, showing(false)
{
cursorLayer = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 40, 40);
SDL_SetTextureBlendMode(cursorLayer, SDL_BLENDMODE_BLEND);
xpos = ypos = 0;
type = Cursor::Type::DEFAULT;
dndObject = nullptr;
cursors =
{
std::make_unique<CAnimImage>("CRADVNTR", 0),
std::make_unique<CAnimImage>("CRCOMBAT", 0),
std::make_unique<CAnimImage>("CRDEFLT", 0),
std::make_unique<CAnimImage>("CRSPELL", 0)
};
currentCursor = cursors.at(static_cast<size_t>(Cursor::Type::DEFAULT)).get();
buffer = CSDL_Ext::newSurface(40,40);
SDL_SetSurfaceBlendMode(buffer, SDL_BLENDMODE_NONE);
SDL_ShowCursor(SDL_DISABLE);
set(Cursor::Map::POINTER);
}
Point CCursorHandler::position() const
{
return Point(xpos, ypos);
}
void CCursorHandler::changeGraphic(Cursor::Type type, size_t index)
{
assert(dndObject == nullptr);
if(type != this->type)
{
this->type = type;
this->frame = index;
currentCursor = cursors.at(static_cast<size_t>(type)).get();
currentCursor->setFrame(index);
}
else if(index != this->frame)
{
this->frame = index;
currentCursor->setFrame(index);
}
replaceBuffer(currentCursor);
}
void CCursorHandler::set(Cursor::Default index)
{
changeGraphic(Cursor::Type::DEFAULT, static_cast<size_t>(index));
}
void CCursorHandler::set(Cursor::Map index)
{
changeGraphic(Cursor::Type::ADVENTURE, static_cast<size_t>(index));
}
void CCursorHandler::set(Cursor::Combat index)
{
changeGraphic(Cursor::Type::COMBAT, static_cast<size_t>(index));
}
void CCursorHandler::set(Cursor::Spellcast index)
{
//Note: this is animated cursor, ignore specified frame and only change type
changeGraphic(Cursor::Type::SPELLBOOK, frame);
}
void CCursorHandler::dragAndDropCursor(std::unique_ptr<CAnimImage> object)
{
dndObject = std::move(object);
if(dndObject)
replaceBuffer(dndObject.get());
else
replaceBuffer(currentCursor);
}
void CCursorHandler::cursorMove(const int & x, const int & y)
{
xpos = x;
ypos = y;
}
void CCursorHandler::shiftPos( int &x, int &y )
{
if(( type == Cursor::Type::COMBAT && frame != static_cast<size_t>(Cursor::Combat::POINTER)) || type == Cursor::Type::SPELLBOOK)
{
x-=16;
y-=16;
// Properly align the melee attack cursors.
if (type == Cursor::Type::COMBAT)
{
switch (static_cast<Cursor::Combat>(frame))
{
case Cursor::Combat::HIT_NORTHEAST:
x -= 6;
y += 16;
break;
case Cursor::Combat::HIT_EAST:
x -= 16;
y += 10;
break;
case Cursor::Combat::HIT_SOUTHEAST:
x -= 6;
y -= 6;
break;
case Cursor::Combat::HIT_SOUTHWEST:
x += 16;
y -= 6;
break;
case Cursor::Combat::HIT_WEST:
x += 16;
y += 11;
break;
case Cursor::Combat::HIT_NORTHWEST:
x += 16;
y += 16;
break;
case Cursor::Combat::HIT_NORTH:
x += 9;
y += 16;
break;
case Cursor::Combat::HIT_SOUTH:
x += 9;
y -= 15;
break;
}
}
}
else if(type == Cursor::Type::ADVENTURE)
{
if (frame == 0)
{
//no-op
}
else if(frame == 2)
{
x -= 12;
y -= 10;
}
else if(frame == 3)
{
x -= 12;
y -= 12;
}
else if(frame < 27)
{
int hlpNum = (frame - 4)%6;
if(hlpNum == 0)
{
x -= 15;
y -= 13;
}
else if(hlpNum == 1)
{
x -= 13;
y -= 13;
}
else if(hlpNum == 2)
{
x -= 20;
y -= 20;
}
else if(hlpNum == 3)
{
x -= 13;
y -= 16;
}
else if(hlpNum == 4)
{
x -= 8;
y -= 9;
}
else if(hlpNum == 5)
{
x -= 14;
y -= 16;
}
}
else if(frame == 41)
{
x -= 14;
y -= 16;
}
else if(frame < 31 || frame == 42)
{
x -= 20;
y -= 20;
}
}
}
void CCursorHandler::centerCursor()
{
this->xpos = static_cast<int>((screen->w / 2.) - (currentCursor->pos.w / 2.));
this->ypos = static_cast<int>((screen->h / 2.) - (currentCursor->pos.h / 2.));
SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
SDL_WarpMouse(this->xpos, this->ypos);
SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
}
void CCursorHandler::render()
{
if(!showing)
return;
if (type == Cursor::Type::SPELLBOOK)
{
static const float frameDisplayDuration = 0.1f;
frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
size_t newFrame = frame;
while (frameTime > frameDisplayDuration)
{
frameTime -= frameDisplayDuration;
newFrame++;
}
auto & animation = cursors.at(static_cast<size_t>(type));
while (newFrame > animation->size())
newFrame -= animation->size();
changeGraphic(Cursor::Type::SPELLBOOK, newFrame);
}
//the must update texture in the main (renderer) thread, but changes to cursor type may come from other threads
updateTexture();
int x = xpos;
int y = ypos;
shiftPos(x, y);
if(dndObject)
{
x -= dndObject->pos.w/2;
y -= dndObject->pos.h/2;
}
SDL_Rect destRect;
destRect.x = x;
destRect.y = y;
destRect.w = 40;
destRect.h = 40;
SDL_RenderCopy(mainRenderer, cursorLayer, nullptr, &destRect);
}
void CCursorHandler::updateTexture()
{
if(needUpdate)
{
SDL_UpdateTexture(cursorLayer, nullptr, buffer->pixels, buffer->pitch);
needUpdate = false;
}
}
CCursorHandler::~CCursorHandler()
{
if(buffer)
SDL_FreeSurface(buffer);
if(cursorLayer)
SDL_DestroyTexture(cursorLayer);
}

View File

@ -14,7 +14,7 @@
#include <SDL.h>
#include "CIntObject.h"
#include "CCursorHandler.h"
#include "CursorHandler.h"
#include "../CGameInfo.h"
#include "../../lib/CThreadHelper.h"

View File

@ -70,13 +70,6 @@ void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect &
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect);
}
void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect, uint8_t alpha)
{
assert(image);
if (image)
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect, alpha);
}
void Canvas::draw(Canvas & image, const Point & pos)
{
blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface);

View File

@ -51,10 +51,6 @@ public:
/// renders section of image bounded by sourceRect at specified position
void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect);
/// renders section of image bounded by sourceRect at specified position at specific transparency value
void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect, uint8_t alpha);
/// renders another canvas onto this canvas
void draw(Canvas & image, const Point & pos);

View File

@ -0,0 +1,402 @@
/*
* CCursorHandler.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 "CursorHandler.h"
#include <SDL.h>
#include "SDL_Extensions.h"
#include "CGuiHandler.h"
#include "CAnimation.h"
#include "../../lib/CConfigHandler.h"
//#include "../CMT.h"
std::unique_ptr<ICursor> CursorHandler::createCursor()
{
if (settings["video"]["softwareCursor"].Bool())
return std::make_unique<CursorSoftware>();
else
return std::make_unique<CursorHardware>();
}
CursorHandler::CursorHandler()
: cursor(createCursor())
, frameTime(0.f)
, showing(false)
, pos(0,0)
{
type = Cursor::Type::DEFAULT;
dndObject = nullptr;
cursors =
{
std::make_unique<CAnimation>("CRADVNTR"),
std::make_unique<CAnimation>("CRCOMBAT"),
std::make_unique<CAnimation>("CRDEFLT"),
std::make_unique<CAnimation>("CRSPELL")
};
for (auto & cursor : cursors)
cursor->preload();
set(Cursor::Map::POINTER);
}
Point CursorHandler::position() const
{
return pos;
}
void CursorHandler::changeGraphic(Cursor::Type type, size_t index)
{
assert(dndObject == nullptr);
if (type == this->type && index == this->frame)
return;
this->type = type;
this->frame = index;
cursor->setImage(getCurrentImage(), getPivotOffset());
}
void CursorHandler::set(Cursor::Default index)
{
changeGraphic(Cursor::Type::DEFAULT, static_cast<size_t>(index));
}
void CursorHandler::set(Cursor::Map index)
{
changeGraphic(Cursor::Type::ADVENTURE, static_cast<size_t>(index));
}
void CursorHandler::set(Cursor::Combat index)
{
changeGraphic(Cursor::Type::COMBAT, static_cast<size_t>(index));
}
void CursorHandler::set(Cursor::Spellcast index)
{
//Note: this is animated cursor, ignore specified frame and only change type
changeGraphic(Cursor::Type::SPELLBOOK, frame);
}
void CursorHandler::dragAndDropCursor(std::shared_ptr<IImage> image)
{
dndObject = image;
cursor->setImage(getCurrentImage(), getPivotOffset());
}
void CursorHandler::dragAndDropCursor (std::string path, size_t index)
{
CAnimation anim(path);
anim.load(index);
dragAndDropCursor(anim.getImage(index));
}
void CursorHandler::cursorMove(const int & x, const int & y)
{
pos.x = x;
pos.y = y;
cursor->setCursorPosition(pos);
}
Point CursorHandler::getPivotOffsetDefault(size_t index)
{
return {0, 0};
}
Point CursorHandler::getPivotOffsetMap(size_t index)
{
static const std::array<Point, 43> offsets = {{
{ 0, 0}, // POINTER = 0,
{ 0, 0}, // HOURGLASS = 1,
{ 12, 10}, // HERO = 2,
{ 12, 12}, // TOWN = 3,
{ 15, 13}, // T1_MOVE = 4,
{ 13, 13}, // T1_ATTACK = 5,
{ 16, 32}, // T1_SAIL = 6,
{ 13, 20}, // T1_DISEMBARK = 7,
{ 8, 9}, // T1_EXCHANGE = 8,
{ 14, 16}, // T1_VISIT = 9,
{ 15, 13}, // T2_MOVE = 10,
{ 13, 13}, // T2_ATTACK = 11,
{ 16, 32}, // T2_SAIL = 12,
{ 13, 20}, // T2_DISEMBARK = 13,
{ 8, 9}, // T2_EXCHANGE = 14,
{ 14, 16}, // T2_VISIT = 15,
{ 15, 13}, // T3_MOVE = 16,
{ 13, 13}, // T3_ATTACK = 17,
{ 16, 32}, // T3_SAIL = 18,
{ 13, 20}, // T3_DISEMBARK = 19,
{ 8, 9}, // T3_EXCHANGE = 20,
{ 14, 16}, // T3_VISIT = 21,
{ 15, 13}, // T4_MOVE = 22,
{ 13, 13}, // T4_ATTACK = 23,
{ 16, 32}, // T4_SAIL = 24,
{ 13, 20}, // T4_DISEMBARK = 25,
{ 8, 9}, // T4_EXCHANGE = 26,
{ 14, 16}, // T4_VISIT = 27,
{ 16, 32}, // T1_SAIL_VISIT = 28,
{ 16, 32}, // T2_SAIL_VISIT = 29,
{ 16, 32}, // T3_SAIL_VISIT = 30,
{ 16, 32}, // T4_SAIL_VISIT = 31,
{ 6, 1}, // SCROLL_NORTH = 32,
{ 16, 2}, // SCROLL_NORTHEAST = 33,
{ 21, 6}, // SCROLL_EAST = 34,
{ 16, 16}, // SCROLL_SOUTHEAST = 35,
{ 6, 21}, // SCROLL_SOUTH = 36,
{ 1, 16}, // SCROLL_SOUTHWEST = 37,
{ 1, 5}, // SCROLL_WEST = 38,
{ 2, 1}, // SCROLL_NORTHWEST = 39,
{ 0, 0}, // POINTER_COPY = 40,
{ 14, 16}, // TELEPORT = 41,
{ 20, 20}, // SCUTTLE_BOAT = 42
}};
assert(offsets.size() == size_t(Cursor::Map::COUNT)); //Invalid number of pivot offsets for cursor
assert(index < offsets.size());
return offsets[index];
}
Point CursorHandler::getPivotOffsetCombat(size_t index)
{
static const std::array<Point, 20> offsets = {{
{ 12, 12 }, // BLOCKED = 0,
{ 10, 14 }, // MOVE = 1,
{ 14, 14 }, // FLY = 2,
{ 12, 12 }, // SHOOT = 3,
{ 12, 12 }, // HERO = 4,
{ 8, 12 }, // QUERY = 5,
{ 0, 0 }, // POINTER = 6,
{ 21, 0 }, // HIT_NORTHEAST = 7,
{ 31, 5 }, // HIT_EAST = 8,
{ 21, 21 }, // HIT_SOUTHEAST = 9,
{ 0, 21 }, // HIT_SOUTHWEST = 10,
{ 0, 5 }, // HIT_WEST = 11,
{ 0, 0 }, // HIT_NORTHWEST = 12,
{ 6, 0 }, // HIT_NORTH = 13,
{ 6, 31 }, // HIT_SOUTH = 14,
{ 14, 0 }, // SHOOT_PENALTY = 15,
{ 12, 12 }, // SHOOT_CATAPULT = 16,
{ 12, 12 }, // HEAL = 17,
{ 12, 12 }, // SACRIFICE = 18,
{ 14, 20 }, // TELEPORT = 19
}};
assert(offsets.size() == size_t(Cursor::Combat::COUNT)); //Invalid number of pivot offsets for cursor
assert(index < offsets.size());
return offsets[index];
}
Point CursorHandler::getPivotOffsetSpellcast()
{
return { 18, 28};
}
Point CursorHandler::getPivotOffset()
{
if (dndObject)
return dndObject->dimensions() / 2;
switch (type) {
case Cursor::Type::ADVENTURE: return getPivotOffsetMap(frame);
case Cursor::Type::COMBAT: return getPivotOffsetCombat(frame);
case Cursor::Type::DEFAULT: return getPivotOffsetDefault(frame);
case Cursor::Type::SPELLBOOK: return getPivotOffsetSpellcast();
};
assert(0);
return {0, 0};
}
std::shared_ptr<IImage> CursorHandler::getCurrentImage()
{
if (dndObject)
return dndObject;
return cursors[static_cast<size_t>(type)]->getImage(frame);
}
void CursorHandler::centerCursor()
{
Point screenSize {screen->w, screen->h};
pos = screenSize / 2 - getPivotOffset();
SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
SDL_WarpMouse(pos.x, pos.y);
SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
cursor->setCursorPosition(pos);
}
void CursorHandler::updateSpellcastCursor()
{
static const float frameDisplayDuration = 0.1f;
frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
size_t newFrame = frame;
while (frameTime >= frameDisplayDuration)
{
frameTime -= frameDisplayDuration;
newFrame++;
}
auto & animation = cursors.at(static_cast<size_t>(type));
while (newFrame >= animation->size())
newFrame -= animation->size();
changeGraphic(Cursor::Type::SPELLBOOK, newFrame);
}
void CursorHandler::render()
{
if(!showing)
return;
if (type == Cursor::Type::SPELLBOOK)
updateSpellcastCursor();
cursor->render();
}
void CursorSoftware::render()
{
//texture must be updated in the main (renderer) thread, but changes to cursor type may come from other threads
if (needUpdate)
updateTexture();
Point renderPos = pos - pivot;
SDL_Rect destRect;
destRect.x = renderPos.x;
destRect.y = renderPos.y;
destRect.w = 40;
destRect.h = 40;
SDL_RenderCopy(mainRenderer, cursorTexture, nullptr, &destRect);
}
void CursorSoftware::createTexture(const Point & dimensions)
{
if(cursorTexture)
SDL_DestroyTexture(cursorTexture);
if (cursorSurface)
SDL_FreeSurface(cursorSurface);
cursorSurface = CSDL_Ext::newSurface(dimensions.x, dimensions.y);
cursorTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, dimensions.x, dimensions.y);
SDL_SetSurfaceBlendMode(cursorSurface, SDL_BLENDMODE_NONE);
SDL_SetTextureBlendMode(cursorTexture, SDL_BLENDMODE_BLEND);
}
void CursorSoftware::updateTexture()
{
Point dimensions(-1, -1);
if (!cursorSurface || Point(cursorSurface->w, cursorSurface->h) != cursorImage->dimensions())
createTexture(cursorImage->dimensions());
Uint32 fillColor = SDL_MapRGBA(cursorSurface->format, 0, 0, 0, 0);
CSDL_Ext::fillRect(cursorSurface, nullptr, fillColor);
cursorImage->draw(cursorSurface);
SDL_UpdateTexture(cursorTexture, NULL, cursorSurface->pixels, cursorSurface->pitch);
needUpdate = false;
}
void CursorSoftware::setImage(std::shared_ptr<IImage> image, const Point & pivotOffset)
{
assert(image != nullptr);
cursorImage = image;
pivot = pivotOffset;
needUpdate = true;
}
void CursorSoftware::setCursorPosition( const Point & newPos )
{
pos = newPos;
}
CursorSoftware::CursorSoftware():
cursorTexture(nullptr),
cursorSurface(nullptr),
needUpdate(false),
pivot(0,0)
{
SDL_ShowCursor(SDL_DISABLE);
}
CursorSoftware::~CursorSoftware()
{
if(cursorTexture)
SDL_DestroyTexture(cursorTexture);
if (cursorSurface)
SDL_FreeSurface(cursorSurface);
}
CursorHardware::CursorHardware():
cursor(nullptr)
{
}
CursorHardware::~CursorHardware()
{
if(cursor)
SDL_FreeCursor(cursor);
}
void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivotOffset)
{
auto cursorSurface = CSDL_Ext::newSurface(image->dimensions().x, image->dimensions().y);
Uint32 fillColor = SDL_MapRGBA(cursorSurface->format, 0, 0, 0, 0);
CSDL_Ext::fillRect(cursorSurface, nullptr, fillColor);
image->draw(cursorSurface);
auto oldCursor = cursor;
cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y);
if (!cursor)
logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError());
SDL_FreeSurface(cursorSurface);
SDL_SetCursor(cursor);
if (oldCursor)
SDL_FreeCursor(oldCursor);
}
void CursorHardware::setCursorPosition( const Point & newPos )
{
//no-op
}
void CursorHardware::render()
{
//no-op
}

View File

@ -8,11 +8,14 @@
*
*/
#pragma once
class CIntObject;
class CAnimImage;
class CAnimation;
class IImage;
struct SDL_Surface;
struct SDL_Texture;
struct Point;
struct SDL_Cursor;
#include "Geometries.h"
namespace Cursor
{
@ -51,7 +54,9 @@ namespace Cursor
SHOOT_CATAPULT = 16,
HEAL = 17,
SACRIFICE = 18,
TELEPORT = 19
TELEPORT = 19,
COUNT
};
enum class Map {
@ -97,7 +102,9 @@ namespace Cursor
SCROLL_NORTHWEST = 39,
//POINTER_COPY = 40, // probably unused
TELEPORT = 41,
SCUTTLE_BOAT = 42
SCUTTLE_BOAT = 42,
COUNT
};
enum class Spellcast {
@ -105,49 +112,92 @@ namespace Cursor
};
}
/// handles mouse cursor
class CCursorHandler final
class ICursor
{
public:
virtual ~ICursor() = default;
virtual void setImage(std::shared_ptr<IImage> image, const Point & pivotOffset) = 0;
virtual void setCursorPosition( const Point & newPos ) = 0;
virtual void render() = 0;
};
class CursorHardware : public ICursor
{
std::shared_ptr<IImage> cursorImage;
SDL_Cursor * cursor;
public:
CursorHardware();
~CursorHardware();
void setImage(std::shared_ptr<IImage> image, const Point & pivotOffset) override;
void setCursorPosition( const Point & newPos ) override;
void render() override;
};
class CursorSoftware : public ICursor
{
std::shared_ptr<IImage> cursorImage;
SDL_Texture * cursorTexture;
SDL_Surface * cursorSurface;
Point pos;
Point pivot;
bool needUpdate;
SDL_Texture * cursorLayer;
SDL_Surface * buffer;
CAnimImage * currentCursor;
void createTexture(const Point & dimensions);
void updateTexture();
public:
CursorSoftware();
~CursorSoftware();
std::unique_ptr<CAnimImage> dndObject; //if set, overrides currentCursor
void setImage(std::shared_ptr<IImage> image, const Point & pivotOffset) override;
void setCursorPosition( const Point & newPos ) override;
void render() override;
};
std::array<std::unique_ptr<CAnimImage>, 4> cursors;
/// handles mouse cursor
class CursorHandler final
{
std::shared_ptr<IImage> dndObject; //if set, overrides currentCursor
std::array<std::unique_ptr<CAnimation>, 4> cursors;
bool showing;
void clearBuffer();
void updateBuffer(CIntObject * payload);
void replaceBuffer(CIntObject * payload);
void shiftPos( int &x, int &y );
void updateTexture();
/// Current cursor
Cursor::Type type;
size_t frame;
float frameTime;
Point pos;
void changeGraphic(Cursor::Type type, size_t index);
/// position of cursor
int xpos, ypos;
Point getPivotOffsetDefault(size_t index);
Point getPivotOffsetMap(size_t index);
Point getPivotOffsetCombat(size_t index);
Point getPivotOffsetSpellcast();
Point getPivotOffset();
void updateSpellcastCursor();
std::shared_ptr<IImage> getCurrentImage();
std::unique_ptr<ICursor> cursor;
static std::unique_ptr<ICursor> createCursor();
public:
CCursorHandler();
~CCursorHandler();
CursorHandler();
~CursorHandler();
/**
* Replaces the cursor with a custom image.
*
* @param image Image to replace cursor with or nullptr to use the normal
* cursor. CursorHandler takes ownership of object
*/
void dragAndDropCursor (std::unique_ptr<CAnimImage> image);
/// Replaces the cursor with a custom image.
/// @param image Image to replace cursor with or nullptr to use the normal cursor.
void dragAndDropCursor(std::shared_ptr<IImage> image);
void dragAndDropCursor(std::string path, size_t index);
/// Returns current position of the cursor
Point position() const;

View File

@ -29,6 +29,7 @@
#include "../../lib/rmg/CMapGenOptions.h"
#include "../../lib/CModHandler.h"
#include "../../lib/rmg/CRmgTemplateStorage.h"
#include "../../lib/RoadHandler.h"
RandomMapTab::RandomMapTab():
InterfaceObjectConfigurable()
@ -108,12 +109,12 @@ RandomMapTab::RandomMapTab():
GH.pushIntT<TeamAlignmentsWidget>(*this);
});
for(auto road : VLC->terrainTypeHandler->roads())
for(auto road : VLC->roadTypeHandler->objects)
{
std::string cbRoadType = "selectRoad_" + road.name;
std::string cbRoadType = "selectRoad_" + road->getJsonKey();
addCallback(cbRoadType, [&, road](bool on)
{
mapGenOptions->setRoadEnabled(road.name, on);
mapGenOptions->setRoadEnabled(road->getJsonKey(), on);
updateMapInfoByHost();
});
}
@ -283,11 +284,11 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
else
w->addTextOverlay(readText(variables["defaultTemplate"]), EFonts::FONT_SMALL);
}
for(auto r : VLC->terrainTypeHandler->roads())
for(auto r : VLC->roadTypeHandler->objects)
{
if(auto w = widget<CToggleButton>(r.name))
if(auto w = widget<CToggleButton>(r->getJsonKey()))
{
w->setSelected(opts->isRoadEnabled(r.name));
w->setSelected(opts->isRoadEnabled(r->getJsonKey()));
}
}
}

View File

@ -21,7 +21,7 @@
#include "../../lib/filesystem/CCompressedStream.h"
#include "../gui/SDL_Extensions.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../CGameInfo.h"
#include "../../lib/CGeneralTextHandler.h"

View File

@ -30,7 +30,9 @@
#include "CMT.h"
#include "CMusicHandler.h"
#include "../lib/CRandomGenerator.h"
#include "../lib/Terrain.h"
#include "../lib/RoadHandler.h"
#include "../lib/RiverHandler.h"
#include "../lib/TerrainHandler.h"
#include "../lib/filesystem/ResourceID.h"
#include "../lib/JsonDetail.h"
@ -175,17 +177,17 @@ void CMapHandler::initTerrainGraphics()
std::map<std::string, std::string> terrainFiles;
std::map<std::string, std::string> riverFiles;
std::map<std::string, std::string> roadFiles;
for(const auto & terrain : VLC->terrainTypeHandler->terrains())
for(const auto & terrain : VLC->terrainTypeHandler->objects)
{
terrainFiles[terrain.name] = terrain.tilesFilename;
terrainFiles[terrain->getJsonKey()] = terrain->tilesFilename;
}
for(const auto & river : VLC->terrainTypeHandler->rivers())
for(const auto & river : VLC->riverTypeHandler->objects)
{
riverFiles[river.fileName] = river.fileName;
riverFiles[river->getJsonKey()] = river->tilesFilename;
}
for(const auto & road : VLC->terrainTypeHandler->roads())
for(const auto & road : VLC->roadTypeHandler->objects)
{
roadFiles[road.fileName] = road.fileName;
roadFiles[road->getJsonKey()] = road->tilesFilename;
}
loadFlipped(terrainAnimations, terrainImages, terrainFiles);
@ -606,7 +608,7 @@ void CMapHandler::CMapBlitter::drawTileTerrain(SDL_Surface * targetSurf, const T
ui8 rotation = tinfo.extTileFlags % 4;
//TODO: use ui8 instead of string key
auto terrainName = tinfo.terType->name;
auto terrainName = tinfo.terType->getJsonKey();
if(parent->terrainImages[terrainName].size()<=tinfo.terView)
return;
@ -786,21 +788,21 @@ void CMapHandler::CMapBlitter::drawObjects(SDL_Surface * targetSurf, const Terra
void CMapHandler::CMapBlitter::drawRoad(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile * tinfoUpper) const
{
if (tinfoUpper && tinfoUpper->roadType->id != Road::NO_ROAD)
if (tinfoUpper && tinfoUpper->roadType->getId() != Road::NO_ROAD)
{
ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4;
Rect source(0, tileSize / 2, tileSize, tileSize / 2);
Rect dest(realPos.x, realPos.y, tileSize, tileSize / 2);
drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType->fileName][tinfoUpper->roadDir][rotation],
drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType->getJsonKey()][tinfoUpper->roadDir][rotation],
&source, targetSurf, &dest);
}
if(tinfo.roadType->id != Road::NO_ROAD) //print road from this tile
if(tinfo.roadType->getId() != Road::NO_ROAD) //print road from this tile
{
ui8 rotation = (tinfo.extTileFlags >> 4) % 4;
Rect source(0, 0, tileSize, halfTileSizeCeil);
Rect dest(realPos.x, realPos.y + tileSize / 2, tileSize, tileSize / 2);
drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType->fileName][tinfo.roadDir][rotation],
drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType->getJsonKey()][tinfo.roadDir][rotation],
&source, targetSurf, &dest);
}
}
@ -809,7 +811,7 @@ void CMapHandler::CMapBlitter::drawRiver(SDL_Surface * targetSurf, const Terrain
{
Rect destRect(realTileRect);
ui8 rotation = (tinfo.extTileFlags >> 2) % 4;
drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType->fileName][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect);
drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType->getJsonKey()][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect);
}
void CMapHandler::CMapBlitter::drawFow(SDL_Surface * targetSurf) const
@ -860,7 +862,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
if(isVisible || info->showAllTerrain)
{
drawTileTerrain(targetSurf, tinfo, tile);
if(tinfo.riverType->id != River::NO_RIVER)
if(tinfo.riverType->getId() != River::NO_RIVER)
drawRiver(targetSurf, tinfo);
drawRoad(targetSurf, tinfo, tinfoUpper);
}
@ -1388,8 +1390,9 @@ void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRM
break;
}
}
if(!isTile2Terrain || out.empty())
out = CGI->generaltexth->terrainNames[t.terType->id];
out = t.terType->getNameTranslated();
if(t.getDiggingStatus(false) == EDiggingStatus::CAN_DIG)
{
@ -1485,4 +1488,3 @@ TerrainTileObject::TerrainTileObject(const CGObjectInstance * obj_, SDL_Rect rec
TerrainTileObject::~TerrainTileObject()
{
}

View File

@ -41,13 +41,14 @@
#include "../../lib/CHeroHandler.h"
#include "../../lib/CModHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/Terrain.h"
#include "../../lib/TerrainHandler.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/JsonNode.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#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),
@ -390,7 +391,7 @@ const SDL_Color & CMinimapInstance::getTileColor(const int3 & pos)
}
// else - use terrain color (blocked version or normal)
const auto & colorPair = parent->colors.find(tile->terType->id)->second;
const auto & colorPair = parent->colors.find(tile->terType->getId())->second;
if (tile->blocked && (!tile->visitable))
return colorPair.second;
else
@ -499,25 +500,25 @@ std::map<TerrainId, std::pair<SDL_Color, SDL_Color> > CMinimap::loadColors()
{
std::map<TerrainId, std::pair<SDL_Color, SDL_Color> > ret;
for(const auto & terrain : CGI->terrainTypeHandler->terrains())
for(const auto & terrain : CGI->terrainTypeHandler->objects)
{
SDL_Color normal =
{
ui8(terrain.minimapUnblocked[0]),
ui8(terrain.minimapUnblocked[1]),
ui8(terrain.minimapUnblocked[2]),
ui8(terrain->minimapUnblocked[0]),
ui8(terrain->minimapUnblocked[1]),
ui8(terrain->minimapUnblocked[2]),
ui8(255)
};
SDL_Color blocked =
{
ui8(terrain.minimapBlocked[0]),
ui8(terrain.minimapBlocked[1]),
ui8(terrain.minimapBlocked[2]),
ui8(terrain->minimapBlocked[0]),
ui8(terrain->minimapBlocked[1]),
ui8(terrain->minimapBlocked[2]),
ui8(255)
};
ret[terrain.id] = std::make_pair(normal, blocked);
ret[terrain->getId()] = std::make_pair(normal, blocked);
}
return ret;
}
@ -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

@ -11,7 +11,6 @@
#include "ObjectLists.h"
#include "../../lib/FunctionList.h"
#include "Terrain.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -424,7 +423,7 @@ public:
void textEdited(const SDL_TextEditingEvent & event) override;
void startEnteringText();
void endEnteringText(bool printEnteredText);
void endEnteringText(bool processEnteredText);
void refreshEnteredText();
CInGameConsole();

View File

@ -11,7 +11,7 @@
#include "CArtifactHolder.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "Buttons.h"
#include "CComponent.h"
@ -257,7 +257,7 @@ void CHeroArtPlace::clickRight(tribool down, bool previousState)
void CArtifactsOfHero::activate()
{
if (commonInfo->src.AOH == this && commonInfo->src.art)
CCS->curh->dragAndDropCursor(std::make_unique<CAnimImage>("artifact", commonInfo->src.art->artType->getIconIndex()));
CCS->curh->dragAndDropCursor("artifact", commonInfo->src.art->artType->getIconIndex());
CIntObject::activate();
}
@ -278,19 +278,18 @@ void CHeroArtPlace::select ()
if (locked)
return;
selectSlot(true);
pickSlot(true);
if(ourArt->canBeDisassembled() && slotID < GameConstants::BACKPACK_START) //worn combined artifact -> locks have to disappear
{
for(int i = 0; i < GameConstants::BACKPACK_START; i++)
for(auto slot : ArtifactUtils::constituentWornSlots())
{
auto ap = ourOwner->getArtPlace(i);
auto ap = ourOwner->getArtPlace(slot);
if(ap)//getArtPlace may return null
ap->pickSlot(ourArt->isPart(ap->ourArt));
}
}
CCS->curh->dragAndDropCursor(std::make_unique<CAnimImage>("artifact", ourArt->artType->getIconIndex()));
CCS->curh->dragAndDropCursor("artifact", ourArt->artType->getIconIndex());
ourOwner->commonInfo->src.setTo(this, false);
ourOwner->markPossibleSlots(ourArt);
@ -309,9 +308,9 @@ void CHeroArtPlace::deselect ()
pickSlot(false);
if(ourArt && ourArt->canBeDisassembled()) //combined art returned to its slot -> restore locks
{
for(int i = 0; i < GameConstants::BACKPACK_START; i++)
for(auto slot : ArtifactUtils::constituentWornSlots())
{
auto place = ourOwner->getArtPlace(i);
auto place = ourOwner->getArtPlace(slot);
if(nullptr != place)//getArtPlace may return null
place->pickSlot(false);
@ -670,6 +669,16 @@ CArtifactsOfHero::CArtifactsOfHero(const Point & position, bool createCommonPart
CArtifactsOfHero::~CArtifactsOfHero()
{
dispose();
// Artifact located in artifactsTransitionPos should be returned
if(!curHero->artifactsTransitionPos.empty())
{
auto artPlace = getArtPlace(
ArtifactUtils::getArtifactDstPosition(curHero->artifactsTransitionPos.begin()->artifact, curHero, curHero->bearerType()));
assert(artPlace);
assert(artPlace->ourOwner);
artPlace->setMeAsDest();
artPlace->ourOwner->realizeCurrentTransaction();
}
}
void CArtifactsOfHero::updateParentWindow()
@ -716,85 +725,76 @@ void CArtifactsOfHero::realizeCurrentTransaction()
ArtifactLocation(commonInfo->dst.AOH->curHero, commonInfo->dst.slotID));
}
void CArtifactsOfHero::artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst)
void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst)
{
bool isCurHeroSrc = src.isHolder(curHero),
isCurHeroDst = dst.isHolder(curHero);
if(isCurHeroSrc && src.slot >= GameConstants::BACKPACK_START)
if(isCurHeroSrc && ArtifactUtils::isSlotBackpack(src.slot))
updateSlot(src.slot);
if(isCurHeroDst && dst.slot >= GameConstants::BACKPACK_START)
if(isCurHeroDst && ArtifactUtils::isSlotBackpack(dst.slot))
updateSlot(dst.slot);
if(isCurHeroSrc || isCurHeroDst) //we need to update all slots, artifact might be combined and affect more slots
// We need to update all slots, artifact might be combined and affect more slots
if(isCurHeroSrc || isCurHeroDst)
updateWornSlots(false);
if (!src.isHolder(curHero) && !isCurHeroDst)
if(!isCurHeroSrc && !isCurHeroDst)
return;
if(commonInfo->src == src) //artifact was taken from us
// When moving one artifact onto another it leads to two art movements: dst->TRANSITION_POS; src->dst
// however after first movement we pick the art from TRANSITION_POS and the second movement coming when
// we have a different artifact may look surprising... but it's valid.
// Used when doing dragAndDrop and artifact swap multiple times
if(src.slot == ArtifactPosition::TRANSITION_POS &&
commonInfo->src.slotID == ArtifactPosition::TRANSITION_POS &&
commonInfo->dst.slotID == ArtifactPosition::PRE_FIRST &&
isCurHeroDst)
{
assert(commonInfo->dst == dst //expected movement from slot ot slot
|| dst.slot == dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START //artifact moved back to backpack (eg. to make place for art we are moving)
auto art = curHero->getArt(ArtifactPosition::TRANSITION_POS);
assert(art);
CCS->curh->dragAndDropCursor("artifact", art->artType->getIconIndex());
markPossibleSlots(art);
commonInfo->src.art = art;
commonInfo->src.slotID = src.slot;
}
// Artifact was taken from us
else if(commonInfo->src == src)
{
// Expected movement from slot ot slot
assert(commonInfo->dst == dst
// Artifact moved back to backpack (eg. to make place for art we are moving)
|| dst.slot == dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START
|| dst.getHolderArtSet()->bearerType() != ArtBearer::HERO);
commonInfo->reset();
unmarkSlots();
}
else if(commonInfo->dst == src) //the dest artifact was moved -> we are picking it
// The dest artifact was moved after the swap -> we are picking it
else if(commonInfo->dst == src)
{
assert(dst.slot >= GameConstants::BACKPACK_START);
assert(dst.slot == ArtifactPosition::TRANSITION_POS);
commonInfo->reset();
CArtifactsOfHero::ArtPlacePtr ap;
for(CArtifactsOfHero *aoh : commonInfo->participants)
for(CArtifactsOfHero * aoh : commonInfo->participants)
{
if(dst.isHolder(aoh->curHero))
{
commonInfo->src.AOH = aoh;
if((ap = aoh->getArtPlace(dst.slot)))//getArtPlace may return null
break;
break;
}
}
if(ap)
{
ap->select();
}
else
{
commonInfo->src.art = dst.getArt();
commonInfo->src.slotID = dst.slot;
assert(commonInfo->src.AOH);
CCS->curh->dragAndDropCursor(std::make_unique<CAnimImage>("artifact", dst.getArt()->artType->getIconIndex()));
markPossibleSlots(dst.getArt());
}
}
else if(src.slot >= GameConstants::BACKPACK_START &&
src.slot < commonInfo->src.slotID &&
src.isHolder(commonInfo->src.AOH->curHero)) //artifact taken from before currently picked one
{
//int fixedSlot = src.hero->getArtPos(commonInfo->src.art);
vstd::advance(commonInfo->src.slotID, -1);
assert(commonInfo->src.valid());
}
else
{
//when moving one artifact onto another it leads to two art movements: dst->backapck; src->dst
// however after first movement we pick the art from backpack and the second movement coming when
// we have a different artifact may look surprising... but it's valid.
commonInfo->src.art = dst.getArt();
commonInfo->src.slotID = dst.slot;
assert(commonInfo->src.AOH);
CCS->curh->dragAndDropCursor("artifact", dst.getArt()->artType->getIconIndex());
}
updateParentWindow();
int shift = 0;
// if(dst.slot >= Arts::BACKPACK_START && dst.slot - Arts::BACKPACK_START < backpackPos)
// shift++;
//
if(src.slot < GameConstants::BACKPACK_START && dst.slot - GameConstants::BACKPACK_START < backpackPos)
shift++;
if(dst.slot < GameConstants::BACKPACK_START && src.slot - GameConstants::BACKPACK_START < backpackPos)
shift--;
if( (isCurHeroSrc && src.slot >= GameConstants::BACKPACK_START)
|| (isCurHeroDst && dst.slot >= GameConstants::BACKPACK_START) )
scrollBackpack(shift); //update backpack slots
// If backpack is changed, update it
if((isCurHeroSrc && ArtifactUtils::isSlotBackpack(src.slot))
|| (isCurHeroDst && ArtifactUtils::isSlotBackpack(dst.slot)))
scrollBackpack(0);
}
void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al)
@ -808,11 +808,15 @@ void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al)
}
}
CArtifactsOfHero::ArtPlacePtr CArtifactsOfHero::getArtPlace(int slot)
CArtifactsOfHero::ArtPlacePtr CArtifactsOfHero::getArtPlace(ArtifactPosition slot)
{
if(slot == ArtifactPosition::TRANSITION_POS)
{
return nullptr;
}
if(slot < GameConstants::BACKPACK_START)
{
if(artWorn.find(ArtifactPosition(slot)) == artWorn.end())
if(artWorn.find(slot) == artWorn.end())
{
logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot);
return nullptr;

View File

@ -141,7 +141,7 @@ public:
void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst);
void artifactRemoved(const ArtifactLocation &al);
void artifactUpdateSlots(const ArtifactLocation &al);
ArtPlacePtr getArtPlace(int slot);//may return null
ArtPlacePtr getArtPlace(ArtifactPosition slot);//may return null
void setHero(const CGHeroInstance * hero);
const CGHeroInstance *getHero() const;

View File

@ -17,7 +17,7 @@
#include <vcmi/spells/Spell.h>
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../CMessage.h"
#include "../CGameInfo.h"

View File

@ -15,7 +15,8 @@
#include "../gui/CAnimation.h"
#include "../gui/SDL_Pixels.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/ColorFilter.h"
#include "../battle/BattleInterface.h"
#include "../battle/BattleInterfaceClasses.h"
@ -339,7 +340,7 @@ void CAnimImage::playerColored(PlayerColor currPlayer)
anim->getImage(0, group)->playerColored(player);
}
CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Delay, size_t Group):
CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Delay, size_t Group, uint8_t alpha):
anim(std::make_shared<CAnimation>(name)),
group(Group),
frame(0),
@ -349,7 +350,7 @@ CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Del
flags(Flags),
xOffset(0),
yOffset(0),
alpha(255)
alpha(alpha)
{
anim->loadGroup(group);
last = anim->size(group);
@ -454,7 +455,12 @@ void CShowableAnim::blitImage(size_t frame, size_t group, SDL_Surface *to)
Rect src( xOffset, yOffset, pos.w, pos.h);
auto img = anim->getImage(frame, group);
if(img)
img->draw(to, pos.x, pos.y, &src, alpha);
{
const ColorFilter alphaFilter = ColorFilter::genAlphaShifter(vstd::lerp(0.0f, 1.0f, alpha/255.0f));
img->adjustPalette(alphaFilter);
img->draw(to, pos.x, pos.y, &src);
}
}
void CShowableAnim::rotate(bool on, bool vertical)

View File

@ -142,7 +142,7 @@ public:
//Set per-surface alpha, 0 = transparent, 255 = opaque
void setAlpha(ui32 alphaValue);
CShowableAnim(int x, int y, std::string name, ui8 flags=0, ui32 Delay=4, size_t Group=0);
CShowableAnim(int x, int y, std::string name, ui8 flags=0, ui32 Delay=4, size_t Group=0, uint8_t alpha = UINT8_MAX);
~CShowableAnim();
//set animation to group or part of group

View File

@ -13,7 +13,7 @@
#include "CComponent.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../CPlayerInterface.h"
#include "../CMessage.h"

View File

@ -524,9 +524,6 @@ CKeyboardFocusListener::CKeyboardFocusListener(CTextInput * textInput)
void CKeyboardFocusListener::focusGot()
{
CSDL_Ext::startTextInput(&textInput->pos);
#ifdef VCMI_ANDROID
textInput->notifyAndroidTextInputChanged(textInput->text);
#endif
usageIndex++;
}
@ -588,9 +585,6 @@ void CTextInput::keyPressed(const SDL_KeyboardEvent & key)
{
redraw();
cb(text);
#ifdef VCMI_ANDROID
notifyAndroidTextInputChanged(text);
#endif
}
}
@ -604,10 +598,6 @@ void CTextInput::setText(const std::string & nText, bool callCb)
CLabel::setText(nText);
if(callCb)
cb(text);
#ifdef VCMI_ANDROID
notifyAndroidTextInputChanged(text);
#endif
}
bool CTextInput::captureThisEvent(const SDL_KeyboardEvent & key)
@ -633,10 +623,6 @@ void CTextInput::textInputed(const SDL_TextInputEvent & event)
cb(text);
}
newText.clear();
#ifdef VCMI_ANDROID
notifyAndroidTextInputChanged(text);
#endif
}
void CTextInput::textEdited(const SDL_TextEditingEvent & event)
@ -647,11 +633,6 @@ void CTextInput::textEdited(const SDL_TextEditingEvent & event)
newText = event.text;
redraw();
cb(text + newText);
#ifdef VCMI_ANDROID
auto editedText = text + newText;
notifyAndroidTextInputChanged(editedText);
#endif
}
void CTextInput::filenameFilter(std::string & text, const std::string &)
@ -698,24 +679,6 @@ void CTextInput::numberFilter(std::string & text, const std::string & oldText, i
}
}
#ifdef VCMI_ANDROID
void CTextInput::notifyAndroidTextInputChanged(std::string & text)
{
if(!focus)
return;
auto fun = [&text](JNIEnv * env, jclass cls, jmethodID method)
{
auto jtext = env->NewStringUTF(text.c_str());
env->CallStaticVoidMethod(cls, method, jtext);
env->DeleteLocalRef(jtext);
};
CAndroidVMHelper vmHelper;
vmHelper.callCustomMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "notifyTextInputChanged",
"(Ljava/lang/String;)V", fun, true);
}
#endif //VCMI_ANDROID
CFocusable::CFocusable()
:CFocusable(std::make_shared<IFocusListener>())
{

View File

@ -212,9 +212,6 @@ class CTextInput : public CLabel, public CFocusable
protected:
std::string visibleText() override;
#ifdef VCMI_ANDROID
void notifyAndroidTextInputChanged(std::string & text);
#endif
public:
CFunctionList<void(const std::string &)> cb;
CFunctionList<void(std::string &, const std::string &)> filters;

View File

@ -32,7 +32,7 @@
#include "../mapHandler.h"
#include "../gui/CAnimation.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/SDL_Extensions.h"
#include "../widgets/MiscWidgets.h"
@ -53,6 +53,7 @@
#include "../../lib/VCMI_Lib.h"
#include "../../lib/StartInfo.h"
#include "../../lib/mapping/CMapInfo.h"
#include "../../lib/TerrainHandler.h"
#define ADVOPT (conf.go()->ac)
using namespace CSDL_Ext;
@ -1038,7 +1039,7 @@ void CAdvMapInt::show(SDL_Surface * to)
{
++heroAnim;
}
if(animValHitCount == 8)
if(animValHitCount >= 8)
{
CGI->mh->updateWater();
animValHitCount = 0;
@ -1414,7 +1415,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView)
auto pos = sel->visitablePos();
auto tile = LOCPLINT->cb->getTile(pos);
if(tile)
CCS->musich->playMusicFromSet("terrain", tile->terType->name, true, false);
CCS->musich->playMusicFromSet("terrain", tile->terType->getJsonKey(), true, false);
}
if(centerView)
centerOn(sel);

View File

@ -44,15 +44,29 @@
CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town, const CStructure * Str)
: CShowableAnim(0, 0, Str->defName, CShowableAnim::BASE),
parent(Par),
town(Town),
str(Str),
stateCounter(80)
parent(Par),
town(Town),
str(Str),
stateTimeCounter(BUILD_ANIMATION_FINISHED_TIMEPOINT)
{
addUsedEvents(LCLICK | RCLICK | HOVER);
pos.x += str->pos.x;
pos.y += str->pos.y;
// special animation frame manipulation for castle shipyard with and without ship
// done due to .def used in special way, not to animate building - first image is for shipyard without citadel moat, 2nd image is for including moat
if(Town->town->faction->getId() == FactionID::CASTLE && Str->building &&
(Str->building->bid == BuildingID::SHIPYARD || Str->building->bid == BuildingID::SHIP))
{
if(Town->hasBuilt(BuildingID::CITADEL))
{
this->first = 1;
this->frame = 1;
}
else
this->last = 0;
}
if(!str->borderName.empty())
border = BitmapHandler::loadBitmap(str->borderName);
else
@ -154,16 +168,11 @@ SDL_Color multiplyColors(const SDL_Color & b, const SDL_Color & a, double f)
void CBuildingRect::show(SDL_Surface * to)
{
const ui32 stageDelay = 16;
uint32_t stageDelay = BUILDING_APPEAR_TIMEPOINT;
const ui32 S1_TRANSP = 16; //0.5 sec building appear 0->100 transparency
const ui32 S2_WHITE_B = 32; //0.5 sec border glows from white to yellow
const ui32 S3_YELLOW_B= 48; //0.5 sec border glows from yellow to normal
const ui32 BUILDED = 80; // 1 sec delay, nothing happens
if(stateCounter < S1_TRANSP)
if(stateTimeCounter < BUILDING_APPEAR_TIMEPOINT)
{
setAlpha(255*stateCounter/stageDelay);
setAlpha(255 * stateTimeCounter / stageDelay);
CShowableAnim::show(to);
}
else
@ -172,9 +181,9 @@ void CBuildingRect::show(SDL_Surface * to)
CShowableAnim::show(to);
}
if(border && stateCounter > S1_TRANSP)
if(border && stateTimeCounter > BUILDING_APPEAR_TIMEPOINT)
{
if(stateCounter == BUILDED)
if(stateTimeCounter >= BUILD_ANIMATION_FINISHED_TIMEPOINT)
{
if(parent->selectedBuilding == this)
blitAtLoc(border,0,0,to);
@ -191,11 +200,11 @@ void CBuildingRect::show(SDL_Surface * to)
SDL_Color oldColor = border->format->palette->colors[colorID];
SDL_Color newColor;
if (stateCounter < S2_WHITE_B)
newColor = multiplyColors(c1, c2, static_cast<double>(stateCounter % stageDelay) / stageDelay);
if (stateTimeCounter < BUILDING_WHITE_BORDER_TIMEPOINT)
newColor = multiplyColors(c1, c2, static_cast<double>(stateTimeCounter % stageDelay) / stageDelay);
else
if (stateCounter < S3_YELLOW_B)
newColor = multiplyColors(c2, c3, static_cast<double>(stateCounter % stageDelay) / stageDelay);
if (stateTimeCounter < BUILDING_YELLOW_BORDER_TIMEPOINT)
newColor = multiplyColors(c2, c3, static_cast<double>(stateTimeCounter % stageDelay) / stageDelay);
else
newColor = oldColor;
@ -204,13 +213,13 @@ void CBuildingRect::show(SDL_Surface * to)
SDL_SetColors(border, &oldColor, colorID, 1);
}
}
if(stateCounter < BUILDED)
stateCounter++;
if(stateTimeCounter < BUILD_ANIMATION_FINISHED_TIMEPOINT)
stateTimeCounter += GH.mainFPSmng->getElapsedMilliseconds();
}
void CBuildingRect::showAll(SDL_Surface * to)
{
if (stateCounter == 0)
if (stateTimeCounter == 0)
return;
CShowableAnim::showAll(to);
@ -632,9 +641,9 @@ void CCastleBuildings::addBuilding(BuildingID building)
{
//reset animation
if(structures.size() == 1)
buildingRect->stateCounter = 0; // transparency -> fully visible stage
buildingRect->stateTimeCounter = 0; // transparency -> fully visible stage
else
buildingRect->stateCounter = 16; // already in fully visible stage
buildingRect->stateTimeCounter = CBuildingRect::BUILDING_APPEAR_TIMEPOINT; // already in fully visible stage
break;
}
}

View File

@ -42,6 +42,14 @@ class CBuildingRect : public CShowableAnim
{
std::string getSubtitle();
public:
enum EBuildingCreationAnimationPhases : uint32_t
{
BUILDING_APPEAR_TIMEPOINT = 500, //500 msec building appears: 0->100% transparency
BUILDING_WHITE_BORDER_TIMEPOINT = 1000, //500 msec border glows from white to yellow
BUILDING_YELLOW_BORDER_TIMEPOINT = 1500, //500 msec border glows from yellow to normal
BUILD_ANIMATION_FINISHED_TIMEPOINT = 2500 //1000 msec delay, nothing happens
};
/// returns building associated with this structure
const CBuilding * getBuilding();
@ -51,7 +59,7 @@ public:
SDL_Surface* border;
SDL_Surface* area;
ui32 stateCounter;//For building construction - current stage in animation
ui32 stateTimeCounter;//For building construction - current stage in animation
CBuildingRect(CCastleBuildings * Par, const CGTownInstance *Town, const CStructure *Str);
~CBuildingRect();

View File

@ -13,7 +13,7 @@
#include "CAdvmapInterface.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../widgets/Images.h"
#include "../CGameInfo.h"
@ -188,7 +188,7 @@ void CTradeWindow::CTradeableItem::clickLeft(tribool down, bool previousState)
aw->arts->markPossibleSlots(art);
//aw->arts->commonInfo->dst.AOH = aw->arts;
CCS->curh->dragAndDropCursor(std::make_unique<CAnimImage>("artifact", art->artType->iconIndex));
CCS->curh->dragAndDropCursor("artifact", art->artType->iconIndex);
aw->arts->artifactsOnAltar.erase(art);
setID(-1);

View File

@ -18,7 +18,7 @@
#include "../gui/SDL_Pixels.h"
#include "../gui/SDL_Extensions.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../battle/BattleInterface.h"
#include "../battle/BattleInterfaceClasses.h"

View File

@ -32,7 +32,7 @@
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../gui/SDL_Extensions.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../widgets/CComponent.h"
#include "../widgets/MiscWidgets.h"
@ -1247,12 +1247,6 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
updateWidgets();
}
CExchangeWindow::~CExchangeWindow()
{
artifs[0]->commonInfo = nullptr;
artifs[1]->commonInfo = nullptr;
}
const CGarrisonSlot * CExchangeWindow::getSelectedSlotID() const
{
return garr->getSelection();

View File

@ -379,7 +379,6 @@ public:
const CGarrisonSlot * getSelectedSlotID() const;
CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID);
~CExchangeWindow();
};
/// Here you can buy ships

View File

@ -24,7 +24,7 @@
#include "../gui/SDL_Pixels.h"
#include "../gui/SDL_Extensions.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CursorHandler.h"
#include "../battle/BattleInterface.h"
#include "../battle/BattleInterfaceClasses.h"

View File

@ -192,8 +192,10 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/ObstacleHandler.cpp
${MAIN_LIB_DIR}/StartInfo.cpp
${MAIN_LIB_DIR}/ResourceSet.cpp
${MAIN_LIB_DIR}/RiverHandler.cpp
${MAIN_LIB_DIR}/RoadHandler.cpp
${MAIN_LIB_DIR}/ScriptHandler.cpp
${MAIN_LIB_DIR}/Terrain.cpp
${MAIN_LIB_DIR}/TerrainHandler.cpp
${MAIN_LIB_DIR}/VCMIDirs.cpp
${MAIN_LIB_DIR}/VCMI_Lib.cpp
@ -441,11 +443,13 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/ObstacleHandler.h
${MAIN_LIB_DIR}/PathfinderUtil.h
${MAIN_LIB_DIR}/ResourceSet.h
${MAIN_LIB_DIR}/RiverHandler.h
${MAIN_LIB_DIR}/RoadHandler.h
${MAIN_LIB_DIR}/ScriptHandler.h
${MAIN_LIB_DIR}/ScopeGuard.h
${MAIN_LIB_DIR}/StartInfo.h
${MAIN_LIB_DIR}/StringConstants.h
${MAIN_LIB_DIR}/Terrain.h
${MAIN_LIB_DIR}/TerrainHandler.h
${MAIN_LIB_DIR}/UnlockGuard.h
${MAIN_LIB_DIR}/VCMIDirs.h
${MAIN_LIB_DIR}/vcmi_endian.h

View File

@ -13,7 +13,7 @@ class VCMI(ConanFile):
"minizip/[~1.2.12]",
"onetbb/[^2021.3]", # Nullkiller AI
"qt/[~5.15.2]", # launcher
"sdl/[~2.24.0]",
"sdl/[~2.26.1 || >=2.0.20 <=2.22.0]", # versions in between have broken sound
"sdl_image/[~2.0.5]",
"sdl_mixer/[~2.0.4]",
"sdl_ttf/[~2.0.18]",

View File

@ -10,6 +10,9 @@
"hero" : 156,
"spell" : 81,
"object" : 256,
"terrain" : 10,
"river" : 5,
"road" : 4,
"mapVersion" : 28 // max supported version, SoD
},

View File

@ -84,7 +84,7 @@
"mageGuild3": { "animation" : "TBCSMAG3.def", "x" : 704, "y" : 107, "z" : 1, "border" : "TOCSM301.bmp", "area" : "TZCSM301.bmp" },
"mageGuild4": { "animation" : "TBCSMAG4.def", "x" : 704, "y" : 76, "z" : 1, "border" : "TOCSM401.bmp", "area" : "TZCSM401.bmp" },
"tavern": { "animation" : "TBCSTVRN.def", "x" : 0, "y" : 230, "z" : 1, "border" : "TOCSTAV1.bmp", "area" : "TZCSTAV1.bmp" },
"shipyard": { "animation" : "TBCSDOCK.def", "x" : 478, "y" : 134, "border" : "TOCSDKMS.bmp", "area" : "TZCSDKMS.bmp" },
"shipyard": { "animation" : "TBCSDOCK.def", "x" : 478, "y" : 134, "z" : 1, "border" : "TOCSDKMS.bmp", "area" : "TZCSDKMS.bmp" },
"fort": { "animation" : "TBCSCSTL.def", "x" : 595, "y" : 66, "border" : "TOCSCAS1.bmp", "area" : "TZCSCAS1.bmp" },
"citadel": { "animation" : "TBCSCAS2.def", "x" : 478, "y" : 66, "border" : "TOCSCAS2.bmp", "area" : "TZCSCAS2.bmp" },
"castle": { "animation" : "TBCSCAS3.def", "x" : 478, "y" : 37, "border" : "TOCSCAS3.bmp", "area" : "TZCSCAS3.bmp" },
@ -98,7 +98,7 @@
"special1": { "animation" : "TBCSSPEC.def", "x" : 533, "y" : 71, "border" : "TOCSLT01.bmp", "area" : "TZCSLT01.bmp" },
"horde1": { "animation" : "TBCSHRD1.def", "x" : 76, "y" : 53, "z" : -1, "border" : "TOCSGR1H.bmp", "area" : "TZCSGR1H.bmp", "hidden" : true },
"horde1Upgr": { "animation" : "TBCSHRD2.def", "x" : 76, "y" : 35, "z" : -1, "border" : "TOCSGR2H.bmp", "area" : "TZCSGR2H.bmp", "hidden" : true, "builds" : "horde1" },
"ship": { "animation" : "TBCSBOAT.def", "x" : 478, "y" : 134, "border" : "TOCSDKMN.bmp", "area" : "TZCSDKMN.bmp", "hidden" : true },
"ship": { "animation" : "TBCSBOAT.def", "x" : 478, "y" : 134, "z" : 1, "border" : "TOCSDKMN.bmp", "area" : "TZCSDKMN.bmp", "hidden" : true },
"special2": { "animation" : "TBCSEXT0.def", "x" : 384, "y" : 193, "z" : -2, "border" : "TOCSCAVM.bmp", "area" : "TZCSCAVM.bmp" },
"special3": { "animation" : "TBCSEXT1.def", "x" : 0, "y" : 198, "z" : 1, "border" : "TOCSTAV2.bmp", "area" : "TZCSTAV2.bmp" },
"grail": { "animation" : "TBCSHOLY.def", "x" : 456, "y" : 109, "z" : -1, "border" : "TOCSHOLY.bmp", "area" : "TZCSHOLY.bmp" },

View File

@ -3,6 +3,7 @@
{
"name" : "Neutral",
"index" : 9,
"nativeTerrain" : "none",
"alignment" : "neutral",
"creatureBackground" :
{

View File

@ -85,6 +85,14 @@
[
"config/terrains.json"
],
"roads":
[
"config/roads.json"
],
"rivers":
[
"config/rivers.json"
],
"battlefields":
[
"config/battlefields.json"

View File

@ -42,4 +42,4 @@
"value" : [2000, 5333, 8666, 12000],
"rewardValue" : [5000, 10000, 15000, 20000]
}
}
}

View File

@ -1,30 +1,34 @@
{
"waterRiver":
{
"originalRiverId": 1,
"code": "rw", //must be 2 characters
"animation": "clrrvr",
"index": 1,
"text" : "Water river",
"shortIdentifier": "rw", //must be 2 characters
"tilesFilename": "clrrvr",
"delta": "clrdelt"
},
"iceRiver":
{
"originalRiverId": 2,
"code": "ri",
"animation": "icyrvr",
"index": 2,
"text" : "Ice river",
"shortIdentifier": "ri",
"tilesFilename": "icyrvr",
"delta": "icedelt"
},
"mudRiver":
{
"originalRiverId": 3,
"code": "rm",
"animation": "mudrvr",
"index": 3,
"text" : "Mud river",
"shortIdentifier": "rm",
"tilesFilename": "mudrvr",
"delta": "muddelt"
},
"lavaRiver":
{
"originalRiverId": 4,
"code": "rl",
"animation": "lavrvr",
"index": 4,
"text" : "Lava river",
"shortIdentifier": "rl",
"tilesFilename": "lavrvr",
"delta": "lavdelt"
}
}
}

View File

@ -1,23 +1,26 @@
{
"dirtRoad":
{
"originalRoadId": 1,
"code": "pd", //must be 2 characters
"animation": "dirtrd",
"index": 1,
"text" : "Dirt road",
"shortIdentifier": "pd", //must be 2 characters
"tilesFilename": "dirtrd",
"moveCost": 75
},
"gravelRoad":
{
"originalRoadId": 2,
"code": "pg",
"animation": "gravrd",
"index": 2,
"text" : "Gravel road",
"shortIdentifier": "pg",
"tilesFilename": "gravrd",
"moveCost": 65
},
"cobblestoneRoad":
{
"originalRoadId": 3,
"code": "pc",
"animation": "cobbrd",
"index": 3,
"text" : "Cobblestone road",
"shortIdentifier": "pc",
"tilesFilename": "cobbrd",
"moveCost": 50
}
}
}

View File

@ -31,7 +31,7 @@
"$schema": "http://json-schema.org/draft-04/schema",
"title" : "VCMI faction format",
"description": "Json format for defining new faction (aka towns) in VCMI",
"required" : [ "name", "alignment", "creatureBackground" ],
"required" : [ "name", "alignment", "creatureBackground", "nativeTerrain" ],
"dependencies" : {
"town" : [ "puzzleMap" ]
},

View File

@ -4,7 +4,41 @@
"title" : "VCMI mod file format",
"description" : "Format used to define main mod file (mod.json) in VCMI",
"required" : [ "name", "description", "version", "author", "contact", "modType" ],
"definitions" : {
"localizable" : {
"type":"object",
"additionalProperties" : false,
"required" : [ "name", "description", "author", "modType" ],
"properties":{
"name": {
"type":"string",
"description": "Short name of your mod. No more than 2-3 words"
},
"description": {
"type":"string",
"description": "More lengthy description of mod. No hard limit"
},
"modType" : {
"type":"string",
"description": "Type of mod, e.g. Town, Artifacts, Graphical."
},
"author" : {
"type":"string",
"description": "Author of the mod. Can be nickname, real name or name of team"
},
"changelog" : {
"type":"object",
"description": "List of changes/new features in each version",
"additionalProperties" : {
"type" : "array",
"items" : { "type":"string" }
}
}
}
}
},
"additionalProperties" : false,
"properties":{
"name": {
@ -78,6 +112,26 @@
"type":"boolean",
"description": "If set to true, mod will not be enabled automatically on install"
},
"english" : {
"$ref" : "#/definitions/localizable"
},
"german" : {
"$ref" : "#/definitions/localizable"
},
"polish" : {
"$ref" : "#/definitions/localizable"
},
"russian" : {
"$ref" : "#/definitions/localizable"
},
"ukrainian" : {
"$ref" : "#/definitions/localizable"
},
"artifacts": {
"type":"array",

37
config/schemas/river.json Normal file
View File

@ -0,0 +1,37 @@
{
"type":"object",
"$schema": "http://json-schema.org/draft-04/schema",
"title" : "VCMI river format",
"description" : "Format used to define new rivers in VCMI",
"required" : [ "text", "shortIdentifier", "tilesFilename", "delta" ],
"additionalProperties" : false,
"properties":{
"index" :
{
"type": "number",
"description": "Internal, do not use"
},
"text":
{
"type": "string",
"description": "Human-readable name of the river"
},
"shortIdentifier":
{
"type": "string",
"description": "Two-letters unique indentifier for this road. Used in map format"
},
"tilesFilename":
{
"type": "string",
"description": "Name of file with river graphics",
"format": "defFile"
},
"delta":
{
"type": "string",
"description": "Name of file with river delta graphics"
}
}
}

37
config/schemas/road.json Normal file
View File

@ -0,0 +1,37 @@
{
"type":"object",
"$schema": "http://json-schema.org/draft-04/schema",
"title" : "VCMI road format",
"description" : "Format used to define new roads in VCMI",
"required" : [ "text", "shortIdentifier", "tilesFilename", "moveCost" ],
"additionalProperties" : false,
"properties":{
"index" :
{
"type": "number",
"description": "Internal, do not use"
},
"text":
{
"type": "string",
"description": "Human-readable name of the road"
},
"shortIdentifier":
{
"type": "string",
"description": "Two-letters unique indentifier for this road. Used in map format"
},
"tilesFilename":
{
"type": "string",
"description": "Name of file with road graphics",
"format": "defFile"
},
"moveCost":
{
"type": "number",
"description": "How many movement points needed to move hero"
}
}
}

View File

@ -37,7 +37,7 @@
},
"encoding" : {
"type" : "string",
"default" : "CP1252"
"default" : "auto"
},
"swipe" : {
"type" : "boolean",
@ -53,6 +53,7 @@
},
"language" : {
"type":"string",
"enum" : [ "english", "german", "polish", "russian", "ukrainian" ],
"default" : "english"
},
"lastSave" : {
@ -81,7 +82,7 @@
"type" : "object",
"additionalProperties" : false,
"default": {},
"required" : [ "screenRes", "bitsPerPixel", "fullscreen", "realFullscreen", "spellbookAnimation","driver", "showIntro", "displayIndex" ],
"required" : [ "screenRes", "bitsPerPixel", "fullscreen", "realFullscreen", "softwareCursor", "spellbookAnimation", "driver", "showIntro", "displayIndex" ],
"properties" : {
"screenRes" : {
"type" : "object",
@ -105,6 +106,10 @@
"type" : "boolean",
"default" : false
},
"softwareCursor" : {
"type" : "boolean",
"default" : false
},
"showIntro" : {
"type" : "boolean",
"default" : true

View File

@ -3,10 +3,20 @@
"$schema": "http://json-schema.org/draft-04/schema",
"title" : "VCMI terrain format",
"description" : "Format used to define new terrains in VCMI",
"required" : [ "tiles", "code", "moveCost" ],
"required" : [ "text", "moveCost", "minimapUnblocked", "minimapBlocked", "music", "tiles", "type", "horseSound", "horseSoundPenalty", "shortIdentifier", "battleFields" ],
"additionalProperties" : false,
"properties":{
"index" :
{
"type": "number",
"description": "Internal, do not use"
},
"text":
{
"type": "string",
"description": "Human-readable name of this terrain"
},
"moveCost":
{
"type": "number",
@ -47,35 +57,38 @@
},
"type":
{
"type": "string",
"type": "array",
"description": "Type of this terrain. Can be land, water, subterranean or rock",
"enum": ["LAND", "WATER", "SUB", "ROCK"]
"items":
{
"enum": ["LAND", "WATER", "SUB", "ROCK", "SURFACE"],
"type": "string"
}
},
"rockTerrain":
{
"type": "string",
"description": "The name of tock type terrain which will be used as borders in the underground"
"description": "The name of rock type terrain which will be used as borders in the underground"
},
"river":
{
"type": "string",
"description": "River type which should be used for that terrain",
"enum": ["", "rw", "ri", "rm", "rl"]
"description": "River type which should be used for that terrain"
},
"horseSoundId":
{
"type": "number",
"description": "Id of horse sound to be played when hero is moving across terrain"
},
"text":
"horseSound":
{
"type": "string",
"description": "Text to be shown when mouse if over terrain"
"description": "Hero movement sound for this terrain, version for moving on tiles with road"
},
"code":
"horseSoundPenalty":
{
"type": "string",
"description": "Two-letters unique indentifier for this terrain. Used for terrain serializaion"
"description": "Hero movement sound for this terrain, version for moving on tiles without road"
},
"shortIdentifier":
{
"type": "string",
"description": "Two-letters unique indentifier for this terrain. Used for map format"
},
"battleFields":
{
@ -86,6 +99,24 @@
"type": "string"
}
},
"sounds":
{
"type": "object",
"description": "list of sounds for this terrain",
"additionalProperties" : false,
"properties":
{
"ambient" :
{
"type": "array",
"description": "list of ambient sounds for this terrain",
"items":
{
"type": "string"
}
}
}
},
"prohibitTransitions":
{
"type": "array",

View File

@ -1,146 +1,162 @@
{
"dirt" :
{
"originalTerrainId": 0,
"index": 0,
"moveCost" : 100,
"minimapUnblocked" : [ 82, 56, 8 ],
"minimapBlocked" : [ 57, 40, 8 ],
"music" : "Dirt.mp3",
"tiles" : "DIRTTL",
"code" : "dt",
"river" : "rm",
"type" : ["SURFACE"],
"shortIdentifier" : "dt",
"river" : "mudRiver",
"battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"],
"terrainViewPatterns" : "dirt",
"horseSoundId" : 0
"horseSound" : "horse00",
"horseSoundPenalty" : "horse20"
},
"sand" :
{
"originalTerrainId": 1,
"index": 1,
"moveCost" : 150,
"minimapUnblocked" : [ 222, 207, 140 ],
"minimapBlocked" : [ 165, 158, 107 ],
"music" : "Sand.mp3",
"tiles" : "SANDTL",
"code" : "sa",
"river" : "rm",
"type" : ["SURFACE"],
"shortIdentifier" : "sa",
"river" : "mudRiver",
"battleFields" : ["sand_mesas"],
"transitionRequired" : true,
"terrainViewPatterns" : "sand",
"horseSoundId" : 1
"horseSound" : "horse01",
"horseSoundPenalty" : "horse21"
},
"grass" :
{
"originalTerrainId": 2,
"index": 2,
"moveCost" : 100,
"minimapUnblocked" : [ 0, 65, 0 ],
"minimapBlocked" : [ 0, 48, 0 ],
"music" : "Grass.mp3",
"tiles" : "GRASTL",
"code" : "gr",
"river" : "rw",
"type" : ["SURFACE"],
"shortIdentifier" : "gr",
"river" : "waterRiver",
"battleFields" : ["grass_hills", "grass_pines"],
"horseSoundId" : 2
"horseSound" : "horse02",
"horseSoundPenalty" : "horse22"
},
"snow" :
{
"originalTerrainId": 3,
"index": 3,
"moveCost" : 150,
"minimapUnblocked" : [ 181, 199, 198 ],
"minimapBlocked" : [ 140, 158, 156 ],
"music" : "Snow.mp3",
"tiles" : "SNOWTL",
"code" : "sn",
"river" : "ri",
"type" : ["SURFACE"],
"shortIdentifier" : "sn",
"river" : "iceRiver",
"battleFields" : ["snow_mountains", "snow_trees"],
"horseSoundId" : 3
"horseSound" : "horse03",
"horseSoundPenalty" : "horse23"
},
"swamp" :
{
"originalTerrainId": 4,
"index": 4,
"moveCost" : 175,
"minimapUnblocked" : [ 74, 134, 107 ],
"minimapBlocked" : [ 33, 89, 66 ],
"music" : "Swamp.mp3",
"tiles" : "SWMPTL",
"code" : "sw",
"river" : "rw",
"type" : ["SURFACE"],
"shortIdentifier" : "sw",
"river" : "waterRiver",
"battleFields" : ["swamp_trees"],
"horseSoundId" : 4
"horseSound" : "horse04",
"horseSoundPenalty" : "horse24"
},
"rough" :
{
"originalTerrainId": 5,
"index": 5,
"moveCost" : 125,
"minimapUnblocked" : [ 132, 113, 49 ],
"minimapBlocked" : [ 99, 81, 33 ],
"music" : "Rough.mp3",
"tiles" : "ROUGTL",
"code" : "rg",
"river" : "rm",
"type" : ["SURFACE"],
"shortIdentifier" : "rg",
"river" : "mudRiver",
"battleFields" : ["rough"],
"horseSoundId" : 5
"horseSound" : "horse05",
"horseSoundPenalty" : "horse25"
},
"subterra" :
{
"originalTerrainId": 6,
"index": 6,
"moveCost" : 100,
"minimapUnblocked" : [ 132, 48, 0 ],
"minimapBlocked" : [ 90, 8, 0 ],
"music" : "Underground.mp3",
"tiles" : "SUBBTL",
"type" : "SUB",
"code" : "sb",
"river" : "rw",
"type" : [ "SUB" ],
"shortIdentifier" : "sb",
"river" : "waterRiver",
"battleFields" : ["subterranean"],
"rockTerrain" : "rock",
"horseSoundId" : 6
"horseSound" : "horse06",
"horseSoundPenalty" : "horse26"
},
"lava" :
{
"originalTerrainId": 7,
"index": 7,
"moveCost" : 100,
"minimapUnblocked" : [ 74, 73, 74 ],
"minimapBlocked" : [ 41, 40, 41 ],
"music" : "Lava.mp3",
"tiles" : "LAVATL",
"type" : ["SUB", "SURFACE"],
"code" : "lv",
"river" : "rl",
"shortIdentifier" : "lv",
"river" : "lavaRiver",
"battleFields" : ["lava"],
"rockTerrain" : "rock",
"horseSoundId" : 7
"horseSound" : "horse07",
"horseSoundPenalty" : "horse27"
},
"water" :
{
"originalTerrainId": 8,
"index": 8,
"moveCost" : 100,
"minimapUnblocked" : [ 8, 81, 148 ],
"minimapBlocked" : [ 8, 81, 148 ],
"music" : "Water.mp3",
"tiles" : "WATRTL",
"type" : "WATER",
"code" : "wt",
"type" : [ "WATER" ],
"shortIdentifier" : "wt",
"battleFields" : ["ship"],
"transitionRequired" : true,
"terrainViewPatterns" : "water",
"horseSoundId" : 8,
"horseSound" : "horse08",
"horseSoundPenalty" : "horse28",
"sounds": {
"ambient": ["LOOPOCEA"]
}
},
"rock" :
{
"originalTerrainId": 9,
"index": 9,
"moveCost" : -1,
"minimapUnblocked" : [ 0, 0, 0 ],
"minimapBlocked" : [ 0, 0, 0 ],
"music" : "Underground.mp3", // Impossible in H3
"tiles" : "ROCKTL",
"type" : "ROCK",
"code" : "rc",
"type" : [ "ROCK" ],
"shortIdentifier" : "rc",
"battleFields" : ["rocklands"],
"transitionRequired" : true,
"terrainViewPatterns" : "rock",
"horseSoundId" : 9
"horseSound" : "horse09",
"horseSoundPenalty" : "horse29"
}
}

View File

@ -20,8 +20,8 @@ The following platforms are supported and known to work, others might require ch
1. Check if your build environment can use the prebuilt binaries: basically, that your compiler version (or Xcode major version) matches the information below. If you're unsure, simply advance to the next step.
- macOS: libraries are built with Apple clang 13 (Xcode 13.4.1), should be consumable by Xcode and Xcode CLT 13.x
- iOS: libraries are built with Apple clang 13 (Xcode 13.4.1), should be consumable by Xcode 13.x
- macOS: libraries are built with Apple clang 14 (Xcode 14.2), should be consumable by Xcode and Xcode CLT 14.x (older library versions are also available for Xcode 13, see Releases in the respective repo)
- iOS: libraries are built with Apple clang 14 (Xcode 14.2), should be consumable by Xcode 14.x (older library versions are also available for Xcode 13, see Releases in the respective repo)
2. Download the binaries archive and unpack it to `~/.conan` directory:
@ -85,7 +85,8 @@ conan install . \
--no-imports \
--build=never \
--profile:build=default \
--profile:host=CI/conan/macos-intel
--profile:host=CI/conan/macos-intel \
-o with_apple_system_libs=True
cmake -S . -B build -G Xcode \
--toolchain conan-generated/conan_toolchain.cmake
@ -116,7 +117,8 @@ conan install . \
--no-imports \
--build=never \
--profile:build=default \
--profile:host=CI/conan/ios-arm64
--profile:host=CI/conan/ios-arm64 \
-o with_apple_system_libs=True
cmake --preset ios-conan
```

View File

@ -62,6 +62,13 @@ set(launcher_FORMS
lobby/lobbyroomrequest_moc.ui
)
set(launcher_TS
translation/english.ts
translation/german.ts
translation/polish.ts
translation/russian.ts
translation/ukrainian.ts)
if(APPLE_IOS)
list(APPEND launcher_SRCS
ios/main.m
@ -83,18 +90,28 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
if(TARGET Qt6::Core)
qt_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS})
if(ENABLE_TRANSLATIONS)
set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/translation)
# TODO: consider using qt_add_translations: https://doc.qt.io/qt-6/qtlinguist-cmake-qt-add-translations.html
qt_add_translation( launcher_QM ${launcher_TS} )
endif()
else()
qt5_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS})
if(ENABLE_TRANSLATIONS)
set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/translation)
qt5_add_translation( launcher_QM ${launcher_TS} )
endif()
endif()
if(WIN32)
set(launcher_ICON VCMI_launcher.rc)
endif()
if(ENABLE_SINGLE_APP_BUILD)
add_library(vcmilauncher STATIC ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS})
add_library(vcmilauncher STATIC ${launcher_QM} ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS})
else()
add_executable(vcmilauncher WIN32 ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS} ${launcher_ICON})
add_executable(vcmilauncher WIN32 ${launcher_QM} ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS} ${launcher_ICON})
endif()
if(WIN32)
@ -126,7 +143,12 @@ vcmi_set_output_dir(vcmilauncher "")
enable_pch(vcmilauncher)
if(APPLE_IOS)
set(ICONS_DESTINATION ${DATA_DIR})
set(RESOURCES_DESTINATION ${DATA_DIR})
# TODO: remove after fixing Conan's Qt recipe
if(XCODE_VERSION VERSION_GREATER_EQUAL 14.0)
target_link_libraries(vcmilauncher "-framework IOKit")
endif()
# workaround https://github.com/conan-io/conan-center-index/issues/13332
if(USING_CONAN)
@ -140,12 +162,14 @@ if(APPLE_IOS)
)
endif()
else()
set(ICONS_DESTINATION ${DATA_DIR}/launcher)
set(RESOURCES_DESTINATION ${DATA_DIR}/launcher)
# Copy to build directory for easier debugging
add_custom_command(TARGET vcmilauncher POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/icons
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/launcher/icons ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/icons
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/launcher/icons ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/icons
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_BINARY_DIR}/translation ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translation
)
install(TARGETS vcmilauncher DESTINATION ${BIN_DIR})
@ -156,4 +180,8 @@ else()
install(FILES "eu.vcmi.VCMI.metainfo.xml" DESTINATION share/metainfo)
endif()
endif()
install(DIRECTORY icons DESTINATION ${ICONS_DESTINATION})
install(DIRECTORY icons DESTINATION ${RESOURCES_DESTINATION})
if(ENABLE_TRANSLATIONS)
install(FILES ${launcher_QM} DESTINATION ${RESOURCES_DESTINATION}/translation)
endif()

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -12,7 +12,7 @@
#include <QTcpSocket>
#include <QAbstractSocket>
const unsigned int ProtocolVersion = 3;
const unsigned int ProtocolVersion = 4;
const std::string ProtocolEncoding = "utf8";
class ProtocolError: public std::runtime_error
@ -24,10 +24,10 @@ public:
enum ProtocolConsts
{
//client consts
GREETING, USERNAME, MESSAGE, VERSION, CREATE, JOIN, LEAVE, KICK, READY, FORCESTART,
GREETING, USERNAME, MESSAGE, VERSION, CREATE, JOIN, LEAVE, KICK, READY, FORCESTART, HERE, ALIVE, HOSTMODE,
//server consts
SESSIONS, CREATED, JOINED, KICKED, SRVERROR, CHAT, START, STATUS, HOST, MODS, CLIENTMODS
SESSIONS, CREATED, JOINED, KICKED, SRVERROR, CHAT, START, STATUS, HOST, MODS, CLIENTMODS, USERS, HEALTH, GAMEMODE
};
const QMap<ProtocolConsts, QString> ProtocolStrings
@ -78,6 +78,16 @@ const QMap<ProtocolConsts, QString> ProtocolStrings
//[unsupported] start session immediately
//%1: room name
{FORCESTART, "<FORCESTART>%1"},
//request user list
{HERE, "<HERE>"},
//used as reponse to healcheck
{ALIVE, "<ALIVE>"},
//host sets game mode (new game or load game)
//%1: game mode - 0 for new game, 1 for load game
{HOSTMODE, "<HOSTMODE>%1"},
//=== server commands ===
//server commands are started from :>>, arguments are enumerated by : symbol
@ -140,7 +150,20 @@ const QMap<ProtocolConsts, QString> ProtocolStrings
//received chat message
//arg[0]: sender username
//arg[1]: message text
{CHAT, "MSG"}
{CHAT, "MSG"},
//list of users currently in lobby
//arg[0]: amount of players, following arguments depend on it
//arg[x]: username
//arg[x+1]: room (empty if not in the room)
{USERS, "USERS"},
//healthcheck from server
{HEALTH, "HEALTH"},
//game mode (new game or load game) set by host
//arg[0]: game mode
{GAMEMODE, "GAMEMODE"},
};
class ServerCommand

View File

@ -16,6 +16,18 @@
#include "../modManager/cmodlist.h"
#include "../../lib/CConfigHandler.h"
enum GameMode
{
NEW_GAME = 0, LOAD_GAME = 1
};
enum ModResolutionRoles
{
ModNameRole = Qt::UserRole + 1,
ModEnableRole,
ModResolvableRole
};
Lobby::Lobby(QWidget *parent) :
QWidget(parent),
ui(new Ui::Lobby)
@ -35,6 +47,15 @@ Lobby::Lobby(QWidget *parent) :
ui->kickButton->setVisible(false);
}
void Lobby::changeEvent(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
}
QWidget::changeEvent(event);
}
Lobby::~Lobby()
{
delete ui;
@ -134,10 +155,14 @@ void Lobby::serverCommand(const ServerCommand & command) try
if(args[1] == username)
{
hostModsMap.clear();
ui->buttonReady->setText("Ready");
ui->chat->clear(); //cleanup the chat
ui->optNewGame->setChecked(true);
sysMessage(joinStr.arg("you", args[0]));
session = args[0];
bool isHost = command.command == JOINED && hostSession == session;
ui->optNewGame->setEnabled(isHost);
ui->optLoadGame->setEnabled(isHost);
ui->stackedWidget->setCurrentWidget(command.command == JOINED ? ui->roomPage : ui->sessionsPage);
}
else
@ -152,33 +177,15 @@ void Lobby::serverCommand(const ServerCommand & command) try
protocolAssert(amount * 2 == (args.size() - 1));
tagPoint = 1;
ui->modsList->clear();
auto enabledMods = buildModsMap();
for(int i = 0; i < amount; ++i, tagPoint += 2)
{
if(enabledMods.contains(args[tagPoint]))
{
if(enabledMods[args[tagPoint]] == args[tagPoint + 1])
enabledMods.remove(args[tagPoint]);
else
ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-update.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1])));
}
else if(isModAvailable(args[tagPoint], args[tagPoint + 1]))
ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-enabled.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1])));
else
ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-delete.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1])));
}
for(auto & remainMod : enabledMods.keys())
{
ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-disabled.png"), QString("%1 (v%2)").arg(remainMod, enabledMods[remainMod])));
}
if(!ui->modsList->count())
ui->modsList->addItem("No issues detected");
hostModsMap[args[tagPoint]] = args[tagPoint + 1];
updateMods();
break;
}
case CLIENTMODS: {
protocolAssert(args.size() > 1);
protocolAssert(args.size() >= 1);
amount = args[1].toInt();
protocolAssert(amount * 2 == (args.size() - 2));
@ -217,6 +224,8 @@ void Lobby::serverCommand(const ServerCommand & command) try
gameArgs << "--lobby";
gameArgs << "--lobby-address" << serverUrl;
gameArgs << "--lobby-port" << QString::number(serverPort);
gameArgs << "--lobby-username" << username;
gameArgs << "--lobby-gamemode" << QString::number(isLoadGameMode);
gameArgs << "--uuid" << args[0];
startGame(gameArgs);
break;
@ -238,6 +247,34 @@ void Lobby::serverCommand(const ServerCommand & command) try
chatMessage(args[0], msg);
break;
}
case HEALTH: {
socketLobby.send(ProtocolStrings[ALIVE]);
break;
}
case USERS: {
protocolAssert(args.size() > 0);
amount = args[0].toInt();
protocolAssert(amount == (args.size() - 1));
ui->listUsers->clear();
for(int i = 0; i < amount; ++i)
{
ui->listUsers->addItem(new QListWidgetItem(args[i + 1]));
}
break;
}
case GAMEMODE: {
protocolAssert(args.size() == 1);
isLoadGameMode = args[0].toInt();
if(isLoadGameMode)
ui->optLoadGame->setChecked(true);
else
ui->optNewGame->setChecked(true);
break;
}
default:
sysMessage("Unknown server command");
@ -291,7 +328,7 @@ void Lobby::onDisconnected()
ui->userEdit->setEnabled(true);
ui->newButton->setEnabled(false);
ui->joinButton->setEnabled(false);
ui->sessionsTable->clear();
ui->sessionsTable->setRowCount(0);
}
void Lobby::chatMessage(QString title, QString body, bool isSystem)
@ -329,6 +366,7 @@ void Lobby::on_connectButton_toggled(bool checked)
{
if(checked)
{
ui->connectButton->setText(tr("Disconnect"));
authentificationStatus = AuthStatus::AUTH_NONE;
username = ui->userEdit->text();
const int connectionTimeout = settings["launcher"]["connectionTimeout"].Integer();
@ -360,12 +398,74 @@ void Lobby::on_connectButton_toggled(bool checked)
}
else
{
ui->connectButton->setText(tr("Connect"));
ui->serverEdit->setEnabled(true);
ui->userEdit->setEnabled(true);
ui->listUsers->clear();
hostModsMap.clear();
updateMods();
socketLobby.disconnectServer();
}
}
void Lobby::updateMods()
{
ui->modsList->clear();
if(hostModsMap.empty())
return;
auto createModListWidget = [](const QIcon & icon, const QString & label, const QString & name, bool enableFlag, bool resolveFlag)
{
auto * lw = new QListWidgetItem(icon, label);
lw->setData(ModResolutionRoles::ModNameRole, name);
lw->setData(ModResolutionRoles::ModEnableRole, enableFlag);
lw->setData(ModResolutionRoles::ModResolvableRole, resolveFlag);
return lw;
};
auto enabledMods = buildModsMap();
for(const auto & mod : hostModsMap.keys())
{
auto & modValue = hostModsMap[mod];
auto modName = QString("%1 (v%2)").arg(mod, modValue);
if(enabledMods.contains(mod))
{
if(enabledMods[mod] == modValue)
enabledMods.remove(mod); //mod fully matches, remove from list
else
{
//mod version mismatch
ui->modsList->addItem(createModListWidget(QIcon("icons:mod-update.png"), modName, mod, true, false));
}
}
else if(isModAvailable(mod, modValue))
{
//mod is available and needs to be enabled
ui->modsList->addItem(createModListWidget(QIcon("icons:mod-enabled.png"), modName, mod, true, true));
}
else
{
//mod is not available and needs to be installed
ui->modsList->addItem(createModListWidget(QIcon("icons:mod-delete.png"), modName, mod, true, false));
}
}
for(const auto & remainMod : enabledMods.keys())
{
auto modName = QString("%1 (v%2)").arg(remainMod, enabledMods[remainMod]);
//mod needs to be disabled
ui->modsList->addItem(createModListWidget(QIcon("icons:mod-disabled.png"), modName, remainMod, false, true));
}
if(!ui->modsList->count())
{
ui->buttonResolve->setEnabled(false);
ui->modsList->addItem(tr("No issues detected"));
}
else
{
ui->buttonResolve->setEnabled(true);
}
}
void Lobby::on_newButton_clicked()
{
new LobbyRoomRequest(socketLobby, "", buildModsMap(), this);
@ -417,3 +517,50 @@ void Lobby::on_kickButton_clicked()
socketLobby.send(ProtocolStrings[KICK].arg(ui->playersList->currentItem()->text()));
}
void Lobby::on_buttonResolve_clicked()
{
QStringList toEnableList, toDisableList;
for(auto * item : ui->modsList->selectedItems())
{
auto modName = item->data(ModResolutionRoles::ModNameRole);
if(modName.isNull())
continue;
bool modToEnable = item->data(ModResolutionRoles::ModEnableRole).toBool();
bool modToResolve = item->data(ModResolutionRoles::ModResolvableRole).toBool();
if(!modToResolve)
continue;
if(modToEnable)
toEnableList << modName.toString();
else
toDisableList << modName.toString();
}
//disabling first, then enabling
for(auto & mod : toDisableList)
emit disableMod(mod);
for(auto & mod : toEnableList)
emit enableMod(mod);
}
void Lobby::on_optNewGame_toggled(bool checked)
{
if(checked)
{
if(isLoadGameMode)
socketLobby.send(ProtocolStrings[HOSTMODE].arg(GameMode::NEW_GAME));
}
}
void Lobby::on_optLoadGame_toggled(bool checked)
{
if(checked)
{
if(!isLoadGameMode)
socketLobby.send(ProtocolStrings[HOSTMODE].arg(GameMode::LOAD_GAME));
}
}

View File

@ -19,9 +19,18 @@ class Lobby : public QWidget
{
Q_OBJECT
void changeEvent(QEvent *event) override;
public:
explicit Lobby(QWidget *parent = nullptr);
~Lobby();
signals:
void enableMod(QString mod);
void disableMod(QString mod);
public slots:
void updateMods();
private slots:
void on_messageEdit_returnPressed();
@ -49,9 +58,16 @@ private slots:
void on_kickButton_clicked();
void on_buttonResolve_clicked();
void on_optNewGame_toggled(bool checked);
void on_optLoadGame_toggled(bool checked);
private:
QString serverUrl;
int serverPort;
bool isLoadGameMode = false;
Ui::Lobby *ui;
SocketLobby socketLobby;
@ -59,6 +75,7 @@ private:
QString session;
QString username;
QStringList gameArgs;
QMap<QString, QString> hostModsMap;
enum AuthStatus
{

View File

@ -7,13 +7,26 @@
<x>0</x>
<y>0</y>
<width>652</width>
<height>329</height>
<height>383</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
<string/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="3">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QPushButton" name="connectButton">
<property name="sizePolicy">
@ -30,36 +43,10 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QLineEdit" name="messageEdit"/>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLineEdit" name="userEdit"/>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="serverEdit">
<property name="text">
<string>127.0.0.1:5002</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QPlainTextEdit" name="chat">
<property name="readOnly">
<bool>true</bool>
<string notr="true">127.0.0.1:5002</string>
</property>
</widget>
</item>
@ -70,144 +57,250 @@
</property>
</widget>
</item>
<item row="2" column="3" rowspan="2" colspan="3">
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<item row="0" column="4">
<widget class="QLineEdit" name="userEdit"/>
</item>
<item row="1" column="0" colspan="6">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="bottomMargin">
<number>0</number>
</property>
<widget class="QWidget" name="sessionsPage">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QTableWidget" name="sessionsTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>80</number>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>20</number>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>20</number>
</attribute>
<column>
<property name="text">
<string>Session</string>
</property>
</column>
<column>
<property name="text">
<string>Players</string>
</property>
</column>
<column>
<property name="text">
<string/>
</property>
</column>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="newButton">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>New room</string>
<string>People in lobby</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="joinButton">
<property name="enabled">
<bool>false</bool>
<item>
<widget class="QListWidget" name="listUsers">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Join room</string>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>96</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="roomPage">
<layout class="QGridLayout" name="gridLayout_3">
<item row="5" column="1">
<widget class="QPushButton" name="buttonReady">
<property name="text">
<string>Ready</string>
<property name="midLineWidth">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Mods mismatch</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QPushButton" name="buttonLeave">
<property name="text">
<string>Leave</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QListWidget" name="modsList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QListWidget" name="playersList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
<property name="layoutMode">
<enum>QListView::SinglePass</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="kickButton">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Kick player</string>
<string>Lobby chat</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Players in the room</string>
<item>
<widget class="QPlainTextEdit" name="chat">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="messageEdit"/>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="sessionsPage">
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QPushButton" name="newButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>New room</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="joinButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Join room</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QTableWidget" name="sessionsTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>80</number>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>20</number>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>20</number>
</attribute>
<column>
<property name="text">
<string>Session</string>
</property>
</column>
<column>
<property name="text">
<string>Players</string>
</property>
</column>
<column>
<property name="text">
<string/>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="roomPage">
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
<widget class="QPushButton" name="kickButton">
<property name="text">
<string>Kick player</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QListWidget" name="playersList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Players in the room</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QPushButton" name="buttonLeave">
<property name="text">
<string>Leave</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Mods mismatch</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QListWidget" name="modsList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QPushButton" name="buttonReady">
<property name="text">
<string>Ready</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="buttonResolve">
<property name="text">
<string>Resolve</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="optNewGame">
<property name="text">
<string>New game</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="optLoadGame">
<property name="text">
<string>Load game</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

View File

@ -27,6 +27,15 @@ LobbyRoomRequest::LobbyRoomRequest(SocketLobby & socket, const QString & room, c
show();
}
void LobbyRoomRequest::changeEvent(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
}
QDialog::changeEvent(event);
}
LobbyRoomRequest::~LobbyRoomRequest()
{
delete ui;

View File

@ -21,6 +21,7 @@ class LobbyRoomRequest : public QDialog
{
Q_OBJECT
void changeEvent(QEvent *event) override;
public:
explicit LobbyRoomRequest(SocketLobby & socket, const QString & room, const QMap<QString, QString> & mods, QWidget *parent = nullptr);
~LobbyRoomRequest();

View File

@ -9,7 +9,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>193</width>
<width>227</width>
<height>188</height>
</rect>
</property>
@ -51,39 +51,42 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentText">
<string notr="true">2</string>
</property>
<item>
<property name="text">
<string>2</string>
<string notr="true">2</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
<string notr="true">3</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
<string notr="true">4</string>
</property>
</item>
<item>
<property name="text">
<string>5</string>
<string notr="true">5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
<string notr="true">6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
<string notr="true">7</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
<string notr="true">8</string>
</property>
</item>
</widget>

View File

@ -32,6 +32,7 @@ int main(int argc, char * argv[])
{
#endif
QApplication vcmilauncher(argc, argv);
MainWindow mainWindow;
mainWindow.show();
result = vcmilauncher.exec();
@ -65,6 +66,13 @@ void startGame(const QStringList & args)
#endif
}
void startEditor(const QStringList & args)
{
#ifdef ENABLE_EDITOR
startExecutable(pathToQString(VCMIDirs::get().mapEditorPath()), args);
#endif
}
#ifndef Q_OS_IOS
void startExecutable(QString name, const QStringList & args)
{

View File

@ -10,6 +10,7 @@
#pragma once
void startGame(const QStringList & args);
void startEditor(const QStringList & args);
#ifdef VCMI_IOS
extern "C" void launchGame(int argc, char * argv[]);

View File

@ -47,12 +47,40 @@ void MainWindow::load()
settings.init();
}
void MainWindow::computeSidePanelSizes()
{
QVector<QToolButton*> widgets = {
ui->modslistButton,
ui->settingsButton,
ui->lobbyButton,
ui->startEditorButton,
ui->startGameButton
};
for(auto & widget : widgets)
{
QFontMetrics metrics(widget->font());
QSize iconSize = widget->iconSize();
// this is minimal space that is needed for our button to avoid text clipping
int buttonHeight = iconSize.height() + metrics.height() + 4;
widget->setMinimumHeight(buttonHeight);
widget->setMaximumHeight(buttonHeight * 1.2);
}
}
MainWindow::MainWindow(QWidget * parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
load(); // load FS before UI
updateTranslation(); // load translation
ui->setupUi(this);
connect(ui->lobbyView, &Lobby::enableMod, ui->modlistView, &CModListView::enableModByName);
connect(ui->lobbyView, &Lobby::disableMod, ui->modlistView, &CModListView::disableModByName);
connect(ui->modlistView, &CModListView::modsChanged, ui->lobbyView, &Lobby::updateMods);
//load window settings
QSettings s(Ui::teamName, Ui::appName);
@ -68,45 +96,32 @@ MainWindow::MainWindow(QWidget * parent)
move(position);
}
//set default margins
#ifndef ENABLE_EDITOR
ui->startEditorButton->hide();
#endif
computeSidePanelSizes();
auto width = ui->startGameTitle->fontMetrics().boundingRect(ui->startGameTitle->text()).width();
if(ui->startGameButton->iconSize().width() < width)
{
ui->startGameButton->setIconSize(QSize(width, width));
}
auto tab_icon_size = ui->tabSelectList->iconSize();
if(tab_icon_size.width() < width)
{
ui->tabSelectList->setIconSize(QSize(width, width + tab_icon_size.height() - tab_icon_size.width()));
ui->tabSelectList->setGridSize(QSize(width, width));
// 4 is a dirty hack to make it look right
ui->tabSelectList->setMaximumWidth(width + 4);
}
ui->tabListWidget->setCurrentIndex(0);
ui->settingsView->isExtraResolutionsModEnabled = ui->modlistView->isExtraResolutionsModEnabled();
ui->settingsView->setDisplayList();
connect(ui->modlistView, &CModListView::extraResolutionsEnabledChanged,
ui->settingsView, &CSettingsView::fillValidResolutions);
connect(ui->tabSelectList, &QListWidget::currentRowChanged, [this](int i) {
#ifdef Q_OS_IOS
if(auto widget = qApp->focusWidget())
widget->clearFocus();
#endif
ui->tabListWidget->setCurrentIndex(i);
});
#ifdef Q_OS_IOS
QScroller::grabGesture(ui->tabSelectList, QScroller::LeftMouseButtonGesture);
ui->tabSelectList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
#endif
if(settings["launcher"]["updateOnStartup"].Bool())
UpdateDialog::showUpdateDialog(false);
}
void MainWindow::changeEvent(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
}
QMainWindow::changeEvent(event);
}
MainWindow::~MainWindow()
{
//save window settings
@ -122,12 +137,63 @@ void MainWindow::on_startGameButton_clicked()
startGame({});
}
void MainWindow::on_tabSelectList_currentRowChanged(int currentRow)
void MainWindow::on_startEditorButton_clicked()
{
ui->startGameButton->setEnabled(currentRow != TabRows::LOBBY);
startEditor({});
}
const CModList & MainWindow::getModList() const
{
return ui->modlistView->getModList();
}
void MainWindow::on_modslistButton_clicked()
{
ui->startGameButton->setEnabled(true);
ui->tabListWidget->setCurrentIndex(TabRows::MODS);
}
void MainWindow::on_settingsButton_clicked()
{
ui->startGameButton->setEnabled(true);
ui->tabListWidget->setCurrentIndex(TabRows::SETTINGS);
}
void MainWindow::on_lobbyButton_clicked()
{
ui->startGameButton->setEnabled(false);
ui->tabListWidget->setCurrentIndex(TabRows::LOBBY);
}
void MainWindow::updateTranslation()
{
#ifdef ENABLE_QT_TRANSLATIONS
std::string translationFile = settings["general"]["language"].String() + ".qm";
logGlobal->info("Loading translation '%s'", translationFile);
QVector<QString> searchPaths;
#ifdef Q_OS_IOS
searchPaths.push_back(pathToQString(VCMIDirs::get().binaryPath() / "translation" / translationFile));
#else
for(auto const & string : VCMIDirs::get().dataPaths())
searchPaths.push_back(pathToQString(string / "launcher" / "translation" / translationFile));
searchPaths.push_back(pathToQString(VCMIDirs::get().userDataPath() / "launcher" / "translation" / translationFile));
#endif
for(auto const & string : boost::adaptors::reverse(searchPaths))
{
logGlobal->info("Searching for translation at '%s'", string.toStdString());
if (translator.load(string))
{
logGlobal->info("Translation found");
if (!qApp->installTranslator(&translator))
logGlobal->error("Failed to install translator");
return;
}
}
logGlobal->error("Failed to find translation");
#endif
}

View File

@ -10,6 +10,7 @@
#pragma once
#include <QMainWindow>
#include <QStringList>
#include <QTranslator>
namespace Ui
{
@ -26,6 +27,9 @@ class MainWindow : public QMainWindow
{
Q_OBJECT
#ifdef ENABLE_QT_TRANSLATIONS
QTranslator translator;
#endif
private:
Ui::MainWindow * ui;
void load();
@ -35,16 +39,22 @@ private:
MODS = 0, SETTINGS = 1, LOBBY = 2
};
void changeEvent(QEvent *event) override;
public:
explicit MainWindow(QWidget * parent = 0);
~MainWindow();
const CModList & getModList() const;
void updateTranslation();
void computeSidePanelSizes();
public slots:
void on_startGameButton_clicked();
private slots:
void on_tabSelectList_currentRowChanged(int currentRow);
void on_modslistButton_clicked();
void on_settingsButton_clicked();
void on_lobbyButton_clicked();
void on_startEditorButton_clicked();
};

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>800</width>
<height>480</height>
<height>410</height>
</rect>
</property>
<property name="sizePolicy">
@ -30,127 +30,288 @@
</size>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QListWidget" name="tabSelectList">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>89</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>89</width>
<height>89</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::NoDragDrop</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectItems</enum>
</property>
<property name="iconSize">
<size>
<width>89</width>
<height>89</height>
</size>
</property>
<property name="movement">
<enum>QListView::Static</enum>
</property>
<property name="flow">
<enum>QListView::TopToBottom</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>false</bool>
</property>
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
<property name="gridSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<property name="text">
<string>Mods</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-mods.png</normaloff>icons:menu-mods.png</iconset>
</property>
<widget class="QToolButton" name="modslistButton">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Mods</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-mods.png</normaloff>icons:menu-mods.png</iconset>
</property>
<property name="iconSize">
<size>
<width>60</width>
<height>60</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<property name="text">
<string>Settings</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-settings.png</normaloff>icons:menu-settings.png</iconset>
</property>
<widget class="QToolButton" name="settingsButton">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Settings</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-settings.png</normaloff>icons:menu-settings.png</iconset>
</property>
<property name="iconSize">
<size>
<width>60</width>
<height>60</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<property name="text">
<string>Lobby</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-lobby.png</normaloff>icons:menu-lobby.png</iconset>
</property>
<widget class="QToolButton" name="lobbyButton">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Lobby</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-lobby.png</normaloff>icons:menu-lobby.png</iconset>
</property>
<property name="iconSize">
<size>
<width>60</width>
<height>60</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</widget>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="startEditorButton">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>5</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Map Editor</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-editor.png</normaloff>icons:menu-editor.png</iconset>
</property>
<property name="iconSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<property name="autoRaise">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="startGameButton">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Start game</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-game.png</normaloff>icons:menu-game.png</iconset>
</property>
<property name="iconSize">
<size>
<width>60</width>
<height>60</height>
</size>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<property name="autoRaise">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="startGameTitle">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Start game</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="3">
<item>
<widget class="QStackedWidget" name="tabListWidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
@ -162,44 +323,6 @@
<widget class="Lobby" name="lobbyView"/>
</widget>
</item>
<item row="1" column="0">
<widget class="QToolButton" name="startGameButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Maximum">
<horstretch>89</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>89</width>
<height>89</height>
</size>
</property>
<property name="text">
<string>Play</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-game.png</normaloff>icons:menu-game.png</iconset>
</property>
<property name="iconSize">
<size>
<width>60</width>
<height>60</height>
</size>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
@ -224,10 +347,6 @@
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabSelectList</tabstop>
<tabstop>startGameButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -10,6 +10,7 @@
#include "StdInc.h"
#include "cmodlist.h"
#include "../lib/CConfigHandler.h"
#include "../../lib/JsonNode.h"
#include "../../lib/filesystem/CFileInputStream.h"
#include "../../lib/GameConstants.h"
@ -125,7 +126,7 @@ bool CModEntry::isCompatible() const
bool CModEntry::isEssential() const
{
return getValue("storedLocaly").toBool();
return getName() == "vcmi";
}
bool CModEntry::isInstalled() const
@ -158,23 +159,34 @@ QString CModEntry::getName() const
QVariant CModEntry::getValue(QString value) const
{
QString langValue = QString::fromStdString(settings["general"]["language"].String());
// Priorities
// 1) data from newest version
// 2) data from preferred language
bool useRepositoryData = repository.contains(value);
if(repository.contains(value) && localData.contains(value))
{
// value is present in both repo and locally installed. Select one from latest version
QString installedVer = localData["installedVersion"].toString();
QString availableVer = repository["latestVersion"].toString();
if(compareVersions(installedVer, availableVer))
return repository[value];
else
return localData[value];
useRepositoryData = compareVersions(installedVer, availableVer);
}
if(repository.contains(value))
return repository[value];
auto & storage = useRepositoryData ? repository : localData;
if(localData.contains(value))
return localData[value];
if(storage.contains(langValue))
{
auto langStorage = storage[langValue].toMap();
if (langStorage.contains(value))
return langStorage[value];
}
if(storage.contains(value))
return storage[value];
return QVariant();
}

View File

@ -25,16 +25,6 @@ static const QString names[ModFields::COUNT] =
"author"
};
static const QString header[ModFields::COUNT] =
{
"Name",
"", // status icon
"", // status icon
"Type",
"Version",
"Size",
"Author"
};
}
namespace ModStatus
@ -155,8 +145,19 @@ Qt::ItemFlags CModListModel::flags(const QModelIndex &) const
QVariant CModListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
static const QString header[ModFields::COUNT] =
{
QT_TR_NOOP("Name"),
QT_TR_NOOP(""), // status icon
QT_TR_NOOP(""), // status icon
QT_TR_NOOP("Type"),
QT_TR_NOOP("Version"),
QT_TR_NOOP("Size"),
QT_TR_NOOP("Author")
};
if(role == Qt::DisplayRole && orientation == Qt::Horizontal)
return ModFields::header[section];
return QCoreApplication::translate("ModFields", header[section].toStdString().c_str());
return QVariant();
}

View File

@ -34,6 +34,16 @@ void CModListView::setupModModel()
this, &CModListView::extraResolutionsEnabledChanged);
}
void CModListView::changeEvent(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
modModel->reloadRepositories();
}
QWidget::changeEvent(event);
}
void CModListView::setupFilterModel()
{
filterModel = new CModFilterModel(modModel, this);
@ -227,8 +237,8 @@ QString CModListView::genModInfoText(CModEntry & mod)
QString textTemplate = prefix + "</p><p align=\"justify\">%2</p>";
QString listTemplate = "<p align=\"justify\">%1: %2</p>";
QString noteTemplate = "<p align=\"justify\">%1</p>";
QString compatibleString = prefix + "Mod is compatible</p>";
QString incompatibleString = redPrefix + "Mod is incompatible</p>";
QString compatibleString = prefix + tr("Mod is compatible") + "</p>";
QString incompatibleString = redPrefix + tr("Mod is incompatible") + "</p>";
QString supportedVersions = redPrefix + "%2 %3 %4</p>";
QString result;
@ -496,7 +506,14 @@ QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled)
void CModListView::on_enableButton_clicked()
{
QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
enableModByName(modName);
checkManagerErrors();
}
void CModListView::enableModByName(QString modName)
{
assert(findBlockingMods(modName).empty());
assert(findInvalidDependencies(modName).empty());
@ -505,17 +522,24 @@ void CModListView::on_enableButton_clicked()
if(modModel->getMod(name).isDisabled())
manager->enableMod(name);
}
checkManagerErrors();
emit modsChanged();
}
void CModListView::on_disableButton_clicked()
{
QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
disableModByName(modName);
checkManagerErrors();
}
void CModListView::disableModByName(QString modName)
{
if(modModel->hasMod(modName) && modModel->getMod(modName).isEnabled())
manager->disableMod(modName);
checkManagerErrors();
emit modsChanged();
}
void CModListView::on_updateButton_clicked()
@ -544,6 +568,8 @@ void CModListView::on_uninstallButton_clicked()
manager->disableMod(modName);
manager->uninstallMod(modName);
}
emit modsChanged();
checkManagerErrors();
}
@ -631,6 +657,8 @@ void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFi
if(doInstallFiles)
installFiles(savedFiles);
emit modsChanged();
}
void CModListView::hideProgressBar()

View File

@ -64,8 +64,11 @@ class CModListView : public QWidget
QString genChangelogText(CModEntry & mod);
QString genModInfoText(CModEntry & mod);
void changeEvent(QEvent *event) override;
signals:
void extraResolutionsEnabledChanged(bool enabled);
void modsChanged();
public:
explicit CModListView(QWidget * parent = 0);
@ -82,6 +85,10 @@ public:
bool isExtraResolutionsModEnabled() const;
const CModList & getModList() const;
public slots:
void enableModByName(QString modName);
void disableModByName(QString modName);
private slots:
void dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight);

Some files were not shown because too many files have changed in this diff Show More