diff --git a/.gitignore b/.gitignore index 56200bfd8..0c0b53772 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,29 @@ +/client/vcmiclient +/server/vcmiserver +/launcher/vcmilauncher +/launcher/vcmilauncher_automoc.cpp + *.dll *.exe *.depend *.o *.a +*.so *.res *.layout *.pro.user *.pro.user.* /CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +Makefile +cmake_install.cmake +install_manifest.txt +*_cotire.cmake +cotire +moc_*.cpp +qrc_*.cpp +ui_*.h +/CPackConfig.cmake +/CPackSourceConfig.cmake +build-* diff --git a/AI/BattleAI/main.cpp b/AI/BattleAI/main.cpp index 5432c3037..fa9b67a7d 100644 --- a/AI/BattleAI/main.cpp +++ b/AI/BattleAI/main.cpp @@ -7,7 +7,7 @@ #define strcpy_s(a, b, c) strncpy(a, c, b) #endif -#ifdef __ANDROID__ +#ifdef VCMI_ANDROID #define GetGlobalAiVersion BattleAI_GetGlobalAiVersion #define GetAiName BattleAI_GetAiName #define GetNewBattleAI BattleAI_GetNewBattleAI diff --git a/AI/StupidAI/main.cpp b/AI/StupidAI/main.cpp index 07cfedee2..70ab20a03 100644 --- a/AI/StupidAI/main.cpp +++ b/AI/StupidAI/main.cpp @@ -7,7 +7,7 @@ #define strcpy_s(a, b, c) strncpy(a, c, b) #endif -#ifdef __ANDROID__ +#ifdef VCMI_ANDROID #define GetGlobalAiVersion StupidAI_GetGlobalAiVersion #define GetAiName StupidAI_GetAiName #define GetNewBattleAI StupidAI_GetNewBattleAI diff --git a/AI/VCAI/main.cpp b/AI/VCAI/main.cpp index 26f3e52c6..04f15137c 100644 --- a/AI/VCAI/main.cpp +++ b/AI/VCAI/main.cpp @@ -5,7 +5,7 @@ #define strcpy_s(a, b, c) strncpy(a, c, b) #endif -#ifdef __ANDROID__ +#ifdef VCMI_ANDROID #define GetGlobalAiVersion VCAI_GetGlobalAiVersion #define GetAiName VCAI_GetAiName #define GetNewAI VCAI_GetNewAI diff --git a/CMakeLists.txt b/CMakeLists.txt index 43f27aec5..629a92527 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,12 +198,27 @@ SET(PCH_PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "StdInc.h" ) +find_path(MINIZIP_INCLUDE_PATH NAMES minizip/unzip.h) +find_library(MINIZIP_LIB NAMES minizip PATH_SUFFIXES dynamic) +mark_as_advanced(MINIZIP_INCLUDE_PATH MINIZIP_LIB) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MINIZIP MINIZIP_INCLUDE_PATH MINIZIP_LIB) +if (MINIZIP_FOUND) + set(MINIZIP_INCLUDE_DIR ${MINIZIP_INCLUDE_PATH}) + set(MINIZIP_LIBRARIES ${MINIZIP_LIB}) + add_definitions(-DUSE_SYSTEM_MINIZIP) +endif() + if (ENABLE_ERM) add_subdirectory(scripting/erm) endif() +if (NOT MINIZIP_FOUND) + add_subdirectory(lib/minizip) + set(MINIZIP_LIBRARIES minizip) +endif() add_subdirectory(lib) add_subdirectory(client) -add_subdirectory(lib/minizip) add_subdirectory(server) add_subdirectory(AI) if (ENABLE_EDITOR) diff --git a/ChangeLog b/ChangeLog index 18fba62ce..77f519c0f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,8 @@ GENERAL: * VCMI can now be compiled with SDL2 * Better upscaling when running in fullscreen mode. * Non-latin characters can now be entered in chat window or used for save names. +* (windows) Moved VCMI data directory from '%userprofile%\vcmi' to '%userprofile%\Documents\My Games\vcmi' +* (windows, OSX) Moved VCMI save directory from 'VCMI_DATA\Games' to 'VCMI_DATA\Saves' RANDOM MAP GENERATOR: diff --git a/Global.h b/Global.h index baa9ee036..b63219e8e 100644 --- a/Global.h +++ b/Global.h @@ -49,10 +49,58 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); # pragma warning (disable : 4800 ) /* disable conversion to bool warning -- I think it's intended in all places */ #endif +/* ---------------------------------------------------------------------------- */ +/* System detection. */ +/* ---------------------------------------------------------------------------- */ +// Based on: http://sourceforge.net/p/predef/wiki/OperatingSystems/ +// and on: http://stackoverflow.com/questions/5919996/how-to-detect-reliably-mac-os-x-ios-linux-windows-in-c-preprocessor +// TODO?: Should be moved to vstd\os_detect.h (and then included by Global.h) +#ifdef _WIN16 // Defined for 16-bit environments +# error "16-bit Windows isn't supported" +#elif defined(_WIN64) // Defined for 64-bit environments +# define VCMI_WINDOWS +# define VCMI_WINDOWS_64 +#elif defined(_WIN32) // Defined for both 32-bit and 64-bit environments +# define VCMI_WINDOWS +# define VCMI_WINDOWS_32 +#elif defined(_WIN32_WCE) +# error "Windows CE isn't supported" +#elif defined(__linux__) || defined(__gnu_linux__) || defined(linux) || defined(__linux) +# define VCMI_UNIX +# define VCMI_LINUX +# ifdef __ANDROID__ +# define VCMI_ANDROID +# endif +#elif defined(__APPLE__) && defined(__MACH__) +# define VCMI_UNIX +# define VCMI_APPLE +# include "TargetConditionals.h" +# if TARGET_IPHONE_SIMULATOR +# define VCMI_IOS +# define VCMI_IOS_SIM +# elif TARGET_OS_IPHONE +# define VCMI_IOS +# elif TARGET_OS_MAC +# define VCMI_MAC +# else +//# warning "Unknown Apple target."? +# endif +#else +# error "VCMI supports only Windows, OSX, Linux and Android targets" +#endif + +#ifdef VCMI_IOS +# error "iOS system isn't yet supported." +#endif + /* ---------------------------------------------------------------------------- */ /* Commonly used C++, Boost headers */ /* ---------------------------------------------------------------------------- */ -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#ifdef VCMI_WINDOWS +# define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers - delete this line if something is missing. +# define NOMINMAX // Exclude min/max macros from . Use std::[min/max] from instead. +#endif + #define _USE_MATH_DEFINES #include @@ -86,7 +134,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #define BOOST_FILESYSTEM_VERSION 3 #if BOOST_VERSION > 105000 -#define BOOST_THREAD_VERSION 3 +# define BOOST_THREAD_VERSION 3 #endif #define BOOST_THREAD_DONT_PROVIDE_THREAD_DESTRUCTOR_CALLS_TERMINATE_IF_JOINABLE 1 #define BOOST_BIND_NO_PLACEHOLDERS @@ -99,9 +147,12 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #include #include #include +#include +#include #include #include #include +#include #include #include #include @@ -112,13 +163,8 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #include #include - -#ifdef ANDROID -#include -#endif - #ifndef M_PI -#define M_PI 3.14159265358979323846 +# define M_PI 3.14159265358979323846 #endif /* ---------------------------------------------------------------------------- */ @@ -151,30 +197,20 @@ typedef boost::lock_guard TLockGuardRec; /* Macros */ /* ---------------------------------------------------------------------------- */ // Import + Export macro declarations -#ifdef _WIN32 +#ifdef VCMI_WINDOWS # ifdef __GNUC__ +# define DLL_IMPORT __attribute__((dllimport)) # define DLL_EXPORT __attribute__((dllexport)) # else +# define DLL_IMPORT __declspec(dllimport) # define DLL_EXPORT __declspec(dllexport) # endif # define ELF_VISIBILITY #else # ifdef __GNUC__ +# define DLL_IMPORT __attribute__ ((visibility("default"))) # define DLL_EXPORT __attribute__ ((visibility("default"))) # define ELF_VISIBILITY __attribute__ ((visibility("default"))) -# endif -#endif - -#ifdef _WIN32 -# ifdef __GNUC__ -# define DLL_IMPORT __attribute__((dllimport)) -# else -# define DLL_IMPORT __declspec(dllimport) -# endif -# define ELF_VISIBILITY -#else -# ifdef __GNUC__ -# define DLL_IMPORT __attribute__ ((visibility("default"))) # define ELF_VISIBILITY __attribute__ ((visibility("default"))) # endif #endif diff --git a/Mods/vcmi/Data/StackQueueLarge.png b/Mods/vcmi/Data/StackQueueLarge.png new file mode 100644 index 000000000..5211f9c73 Binary files /dev/null and b/Mods/vcmi/Data/StackQueueLarge.png differ diff --git a/Mods/vcmi/Data/StackQueueSmall.png b/Mods/vcmi/Data/StackQueueSmall.png new file mode 100644 index 000000000..a439f3a74 Binary files /dev/null and b/Mods/vcmi/Data/StackQueueSmall.png differ diff --git a/Mods/vcmi/Data/questDialog.png b/Mods/vcmi/Data/questDialog.png new file mode 100644 index 000000000..51666fac0 Binary files /dev/null and b/Mods/vcmi/Data/questDialog.png differ diff --git a/Mods/vcmi/Data/stackWindow/bonus-effects.png b/Mods/vcmi/Data/stackWindow/bonus-effects.png new file mode 100644 index 000000000..aed22ccdf Binary files /dev/null and b/Mods/vcmi/Data/stackWindow/bonus-effects.png differ diff --git a/Mods/vcmi/Data/stackWindow/button-panel.png b/Mods/vcmi/Data/stackWindow/button-panel.png new file mode 100644 index 000000000..3a75239d8 Binary files /dev/null and b/Mods/vcmi/Data/stackWindow/button-panel.png differ diff --git a/Mods/vcmi/Data/stackWindow/commander-abilities.png b/Mods/vcmi/Data/stackWindow/commander-abilities.png new file mode 100644 index 000000000..42c4b885e Binary files /dev/null and b/Mods/vcmi/Data/stackWindow/commander-abilities.png differ diff --git a/Mods/vcmi/Data/stackWindow/commander-bg.png b/Mods/vcmi/Data/stackWindow/commander-bg.png new file mode 100644 index 000000000..e274a979d Binary files /dev/null and b/Mods/vcmi/Data/stackWindow/commander-bg.png differ diff --git a/Mods/vcmi/Data/stackWindow/icons.png b/Mods/vcmi/Data/stackWindow/icons.png new file mode 100644 index 000000000..2ce296048 Binary files /dev/null and b/Mods/vcmi/Data/stackWindow/icons.png differ diff --git a/Mods/vcmi/Data/stackWindow/info-panel-0.png b/Mods/vcmi/Data/stackWindow/info-panel-0.png new file mode 100644 index 000000000..3933e4867 Binary files /dev/null and b/Mods/vcmi/Data/stackWindow/info-panel-0.png differ diff --git a/Mods/vcmi/Data/stackWindow/info-panel-1.png b/Mods/vcmi/Data/stackWindow/info-panel-1.png new file mode 100644 index 000000000..175e2a9dd Binary files /dev/null and b/Mods/vcmi/Data/stackWindow/info-panel-1.png differ diff --git a/Mods/vcmi/Data/stackWindow/info-panel-2.png b/Mods/vcmi/Data/stackWindow/info-panel-2.png new file mode 100644 index 000000000..f18130afd Binary files /dev/null and b/Mods/vcmi/Data/stackWindow/info-panel-2.png differ diff --git a/Mods/vcmi/Data/stackWindow/spell-effects.png b/Mods/vcmi/Data/stackWindow/spell-effects.png new file mode 100644 index 000000000..268da2a52 Binary files /dev/null and b/Mods/vcmi/Data/stackWindow/spell-effects.png differ diff --git a/Mods/vcmi/Sprites/buttons/commander.json b/Mods/vcmi/Sprites/buttons/commander.json new file mode 100644 index 000000000..a5dacd51c --- /dev/null +++ b/Mods/vcmi/Sprites/buttons/commander.json @@ -0,0 +1,8 @@ +{ + "basepath" : "buttons/", + "images" : + [ + { "frame" : 0, "file" : "commanderNormal.png"}, + { "frame" : 1, "file" : "commanderPressed.png"} + ] +} diff --git a/Mods/vcmi/Sprites/buttons/commanderNormal.png b/Mods/vcmi/Sprites/buttons/commanderNormal.png new file mode 100644 index 000000000..9d91a3a3f Binary files /dev/null and b/Mods/vcmi/Sprites/buttons/commanderNormal.png differ diff --git a/Mods/vcmi/Sprites/buttons/commanderPressed.png b/Mods/vcmi/Sprites/buttons/commanderPressed.png new file mode 100644 index 000000000..b0b75f54e Binary files /dev/null and b/Mods/vcmi/Sprites/buttons/commanderPressed.png differ diff --git a/Mods/vcmi/Sprites/buttons/resolution.json b/Mods/vcmi/Sprites/buttons/resolution.json new file mode 100644 index 000000000..e28b0b4c5 --- /dev/null +++ b/Mods/vcmi/Sprites/buttons/resolution.json @@ -0,0 +1,8 @@ +{ + "basepath" : "buttons/", + "images" : + [ + { "frame" : 0, "file" : "resolutionNormal.png"}, + { "frame" : 1, "file" : "resolutionPressed.png"} + ] +} diff --git a/Mods/vcmi/Sprites/buttons/resolutionNormal.png b/Mods/vcmi/Sprites/buttons/resolutionNormal.png new file mode 100644 index 000000000..459dad2da Binary files /dev/null and b/Mods/vcmi/Sprites/buttons/resolutionNormal.png differ diff --git a/Mods/vcmi/Sprites/buttons/resolutionPressed.png b/Mods/vcmi/Sprites/buttons/resolutionPressed.png new file mode 100644 index 000000000..04e9a9288 Binary files /dev/null and b/Mods/vcmi/Sprites/buttons/resolutionPressed.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/cancel-normal.png b/Mods/vcmi/Sprites/stackWindow/cancel-normal.png new file mode 100644 index 000000000..017b6ef43 Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/cancel-normal.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/cancel-pressed.png b/Mods/vcmi/Sprites/stackWindow/cancel-pressed.png new file mode 100644 index 000000000..76ca993be Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/cancel-pressed.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/cancelButton.json b/Mods/vcmi/Sprites/stackWindow/cancelButton.json new file mode 100644 index 000000000..579feae62 --- /dev/null +++ b/Mods/vcmi/Sprites/stackWindow/cancelButton.json @@ -0,0 +1,8 @@ +{ + "basepath" : "stackWindow/", + "images" : + [ + { "frame" : 0, "file" : "cancel-normal.png"}, + { "frame" : 1, "file" : "cancel-pressed.png"} + ] +} diff --git a/Mods/vcmi/Sprites/stackWindow/level-0.png b/Mods/vcmi/Sprites/stackWindow/level-0.png new file mode 100644 index 000000000..227401e2d Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-0.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/level-1.png b/Mods/vcmi/Sprites/stackWindow/level-1.png new file mode 100644 index 000000000..e107d1f22 Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-1.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/level-10.png b/Mods/vcmi/Sprites/stackWindow/level-10.png new file mode 100644 index 000000000..84b57b1ea Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-10.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/level-2.png b/Mods/vcmi/Sprites/stackWindow/level-2.png new file mode 100644 index 000000000..bc079b063 Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-2.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/level-3.png b/Mods/vcmi/Sprites/stackWindow/level-3.png new file mode 100644 index 000000000..206faa9cd Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-3.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/level-4.png b/Mods/vcmi/Sprites/stackWindow/level-4.png new file mode 100644 index 000000000..1d544b22a Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-4.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/level-5.png b/Mods/vcmi/Sprites/stackWindow/level-5.png new file mode 100644 index 000000000..2853c871b Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-5.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/level-6.png b/Mods/vcmi/Sprites/stackWindow/level-6.png new file mode 100644 index 000000000..003dc77ab Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-6.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/level-7.png b/Mods/vcmi/Sprites/stackWindow/level-7.png new file mode 100644 index 000000000..bf954324c Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-7.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/level-8.png b/Mods/vcmi/Sprites/stackWindow/level-8.png new file mode 100644 index 000000000..237a3286f Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-8.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/level-9.png b/Mods/vcmi/Sprites/stackWindow/level-9.png new file mode 100644 index 000000000..7e41614ce Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/level-9.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/levels.json b/Mods/vcmi/Sprites/stackWindow/levels.json new file mode 100644 index 000000000..26a235e4c --- /dev/null +++ b/Mods/vcmi/Sprites/stackWindow/levels.json @@ -0,0 +1,17 @@ +{ + "basepath" : "stackWindow/", + "images" : + [ + { "frame" : 0, "file" : "level-0.png"}, + { "frame" : 1, "file" : "level-1.png"}, + { "frame" : 2, "file" : "level-2.png"}, + { "frame" : 3, "file" : "level-3.png"}, + { "frame" : 4, "file" : "level-4.png"}, + { "frame" : 5, "file" : "level-5.png"}, + { "frame" : 6, "file" : "level-6.png"}, + { "frame" : 7, "file" : "level-7.png"}, + { "frame" : 8, "file" : "level-8.png"}, + { "frame" : 9, "file" : "level-9.png"}, + { "frame" : 10,"file" : "level-10.png"} + ] +} diff --git a/Mods/vcmi/Sprites/stackWindow/switchModeIcons.json b/Mods/vcmi/Sprites/stackWindow/switchModeIcons.json new file mode 100644 index 000000000..a870ed04b --- /dev/null +++ b/Mods/vcmi/Sprites/stackWindow/switchModeIcons.json @@ -0,0 +1,7 @@ +{ + "images" : + [ + { "frame" : 0, "file" : "SECSK32:69"}, + { "frame" : 1, "file" : "SECSK32:28"} + ] +} diff --git a/Mods/vcmi/Sprites/stackWindow/upgrade-normal.png b/Mods/vcmi/Sprites/stackWindow/upgrade-normal.png new file mode 100644 index 000000000..c5cb9d390 Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/upgrade-normal.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/upgrade-pressed.png b/Mods/vcmi/Sprites/stackWindow/upgrade-pressed.png new file mode 100644 index 000000000..bafd221b0 Binary files /dev/null and b/Mods/vcmi/Sprites/stackWindow/upgrade-pressed.png differ diff --git a/Mods/vcmi/Sprites/stackWindow/upgradeButton.json b/Mods/vcmi/Sprites/stackWindow/upgradeButton.json new file mode 100644 index 000000000..c976a6bf3 --- /dev/null +++ b/Mods/vcmi/Sprites/stackWindow/upgradeButton.json @@ -0,0 +1,8 @@ +{ + "basepath" : "stackWindow/", + "images" : + [ + { "frame" : 0, "file" : "upgrade-normal.png"}, + { "frame" : 1, "file" : "upgrade-pressed.png"} + ] +} diff --git a/README.linux b/README.linux index 72346f3a5..12f914307 100644 --- a/README.linux +++ b/README.linux @@ -31,43 +31,50 @@ On Debian-based systems (e.g. Ubuntu) run: sudo apt-get install cmake g++ libsdl1.2debian libsdl-image1.2-dev libsdl-ttf2.0-dev libsdl-mixer1.2-dev zlib1g-dev libavformat-dev libswscale-dev libboost-dev libboost-filesystem-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-locale-dev qtbase5-dev On RPM-based distributions (e.g. Fedora) run: - sudo yum install cmake gcc-c++ SDL-devel SDL_image-devel SDL_ttf-devel SDL_mixer-devel boost boost-devel boost-filesystem boost-system boost-thread boost-program-options boost-locale zlib-devel ffmpeg-devel ffmpeg-libs + sudo yum install cmake gcc-c++ SDL2-devel SDL2_image-devel SDL2_ttf-devel SDL2_mixer-devel boost boost-devel boost-filesystem boost-system boost-thread boost-program-options boost-locale zlib-devel ffmpeg-devel ffmpeg-libs II. Getting the sources VCMI is still in development. We recommend the following initial directory structure: trunk -trunk/src -> contains sources and is under SVN control +trunk/vcmi -> contains sources and is under git control trunk/build -> contains build output, makefiles, object files,... You can get latest sources with subversion: - cd trunk - svn co http://svn.code.sf.net/p/vcmi/code/trunk/ + git clone https://github.com/vcmi/vcmi.git III. Compilation Run configure: mkdir build && cd build - cmake ../src + cmake ../vcmi Additional options that you may want to use: To enable debugging: -DCMAKE_BUILD_TYPE=Debug -To enable launcher: -DENABLE_LAUNCHER=Yes +To change installation directory: -DCMAKE_INSTALL_PREFIX=$absolute_path_to_directory Notice: -The ../src/ is not a typo, it will place makefile scripts into the build dir +The ../vcmi/ is not a typo, it will place makefile scripts into the build dir as the build dir is your working dir when calling CMake. Then build vcmi: - make -j2 (j2 = compile with 2 cpu cores, you can specifiy any value) + make -j2 (j2 = compile with 2 threads, you can specify any value) -That will generate vcmiclient, vcmiserver as well as 3 .so libraries. +That will generate vcmiclient, vcmiserver, vcmilauncher as well as 3 .so libraries. III. Installing binaries To install VCMI you can use "make install" command however generation of distribution-specific packages is usually a better idea. In most cases this can be achieved using tool called "checkinstall" -If you're compiling vcmi for development puposes, it's better to use links instead. +If you're compiling vcmi for development puposes, the easiest is to use cmake prefix and then make install: + +# mkdir .../trunk/install +# cmake -DCMAKE_INSTALL_PREFIX=.../trunk/install ../vcmi +# make && make install +# .../trunk/install/bin/vcmiclient + + +it's better to use links instead. Go to /BIN_PATH/, and type: ln -s .../trunk/build/client/vcmiclient @@ -86,5 +93,3 @@ Go to /LIB_PATH/vcmi/AI, and type: Go to /DATA_PATH/vcmi, and type: ln -s .../trunk/source/config ln -s .../trunk/source/Mods - - diff --git a/README.md b/README.md index 60c80e7e8..bca695f21 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ For building from source see project wiki at http://wiki.vcmi.eu/ ## Copyright and license -VCMI Project is released under GPL version 2 or later +VCMI Project source code is licensed under GPL version 2 or later. +VCMI Project assets are licensed under CC-BY-SA 4.0. Assets sources and information about contributors are available under following link: [https://github.com/vcmi/vcmi-assets] Copyright (C) 2007-2014 VCMI Team (check AUTHORS file for the contributors list) diff --git a/client/CCreatureWindow.cpp b/client/CCreatureWindow.cpp deleted file mode 100644 index b524e654b..000000000 --- a/client/CCreatureWindow.cpp +++ /dev/null @@ -1,935 +0,0 @@ -#include "StdInc.h" -#include "CCreatureWindow.h" - -#include "../lib/CCreatureSet.h" -#include "CGameInfo.h" -#include "../lib/CGeneralTextHandler.h" -#include "../lib/BattleState.h" -#include "../CCallback.h" - -#include -#include "gui/SDL_Extensions.h" -#include "CBitmapHandler.h" -#include "CDefHandler.h" -#include "Graphics.h" -#include "CPlayerInterface.h" -#include "../lib/CConfigHandler.h" -#include "CAnimation.h" - -#include "../lib/CGameState.h" -#include "../lib/BattleState.h" -#include "../lib/CSpellHandler.h" -#include "../lib/CArtHandler.h" -#include "../lib/NetPacksBase.h" //ArtifactLocation -#include "../lib/CModHandler.h" -#include "../lib/IBonusTypeHandler.h" - -#include "gui/CGuiHandler.h" -#include "gui/CIntObjectClasses.h" - -using namespace CSDL_Ext; - -class CCreatureArtifactInstance; -class CSelectableSkill; - -/* - * CCreatureWindow.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 - * - */ - -CCreatureWindow::CCreatureWindow (const CStack &stack, CreWinType Type): - CWindowObject(PLAYER_COLORED | (Type == OTHER ? RCLICK_POPUP : 0 ) ), - type(Type) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - if (stack.base) - init(stack.base, &stack, dynamic_cast(stack.base->armyObj)); - else - { - auto s = new CStackInstance(stack.type, 1); //TODO: war machines and summons should be regular stacks - init(s, &stack, nullptr); - delete s; - } -} - -CCreatureWindow::CCreatureWindow (const CStackInstance &stack, CreWinType Type): - CWindowObject(PLAYER_COLORED | (Type == OTHER ? RCLICK_POPUP : 0 ) ), - type(Type) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - init(&stack, &stack, dynamic_cast(stack.armyObj)); -} - -CCreatureWindow::CCreatureWindow(CreatureID Cid, CreWinType Type, int creatureCount): - CWindowObject(PLAYER_COLORED | (Type == OTHER ? RCLICK_POPUP : 0 ) ), - type(Type) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - auto stack = new CStackInstance(Cid, creatureCount); //TODO: simplify? - init(stack, CGI->creh->creatures[Cid], nullptr); - delete stack; -} - -CCreatureWindow::CCreatureWindow(const CStackInstance &st, CreWinType Type, std::function Upg, std::function Dsm, UpgradeInfo *ui): - CWindowObject(PLAYER_COLORED | (Type == OTHER ? RCLICK_POPUP : 0 ) ), - type(Type), - dismiss(nullptr), - upgrade(nullptr), - ok(nullptr), - dsm(Dsm) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - init(&st, &st,dynamic_cast(st.armyObj)); - - //print abilities text - if r-click popup - if(type) - { - if(Upg && ui) - { - TResources upgradeCost = ui->cost[0] * st.count; - for(TResources::nziterator i(upgradeCost); i.valid(); i++) - { - BLOCK_CAPTURING; - upgResCost.push_back(new CComponent(CComponent::resource, i->resType, i->resVal)); - } - - if(LOCPLINT->cb->getResourceAmount().canAfford(upgradeCost)) - { - CFunctionList fs; - fs += Upg; - fs += std::bind(&CCreatureWindow::close,this); - CFunctionList cfl; - cfl += std::bind(&CPlayerInterface::showYesNoDialog, - LOCPLINT, CGI->generaltexth->allTexts[207], fs, nullptr, false, upgResCost); - upgrade = new CAdventureMapButton("",CGI->generaltexth->zelp[446].second,cfl,385, 148,"IVIEWCR.DEF",SDLK_u); - } - else - { - upgrade = new CAdventureMapButton("",CGI->generaltexth->zelp[446].second,std::function(),385, 148,"IVIEWCR.DEF"); - upgrade->callback.funcs.clear(); - upgrade->setOffset(2); - } - - } - if(Dsm) - { - CFunctionList fs[2]; - //on dismiss confirmed - fs[0] += Dsm; //dismiss - fs[0] += std::bind(&CCreatureWindow::close,this);//close this window - CFunctionList cfl; - cfl = std::bind(&CPlayerInterface::showYesNoDialog,LOCPLINT,CGI->generaltexth->allTexts[12],fs[0],fs[1],false,std::vector()); - dismiss = new CAdventureMapButton("",CGI->generaltexth->zelp[445].second,cfl,333, 148,"IVIEWCR2.DEF",SDLK_d); - } - } -} - -CCreatureWindow::CCreatureWindow (const CCommanderInstance * Commander, const CStack * stack): - CWindowObject(PLAYER_COLORED), - commander (Commander) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - if (stack) - { - type = COMMANDER_BATTLE; - init(commander, stack, dynamic_cast(commander->armyObj)); - } - else - { - type = COMMANDER; - init(commander, commander, dynamic_cast(commander->armyObj)); - } - - std::function Dsm; - CFunctionList fs[2]; - //on dismiss confirmed - fs[0] += Dsm; //dismiss - fs[0] += std::bind(&CCreatureWindow::close,this);//close this window - CFunctionList cfl; - cfl = std::bind(&CPlayerInterface::showYesNoDialog,LOCPLINT,CGI->generaltexth->allTexts[12],fs[0],fs[1],false,std::vector()); - if (type < COMMANDER_LEVEL_UP) //can dismiss only in regular window - dismiss = new CAdventureMapButton("",CGI->generaltexth->zelp[445].second, cfl, 333, 148,"IVIEWCR2.DEF", SDLK_d); -} - -CCreatureWindow::CCreatureWindow (std::vector &skills, const CCommanderInstance * Commander, std::function callback): - CWindowObject(PLAYER_COLORED), - type(COMMANDER_LEVEL_UP), - commander (Commander), - selectedOption (0), //choose something before drawing - upgradeOptions(skills), //copy skills to choose from - levelUp (callback) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - init(commander, commander, dynamic_cast(commander->armyObj)); - - std::function Dsm; - CFunctionList fs[2]; - //on dismiss confirmed - fs[0] += Dsm; //dismiss - fs[0] += std::bind(&CCreatureWindow::close,this);//close this window - CFunctionList cfl; - cfl = std::bind(&CPlayerInterface::showYesNoDialog,LOCPLINT,CGI->generaltexth->allTexts[12],fs[0],fs[1],false,std::vector()); - if (type < COMMANDER_LEVEL_UP) //can dismiss only in regular window - dismiss = new CAdventureMapButton("",CGI->generaltexth->zelp[445].second, cfl, 333, 148,"IVIEWCR2.DEF", SDLK_d); -} - -void CCreatureWindow::init(const CStackInstance *Stack, const CBonusSystemNode *StackNode, const CGHeroInstance *HeroOwner) -{ - creatureArtifact = nullptr; //may be set later - artifactImage = nullptr; - spellEffectsPics = nullptr; - stack = Stack; - c = stack->type; - if(!StackNode) - stackNode = c; - else - stackNode = StackNode; - const CStack *battleStack = dynamic_cast(stackNode); //only during battle - heroOwner = HeroOwner; - - if (battleStack) - count = boost::lexical_cast(battleStack->count); - else if (Stack->count) - count = boost::lexical_cast(Stack->count); - - if (type < COMMANDER) - commander = nullptr; - - bool creArt = false; - displayedArtifact = ArtifactPosition::CREATURE_SLOT; // 0 - - //Basic graphics - need to calculate size - - int commanderOffset = 0; - if (type >= COMMANDER) - commanderOffset = 74; - - if (commander) //secondary skills - { - creArt = true; - for (int i = ECommander::ATTACK; i <= ECommander::SPELL_POWER; ++i) - { - if (commander->secondarySkills[i] || vstd::contains(upgradeOptions, i)) - { - std::string file = skillToFile(i); - - skillPictures.push_back(new CPicture(file, 0,0)); - } - } - - if (type == COMMANDER_LEVEL_UP) - { - for (auto option : upgradeOptions) - { - ui32 index = selectableSkills.size(); - auto selectableSkill = new CSelectableSkill(); - selectableSkill->callback = std::bind(&CCreatureWindow::selectSkill, this, index); - - if (option < 100) - { - selectableSkill->pos = skillPictures[index]->pos; //resize - selectableSkills.push_back (selectableSkill); - } - else - { - selectableSkill->pos = Rect (95, 256, 55, 55); //TODO: scroll - const Bonus *b = CGI->creh->skillRequirements[option-100].first; - bonusItems.push_back (new CBonusItem (genRect(0, 0, 251, 57), stack->bonusToString(b, false), stack->bonusToString(b, true), stack->bonusToGraphics(b))); - selectableBonuses.push_back (selectableSkill); //insert these before other bonuses - } - } - } - } - - BonusList bl, blTemp; - blTemp = (*(stackNode->getBonuses(Selector::durationType(Bonus::PERMANENT).And(Selector::anyRange())))); - - while (blTemp.size()) - { - Bonus * b = blTemp.front(); - - bl.push_back (new Bonus(*b)); - bl.back()->val = blTemp.valOfBonuses(Selector::typeSubtype(b->type, b->subtype)); //merge multiple bonuses into one - blTemp.remove_if (Selector::typeSubtype(b->type, b->subtype)); //remove used bonuses - } - - std::string text, img; - for(Bonus* b : bl) - { - text = stack->bonusToString(b, false); - img = stack->bonusToGraphics(b); - if (text.size() || img.size()) //if it's possible to give any description or image for this kind of bonus - { - bonusItems.push_back (new CBonusItem(genRect(0, 0, 251, 57), text, stack->bonusToString(b, true), img)); - } - } - - - //handle Magic resistance separately :/ - const IBonusBearer *temp = stack; - - if (battleStack) - { - temp = battleStack; - } - - int magicResistance = temp->magicResistance(); - - if (magicResistance) - { - Bonus b; - b.type = Bonus::MAGIC_RESISTANCE; - - text = VLC->getBth()->bonusToString(&b,temp,false); - const std::string description = VLC->getBth()->bonusToString(&b,temp,true); - bonusItems.push_back (new CBonusItem(genRect(0, 0, 251, 57), text, description, stack->bonusToGraphics(&b))); - } - - bonusRows = std::min ((int)((bonusItems.size() + 1) / 2), (screen->h - 230) / 60); - if (type >= COMMANDER) - vstd::amin(bonusRows, 3); - else - vstd::amin(bonusRows, 4); - vstd::amax(bonusRows, 1); - - if (type >= COMMANDER) - { - setBackground("CommWin" + boost::lexical_cast(bonusRows) + ".pcx"); - for (int i = 0; i < skillPictures.size(); ++i) - { - skillPictures[i]->moveTo (Point (pos.x + 37 + i * 84, pos.y + 224)); - } - for (int i = 0; i < selectableSkills.size(); ++i) - { - if (upgradeOptions[i] < skillPictures.size()) // it's secondary skill - { - selectableSkills[i]->pos = skillPictures[upgradeOptions[i]]->pos; //dirty workaround - } - else - break; - } - //print commander level - new CLabel(488, 62, FONT_MEDIUM, CENTER, Colors::YELLOW, - boost::lexical_cast((ui16)(commander->level))); - - new CLabel(488, 82, FONT_SMALL, CENTER, Colors::WHITE, - boost::lexical_cast(stack->experience)); - } - else - setBackground("CreWin" + boost::lexical_cast(bonusRows) + ".pcx"); //1 to 4 rows for now - - //Buttons - ok = new CAdventureMapButton("",CGI->generaltexth->zelp[445].second, std::bind(&CCreatureWindow::close,this), 489, 148, "hsbtns.def", SDLK_RETURN); - ok->assignedKeys.insert(SDLK_ESCAPE); - - if (type <= BATTLE) //in battle or info window - { - upgrade = nullptr; - dismiss = nullptr; - } - anim = new CCreaturePic(22, 48, c); - - //Stats - morale = new MoraleLuckBox(true, genRect(42, 42, 335, 100)); - morale->set(stackNode); - luck = new MoraleLuckBox(false, genRect(42, 42, 387, 100)); - luck->set(stackNode); - - new CAnimImage("PSKIL42", 4, 0, 387, 51); //exp icon - Print it always? - if (type) //not in fort window - { - if (CGI->modh->modules.STACK_EXP && type < COMMANDER) - { - int rank = std::min(stack->getExpRank(), 10); //hopefully nobody adds more - new CLabel(488, 82, FONT_SMALL, CENTER, Colors::WHITE, boost::lexical_cast(stack->experience)); - new CLabel(488, 62, FONT_MEDIUM, CENTER, Colors::YELLOW, - CGI->generaltexth->zcrexp[rank] + " [" + boost::lexical_cast(rank) + "]"); - - if (type > BATTLE) //we need it only on adv. map - { - int tier = stack->type->level; - if (!vstd::iswithin(tier, 1, 7)) - tier = 0; - int number; - std::string expText = CGI->generaltexth->zcrexp[324]; - boost::replace_first (expText, "%s", c->namePl); - boost::replace_first (expText, "%s", CGI->generaltexth->zcrexp[rank]); - boost::replace_first (expText, "%i", boost::lexical_cast(rank)); - boost::replace_first (expText, "%i", boost::lexical_cast(stack->experience)); - number = CGI->creh->expRanks[tier][rank] - stack->experience; - boost::replace_first (expText, "%i", boost::lexical_cast(number)); - - number = CGI->creh->maxExpPerBattle[tier]; //percent - boost::replace_first (expText, "%i%", boost::lexical_cast(number)); - number *= CGI->creh->expRanks[tier].back() / 100; //actual amount - boost::replace_first (expText, "%i", boost::lexical_cast(number)); - - boost::replace_first (expText, "%i", boost::lexical_cast(stack->count)); //Number of Creatures in stack - - int expmin = std::max(CGI->creh->expRanks[tier][std::max(rank-1, 0)], (ui32)1); - number = (stack->count * (stack->experience - expmin)) / expmin; //Maximum New Recruits without losing current Rank - boost::replace_first (expText, "%i", boost::lexical_cast(number)); //TODO - - boost::replace_first (expText, "%.2f", boost::lexical_cast(1)); //TODO Experience Multiplier - number = CGI->creh->expAfterUpgrade; - boost::replace_first (expText, "%.2f", boost::lexical_cast(number) + "%"); //Upgrade Multiplier - - expmin = CGI->creh->expRanks[tier][9]; - int expmax = CGI->creh->expRanks[tier][10]; - number = expmax - expmin; - boost::replace_first (expText, "%i", boost::lexical_cast(number)); //Experience after Rank 10 - number = (stack->count * (expmax - expmin)) / expmin; - boost::replace_first (expText, "%i", boost::lexical_cast(number)); //Maximum New Recruits to remain at Rank 10 if at Maximum Experience - - expArea = new LRClickableAreaWTextComp(Rect(334, 49, 160, 44),CComponent::experience); - expArea->text = expText; - expArea->bonusValue = 0; //TDO: some specific value or no number at all - } - } - - if (CGI->modh->modules.STACK_ARTIFACT) - { - creArt = true; - } - } - if (creArt) //stack or commander artifacts - { - setArt (stack->getArt(ArtifactPosition::CREATURE_SLOT)); - if (type > BATTLE && type < COMMANDER_BATTLE) //artifact buttons inactive in battle - { - //TODO: disable buttons if no artifact is equipped - leftArtRoll = new CAdventureMapButton(std::string(), std::string(), std::bind (&CCreatureWindow::scrollArt, this, -1), 437, 98, "hsbtns3.def", SDLK_LEFT); - rightArtRoll = new CAdventureMapButton(std::string(), std::string(), std::bind (&CCreatureWindow::scrollArt, this, +1), 516, 98, "hsbtns5.def", SDLK_RIGHT); - if (heroOwner) - passArtToHero = new CAdventureMapButton(std::string(), std::string(), std::bind (&CCreatureWindow::passArtifactToHero, this), 437, 148, "OVBUTN1.DEF", SDLK_HOME); - } - } - - if (battleStack) //only during battle - { - spellEffectsPics = new CAnimation("SpellInt.def"); - - //spell effects - int printed=0; //how many effect pics have been printed - std::vector spells = battleStack->activeSpells(); - for(si32 effect : spells) - { - const si32 imageIndex = effect+1; //there is "null" frame with index 0 in SpellInt.def - std::string spellText; - spellEffectsPics->load(imageIndex); - IImage * effectIcon = spellEffectsPics->getImage(imageIndex,0,false); //todo: better way to determine presence of icon - spellEffectsPics->unload(imageIndex); - if (effectIcon != nullptr) //not all effects have graphics (for eg. Acid Breath) - { - spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds." - boost::replace_first (spellText, "%s", CGI->spellh->objects[effect]->name); - int duration = battleStack->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain; - boost::replace_first (spellText, "%d", boost::lexical_cast(duration)); - - new CAnimImage("SpellInt.def", imageIndex, 0, 20 + 52 * printed, 184); - spellEffects.push_back(new LRClickableAreaWText(Rect(20 + 52 * printed, 184, 50, 38), spellText, spellText)); - if (++printed >= 10) //we can fit only 10 effects - break; - } - } - - //print current health - printLine (5, CGI->generaltexth->allTexts[200], battleStack->firstHPleft); - } - - if (bonusItems.size() > (bonusRows << 1)) //only after graphics are created - { - slider = new CSlider(528, 231 + commanderOffset, bonusRows*60, std::bind (&CCreatureWindow::sliderMoved, this, _1), - bonusRows, (bonusItems.size() + 1) >> 1, 0, false, 0); - } - else //slider automatically places bonus Items - recreateSkillList (0); - - showAll(screen2); - - //AUIDAT.DEF -} - -void CCreatureWindow::printLine(int nr, const std::string &text, int baseVal, int val/*=-1*/, bool range/*=false*/) -{ - new CLabel(162, 48 + nr*19, FONT_SMALL, TOPLEFT, Colors::WHITE, text); - - std::string hlp; - if(range && baseVal != val) - hlp = boost::str(boost::format("%d - %d") % baseVal % val); - else if(baseVal != val && val>=0) - hlp = boost::str(boost::format("%d (%d)") % baseVal % val); - else - hlp = boost::lexical_cast(baseVal); - - new CLabel(325, 64 + nr*19, FONT_SMALL, BOTTOMRIGHT, Colors::WHITE, hlp); -} - -void CCreatureWindow::recreateSkillList(int Pos) -{ - int commanderOffset = 0; - if (type >= COMMANDER) - commanderOffset = 74; - - int n = 0, i = 0, j = 0; - int numSkills = std::min ((bonusRows + Pos) << 1, (int)bonusItems.size()); - for (n = 0; n < Pos << 1; ++n) - { - bonusItems[n]->visible = false; - if (n < selectableBonuses.size()) - selectableBonuses[n]->deactivate(); //we assume that bonuses are at front of the list - } - for (n = Pos << 1; n < numSkills; ++n) - { - int offsetx = 257*j - (bonusRows == 4 ? 1 : 0); - int offsety = 60*i + (bonusRows > 1 ? 1 : 0) + commanderOffset; //lack of precision :/ - - bonusItems[n]->moveTo (Point(pos.x + offsetx + 10, pos.y + offsety + 230), true); - bonusItems[n]->visible = true; - if (n < selectableBonuses.size()) - { - selectableBonuses[n]->moveTo (Point(bonusItems[n]->pos.x + 12, bonusItems[n]->pos.y + 2)); //for some reason bonusItems have dimensions 0? - //selectableBonuses[n]->pos = bonusItems[n]->bonusGraphics->pos; - selectableBonuses[n]->activate(); - } - - if (++j > 1) //next line - { - ++i; - j = 0; - } - } - for (n = numSkills; n < bonusItems.size(); ++n) - { - bonusItems[n]->visible = false; - if (n < selectableBonuses.size()) - selectableBonuses[n]->deactivate(); - } -} - -void CCreatureWindow::showAll(SDL_Surface * to) -{ - CIntObject::showAll(to); - - printAtMiddleLoc((type >= COMMANDER ? c->nameSing : c->namePl), 180, 30, FONT_SMALL, Colors::YELLOW, to); //creature name - - printLine(0, CGI->generaltexth->primarySkillNames[0], c->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), stackNode->Attack()); - printLine(1, CGI->generaltexth->primarySkillNames[1], c->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE), stackNode->Defense()); - - if (stackNode->valOfBonuses(Bonus::SHOTS) && stackNode->hasBonusOfType(Bonus::SHOOTER)) - {//only for shooting units - important with wog exp shooters - if (type == BATTLE) - printLine(2, CGI->generaltexth->allTexts[198], stackNode->valOfBonuses(Bonus::SHOTS), dynamic_cast(stackNode)->shots); - else - printLine(2, CGI->generaltexth->allTexts[198], stackNode->valOfBonuses(Bonus::SHOTS)); - } - if (stackNode->valOfBonuses(Bonus::CASTS)) - { - printAtMiddleLoc(CGI->generaltexth->allTexts[399], 356, 62, FONT_SMALL, Colors::WHITE, to); - std::string casts; - if (type == BATTLE) - casts = boost::lexical_cast((ui16)dynamic_cast(stackNode)->casts); //ui8 is converted to char :( - else - casts = boost::lexical_cast(stackNode->valOfBonuses(Bonus::CASTS)); - printAtMiddleLoc(casts, 356, 82, FONT_SMALL, Colors::WHITE, to); - } - - //TODO - int dmgMultiply = 1; - if(heroOwner && stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON)) - dmgMultiply += heroOwner->Attack(); - - printLine(3, CGI->generaltexth->allTexts[199], stackNode->getMinDamage() * dmgMultiply, stackNode->getMaxDamage() * dmgMultiply, true); - printLine(4, CGI->generaltexth->allTexts[388], c->valOfBonuses(Bonus::STACK_HEALTH), stackNode->valOfBonuses(Bonus::STACK_HEALTH)); - printLine(6, CGI->generaltexth->zelp[441].first, c->Speed(), stackNode->Speed()); - - for(CBonusItem* b : bonusItems) - b->showAll (to); - - for(auto s : selectableSkills) - s->showAll (to); - - for (int i = 0; i < skillPictures.size(); i++) - { - skillPictures[i]->bg = BitmapHandler::loadBitmap (skillToFile(i)); - skillPictures[i]->showAll (to); - } - - if (upgradeOptions.size() && (type == COMMANDER_LEVEL_UP && upgradeOptions[selectedOption] >= 100)) //add frame to selected skill - { - int index = selectedOption - selectableSkills.size(); //this is screwed - CSDL_Ext::drawBorder(to, Rect::around(selectableBonuses[index]->pos), int3(Colors::METALLIC_GOLD.r, Colors::METALLIC_GOLD.g, Colors::METALLIC_GOLD.b)); - } -} - -void CCreatureWindow::show(SDL_Surface * to) -{ - CIntObject::show(to); - if (!count.empty()) //army stack - graphics->fonts[FONT_TIMES]->renderTextRight(to, count, Colors::WHITE, Point(pos.x + 114, pos.y + 174)); -} - - -void CCreatureWindow::close() -{ - if (upgradeOptions.size()) //a skill for commander was chosen - levelUp (selectedOption); //callback value is vector index - - GH.popIntTotally(this); -} - -void CCreatureWindow::sliderMoved(int newpos) -{ - recreateSkillList(newpos); //move components - redraw(); -} - -std::string CCreatureWindow::skillToFile (int skill) -{ - std::string file = "zvs/Lib1.res/_"; - switch (skill) - { - case ECommander::ATTACK: - file += "AT"; - break; - case ECommander::DEFENSE: - file += "DF"; - break; - case ECommander::HEALTH: - file += "HP"; - break; - case ECommander::DAMAGE: - file += "DM"; - break; - case ECommander::SPEED: - file += "SP"; - break; - case ECommander::SPELL_POWER: - file += "MP"; - break; - } - std::string sufix = boost::lexical_cast((int)(commander->secondarySkills[skill])); //casting ui8 causes ascii char conversion - if (type == COMMANDER_LEVEL_UP) - { - if (upgradeOptions.size() && upgradeOptions[selectedOption] == skill)//that one specific skill is selected - sufix += "="; //level-up highlight - else if (!vstd::contains(upgradeOptions, skill)) - sufix = "no"; //not available - no number - } - file += sufix += ".bmp"; - - return file; -} - -void CCreatureWindow::setArt(const CArtifactInstance *art) -{ - creatureArtifact = art; - if (creatureArtifact) - { - if (artifactImage == nullptr) - artifactImage = new CAnimImage("ARTIFACT", creatureArtifact->artType->iconIndex, 0, 466, 100); - else - artifactImage->setFrame(creatureArtifact->artType->iconIndex); - } - else - artifactImage = nullptr; - - redraw(); -} - -void CCreatureWindow::scrollArt(int dir) -{ - //TODO: get next artifact - int size = stack->artifactsWorn.size(); - displayedArtifact = size ? static_cast((displayedArtifact + dir) % size) - : static_cast(ArtifactPosition::CREATURE_SLOT); - setArt (stack->getArt(displayedArtifact)); -} - -void CCreatureWindow::passArtifactToHero() -{ - const CGHeroInstance * h = dynamic_cast(stack->armyObj); - if (h && creatureArtifact) - { - LOCPLINT->cb->swapArtifacts (ArtifactLocation (stack, displayedArtifact), ArtifactLocation(h, creatureArtifact->firstBackpackSlot(h))); - } - else - logGlobal->warnStream() << "Pass artifact to hero should be disabled, no hero or no artifact!"; - - //redraw is handled via CArtifactHolder interface -} - -void CCreatureWindow::artifactRemoved (const ArtifactLocation &artLoc) -{ - //align artifacts to remove holes - for (auto al : stack->artifactsWorn) - { - ArtifactPosition freeSlot = al.second.artifact->firstAvailableSlot(stack); - if (freeSlot < al.first) - LOCPLINT->cb->swapArtifacts (ArtifactLocation(stack, al.first), ArtifactLocation(stack, freeSlot)); - } - int size = stack->artifactsWorn.size(); - displayedArtifact = size ? static_cast(displayedArtifact % size) - : static_cast(ArtifactPosition::CREATURE_SLOT); //0 - setArt (stack->getArt(displayedArtifact)); -} -void CCreatureWindow::artifactMoved (const ArtifactLocation &artLoc, const ArtifactLocation &destLoc) -{ - artifactRemoved (artLoc); //same code -} - -void CCreatureWindow::selectSkill (ui32 which) -{ - selectedOption = which; - redraw(); -} - -CCreatureWindow::~CCreatureWindow() -{ - for (auto & elem : upgResCost) - delete elem; - bonusItems.clear(); - - if(spellEffectsPics!=nullptr) - delete spellEffectsPics; -} - -CBonusItem::CBonusItem() -{ - -} - -CBonusItem::CBonusItem(const Rect &Pos, const std::string &Name, const std::string &Description, const std::string &graphicsName) -{ - OBJ_CONSTRUCTION; - visible = false; - - name = Name; - description = Description; - if (graphicsName.size()) - bonusGraphics = new CPicture(graphicsName, 26, 232); - else - bonusGraphics = nullptr; - - removeUsedEvents(ALL); //no actions atm -} - -void CBonusItem::showAll (SDL_Surface * to) -{ - if (visible) - { - graphics->fonts[FONT_SMALL]->renderTextLeft(to, name, Colors::YELLOW, Point(pos.x + 72, pos.y + 6)); - graphics->fonts[FONT_SMALL]->renderTextLeft(to, description, Colors::WHITE, Point(pos.x + 72, pos.y + 30)); - if (bonusGraphics && bonusGraphics->bg) - blitAtLoc(bonusGraphics->bg, 12, 2, to); - } -} - -CBonusItem::~CBonusItem() -{ - //delete bonusGraphics; //automatic destruction -} - -void CSelectableSkill::clickLeft(tribool down, bool previousState) -{ - if (down) - callback(); -} - -void CCreInfoWindow::show(SDL_Surface * to) -{ - CIntObject::show(to); - creatureCount->showAll(to); -} - -CCreInfoWindow::CCreInfoWindow(const CStackInstance &stack, bool LClicked, std::function upgradeFunc, std::function dismissFunc, UpgradeInfo *upgradeInfo): - CWindowObject(PLAYER_COLORED | (LClicked ? 0 : RCLICK_POPUP), "CRSTKPU") -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - init(stack.type, &stack, dynamic_cast(stack.armyObj), stack.count, LClicked); - - //additional buttons if opened with left click - if(LClicked) - { - std::function closeFunc = std::bind(&CCreInfoWindow::close,this); - - if(upgradeFunc && upgradeInfo) - { - TResources upgradeCost = upgradeInfo->cost[0] * stack.count; - for(TResources::nziterator i(upgradeCost); i.valid(); i++) - { - BLOCK_CAPTURING; - upgResCost.push_back(new CComponent(CComponent::resource, i->resType, i->resVal)); - } - - CFunctionList onUpgrade; - onUpgrade += upgradeFunc; - onUpgrade += closeFunc; - - std::function dialog = std::bind(&CPlayerInterface::showYesNoDialog, - LOCPLINT, - CGI->generaltexth->allTexts[207], - onUpgrade, 0, false, - std::ref(upgResCost)); - - upgrade = new CAdventureMapButton("", CGI->generaltexth->zelp[446].second, dialog, 76, 237, "IVIEWCR", SDLK_u); - upgrade->block(!LOCPLINT->cb->getResourceAmount().canAfford(upgradeCost)); - } - - if(dismissFunc) - { - CFunctionList onDismiss; - onDismiss += dismissFunc; - onDismiss += closeFunc; - - std::function dialog = std::bind(&CPlayerInterface::showYesNoDialog, - LOCPLINT, - CGI->generaltexth->allTexts[12], - onDismiss, 0, true, std::vector()); - - dismiss = new CAdventureMapButton("", CGI->generaltexth->zelp[445].second, dialog, 21, 237, "IVIEWCR2",SDLK_d); - } - } -} - -CCreInfoWindow::CCreInfoWindow(int creatureID, bool LClicked, int creatureCount): - CWindowObject(PLAYER_COLORED | (LClicked ? 0 : RCLICK_POPUP), "CRSTKPU") -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - const CCreature *creature = CGI->creh->creatures[creatureID]; - init(creature, nullptr, nullptr, creatureCount, LClicked); -} - -CCreInfoWindow::CCreInfoWindow(const CStack &stack, bool LClicked): - CWindowObject(PLAYER_COLORED | (LClicked ? 0 : RCLICK_POPUP), "CRSTKPU") -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - init(stack.getCreature(), &stack, stack.getMyHero(), stack.count, LClicked); -} - -CCreInfoWindow::~CCreInfoWindow() -{ - for(CComponent* object : upgResCost) - delete object; -} - -void CCreInfoWindow::printLine(int position, const std::string &text, int baseVal, int val/*=-1*/, bool range/*=false*/) -{ - infoTexts[position].first = new CLabel(155, 48 + position*19, FONT_SMALL, TOPLEFT, Colors::WHITE, text); - std::string valueStr; - - if(range && baseVal != val) - valueStr = boost::str(boost::format("%d - %d") % baseVal % val); - - else if(baseVal != val && val>=0) - valueStr = boost::str(boost::format("%d (%d)") % baseVal % val); - - else - valueStr = boost::lexical_cast(baseVal); - - infoTexts[position].second = new CLabel(276, 63 + position*19, FONT_SMALL, BOTTOMRIGHT, Colors::WHITE, valueStr); -} - -void CCreInfoWindow::init(const CCreature *creature, const CBonusSystemNode *stackNode, const CGHeroInstance *heroOwner, int count, bool LClicked) -{ - removeUsedEvents(ALL); - if (!LClicked) - addUsedEvents(RCLICK); - - if(!stackNode) - stackNode = creature; - - animation = new CCreaturePic(21, 48, creature); - - std::string countStr = boost::lexical_cast(count); - creatureCount = new CLabel(114, 174, FONT_TIMES, BOTTOMRIGHT, Colors::WHITE, countStr); - - creatureName = new CLabel(149, 30, FONT_SMALL, CENTER, Colors::YELLOW, creature->namePl); - - printLine(0, CGI->generaltexth->primarySkillNames[0], creature->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), stackNode->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK)); - printLine(1, CGI->generaltexth->primarySkillNames[1], creature->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE), stackNode->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE)); - - if(stackNode->valOfBonuses(Bonus::SHOTS)) - printLine(2, CGI->generaltexth->allTexts[198], stackNode->valOfBonuses(Bonus::SHOTS)); - - //TODO - int dmgMultiply = 1; - if(heroOwner && stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON)) - dmgMultiply += heroOwner->Attack(); - - printLine(3, CGI->generaltexth->allTexts[199], stackNode->getMinDamage() * dmgMultiply, stackNode->getMaxDamage() * dmgMultiply, true); - printLine(4, CGI->generaltexth->allTexts[388], creature->valOfBonuses(Bonus::STACK_HEALTH), stackNode->valOfBonuses(Bonus::STACK_HEALTH)); - printLine(6, CGI->generaltexth->zelp[441].first, creature->valOfBonuses(Bonus::STACKS_SPEED), stackNode->valOfBonuses(Bonus::STACKS_SPEED)); - - //setting morale - morale = new MoraleLuckBox(true, genRect(42, 42, 22, 186)); - morale->set(stackNode); - //setting luck - luck = new MoraleLuckBox(false, genRect(42, 42, 75, 186)); - luck->set(stackNode); - - if(!LClicked) - { - abilityText = new CLabel(17, 231, FONT_SMALL, TOPLEFT, Colors::WHITE, creature->abilityText); - } - else - { - abilityText = nullptr; - ok = new CAdventureMapButton("", CGI->generaltexth->zelp[445].second, - std::bind(&CCreInfoWindow::close,this), 216, 237, "IOKAY.DEF", SDLK_RETURN); - ok->assignedKeys.insert(SDLK_ESCAPE); - } - - //if we are displying window fo r stack in battle, there are several more things that we need to display - if(const CStack *battleStack = dynamic_cast(stackNode)) - { - //print at most 3 spell effects - std::vector spells = battleStack->activeSpells(); - for (size_t i=0; i< std::min(spells.size(), size_t(3)); i++) - effects.push_back(new CAnimImage("SpellInt", spells[i]+1, 0, 127 + 52*i, 186)); - - //print current health - printLine(5, CGI->generaltexth->allTexts[200], battleStack->firstHPleft); - } -} - -CIntObject * createCreWindow( - const CStack *s, bool lclick/* = false*/) -{ - auto c = dynamic_cast(s->base); - if (c) - { - return new CCreatureWindow (c, s); - } - else - { - if(settings["general"]["classicCreatureWindow"].Bool()) - return new CCreInfoWindow(*s, lclick); - else - return new CCreatureWindow(*s, LOCPLINT->battleInt ? CCreatureWindow::BATTLE : CCreatureWindow::OTHER); - } -} - -CIntObject * createCreWindow(CreatureID Cid, CCreatureWindow::CreWinType Type, int creatureCount) -{ - if(settings["general"]["classicCreatureWindow"].Bool()) - return new CCreInfoWindow(Cid, Type, creatureCount); - else - return new CCreatureWindow(Cid, Type, creatureCount); -} - -CIntObject * createCreWindow(const CStackInstance *s, CCreatureWindow::CreWinType type, std::function Upg, std::function Dsm, UpgradeInfo *ui) -{ - if(settings["general"]["classicCreatureWindow"].Bool()) - return new CCreInfoWindow(*s, type==CCreatureWindow::HERO, Upg, Dsm, ui); - else - return new CCreatureWindow(*s, type, Upg, Dsm, ui); -} diff --git a/client/CCreatureWindow.h b/client/CCreatureWindow.h deleted file mode 100644 index 39254da14..000000000 --- a/client/CCreatureWindow.h +++ /dev/null @@ -1,163 +0,0 @@ -#pragma once - -#include "gui/CIntObject.h" -#include "../lib/HeroBonus.h" -#include "GUIClasses.h" - -/* - * CCreatureWindow.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 - * - */ - -struct Bonus; -class CCreature; -class CStackInstance; -class CCommanderInstance; -class CStack; -struct ArtifactLocation; -class CCreatureArtifactInstance; -class CAdventureMapButton; -class CBonusItem; -class CGHeroInstance; -class CComponent; -class LRClickableAreaWText; -class MoraleLuckBox; -class CAdventureMapButton; -struct UpgradeInfo; -class CPicture; -class CCreaturePic; -class LRClickableAreaWTextComp; -class CSlider; -class CLabel; -class CAnimImage; -class CSelectableSkill; - -// New creature window -class CCreatureWindow : public CWindowObject, public CArtifactHolder -{ -public: - enum CreWinType {OTHER = 0, BATTLE = 1, ARMY = 2, HERO = 3, COMMANDER = 4, COMMANDER_LEVEL_UP = 5, COMMANDER_BATTLE = 6}; // > 3 are opened permanently - //bool active; //TODO: comment me - CreWinType type; - int bonusRows; //height of skill window - ArtifactPosition displayedArtifact; - - std::string count; //creature count in text format - const CCreature *c; //related creature - const CStackInstance *stack; - const CBonusSystemNode *stackNode; - const CCommanderInstance * commander; - const CGHeroInstance *heroOwner; - const CArtifactInstance *creatureArtifact; //currently worn artifact - std::vector upgResCost; //cost of upgrade (if not possible then empty) - std::vector bonusItems; - std::vector spellEffects; - - CCreaturePic *anim; //related creature's animation - MoraleLuckBox *luck, *morale; - LRClickableAreaWTextComp * expArea; //displays exp details - CSlider * slider; //Abilities - CAdventureMapButton *dismiss, *upgrade, *ok; - CAdventureMapButton * leftArtRoll, * rightArtRoll; //artifact selection - CAdventureMapButton * passArtToHero; - CAnimImage * artifactImage; - CAnimation * spellEffectsPics; //bitmaps representing spells affecting a stack in battle - - //commander level-up - int selectedOption; //index for upgradeOptions - std::vector upgradeOptions; //value 0-5 - secondary skills, 100+ - special skills - std::vector selectableSkills, selectableBonuses; - std::vector skillPictures; //secondary skills - - std::string skillToFile(int skill); //return bitmap for secondary skill depending on selection / avaliability - void selectSkill (ui32 which); - void setArt(const CArtifactInstance *creatureArtifact); - - void artifactRemoved (const ArtifactLocation &artLoc); - void artifactMoved (const ArtifactLocation &artLoc, const ArtifactLocation &destLoc); - void artifactDisassembled (const ArtifactLocation &artLoc) {return;}; - void artifactAssembled (const ArtifactLocation &artLoc) {return;}; - - std::function dsm; //dismiss button callback - std::function Upg; //upgrade button callback - std::function levelUp; //choose commander skill to level up - - CCreatureWindow(const CStack & stack, CreWinType type); //battle c-tor - CCreatureWindow (const CStackInstance &stack, CreWinType Type); //pop-up c-tor - CCreatureWindow(const CStackInstance &st, CreWinType Type, std::function Upg, std::function Dsm, UpgradeInfo *ui); //full garrison window - CCreatureWindow(const CCommanderInstance * commander, const CStack * stack = nullptr); //commander window - CCreatureWindow(std::vector &skills, const CCommanderInstance * commander, std::function callback); - CCreatureWindow(CreatureID Cid, CreWinType Type, int creatureCount); //c-tor - - void init(const CStackInstance *stack, const CBonusSystemNode *stackNode, const CGHeroInstance *heroOwner); - void showAll(SDL_Surface * to); - void show(SDL_Surface * to); - void printLine(int nr, const std::string &text, int baseVal, int val=-1, bool range=false); - void sliderMoved(int newpos); - void close(); - ~CCreatureWindow(); //d-tor - - void recreateSkillList(int pos); - void scrollArt(int dir); - void passArtifactToHero(); -}; - -class CBonusItem : public LRClickableAreaWTextComp //responsible for displaying creature skill, active or not -{ -public: - std::string name, description; - CPicture * bonusGraphics; - bool visible; - - CBonusItem(); - CBonusItem(const Rect &Pos, const std::string &Name, const std::string &Description, const std::string &graphicsName); - ~CBonusItem(); - - void showAll (SDL_Surface * to); -}; - -class CSelectableSkill : public LRClickableAreaWText -{ -public: - std::function callback; //TODO: create more generic clickable class than AdvMapButton? - - virtual void clickLeft(tribool down, bool previousState); - virtual void clickRight(tribool down, bool previousState){}; -}; - -/// original creature info window -class CCreInfoWindow : public CWindowObject -{ -public: - CLabel * creatureCount; - CLabel * creatureName; - CLabel * abilityText; - - CCreaturePic * animation; - std::vector upgResCost; //cost of upgrade (if not possible then empty) - std::vector effects; - std::map > infoTexts; - - MoraleLuckBox * luck, * morale; - - CAdventureMapButton * dismiss, * upgrade, * ok; - - CCreInfoWindow(const CStackInstance & st, bool LClicked, std::function Upg = nullptr, std::function Dsm = nullptr, UpgradeInfo * ui = nullptr); - CCreInfoWindow(const CStack & st, bool LClicked = 0); - CCreInfoWindow(int Cid, bool LClicked, int creatureCount); - ~CCreInfoWindow(); - - void init(const CCreature * cre, const CBonusSystemNode * stackNode, const CGHeroInstance * heroOwner, int creatureCount, bool LClicked); - void printLine(int nr, const std::string & text, int baseVal, int val = -1, bool range = false); - - void show(SDL_Surface * to); -}; - -CIntObject *createCreWindow(const CStack *s, bool lclick = false); -CIntObject *createCreWindow(CreatureID Cid, CCreatureWindow::CreWinType Type, int creatureCount); -CIntObject *createCreWindow(const CStackInstance *s, CCreatureWindow::CreWinType type, std::function Upg = nullptr, std::function Dsm = nullptr, UpgradeInfo *ui = nullptr); diff --git a/client/CMT.cpp b/client/CMT.cpp index 31f227f69..382bfd6e0 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -8,13 +8,13 @@ #include "../lib/filesystem/Filesystem.h" #include "CPreGame.h" -#include "CCastleInterface.h" +#include "windows/CCastleInterface.h" #include "../lib/CConsoleHandler.h" #include "gui/CCursorHandler.h" #include "../lib/CGameState.h" #include "../CCallback.h" #include "CPlayerInterface.h" -#include "CAdvmapInterface.h" +#include "windows/CAdvmapInterface.h" #include "../lib/CBuildingHandler.h" #include "CVideoHandler.h" #include "../lib/CHeroHandler.h" @@ -39,8 +39,9 @@ #include "../lib/GameConstants.h" #include "gui/CGuiHandler.h" #include "../lib/logging/CBasicLogConfigurator.h" +#include "../lib/CondSh.h" -#ifdef _WIN32 +#ifdef VCMI_WINDOWS #include "SDL_syswm.h" #endif #include "../lib/UnlockGuard.h" @@ -51,6 +52,7 @@ #endif namespace po = boost::program_options; +namespace bfs = boost::filesystem; /* * CMT.cpp, part of VCMI engine @@ -72,13 +74,12 @@ int preferredDriverIndex = -1; SDL_Window * mainWindow = nullptr; SDL_Renderer * mainRenderer = nullptr; SDL_Texture * screenTexture = nullptr; - #endif // VCMI_SDL1 extern boost::thread_specific_ptr inGuiThread; SDL_Surface *screen = nullptr, //main screen surface - *screen2 = nullptr,//and hlp surface (used to store not-active interfaces layer) + *screen2 = nullptr, //and hlp surface (used to store not-active interfaces layer) *screenBuf = screen; //points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed std::queue events; @@ -99,31 +100,29 @@ static void mainLoop(); void startGame(StartInfo * options, CConnection *serv = nullptr); void endGame(); -#ifndef _WIN32 +#ifndef VCMI_WINDOWS #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #endif -void startGameFromFile(const std::string &fname) +void startGameFromFile(const bfs::path &fname) { StartInfo si; try //attempt retrieving start info from given file { - if(!fname.size() || !boost::filesystem::exists(fname)) - throw std::runtime_error("Startfile \"" + fname + "\" does not exist!"); + if(fname.empty() || !bfs::exists(fname)) + throw std::runtime_error("Startfile \"" + fname.string() + "\" does not exist!"); CLoadFile out(fname); - if(!out.sfile || !*out.sfile) - { - throw std::runtime_error("Cannot read from startfile \"" + fname + "\"!"); - } + if (!out.sfile || !*out.sfile) + throw std::runtime_error("Cannot read from startfile \"" + fname.string() +"\"!"); out >> si; } catch(std::exception &e) { - logGlobal->errorStream() << "Failed to start from the file: " + fname << ". Error: " << e.what() + logGlobal->errorStream() << "Failed to start from the file: " << fname << ". Error: " << e.what() << " Falling back to main menu."; GH.curInt = CGPreGame::create(); return; @@ -183,19 +182,19 @@ static void prog_help(const po::options_description &opts) // printf(" -v, --version display version information and exit\n"); } -#ifdef __APPLE__ +#ifdef VCMI_APPLE void OSX_checkForUpdates(); #endif -#if defined(_WIN32) && !defined (__GNUC__) +#if defined(VCMI_WINDOWS) && !defined (__GNUC__) int wmain(int argc, wchar_t* argv[]) -#elif defined(__APPLE__) +#elif defined(VCMI_APPLE) int SDL_main(int argc, char *argv[]) #else int main(int argc, char** argv) #endif { -#ifdef __APPLE__ +#ifdef VCMI_APPLE // Correct working dir executable folder (not bundle folder) so we can use executable relative paths std::string executablePath = argv[0]; std::string workDir = executablePath.substr(0, executablePath.rfind('/')); @@ -205,7 +204,7 @@ int main(int argc, char** argv) OSX_checkForUpdates(); // Check that game data is prepared. Otherwise run vcmibuilder helper application - FILE* check = fopen((VCMIDirs::get().userDataPath() + "/game_data_prepared").c_str(), "r"); + FILE* check = fopen((VCMIDirs::get().userDataPath() / "game_data_prepared").string().c_str(), "r"); if (check == nullptr) { system("open ./vcmibuilder.app"); return 0; @@ -218,7 +217,7 @@ int main(int argc, char** argv) ("help,h", "display help and exit") ("version,v", "display version information and exit") ("battle,b", po::value(), "runs game in duel mode (battle-only") - ("start", po::value(), "starts game from saved StartInfo file") + ("start", po::value(), "starts game from saved StartInfo file") ("onlyAI", "runs without human player, all players will be default AI") ("noGUI", "runs without GUI, implies --onlyAI") ("ai", po::value>(), "AI to be used for the player, can be specified several times for the consecutive players") @@ -271,17 +270,17 @@ int main(int argc, char** argv) CStopWatch total, pomtime; std::cout.flags(std::ios::unitbuf); console = new CConsoleHandler; - *console->cb = std::bind(&processCommand, _1); + *console->cb = processCommand; console->start(); atexit(dispose); - const auto logPath = VCMIDirs::get().userCachePath() + "/VCMI_Client_log.txt"; + const bfs::path logPath = VCMIDirs::get().userCachePath() / "VCMI_Client_log.txt"; CBasicLogConfigurator logConfig(logPath, console); logConfig.configureDefault(); logGlobal->infoStream() << "Creating console and configuring logger: " << pomtime.getDiff(); logGlobal->infoStream() << "The log file will be saved to " << logPath; -#ifdef __ANDROID__ +#ifdef VCMI_ANDROID // boost will crash without this setenv("LANG", "C", 1); #endif @@ -303,8 +302,7 @@ int main(int argc, char** argv) }; if (!testFile("DATA/HELP.TXT", "Heroes III data") || - !testFile("MODS/VCMI/MOD.JSON", "VCMI mod") || - !testFile("DATA/StackQueueBgBig.PCX", "VCMI data")) + !testFile("MODS/VCMI/MOD.JSON", "VCMI data")) exit(1); // These are unrecoverable errors // these two are optional + some installs have them on CD and not in data directory @@ -388,7 +386,7 @@ int main(int argc, char** argv) logGlobal->infoStream()<<"\tInitializing video: "<(); + fileToStartFrom = vm["start"].as(); - if(fileToStartFrom.size() && boost::filesystem::exists(fileToStartFrom)) + if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom)) startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file else { - if(fileToStartFrom.size()) + if(!fileToStartFrom.empty()) { logGlobal->warnStream() << "Warning: cannot find given file to start from (" << fileToStartFrom << "). Falling back to main menu."; @@ -584,7 +582,8 @@ void processCommand(const std::string &message) { std::cout<<"Command accepted.\t"; - std::string outPath = VCMIDirs::get().userCachePath() + "/extracted/"; + const bfs::path outPath = + VCMIDirs::get().userCachePath() / "extracted"; auto list = CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident) { @@ -593,18 +592,18 @@ void processCommand(const std::string &message) for (auto & filename : list) { - std::string outName = outPath + filename.getName(); + const bfs::path filePath = outPath / (filename.getName() + ".TXT"); + + bfs::create_directories(filePath.parent_path()); - boost::filesystem::create_directories(outName.substr(0, outName.find_last_of("/"))); - - std::ofstream file(outName + ".TXT"); + 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"; + std::cout << " Extracted files can be found in " << outPath << " directory\n"; } else if(cn=="crash") { @@ -710,15 +709,13 @@ void processCommand(const std::string &message) { CDefEssential * cde = CDefHandler::giveDefEss(URI); - std::string outName = URI; - std::string outPath = VCMIDirs::get().userCachePath() + "/extracted/"; + const bfs::path outPath = VCMIDirs::get().userCachePath() / "extracted" / URI; + bfs::create_directories(outPath); - boost::filesystem::create_directories(outPath + outName); - - for (size_t i=0; iourImages.size(); i++) + for (size_t i = 0; i < cde->ourImages.size(); ++i) { - std::string filename = outPath + outName + '/' + boost::lexical_cast(i) + ".bmp"; - SDL_SaveBMP(cde->ourImages[i].bitmap, filename.c_str()); + const bfs::path filePath = outPath / (boost::lexical_cast(i)+".bmp"); + SDL_SaveBMP(cde->ourImages[i].bitmap, filePath.string().c_str()); } } else @@ -731,14 +728,12 @@ void processCommand(const std::string &message) if (CResourceHandler::get()->existsResource(ResourceID(URI))) { - std::string outName = URI; - std::string outPath = VCMIDirs::get().userCachePath() + "/extracted/"; - std::string fullPath = outPath + outName; + const bfs::path outPath = VCMIDirs::get().userCachePath() / "extracted" / URI; auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll(); - boost::filesystem::create_directories(fullPath.substr(0, fullPath.find_last_of("/"))); - std::ofstream outFile(outPath + outName, std::ofstream::binary); + bfs::create_directories(outPath.parent_path()); + bfs::ofstream outFile(outPath, bfs::ofstream::binary); outFile.write((char*)data.first.get(), data.second); } else @@ -1013,7 +1008,7 @@ static void setScreenRes(int w, int h, int bpp, bool fullscreen, bool resetVideo SDL_ShowCursor(SDL_DISABLE); SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); -#ifdef _WIN32 +#ifdef VCMI_WINDOWS SDL_SysWMinfo wm; SDL_VERSION(&wm.version); int getwm = SDL_GetWMInfo(&wm); diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 079a8392b..397c427a3 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -14,39 +14,56 @@ set(client_SRCS battle/CBattleInterfaceClasses.cpp battle/CCreatureAnimation.cpp + gui/CAnimation.cpp + gui/CCursorHandler.cpp gui/CGuiHandler.cpp gui/CIntObject.cpp - gui/CIntObjectClasses.cpp gui/Fonts.cpp gui/Geometries.cpp - gui/CCursorHandler.cpp gui/SDL_Extensions.cpp - CPreGame.cpp - Client.cpp - CPlayerInterface.cpp - CMT.cpp - GUIClasses.cpp - AdventureMapClasses.cpp - CAdvmapInterface.cpp - CAnimation.cpp + widgets/AdventureMapClasses.cpp + widgets/Buttons.cpp + widgets/CArtifactHolder.cpp + widgets/CComponent.cpp + widgets/CGarrisonInt.cpp + widgets/Images.cpp + widgets/MiscWidgets.cpp + widgets/ObjectLists.cpp + widgets/TextControls.cpp + + windows/CAdvmapInterface.cpp + windows/CCastleInterface.cpp + windows/CCreatureWindow.cpp + windows/CHeroWindow.cpp + windows/CKingdomInterface.cpp + windows/CQuestLog.cpp + windows/CSpellWindow.cpp + windows/CTradeWindow.cpp + windows/CWindowObject + windows/InfoWindows.cpp + windows/GUIClasses.cpp + CBitmapHandler.cpp - CCastleInterface.cpp - CCreatureWindow.cpp CDefHandler.cpp CGameInfo.cpp - CHeroWindow.cpp - CKingdomInterface.cpp + Client.cpp CMessage.cpp + CMT.cpp CMusicHandler.cpp - CSpellWindow.cpp + CPlayerInterface.cpp + CPreGame.cpp CVideoHandler.cpp - CQuestLog.cpp Graphics.cpp mapHandler.cpp NetPacksClient.cpp ) +set(client_HEADERS + gui/SDL_Pixels.h + gui/SDL_Compat.h +) + if(APPLE) # OS X specific includes include_directories(${SPARKLE_INCLUDE_DIR}) diff --git a/client/CMessage.cpp b/client/CMessage.cpp index b14af97bf..057569c5c 100644 --- a/client/CMessage.cpp +++ b/client/CMessage.cpp @@ -11,17 +11,19 @@ #include "StdInc.h" #include "CMessage.h" -#include "SDL_ttf.h" #include "CDefHandler.h" -#include "CAnimation.h" #include "CGameInfo.h" #include "gui/SDL_Extensions.h" #include "../lib/CGeneralTextHandler.h" #include "Graphics.h" -#include "GUIClasses.h" +#include "windows/GUIClasses.h" #include "../lib/CConfigHandler.h" #include "CBitmapHandler.h" -#include "gui/CIntObjectClasses.h" + +#include "widgets/CComponent.h" +#include "windows/InfoWindows.h" +#include "widgets/Buttons.h" +#include "widgets/TextControls.h" const int BETWEEN_COMPS_ROWS = 10; const int BEFORE_COMPONENTS = 30; diff --git a/client/CMusicHandler.h b/client/CMusicHandler.h index d5f87981c..9ba1387cc 100644 --- a/client/CMusicHandler.h +++ b/client/CMusicHandler.h @@ -2,7 +2,6 @@ #include "../lib/CConfigHandler.h" #include "../lib/CSoundBase.h" -#include "../lib/CCreatureHandler.h" /* * CMusicHandler.h, part of VCMI engine diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index d243f2462..a181d3dda 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1,27 +1,31 @@ #include "StdInc.h" -#include "CAdvmapInterface.h" +#include "windows/CAdvmapInterface.h" #include "battle/CBattleInterface.h" #include "battle/CBattleInterfaceClasses.h" #include "../CCallback.h" -#include "CCastleInterface.h" +#include "windows/CCastleInterface.h" #include "gui/CCursorHandler.h" -#include "CKingdomInterface.h" +#include "windows/CKingdomInterface.h" #include "CGameInfo.h" -#include "CHeroWindow.h" -#include "CCreatureWindow.h" -#include "CQuestLog.h" +#include "windows/CHeroWindow.h" +#include "windows/CCreatureWindow.h" +#include "windows/CQuestLog.h" #include "CMessage.h" #include "CPlayerInterface.h" #include "gui/SDL_Extensions.h" +#include "widgets/CComponent.h" +#include "windows/CTradeWindow.h" #include "../lib/CConfigHandler.h" #include "battle/CCreatureAnimation.h" #include "Graphics.h" +#include "windows/GUIClasses.h" #include "../lib/CArtHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/Connection.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" // For displaying correct UI when interacting with objects #include "../lib/BattleState.h" #include "../lib/JsonNode.h" #include "CMusicHandler.h" @@ -35,14 +39,9 @@ #include "../lib/CGameState.h" #include "../lib/GameConstants.h" #include "gui/CGuiHandler.h" +#include "windows/InfoWindows.h" #include "../lib/UnlockGuard.h" - -#ifdef min -#undef min -#endif -#ifdef max -#undef max -#endif +#include /* * CPlayerInterface.cpp, part of VCMI engine @@ -245,10 +244,6 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) return; } - adventureInt->centerOn(hero); //actualizing screen pos - adventureInt->minimap.redraw(); - adventureInt->heroList.redraw(); - bool directlyAttackingCreature = details.attackedFrom && adventureInt->terrain.currentPath //in case if movement has been canceled in the meantime and path was already erased @@ -305,12 +300,30 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) return; } + ui32 speed; + if (makingTurn) // our turn, our hero moves + speed = settings["adventure"]["heroSpeed"].Float(); + else + speed = settings["adventure"]["enemySpeed"].Float(); + + if (speed == 0) + { + //FIXME: is this a proper solution? + CGI->mh->hideObject(hero); + CGI->mh->printObject(hero); + return; // no animation + } + + + adventureInt->centerOn(hero); //actualizing screen pos + adventureInt->minimap.redraw(); + adventureInt->heroList.redraw(); + initMovement(details, hero, hp); //first initializing done GH.mainFPSmng->framerateDelay(); // after first move - ui32 speed = settings["adventure"]["heroSpeed"].Float(); //main moving for(int i=1; i<32; i+=2*speed) { @@ -319,14 +332,14 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) adventureInt->show(screen); { //evil returns here ... - //todo: get rid of it + //todo: get rid of it logGlobal->traceStream() << "before [un]locks in " << __FUNCTION__; auto unlockPim = vstd::makeUnlockGuard(*pim); //let frame to be rendered GH.mainFPSmng->framerateDelay(); //for animation purposes - logGlobal->traceStream() << "after [un]locks in " << __FUNCTION__; + logGlobal->traceStream() << "after [un]locks in " << __FUNCTION__; } //CSDL_Ext::update(screen); - + } //for(int i=1; i<32; i+=4) //main moving done @@ -494,10 +507,12 @@ void CPlayerInterface::commanderGotLevel (const CCommanderInstance * commander, waitWhileDialog(); CCS->soundh->playSound(soundBase::heroNewLevel); - CCreatureWindow * cw = new CCreatureWindow(skills, commander, - [=](ui32 selection){ cb->selectionMade(selection, queryID); }); - GH.pushInt(cw); + GH.pushInt(new CStackWindow(commander, skills, [=](ui32 selection) + { + cb->selectionMade(selection, queryID); + })); } + void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town) { EVENT_HANDLER_CALLED_BY_CLIENT; @@ -1285,8 +1300,8 @@ void CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path ) if (adventureInt && adventureInt->isHeroSleeping(h)) { - adventureInt->sleepWake.clickLeft(true, false); - adventureInt->sleepWake.clickLeft(false, true); + adventureInt->sleepWake->clickLeft(true, false); + adventureInt->sleepWake->clickLeft(false, true); //could've just called //adventureInt->fsleepWake(); //but no authentic button click/sound ;-) @@ -1321,7 +1336,7 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer waitForAllDialogs(); auto cgw = new CGarrisonWindow(up,down,removableUnits); - cgw->quit->callback += onEnd; + cgw->quit->addCallback(onEnd); GH.pushInt(cgw); } @@ -1515,6 +1530,16 @@ void CPlayerInterface::centerView (int3 pos, int focusTime) void CPlayerInterface::objectRemoved( const CGObjectInstance *obj ) { EVENT_HANDLER_CALLED_BY_CLIENT; + if (LOCPLINT->cb->getCurrentPlayer() == playerID) { + std::string handlerName = VLC->objtypeh->getObjectHandlerName(obj->ID); + if ((handlerName == "pickable") || (handlerName == "scholar") || (handlerName== "artifact") || (handlerName == "pandora")) { + waitWhileDialog(); + CCS->soundh->playSoundFromSet(CCS->soundh->pickupSounds); + } else if ((handlerName == "monster") || (handlerName == "hero")) { + waitWhileDialog(); + CCS->soundh->playSound(soundBase::KillFade); + } + } if(obj->ID == Obj::HERO && obj->tempOwner == playerID) { const CGHeroInstance *h = static_cast(obj); @@ -1601,22 +1626,20 @@ int CPlayerInterface::getLastIndex( std::string namePrefix) path gamesDir = VCMIDirs::get().userSavePath(); std::map dates; //save number => datestamp - directory_iterator enddir; + const directory_iterator enddir; if(!exists(gamesDir)) create_directory(gamesDir); - - for (directory_iterator dir(gamesDir); dir!=enddir; dir++) + else + for (directory_iterator dir(gamesDir); dir != enddir; ++dir) { if(is_regular(dir->status())) { - std::string name = dir->path().leaf().string(); + std::string name = dir->path().filename().string(); if(starts_with(name, namePrefix) && ends_with(name, ".vcgm1")) { char nr = name[namePrefix.size()]; if(std::isdigit(nr)) - { dates[last_write_time(dir->path())] = boost::lexical_cast(nr); - } } } } @@ -2239,7 +2262,7 @@ void CPlayerInterface::acceptTurn() if(CInfoWindow *iw = dynamic_cast(GH.topInt())) iw->close(); - adventureInt->endTurn.callback(); + adventureInt->fendTurn(); } // warn player if he has no town diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 07674e62b..cc3d36097 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -1,11 +1,12 @@ #pragma once -#include "../lib/CondSh.h" +//#include "../lib/CondSh.h" #include "../lib/FunctionList.h" #include "../lib/CGameInterface.h" +#include "../lib/NetPacksBase.h" #include "gui/CIntObject.h" -#include "../lib/CGameState.h" +//#include "../lib/CGameState.h" #ifdef __GNUC__ #define sprintf_s snprintf @@ -29,8 +30,8 @@ */ class CDefEssential; -class CAdventureMapButton; -class CHighlightableButtonsGroup; +class CButton; +class CToggleGroup; class CDefHandler; struct TryMoveHero; class CDefEssential; diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index 74d9a978f..44bc416b9 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -9,7 +9,6 @@ #include "gui/SDL_Extensions.h" #include "CGameInfo.h" #include "gui/CCursorHandler.h" -#include "CAnimation.h" #include "CDefHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" @@ -23,7 +22,7 @@ #include "../lib/Connection.h" #include "../lib/VCMIDirs.h" #include "../lib/mapping/CMap.h" -#include "GUIClasses.h" +#include "windows/GUIClasses.h" #include "CPlayerInterface.h" #include "../CCallback.h" #include "CMessage.h" @@ -38,7 +37,13 @@ #include "../lib/CConfigHandler.h" #include "../lib/GameConstants.h" #include "gui/CGuiHandler.h" -#include "gui/CIntObjectClasses.h" +#include "gui/CAnimation.h" +#include "widgets/CComponent.h" +#include "widgets/Buttons.h" +#include "widgets/MiscWidgets.h" +#include "widgets/ObjectLists.h" +#include "widgets/TextControls.h" +#include "windows/InfoWindows.h" #include "../lib/mapping/CMapService.h" #include "../lib/mapping/CMap.h" #include "../lib/CRandomGenerator.h" @@ -367,7 +372,7 @@ static std::function genCommand(CMenuScreen* menu, std::vector(); } -CAdventureMapButton* CMenuEntry::createButton(CMenuScreen* parent, const JsonNode& button) +CButton* CMenuEntry::createButton(CMenuScreen* parent, const JsonNode& button) { std::function command = genCommand(parent, parent->menuNameToEntry, button["command"].String()); @@ -383,7 +388,7 @@ CAdventureMapButton* CMenuEntry::createButton(CMenuScreen* parent, const JsonNod if (posy < 0) posy = pos.h + posy; - return new CAdventureMapButton(help, command, posx, posy, button["name"].String(), button["hotkey"].Float()); + return new CButton(Point(posx, posy), button["name"].String(), help, command, button["hotkey"].Float()); } CMenuEntry::CMenuEntry(CMenuScreen* parent, const JsonNode &config) @@ -639,38 +644,39 @@ CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMulti { case CMenuScreen::newGame: { - card->difficulty->onChange = std::bind(&CSelectionScreen::difficultyChange, this, _1); - card->difficulty->select(1, 0); - CAdventureMapButton * select = new CAdventureMapButton(CGI->generaltexth->zelp[45], 0, 411, 80, "GSPBUTT.DEF", SDLK_s); - select->callback = [&]() + SDL_Color orange = {232, 184, 32, 0}; + SDL_Color overlayColor = multiPlayer == CMenuScreen::MULTI_NETWORK_GUEST ? orange : Colors::WHITE; + + card->difficulty->addCallback(std::bind(&CSelectionScreen::difficultyChange, this, _1)); + card->difficulty->setSelected(1); + CButton * select = new CButton(Point(411, 80), "GSPBUTT.DEF", CGI->generaltexth->zelp[45], 0, SDLK_s); + select->addCallback([&]() { toggleTab(sel); changeSelection(sel->getSelectedMapInfo()); - }; - select->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL); + }); + select->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, overlayColor); - CAdventureMapButton *opts = new CAdventureMapButton(CGI->generaltexth->zelp[46], std::bind(&CSelectionScreen::toggleTab, this, opt), 411, 510, "GSPBUTT.DEF", SDLK_a); - opts->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL); + CButton *opts = new CButton(Point(411, 510), "GSPBUTT.DEF", CGI->generaltexth->zelp[46], std::bind(&CSelectionScreen::toggleTab, this, opt), SDLK_a); + opts->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, overlayColor); - CAdventureMapButton * randomBtn = new CAdventureMapButton(CGI->generaltexth->zelp[47], 0, 411, 105, "GSPBUTT.DEF", SDLK_r); - randomBtn->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL); - randomBtn->callback = [&]() + CButton * randomBtn = new CButton(Point(411, 105), "GSPBUTT.DEF", CGI->generaltexth->zelp[47], 0, SDLK_r); + randomBtn->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, overlayColor); + randomBtn->addCallback([&]() { toggleTab(randMapTab); changeSelection(randMapTab->getMapInfo()); - }; + }); - start = new CAdventureMapButton(CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), 411, 535, "SCNRBEG.DEF", SDLK_b); + start = new CButton(Point(411, 535), "SCNRBEG.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_b); if(network) { - CAdventureMapButton *hideChat = new CAdventureMapButton(CGI->generaltexth->zelp[48], std::bind(&InfoCard::toggleChat, card), 619, 83, "GSPBUT2.DEF", SDLK_h); + CButton *hideChat = new CButton(Point(619, 83), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&InfoCard::toggleChat, card), SDLK_h); hideChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL); if(multiPlayer == CMenuScreen::MULTI_NETWORK_GUEST) { - SDL_Color orange = {232, 184, 32, 0}; - select->text->color = opts->text->color = randomBtn->text->color = orange; select->block(true); opts->block(true); randomBtn->block(true); @@ -681,21 +687,21 @@ CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMulti break; case CMenuScreen::loadGame: sel->recActions = 255; - start = new CAdventureMapButton(CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), 411, 535, "SCNRLOD.DEF", SDLK_l); + start = new CButton(Point(411, 535), "SCNRLOD.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_l); break; case CMenuScreen::saveGame: sel->recActions = 255; - start = new CAdventureMapButton("", CGI->generaltexth->zelp[103].second, std::bind(&CSelectionScreen::startScenario, this), 411, 535, "SCNRSAV.DEF"); + start = new CButton(Point(411, 535), "SCNRSAV.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_s); break; case CMenuScreen::campaignList: sel->recActions = 255; - start = new CAdventureMapButton(std::pair(), std::bind(&CSelectionScreen::startCampaign, this), 411, 535, "SCNRLOD.DEF", SDLK_b); + start = new CButton(Point(411, 535), "SCNRLOD.DEF", CButton::tooltip(), std::bind(&CSelectionScreen::startCampaign, this), SDLK_b); break; } start->assignedKeys.insert(SDLK_RETURN); - back = new CAdventureMapButton("", CGI->generaltexth->zelp[105].second, std::bind(&CGuiHandler::popIntTotally, &GH, this), 581, 535, "SCNRBACK.DEF", SDLK_ESCAPE); + back = new CButton(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE); if(network) { @@ -995,7 +1001,7 @@ void CSelectionScreen::setSInfo(const StartInfo &si) if(current) opt->recreate(); //will force to recreate using current sInfo - card->difficulty->select(si.difficulty, 0); + card->difficulty->setSelected(si.difficulty); GH.totalRedraw(); } @@ -1258,7 +1264,7 @@ SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::functiongeneraltexth->zelp[54+i].second, std::bind(&SelectionTab::filter, this, sizes[i], true), 158 + 47*i, 46, names[i]); + new CButton(Point(158 + 47*i, 46), names[i], CGI->generaltexth->zelp[54+i], std::bind(&SelectionTab::filter, this, sizes[i], true)); } //sort buttons buttons @@ -1272,20 +1278,19 @@ SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::functiongeneraltexth->zelp[107+i].second, std::bind(&SelectionTab::sortBy, this, criteria), xpos[i], 86, names[i]); + new CButton(Point(xpos[i], 86), names[i], CGI->generaltexth->zelp[107+i], std::bind(&SelectionTab::sortBy, this, criteria)); } } } else { //sort by buttons - new CAdventureMapButton("", "", std::bind(&SelectionTab::sortBy, this, _numOfMaps), 23, 86, "CamCusM.DEF"); //by num of maps - new CAdventureMapButton("", "", std::bind(&SelectionTab::sortBy, this, _name), 55, 86, "CamCusL.DEF"); //by name + new CButton(Point(23, 86), "CamCusM.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps)); //by num of maps + new CButton(Point(55, 86), "CamCusL.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name)); //by name } - slider = new CSlider(372, 86, tabType != CMenuScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positions, curItems.size(), 0, false, 1); + slider = new CSlider(Point(372, 86), tabType != CMenuScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positions, curItems.size(), 0, false, CSlider::BLUE); slider->addUsedEvents(WHEEL); - slider->slider->keepFrame = true; format = CDefHandler::giveDef("SCSELC.DEF"); sortingBy = _format; @@ -1351,16 +1356,16 @@ void SelectionTab::select( int position ) if(!curItems.size()) return; // New selection. py is the index in curItems. - int py = position + slider->value; + int py = position + slider->getValue(); vstd::amax(py, 0); vstd::amin(py, curItems.size()-1); selectionPos = py; if(position < 0) - slider->moveTo(slider->value + position); + slider->moveBy(position); else if(position >= positions) - slider->moveTo(slider->value + position - positions + 1); + slider->moveBy(position - positions + 1); if(txt) { @@ -1374,7 +1379,7 @@ void SelectionTab::select( int position ) void SelectionTab::selectAbs( int position ) { - select(position - slider->value); + select(position - slider->getValue()); } int SelectionTab::getPosition( int x, int y ) @@ -1397,7 +1402,7 @@ void SelectionTab::sliderMove( int slidPos ) void SelectionTab::printMaps(SDL_Surface *to) { - int elemIdx = slider->value; + int elemIdx = slider->getValue(); // Display all elements if there's enough space //if(slider->amount < slider->capacity) @@ -1555,15 +1560,15 @@ void SelectionTab::keyPressed( const SDL_KeyboardEvent & key ) moveBy = +positions-1; break; case SDLK_HOME: - select(-slider->value); + select(-slider->getValue()); return; case SDLK_END: - select(curItems.size() - slider->value); + select(curItems.size() - slider->getValue()); return; default: return; } - select(selectionPos - slider->value + moveBy); + select(selectionPos - slider->getValue() + moveBy); } void SelectionTab::onDoubleClick() @@ -1571,7 +1576,7 @@ void SelectionTab::onDoubleClick() if(getLine() != -1) //double clicked scenarios list { //act as if start button was pressed - (static_cast(parent))->start->callback(); + (static_cast(parent))->start->clickLeft(false, true); } } @@ -1616,27 +1621,25 @@ CRandomMapTab::CRandomMapTab() bg = new CPicture("RANMAPBK", 0, 6); // Map Size - mapSizeBtnGroup = new CHighlightableButtonsGroup(0); + mapSizeBtnGroup = new CToggleGroup(0); mapSizeBtnGroup->pos.y += 81; mapSizeBtnGroup->pos.x += 158; const std::vector mapSizeBtns = boost::assign::list_of("RANSIZS")("RANSIZM")("RANSIZL")("RANSIZX"); addButtonsToGroup(mapSizeBtnGroup, mapSizeBtns, 0, 3, 47, 198); - mapSizeBtnGroup->select(1, false); - mapSizeBtnGroup->onChange = [&](int btnId) + mapSizeBtnGroup->setSelected(1); + mapSizeBtnGroup->addCallback([&](int btnId) { const std::vector mapSizeVal = boost::assign::list_of(CMapHeader::MAP_SIZE_SMALL)(CMapHeader::MAP_SIZE_MIDDLE) (CMapHeader::MAP_SIZE_LARGE)(CMapHeader::MAP_SIZE_XLARGE); mapGenOptions.setWidth(mapSizeVal[btnId]); mapGenOptions.setHeight(mapSizeVal[btnId]); updateMapInfo(); - }; + }); // Two levels - twoLevelsBtn = new CHighlightableButton(0, 0, std::map(), - CGI->generaltexth->zelp[202].second, false, "RANUNDR", nullptr, 346, 81); + twoLevelsBtn = new CToggleButton(Point(346, 81), "RANUNDR", CGI->generaltexth->zelp[202]); //twoLevelsBtn->select(true); for now, deactivated - twoLevelsBtn->callback = [&]() { mapGenOptions.setHasTwoLevels(true); updateMapInfo(); }; - twoLevelsBtn->callback2 = [&]() { mapGenOptions.setHasTwoLevels(false); updateMapInfo(); }; + twoLevelsBtn->addCallback([&](bool on) { mapGenOptions.setHasTwoLevels(on); updateMapInfo(); }); // Create number defs list std::vector numberDefs; @@ -1648,128 +1651,130 @@ CRandomMapTab::CRandomMapTab() const int NUMBERS_WIDTH = 32; const int BTNS_GROUP_LEFT_MARGIN = 67; // Amount of players - playersCntGroup = new CHighlightableButtonsGroup(0); + playersCntGroup = new CToggleGroup(0); playersCntGroup->pos.y += 153; playersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(playersCntGroup, numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212); - playersCntGroup->onChange = [&](int btnId) + playersCntGroup->addCallback([&](int btnId) { mapGenOptions.setPlayerCount(btnId); deactivateButtonsFrom(teamsCntGroup, btnId); deactivateButtonsFrom(compOnlyPlayersCntGroup, 8 - btnId + 1); validatePlayersCnt(btnId); updateMapInfo(); - }; + }); // Amount of teams - teamsCntGroup = new CHighlightableButtonsGroup(0); + teamsCntGroup = new CToggleGroup(0); teamsCntGroup->pos.y += 219; teamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(teamsCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222); - teamsCntGroup->onChange = [&](int btnId) + teamsCntGroup->addCallback([&](int btnId) { mapGenOptions.setTeamCount(btnId); updateMapInfo(); - }; + }); // Computer only players - compOnlyPlayersCntGroup = new CHighlightableButtonsGroup(0); + compOnlyPlayersCntGroup = new CToggleGroup(0); compOnlyPlayersCntGroup->pos.y += 285; compOnlyPlayersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(compOnlyPlayersCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232); - compOnlyPlayersCntGroup->select(0, true); - compOnlyPlayersCntGroup->onChange = [&](int btnId) + compOnlyPlayersCntGroup->setSelected(0); + compOnlyPlayersCntGroup->addCallback([&](int btnId) { mapGenOptions.setCompOnlyPlayerCount(btnId); deactivateButtonsFrom(compOnlyTeamsCntGroup, btnId); validateCompOnlyPlayersCnt(btnId); updateMapInfo(); - }; + }); // Computer only teams - compOnlyTeamsCntGroup = new CHighlightableButtonsGroup(0); + compOnlyTeamsCntGroup = new CToggleGroup(0); compOnlyTeamsCntGroup->pos.y += 351; compOnlyTeamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(compOnlyTeamsCntGroup, numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241); deactivateButtonsFrom(compOnlyTeamsCntGroup, 0); - compOnlyTeamsCntGroup->onChange = [&](int btnId) + compOnlyTeamsCntGroup->addCallback([&](int btnId) { mapGenOptions.setCompOnlyTeamCount(btnId); updateMapInfo(); - }; + }); const int WIDE_BTN_WIDTH = 85; // Water content - waterContentGroup = new CHighlightableButtonsGroup(0); + waterContentGroup = new CToggleGroup(0); waterContentGroup->pos.y += 419; waterContentGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; const std::vector waterContentBtns = boost::assign::list_of("RANNONE")("RANNORM")("RANISLD"); addButtonsWithRandToGroup(waterContentGroup, waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246); - waterContentGroup->onChange = [&](int btnId) + waterContentGroup->addCallback([&](int btnId) { mapGenOptions.setWaterContent(static_cast(btnId)); - }; + }); // Monster strength - monsterStrengthGroup = new CHighlightableButtonsGroup(0); + monsterStrengthGroup = new CToggleGroup(0); monsterStrengthGroup->pos.y += 485; monsterStrengthGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; const std::vector monsterStrengthBtns = boost::assign::list_of("RANWEAK")("RANNORM")("RANSTRG"); addButtonsWithRandToGroup(monsterStrengthGroup, monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251); - monsterStrengthGroup->onChange = [&](int btnId) + monsterStrengthGroup->addCallback([&](int btnId) { if (btnId < 0) mapGenOptions.setMonsterStrength(EMonsterStrength::RANDOM); else mapGenOptions.setMonsterStrength(static_cast(btnId + EMonsterStrength::GLOBAL_WEAK)); //value 2 to 4 - }; + }); // Show random maps btn - showRandMaps = new CAdventureMapButton("", CGI->generaltexth->zelp[252].second, 0, 54, 535, "RANSHOW"); + showRandMaps = new CButton(Point(54, 535), "RANSHOW", CGI->generaltexth->zelp[252]); // Initialize map info object updateMapInfo(); } -void CRandomMapTab::addButtonsWithRandToGroup(CHighlightableButtonsGroup * group, const std::vector & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex) const +void CRandomMapTab::addButtonsWithRandToGroup(CToggleGroup * group, const std::vector & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex) const { addButtonsToGroup(group, defs, nStart, nEnd, btnWidth, helpStartIndex); // Buttons are relative to button group, TODO better solution? SObjectConstruction obj__i(group); const std::string RANDOM_DEF = "RANRAND"; - group->addButton(new CHighlightableButton("", CGI->generaltexth->zelp[helpRandIndex].second, 0, 256, 0, RANDOM_DEF, CMapGenOptions::RANDOM_SIZE)); - group->select(CMapGenOptions::RANDOM_SIZE, true); + group->addToggle(CMapGenOptions::RANDOM_SIZE, new CToggleButton(Point(256, 0), RANDOM_DEF, CGI->generaltexth->zelp[helpRandIndex])); + group->setSelected(CMapGenOptions::RANDOM_SIZE); } -void CRandomMapTab::addButtonsToGroup(CHighlightableButtonsGroup * group, const std::vector & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex) const +void CRandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex) const { // Buttons are relative to button group, TODO better solution? SObjectConstruction obj__i(group); int cnt = nEnd - nStart + 1; for(int i = 0; i < cnt; ++i) { - group->addButton(new CHighlightableButton("", CGI->generaltexth->zelp[helpStartIndex + i].second, 0, i * btnWidth, 0, defs[i + nStart], i + nStart)); + auto button = new CToggleButton(Point(i * btnWidth, 0), defs[i + nStart], CGI->generaltexth->zelp[helpStartIndex + i]); + // For blocked state we should use pressed image actually + button->setImageOrder(0, 1, 1, 3); + + group->addToggle(i + nStart, button); } } -void CRandomMapTab::deactivateButtonsFrom(CHighlightableButtonsGroup * group, int startId) +void CRandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId) { - for(CHighlightableButton * btn : group->buttons) + logGlobal->infoStream() << "Blocking buttons from " << startId; + for(auto toggle : group->buttons) { - if(startId == CMapGenOptions::RANDOM_SIZE || btn->ID < startId) + if (auto button = dynamic_cast(toggle.second)) { - if(btn->isBlocked()) + if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId) { - btn->setOffset(0); - btn->setState(CButtonBase::NORMAL); + button->block(false); + } + else + { + button->block(true); } - } - else - { - // Blocked state looks like frame 'selected'=1 - btn->setOffset(-1); - btn->setState(CButtonBase::BLOCKED); } } } @@ -1784,12 +1789,12 @@ void CRandomMapTab::validatePlayersCnt(int playersCnt) if(mapGenOptions.getTeamCount() >= playersCnt) { mapGenOptions.setTeamCount(playersCnt - 1); - teamsCntGroup->select(mapGenOptions.getTeamCount(), true); + teamsCntGroup->setSelected(mapGenOptions.getTeamCount()); } if(mapGenOptions.getCompOnlyPlayerCount() > 8 - playersCnt) { mapGenOptions.setCompOnlyPlayerCount(8 - playersCnt); - compOnlyPlayersCntGroup->select(mapGenOptions.getCompOnlyPlayerCount(), true); + compOnlyPlayersCntGroup->setSelected(mapGenOptions.getCompOnlyPlayerCount()); } validateCompOnlyPlayersCnt(mapGenOptions.getCompOnlyPlayerCount()); @@ -1805,7 +1810,7 @@ void CRandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt) if(mapGenOptions.getCompOnlyTeamCount() >= compOnlyPlayersCnt) { mapGenOptions.setCompOnlyTeamCount(compOnlyPlayersCnt - 1); - compOnlyTeamsCntGroup->select(mapGenOptions.getCompOnlyTeamCount(), true); + compOnlyTeamsCntGroup->setSelected(mapGenOptions.getCompOnlyTeamCount()); } } @@ -1960,20 +1965,20 @@ InfoCard::InfoCard( bool Network ) pos.h = bg->pos.h; sizes = CDefHandler::giveDef("SCNRMPSZ.DEF"); sFlags = CDefHandler::giveDef("ITGFLAGS.DEF"); - difficulty = new CHighlightableButtonsGroup(0); + difficulty = new CToggleGroup(0); { static const char *difButns[] = {"GSPBUT3.DEF", "GSPBUT4.DEF", "GSPBUT5.DEF", "GSPBUT6.DEF", "GSPBUT7.DEF"}; for(int i = 0; i < 5; i++) { - difficulty->addButton(new CHighlightableButton("", CGI->generaltexth->zelp[24+i].second, 0, 110 + i*32, 450, difButns[i], i)); + auto button = new CToggleButton(Point(110 + i*32, 450), difButns[i], CGI->generaltexth->zelp[24+i]); + + difficulty->addToggle(i, button); + if(SEL->screenType != CMenuScreen::newGame) + button->block(true); } } - if(SEL->screenType != CMenuScreen::newGame) - difficulty->block(true); - - if(network) { playerListBg = new CPicture("CHATPLUG.bmp", 16, 276); @@ -2161,8 +2166,8 @@ void InfoCard::changeSelection( const CMapInfo *to ) mapDescription->setText(to->mapHeader->description); if(SEL->screenType != CMenuScreen::newGame && SEL->screenType != CMenuScreen::campaignList) { - difficulty->block(true); - difficulty->select(SEL->sInfo.difficulty, 0); + //difficulty->block(true); + difficulty->setSelected(SEL->sInfo.difficulty); } } redraw(); @@ -2242,7 +2247,7 @@ OptionsTab::OptionsTab(): pos = bg->pos; if(SEL->screenType == CMenuScreen::newGame) - turnDuration = new CSlider(55, 551, 194, std::bind(&OptionsTab::setTurnLength, this, _1), 1, 11, 11, true, 1); + turnDuration = new CSlider(Point(55, 551), 194, std::bind(&OptionsTab::setTurnLength, this, _1), 1, 11, 11, true, CSlider::BLUE); } OptionsTab::~OptionsTab() @@ -2261,7 +2266,7 @@ void OptionsTab::showAll(SDL_Surface * to) printAtMiddleWBLoc(CGI->generaltexth->allTexts[520], 349, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Bonus printAtMiddleLoc(CGI->generaltexth->allTexts[521], 222, 538, FONT_SMALL, Colors::YELLOW, to); // Player Turn Duration if (turnDuration) - printAtMiddleLoc(CGI->generaltexth->turnDurations[turnDuration->value], 319,559, FONT_SMALL, Colors::WHITE, to);//Turn duration value + printAtMiddleLoc(CGI->generaltexth->turnDurations[turnDuration->getValue()], 319,559, FONT_SMALL, Colors::WHITE, to);//Turn duration value } void OptionsTab::nextCastle( PlayerColor player, int dir ) @@ -2546,12 +2551,12 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry( OptionsTab *owner, PlayerSet bg = new CPicture(BitmapHandler::loadBitmap(bgs[s.color.getNum()]), 0, 0, true); if(SEL->screenType == CMenuScreen::newGame) { - btns[0] = new CAdventureMapButton(CGI->generaltexth->zelp[132], std::bind(&OptionsTab::nextCastle, owner, s.color, -1), 107, 5, "ADOPLFA.DEF"); - btns[1] = new CAdventureMapButton(CGI->generaltexth->zelp[133], std::bind(&OptionsTab::nextCastle, owner, s.color, +1), 168, 5, "ADOPRTA.DEF"); - btns[2] = new CAdventureMapButton(CGI->generaltexth->zelp[148], std::bind(&OptionsTab::nextHero, owner, s.color, -1), 183, 5, "ADOPLFA.DEF"); - btns[3] = new CAdventureMapButton(CGI->generaltexth->zelp[149], std::bind(&OptionsTab::nextHero, owner, s.color, +1), 244, 5, "ADOPRTA.DEF"); - btns[4] = new CAdventureMapButton(CGI->generaltexth->zelp[164], std::bind(&OptionsTab::nextBonus, owner, s.color, -1), 259, 5, "ADOPLFA.DEF"); - btns[5] = new CAdventureMapButton(CGI->generaltexth->zelp[165], std::bind(&OptionsTab::nextBonus, owner, s.color, +1), 320, 5, "ADOPRTA.DEF"); + btns[0] = new CButton(Point(107, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[132], std::bind(&OptionsTab::nextCastle, owner, s.color, -1)); + btns[1] = new CButton(Point(168, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[133], std::bind(&OptionsTab::nextCastle, owner, s.color, +1)); + btns[2] = new CButton(Point(183, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[148], std::bind(&OptionsTab::nextHero, owner, s.color, -1)); + btns[3] = new CButton(Point(244, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[149], std::bind(&OptionsTab::nextHero, owner, s.color, +1)); + btns[4] = new CButton(Point(259, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[164], std::bind(&OptionsTab::nextBonus, owner, s.color, -1)); + btns[5] = new CButton(Point(320, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[165], std::bind(&OptionsTab::nextBonus, owner, s.color, +1)); } else for(auto & elem : btns) @@ -2573,7 +2578,7 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry( OptionsTab *owner, PlayerSet && SEL->current->mapHeader->players[s.color.getNum()].canHumanPlay && SEL->multiPlayer != CMenuScreen::MULTI_NETWORK_GUEST) { - flag = new CAdventureMapButton(CGI->generaltexth->zelp[180], std::bind(&OptionsTab::flagPressed, owner, s.color), -43, 2, flags[s.color.getNum()]); + flag = new CButton(Point(-43, 2), flags[s.color.getNum()], CGI->generaltexth->zelp[180], std::bind(&OptionsTab::flagPressed, owner, s.color)); flag->hoverable = true; } else @@ -2979,8 +2984,8 @@ CScenarioInfo::CScenarioInfo(const CMapHeader *mapHeader, const StartInfo *start opt->recreate(); card->changeSelection(current); - card->difficulty->select(startInfo->difficulty, 0); - back = new CAdventureMapButton("", CGI->generaltexth->zelp[105].second, std::bind(&CGuiHandler::popIntTotally, &GH, this), 584, 535, "SCNRBACK.DEF", SDLK_ESCAPE); + card->difficulty->setSelected(startInfo->difficulty); + back = new CButton(Point(584, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE); } CScenarioInfo::~CScenarioInfo() @@ -3059,10 +3064,10 @@ CMultiMode::CMultiMode() txt = new CTextInput(Rect(19, 436, 334, 16), *bg); txt->setText(settings["general"]["playerName"].String()); //Player - btns[0] = new CAdventureMapButton(CGI->generaltexth->zelp[266], std::bind(&CMultiMode::openHotseat, this), 373, 78, "MUBHOT.DEF"); - btns[1] = new CAdventureMapButton("Host TCP/IP game", "", std::bind(&CMultiMode::hostTCP, this), 373, 78 + 57*1, "MUBHOST.DEF"); - btns[2] = new CAdventureMapButton("Join TCP/IP game", "", std::bind(&CMultiMode::joinTCP, this), 373, 78 + 57*2, "MUBJOIN.DEF"); - btns[6] = new CAdventureMapButton(CGI->generaltexth->zelp[288], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), 373, 424, "MUBCANC.DEF", SDLK_ESCAPE); + btns[0] = new CButton(Point(373, 78), "MUBHOT.DEF", CGI->generaltexth->zelp[266], std::bind(&CMultiMode::openHotseat, this)); + btns[1] = new CButton(Point(373, 78 + 57*1), "MUBHOST.DEF", CButton::tooltip("Host TCP/IP game", ""), std::bind(&CMultiMode::hostTCP, this)); + btns[2] = new CButton(Point(373, 78 + 57*2), "MUBJOIN.DEF", CButton::tooltip("Join TCP/IP game", ""), std::bind(&CMultiMode::joinTCP, this)); + btns[6] = new CButton(Point(373, 424), "MUBCANC.DEF", CGI->generaltexth->zelp[288], [&] { GH.popIntTotally(this);}, SDLK_ESCAPE); } void CMultiMode::openHotseat() @@ -3102,8 +3107,8 @@ CHotSeatPlayers::CHotSeatPlayers(const std::string &firstPlayer) txt[i]->cb += std::bind(&CHotSeatPlayers::onChange, this, _1); } - ok = new CAdventureMapButton(CGI->generaltexth->zelp[560], std::bind(&CHotSeatPlayers::enterSelectionScreen, this), 95, 338, "MUBCHCK.DEF", SDLK_RETURN); - cancel = new CAdventureMapButton(CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), 205, 338, "MUBCANC.DEF", SDLK_ESCAPE); + ok = new CButton(Point(95, 338), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CHotSeatPlayers::enterSelectionScreen, this), SDLK_RETURN); + cancel = new CButton(Point(205, 338), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE); bar = new CGStatusBar(new CPicture(Rect(7, 381, 348, 18), 0));//226, 472 txt[0]->setText(firstPlayer, true); @@ -3166,9 +3171,9 @@ void CBonusSelection::init() blitAt(panel, 456, 6, background); - startB = new CAdventureMapButton("", "", std::bind(&CBonusSelection::startMap, this), 475, 536, "CBBEGIB.DEF", SDLK_RETURN); - restartB = new CAdventureMapButton("", "", std::bind(&CBonusSelection::restartMap, this), 475, 536, "CBRESTB.DEF", SDLK_RETURN); - backB = new CAdventureMapButton("", "", std::bind(&CBonusSelection::goBack, this), 624, 536, "CBCANCB.DEF", SDLK_ESCAPE); + startB = new CButton(Point(475, 536), "CBBEGIB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), SDLK_RETURN); + restartB = new CButton(Point(475, 536), "CBRESTB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), SDLK_RETURN); + backB = new CButton(Point(624, 536), "CBCANCB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), SDLK_ESCAPE); //campaign name if (ourCampaign->camp->header.name.length()) @@ -3190,7 +3195,7 @@ void CBonusSelection::init() //bonus choosing graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, CGI->generaltexth->allTexts[71], Colors::WHITE, Point(511, 432)); - bonuses = new CHighlightableButtonsGroup(bind(&CBonusSelection::selectBonus, this, _1)); + bonuses = new CToggleGroup(bind(&CBonusSelection::selectBonus, this, _1)); //set left part of window bool isCurrentMapConquerable = ourCampaign->currentMap && ourCampaign->camp->conquerable(*ourCampaign->currentMap); @@ -3241,8 +3246,8 @@ void CBonusSelection::init() //difficulty selection buttons if (ourCampaign->camp->header.difficultyChoosenByPlayer) { - diffLb = new CAdventureMapButton("", "", std::bind(&CBonusSelection::decreaseDifficulty, this), 694, 508, "SCNRBLF.DEF"); - diffRb = new CAdventureMapButton("", "", std::bind(&CBonusSelection::increaseDifficulty, this), 738, 508, "SCNRBRT.DEF"); + diffLb = new CButton(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this)); + diffRb = new CButton(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this)); } //load miniflags @@ -3442,13 +3447,8 @@ void CBonusSelection::updateBonusSelection() updateStartButtonState(-1); - for (auto & elem : bonuses->buttons) - { - if (elem->active) - elem->deactivate(); - delete elem; - } - bonuses->buttons.clear(); + delete bonuses; + bonuses = new CToggleGroup(bind(&CBonusSelection::selectBonus, this, _1)); static const char *bonusPics[] = {"SPELLBON.DEF", "TWCRPORT.DEF", "", "ARTIFBON.DEF", "SPELLBON.DEF", "PSKILBON.DEF", "SSKILBON.DEF", "BORES.DEF", "PORTRAITSLARGE", "PORTRAITSLARGE"}; @@ -3602,7 +3602,7 @@ void CBonusSelection::updateBonusSelection() break; } - CHighlightableButton *bonusButton = new CHighlightableButton(desc, desc, 0, 475 + i*68, 455, "", i); + CToggleButton *bonusButton = new CToggleButton(Point(475 + i*68, 455), "", CButton::tooltip(desc, desc)); if (picNumber != -1) picName += ":" + boost::lexical_cast(picNumber); @@ -3612,13 +3612,13 @@ void CBonusSelection::updateBonusSelection() bonusButton->setImage(anim); const SDL_Color brightYellow = { 242, 226, 110, 0 }; bonusButton->borderColor = brightYellow; - bonuses->addButton(bonusButton); + bonuses->addToggle(i, bonusButton); } // set bonus if already chosen if(vstd::contains(ourCampaign->chosenCampaignBonuses, selectedMap)) { - bonuses->select(ourCampaign->chosenCampaignBonuses[selectedMap], false); + bonuses->setSelected(ourCampaign->chosenCampaignBonuses[selectedMap]); } } @@ -3727,11 +3727,11 @@ void CBonusSelection::updateStartButtonState(int selected /*= -1*/) { if(selected == -1) { - startB->setState(ourCampaign->camp->scenarios[selectedMap].travelOptions.bonusesToChoose.size() ? CButtonBase::BLOCKED : CButtonBase::NORMAL); + startB->block(ourCampaign->camp->scenarios[selectedMap].travelOptions.bonusesToChoose.size()); } - else if(startB->getState() == CButtonBase::BLOCKED) + else if(startB->isBlocked()) { - startB->setState(CButtonBase::NORMAL); + startB->block(false); } } @@ -4091,14 +4091,14 @@ void CCampaignScreen::CCampaignButton::show(SDL_Surface * to) } } -CAdventureMapButton* CCampaignScreen::createExitButton(const JsonNode& button) +CButton* CCampaignScreen::createExitButton(const JsonNode& button) { std::pair help; if (!button["help"].isNull() && button["help"].Float() > 0) help = CGI->generaltexth->zelp[button["help"].Float()]; - std::function close = std::bind(&CGuiHandler::popIntTotally, &GH, this); - return new CAdventureMapButton(help, close, button["x"].Float(), button["y"].Float(), button["name"].String(), button["hotkey"].Float()); + std::function close = std::bind(&CGuiHandler::popIntTotally, &GH, this); + return new CButton(Point(button["x"].Float(), button["y"].Float()), button["name"].String(), help, close, button["hotkey"].Float()); } @@ -4239,8 +4239,8 @@ CSimpleJoinScreen::CSimpleJoinScreen() port->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1); port->filters.add(std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535)); - ok = new CAdventureMapButton(CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::enterSelectionScreen, this), 26, 142, "MUBCHCK.DEF", SDLK_RETURN); - cancel = new CAdventureMapButton(CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), 142, 142, "MUBCANC.DEF", SDLK_ESCAPE); + ok = new CButton(Point( 26, 142), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::enterSelectionScreen, this), SDLK_RETURN); + cancel = new CButton(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE); bar = new CGStatusBar(new CPicture(Rect(7, 186, 218, 18), 0)); port->setText(boost::lexical_cast(settings["server"]["port"].Float()), true); diff --git a/client/CPreGame.h b/client/CPreGame.h index 21a67a3ce..a38d29d4a 100644 --- a/client/CPreGame.h +++ b/client/CPreGame.h @@ -1,12 +1,11 @@ #pragma once -#include "../lib/filesystem/Filesystem.h" -#include +//#include "../lib/filesystem/Filesystem.h" #include "../lib/StartInfo.h" -#include "GUIClasses.h" #include "../lib/FunctionList.h" #include "../lib/mapping/CMapInfo.h" #include "../lib/rmg/CMapGenerator.h" +#include "windows/CWindowObject.h" /* * CPreGame.h, part of VCMI engine @@ -18,6 +17,7 @@ * */ +class CMapInfo; class CMusicHandler; class CMapHeader; class CCampaignHeader; @@ -32,6 +32,12 @@ class CMapGenOptions; class CRandomMapTab; struct CPackForSelectionScreen; struct PlayerInfo; +class CMultiLineLabel; +class CToggleButton; +class CToggleGroup; +class CTabbedInt; +class CButton; +class CSlider; namespace boost{ class thread; class recursive_mutex;} @@ -80,9 +86,9 @@ public: class CMenuEntry : public CIntObject { std::vector images; - std::vector buttons; + std::vector buttons; - CAdventureMapButton* createButton(CMenuScreen* parent, const JsonNode& button); + CButton* createButton(CMenuScreen* parent, const JsonNode& button); public: CMenuEntry(CMenuScreen* parent, const JsonNode &config); }; @@ -126,7 +132,7 @@ public: CChatBox *chat; CPicture *playerListBg; - CHighlightableButtonsGroup *difficulty; + CToggleGroup *difficulty; CDefHandler *sizes, *sFlags; void changeSelection(const CMapInfo *to); @@ -239,8 +245,8 @@ public: PlayerInfo π PlayerSettings &s; CPicture *bg; - CAdventureMapButton *btns[6]; //left and right for town, hero, bonus - CAdventureMapButton *flag; + CButton *btns[6]; //left and right for town, hero, bonus + CButton *flag; SelectedBox *town; SelectedBox *hero; SelectedBox *bonus; @@ -296,17 +302,17 @@ public: const CMapGenOptions & getMapGenOptions() const; private: - void addButtonsToGroup(CHighlightableButtonsGroup * group, const std::vector & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex) const; - void addButtonsWithRandToGroup(CHighlightableButtonsGroup * group, const std::vector & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex) const; - void deactivateButtonsFrom(CHighlightableButtonsGroup * group, int startId); + void addButtonsToGroup(CToggleGroup * group, const std::vector & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex) const; + void addButtonsWithRandToGroup(CToggleGroup * group, const std::vector & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex) const; + void deactivateButtonsFrom(CToggleGroup * group, int startId); void validatePlayersCnt(int playersCnt); void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt); CPicture * bg; - CHighlightableButton * twoLevelsBtn; - CHighlightableButtonsGroup * mapSizeBtnGroup, * playersCntGroup, * teamsCntGroup, * compOnlyPlayersCntGroup, + CToggleButton * twoLevelsBtn; + CToggleGroup * mapSizeBtnGroup, * playersCntGroup, * teamsCntGroup, * compOnlyPlayersCntGroup, * compOnlyTeamsCntGroup, * waterContentGroup, * monsterStrengthGroup; - CAdventureMapButton * showRandMaps; + CButton * showRandMaps; CMapGenOptions mapGenOptions; unique_ptr mapInfo; CFunctionList mapInfoChanged; @@ -347,7 +353,7 @@ public: InfoCard *card; OptionsTab *opt; CRandomMapTab * randMapTab; - CAdventureMapButton *start, *back; + CButton *start, *back; SelectionTab *sel; CIntObject *curTab; @@ -395,7 +401,7 @@ public: class CScenarioInfo : public CIntObject, public ISelectionScreenInfo { public: - CAdventureMapButton *back; + CButton *back; InfoCard *card; OptionsTab *opt; @@ -409,7 +415,7 @@ class CMultiMode : public CIntObject public: CPicture *bg; CTextInput *txt; - CAdventureMapButton *btns[7]; //0 - hotseat, 6 - cancel + CButton *btns[7]; //0 - hotseat, 6 - cancel CGStatusBar *bar; CMultiMode(); @@ -424,7 +430,7 @@ class CHotSeatPlayers : public CIntObject CPicture *bg; CTextBox *title; CTextInput* txt[8]; - CAdventureMapButton *ok, *cancel; + CButton *ok, *cancel; CGStatusBar *bar; void onChange(std::string newText); @@ -512,14 +518,14 @@ private: // GUI components SDL_Surface * background; - CAdventureMapButton * startB, * restartB, * backB; + CButton * startB, * restartB, * backB; CTextBox * campaignDescription, * mapDescription; std::vector campDescriptions; std::vector regions; CRegion * highlightedRegion; - CHighlightableButtonsGroup * bonuses; + CToggleGroup * bonuses; SDL_Surface * diffPics[5]; //pictures of difficulties, user-selectable (or not if campaign locks this) - CAdventureMapButton * diffLb, * diffRb; //buttons for changing difficulty + CButton * diffLb, * diffRb; //buttons for changing difficulty CDefHandler * sizes; //icons of map sizes CDefHandler * sFlags; @@ -560,11 +566,11 @@ private: void show(SDL_Surface * to); }; - CAdventureMapButton *back; + CButton *back; std::vector campButtons; std::vector images; - CAdventureMapButton* createExitButton(const JsonNode& button); + CButton* createExitButton(const JsonNode& button); public: enum CampaignSet {ROE, AB, SOD, WOG}; @@ -630,7 +636,7 @@ class CSimpleJoinScreen : public CIntObject { CPicture * bg; CTextBox * title; - CAdventureMapButton * ok, * cancel; + CButton * ok, * cancel; CGStatusBar * bar; CTextInput * address; CTextInput * port; diff --git a/client/CVideoHandler.cpp b/client/CVideoHandler.cpp index e3c3850f2..8dfe2b768 100644 --- a/client/CVideoHandler.cpp +++ b/client/CVideoHandler.cpp @@ -21,9 +21,6 @@ static bool keyDown() } return false; } -#endif - - #ifdef _MSC_VER #pragma comment(lib, "avcodec.lib") @@ -32,9 +29,6 @@ static bool keyDown() #pragma comment(lib, "swscale.lib") #endif // _MSC_VER - -#ifndef DISABLE_VIDEO - // Define a set of functions to read data static int lodRead(void* opaque, uint8_t* buf, int size) { diff --git a/client/CVideoHandler.h b/client/CVideoHandler.h index 22e1445ab..64d643b0a 100644 --- a/client/CVideoHandler.h +++ b/client/CVideoHandler.h @@ -30,18 +30,17 @@ public: class CEmptyVideoPlayer : public IMainVideoPlayer { -public: +public: int curFrame() const override {return -1;}; int frameCount() const override {return -1;}; - void redraw(int x, int y, SDL_Surface *dst, bool update = true) override {}; - void show(int x, int y, SDL_Surface *dst, bool update = true) override{}; + void redraw( int x, int y, SDL_Surface *dst, bool update = true ) override {}; + void show( int x, int y, SDL_Surface *dst, bool update = true ) override {}; bool nextFrame() override {return false;}; void close() override {}; bool wait() override {return false;}; bool open(std::string name, bool scale = false) override {return false;}; }; - #ifndef DISABLE_VIDEO #include "../lib/filesystem/CInputStream.h" diff --git a/client/Client.cpp b/client/Client.cpp index 528895cfd..b02abab20 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -1,4 +1,7 @@ #include "StdInc.h" +#include "Client.h" + +#include #include "CMusicHandler.h" #include "../lib/mapping/CCampaignHandler.h" @@ -17,7 +20,7 @@ #include "../lib/CBuildingHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/Connection.h" -#ifndef __ANDROID__ +#ifndef VCMI_ANDROID #include "../lib/Interprocess.h" #endif #include "../lib/NetPacks.h" @@ -37,7 +40,7 @@ #include "CMT.h" extern std::string NAME; -#ifndef __ANDROID__ +#ifndef VCMI_ANDROID namespace intpr = boost::interprocess; #endif @@ -777,8 +780,9 @@ std::string CClient::aiNameForPlayer(const PlayerSettings &ps, bool battleAI) { if(ps.name.size()) { - std::string filename = VCMIDirs::get().libraryPath() + "/AI/" + VCMIDirs::get().libraryName(ps.name); - if(boost::filesystem::exists(filename)) + const boost::filesystem::path aiPath = + VCMIDirs::get().libraryPath() / "AI" / VCMIDirs::get().libraryName(ps.name); + if (boost::filesystem::exists(aiPath)) return ps.name; } @@ -811,7 +815,7 @@ void CServerHandler::waitForServer() startServer(); th.update(); -#ifndef __ANDROID__ +#ifndef VCMI_ANDROID intpr::scoped_lock slock(shared->sr->mutex); while(!shared->sr->ready) { @@ -824,7 +828,7 @@ void CServerHandler::waitForServer() CConnection * CServerHandler::connectToServer() { -#ifndef __ANDROID__ +#ifndef VCMI_ANDROID if(!shared->sr->ready) waitForServer(); #else @@ -847,7 +851,7 @@ CServerHandler::CServerHandler(bool runServer /*= false*/) port = boost::lexical_cast(settings["server"]["port"].Float()); verbose = true; -#ifndef __ANDROID__ +#ifndef VCMI_ANDROID boost::interprocess::shared_memory_object::remove("vcmi_memory"); //if the application has previously crashed, the memory may not have been removed. to avoid problems - try to destroy it try { @@ -865,8 +869,8 @@ CServerHandler::~CServerHandler() void CServerHandler::callServer() { setThreadName("CServerHandler::callServer"); - std::string logName = VCMIDirs::get().userCachePath() + "/server_log.txt"; - std::string comm = VCMIDirs::get().serverPath() + " --port=" + port + " > " + logName; + const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string(); + const std::string comm = VCMIDirs::get().serverPath().string() + " --port=" + port + " > \"" + logName + '\"'; int result = std::system(comm.c_str()); if (result == 0) logNetwork->infoStream() << "Server closed correctly"; diff --git a/client/Client.h b/client/Client.h index ed16a87c6..72adab44d 100644 --- a/client/Client.h +++ b/client/Client.h @@ -2,8 +2,9 @@ #include "../lib/IGameCallback.h" -#include "../lib/CondSh.h" +#include "../lib/BattleAction.h" #include "../lib/CStopWatch.h" +#include "../lib/int3.h" /* * Client.h, part of VCMI engine @@ -15,6 +16,9 @@ * */ +class CPack; +class CCampaignState; +class CBattleCallback; class IGameEventsReceiver; class IBattleEventsReceiver; class CBattleGameInterface; diff --git a/client/GUIClasses.cpp b/client/GUIClasses.cpp deleted file mode 100644 index 7cb5d9068..000000000 --- a/client/GUIClasses.cpp +++ /dev/null @@ -1,6263 +0,0 @@ -#include "StdInc.h" -#include "GUIClasses.h" -#include "gui/SDL_Extensions.h" - -#include "CAdvmapInterface.h" -#include "CBitmapHandler.h" -#include "CDefHandler.h" -#include "battle/CBattleInterface.h" -#include "battle/CBattleInterfaceClasses.h" -#include "../CCallback.h" -#include "CCastleInterface.h" -#include "CCreatureWindow.h" -#include "gui/CCursorHandler.h" -#include "CGameInfo.h" -#include "CHeroWindow.h" -#include "CMessage.h" -#include "../lib/CConfigHandler.h" -#include "battle/CCreatureAnimation.h" -#include "CPlayerInterface.h" -#include "Graphics.h" -#include "CAnimation.h" -#include "../lib/CArtHandler.h" -#include "../lib/CBuildingHandler.h" -#include "../lib/CGeneralTextHandler.h" -#include "../lib/CHeroHandler.h" -#include "../lib/CModHandler.h" -#include "../lib/CSpellHandler.h" -#include "../lib/CTownHandler.h" -#include "../lib/CondSh.h" -#include "../lib/mapping/CMap.h" -#include "mapHandler.h" -#include "../lib/CStopWatch.h" -#include "../lib/NetPacksBase.h" -#include "CSpellWindow.h" -#include "CHeroWindow.h" -#include "CVideoHandler.h" -#include "../lib/StartInfo.h" -#include "CPreGame.h" -#include "../lib/HeroBonus.h" -#include "../lib/CCreatureHandler.h" -#include "CMusicHandler.h" -#include "../lib/BattleState.h" -#include "../lib/CGameState.h" -#include "../lib/GameConstants.h" -#include "gui/CGuiHandler.h" - -/* - * GUIClasses.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 - * - */ - -using namespace boost::assign; -using namespace CSDL_Ext; - -extern std::queue events; -extern boost::mutex eventsM; - -std::list CFocusable::focusables; -CFocusable * CFocusable::inputWithFocus; - -#undef min -#undef max - -void CArmyTooltip::init(const InfoAboutArmy &army) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - new CLabel(66, 2, FONT_SMALL, TOPLEFT, Colors::WHITE, army.name); - - std::vector slotsPos; - slotsPos.push_back(Point(36,73)); - slotsPos.push_back(Point(72,73)); - slotsPos.push_back(Point(108,73)); - slotsPos.push_back(Point(18,122)); - slotsPos.push_back(Point(54,122)); - slotsPos.push_back(Point(90,122)); - slotsPos.push_back(Point(126,122)); - - for(auto & slot : army.army) - { - if(slot.first.getNum() >= GameConstants::ARMY_SIZE) - { - logGlobal->warnStream() << "Warning: " << army.name << " has stack in slot " << slot.first; - continue; - } - - new CAnimImage("CPRSMALL", slot.second.type->iconIndex, 0, slotsPos[slot.first.getNum()].x, slotsPos[slot.first.getNum()].y); - - std::string subtitle; - if(army.army.isDetailed) - subtitle = boost::lexical_cast(slot.second.count); - else - { - //if =0 - we have no information about stack size at all - if (slot.second.count) - subtitle = CGI->generaltexth->arraytxt[171 + 3*(slot.second.count)]; - } - - new CLabel(slotsPos[slot.first.getNum()].x + 17, slotsPos[slot.first.getNum()].y + 41, FONT_TINY, CENTER, Colors::WHITE, subtitle); - } - -} - -CArmyTooltip::CArmyTooltip(Point pos, const InfoAboutArmy &army): - CIntObject(0, pos) -{ - init(army); -} - -CArmyTooltip::CArmyTooltip(Point pos, const CArmedInstance * army): - CIntObject(0, pos) -{ - init(InfoAboutArmy(army, true)); -} - -void CHeroTooltip::init(const InfoAboutHero &hero) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - new CAnimImage("PortraitsLarge", hero.portrait, 0, 3, 2); - - if(hero.details) - { - for (size_t i = 0; i < hero.details->primskills.size(); i++) - new CLabel(75 + 28 * i, 58, FONT_SMALL, CENTER, Colors::WHITE, - boost::lexical_cast(hero.details->primskills[i])); - - new CLabel(158, 98, FONT_TINY, CENTER, Colors::WHITE, - boost::lexical_cast(hero.details->mana)); - - new CAnimImage("IMRL22", hero.details->morale + 3, 0, 5, 74); - new CAnimImage("ILCK22", hero.details->luck + 3, 0, 5, 91); - } -} - -CHeroTooltip::CHeroTooltip(Point pos, const InfoAboutHero &hero): - CArmyTooltip(pos, hero) -{ - init(hero); -} - -CHeroTooltip::CHeroTooltip(Point pos, const CGHeroInstance * hero): - CArmyTooltip(pos, InfoAboutHero(hero, true)) -{ - init(InfoAboutHero(hero, true)); -} - -void CTownTooltip::init(const InfoAboutTown &town) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - //order of icons in def: fort, citadel, castle, no fort - size_t fortIndex = town.fortLevel ? town.fortLevel - 1 : 3; - - new CAnimImage("ITMCLS", fortIndex, 0, 105, 31); - - assert(town.tType); - - size_t iconIndex = town.tType->clientInfo.icons[town.fortLevel > 0][town.built >= CGI->modh->settings.MAX_BUILDING_PER_TURN]; - - new CAnimImage("itpt", iconIndex, 0, 3, 2); - - if(town.details) - { - new CAnimImage("ITMTLS", town.details->hallLevel, 0, 67, 31); - - if (town.details->goldIncome) - new CLabel(157, 58, FONT_TINY, CENTER, Colors::WHITE, - boost::lexical_cast(town.details->goldIncome)); - - if(town.details->garrisonedHero) //garrisoned hero icon - new CPicture("TOWNQKGH", 149, 76); - - if(town.details->customRes)//silo is built - { - if (town.tType->primaryRes == Res::WOOD_AND_ORE )// wood & ore - { - new CAnimImage("SMALRES", Res::WOOD, 0, 7, 75); - new CAnimImage("SMALRES", Res::ORE , 0, 7, 88); - } - else - new CAnimImage("SMALRES", town.tType->primaryRes, 0, 7, 81); - } - } -} - -CTownTooltip::CTownTooltip(Point pos, const InfoAboutTown &town): - CArmyTooltip(pos, town) -{ - init(town); -} - -CTownTooltip::CTownTooltip(Point pos, const CGTownInstance * town): - CArmyTooltip(pos, InfoAboutTown(town, true)) -{ - init(InfoAboutTown(town, true)); -} - -void CGarrisonSlot::setHighlight(bool on) -{ - if (on) - selectionImage->enable(); //show - else - selectionImage->disable(); //hide -} - -void CGarrisonSlot::hover (bool on) -{ - ////Hoverable::hover(on); - if(on) - { - std::string temp; - if(creature) - { - if(owner->getSelection()) - { - if(owner->getSelection() == this) - { - temp = CGI->generaltexth->tcommands[4]; //View %s - boost::algorithm::replace_first(temp,"%s",creature->nameSing); - } - else if (owner->getSelection()->creature == creature) - { - temp = CGI->generaltexth->tcommands[2]; //Combine %s armies - boost::algorithm::replace_first(temp,"%s",creature->nameSing); - } - else if (owner->getSelection()->creature) - { - temp = CGI->generaltexth->tcommands[7]; //Exchange %s with %s - boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->nameSing); - boost::algorithm::replace_first(temp,"%s",creature->nameSing); - } - else - { - logGlobal->warnStream() << "Warning - shouldn't be - highlighted void slot "<getSelection(); - logGlobal->warnStream() << "Highlighted set to nullptr"; - owner->selectSlot(nullptr); - } - } - else - { - if(upg) - { - temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting) - } - else if(owner->armedObjs[0] && owner->armedObjs[0]->ID == Obj::TOWN) - { - temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison) - } - else - { - temp = CGI->generaltexth->allTexts[481]; //Select %s - } - boost::algorithm::replace_first(temp,"%s",creature->nameSing); - }; - } - else - { - if(owner->getSelection()) - { - const CArmedInstance *highl = owner->getSelection()->getObj(); - if( highl->needsLastStack() //we are moving stack from hero's - && highl->stacksCount() == 1 //it's only stack - && owner->getSelection()->upg != upg //we're moving it to the other garrison - ) - { - temp = CGI->generaltexth->tcommands[5]; //Cannot move last army to garrison - } - else - { - temp = CGI->generaltexth->tcommands[6]; //Move %s - boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->nameSing); - } - } - else - { - temp = CGI->generaltexth->tcommands[11]; //Empty - } - } - GH.statusbar->setText(temp); - } - else - { - GH.statusbar->clear(); - } -} - -const CArmedInstance * CGarrisonSlot::getObj() const -{ - return (!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]); -} - -bool CGarrisonSlot::our() const -{ - return upg?(owner->owned[1]):(owner->owned[0]); -} - -void CGarrisonSlot::clickRight(tribool down, bool previousState) -{ - if(down && creature) - { - GH.pushInt(createCreWindow(myStack, CCreatureWindow::ARMY)); - } -} -void CGarrisonSlot::clickLeft(tribool down, bool previousState) -{ - if(down) - { - bool refr = false; - if(owner->getSelection()) - { - if(owner->getSelection() == this) //view info - { - UpgradeInfo pom; - LOCPLINT->cb->getUpgradeInfo(getObj(), ID, pom); - - bool canUpgrade = getObj()->tempOwner == LOCPLINT->playerID && pom.oldID>=0; //upgrade is possible - bool canDismiss = getObj()->tempOwner == LOCPLINT->playerID && (getObj()->stacksCount()>1 || !getObj()->needsLastStack()); - std::function upgr = nullptr; - std::function dism = nullptr; - if(canUpgrade) upgr = [=] { LOCPLINT->cb->upgradeCreature(getObj(), ID, pom.newID[0]); }; - if(canDismiss) dism = [=] { LOCPLINT->cb->dismissCreature(getObj(), ID); }; - - owner->selectSlot(nullptr); - owner->setSplittingMode(false); - - for(auto & elem : owner->splitButtons) - elem->block(true); - - redraw(); - refr = true; - CIntObject *creWindow = createCreWindow(myStack, CCreatureWindow::HERO, upgr, dism, &pom); - GH.pushInt(creWindow); - } - else - { - // Only allow certain moves if troops aren't removable or not ours. - if ( ( owner->getSelection()->our()//our creature is selected - || owner->getSelection()->creature == creature )//or we are rebalancing army - && ( owner->removableUnits - || (upg == 0 && ( owner->getSelection()->upg == 1 && !creature ) ) - || (upg == 1 && owner->getSelection()->upg == 1 ) ) ) - { - //we want to split - if((owner->getSplittingMode() || LOCPLINT->shiftPressed()) - && (!creature - || (creature == owner->getSelection()->creature))) - { - owner->p2 = ID; //store the second stack pos - owner->pb = upg;//store the second stack owner (up or down army) - owner->setSplittingMode(false); - - int minLeft=0, minRight=0; - - if(upg != owner->getSelection()->upg) //not splitting within same army - { - if(owner->getSelection()->getObj()->stacksCount() == 1 //we're splitting away the last stack - && owner->getSelection()->getObj()->needsLastStack() ) - { - minLeft = 1; - } - if(getObj()->stacksCount() == 1 //destination army can't be emptied, unless we're rebalancing two stacks of same creature - && owner->getSelection()->creature == creature - && getObj()->needsLastStack() ) - { - minRight = 1; - } - } - - int countLeft = owner->getSelection()->myStack ? owner->getSelection()->myStack->count : 0; - int countRight = myStack ? myStack->count : 0; - - GH.pushInt(new CSplitWindow(owner->getSelection()->creature, std::bind(&CGarrisonInt::splitStacks, owner, _1, _2), - minLeft, minRight, countLeft, countRight)); - refr = true; - } - else if(creature != owner->getSelection()->creature) //swap - { - LOCPLINT->cb->swapCreatures( - (!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]), - (!owner->getSelection()->upg)?(owner->armedObjs[0]):(owner->armedObjs[1]), - ID,owner->getSelection()->ID); - } - else //merge - { - LOCPLINT->cb->mergeStacks( - (!owner->getSelection()->upg)?(owner->armedObjs[0]):(owner->armedObjs[1]), - (!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]), - owner->getSelection()->ID,ID); - } - } - else // Highlight - { - if(creature) - owner->selectSlot(this); - redraw(); - refr = true; - } - } - } - else //highlight or drop artifact - { - bool artSelected = false; - if (CWindowWithArtifacts* chw = dynamic_cast(GH.topInt())) //dirty solution - { - const CArtifactsOfHero::SCommonPart *commonInfo = chw->artSets.front()->commonInfo; - if (const CArtifactInstance *art = commonInfo->src.art) - { - const CGHeroInstance *srcHero = commonInfo->src.AOH->getHero(); - artSelected = true; - ArtifactLocation src(srcHero, commonInfo->src.slotID); - ArtifactLocation dst(myStack, ArtifactPosition::CREATURE_SLOT); - if (art->canBePutAt(dst, true)) - { //equip clicked stack - if(dst.getArt()) - { - //creature can wear only one active artifact - //if we are placing a new one, the old one will be returned to the hero's backpack - LOCPLINT->cb->swapArtifacts(dst, ArtifactLocation(srcHero, dst.getArt()->firstBackpackSlot(srcHero))); - } - LOCPLINT->cb->swapArtifacts(src, dst); - } - } - } - if (!artSelected && creature) - { - owner->selectSlot(this); - if(creature) - { - for(auto & elem : owner->splitButtons) - elem->block(false); - } - } - redraw(); - refr = true; - } - if(refr) {hover(false); hover(true); } //to refresh statusbar - } -} - -void CGarrisonSlot::update() -{ - if (getObj() != nullptr) - { - addUsedEvents(LCLICK | RCLICK | HOVER); - myStack = getObj()->getStackPtr(ID); - creature = myStack ? myStack->type : nullptr; - } - else - { - removeUsedEvents(LCLICK | RCLICK | HOVER); - myStack = nullptr; - creature = nullptr; - } - - if (creature) - { - creatureImage->enable(); - creatureImage->setFrame(creature->iconIndex); - - stackCount->enable(); - stackCount->setText(boost::lexical_cast(myStack->count)); - } - else - { - creatureImage->disable(); - stackCount->disable(); - } -} - -CGarrisonSlot::CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, int Upg, const CStackInstance * Creature): - ID(IID), - owner(Owner), - myStack(Creature), - creature(Creature ? Creature->type : nullptr), - upg(Upg) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - if (getObj()) - addUsedEvents(LCLICK | RCLICK | HOVER); - pos.x += x; - pos.y += y; - - std::string imgName = owner->smallIcons ? "cprsmall" : "TWCRPORT"; - - creatureImage = new CAnimImage(imgName, creature ? creature->iconIndex : 0); - if (!creature) - creatureImage->disable(); - - selectionImage = new CAnimImage(imgName, 1); - selectionImage->disable(); - - if(Owner->smallIcons) - { - pos.w = 32; - pos.h = 32; - } - else - { - pos.w = 58; - pos.h = 64; - } - - stackCount = new CLabel(pos.w, pos.h, owner->smallIcons ? FONT_TINY : FONT_MEDIUM, BOTTOMRIGHT, Colors::WHITE); - if (!creature) - stackCount->disable(); - else - stackCount->setText(boost::lexical_cast(myStack->count)); -} - -void CGarrisonInt::addSplitBtn(CAdventureMapButton * button) -{ - addChild(button); - button->recActions = defActions; - splitButtons.push_back(button); - button->block(getSelection() == nullptr); -} - -void CGarrisonInt::createSet(std::vector &ret, const CCreatureSet * set, int posX, int posY, int distance, int Upg ) -{ - ret.resize(7); - - if (set) - { - for(auto & elem : set->Slots()) - { - ret[elem.first.getNum()] = new CGarrisonSlot(this, posX + (elem.first.getNum()*distance), posY, elem.first, Upg, elem.second); - } - } - - for(int i=0; ipos.x -= 126; - ret[i]->pos.y += 37; - }; -} - -void CGarrisonInt::createSlots() -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - int width = smallIcons? 32 : 58; - - createSet(slotsUp, armedObjs[0], 0, 0, width+interx, 0); - createSet(slotsDown, armedObjs[1], garOffset.x, garOffset.y, width+interx, 1); -} - -void CGarrisonInt::recreateSlots() -{ - selectSlot(nullptr); - setSplittingMode(false); - - for(auto & elem : splitButtons) - elem->block(true); - - - for(CGarrisonSlot * slot : slotsUp) - slot->update(); - - for(CGarrisonSlot * slot : slotsDown) - slot->update(); -} - -void CGarrisonInt::splitClick() -{ - if(!getSelection()) - return; - setSplittingMode(!getSplittingMode()); - redraw(); -} -void CGarrisonInt::splitStacks(int, int amountRight) -{ - LOCPLINT->cb->splitStack(armedObjs[getSelection()->upg], armedObjs[pb], getSelection()->ID, p2, amountRight); -} - -CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point &garsOffset, - SDL_Surface *pomsur, const Point& SurOffset, - const CArmedInstance *s1, const CArmedInstance *s2, - bool _removableUnits, bool smallImgs, bool _twoRows ) : - highlighted(nullptr), - inSplittingMode(false), - interx(inx), - garOffset(garsOffset), - smallIcons(smallImgs), - removableUnits(_removableUnits), - twoRows(_twoRows) -{ - setArmy(s1, false); - setArmy(s2, true); - pos.x += x; - pos.y += y; - createSlots(); -} - -const CGarrisonSlot * CGarrisonInt::getSelection() -{ - return highlighted; -} - -void CGarrisonInt::selectSlot(CGarrisonSlot *slot) -{ - if (slot != highlighted) - { - if (highlighted) - highlighted->setHighlight(false); - - highlighted = slot; - for (auto button : splitButtons) - button->block(highlighted == nullptr); - - if (highlighted) - highlighted->setHighlight(true); - } -} - -void CGarrisonInt::setSplittingMode(bool on) -{ - assert(on == false || highlighted != nullptr); //can't be in splitting mode without selection - - if (inSplittingMode || on) - { - for(CGarrisonSlot * slot : slotsUp) - slot->setHighlight( ( on && (slot->creature == nullptr || slot->creature == getSelection()->creature))); - - for(CGarrisonSlot * slot : slotsDown) - slot->setHighlight( ( on && (slot->creature == nullptr || slot->creature == getSelection()->creature))); - inSplittingMode = on; - } -} - -bool CGarrisonInt::getSplittingMode() -{ - return inSplittingMode; -} - -void CGarrisonInt::setArmy(const CArmedInstance *army, bool bottomGarrison) -{ - owned[bottomGarrison] = army ? (army->tempOwner == LOCPLINT->playerID || army->tempOwner == PlayerColor::UNFLAGGABLE) : false; - armedObjs[bottomGarrison] = army; -} - -CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo &comps, const TButtonsInfo &Buttons, bool delComps) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - type |= BLOCK_ADV_HOTKEYS; - ID = QueryID(-1); - for(auto & Button : Buttons) - { - CAdventureMapButton *button = new CAdventureMapButton("","",std::bind(&CInfoWindow::close,this),0,0,Button.first); - button->borderColor = Colors::METALLIC_GOLD; - button->borderEnabled = true; - button->callback.add(Button.second); //each button will close the window apart from call-defined actions - buttons.push_back(button); - } - - text = new CTextBox(Text, Rect(0, 0, 250, 100), 0, FONT_MEDIUM, CENTER, Colors::WHITE); - if(!text->slider) - { - text->resize(text->label->textSize); - } - - if(buttons.size()) - { - buttons.front()->assignedKeys.insert(SDLK_RETURN); //first button - reacts on enter - buttons.back()->assignedKeys.insert(SDLK_ESCAPE); //last button - reacts on escape - } - - for(auto & comp : comps) - { - comp->recActions = 0xff; - addChild(comp); - comp->recActions &= ~(SHOWALL | UPDATE); - components.push_back(comp); - } - setDelComps(delComps); - CMessage::drawIWindow(this,Text,player); -} - -CInfoWindow::CInfoWindow() -{ - ID = QueryID(-1); - setDelComps(false); - text = nullptr; -} - -void CInfoWindow::close() -{ - GH.popIntTotally(this); - if(LOCPLINT) - LOCPLINT->showingDialog->setn(false); -} - -void CInfoWindow::show(SDL_Surface * to) -{ - CIntObject::show(to); -} - -CInfoWindow::~CInfoWindow() -{ - if(!delComps) - { - for (auto & elem : components) - removeChild(elem); - } -} - -void CInfoWindow::showAll(SDL_Surface * to) -{ - CSimpleWindow::show(to); - CIntObject::showAll(to); -} - -void CInfoWindow::showInfoDialog(const std::string &text, const std::vector *components, bool DelComps, PlayerColor player) -{ - CInfoWindow * window = CInfoWindow::create(text, player, components, DelComps); - GH.pushInt(window); -} - -void CInfoWindow::showYesNoDialog(const std::string & text, const std::vector *components, const CFunctionList &onYes, const CFunctionList &onNo, bool DelComps, PlayerColor player) -{ - assert(!LOCPLINT || LOCPLINT->showingDialog->get()); - std::vector > > pom; - pom.push_back(std::pair >("IOKAY.DEF",0)); - pom.push_back(std::pair >("ICANCEL.DEF",0)); - CInfoWindow * temp = new CInfoWindow(text, player, components ? *components : std::vector(), pom, DelComps); - for(auto & elem : onYes.funcs) - temp->buttons[0]->callback += elem; - for(auto & elem : onNo.funcs) - temp->buttons[1]->callback += elem; - - GH.pushInt(temp); -} - -void CInfoWindow::showOkDialog(const std::string & text, const std::vector *components, const std::function & onOk, bool delComps, PlayerColor player) -{ - std::vector > > pom; - pom.push_back(std::pair >("IOKAY.DEF",0)); - CInfoWindow * temp = new CInfoWindow(text, player, *components, pom, delComps); - temp->buttons[0]->callback += onOk; - - GH.pushInt(temp); -} - -CInfoWindow * CInfoWindow::create(const std::string &text, PlayerColor playerID /*= 1*/, const std::vector *components /*= nullptr*/, bool DelComps) -{ - std::vector > > pom; - pom.push_back(std::pair >("IOKAY.DEF",0)); - CInfoWindow * ret = new CInfoWindow(text, playerID, components ? *components : std::vector(), pom, DelComps); - return ret; -} - -std::string CInfoWindow::genText(std::string title, std::string description) -{ - return std::string("{") + title + "}" + "\n\n" + description; -} - -void CInfoWindow::setDelComps(bool DelComps) -{ - delComps = DelComps; - for(CComponent *comp : components) - { - if(delComps) - comp->recActions |= DISPOSE; - else - comp->recActions &= ~DISPOSE; - } -} - -CInfoPopup::CInfoPopup(SDL_Surface * Bitmap, int x, int y, bool Free) - :free(Free),bitmap(Bitmap) -{ - init(x, y); -} - - -CInfoPopup::CInfoPopup(SDL_Surface * Bitmap, const Point &p, EAlignment alignment, bool Free/*=false*/) - : free(Free),bitmap(Bitmap) -{ - switch(alignment) - { - case BOTTOMRIGHT: - init(p.x - Bitmap->w, p.y - Bitmap->h); - break; - case CENTER: - init(p.x - Bitmap->w/2, p.y - Bitmap->h/2); - break; - case TOPLEFT: - init(p.x, p.y); - break; - default: - assert(0); //not implemented - } -} - -CInfoPopup::CInfoPopup(SDL_Surface *Bitmap, bool Free) -{ - CCS->curh->hide(); - - free=Free; - bitmap=Bitmap; - - if(bitmap) - { - pos.x = screen->w/2 - bitmap->w/2; - pos.y = screen->h/2 - bitmap->h/2; - pos.h = bitmap->h; - pos.w = bitmap->w; - } -} - -void CInfoPopup::close() -{ - if(free) - SDL_FreeSurface(bitmap); - GH.popIntTotally(this); -} -void CInfoPopup::show(SDL_Surface * to) -{ - blitAt(bitmap,pos.x,pos.y,to); -} -CInfoPopup::~CInfoPopup() -{ - CCS->curh->show(); -} - -void CInfoPopup::init(int x, int y) -{ - CCS->curh->hide(); - - pos.x = x; - pos.y = y; - pos.h = bitmap->h; - pos.w = bitmap->w; - - // Put the window back on screen if necessary - vstd::amax(pos.x, 0); - vstd::amax(pos.y, 0); - vstd::amin(pos.x, screen->w - bitmap->w); - vstd::amin(pos.y, screen->h - bitmap->h); -} - -CComponent::CComponent(Etype Type, int Subtype, int Val, ESize imageSize): - image(nullptr), - perDay(false) -{ - addUsedEvents(RCLICK); - init(Type, Subtype, Val, imageSize); -} - -CComponent::CComponent(const Component &c): - image(nullptr), - perDay(false) -{ - addUsedEvents(RCLICK); - - if(c.id == Component::RESOURCE && c.when==-1) - perDay = true; - - init((Etype)c.id,c.subtype,c.val, large); -} - -void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - compType = Type; - subtype = Subtype; - val = Val; - size = imageSize; - - assert(compType < typeInvalid); - assert(size < sizeInvalid); - - setSurface(getFileName()[size], getIndex()); - - pos.w = image->pos.w; - pos.h = image->pos.h; - - EFonts font = FONT_SMALL; - if (imageSize < small) - font = FONT_TINY; //other sizes? - - pos.h += 4; //distance between text and image - - std::vector textLines = CMessage::breakText(getSubtitle(), std::max(80, pos.w), font); - for(auto & line : textLines) - { - int height = graphics->fonts[font]->getLineHeight(); - auto label = new CLabel(pos.w/2, pos.h + height/2, font, CENTER, Colors::WHITE, line); - - pos.h += height; - if (label->pos.w > pos.w) - { - pos.x -= (label->pos.w - pos.w)/2; - pos.w = label->pos.w; - } - } -} - -const std::vector CComponent::getFileName() -{ - static const std::string primSkillsArr [] = {"PSKIL32", "PSKIL32", "PSKIL42", "PSKILL"}; - static const std::string secSkillsArr [] = {"SECSK32", "SECSK32", "SECSKILL", "SECSK82"}; - static const std::string resourceArr [] = {"SMALRES", "RESOURCE", "RESOUR82", "RESOUR82"}; - static const std::string creatureArr [] = {"CPRSMALL", "CPRSMALL", "TWCRPORT", "TWCRPORT"}; - static const std::string artifactArr[] = {"Artifact", "Artifact", "Artifact", "Artifact"}; - static const std::string spellsArr [] = {"SpellInt", "SpellInt", "SPELLSCR", "SPELLSCR"}; - static const std::string moraleArr [] = {"IMRL22", "IMRL30", "IMRL42", "imrl82"}; - static const std::string luckArr [] = {"ILCK22", "ILCK30", "ILCK42", "ilck82"}; - static const std::string heroArr [] = {"PortraitsSmall", "PortraitsSmall", "PortraitsLarge", "PortraitsLarge"}; - static const std::string flagArr [] = {"CREST58", "CREST58", "CREST58", "CREST58"}; - - auto gen = [](const std::string * arr) - { - return std::vector(arr, arr + 4); - }; - - switch(compType) - { - case primskill: return gen(primSkillsArr); - case secskill: return gen(secSkillsArr); - case resource: return gen(resourceArr); - case creature: return gen(creatureArr); - case artifact: return gen(artifactArr); - case experience: return gen(primSkillsArr); - case spell: return gen(spellsArr); - case morale: return gen(moraleArr); - case luck: return gen(luckArr); - case building: return std::vector(4, CGI->townh->factions[subtype]->town->clientInfo.buildingsIcons); - case hero: return gen(heroArr); - case flag: return gen(flagArr); - } - assert(0); - return std::vector(); -} - -size_t CComponent::getIndex() -{ - switch(compType) - { - case primskill: return subtype; - case secskill: return subtype*3 + 3 + val - 1; - case resource: return subtype; - case creature: return CGI->creh->creatures[subtype]->iconIndex; - case artifact: return CGI->arth->artifacts[subtype]->iconIndex; - case experience: return 4; - case spell: return subtype; - case morale: return val+3; - case luck: return val+3; - case building: return val; - case hero: return subtype; - case flag: return subtype; - } - assert(0); - return 0; -} - -std::string CComponent::getDescription() -{ - switch (compType) - { - case primskill: return (subtype < 4)? CGI->generaltexth->arraytxt[2+subtype] //Primary skill - : CGI->generaltexth->allTexts[149]; //mana - case secskill: return CGI->generaltexth->skillInfoTexts[subtype][val-1]; - case resource: return CGI->generaltexth->allTexts[242]; - case creature: return ""; - case artifact: return CGI->arth->artifacts[subtype]->Description(); - case experience: return CGI->generaltexth->allTexts[241]; - case spell: return CGI->spellh->objects[subtype]->getLevelInfo(val).description; - case morale: return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)]; - case luck: return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)]; - case building: return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Description(); - case hero: return ""; - case flag: return ""; - } - assert(0); - return ""; -} - -std::string CComponent::getSubtitle() -{ - if (!perDay) - return getSubtitleInternal(); - - std::string ret = CGI->generaltexth->allTexts[3]; - boost::replace_first(ret, "%d", getSubtitleInternal()); - return ret; -} - -std::string CComponent::getSubtitleInternal() -{ - //FIXME: some of these are horrible (e.g creature) - switch(compType) - { - case primskill: return boost::str(boost::format("%+d %s") % val % (subtype < 4 ? CGI->generaltexth->primarySkillNames[subtype] : CGI->generaltexth->allTexts[387])); - case secskill: return CGI->generaltexth->levels[val-1] + "\n" + CGI->generaltexth->skillName[subtype]; - case resource: return boost::lexical_cast(val); - case creature: return (val? boost::lexical_cast(val) + " " : "") + CGI->creh->creatures[subtype]->*(val != 1 ? &CCreature::namePl : &CCreature::nameSing); - case artifact: return CGI->arth->artifacts[subtype]->Name(); - case experience: - { - if (subtype == 1) //+1 level - tree of knowledge - { - std::string level = CGI->generaltexth->allTexts[442]; - boost::replace_first(level, "1", boost::lexical_cast(val)); - return level; - } - else - return boost::lexical_cast(val); //amount of experience OR level required for seer hut; - } - case spell: return CGI->spellh->objects[subtype]->name; - case morale: return ""; - case luck: return ""; - case building: return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Name(); - case hero: return ""; - case flag: return CGI->generaltexth->capColors[subtype]; - } - assert(0); - return ""; -} - -void CComponent::setSurface(std::string defName, int imgPos) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - vstd::clear_pointer(image); - image = new CAnimImage(defName, imgPos); -} - -void CComponent::clickRight(tribool down, bool previousState) -{ - if(!getDescription().empty()) - adventureInt->handleRightClick(getDescription(), down); -} - -void CSelectableComponent::clickLeft(tribool down, bool previousState) -{ - if (down) - { - if(onSelect) - onSelect(); - } -} - -void CSelectableComponent::init() -{ - selected = false; -} - -CSelectableComponent::CSelectableComponent(const Component &c, std::function OnSelect): - CComponent(c),onSelect(OnSelect) -{ - type |= REDRAW_PARENT; - addUsedEvents(LCLICK | KEYBOARD); - init(); -} - -CSelectableComponent::CSelectableComponent(Etype Type, int Sub, int Val, ESize imageSize, std::function OnSelect): - CComponent(Type,Sub,Val, imageSize),onSelect(OnSelect) -{ - type |= REDRAW_PARENT; - addUsedEvents(LCLICK | KEYBOARD); - init(); -} - -void CSelectableComponent::select(bool on) -{ - if(on != selected) - { - selected = on; - redraw(); - } -} - -void CSelectableComponent::showAll(SDL_Surface * to) -{ - CComponent::showAll(to); - if(selected) - { - CSDL_Ext::drawBorder(to, Rect::around(image->pos), int3(239,215,123)); - } -} - -void CComponentBox::selectionChanged(CSelectableComponent * newSelection) -{ - if (newSelection == selected) - return; - - if (selected) - selected->select(false); - - selected = newSelection; - if (onSelect) - onSelect(selectedIndex()); - - if (selected) - selected->select(true); -} - -int CComponentBox::selectedIndex() -{ - if (selected) - return std::find(components.begin(), components.end(), selected) - components.begin(); - return -1; -} - -Point CComponentBox::getOrTextPos(CComponent *left, CComponent *right) -{ - int leftSubtitle = ( left->pos.w - left->image->pos.w) / 2; - int rightSubtitle = (right->pos.w - right->image->pos.w) / 2; - int fullDistance = getDistance(left, right) + leftSubtitle + rightSubtitle; - - return Point(fullDistance/2 - leftSubtitle, (left->image->pos.h + right->image->pos.h) / 4); -} - -int CComponentBox::getDistance(CComponent *left, CComponent *right) -{ - static const int betweenImagesMin = 20; - static const int betweenSubtitlesMin = 10; - - int leftSubtitle = ( left->pos.w - left->image->pos.w) / 2; - int rightSubtitle = (right->pos.w - right->image->pos.w) / 2; - int subtitlesOffset = leftSubtitle + rightSubtitle; - - return std::max(betweenSubtitlesMin, betweenImagesMin - subtitlesOffset); -} - -void CComponentBox::placeComponents(bool selectable) -{ - static const int betweenRows = 22; - - OBJ_CONSTRUCTION_CAPTURING_ALL; - if (components.empty()) - return; - - //prepare components - for(auto & comp : components) - { - addChild(comp); - comp->moveTo(Point(pos.x, pos.y)); - } - - struct RowData - { - size_t comps; - int width; - int height; - RowData (size_t Comps, int Width, int Height): - comps(Comps), width (Width), height (Height){}; - }; - std::vector rows; - rows.push_back (RowData (0,0,0)); - - //split components in rows - CComponent * prevComp = nullptr; - - for(CComponent * comp : components) - { - //make sure that components are smaller than our width - //assert(pos.w == 0 || pos.w < comp->pos.w); - - const int distance = prevComp ? getDistance(prevComp, comp) : 0; - - //start next row - if ((pos.w != 0 && rows.back().width + comp->pos.w + distance > pos.w) // row is full - || rows.back().comps >= 4) // no more than 4 comps per row - { - prevComp = nullptr; - rows.push_back (RowData (0,0,0)); - } - - if (prevComp) - rows.back().width += distance; - - rows.back().comps++; - rows.back().width += comp->pos.w; - - vstd::amax(rows.back().height, comp->pos.h); - prevComp = comp; - } - - if (pos.w == 0) - { - for(auto & row : rows) - vstd::amax(pos.w, row.width); - } - - int height = (rows.size() - 1) * betweenRows; - for(auto & row : rows) - height += row.height; - - //assert(pos.h == 0 || pos.h < height); - if (pos.h == 0) - pos.h = height; - - auto iter = components.begin(); - int currentY = (pos.h - height) / 2; - - //move components to their positions - for (auto & rows_row : rows) - { - // amount of free space we may add on each side of every component - int freeSpace = (pos.w - rows_row.width) / (rows_row.comps * 2); - prevComp = nullptr; - - int currentX = 0; - for (size_t col = 0; col < rows_row.comps; col++) - { - currentX += freeSpace; - if (prevComp) - { - if (selectable) - { - Point orPos = Point(currentX - freeSpace, currentY) + getOrTextPos(prevComp, *iter); - - new CLabel(orPos.x, orPos.y, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[4]); - } - currentX += getDistance(prevComp, *iter); - } - - (*iter)->moveBy(Point(currentX, currentY)); - currentX += (*iter)->pos.w; - currentX += freeSpace; - - prevComp = *(iter++); - } - currentY += rows_row.height + betweenRows; - } -} - -CComponentBox::CComponentBox(CComponent * _components, Rect position): - components(1, _components), - selected(nullptr) -{ - type |= REDRAW_PARENT; - pos = position + pos; - placeComponents(false); -} - -CComponentBox::CComponentBox(std::vector _components, Rect position): - components(_components), - selected(nullptr) -{ - type |= REDRAW_PARENT; - pos = position + pos; - placeComponents(false); -} - -CComponentBox::CComponentBox(std::vector _components, Rect position, std::function _onSelect): - components(_components.begin(), _components.end()), - selected(nullptr), - onSelect(_onSelect) -{ - type |= REDRAW_PARENT; - pos = position + pos; - placeComponents(true); - - assert(!components.empty()); - - int key = SDLK_1; - for(auto & comp : _components) - { - comp->onSelect = std::bind(&CComponentBox::selectionChanged, this, comp); - comp->assignedKeys.insert(key++); - } - selectionChanged(_components.front()); -} - -void CSelWindow::selectionChange(unsigned to) -{ - for (unsigned i=0;i(components[i]); - if (!pom) - continue; - pom->select(i==to); - } - redraw(); -} - -CSelWindow::CSelWindow(const std::string &Text, PlayerColor player, int charperline, const std::vector &comps, const std::vector > > &Buttons, QueryID askID) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - ID = askID; - for(int i=0;i= 0) - buttons.back()->callback += std::bind(&CSelWindow::madeChoice,this); - buttons[i]->callback += std::bind(&CInfoWindow::close,this); //each button will close the window apart from call-defined actions - } - - text = new CTextBox(Text, Rect(0, 0, 250, 100), 0, FONT_MEDIUM, CENTER, Colors::WHITE); - - buttons.front()->assignedKeys.insert(SDLK_RETURN); //first button - reacts on enter - buttons.back()->assignedKeys.insert(SDLK_ESCAPE); //last button - reacts on escape - - if(buttons.size() > 1 && askID.getNum() >= 0) //cancel button functionality - buttons.back()->callback += std::bind(&CCallback::selectionMade,LOCPLINT->cb.get(),0,askID); - - for(int i=0;irecActions = 255; - addChild(comps[i]); - components.push_back(comps[i]); - comps[i]->onSelect = std::bind(&CSelWindow::selectionChange,this,i); - if(i<9) - comps[i]->assignedKeys.insert(SDLK_1+i); - } - CMessage::drawIWindow(this, Text, player); -} - -void CSelWindow::madeChoice() -{ - if(ID.getNum() < 0) - return; - int ret = -1; - for (int i=0;i(components[i])->selected) - { - ret = i; - } - } - LOCPLINT->cb->selectionMade(ret+1,ID); -} - -CCreaturePic::CCreaturePic(int x, int y, const CCreature *cre, bool Big, bool Animated) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - pos.x+=x; - pos.y+=y; - - TFaction faction = cre->faction; - - assert(CGI->townh->factions.size() > faction); - - if(Big) - bg = new CPicture(CGI->townh->factions[faction]->creatureBg130); - else - bg = new CPicture(CGI->townh->factions[faction]->creatureBg120); - bg->needRefresh = true; - anim = new CCreatureAnim(0, 0, cre->animDefName, Rect()); - anim->clipRect(cre->isDoubleWide()?170:150, 155, bg->pos.w, bg->pos.h); - anim->startPreview(cre->hasBonusOfType(Bonus::SIEGE_WEAPON)); - - pos.w = bg->pos.w; - pos.h = bg->pos.h; -} - -CRecruitmentWindow::CCreatureCard::CCreatureCard(CRecruitmentWindow *window, const CCreature *crea, int totalAmount): - CIntObject(LCLICK | RCLICK), - parent(window), - selected(false), - creature(crea), - amount(totalAmount) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - pic = new CCreaturePic(1,1, creature, true, true); - // 1 + 1 px for borders - pos.w = pic->pos.w + 2; - pos.h = pic->pos.h + 2; -} - -void CRecruitmentWindow::CCreatureCard::select(bool on) -{ - selected = on; - redraw(); -} - -void CRecruitmentWindow::CCreatureCard::clickLeft(tribool down, bool previousState) -{ - if (down) - parent->select(this); -} - -void CRecruitmentWindow::CCreatureCard::clickRight(tribool down, bool previousState) -{ - if (down) - GH.pushInt(createCreWindow(creature->idNumber, CCreatureWindow::OTHER, 0)); -} - -void CRecruitmentWindow::CCreatureCard::showAll(SDL_Surface * to) -{ - CIntObject::showAll(to); - if (selected) - drawBorder(to, pos, int3(248, 0, 0)); - else - drawBorder(to, pos, int3(232, 212, 120)); -} - -CRecruitmentWindow::CCostBox::CCostBox(Rect position, std::string title) -{ - type |= REDRAW_PARENT; - pos = position + pos; - OBJ_CONSTRUCTION_CAPTURING_ALL; - new CLabel(pos.w/2, 10, FONT_SMALL, CENTER, Colors::WHITE, title); -} - -void CRecruitmentWindow::CCostBox::set(TResources res) -{ - //just update values - for(auto & item : resources) - { - item.second.first->setText(boost::lexical_cast(res[item.first])); - } -} - -void CRecruitmentWindow::CCostBox::createItems(TResources res) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - for(auto & curr : resources) - { - delete curr.second.first; - delete curr.second.second; - } - resources.clear(); - - TResources::nziterator iter(res); - while (iter.valid()) - { - CAnimImage * image = new CAnimImage("RESOURCE", iter->resType); - CLabel * text = new CLabel(15, 43, FONT_SMALL, CENTER, Colors::WHITE, "0"); - - resources.insert(std::make_pair(iter->resType, std::make_pair(text, image))); - iter++; - } - - if (!resources.empty()) - { - int curx = pos.w / 2 - (16 * resources.size()) - (8 * (resources.size() - 1)); - //reverse to display gold as first resource - for (auto & res : boost::adaptors::reverse(resources)) - { - res.second.first->moveBy(Point(curx, 22)); - res.second.second->moveBy(Point(curx, 22)); - curx += 48; - } - } - redraw(); -} - -void CRecruitmentWindow::select(CCreatureCard *card) -{ - if (card == selected) - return; - - if (selected) - selected->select(false); - - selected = card; - - if (selected) - selected->select(true); - - if (card) - { - si32 maxAmount = card->creature->maxAmount(LOCPLINT->cb->getResourceAmount()); - - vstd::amin(maxAmount, card->amount); - - slider->setAmount(maxAmount); - - if(slider->value != maxAmount) - slider->moveTo(maxAmount); - else // if slider already at 0 - emulate call to sliderMoved() - sliderMoved(maxAmount); - - costPerTroopValue->createItems(card->creature->cost); - totalCostValue->createItems(card->creature->cost); - - costPerTroopValue->set(card->creature->cost); - totalCostValue->set(card->creature->cost * maxAmount); - - //Recruit %s - title->setText(boost::str(boost::format(CGI->generaltexth->tcommands[21]) % card->creature->namePl)); - - maxButton->block(maxAmount == 0); - slider->block(maxAmount == 0); - } -} - -void CRecruitmentWindow::buy() -{ - CreatureID crid = selected->creature->idNumber; - SlotID dstslot = dst-> getSlotFor(crid); - - if(!dstslot.validSlot() && !vstd::contains(CGI->arth->bigArtifacts,CGI->arth->creatureToMachineID(crid))) //no available slot - { - std::string txt; - if(dst->ID == Obj::HERO) - { - txt = CGI->generaltexth->allTexts[425]; //The %s would join your hero, but there aren't enough provisions to support them. - boost::algorithm::replace_first(txt, "%s", slider->value > 1 ? CGI->creh->creatures[crid]->namePl : CGI->creh->creatures[crid]->nameSing); - } - else - { - txt = CGI->generaltexth->allTexts[17]; //There is no room in the garrison for this army. - } - - LOCPLINT->showInfoDialog(txt); - return; - } - - onRecruit(crid, slider->value); - if(level >= 0) - close(); -} - -void CRecruitmentWindow::showAll(SDL_Surface * to) -{ - CWindowObject::showAll(to); - - // recruit\total values - drawBorder(to, pos.x + 172, pos.y + 222, 67, 42, int3(239,215,123)); - drawBorder(to, pos.x + 246, pos.y + 222, 67, 42, int3(239,215,123)); - - //cost boxes - drawBorder(to, pos.x + 64, pos.y + 222, 99, 76, int3(239,215,123)); - drawBorder(to, pos.x + 322, pos.y + 222, 99, 76, int3(239,215,123)); - - //buttons borders - drawBorder(to, pos.x + 133, pos.y + 312, 66, 34, int3(173,142,66)); - drawBorder(to, pos.x + 211, pos.y + 312, 66, 34, int3(173,142,66)); - drawBorder(to, pos.x + 289, pos.y + 312, 66, 34, int3(173,142,66)); -} - -CRecruitmentWindow::CRecruitmentWindow(const CGDwelling *Dwelling, int Level, const CArmedInstance *Dst, const std::function &Recruit, int y_offset): - CWindowObject(PLAYER_COLORED, "TPRCRT"), - onRecruit(Recruit), - level(Level), - dst(Dst), - selected(nullptr), - dwelling(Dwelling) -{ - moveBy(Point(0, y_offset)); - - OBJ_CONSTRUCTION_CAPTURING_ALL; - new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); - - slider = new CSlider(176,279,135,nullptr,0,0,0,true); - slider->moved = std::bind(&CRecruitmentWindow::sliderMoved,this, _1); - - maxButton = new CAdventureMapButton(CGI->generaltexth->zelp[553],std::bind(&CSlider::moveToMax,slider),134,313,"IRCBTNS.DEF",SDLK_m); - buyButton = new CAdventureMapButton(CGI->generaltexth->zelp[554],std::bind(&CRecruitmentWindow::buy,this),212,313,"IBY6432.DEF",SDLK_RETURN); - cancelButton = new CAdventureMapButton(CGI->generaltexth->zelp[555],std::bind(&CRecruitmentWindow::close,this),290,313,"ICN6432.DEF",SDLK_ESCAPE); - - title = new CLabel(243, 32, FONT_BIG, CENTER, Colors::YELLOW); - availableValue = new CLabel(205, 253, FONT_SMALL, CENTER, Colors::WHITE); - toRecruitValue = new CLabel(279, 253, FONT_SMALL, CENTER, Colors::WHITE); - - costPerTroopValue = new CCostBox(Rect(65, 222, 97, 74), CGI->generaltexth->allTexts[346]); - totalCostValue = new CCostBox(Rect(323, 222, 97, 74), CGI->generaltexth->allTexts[466]); - - new CLabel(205, 233, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[465]); //available t - new CLabel(279, 233, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[16]); //recruit t - - availableCreaturesChanged(); -} - -void CRecruitmentWindow::availableCreaturesChanged() -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - size_t selectedIndex = 0; - - if (!cards.empty() && selected) // find position of selected item - selectedIndex = std::find(cards.begin(), cards.end(), selected) - cards.begin(); - - //deselect card - select(nullptr); - - //delete old cards - for(auto & card : cards) - delete card; - cards.clear(); - - for(int i=0; icreatures.size(); i++) - { - //find appropriate level - if(level >= 0 && i != level) - continue; - - int amount = dwelling->creatures[i].first; - - //create new cards - for(auto & creature : boost::adaptors::reverse(dwelling->creatures[i].second)) - cards.push_back(new CCreatureCard(this, CGI->creh->creatures[creature], amount)); - } - - assert(!cards.empty()); - - const int creatureWidth = 102; - - //normal distance between cards - 18px - int requiredSpace = 18; - //maximum distance we can use without reaching window borders - int availableSpace = pos.w - 50 - creatureWidth * cards.size(); - - if (cards.size() > 1) // avoid division by zero - availableSpace /= cards.size() - 1; - else - availableSpace = 0; - - assert(availableSpace >= 0); - - const int spaceBetween = std::min(requiredSpace, availableSpace); - const int totalCreatureWidth = spaceBetween + creatureWidth; - - //now we know total amount of cards and can move them to correct position - int curx = pos.w / 2 - (creatureWidth*cards.size()/2) - (spaceBetween*(cards.size()-1)/2); - for(auto & card : cards) - { - card->moveBy(Point(curx, 64)); - curx += totalCreatureWidth; - } - - //restore selection - select(cards[selectedIndex]); - - if(slider->value == slider->amount) - slider->moveTo(slider->amount); - else // if slider already at 0 - emulate call to sliderMoved() - sliderMoved(slider->amount); -} - -void CRecruitmentWindow::sliderMoved(int to) -{ - if (!selected) - return; - - buyButton->block(!to); - availableValue->setText(boost::lexical_cast(selected->amount - to)); - toRecruitValue->setText(boost::lexical_cast(to)); - - totalCostValue->set(selected->creature->cost * to); -} - -CSplitWindow::CSplitWindow(const CCreature * creature, std::function callback_, - int leftMin_, int rightMin_, int leftAmount_, int rightAmount_): - CWindowObject(PLAYER_COLORED, "GPUCRDIV"), - callback(callback_), - leftAmount(leftAmount_), - rightAmount(rightAmount_), - leftMin(leftMin_), - rightMin(rightMin_), - slider(nullptr) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - int total = leftAmount + rightAmount; - int leftMax = total - rightMin; - int rightMax = total - leftMin; - - ok = new CAdventureMapButton("", "", std::bind(&CSplitWindow::apply, this), 20, 263, "IOK6432", SDLK_RETURN); - cancel = new CAdventureMapButton("", "", std::bind(&CSplitWindow::close, this), 214, 263, "ICN6432", SDLK_ESCAPE); - - int sliderPositions = total - leftMin - rightMin; - - leftInput = new CTextInput(Rect(20, 218, 100, 36), FONT_BIG, std::bind(&CSplitWindow::setAmountText, this, _1, true)); - rightInput = new CTextInput(Rect(176, 218, 100, 36), FONT_BIG, std::bind(&CSplitWindow::setAmountText, this, _1, false)); - - //add filters to allow only number input - leftInput->filters.add(std::bind(&CTextInput::numberFilter, _1, _2, leftMin, leftMax)); - rightInput->filters.add(std::bind(&CTextInput::numberFilter, _1, _2, rightMin, rightMax)); - - leftInput->setText(boost::lexical_cast(leftAmount), false); - rightInput->setText(boost::lexical_cast(rightAmount), false); - - animLeft = new CCreaturePic(20, 54, creature, true, false); - animRight = new CCreaturePic(177, 54,creature, true, false); - - slider = new CSlider(21, 194, 257, std::bind(&CSplitWindow::sliderMoved, this, _1), 0, sliderPositions, rightAmount - rightMin, true); - - std::string title = CGI->generaltexth->allTexts[256]; - boost::algorithm::replace_first(title,"%s", creature->namePl); - new CLabel(150, 34, FONT_BIG, CENTER, Colors::YELLOW, title); -} - -void CSplitWindow::setAmountText(std::string text, bool left) -{ - try - { - setAmount(boost::lexical_cast(text), left); - slider->moveTo(rightAmount - rightMin); - } - catch(boost::bad_lexical_cast &) - { - } -} - -void CSplitWindow::setAmount(int value, bool left) -{ - int total = leftAmount + rightAmount; - leftAmount = left ? value : total - value; - rightAmount = left ? total - value : value; - - leftInput->setText(boost::lexical_cast(leftAmount)); - rightInput->setText(boost::lexical_cast(rightAmount)); -} - -void CSplitWindow::apply() -{ - callback(leftAmount, rightAmount); - close(); -} - -void CSplitWindow::sliderMoved(int to) -{ - setAmount(rightMin + to, false); -} - -CLevelWindow::CLevelWindow(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector &skills, std::function callback): - CWindowObject(PLAYER_COLORED, "LVLUPBKG"), - cb(callback) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - LOCPLINT->showingDialog->setn(true); - - new CAnimImage("PortraitsLarge", hero->portrait, 0, 170, 66); - new CAdventureMapButton("", "", std::bind(&CLevelWindow::close, this), 297, 413, "IOKAY", SDLK_RETURN); - - //%s has gained a level. - new CLabel(192, 33, FONT_MEDIUM, CENTER, Colors::WHITE, - boost::str(boost::format(CGI->generaltexth->allTexts[444]) % hero->name)); - - //%s is now a level %d %s. - new CLabel(192, 162, FONT_MEDIUM, CENTER, Colors::WHITE, - boost::str(boost::format(CGI->generaltexth->allTexts[445]) % hero->name % hero->level % hero->type->heroClass->name)); - - new CAnimImage("PSKIL42", pskill, 0, 174, 190); - - new CLabel(192, 253, FONT_MEDIUM, CENTER, Colors::WHITE, - CGI->generaltexth->primarySkillNames[pskill] + " +1"); - - if (!skills.empty()) - { - std::vector comps; - - for(auto & skill : skills) - { - comps.push_back(new CSelectableComponent( - CComponent::secskill, - skill, - hero->getSecSkillLevel( SecondarySkill(skill) )+1, - CComponent::medium)); - } - box = new CComponentBox(comps, Rect(75, 300, pos.w - 150, 100)); - } - else - box = nullptr; -} - -CLevelWindow::~CLevelWindow() -{ - //FIXME: call callback if there was nothing to select? - if (box && box->selectedIndex() != -1) - cb(box->selectedIndex()); - - LOCPLINT->showingDialog->setn(false); -} - -void CMinorResDataBar::show(SDL_Surface * to) -{ -} - -void CMinorResDataBar::showAll(SDL_Surface * to) -{ - blitAt(bg,pos.x,pos.y,to); - for (Res::ERes i=Res::WOOD; i<=Res::GOLD; vstd::advance(i, 1)) - { - std::string text = boost::lexical_cast(LOCPLINT->cb->getResourceAmount(i)); - - graphics->fonts[FONT_SMALL]->renderTextCenter(to, text, Colors::WHITE, Point(pos.x + 50 + 76 * i, pos.y + pos.h/2)); - } - std::vector temp; - - temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(Date::MONTH))); - temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(Date::WEEK))); - temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK))); - - std::string datetext = CGI->generaltexth->allTexts[62]+": %s, " + CGI->generaltexth->allTexts[63] - + ": %s, " + CGI->generaltexth->allTexts[64] + ": %s"; - - graphics->fonts[FONT_SMALL]->renderTextCenter(to, processStr(datetext,temp), Colors::WHITE, Point(pos.x+545+(pos.w-545)/2,pos.y+pos.h/2)); -} - -CMinorResDataBar::CMinorResDataBar() -{ - bg = BitmapHandler::loadBitmap("KRESBAR.bmp"); - CSDL_Ext::setDefaultColorKey(bg); - graphics->blueToPlayersAdv(bg,LOCPLINT->playerID); - pos.x = 7; - pos.y = 575; - pos.w = bg->w; - pos.h = bg->h; -} -CMinorResDataBar::~CMinorResDataBar() -{ - SDL_FreeSurface(bg); -} - -CObjectListWindow::CItem::CItem(CObjectListWindow *_parent, size_t _id, std::string _text): - parent(_parent), - index(_id) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - border = new CPicture("TPGATES"); - pos = border->pos; - addUsedEvents(LCLICK); - type |= REDRAW_PARENT; - - text = new CLabel(pos.w/2, pos.h/2, FONT_SMALL, CENTER, Colors::WHITE, _text); - select(index == parent->selected); -} - -void CObjectListWindow::CItem::select(bool on) -{ - if (on) - border->recActions = 255; - else - border->recActions = ~(UPDATE | SHOWALL); - redraw(); -} - -void CObjectListWindow::CItem::clickLeft(tribool down, bool previousState) -{ - if( previousState && !down) - parent->changeSelection(index); -} - -CObjectListWindow::CObjectListWindow(const std::vector &_items, CIntObject * titlePic, std::string _title, std::string _descr, - std::function Callback): - CWindowObject(PLAYER_COLORED, "TPGATE"), - onSelect(Callback) -{ - items.reserve(_items.size()); - for(int id : _items) - { - items.push_back(std::make_pair(id, CGI->mh->map->objects[id]->getObjectName())); - } - - init(titlePic, _title, _descr); -} - -CObjectListWindow::CObjectListWindow(const std::vector &_items, CIntObject * titlePic, std::string _title, std::string _descr, - std::function Callback): - CWindowObject(PLAYER_COLORED, "TPGATE"), - onSelect(Callback) -{ - items.reserve(_items.size()); - - for (size_t i=0; i<_items.size(); i++) - items.push_back(std::make_pair(int(i), _items[i])); - - init(titlePic, _title, _descr); -} - -void CObjectListWindow::init(CIntObject * titlePic, std::string _title, std::string _descr) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - title = new CLabel(152, 27, FONT_BIG, CENTER, Colors::YELLOW, _title); - descr = new CLabel(145, 133, FONT_SMALL, CENTER, Colors::WHITE, _descr); - - ok = new CAdventureMapButton("","",std::bind(&CObjectListWindow::elementSelected, this),15,402,"IOKAY.DEF", SDLK_RETURN); - ok->block(true); - exit = new CAdventureMapButton("","",std::bind(&CGuiHandler::popIntTotally,&GH, this),228,402,"ICANCEL.DEF",SDLK_ESCAPE); - - if (titlePic) - { - titleImage = titlePic; - addChild(titleImage); - titleImage->recActions = defActions; - titleImage->pos.x = pos.w/2 + pos.x - titleImage->pos.w/2; - titleImage->pos.y =75 + pos.y - titleImage->pos.h/2; - } - list = new CListBox(std::bind(&CObjectListWindow::genItem, this, _1), CListBox::DestroyFunc(), - Point(14, 151), Point(0, 25), 9, items.size(), 0, 1, Rect(262, -32, 256, 256) ); - list->type |= REDRAW_PARENT; -} - -CIntObject * CObjectListWindow::genItem(size_t index) -{ - if (index < items.size()) - return new CItem(this, index, items[index].second); - return nullptr; -} - -void CObjectListWindow::elementSelected() -{ - std::function toCall = onSelect;//save - int where = items[selected].first; //required variables - GH.popIntTotally(this);//then destroy window - toCall(where);//and send selected object -} - -void CObjectListWindow::changeSelection(size_t which) -{ - ok->block(false); - if (selected == which) - return; - - std::list< CIntObject * > elements = list->getItems(); - for(CIntObject * element : elements) - { - CItem *item; - if ( (item = dynamic_cast(element)) ) - { - if (item->index == selected) - item->select(false); - if (item->index == which) - item->select(true); - } - } - selected = which; -} - -void CObjectListWindow::keyPressed (const SDL_KeyboardEvent & key) -{ - if(key.state != SDL_PRESSED) - return; - - int sel = selected; - - switch(key.keysym.sym) - { - break; case SDLK_UP: - sel -=1; - - break; case SDLK_DOWN: - sel +=1; - - break; case SDLK_PAGEUP: - sel -=9; - - break; case SDLK_PAGEDOWN: - sel +=9; - - break; case SDLK_HOME: - sel = 0; - - break; case SDLK_END: - sel = items.size(); - - break; default: - return; - } - - vstd::abetween(sel, 0, items.size()-1); - list->scrollTo(sel); - changeSelection(sel); -} - -CTradeWindow::CTradeableItem::CTradeableItem(Point pos, EType Type, int ID, bool Left, int Serial): - CIntObject(LCLICK | HOVER | RCLICK, pos), - type(EType(-1)),// set to invalid, will be corrected in setType - id(ID), - serial(Serial), - left(Left) -{ - downSelection = false; - hlp = nullptr; - image = nullptr; - setType(Type); -} - -void CTradeWindow::CTradeableItem::setType(EType newType) -{ - if (type != newType) - { - OBJ_CONSTRUCTION_CAPTURING_ALL; - type = newType; - delete image; - - if (getIndex() < 0) - { - image = new CAnimImage(getFilename(), 0); - image->disable(); - } - else - image = new CAnimImage(getFilename(), getIndex()); - } -} - -void CTradeWindow::CTradeableItem::setID(int newID) -{ - if (id != newID) - { - id = newID; - if (image) - { - int index = getIndex(); - if (index < 0) - image->disable(); - else - { - image->enable(); - image->setFrame(index); - } - } - } -} - -std::string CTradeWindow::CTradeableItem::getFilename() -{ - switch(type) - { - case RESOURCE: - return "RESOURCE"; - case PLAYER: - return "CREST58"; - case ARTIFACT_TYPE: - case ARTIFACT_PLACEHOLDER: - case ARTIFACT_INSTANCE: - return "artifact"; - case CREATURE: - return "TWCRPORT"; - default: - return ""; - } -} - -int CTradeWindow::CTradeableItem::getIndex() -{ - if (id < 0) - return -1; - - switch(type) - { - case RESOURCE: - case PLAYER: - return id; - case ARTIFACT_TYPE: - case ARTIFACT_INSTANCE: - case ARTIFACT_PLACEHOLDER: - return VLC->arth->artifacts[id]->iconIndex; - case CREATURE: - return VLC->creh->creatures[id]->iconIndex; - default: - return -1; - } -} - -void CTradeWindow::CTradeableItem::showAll(SDL_Surface * to) -{ - Point posToBitmap; - Point posToSubCenter; - - switch(type) - { - case RESOURCE: - posToBitmap = Point(19,9); - posToSubCenter = Point(36, 59); - break; - case CREATURE_PLACEHOLDER: - case CREATURE: - posToSubCenter = Point(29, 76); - if(downSelection) - posToSubCenter.y += 5; - break; - case PLAYER: - posToSubCenter = Point(31, 76); - break; - case ARTIFACT_PLACEHOLDER: - case ARTIFACT_INSTANCE: - posToSubCenter = Point(19, 55); - if(downSelection) - posToSubCenter.y += 8; - break; - case ARTIFACT_TYPE: - posToSubCenter = Point(19, 58); - break; - } - - if (image) - { - image->moveTo(pos.topLeft() + posToBitmap); - CIntObject::showAll(to); - } - - printAtMiddleLoc(subtitle, posToSubCenter, FONT_SMALL, Colors::WHITE, to); -} - -void CTradeWindow::CTradeableItem::clickLeft(tribool down, bool previousState) -{ - CTradeWindow *mw = dynamic_cast(parent); - assert(mw); - if(down) - { - - if(type == ARTIFACT_PLACEHOLDER) - { - CAltarWindow *aw = static_cast(mw); - if(const CArtifactInstance *movedArt = aw->arts->commonInfo->src.art) - { - aw->moveFromSlotToAltar(aw->arts->commonInfo->src.slotID, this, movedArt); - } - else if(const CArtifactInstance *art = getArtInstance()) - { - aw->arts->commonInfo->src.AOH = aw->arts; - aw->arts->commonInfo->src.art = art; - aw->arts->commonInfo->src.slotID = aw->hero->getArtPos(art); - aw->arts->markPossibleSlots(art); - - //aw->arts->commonInfo->dst.AOH = aw->arts; - CCS->curh->dragAndDropCursor(new CAnimImage("artifact", art->artType->iconIndex)); - - aw->arts->artifactsOnAltar.erase(art); - setID(-1); - subtitle = ""; - aw->deal->block(!aw->arts->artifactsOnAltar.size()); - } - - aw->calcTotalExp(); - return; - } - if(left) - { - if(mw->hLeft != this) - mw->hLeft = this; - else - return; - } - else - { - if(mw->hRight != this) - mw->hRight = this; - else - return; - } - mw->selectionChanged(left); - } -} - -void CTradeWindow::CTradeableItem::showAllAt(const Point &dstPos, const std::string &customSub, SDL_Surface * to) -{ - Rect oldPos = pos; - std::string oldSub = subtitle; - downSelection = true; - - moveTo(dstPos); - subtitle = customSub; - showAll(to); - - downSelection = false; - moveTo(oldPos.topLeft()); - subtitle = oldSub; -} - -void CTradeWindow::CTradeableItem::hover(bool on) -{ - if(!on) - { - GH.statusbar->clear(); - return; - } - - switch(type) - { - case CREATURE: - case CREATURE_PLACEHOLDER: - GH.statusbar->setText(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->creatures[id]->namePl)); - break; - case ARTIFACT_PLACEHOLDER: - if(id < 0) - GH.statusbar->setText(CGI->generaltexth->zelp[582].first); - else - GH.statusbar->setText(CGI->arth->artifacts[id]->Name()); - break; - } -} - -void CTradeWindow::CTradeableItem::clickRight(tribool down, bool previousState) -{ - if(down) - { - switch(type) - { - case CREATURE: - case CREATURE_PLACEHOLDER: - //GH.statusbar->print(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->creatures[id]->namePl)); - break; - case ARTIFACT_TYPE: - case ARTIFACT_PLACEHOLDER: - if(id >= 0) - adventureInt->handleRightClick(CGI->arth->artifacts[id]->Description(), down); - break; - } - } -} - -std::string CTradeWindow::CTradeableItem::getName(int number /*= -1*/) const -{ - switch(type) - { - case PLAYER: - return CGI->generaltexth->capColors[id]; - case RESOURCE: - return CGI->generaltexth->restypes[id]; - case CREATURE: - if(number == 1) - return CGI->creh->creatures[id]->nameSing; - else - return CGI->creh->creatures[id]->namePl; - case ARTIFACT_TYPE: - case ARTIFACT_INSTANCE: - return CGI->arth->artifacts[id]->Name(); - } - assert(0); - return ""; -} - -const CArtifactInstance * CTradeWindow::CTradeableItem::getArtInstance() const -{ - switch(type) - { - case ARTIFACT_PLACEHOLDER: - case ARTIFACT_INSTANCE: - return (const CArtifactInstance *)hlp; - default: - return nullptr; - } -} - -void CTradeWindow::CTradeableItem::setArtInstance(const CArtifactInstance *art) -{ - assert(type == ARTIFACT_PLACEHOLDER || type == ARTIFACT_INSTANCE); - hlp = art; - if(art) - setID(art->artType->id); - else - setID(-1); -} - -CTradeWindow::CTradeWindow(std::string bgName, const IMarket *Market, const CGHeroInstance *Hero, EMarketMode::EMarketMode Mode): - CWindowObject(PLAYER_COLORED, bgName), - market(Market), - hero(Hero), - arts(nullptr), - hLeft(nullptr), - hRight(nullptr), - readyToTrade(false) -{ - type |= BLOCK_ADV_HOTKEYS; - mode = Mode; - initTypes(); -} - -void CTradeWindow::initTypes() -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - itemsType[1] = RESOURCE; - itemsType[0] = RESOURCE; - break; - case EMarketMode::RESOURCE_PLAYER: - itemsType[1] = RESOURCE; - itemsType[0] = PLAYER; - break; - case EMarketMode::CREATURE_RESOURCE: - itemsType[1] = CREATURE; - itemsType[0] = RESOURCE; - break; - case EMarketMode::RESOURCE_ARTIFACT: - itemsType[1] = RESOURCE; - itemsType[0] = ARTIFACT_TYPE; - break; - case EMarketMode::ARTIFACT_RESOURCE: - itemsType[1] = ARTIFACT_INSTANCE; - itemsType[0] = RESOURCE; - break; - case EMarketMode::CREATURE_EXP: - itemsType[1] = CREATURE; - itemsType[0] = CREATURE_PLACEHOLDER; - break; - case EMarketMode::ARTIFACT_EXP: - itemsType[1] = ARTIFACT_TYPE; - itemsType[0] = ARTIFACT_PLACEHOLDER; - break; - } -} - -void CTradeWindow::initItems(bool Left) -{ - if(Left && (itemsType[1] == ARTIFACT_TYPE || itemsType[1] == ARTIFACT_INSTANCE)) - { - int xOffset = 0, yOffset = 0; - if(mode == EMarketMode::ARTIFACT_RESOURCE) - { - xOffset = -361; - yOffset = +46; - - auto hlp = new CTradeableItem(Point(137, 469), itemsType[Left], -1, 1, 0); - hlp->recActions &= ~(UPDATE | SHOWALL); - items[Left].push_back(hlp); - } - else //ARTIFACT_EXP - { - xOffset = -363; - yOffset = -12; - } - - BLOCK_CAPTURING; - arts = new CArtifactsOfHero(Point(pos.x+xOffset, pos.y+yOffset)); - arts->commonInfo = new CArtifactsOfHero::SCommonPart; - arts->commonInfo->participants.insert(arts); - arts->recActions = 255; - arts->setHero(hero); - arts->allowedAssembling = false; - addChild(arts); - artSets.push_back(arts); - - if(mode == EMarketMode::ARTIFACT_RESOURCE) - arts->highlightModeCallback = std::bind(&CTradeWindow::artifactSelected, this, _1); - return; - } - - std::vector *ids = getItemsIds(Left); - std::vector pos; - int amount = -1; - - getPositionsFor(pos, Left, itemsType[Left]); - - if(Left || !ids) - amount = 7; - else - amount = ids->size(); - - if(ids) - vstd::amin(amount, ids->size()); - - for(int j=0; jsize()>j) ? (*ids)[j] : j; - if(id < 0 && mode != EMarketMode::ARTIFACT_EXP) //when sacrificing artifacts we need to prepare empty slots - continue; - - auto hlp = new CTradeableItem(pos[j].topLeft(), itemsType[Left], id, Left, j); - hlp->pos = pos[j] + this->pos.topLeft(); - items[Left].push_back(hlp); - } - - initSubs(Left); -} - -std::vector *CTradeWindow::getItemsIds(bool Left) -{ - std::vector *ids = nullptr; - - if(mode == EMarketMode::ARTIFACT_EXP) - return new std::vector(22, -1); - - if(Left) - { - switch(itemsType[1]) - { - case CREATURE: - ids = new std::vector; - for(int i = 0; i < 7; i++) - { - if(const CCreature *c = hero->getCreature(SlotID(i))) - ids->push_back(c->idNumber); - else - ids->push_back(-1); - } - break; - } - } - else - { - switch(itemsType[0]) - { - case PLAYER: - ids = new std::vector; - for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) - if(PlayerColor(i) != LOCPLINT->playerID && LOCPLINT->cb->getPlayerStatus(PlayerColor(i)) == EPlayerStatus::INGAME) - ids->push_back(i); - break; - - case ARTIFACT_TYPE: - ids = new std::vector(market->availableItemsIds(mode)); - break; - } - } - - return ids; -} - -void CTradeWindow::getPositionsFor(std::vector &poss, bool Left, EType type) const -{ - if(mode == EMarketMode::ARTIFACT_EXP && !Left) - { - //22 boxes, 5 in row, last row: two boxes centered - int h, w, x, y, dx, dy; - h = w = 44; - x = 317; - y = 53; - dx = 54; - dy = 70; - for (int i = 0; i < 4 ; i++) - for (int j = 0; j < 5 ; j++) - poss += Rect(x + dx*j, y + dy*i, w, h); - - poss += Rect(x + dx*1.5, y + dy*4, w, h); - poss += Rect(x + dx*2.5, y + dy*4, w, h); - } - else - { - //seven boxes: - // X X X - // X X X - // X - int h, w, x, y, dx, dy; - int leftToRightOffset; - getBaseForPositions(type, dx, dy, x, y, h, w, !Left, leftToRightOffset); - - poss += genRect(h, w, x, y), genRect(h, w, x + dx, y), genRect(h, w, x + 2*dx, y), - genRect(h, w, x, y + dy), genRect(h, w, x + dx, y + dy), genRect(h, w, x + 2*dx, y + dy), - genRect(h, w, x + dx, y + 2*dy); - - if(!Left) - { - for(Rect &r : poss) - r.x += leftToRightOffset; - } - } -} - -void CTradeWindow::initSubs(bool Left) -{ - for(CTradeableItem *t : items[Left]) - { - if(Left) - { - switch(itemsType[1]) - { - case CREATURE: - t->subtitle = boost::lexical_cast(hero->getStackCount(SlotID(t->serial))); - break; - case RESOURCE: - t->subtitle = boost::lexical_cast(LOCPLINT->cb->getResourceAmount(static_cast(t->serial))); - break; - } - } - else //right side - { - if(itemsType[0] == PLAYER) - { - t->subtitle = CGI->generaltexth->capColors[t->id]; - } - else if(hLeft)//artifact, creature - { - int h1, h2; //hlp variables for getting offer - market->getOffer(hLeft->id, t->id, h1, h2, mode); - if(t->id != hLeft->id || mode != EMarketMode::RESOURCE_RESOURCE) //don't allow exchanging same resources - { - std::ostringstream oss; - oss << h2; - if(h1!=1) - oss << "/" << h1; - t->subtitle = oss.str(); - } - else - t->subtitle = CGI->generaltexth->allTexts[164]; // n/a - } - else - t->subtitle = ""; - } - } -} - -void CTradeWindow::showAll(SDL_Surface * to) -{ - CWindowObject::showAll(to); - - if(hRight) - CSDL_Ext::drawBorder(to,hRight->pos.x-1,hRight->pos.y-1,hRight->pos.w+2,hRight->pos.h+2,int3(255,231,148)); - if(hLeft && hLeft->type != ARTIFACT_INSTANCE) - CSDL_Ext::drawBorder(to,hLeft->pos.x-1,hLeft->pos.y-1,hLeft->pos.w+2,hLeft->pos.h+2,int3(255,231,148)); - - if(readyToTrade) - { - hLeft->showAllAt(pos.topLeft() + selectionOffset(true), selectionSubtitle(true), to); - hRight->showAllAt(pos.topLeft() + selectionOffset(false), selectionSubtitle(false), to); - } -} - -void CTradeWindow::removeItems(const std::set &toRemove) -{ - for(CTradeableItem *t : toRemove) - removeItem(t); -} - -void CTradeWindow::removeItem(CTradeableItem * t) -{ - items[t->left] -= t; - delete t; - - if(hRight == t) - { - hRight = nullptr; - selectionChanged(false); - } -} - -void CTradeWindow::getEmptySlots(std::set &toRemove) -{ - for(CTradeableItem *t : items[1]) - if(!hero->getStackCount(SlotID(t->serial))) - toRemove.insert(t); -} - -void CTradeWindow::setMode(EMarketMode::EMarketMode Mode) -{ - const IMarket *m = market; - const CGHeroInstance *h = hero; - CTradeWindow *nwindow = nullptr; - - GH.popIntTotally(this); - - switch(Mode) - { - case EMarketMode::CREATURE_EXP: - case EMarketMode::ARTIFACT_EXP: - nwindow = new CAltarWindow(m, h, Mode); - break; - default: - nwindow = new CMarketplaceWindow(m, h, Mode); - break; - } - - GH.pushInt(nwindow); -} - -void CTradeWindow::artifactSelected(CArtPlace *slot) -{ - assert(mode == EMarketMode::ARTIFACT_RESOURCE); - items[1][0]->setArtInstance(slot->ourArt); - if(slot->ourArt) - hLeft = items[1][0]; - else - hLeft = nullptr; - - selectionChanged(true); -} - -std::string CMarketplaceWindow::getBackgroundForMode(EMarketMode::EMarketMode mode) -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - return "TPMRKRES.bmp"; - case EMarketMode::RESOURCE_PLAYER: - return "TPMRKPTS.bmp"; - case EMarketMode::CREATURE_RESOURCE: - return "TPMRKCRS.bmp"; - case EMarketMode::RESOURCE_ARTIFACT: - return "TPMRKABS.bmp"; - case EMarketMode::ARTIFACT_RESOURCE: - return "TPMRKASS.bmp"; - } - assert(0); - return ""; -} - -CMarketplaceWindow::CMarketplaceWindow(const IMarket *Market, const CGHeroInstance *Hero, EMarketMode::EMarketMode Mode) - : CTradeWindow(getBackgroundForMode(Mode), Market, Hero, Mode) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - madeTransaction = false; - bool sliderNeeded = true; - - new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); - - std::string title; - - if (market->o->ID == Obj::TOWN) - { - switch (mode) - { - break; case EMarketMode::CREATURE_RESOURCE: - title = CGI->townh->factions[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->Name(); - - break; case EMarketMode::RESOURCE_ARTIFACT: - title = CGI->townh->factions[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name(); - sliderNeeded = false; - - break; case EMarketMode::ARTIFACT_RESOURCE: - title = CGI->townh->factions[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name(); - sliderNeeded = false; - - break; default: - title = CGI->generaltexth->allTexts[158]; - } - } - else - { - switch (market->o->ID) - { - break; case Obj::BLACK_MARKET: title = CGI->generaltexth->allTexts[349]; - break; case Obj::TRADING_POST: title = CGI->generaltexth->allTexts[159]; - break; case Obj::TRADING_POST_SNOW: title = CGI->generaltexth->allTexts[159]; - break; default: title = market->o->getObjectName(); - } - } - - new CLabel(300, 27, FONT_BIG, CENTER, Colors::YELLOW, title); - - initItems(false); - initItems(true); - - ok = new CAdventureMapButton(CGI->generaltexth->zelp[600],std::bind(&CGuiHandler::popIntTotally,&GH,this),516,520,"IOK6432.DEF",SDLK_RETURN); - ok->assignedKeys.insert(SDLK_ESCAPE); - deal = new CAdventureMapButton(CGI->generaltexth->zelp[595],std::bind(&CMarketplaceWindow::makeDeal,this),307,520,"TPMRKB.DEF"); - deal->block(true); - - if(sliderNeeded) - { - slider = new CSlider(231,490,137,nullptr,0,0); - slider->moved = std::bind(&CMarketplaceWindow::sliderMoved,this,_1); - max = new CAdventureMapButton(CGI->generaltexth->zelp[596],std::bind(&CMarketplaceWindow::setMax,this),229,520,"IRCBTNS.DEF"); - max->block(true); - } - else - { - slider = nullptr; - max = nullptr; - deal->moveBy(Point(-30, 0)); - } - - Rect traderTextRect; - - //left side - switch(Mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::RESOURCE_PLAYER: - case EMarketMode::RESOURCE_ARTIFACT: - new CLabel(154, 148, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[270]); - break; - - case EMarketMode::CREATURE_RESOURCE: - //%s's Creatures - new CLabel(152, 102, FONT_SMALL, CENTER, Colors::WHITE, - boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)); - break; - case EMarketMode::ARTIFACT_RESOURCE: - //%s's Artifacts - new CLabel(152, 102, FONT_SMALL, CENTER, Colors::WHITE, - boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)); - break; - } - - //right side - switch(Mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::CREATURE_RESOURCE: - case EMarketMode::RESOURCE_ARTIFACT: - case EMarketMode::ARTIFACT_RESOURCE: - new CLabel(445, 148, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[168]); - traderTextRect = Rect(316, 48, 260, 75); - break; - case EMarketMode::RESOURCE_PLAYER: - new CLabel(445, 55, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[169]); - traderTextRect = Rect(28, 48, 260, 75); - break; - } - - traderText = new CTextBox("", traderTextRect, 0, FONT_SMALL, CENTER); - int specialOffset = mode == EMarketMode::ARTIFACT_RESOURCE ? 35 : 0; //in selling artifacts mode we need to move res-res and art-res buttons down - - if(printButtonFor(EMarketMode::RESOURCE_PLAYER)) - new CAdventureMapButton(CGI->generaltexth->zelp[612],std::bind(&CMarketplaceWindow::setMode,this, EMarketMode::RESOURCE_PLAYER), 18, 520,"TPMRKBU1.DEF"); - if(printButtonFor(EMarketMode::RESOURCE_RESOURCE)) - new CAdventureMapButton(CGI->generaltexth->zelp[605],std::bind(&CMarketplaceWindow::setMode,this, EMarketMode::RESOURCE_RESOURCE), 516, 450 + specialOffset,"TPMRKBU5.DEF"); - if(printButtonFor(EMarketMode::CREATURE_RESOURCE)) - new CAdventureMapButton(CGI->generaltexth->zelp[599],std::bind(&CMarketplaceWindow::setMode,this, EMarketMode::CREATURE_RESOURCE), 516, 485,"TPMRKBU4.DEF"); //was y=450, changed to not overlap res-res in some conditions - if(printButtonFor(EMarketMode::RESOURCE_ARTIFACT)) - new CAdventureMapButton(CGI->generaltexth->zelp[598],std::bind(&CMarketplaceWindow::setMode,this, EMarketMode::RESOURCE_ARTIFACT), 18, 450 + specialOffset,"TPMRKBU2.DEF"); - if(printButtonFor(EMarketMode::ARTIFACT_RESOURCE)) - new CAdventureMapButton(CGI->generaltexth->zelp[613],std::bind(&CMarketplaceWindow::setMode,this, EMarketMode::ARTIFACT_RESOURCE), 18, 485,"TPMRKBU3.DEF"); //was y=450, changed to not overlap res-art in some conditions - - updateTraderText(); -} - -CMarketplaceWindow::~CMarketplaceWindow() -{ - hLeft = hRight = nullptr; - for(auto & elem : items[1]) - delete elem; - for(auto & elem : items[0]) - delete elem; - - items[1].clear(); - items[0].clear(); -} - - - -void CMarketplaceWindow::setMax() -{ - slider->moveToMax(); -} - -void CMarketplaceWindow::makeDeal() -{ - int sliderValue = 0; - if(slider) - sliderValue = slider->value; - else - sliderValue = !deal->isBlocked(); //should always be 1 - - if(!sliderValue) - return; - - int leftIdToSend = -1; - switch (mode) - { - case EMarketMode::CREATURE_RESOURCE: - leftIdToSend = hLeft->serial; - break; - case EMarketMode::ARTIFACT_RESOURCE: - leftIdToSend = hLeft->getArtInstance()->id.getNum(); - break; - default: - leftIdToSend = hLeft->id; - break; - } - - if(slider) - { - LOCPLINT->cb->trade(market->o, mode, leftIdToSend, hRight->id, slider->value*r1, hero); - slider->moveTo(0); - } - else - { - LOCPLINT->cb->trade(market->o, mode, leftIdToSend, hRight->id, r2, hero); - } - madeTransaction = true; - - hLeft = nullptr; - hRight = nullptr; - selectionChanged(true); -} - -void CMarketplaceWindow::sliderMoved( int to ) -{ - redraw(); -} - -void CMarketplaceWindow::selectionChanged(bool side) -{ - readyToTrade = hLeft && hRight; - if(mode == EMarketMode::RESOURCE_RESOURCE) - readyToTrade = readyToTrade && (hLeft->id != hRight->id); //for resource trade, two DIFFERENT resources must be selected - - if(mode == EMarketMode::ARTIFACT_RESOURCE && !hLeft) - arts->unmarkSlots(false); - - if(readyToTrade) - { - int soldItemId = hLeft->id; - market->getOffer(soldItemId, hRight->id, r1, r2, mode); - - if(slider) - { - int newAmount = -1; - if(itemsType[1] == RESOURCE) - newAmount = LOCPLINT->cb->getResourceAmount(static_cast(soldItemId)); - else if(itemsType[1] == CREATURE) - newAmount = hero->getStackCount(SlotID(hLeft->serial)) - (hero->Slots().size() == 1 && hero->needsLastStack()); - else - assert(0); - - slider->setAmount(newAmount / r1); - slider->moveTo(0); - max->block(false); - deal->block(false); - } - else if(itemsType[1] == RESOURCE) //buying -> check if we can afford transaction - { - deal->block(LOCPLINT->cb->getResourceAmount(static_cast(soldItemId)) < r1); - } - else - deal->block(false); - } - else - { - if(slider) - { - max->block(true); - slider->setAmount(0); - slider->moveTo(0); - } - deal->block(true); - } - - if(side && itemsType[0] != PLAYER) //items[1] selection changed, recalculate offers - initSubs(false); - - updateTraderText(); - redraw(); -} - -bool CMarketplaceWindow::printButtonFor(EMarketMode::EMarketMode M) const -{ - return market->allowsTrade(M) && M != mode && (hero || ( M != EMarketMode::CREATURE_RESOURCE && M != EMarketMode::RESOURCE_ARTIFACT && M != EMarketMode::ARTIFACT_RESOURCE )); -} - -void CMarketplaceWindow::garrisonChanged() -{ - if(mode != EMarketMode::CREATURE_RESOURCE) - return; - - std::set toRemove; - getEmptySlots(toRemove); - - - removeItems(toRemove); - initSubs(true); -} - -void CMarketplaceWindow::artifactsChanged(bool Left) -{ - assert(!Left); - if(mode != EMarketMode::RESOURCE_ARTIFACT) - return; - - std::vector available = market->availableItemsIds(mode); - std::set toRemove; - for(CTradeableItem *t : items[0]) - if(!vstd::contains(available, t->id)) - toRemove.insert(t); - - removeItems(toRemove); - redraw(); -} - -std::string CMarketplaceWindow::selectionSubtitle(bool Left) const -{ - if(Left) - { - switch(itemsType[1]) - { - case RESOURCE: - case CREATURE: - { - int val = slider - ? slider->value * r1 - : (((deal->isBlocked())) ? 0 : r1); - - return boost::lexical_cast(val); - } - case ARTIFACT_INSTANCE: - return ((deal->isBlocked()) ? "0" : "1"); - } - } - else - { - switch(itemsType[0]) - { - case RESOURCE: - if(slider) - return boost::lexical_cast( slider->value * r2 ); - else - return boost::lexical_cast(r2); - case ARTIFACT_TYPE: - return ((deal->isBlocked()) ? "0" : "1"); - case PLAYER: - return (hRight ? CGI->generaltexth->capColors[hRight->id] : ""); - } - } - - return "???"; -} - -Point CMarketplaceWindow::selectionOffset(bool Left) const -{ - if(Left) - { - switch(itemsType[1]) - { - case RESOURCE: - return Point(122, 446); - case CREATURE: - return Point(128, 450); - case ARTIFACT_INSTANCE: - return Point(134, 466); - } - } - else - { - switch(itemsType[0]) - { - case RESOURCE: - if(mode == EMarketMode::ARTIFACT_RESOURCE) - return Point(410, 469); - else - return Point(410, 446); - case ARTIFACT_TYPE: - return Point(425, 447); - case PLAYER: - return Point(417, 451); - } - } - - assert(0); - return Point(0,0); -} - -void CMarketplaceWindow::resourceChanged(int type, int val) -{ - initSubs(true); -} - -void CMarketplaceWindow::getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const -{ - switch(type) - { - case RESOURCE: - dx = 82; - dy = 79; - x = 39; - y = 180; - h = 66; - w = 74; - break; - case PLAYER: - dx = 83; - dy = 118; - h = 64; - w = 58; - x = 44; - y = 83; - assert(Right); - break; - case CREATURE://45,123 - x = 45; - y = 123; - w = 58; - h = 64; - dx = 83; - dy = 98; - assert(!Right); - break; - case ARTIFACT_TYPE://45,123 - x = 340-289; - y = 180; - w = 44; - h = 44; - dx = 83; - dy = 79; - break; - } - - leftToRightOffset = 289; -} - -void CMarketplaceWindow::updateTraderText() -{ - if(readyToTrade) - { - if(mode == EMarketMode::RESOURCE_PLAYER) - { - //I can give %s to the %s player. - traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[165]) % hLeft->getName() % hRight->getName())); - } - else if(mode == EMarketMode::RESOURCE_ARTIFACT) - { - //I can offer you the %s for %d %s of %s. - traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[267]) % hRight->getName() % r1 % CGI->generaltexth->allTexts[160 + (r1==1)] % hLeft->getName())); - } - else if(mode == EMarketMode::RESOURCE_RESOURCE) - { - //I can offer you %d %s of %s for %d %s of %s. - traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[157]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % r1 % CGI->generaltexth->allTexts[160 + (r1==1)] % hLeft->getName())); - } - else if(mode == EMarketMode::CREATURE_RESOURCE) - { - //I can offer you %d %s of %s for %d %s. - traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[269]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % r1 % hLeft->getName(r1))); - } - else if(mode == EMarketMode::ARTIFACT_RESOURCE) - { - //I can offer you %d %s of %s for your %s. - traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[268]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % hLeft->getName(r1))); - } - return; - } - - int gnrtxtnr = -1; - if(madeTransaction) - { - if(mode == EMarketMode::RESOURCE_PLAYER) - gnrtxtnr = 166; //Are there any other resources you'd like to give away? - else - gnrtxtnr = 162; //You have received quite a bargain. I expect to make no profit on the deal. Can I interest you in any of my other wares? - } - else - { - if(mode == EMarketMode::RESOURCE_PLAYER) - gnrtxtnr = 167; //If you'd like to give any of your resources to another player, click on the item you wish to give and to whom. - else - gnrtxtnr = 163; //Please inspect our fine wares. If you feel like offering a trade, click on the items you wish to trade with and for. - } - traderText->setText(CGI->generaltexth->allTexts[gnrtxtnr]); -} - -CAltarWindow::CAltarWindow(const IMarket *Market, const CGHeroInstance *Hero /*= nullptr*/, EMarketMode::EMarketMode Mode) - :CTradeWindow((Mode == EMarketMode::CREATURE_EXP ? "ALTARMON.bmp" : "ALTRART2.bmp"), Market, Hero, Mode) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - if(Mode == EMarketMode::CREATURE_EXP) - { - //%s's Creatures - new CLabel(155, 30, FONT_SMALL, CENTER, Colors::YELLOW, - boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)); - - //Altar of Sacrifice - new CLabel(450, 30, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[479]); - - //To sacrifice creatures, move them from your army on to the Altar and click Sacrifice - new CTextBox(CGI->generaltexth->allTexts[480], Rect(320, 56, 256, 40), 0, FONT_SMALL, CENTER, Colors::YELLOW); - - slider = new CSlider(231,481,137,nullptr,0,0); - slider->moved = std::bind(&CAltarWindow::sliderMoved,this,_1); - max = new CAdventureMapButton(CGI->generaltexth->zelp[578],std::bind(&CSlider::moveToMax, slider),147,520,"IRCBTNS.DEF"); - - sacrificedUnits.resize(GameConstants::ARMY_SIZE, 0); - sacrificeAll = new CAdventureMapButton(CGI->generaltexth->zelp[579],std::bind(&CAltarWindow::SacrificeAll,this),393,520,"ALTARMY.DEF"); - sacrificeBackpack = nullptr; - - initItems(true); - mimicCres(); - artIcon = nullptr; - } - else - { - //Sacrifice artifacts for experience - new CLabel(450, 34, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[477]); - //%s's Creatures - new CLabel(302, 423, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[478]); - - sacrificeAll = new CAdventureMapButton(CGI->generaltexth->zelp[571], std::bind(&CAltarWindow::SacrificeAll,this),393,520,"ALTFILL.DEF"); - sacrificeAll->block(hero->artifactsInBackpack.empty() && hero->artifactsWorn.empty()); - sacrificeBackpack = new CAdventureMapButton(CGI->generaltexth->zelp[570],std::bind(&CAltarWindow::SacrificeBackpack,this),147,520,"ALTEMBK.DEF"); - sacrificeBackpack->block(hero->artifactsInBackpack.empty()); - - slider = nullptr; - max = nullptr; - - initItems(true); - initItems(false); - artIcon = new CAnimImage("ARTIFACT", 0, 0, 281, 442); - artIcon->disable(); - } - - //Experience needed to reach next level - new CTextBox(CGI->generaltexth->allTexts[475], Rect(15, 415, 125, 50), 0, FONT_SMALL, CENTER, Colors::YELLOW); - //Total experience on the Altar - new CTextBox(CGI->generaltexth->allTexts[476], Rect(15, 495, 125, 40), 0, FONT_SMALL, CENTER, Colors::YELLOW); - - new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); - - ok = new CAdventureMapButton(CGI->generaltexth->zelp[568],std::bind(&CGuiHandler::popIntTotally,&GH,this),516,520,"IOK6432.DEF",SDLK_RETURN); - ok->assignedKeys.insert(SDLK_ESCAPE); - - deal = new CAdventureMapButton(CGI->generaltexth->zelp[585],std::bind(&CAltarWindow::makeDeal,this),269,520,"ALTSACR.DEF"); - - if(Hero->getAlignment() != ::EAlignment::EVIL && Mode == EMarketMode::CREATURE_EXP) - new CAdventureMapButton(CGI->generaltexth->zelp[580], std::bind(&CTradeWindow::setMode,this, EMarketMode::ARTIFACT_EXP), 516, 421, "ALTART.DEF"); - if(Hero->getAlignment() != ::EAlignment::GOOD && Mode == EMarketMode::ARTIFACT_EXP) - new CAdventureMapButton(CGI->generaltexth->zelp[572], std::bind(&CTradeWindow::setMode,this, EMarketMode::CREATURE_EXP), 516, 421, "ALTSACC.DEF"); - - expPerUnit.resize(GameConstants::ARMY_SIZE, 0); - getExpValues(); - - expToLevel = new CLabel(73, 475, FONT_SMALL, CENTER); - expOnAltar = new CLabel(73, 543, FONT_SMALL, CENTER); - - setExpToLevel(); - calcTotalExp(); - blockTrade(); -} - -CAltarWindow::~CAltarWindow() -{ - -} - -void CAltarWindow::getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const -{ - leftToRightOffset = 289; - x = 45; - y = 110; - w = 58; - h = 64; - dx = 83; - dy = 98; -} - -void CAltarWindow::sliderMoved(int to) -{ - sacrificedUnits[hLeft->serial] = to; - updateRight(hRight); - deal->block(!to); - calcTotalExp(); - redraw(); -} - -void CAltarWindow::makeDeal() -{ - if(mode == EMarketMode::CREATURE_EXP) - { - blockTrade(); - slider->value = 0; - - std::vector toSacrifice = sacrificedUnits; - for (int i = 0; i < toSacrifice.size(); i++) - { - if(toSacrifice[i]) - LOCPLINT->cb->trade(market->o, mode, i, 0, toSacrifice[i], hero); - } - - for(int& val : sacrificedUnits) - val = 0; - - for(CTradeableItem *t : items[0]) - { - t->setType(CREATURE_PLACEHOLDER); - t->subtitle = ""; - } - } - else - { - for(const CArtifactInstance *art : arts->artifactsOnAltar) //sacrifice each artifact on the list - { - LOCPLINT->cb->trade(market->o, mode, hero->getArtPos(art), -1, 1, hero); - } - arts->artifactsOnAltar.clear(); - - for(CTradeableItem *t : items[0]) - { - t->setID(-1); - t->subtitle = ""; - } - - arts->commonInfo->reset(); - //arts->scrollBackpack(0); - deal->block(true); - } - - calcTotalExp(); -} - -void CAltarWindow::SacrificeAll() -{ - if(mode == EMarketMode::CREATURE_EXP) - { - bool movedAnything = false; - for(CTradeableItem *t : items[1]) - sacrificedUnits[t->serial] = hero->getStackCount(SlotID(t->serial)); - - sacrificedUnits[items[1].front()->serial]--; - - for(CTradeableItem *t : items[0]) - { - updateRight(t); - if(t->type == CREATURE) - movedAnything = true; - } - - deal->block(!movedAnything); - calcTotalExp(); - } - else - { - for(auto i = hero->artifactsWorn.cbegin(); i != hero->artifactsWorn.cend(); i++) - { - if(i->second.artifact->artType->id != ArtifactID::ART_LOCK) //ignore locks from assembled artifacts - moveFromSlotToAltar(i->first, nullptr, i->second.artifact); - } - - SacrificeBackpack(); - } - redraw(); -} - -void CAltarWindow::selectionChanged(bool side) -{ - if(mode != EMarketMode::CREATURE_EXP) - return; - - CTradeableItem *&selected = side ? hLeft : hRight; - CTradeableItem *&theOther = side ? hRight : hLeft; - - theOther = *std::find_if(items[!side].begin(), items[!side].end(), [&](const CTradeableItem * item) - { - return item->serial == selected->serial; - }); - - int stackCount = 0; - for (int i = 0; i < GameConstants::ARMY_SIZE; i++) - if(hero->getStackCount(SlotID(i)) > sacrificedUnits[i]) - stackCount++; - - slider->setAmount(hero->getStackCount(SlotID(hLeft->serial)) - (stackCount == 1)); - slider->block(!slider->amount); - slider->value = sacrificedUnits[hLeft->serial]; - max->block(!slider->amount); - readyToTrade = true; - redraw(); -} - -void CAltarWindow::mimicCres() -{ - std::vector positions; - getPositionsFor(positions, false, CREATURE); - - for(CTradeableItem *t : items[1]) - { - auto hlp = new CTradeableItem(positions[t->serial].topLeft(), CREATURE_PLACEHOLDER, t->id, false, t->serial); - hlp->pos = positions[t->serial] + this->pos.topLeft(); - items[0].push_back(hlp); - } -} - -Point CAltarWindow::selectionOffset(bool Left) const -{ - if(Left) - return Point(150, 421); - else - return Point(396, 421); -} - -std::string CAltarWindow::selectionSubtitle(bool Left) const -{ - if(Left && slider && hLeft) - return boost::lexical_cast(slider->value); - else if(!Left && hRight) - return hRight->subtitle; - else - return ""; -} - -void CAltarWindow::artifactsChanged(bool left) -{ - -} - -void CAltarWindow::garrisonChanged() -{ - if(mode != EMarketMode::CREATURE_EXP) - return; - - std::set empty; - getEmptySlots(empty); - - for(CTradeableItem *t : empty) - { - removeItem(*std::find_if(items[0].begin(), items[0].end(), [&](const CTradeableItem * item) - { - return item->serial == t->serial; - })); - } - - initSubs(true); - getExpValues(); -} - -void CAltarWindow::getExpValues() -{ - int dump; - for(CTradeableItem *t : items[1]) - if(t->id >= 0) - market->getOffer(t->id, 0, dump, expPerUnit[t->serial], EMarketMode::CREATURE_EXP); -} - -void CAltarWindow::calcTotalExp() -{ - int val = 0; - if(mode == EMarketMode::CREATURE_EXP) - { - for (int i = 0; i < sacrificedUnits.size(); i++) - { - val += expPerUnit[i] * sacrificedUnits[i]; - } - } - else - { - for(const CArtifactInstance *art : arts->artifactsOnAltar) - { - int dmp, valOfArt; - market->getOffer(art->artType->id, 0, dmp, valOfArt, mode); - val += valOfArt; //WAS val += valOfArt * arts->artifactsOnAltar.count(*i); - } - } - val = hero->calculateXp(val); - expOnAltar->setText(boost::lexical_cast(val)); -} - -void CAltarWindow::setExpToLevel() -{ - expToLevel->setText(boost::lexical_cast(CGI->heroh->reqExp(CGI->heroh->level(hero->exp)+1) - hero->exp)); -} - -void CAltarWindow::blockTrade() -{ - hLeft = hRight = nullptr; - readyToTrade = false; - if(slider) - { - slider->block(true); - max->block(true); - } - deal->block(true); -} - -void CAltarWindow::updateRight(CTradeableItem *toUpdate) -{ - int val = sacrificedUnits[toUpdate->serial]; - toUpdate->setType(val ? CREATURE : CREATURE_PLACEHOLDER); - toUpdate->subtitle = val ? boost::str(boost::format(CGI->generaltexth->allTexts[122]) % boost::lexical_cast(val * expPerUnit[toUpdate->serial])) : ""; //%s exp -} - -int CAltarWindow::firstFreeSlot() -{ - int ret = -1; - while(items[0][++ret]->id >= 0 && ret + 1 < items[0].size()); - return ret < items[0].size() ? ret : -1; -} - -void CAltarWindow::SacrificeBackpack() -{ - std::multiset toOmmit = arts->artifactsOnAltar; - - for (auto & elem : hero->artifactsInBackpack) - { - - if(vstd::contains(toOmmit, elem.artifact)) - { - toOmmit -= elem.artifact; - continue; - } - - putOnAltar(nullptr, elem.artifact); - } - - arts->scrollBackpack(0); - calcTotalExp(); -} - -void CAltarWindow::artifactPicked() -{ - redraw(); -} - -void CAltarWindow::showAll(SDL_Surface * to) -{ - CTradeWindow::showAll(to); - if(mode == EMarketMode::ARTIFACT_EXP && arts && arts->commonInfo->src.art) - { - artIcon->setFrame(arts->commonInfo->src.art->artType->iconIndex); - artIcon->showAll(to); - - int dmp, val; - market->getOffer(arts->commonInfo->src.art->artType->id, 0, dmp, val, EMarketMode::ARTIFACT_EXP); - printAtMiddleLoc(boost::lexical_cast(val), 304, 498, FONT_SMALL, Colors::WHITE, to); - } -} - -bool CAltarWindow::putOnAltar(CTradeableItem* altarSlot, const CArtifactInstance *art) -{ - int artID = art->artType->id; - if(artID != 1 && artID < 7) //special art - { - logGlobal->warnStream() << "Cannot put special artifact on altar!"; - return false; - } - - if(!altarSlot) - { - int slotIndex = firstFreeSlot(); - if(slotIndex < 0) - { - logGlobal->warnStream() << "No free slots on altar!"; - return false; - } - altarSlot = items[0][slotIndex]; - } - - int dmp, val; - market->getOffer(artID, 0, dmp, val, EMarketMode::ARTIFACT_EXP); - - arts->artifactsOnAltar.insert(art); - altarSlot->setArtInstance(art); - altarSlot->subtitle = boost::lexical_cast(val); - - deal->block(false); - return true; -} - -void CAltarWindow::moveFromSlotToAltar(ArtifactPosition slotID, CTradeableItem* altarSlot, const CArtifactInstance *art) -{ - auto freeBackpackSlot = ArtifactPosition(hero->artifactsInBackpack.size() + GameConstants::BACKPACK_START); - if(arts->commonInfo->src.art) - { - arts->commonInfo->dst.slotID = freeBackpackSlot; - arts->commonInfo->dst.AOH = arts; - } - - if(putOnAltar(altarSlot, art)) - { - if(slotID < GameConstants::BACKPACK_START) - LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero, slotID), ArtifactLocation(hero, freeBackpackSlot)); - else - { - arts->commonInfo->src.clear(); - arts->commonInfo->dst.clear(); - CCS->curh->dragAndDropCursor(nullptr); - arts->unmarkSlots(false); - } - } -} - -void CSystemOptionsWindow::setMusicVolume( int newVolume ) -{ - Settings volume = settings.write["general"]["music"]; - volume->Float() = newVolume; -} - -void CSystemOptionsWindow::setSoundVolume( int newVolume ) -{ - Settings volume = settings.write["general"]["sound"]; - volume->Float() = newVolume; -} - -void CSystemOptionsWindow::setHeroMoveSpeed( int newSpeed ) -{ - Settings speed = settings.write["adventure"]["heroSpeed"]; - speed->Float() = newSpeed; -} - -void CSystemOptionsWindow::setMapScrollingSpeed( int newSpeed ) -{ - Settings speed = settings.write["adventure"]["scrollSpeed"]; - speed->Float() = newSpeed; -} - -CSystemOptionsWindow::CSystemOptionsWindow(): - CWindowObject(PLAYER_COLORED, "SysOpBck"), - onFullscreenChanged(settings.listen["video"]["fullscreen"]) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - title = new CLabel(242, 32, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[568]); - - const JsonNode & texts = CGI->generaltexth->localizedTexts["systemOptions"]; - - //left window section - leftGroup = new CLabelGroup(FONT_MEDIUM, CENTER, Colors::YELLOW); - leftGroup->add(122, 64, CGI->generaltexth->allTexts[569]); - leftGroup->add(122, 130, CGI->generaltexth->allTexts[570]); - leftGroup->add(122, 196, CGI->generaltexth->allTexts[571]); - leftGroup->add(122, 262, texts["resolutionButton"]["label"].String()); - leftGroup->add(122, 347, CGI->generaltexth->allTexts[394]); - leftGroup->add(122, 412, CGI->generaltexth->allTexts[395]); - - //right section - rightGroup = new CLabelGroup(FONT_MEDIUM, TOPLEFT, Colors::WHITE); - rightGroup->add(282, 57, CGI->generaltexth->allTexts[572]); - rightGroup->add(282, 89, CGI->generaltexth->allTexts[573]); - rightGroup->add(282, 121, CGI->generaltexth->allTexts[574]); - rightGroup->add(282, 153, CGI->generaltexth->allTexts[577]); - rightGroup->add(282, 185, texts["creatureWindowButton"]["label"].String()); - rightGroup->add(282, 217, texts["fullscreenButton"]["label"].String()); - - //setting up buttons - load = new CAdventureMapButton (CGI->generaltexth->zelp[321].first, CGI->generaltexth->zelp[321].second, - std::bind(&CSystemOptionsWindow::bloadf, this), 246, 298, "SOLOAD.DEF", SDLK_l); - load->swappedImages = true; - load->update(); - - save = new CAdventureMapButton (CGI->generaltexth->zelp[322].first, CGI->generaltexth->zelp[322].second, - std::bind(&CSystemOptionsWindow::bsavef, this), 357, 298, "SOSAVE.DEF", SDLK_s); - save->swappedImages = true; - save->update(); - - restart = new CAdventureMapButton (CGI->generaltexth->zelp[323].first, CGI->generaltexth->zelp[323].second, - std::bind(&CSystemOptionsWindow::brestartf, this), 246, 357, "SORSTRT", SDLK_r); - restart->swappedImages = true; - restart->update(); - - mainMenu = new CAdventureMapButton (CGI->generaltexth->zelp[320].first, CGI->generaltexth->zelp[320].second, - std::bind(&CSystemOptionsWindow::bmainmenuf, this), 357, 357, "SOMAIN.DEF", SDLK_m); - mainMenu->swappedImages = true; - mainMenu->update(); - - quitGame = new CAdventureMapButton (CGI->generaltexth->zelp[324].first, CGI->generaltexth->zelp[324].second, - std::bind(&CSystemOptionsWindow::bquitf, this), 246, 415, "soquit.def", SDLK_q); - quitGame->swappedImages = true; - quitGame->update(); - backToMap = new CAdventureMapButton (CGI->generaltexth->zelp[325].first, CGI->generaltexth->zelp[325].second, - std::bind(&CSystemOptionsWindow::breturnf, this), 357, 415, "soretrn.def", SDLK_RETURN); - backToMap->swappedImages = true; - backToMap->update(); - backToMap->assignedKeys.insert(SDLK_ESCAPE); - - heroMoveSpeed = new CHighlightableButtonsGroup(0); - heroMoveSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[349].second),CGI->generaltexth->zelp[349].second, "sysopb1.def", 28, 77, 1); - heroMoveSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[350].second),CGI->generaltexth->zelp[350].second, "sysopb2.def", 76, 77, 2); - heroMoveSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[351].second),CGI->generaltexth->zelp[351].second, "sysopb3.def", 124, 77, 4); - heroMoveSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[352].second),CGI->generaltexth->zelp[352].second, "sysopb4.def", 172, 77, 8); - heroMoveSpeed->select(settings["adventure"]["heroSpeed"].Float(), 1); - heroMoveSpeed->onChange = std::bind(&CSystemOptionsWindow::setHeroMoveSpeed, this, _1); - - mapScrollSpeed = new CHighlightableButtonsGroup(0); - mapScrollSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[357].second),CGI->generaltexth->zelp[357].second, "sysopb9.def", 28, 210, 1); - mapScrollSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[358].second),CGI->generaltexth->zelp[358].second, "sysob10.def", 92, 210, 2); - mapScrollSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[359].second),CGI->generaltexth->zelp[359].second, "sysob11.def", 156, 210, 4); - mapScrollSpeed->select(settings["adventure"]["scrollSpeed"].Float(), 1); - mapScrollSpeed->onChange = std::bind(&CSystemOptionsWindow::setMapScrollingSpeed, this, _1); - - musicVolume = new CHighlightableButtonsGroup(0, true); - for(int i=0; i<10; ++i) - musicVolume->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[326+i].second),CGI->generaltexth->zelp[326+i].second, "syslb.def", 29 + 19*i, 359, i*11); - - musicVolume->select(CCS->musich->getVolume(), 1); - musicVolume->onChange = std::bind(&CSystemOptionsWindow::setMusicVolume, this, _1); - - effectsVolume = new CHighlightableButtonsGroup(0, true); - for(int i=0; i<10; ++i) - effectsVolume->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[336+i].second),CGI->generaltexth->zelp[336+i].second, "syslb.def", 29 + 19*i, 425, i*11); - - effectsVolume->select(CCS->soundh->getVolume(), 1); - effectsVolume->onChange = std::bind(&CSystemOptionsWindow::setSoundVolume, this, _1); - - showReminder = new CHighlightableButton( - std::bind(&CSystemOptionsWindow::toggleReminder, this, true), std::bind(&CSystemOptionsWindow::toggleReminder, this, false), - std::map(), CGI->generaltexth->zelp[361].second, false, "sysopchk.def", nullptr, 246, 87, false); - - quickCombat = new CHighlightableButton( - std::bind(&CSystemOptionsWindow::toggleQuickCombat, this, true), std::bind(&CSystemOptionsWindow::toggleQuickCombat, this, false), - std::map(), CGI->generaltexth->zelp[362].second, false, "sysopchk.def", nullptr, 246, 87+32, false); - - spellbookAnim = new CHighlightableButton( - std::bind(&CSystemOptionsWindow::toggleSpellbookAnim, this, true), std::bind(&CSystemOptionsWindow::toggleSpellbookAnim, this, false), - std::map(), CGI->generaltexth->zelp[364].second, false, "sysopchk.def", nullptr, 246, 87+64, false); - - newCreatureWin = new CHighlightableButton( - std::bind(&CSystemOptionsWindow::toggleCreatureWin, this, true), std::bind(&CSystemOptionsWindow::toggleCreatureWin, this, false), - std::map(), texts["creatureWindowButton"]["help"].String(), false, "sysopchk.def", nullptr, 246, 183, false); - - fullscreen = new CHighlightableButton( - std::bind(&CSystemOptionsWindow::toggleFullscreen, this, true), std::bind(&CSystemOptionsWindow::toggleFullscreen, this, false), - std::map(), texts["fullscreenButton"]["help"].String(), false, "sysopchk.def", nullptr, 246, 215, false); - - showReminder->select(settings["adventure"]["heroReminder"].Bool()); - quickCombat->select(settings["adventure"]["quickCombat"].Bool()); - spellbookAnim->select(settings["video"]["spellbookAnimation"].Bool()); - newCreatureWin->select(settings["general"]["classicCreatureWindow"].Bool()); - fullscreen->select(settings["video"]["fullscreen"].Bool()); - - onFullscreenChanged([&](const JsonNode &newState){ fullscreen->select(newState.Bool());}); - - gameResButton = new CAdventureMapButton("", texts["resolutionButton"]["help"].String(), - std::bind(&CSystemOptionsWindow::selectGameRes, this), - 28, 275,"SYSOB12", SDLK_g); - - std::string resText; - resText += boost::lexical_cast(settings["video"]["screenRes"]["width"].Float()); - resText += "x"; - resText += boost::lexical_cast(settings["video"]["screenRes"]["height"].Float()); - gameResLabel = new CLabel(170, 292, FONT_MEDIUM, CENTER, Colors::YELLOW, resText); - -} - -void CSystemOptionsWindow::selectGameRes() -{ - std::vector items; - const JsonNode & texts = CGI->generaltexth->localizedTexts["systemOptions"]["resolutionMenu"]; - - for( config::CConfigHandler::GuiOptionsMap::value_type& value : conf.guiOptions) - { - std::string resX = boost::lexical_cast(value.first.first); - std::string resY = boost::lexical_cast(value.first.second); - items.push_back(resX + 'x' + resY); - } - - GH.pushInt(new CObjectListWindow(items, nullptr, texts["label"].String(), texts["help"].String(), - std::bind(&CSystemOptionsWindow::setGameRes, this, _1))); -} - -void CSystemOptionsWindow::setGameRes(int index) -{ - auto iter = conf.guiOptions.begin(); - std::advance(iter, index); - - //do not set resolution to illegal one (0x0) - assert(iter!=conf.guiOptions.end() && iter->first.first > 0 && iter->first.second > 0); - - Settings gameRes = settings.write["video"]["screenRes"]; - gameRes["width"].Float() = iter->first.first; - gameRes["height"].Float() = iter->first.second; - - std::string resText; - resText += boost::lexical_cast(iter->first.first); - resText += "x"; - resText += boost::lexical_cast(iter->first.second); - gameResLabel->setText(resText); -} - -void CSystemOptionsWindow::toggleReminder(bool on) -{ - Settings heroReminder = settings.write["adventure"]["heroReminder"]; - heroReminder->Bool() = on; -} - -void CSystemOptionsWindow::toggleQuickCombat(bool on) -{ - Settings quickCombat = settings.write["adventure"]["quickCombat"]; - quickCombat->Bool() = on; -} - -void CSystemOptionsWindow::toggleSpellbookAnim(bool on) -{ - Settings quickCombat = settings.write["video"]["spellbookAnimation"]; - quickCombat->Bool() = on; -} - -void CSystemOptionsWindow::toggleCreatureWin(bool on) -{ - Settings classicCreatureWindow = settings.write["general"]["classicCreatureWindow"]; - classicCreatureWindow->Bool() = on; -} - -void CSystemOptionsWindow::toggleFullscreen(bool on) -{ - Settings fullscreen = settings.write["video"]["fullscreen"]; - fullscreen->Bool() = on; -} - -void CSystemOptionsWindow::bquitf() -{ - LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this]{ closeAndPushEvent(SDL_QUIT); }, 0); -} - -void CSystemOptionsWindow::breturnf() -{ - GH.popIntTotally(this); -} - -void CSystemOptionsWindow::bmainmenuf() -{ - LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this]{ closeAndPushEvent(SDL_USEREVENT, RETURN_TO_MAIN_MENU); }, 0); -} - -void CSystemOptionsWindow::bloadf() -{ - GH.popIntTotally(this); - LOCPLINT->proposeLoadingGame(); -} - -void CSystemOptionsWindow::bsavef() -{ - GH.popIntTotally(this); - GH.pushInt(new CSavingScreen(CPlayerInterface::howManyPeople > 1)); -} - -void CSystemOptionsWindow::brestartf() -{ - LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [this]{ closeAndPushEvent(SDL_USEREVENT, RESTART_GAME); }, 0); -} - -void CSystemOptionsWindow::closeAndPushEvent(int eventType, int code /*= 0*/) -{ - GH.popIntTotally(this); - GH.pushSDLEvent(eventType, code); -} - -CTavernWindow::CTavernWindow(const CGObjectInstance *TavernObj): - CWindowObject(PLAYER_COLORED, "TPTAVERN"), - tavernObj(TavernObj) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - std::vector h = LOCPLINT->cb->getAvailableHeroes(TavernObj); - if(h.size() < 2) - h.resize(2, nullptr); - - h1 = new HeroPortrait(selected,0,72,299,h[0]); - h2 = new HeroPortrait(selected,1,162,299,h[1]); - - selected = 0; - if (!h[0]) - selected = 1; - if (!h[0] && !h[1]) - selected = -1; - oldSelected = -1; - - new CLabel(200, 35, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[37]); - new CLabel(320, 328, FONT_SMALL, CENTER, Colors::WHITE, "2500"); - new CTextBox(LOCPLINT->cb->getTavernGossip(tavernObj), Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE); - - new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); - cancel = new CAdventureMapButton(CGI->generaltexth->tavernInfo[7],"", std::bind(&CTavernWindow::close, this), 310, 428, "ICANCEL.DEF", SDLK_ESCAPE); - recruit = new CAdventureMapButton("", "", std::bind(&CTavernWindow::recruitb, this), 272, 355, "TPTAV01.DEF", SDLK_RETURN); - thiefGuild = new CAdventureMapButton(CGI->generaltexth->tavernInfo[5],"", std::bind(&CTavernWindow::thievesguildb, this), 22, 428, "TPTAV02.DEF", SDLK_t); - - if(LOCPLINT->cb->getResourceAmount(Res::GOLD) < 2500) //not enough gold - { - recruit->hoverTexts[0] = CGI->generaltexth->tavernInfo[0]; //Cannot afford a Hero - recruit->block(true); - } - else if(LOCPLINT->castleInt && LOCPLINT->cb->howManyHeroes(true) >= VLC->modh->settings.MAX_HEROES_AVAILABLE_PER_PLAYER) - { - recruit->hoverTexts[0] = CGI->generaltexth->tavernInfo[1]; //Cannot recruit. You already have %d Heroes. - boost::algorithm::replace_first(recruit->hoverTexts[0],"%d",boost::lexical_cast(LOCPLINT->cb->howManyHeroes(true))); - recruit->block(true); - } - else if((!LOCPLINT->castleInt) && LOCPLINT->cb->howManyHeroes(false) >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER) - { - recruit->hoverTexts[0] = CGI->generaltexth->tavernInfo[1]; //Cannot recruit. You already have %d Heroes. - boost::algorithm::replace_first(recruit->hoverTexts[0], "%d", boost::lexical_cast(LOCPLINT->cb->howManyHeroes(false))); - recruit->block(true); - } - else if(LOCPLINT->castleInt && LOCPLINT->castleInt->town->visitingHero) - { - recruit->hoverTexts[0] = CGI->generaltexth->tavernInfo[2]; //Cannot recruit. You already have a Hero in this town. - recruit->block(true); - } - else - { - if(selected == -1) - recruit->block(true); - } - if (LOCPLINT->castleInt) - CCS->videoh->open(LOCPLINT->castleInt->town->town->clientInfo.tavernVideo); - else - CCS->videoh->open("TAVERN.BIK"); -} - -void CTavernWindow::recruitb() -{ - const CGHeroInstance *toBuy = (selected ? h2 : h1)->h; - const CGObjectInstance *obj = tavernObj; - close(); - LOCPLINT->cb->recruitHero(obj, toBuy); -} - -void CTavernWindow::thievesguildb() -{ - GH.pushInt( new CThievesGuildWindow(tavernObj) ); -} - -CTavernWindow::~CTavernWindow() -{ - CCS->videoh->close(); -} - -void CTavernWindow::show(SDL_Surface * to) -{ - CWindowObject::show(to); - - CCS->videoh->update(pos.x+70, pos.y+56, to, true, false); - if(selected >= 0) - { - HeroPortrait *sel = selected ? h2 : h1; - - if (selected != oldSelected && !recruit->isBlocked()) - { - // Selected hero just changed. Update RECRUIT button hover text if recruitment is allowed. - oldSelected = selected; - - recruit->hoverTexts[0] = CGI->generaltexth->tavernInfo[3]; //Recruit %s the %s - boost::algorithm::replace_first(recruit->hoverTexts[0],"%s",sel->h->name); - boost::algorithm::replace_first(recruit->hoverTexts[0],"%s",sel->h->type->heroClass->name); - } - - printAtMiddleWBLoc(sel->description, 146, 395, FONT_SMALL, 200, Colors::WHITE, to); - CSDL_Ext::drawBorder(to,sel->pos.x-2,sel->pos.y-2,sel->pos.w+4,sel->pos.h+4,int3(247,223,123)); - } -} - -void CTavernWindow::HeroPortrait::clickLeft(tribool down, bool previousState) -{ - if(previousState && !down && h) - *_sel = _id; -} - -void CTavernWindow::HeroPortrait::clickRight(tribool down, bool previousState) -{ - if(down && h) - { - GH.pushInt(new CRClickPopupInt(new CHeroWindow(h), true)); - } -} - -CTavernWindow::HeroPortrait::HeroPortrait(int &sel, int id, int x, int y, const CGHeroInstance *H) -: h(H), _sel(&sel), _id(id) -{ - addUsedEvents(LCLICK | RCLICK | HOVER); - OBJ_CONSTRUCTION_CAPTURING_ALL; - h = H; - pos.x += x; - pos.y += y; - pos.w = 58; - pos.h = 64; - - if(H) - { - hoverName = CGI->generaltexth->tavernInfo[4]; - boost::algorithm::replace_first(hoverName,"%s",H->name); - - int artifs = h->artifactsWorn.size() + h->artifactsInBackpack.size(); - for(int i=13; i<=17; i++) //war machines and spellbook don't count - if(vstd::contains(h->artifactsWorn, ArtifactPosition(i))) - artifs--; - - description = CGI->generaltexth->allTexts[215]; - boost::algorithm::replace_first(description, "%s", h->name); - boost::algorithm::replace_first(description, "%d", boost::lexical_cast(h->level)); - boost::algorithm::replace_first(description, "%s", h->type->heroClass->name); - boost::algorithm::replace_first(description, "%d", boost::lexical_cast(artifs)); - - new CAnimImage("portraitsLarge", h->portrait); - } -} - -void CTavernWindow::HeroPortrait::hover( bool on ) -{ - //Hoverable::hover(on); - if(on) - GH.statusbar->setText(hoverName); - else - GH.statusbar->clear(); -} - -void CInGameConsole::show(SDL_Surface * to) -{ - int number = 0; - - std::vector >::iterator> toDel; - - boost::unique_lock lock(texts_mx); - for(auto it = texts.begin(); it != texts.end(); ++it, ++number) - { - Point leftBottomCorner(0, screen->h); - if(LOCPLINT->battleInt) - { - leftBottomCorner = LOCPLINT->battleInt->pos.bottomLeft(); - } - graphics->fonts[FONT_MEDIUM]->renderTextLeft(to, it->first, Colors::GREEN, - Point(leftBottomCorner.x + 50, leftBottomCorner.y - texts.size() * 20 - 80 + number*20)); - - if(SDL_GetTicks() - it->second > defaultTimeout) - { - toDel.push_back(it); - } - } - - for(auto & elem : toDel) - { - texts.erase(elem); - } -} - -void CInGameConsole::print(const std::string &txt) -{ - boost::unique_lock lock(texts_mx); - int lineLen = conf.go()->ac.outputLineLength; - - if(txt.size() < lineLen) - { - texts.push_back(std::make_pair(txt, SDL_GetTicks())); - if(texts.size() > maxDisplayedTexts) - { - texts.pop_front(); - } - } - else - { - assert(lineLen); - for(int g=0; g maxDisplayedTexts) - { - texts.pop_front(); - } - } - } -} - -void CInGameConsole::keyPressed (const SDL_KeyboardEvent & key) -{ - if(key.type != SDL_KEYDOWN) return; - - if(!captureAllKeys && key.keysym.sym != SDLK_TAB) return; //because user is not entering any text - - switch(key.keysym.sym) - { - case SDLK_TAB: - case SDLK_ESCAPE: - { - if(captureAllKeys) - { - captureAllKeys = false; - endEnteringText(false); - } - else if(SDLK_TAB) - { - captureAllKeys = true; - startEnteringText(); - } - break; - } - case SDLK_RETURN: //enter key - { - if(enteredText.size() > 0 && captureAllKeys) - { - captureAllKeys = false; - endEnteringText(true); - CCS->soundh->playSound("CHAT"); - } - break; - } - case SDLK_BACKSPACE: - { - if(enteredText.size() > 1) - { - Unicode::trimRight(enteredText,2); - enteredText += '_'; - refreshEnteredText(); - } - break; - } - case SDLK_UP: //up arrow - { - if(previouslyEntered.size() == 0) - break; - - if(prevEntDisp == -1) - { - prevEntDisp = previouslyEntered.size() - 1; - enteredText = previouslyEntered[prevEntDisp] + "_"; - refreshEnteredText(); - } - else if( prevEntDisp > 0) - { - --prevEntDisp; - enteredText = previouslyEntered[prevEntDisp] + "_"; - refreshEnteredText(); - } - break; - } - case SDLK_DOWN: //down arrow - { - if(prevEntDisp != -1 && prevEntDisp+1 < previouslyEntered.size()) - { - ++prevEntDisp; - enteredText = previouslyEntered[prevEntDisp] + "_"; - refreshEnteredText(); - } - else if(prevEntDisp+1 == previouslyEntered.size()) //useful feature - { - prevEntDisp = -1; - enteredText = "_"; - refreshEnteredText(); - } - break; - } - default: - { - #ifdef VCMI_SDL1 - if(enteredText.size() > 0 && enteredText.size() < conf.go()->ac.inputLineLength) - { - if( key.keysym.unicode < 0x80 && key.keysym.unicode > 0 ) - { - enteredText[enteredText.size()-1] = (char)key.keysym.unicode; - enteredText += "_"; - refreshEnteredText(); - } - } - #endif // VCMI_SDL1 - break; - } - } -} - -#ifndef VCMI_SDL1 - -void CInGameConsole::textInputed(const SDL_TextInputEvent & event) -{ - if(!captureAllKeys || enteredText.size() == 0) - return; - enteredText.resize(enteredText.size()-1); - - enteredText += event.text; - enteredText += "_"; - - refreshEnteredText(); -} - -void CInGameConsole::textEdited(const SDL_TextEditingEvent & event) -{ - //do nothing here -} - -#endif // VCMI_SDL1 - -void CInGameConsole::startEnteringText() -{ - CSDL_Ext::startTextInput(&pos); - - enteredText = "_"; - if(GH.topInt() == adventureInt) - { - GH.statusbar->alignment = TOPLEFT; - GH.statusbar->setText(enteredText); - - //Prevent changes to the text from mouse interaction with the adventure map - GH.statusbar->lock(true); - } - else if(LOCPLINT->battleInt) - { - LOCPLINT->battleInt->console->ingcAlter = enteredText; - } -} - -void CInGameConsole::endEnteringText(bool printEnteredText) -{ - CSDL_Ext::stopTextInput(); - - prevEntDisp = -1; - if(printEnteredText) - { - std::string txt = enteredText.substr(0, enteredText.size()-1); - LOCPLINT->cb->sendMessage(txt); - previouslyEntered.push_back(txt); - //print(txt); - } - enteredText = ""; - if(GH.topInt() == adventureInt) - { - GH.statusbar->alignment = CENTER; - GH.statusbar->lock(false); - GH.statusbar->clear(); - } - else if(LOCPLINT->battleInt) - { - LOCPLINT->battleInt->console->ingcAlter = ""; - } -} - -void CInGameConsole::refreshEnteredText() -{ - if(GH.topInt() == adventureInt) - { - GH.statusbar->lock(false); - GH.statusbar->clear(); - GH.statusbar->setText(enteredText); - GH.statusbar->lock(true); - } - else if(LOCPLINT->battleInt) - { - LOCPLINT->battleInt->console->ingcAlter = enteredText; - } -} - -CInGameConsole::CInGameConsole() : prevEntDisp(-1), defaultTimeout(10000), maxDisplayedTexts(10) -{ - #ifdef VCMI_SDL1 - addUsedEvents(KEYBOARD); - #else - addUsedEvents(KEYBOARD | TEXTINPUT); - #endif -} - -CGarrisonWindow::CGarrisonWindow( const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits ): - CWindowObject(PLAYER_COLORED, "GARRISON") -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - garr = new CGarrisonInt(92, 127, 4, Point(0,96), background->bg, Point(93,127), up, down, removableUnits); - { - CAdventureMapButton *split = new CAdventureMapButton(CGI->generaltexth->tcommands[3],"",std::bind(&CGarrisonInt::splitClick,garr),88,314,"IDV6432.DEF"); - removeChild(split); - garr->addSplitBtn(split); - } - quit = new CAdventureMapButton(CGI->generaltexth->tcommands[8],"",std::bind(&CGarrisonWindow::close,this),399,314,"IOK6432.DEF",SDLK_RETURN); - - std::string titleText; - if (garr->armedObjs[1]->tempOwner == garr->armedObjs[0]->tempOwner) - titleText = CGI->generaltexth->allTexts[709]; - else - { - titleText = CGI->generaltexth->allTexts[35]; - boost::algorithm::replace_first(titleText, "%s", garr->armedObjs[0]->Slots().begin()->second->type->namePl); - } - new CLabel(275, 30, FONT_BIG, CENTER, Colors::YELLOW, titleText); - - new CAnimImage("CREST58", garr->armedObjs[0]->getOwner().getNum(), 0, 28, 124); - new CAnimImage("PortraitsLarge", dynamic_cast(garr->armedObjs[1])->portrait, 0, 29, 222); -} - - -IShowActivatable::IShowActivatable() -{ - type = 0; -} - -CGarrisonHolder::CGarrisonHolder() -{ -} - -void CWindowWithGarrison::updateGarrisons() -{ - garr->recreateSlots(); -} - -CArtPlace::CArtPlace(Point position, const CArtifactInstance * Art): - locked(false), picked(false), marked(false), ourArt(Art) -{ - pos += position; - pos.w = pos.h = 44; - createImage(); -} - -void CArtPlace::createImage() -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - int graphic = 0; - if (ourArt) - graphic = ourArt->artType->iconIndex; - if (locked) - graphic = ArtifactID::ART_LOCK; - - image = new CAnimImage("artifact", graphic); - if (!ourArt) - image->disable(); - - selection = new CAnimImage("artifact", ArtifactID::ART_SELECTION); - selection->disable(); -} - -void CArtPlace::lockSlot(bool on) -{ - if (locked == on) - return; - - locked = on; - - if (on) - image->setFrame(ArtifactID::ART_LOCK); - else - image->setFrame(ourArt->artType->iconIndex); -} - -void CArtPlace::pickSlot(bool on) -{ - if (picked == on) - return; - - picked = on; - if (on) - image->disable(); - else - image->enable(); -} - -void CArtPlace::selectSlot(bool on) -{ - if (marked == on) - return; - - marked = on; - if (on) - selection->enable(); - else - selection->disable(); -} - -void CArtPlace::clickLeft(tribool down, bool previousState) -{ - //LRClickableAreaWTextComp::clickLeft(down); - bool inBackpack = slotID >= GameConstants::BACKPACK_START, - srcInBackpack = ourOwner->commonInfo->src.slotID >= GameConstants::BACKPACK_START, - srcInSameHero = ourOwner->commonInfo->src.AOH == ourOwner; - - if(ourOwner->highlightModeCallback && ourArt) - { - if(down) - { - if(ourArt->artType->id < 7) //War Machine or Spellbook - { - LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21]); //This item can't be traded. - } - else - { - ourOwner->unmarkSlots(false); - selectSlot(true); - ourOwner->highlightModeCallback(this); - } - } - return; - } - - // If clicked on spellbook, open it only if no artifact is held at the moment. - if(ourArt && !down && previousState && !ourOwner->commonInfo->src.AOH) - { - if(ourArt->artType->id == 0) - { - auto spellWindow = new CSpellWindow(genRect(595, 620, (screen->w - 620)/2, (screen->h - 595)/2), ourOwner->curHero, LOCPLINT, LOCPLINT->battleInt); - GH.pushInt(spellWindow); - } - } - - if (!down && previousState) - { - if(ourArt && ourArt->artType->id == 0) //spellbook - return; //this is handled separately - - if(!ourOwner->commonInfo->src.AOH) //nothing has been clicked - { - if(ourArt //to prevent selecting empty slots (bugfix to what GrayFace reported) - && ourOwner->curHero->tempOwner == LOCPLINT->playerID)//can't take art from another player - { - if(ourArt->artType->id == 3) //catapult cannot be highlighted - { - std::vector catapult(1, new CComponent(CComponent::artifact, 3, 0)); - LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[312], catapult); //The Catapult must be equipped. - return; - } - select(); - } - } - else if(ourArt == ourOwner->commonInfo->src.art) //restore previously picked artifact - { - deselect(); - } - else //perform artifact transition - { - if(inBackpack) // Backpack destination. - { - if(srcInBackpack && slotID == ourOwner->commonInfo->src.slotID + 1) //next slot (our is not visible, so visually same as "old" place) to the art -> make nothing, return artifact to slot - { - deselect(); - } - else - { - const CArtifact * const cur = ourOwner->commonInfo->src.art->artType; - - switch(cur->id) - { - case ArtifactID::CATAPULT: - //should not happen, catapult cannot be selected - assert(cur->id != ArtifactID::CATAPULT); - break; - case ArtifactID::BALLISTA: case ArtifactID::AMMO_CART: case ArtifactID::FIRST_AID_TENT: //war machines cannot go to backpack - LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->Name())); - break; - default: - setMeAsDest(); - vstd::amin(ourOwner->commonInfo->dst.slotID, ArtifactPosition( - ourOwner->curHero->artifactsInBackpack.size() + GameConstants::BACKPACK_START)); - if(srcInBackpack && srcInSameHero) - { - if(!ourArt //cannot move from backpack to AFTER backpack -> combined with vstd::amin above it will guarantee that dest is at most the last artifact - || ourOwner->commonInfo->src.slotID < ourOwner->commonInfo->dst.slotID) //rearranging arts in backpack after taking src artifact, the dest id will be shifted - vstd::advance(ourOwner->commonInfo->dst.slotID, -1); - } - if(srcInSameHero && ourOwner->commonInfo->dst.slotID == ourOwner->commonInfo->src.slotID) //we came to src == dst - deselect(); - else - ourOwner->realizeCurrentTransaction(); - break; - } - } - } - //check if swap is possible - else if (fitsHere(ourOwner->commonInfo->src.art) && - (!ourArt || ourOwner->curHero->tempOwner == LOCPLINT->playerID)) - { - setMeAsDest(); -// -// // Special case when the dest artifact can't be fit into the src slot. -// //CGI->arth->unequipArtifact(ourOwner->curHero->artifWorn, slotID); -// const CArtifactsOfHero* srcAOH = ourOwner->commonInfo->src.AOH; -// ui16 srcSlotID = ourOwner->commonInfo->src.slotID; -// if (ourArt && srcSlotID < 19 && !ourArt->canBePutAt(ArtifactLocation(srcAOH->curHero, srcSlotID))) -// { -// // Put dest artifact into owner's backpack. -// ourOwner->commonInfo->src.AOH = ourOwner; -// ourOwner->commonInfo->src.slotID = ourOwner->curHero->artifacts.size() + 19; -// } - - ourOwner->realizeCurrentTransaction(); - } - } - } -} - -void CArtPlace::clickRight(tribool down, bool previousState) -{ - if(down && ourArt && !locked && text.size() && !picked) //if there is no description or it's a lock, do nothing ;] - { - if (slotID < GameConstants::BACKPACK_START) - { - if(ourOwner->allowedAssembling) - { - std::vector assemblyPossibilities = ourArt->assemblyPossibilities(ourOwner->curHero); - - // If the artifact can be assembled, display dialog. - for(const CArtifact *combination : assemblyPossibilities) - { - LOCPLINT->showArtifactAssemblyDialog( - ourArt->artType->id, - combination->id, - true, - std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), ourOwner->curHero, slotID, true, combination->id), - 0); - - if(assemblyPossibilities.size() > 2) - { - logGlobal->warnStream() << "More than one possibility of assembling... taking only first"; - break; - } - return; - } - - // Otherwise if the artifact can be diasassembled, display dialog. - if(ourArt->canBeDisassembled()) - { - LOCPLINT->showArtifactAssemblyDialog( - ourArt->artType->id, - 0, - false, - std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), ourOwner->curHero, slotID, false, ArtifactID()), - 0); - return; - } - } - } - - // Lastly just show the artifact description. - LRClickableAreaWTextComp::clickRight(down, previousState); - } -} - -/** - * Selects artifact slot so that the containing artifact looks like it's picked up. - */ -void CArtPlace::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++) - { - CArtPlace *ap = ourOwner->getArtPlace(i); - ap->pickSlot(ourArt->isPart(ap->ourArt)); - } - } - - //int backpackCorrection = -(slotID - Arts::BACKPACK_START < ourOwner->backpackPos); - - CCS->curh->dragAndDropCursor(new CAnimImage("artifact", ourArt->artType->iconIndex)); - ourOwner->commonInfo->src.setTo(this, false); - ourOwner->markPossibleSlots(ourArt); - - if(slotID >= GameConstants::BACKPACK_START) - ourOwner->scrollBackpack(0); //will update slots - - ourOwner->updateParentWindow(); - ourOwner->safeRedraw(); -} - -/** - * Deselects the artifact slot. FIXME: Not used. Maybe it should? - */ -void CArtPlace::deselect () -{ - pickSlot(false); - if(ourArt && ourArt->canBeDisassembled()) //combined art returned to its slot -> restore locks - { - for(int i = 0; i < GameConstants::BACKPACK_START; i++) - ourOwner->getArtPlace(i)->pickSlot(false); - } - - CCS->curh->dragAndDropCursor(nullptr); - ourOwner->unmarkSlots(); - ourOwner->commonInfo->src.clear(); - if(slotID >= GameConstants::BACKPACK_START) - ourOwner->scrollBackpack(0); //will update slots - - - ourOwner->updateParentWindow(); - ourOwner->safeRedraw(); -} - -void CArtPlace::showAll(SDL_Surface * to) -{ - if (ourArt && !picked && ourArt == ourOwner->curHero->getArt(slotID, false)) //last condition is needed for disassembling -> artifact may be gone, but we don't know yet TODO: real, nice solution - { - CIntObject::showAll(to); - } - - if(marked && active) - { - // Draw vertical bars. - for (int i = 0; i < pos.h; ++i) - { - CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x, pos.y + i, 240, 220, 120); - CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x + pos.w - 1, pos.y + i, 240, 220, 120); - } - - // Draw horizontal bars. - for (int i = 0; i < pos.w; ++i) - { - CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x + i, pos.y, 240, 220, 120); - CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x + i, pos.y + pos.h - 1, 240, 220, 120); - } - } -} - -bool CArtPlace::fitsHere(const CArtifactInstance * art) const -{ - // You can place 'no artifact' anywhere. - if(!art) - return true; - - // Anything can but War Machines can be placed in backpack. - if (slotID >= GameConstants::BACKPACK_START) - return !CGI->arth->isBigArtifact(art->artType->id); - - return art->canBePutAt(ArtifactLocation(ourOwner->curHero, slotID), true); -} - -void CArtPlace::setMeAsDest(bool backpackAsVoid /*= true*/) -{ - ourOwner->commonInfo->dst.setTo(this, backpackAsVoid); -} - -void CArtPlace::setArtifact(const CArtifactInstance *art) -{ - baseType = -1; //by default we don't store any component - ourArt = art; - if(!art) - { - image->disable(); - text = std::string(); - hoverText = CGI->generaltexth->allTexts[507]; - } - else - { - image->enable(); - image->setFrame(locked ? ArtifactID::ART_LOCK : art->artType->iconIndex); - - std::string artDesc = ourArt->artType->Description(); - if (vstd::contains (artDesc, '{')) - text = artDesc; - else - text = '{' + ourArt->artType->Name() + "}\n\n" + artDesc; //workaround for new artifacts with single name, turns it to H3-style - - if(art->artType->id == 1) //spell scroll - { - // we expect scroll description to be like this: This scroll contains the [spell name] spell which is added into your spell book for as long as you carry the scroll. - // so we want to replace text in [...] with a spell name - // however other language versions don't have name placeholder at all, so we have to be careful - int spellID = art->getGivenSpellID(); - size_t nameStart = text.find_first_of('['); - size_t nameEnd = text.find_first_of(']', nameStart); - if(spellID >= 0) - { - if(nameStart != std::string::npos && nameEnd != std::string::npos) - text = text.replace(nameStart, nameEnd - nameStart + 1, CGI->spellh->objects[spellID]->name); - - //add spell component info (used to provide a pic in r-click popup) - baseType = CComponent::spell; - type = spellID; - bonusValue = 0; - } - } - else - { - baseType = CComponent::artifact; - type = art->artType->id; - bonusValue = 0; - } - if (art->artType->constituents) //display info about components of combined artifact - { - //TODO - } - else if (art->artType->constituentOf.size()) //display info about set - { - std::string artList; - auto combinedArt = art->artType->constituentOf[0]; - text += "\n\n"; - text += "{" + combinedArt->Name() + "}"; - int wornArtifacts = 0; - for (auto a : *combinedArt->constituents) //TODO: can the artifact be a part of more than one set? - { - artList += "\n" + a->Name(); - if (ourOwner->curHero->hasArt(a->id, true)) - wornArtifacts++; - } - text += " (" + boost::str(boost::format("%d") % wornArtifacts) + " / " + - boost::str(boost::format("%d") % combinedArt->constituents->size()) + ")" + artList; - //TODO: fancy colors and fonts for this text - } - - if (locked) // Locks should appear as empty. - hoverText = CGI->generaltexth->allTexts[507]; - else - hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->Name()); - } -} - -void LRClickableAreaWTextComp::clickLeft(tribool down, bool previousState) -{ - if((!down) && previousState) - { - std::vector comp(1, createComponent()); - LOCPLINT->showInfoDialog(text, comp); - } -} - -LRClickableAreaWTextComp::LRClickableAreaWTextComp(const Rect &Pos, int BaseType) - : LRClickableAreaWText(Pos), baseType(BaseType), bonusValue(-1) -{ -} - -CComponent * LRClickableAreaWTextComp::createComponent() const -{ - if(baseType >= 0) - return new CComponent(CComponent::Etype(baseType), type, bonusValue); - else - return nullptr; -} - -void LRClickableAreaWTextComp::clickRight(tribool down, bool previousState) -{ - if(down) - { - if(CComponent *comp = createComponent()) - { - CRClickPopup::createAndPush(text, CInfoWindow::TCompsInfo(1, comp)); - return; - } - } - - LRClickableAreaWText::clickRight(down, previousState); //only if with-component variant not occurred -} - -CHeroArea::CHeroArea(int x, int y, const CGHeroInstance * _hero):hero(_hero) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - addUsedEvents(LCLICK | RCLICK | HOVER); - pos.x += x; pos.w = 58; - pos.y += y; pos.h = 64; - - if (hero) - new CAnimImage("PortraitsLarge", hero->portrait); -} - -void CHeroArea::clickLeft(tribool down, bool previousState) -{ - if((!down) && previousState && hero) - LOCPLINT->openHeroWindow(hero); -} - -void CHeroArea::clickRight(tribool down, bool previousState) -{ - if((!down) && previousState && hero) - LOCPLINT->openHeroWindow(hero); -} - -void CHeroArea::hover(bool on) -{ - if (on && hero) - GH.statusbar->setText(hero->getObjectName()); - else - GH.statusbar->clear(); -} - -void LRClickableAreaOpenTown::clickLeft(tribool down, bool previousState) -{ - if((!down) && previousState && town) - { - LOCPLINT->openTownWindow(town); - if ( type == 2 ) - LOCPLINT->castleInt->builds->buildingClicked(BuildingID::VILLAGE_HALL); - else if ( type == 3 && town->fortLevel() ) - LOCPLINT->castleInt->builds->buildingClicked(BuildingID::FORT); - } -} - -void LRClickableAreaOpenTown::clickRight(tribool down, bool previousState) -{ - if((!down) && previousState && town) - LOCPLINT->openTownWindow(town);//TODO: popup? -} - -LRClickableAreaOpenTown::LRClickableAreaOpenTown() - : LRClickableAreaWTextComp(Rect(0,0,0,0), -1) -{ -} - -void CArtifactsOfHero::SCommonPart::reset() -{ - src.clear(); - dst.clear(); - CCS->curh->dragAndDropCursor(nullptr); -} - -void CArtifactsOfHero::setHero(const CGHeroInstance * hero) -{ -// // An update is made, rather than initialization. -// if (curHero && curHero->id == hero->id) -// { -// if(curHero != hero) -// { -// //delete curHero; -// curHero = hero; //was: creating a copy -// } -// -// // Compensate backpack pos if an artifact was insertad before it. -// if (commonInfo->dst.slotID >= 19 && commonInfo->destAOH == this -// && commonInfo->dst.slotID - 19 < backpackPos) -// { -// backpackPos++; -// } -// -// if (updateState && commonInfo->srcAOH == this) -// { -// // A swap was made, make the replaced artifact the current selected. -// if (commonInfo->dst.slotID < 19 && commonInfo->destArtifact) -// { -// // // Temporarily remove artifact from hero. -// // if (commonInfo->srcSlotID < 19) -// // CGI->arth->unequipArtifact(curHero->artifWorn, commonInfo->srcSlotID); -// // else -// // curHero->artifacts.erase(curHero->artifacts.begin() + (commonInfo->srcSlotID - 19)); -// -// updateParentWindow(); //TODO: evil! but does the thing -// -// // Source <- Dest -// commonInfo->srcArtifact = commonInfo->destArtifact; -// -// // Reset destination parameters. -// commonInfo->dst.clear(); -// -// CCS->curh->dragAndDropCursor(graphics->artDefs->ourImages[commonInfo->srcArtifact->id].bitmap); -// markPossibleSlots(commonInfo->srcArtifact); -// } -// else if (commonInfo->destAOH != nullptr) -// { -// // Reset all parameters. -// commonInfo->reset(); -// unmarkSlots(); -// } -// } -// } -// else -// { -// commonInfo->reset(); -// } -// -// if(hero != curHero) -// { -// // delete curHero; -// // curHero = new CGHeroInstance(*hero); -// curHero = hero; //was: creating a copy -// } - - curHero = hero; - if (curHero->artifactsInBackpack.size() > 0) - backpackPos %= curHero->artifactsInBackpack.size(); - else - backpackPos = 0; - - // Fill the slots for worn artifacts and backpack. - for (int g = 0; g < artWorn.size() ; g++) - setSlotData(artWorn[g], ArtifactPosition(g)); - scrollBackpack(0); -} - -void CArtifactsOfHero::dispose() -{ - //vstd::clear_pointer(curHero); - //unmarkSlots(false); - CCS->curh->dragAndDropCursor(nullptr); -} - -void CArtifactsOfHero::scrollBackpack(int dir) -{ - int artsInBackpack = curHero->artifactsInBackpack.size(); - backpackPos += dir; - if(backpackPos < 0)// No guarantee of modulus behavior with negative operands -> we keep it positive - backpackPos += artsInBackpack; - - if(artsInBackpack) - backpackPos %= artsInBackpack; - - std::multiset toOmit = artifactsOnAltar; - if(commonInfo->src.art) //if we picked an art from backapck, its slot has to be omitted - toOmit.insert(commonInfo->src.art); - - int omitedSoFar = 0; - - //set new data - size_t s = 0; - for( ; s < artsInBackpack; ++s) - { - - if (s < artsInBackpack) - { - auto slotID = ArtifactPosition(GameConstants::BACKPACK_START + (s + backpackPos)%artsInBackpack); - const CArtifactInstance *art = curHero->getArt(slotID); - assert(art); - if(!vstd::contains(toOmit, art)) - { - if(s - omitedSoFar < backpack.size()) - setSlotData(backpack[s-omitedSoFar], slotID); - } - else - { - toOmit -= art; - omitedSoFar++; - continue; - } - } - } - for( ; s - omitedSoFar < backpack.size(); s++) - eraseSlotData(backpack[s-omitedSoFar], ArtifactPosition(GameConstants::BACKPACK_START + s)); - - //in artifact merchant selling artifacts we may have highlight on one of backpack artifacts -> market needs update, cause artifact under highlight changed - if(highlightModeCallback) - { - for(auto & elem : backpack) - { - if(elem->marked) - { - highlightModeCallback(elem); - break; - } - } - } - - //blocking scrolling if there is not enough artifacts to scroll - bool scrollingPossible = artsInBackpack - omitedSoFar > backpack.size(); - leftArtRoll->block(!scrollingPossible); - rightArtRoll->block(!scrollingPossible); - - safeRedraw(); - -} - -/** - * Marks possible slots where a given artifact can be placed, except backpack. - * - * @param art Artifact checked against. - */ -void CArtifactsOfHero::markPossibleSlots(const CArtifactInstance* art) -{ - for(CArtifactsOfHero *aoh : commonInfo->participants) - for(CArtPlace *place : aoh->artWorn) - place->selectSlot(art->canBePutAt(ArtifactLocation(aoh->curHero, place->slotID), true)); - - safeRedraw(); -} - -/** - * Unamarks all slots. - */ -void CArtifactsOfHero::unmarkSlots(bool withRedraw /*= true*/) -{ - if(commonInfo) - for(CArtifactsOfHero *aoh : commonInfo->participants) - aoh->unmarkLocalSlots(false); - else - unmarkLocalSlots(false);\ - - if(withRedraw) - safeRedraw(); -} - -void CArtifactsOfHero::unmarkLocalSlots(bool withRedraw /*= true*/) -{ - for(CArtPlace *place : artWorn) - place->selectSlot(false); - for(CArtPlace *place : backpack) - place->selectSlot(false); - - if(withRedraw) - safeRedraw(); -} - -/** - * Assigns an artifacts to an artifact place depending on it's new slot ID. - */ -void CArtifactsOfHero::setSlotData(CArtPlace* artPlace, ArtifactPosition slotID) -{ - if(!artPlace && slotID >= GameConstants::BACKPACK_START) //spurious call from artifactMoved in attempt to update hidden backpack slot - { - return; - } - - artPlace->pickSlot(false); - artPlace->slotID = slotID; - - if(const ArtSlotInfo *asi = curHero->getSlot(slotID)) - { - artPlace->setArtifact(asi->artifact); - artPlace->lockSlot(asi->locked); - } - else - artPlace->setArtifact(nullptr); -} - -/** - * Makes given artifact slot appear as empty with a certain slot ID. - */ -void CArtifactsOfHero::eraseSlotData (CArtPlace* artPlace, ArtifactPosition slotID) -{ - artPlace->pickSlot(false); - artPlace->slotID = slotID; - artPlace->setArtifact(nullptr); -} - -CArtifactsOfHero::CArtifactsOfHero(std::vector ArtWorn, std::vector Backpack, - CAdventureMapButton *leftScroll, CAdventureMapButton *rightScroll, bool createCommonPart): - - curHero(nullptr), - artWorn(ArtWorn), backpack(Backpack), - backpackPos(0), commonInfo(nullptr), updateState(false), - leftArtRoll(leftScroll), rightArtRoll(rightScroll), - allowedAssembling(true), highlightModeCallback(nullptr) -{ - if(createCommonPart) - { - commonInfo = new CArtifactsOfHero::SCommonPart; - commonInfo->participants.insert(this); - } - - // Init slots for worn artifacts. - for (size_t g = 0; g < artWorn.size() ; g++) - { - artWorn[g]->ourOwner = this; - eraseSlotData(artWorn[g], ArtifactPosition(g)); - } - - // Init slots for the backpack. - for(size_t s=0; sourOwner = this; - eraseSlotData(backpack[s], ArtifactPosition(GameConstants::BACKPACK_START + s)); - } - - leftArtRoll->callback += std::bind(&CArtifactsOfHero::scrollBackpack,this,-1); - rightArtRoll->callback += std::bind(&CArtifactsOfHero::scrollBackpack,this,+1); -} - -CArtifactsOfHero::CArtifactsOfHero(const Point& position, bool createCommonPart /*= false*/) - : curHero(nullptr), backpackPos(0), commonInfo(nullptr), updateState(false), allowedAssembling(true), highlightModeCallback(nullptr) -{ - if(createCommonPart) - { - commonInfo = new CArtifactsOfHero::SCommonPart; - commonInfo->participants.insert(this); - } - - OBJ_CONSTRUCTION_CAPTURING_ALL; - pos += position; - artWorn.resize(19); - - std::vector slotPos; - slotPos += Point(509,30), Point(567,240), Point(509,80), - Point(383,68), Point(564,183), Point(509,130), - Point(431,68), Point(610,183), Point(515,295), - Point(383,143), Point(399,194), Point(415,245), - Point(431,296), Point(564,30), Point(610,30), - Point(610,76), Point(610,122), Point(610,310), - Point(381,296); - - // Create slots for worn artifacts. - for (size_t g = 0; g < GameConstants::BACKPACK_START ; g++) - { - artWorn[g] = new CArtPlace(slotPos[g]); - artWorn[g]->ourOwner = this; - eraseSlotData(artWorn[g], ArtifactPosition(g)); - } - - // Create slots for the backpack. - for(size_t s=0; s<5; ++s) - { - auto add = new CArtPlace(Point(403 + 46 * s, 365)); - - add->ourOwner = this; - eraseSlotData(add, ArtifactPosition(GameConstants::BACKPACK_START + s)); - - backpack.push_back(add); - } - - leftArtRoll = new CAdventureMapButton(std::string(), std::string(), std::bind(&CArtifactsOfHero::scrollBackpack,this,-1), 379, 364, "hsbtns3.def", SDLK_LEFT); - rightArtRoll = new CAdventureMapButton(std::string(), std::string(), std::bind(&CArtifactsOfHero::scrollBackpack,this,+1), 632, 364, "hsbtns5.def", SDLK_RIGHT); -} - -CArtifactsOfHero::~CArtifactsOfHero() -{ - dispose(); -} - -void CArtifactsOfHero::updateParentWindow() -{ - if (CHeroWindow* chw = dynamic_cast(GH.topInt())) - { - if(updateState) - chw->curHero = curHero; - else - chw->update(curHero, true); - } - else if(CExchangeWindow* cew = dynamic_cast(GH.topInt())) - { - - //use our copy of hero to draw window - if(cew->heroInst[0]->id == curHero->id) - cew->heroInst[0] = curHero; - else - cew->heroInst[1] = curHero; - - if(!updateState) - { - cew->deactivate(); -// for(int g=0; gheroInst); ++g) -// { -// if(cew->heroInst[g] == curHero) -// { -// cew->artifs[g]->setHero(curHero); -// } -// } - - - cew->prepareBackground(); - cew->redraw(); - cew->activate(); - } - } -} - -void CArtifactsOfHero::safeRedraw() -{ - if (active) - { - if(parent) - parent->redraw(); - else - redraw(); - } -} - -void CArtifactsOfHero::realizeCurrentTransaction() -{ - assert(commonInfo->src.AOH); - assert(commonInfo->dst.AOH); - LOCPLINT->cb->swapArtifacts(ArtifactLocation(commonInfo->src.AOH->curHero, commonInfo->src.slotID), - ArtifactLocation(commonInfo->dst.AOH->curHero, commonInfo->dst.slotID)); -} - -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) - updateSlot(src.slot); - if(isCurHeroDst && dst.slot >= GameConstants::BACKPACK_START) - updateSlot(dst.slot); - if(isCurHeroSrc || isCurHeroDst) //we need to update all slots, artifact might be combined and affect more slots - updateWornSlots(false); - - if (!src.isHolder(curHero) && !isCurHeroDst) - return; - - if(commonInfo->src == src) //artifact was taken from us - { - 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) - || dst.getHolderArtSet()->bearerType() != ArtBearer::HERO); - commonInfo->reset(); - unmarkSlots(); - } - else if(commonInfo->dst == src) //the dest artifact was moved -> we are picking it - { - assert(dst.slot >= GameConstants::BACKPACK_START); - commonInfo->reset(); - - CArtPlace *ap = nullptr; - for(CArtifactsOfHero *aoh : commonInfo->participants) - { - if(dst.isHolder(aoh->curHero)) - { - commonInfo->src.AOH = aoh; - if((ap = aoh->getArtPlace(dst.slot))) - break; - } - } - - if(ap) - { - ap->select(); - } - else - { - commonInfo->src.art = dst.getArt(); - commonInfo->src.slotID = dst.slot; - assert(commonInfo->src.AOH); - CCS->curh->dragAndDropCursor(new CAnimImage("artifact", dst.getArt()->artType->iconIndex)); - 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. - } - - 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 -} - -void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al) -{ - if(al.isHolder(curHero)) - { - if(al.slot < GameConstants::BACKPACK_START) - updateWornSlots(0); - else - scrollBackpack(0); //update backpack slots - } -} - -CArtPlace * CArtifactsOfHero::getArtPlace(int slot) -{ - if(slot < GameConstants::BACKPACK_START) - { - return artWorn[slot]; - } - else - { - for(CArtPlace *ap : backpack) - if(ap->slotID == slot) - return ap; - } - - return nullptr; -} - -void CArtifactsOfHero::artifactAssembled(const ArtifactLocation &al) -{ - if(al.isHolder(curHero)) - updateWornSlots(); -} - -void CArtifactsOfHero::artifactDisassembled(const ArtifactLocation &al) -{ - if(al.isHolder(curHero)) - updateWornSlots(); -} - -void CArtifactsOfHero::updateWornSlots(bool redrawParent /*= true*/) -{ - for(int i = 0; i < artWorn.size(); i++) - updateSlot(ArtifactPosition(i)); - - - if(redrawParent) - updateParentWindow(); -} - -const CGHeroInstance * CArtifactsOfHero::getHero() const -{ - return curHero; -} - -void CArtifactsOfHero::updateSlot(ArtifactPosition slotID) -{ - setSlotData(getArtPlace(slotID), slotID); -} - -void CExchangeWindow::questlog(int whichHero) -{ - CCS->curh->dragAndDropCursor(nullptr); -} - -void CExchangeWindow::prepareBackground() -{ - //printing heroes' names and levels - auto genTitle = [](const CGHeroInstance *h) - { - return boost::str(boost::format(CGI->generaltexth->allTexts[138]) - % h->name % h->level % h->type->heroClass->name); - }; - - new CLabel(147, 25, FONT_SMALL, CENTER, Colors::WHITE, genTitle(heroInst[0])); - new CLabel(653, 25, FONT_SMALL, CENTER, Colors::WHITE, genTitle(heroInst[1])); - - //printing primary skills - for(int g=0; g<4; ++g) - new CAnimImage("PSKIL32", g, 0, 385, 19 + 36*g); - - //heroes related thing - for(int b=0; b(heroWArt.getPrimSkillLevel(static_cast(m)))); - - //printing secondary skills - for(int m=0; msecSkills.size(); ++m) - { - int id = heroInst[b]->secSkills[m].first; - int level = heroInst[b]->secSkills[m].second; - new CAnimImage("SECSK32", id*3 + level + 2 , 0, 32 + 36 * m + 454 * b, 88); - } - - //hero's specialty - new CAnimImage("UN32", heroInst[b]->type->imageIndex, 0, 67 + 490*b, 45); - - //experience - new CAnimImage("PSKIL32", 4, 0, 103 + 490*b, 45); - new CLabel(119 + 490*b, 71, FONT_SMALL, CENTER, Colors::WHITE, makeNumberShort(heroInst[b]->exp)); - - //mana points - new CAnimImage("PSKIL32", 5, 0, 139 + 490*b, 45); - new CLabel(155 + 490*b, 71, FONT_SMALL, CENTER, Colors::WHITE, makeNumberShort(heroInst[b]->mana)); - } - - //printing portraits - new CAnimImage("PortraitsLarge", heroInst[0]->portrait, 0, 257, 13); - new CAnimImage("PortraitsLarge", heroInst[1]->portrait, 0, 485, 13); -} - -CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID): - CWindowObject(PLAYER_COLORED | BORDERED, "TRADE2") -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - heroInst[0] = LOCPLINT->cb->getHero(hero1); - heroInst[1] = LOCPLINT->cb->getHero(hero2); - - prepareBackground(); - - artifs[0] = new CArtifactsOfHero(Point(-334, 150)); - artifs[0]->commonInfo = new CArtifactsOfHero::SCommonPart; - artifs[0]->commonInfo->participants.insert(artifs[0]); - artifs[0]->setHero(heroInst[0]); - artifs[1] = new CArtifactsOfHero(Point(96, 150)); - artifs[1]->commonInfo = artifs[0]->commonInfo; - artifs[1]->commonInfo->participants.insert(artifs[1]); - artifs[1]->setHero(heroInst[1]); - - artSets.push_back(artifs[0]); - artSets.push_back(artifs[1]); - - //primary skills - for(int g=0; g<4; ++g) - { - //primary skill's clickable areas - primSkillAreas.push_back(new LRClickableAreaWTextComp()); - primSkillAreas[g]->pos = genRect(32, 140, pos.x + 329, pos.y + 19 + 36 * g); - primSkillAreas[g]->text = CGI->generaltexth->arraytxt[2+g]; - primSkillAreas[g]->type = g; - primSkillAreas[g]->bonusValue = -1; - primSkillAreas[g]->baseType = 0; - primSkillAreas[g]->hoverText = CGI->generaltexth->heroscrn[1]; - boost::replace_first(primSkillAreas[g]->hoverText, "%s", CGI->generaltexth->primarySkillNames[g]); - } - - //heroes related thing - for(int b=0; bsecSkills.size(); ++g) - { - int skill = heroInst[b]->secSkills[g].first, - level = heroInst[b]->secSkills[g].second; // <1, 3> - secSkillAreas[b].push_back(new LRClickableAreaWTextComp()); - secSkillAreas[b][g]->pos = genRect(32, 32, pos.x + 32 + g*36 + b*454 , pos.y + 88); - secSkillAreas[b][g]->baseType = 1; - - secSkillAreas[b][g]->type = skill; - secSkillAreas[b][g]->bonusValue = level; - secSkillAreas[b][g]->text = CGI->generaltexth->skillInfoTexts[skill][level-1]; - - secSkillAreas[b][g]->hoverText = CGI->generaltexth->heroscrn[21]; - boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->levels[level - 1]); - boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->skillName[skill]); - } - - portrait[b] = new CHeroArea(257 + 228*b, 13, heroInst[b]); - - specialty[b] = new LRClickableAreaWText(); - specialty[b]->pos = genRect(32, 32, pos.x + 69 + 490*b, pos.y + 45); - specialty[b]->hoverText = CGI->generaltexth->heroscrn[27]; - specialty[b]->text = heroInst[b]->type->specDescr; - - experience[b] = new LRClickableAreaWText(); - experience[b]->pos = genRect(32, 32, pos.x + 105 + 490*b, pos.y + 45); - experience[b]->hoverText = CGI->generaltexth->heroscrn[9]; - experience[b]->text = CGI->generaltexth->allTexts[2].c_str(); - boost::algorithm::replace_first(experience[b]->text, "%d", boost::lexical_cast(heroInst[b]->level)); - boost::algorithm::replace_first(experience[b]->text, "%d", boost::lexical_cast(CGI->heroh->reqExp(heroInst[b]->level+1))); - boost::algorithm::replace_first(experience[b]->text, "%d", boost::lexical_cast(heroInst[b]->exp)); - - spellPoints[b] = new LRClickableAreaWText(); - spellPoints[b]->pos = genRect(32, 32, pos.x + 141 + 490*b, pos.y + 45); - spellPoints[b]->hoverText = CGI->generaltexth->heroscrn[22]; - spellPoints[b]->text = CGI->generaltexth->allTexts[205]; - boost::algorithm::replace_first(spellPoints[b]->text, "%s", heroInst[b]->name); - boost::algorithm::replace_first(spellPoints[b]->text, "%d", boost::lexical_cast(heroInst[b]->mana)); - boost::algorithm::replace_first(spellPoints[b]->text, "%d", boost::lexical_cast(heroInst[b]->manaLimit())); - - //setting morale - morale[b] = new MoraleLuckBox(true, genRect(32, 32, 176 + 490*b, 39), true); - morale[b]->set(heroInst[b]); - //setting luck - luck[b] = new MoraleLuckBox(false, genRect(32, 32, 212 + 490*b, 39), true); - luck[b]->set(heroInst[b]); - } - - //buttons - quit = new CAdventureMapButton(CGI->generaltexth->zelp[600], std::bind(&CExchangeWindow::close, this), 732, 567, "IOKAY.DEF", SDLK_RETURN); - if(queryID.getNum() > 0) - quit->callback += [=]{ LOCPLINT->cb->selectionMade(0, queryID); }; - - questlogButton[0] = new CAdventureMapButton(CGI->generaltexth->heroscrn[0], "", std::bind(&CExchangeWindow::questlog,this, 0), 10, 44, "hsbtns4.def"); - questlogButton[1] = new CAdventureMapButton(CGI->generaltexth->heroscrn[0], "", std::bind(&CExchangeWindow::questlog,this, 1), 740, 44, "hsbtns4.def"); - - Rect barRect(5, 578, 725, 18); - ourBar = new CGStatusBar(new CPicture(*background, barRect, 5, 578, false)); - - //garrison interface - garr = new CGarrisonInt(69, 131, 4, Point(418,0), *background, Point(69,131), heroInst[0],heroInst[1], true, true); - garr->addSplitBtn(new CAdventureMapButton(CGI->generaltexth->tcommands[3], "", std::bind(&CGarrisonInt::splitClick, garr), 10, 132, "TSBTNS.DEF")); - garr->addSplitBtn(new CAdventureMapButton(CGI->generaltexth->tcommands[3], "", std::bind(&CGarrisonInt::splitClick, garr), 740, 132, "TSBTNS.DEF")); -} - -CExchangeWindow::~CExchangeWindow() //d-tor -{ - delete artifs[0]->commonInfo; - artifs[0]->commonInfo = nullptr; - artifs[1]->commonInfo = nullptr; -} - -CShipyardWindow::CShipyardWindow(const std::vector &cost, int state, int boatType, const std::function &onBuy): - CWindowObject(PLAYER_COLORED, "TPSHIP") -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - bgWater = new CPicture("TPSHIPBK", 100, 69); - - std::string boatFilenames[3] = {"AB01_", "AB02_", "AB03_"}; - - Point waterCenter = Point(bgWater->pos.x+bgWater->pos.w/2, bgWater->pos.y+bgWater->pos.h/2); - bgShip = new CAnimImage(boatFilenames[boatType], 0, 7, 120, 96, CShowableAnim::USE_RLE); - bgShip->center(waterCenter); - - // Create resource icons and costs. - std::string goldValue = boost::lexical_cast(cost[Res::GOLD]); - std::string woodValue = boost::lexical_cast(cost[Res::WOOD]); - - goldCost = new CLabel(118, 294, FONT_SMALL, CENTER, Colors::WHITE, goldValue); - woodCost = new CLabel(212, 294, FONT_SMALL, CENTER, Colors::WHITE, woodValue); - - goldPic = new CAnimImage("RESOURCE", Res::GOLD, 0, 100, 244); - woodPic = new CAnimImage("RESOURCE", Res::WOOD, 0, 196, 244); - - quit = new CAdventureMapButton(CGI->generaltexth->allTexts[599], "", std::bind(&CShipyardWindow::close, this), 224, 312, "ICANCEL", SDLK_RETURN); - build = new CAdventureMapButton(CGI->generaltexth->allTexts[598], "", std::bind(&CShipyardWindow::close, this), 42, 312, "IBUY30", SDLK_RETURN); - build->callback += onBuy; - - for(Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1)) - { - if(cost[i] > LOCPLINT->cb->getResourceAmount(i)) - { - build->block(true); - break; - } - } - - statusBar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); - - title = new CLabel(164, 27, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[13]); - costLabel = new CLabel(164, 220, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->jktexts[14]); -} - -CPuzzleWindow::CPuzzleWindow(const int3 &GrailPos, double discoveredRatio): - CWindowObject(PLAYER_COLORED | BORDERED, "PUZZLE"), - grailPos(GrailPos), - currentAlpha(SDL_ALPHA_OPAQUE) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - CCS->soundh->playSound(soundBase::OBELISK); - - quitb = new CAdventureMapButton(CGI->generaltexth->allTexts[599], "", std::bind(&CPuzzleWindow::close, this), 670, 538, "IOK6432.DEF", SDLK_RETURN); - quitb->assignedKeys.insert(SDLK_ESCAPE); - quitb->borderColor = Colors::METALLIC_GOLD; - quitb->borderEnabled = true; - - new CPicture("PUZZLOGO", 607, 3); - new CLabel(700, 95, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[463]); - new CResDataBar("ARESBAR.bmp", 3, 575, 32, 2, 85, 85); - - int faction = LOCPLINT->cb->getStartInfo()->playerInfos.find(LOCPLINT->playerID)->second.castle; - - auto & puzzleMap = CGI->townh->factions[faction]->puzzleMap; - - for(auto & elem : puzzleMap) - { - const SPuzzleInfo & info = elem; - - auto piece = new CPicture(info.filename, info.x, info.y); - - //piece that will slowly disappear - if(info.whenUncovered <= GameConstants::PUZZLE_MAP_PIECES * discoveredRatio) - { - piecesToRemove.push_back(piece); - piece->needRefresh = true; - piece->recActions = piece->recActions & ~SHOWALL; - #ifndef VCMI_SDL1 - SDL_SetSurfaceBlendMode(piece->bg,SDL_BLENDMODE_BLEND); - #endif // VCMI_SDL1 - } - } -} - -void CPuzzleWindow::showAll(SDL_Surface * to) -{ - int3 moveInt = int3(8, 9, 0); - Rect mapRect = genRect(544, 591, pos.x + 8, pos.y + 7); - - CGI->mh->terrainRect - (grailPos - moveInt, adventureInt->anim, - &LOCPLINT->cb->getVisibilityMap(), true, adventureInt->heroAnim, - to, &mapRect, 0, 0, true, moveInt); - - CWindowObject::showAll(to); -} - -void CPuzzleWindow::show(SDL_Surface * to) -{ - static int animSpeed = 2; - - if (currentAlpha < animSpeed) - { - //animation done - for(auto & piece : piecesToRemove) - delete piece; - piecesToRemove.clear(); - } - else - { - //update disappearing puzzles - for(auto & piece : piecesToRemove) - piece->setAlpha(currentAlpha); - currentAlpha -= animSpeed; - } - CWindowObject::show(to); -} - -void CTransformerWindow::CItem::move() -{ - if (left) - moveBy(Point(289, 0)); - else - moveBy(Point(-289, 0)); - left = !left; -} - -void CTransformerWindow::CItem::clickLeft(tribool down, bool previousState) -{ - if(previousState && (!down)) - { - move(); - parent->showAll(screen2); - } -} - -void CTransformerWindow::CItem::update() -{ - icon->setFrame(parent->army->getCreature(SlotID(id))->idNumber + 2); -} - -CTransformerWindow::CItem::CItem(CTransformerWindow * parent, int size, int id): - id(id), size(size), parent(parent) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - addUsedEvents(LCLICK); - left = true; - pos.w = 58; - pos.h = 64; - - pos.x += 45 + (id%3)*83 + id/6*83; - pos.y += 109 + (id/3)*98; - icon = new CAnimImage("TWCRPORT", parent->army->getCreature(SlotID(id))->idNumber + 2); - new CLabel(28, 76,FONT_SMALL, CENTER, Colors::WHITE, boost::lexical_cast(size));//stack size -} - -void CTransformerWindow::makeDeal() -{ - for (auto & elem : items) - if (!elem->left) - LOCPLINT->cb->trade(town, EMarketMode::CREATURE_UNDEAD, elem->id, 0, 0, hero); -} - -void CTransformerWindow::addAll() -{ - for (auto & elem : items) - if (elem->left) - elem->move(); - showAll(screen2); -} - -void CTransformerWindow::updateGarrisons() -{ - for(auto & item : items) - { - item->update(); - } -} - -CTransformerWindow::CTransformerWindow(const CGHeroInstance * _hero, const CGTownInstance * _town): - CWindowObject(PLAYER_COLORED, "SKTRNBK"), - hero(_hero), - town(_town) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - if (hero) - army = hero; - else - army = town; - - for (int i=0; igetCreature(SlotID(i)) ) - items.push_back(new CItem(this, army->getStackCount(SlotID(i)), i)); - - all = new CAdventureMapButton(CGI->generaltexth->zelp[590],std::bind(&CTransformerWindow::addAll,this), 146,416,"ALTARMY.DEF",SDLK_a); - convert= new CAdventureMapButton(CGI->generaltexth->zelp[591],std::bind(&CTransformerWindow::makeDeal,this), 269,416,"ALTSACR.DEF",SDLK_RETURN); - cancel = new CAdventureMapButton(CGI->generaltexth->zelp[592],std::bind(&CTransformerWindow::close, this),392,416,"ICANCEL.DEF",SDLK_ESCAPE); - bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); - - new CLabel(153, 29,FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[485]);//holding area - new CLabel(153+295, 29, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[486]);//transformer - new CTextBox(CGI->generaltexth->allTexts[487], Rect(26, 56, 255, 40), 0, FONT_MEDIUM, CENTER, Colors::YELLOW);//move creatures to create skeletons - new CTextBox(CGI->generaltexth->allTexts[488], Rect(320, 56, 255, 40), 0, FONT_MEDIUM, CENTER, Colors::YELLOW);//creatures here will become skeletons - -} - -void CUniversityWindow::CItem::clickLeft(tribool down, bool previousState) -{ - if(previousState && (!down)) - { - if ( state() != 2 ) - return; - auto win = new CUnivConfirmWindow(parent, ID, LOCPLINT->cb->getResourceAmount(Res::GOLD) >= 2000); - GH.pushInt(win); - } -} - -void CUniversityWindow::CItem::clickRight(tribool down, bool previousState) -{ - if(down) - { - CRClickPopup::createAndPush(CGI->generaltexth->skillInfoTexts[ID][0], - new CComponent(CComponent::secskill, ID, 1)); - } -} - -void CUniversityWindow::CItem::hover(bool on) -{ - if (on) - GH.statusbar->setText(CGI->generaltexth->skillName[ID]); - else - GH.statusbar->clear(); -} - -int CUniversityWindow::CItem::state() -{ - if (parent->hero->getSecSkillLevel(SecondarySkill(ID)))//hero know this skill - return 1; - if (!parent->hero->canLearnSkill())//can't learn more skills - return 0; - if (parent->hero->type->heroClass->secSkillProbability[ID]==0)//can't learn this skill (like necromancy for most of non-necros) - return 0; - return 2; -} - -void CUniversityWindow::CItem::showAll(SDL_Surface * to) -{ - CPicture * bar; - switch (state()) - { - case 0: bar = parent->red; - break; - case 1: bar = parent->yellow; - break; - case 2: bar = parent->green; - break; - default:bar = nullptr; - break; - } - assert(bar); - - blitAtLoc(bar->bg, -28, -22, to); - blitAtLoc(bar->bg, -28, 48, to); - printAtMiddleLoc (CGI->generaltexth->skillName[ID], 22, -13, FONT_SMALL, Colors::WHITE,to);//Name - printAtMiddleLoc (CGI->generaltexth->levels[0], 22, 57, FONT_SMALL, Colors::WHITE,to);//Level(always basic) - - CAnimImage::showAll(to); -} - -CUniversityWindow::CItem::CItem(CUniversityWindow * _parent, int _ID, int X, int Y): - CAnimImage ("SECSKILL", _ID*3+3, 0, X, Y), - ID(_ID), - parent(_parent) -{ - addUsedEvents(LCLICK | RCLICK | HOVER); -} - -CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket * _market): - CWindowObject(PLAYER_COLORED, "UNIVERS1"), - hero(_hero), - market(_market) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - green = new CPicture("UNIVGREN.PCX"); - yellow = new CPicture("UNIVGOLD.PCX");//bars - red = new CPicture("UNIVRED.PCX"); - - green->recActions = - yellow->recActions = - red->recActions = DISPOSE; - - CIntObject * titlePic = nullptr; - - if (market->o->ID == Obj::TOWN) - titlePic = new CAnimImage(CGI->townh->factions[ETownType::CONFLUX]->town->clientInfo.buildingsIcons, BuildingID::MAGIC_UNIVERSITY); - else - titlePic = new CPicture("UNIVBLDG"); - - titlePic->center(Point(232 + pos.x, 76 + pos.y)); - - //Clerk speech - new CTextBox(CGI->generaltexth->allTexts[603], Rect(24, 129, 413, 70), 0, FONT_SMALL, CENTER, Colors::WHITE); - - //University - new CLabel(231, 26, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[602]); - - std::vector list = market->availableItemsIds(EMarketMode::RESOURCE_SKILL); - - assert(list.size() == 4); - - for (int i=0; igeneraltexth->zelp[632], - std::bind(&CUniversityWindow::close, this),200,313,"IOKAY.DEF",SDLK_RETURN); - - bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); -} - -CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * PARENT, int SKILL, bool available ): - CWindowObject(PLAYER_COLORED, "UNIVERS2.PCX"), - parent(PARENT) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - std::string text = CGI->generaltexth->allTexts[608]; - boost::replace_first(text, "%s", CGI->generaltexth->levels[0]); - boost::replace_first(text, "%s", CGI->generaltexth->skillName[SKILL]); - boost::replace_first(text, "%d", "2000"); - - new CTextBox(text, Rect(24, 129, 413, 70), 0, FONT_SMALL, CENTER, Colors::WHITE);//Clerk speech - - new CLabel(230, 37, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth-> skillName[SKILL]);//Skill name - new CAnimImage("SECSKILL", SKILL*3+3, 0, 211, 51);//skill - new CLabel(230, 107, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->levels[1]);//Skill level - - new CAnimImage("RESOURCE", Res::GOLD, 0, 210, 210);//gold - new CLabel(230, 267, FONT_SMALL, CENTER, Colors::WHITE, "2000");//Cost - - std::string hoverText = CGI->generaltexth->allTexts[609]; - boost::replace_first(hoverText, "%s", CGI->generaltexth->levels[0]+ " " + CGI->generaltexth->skillName[SKILL]); - - text = CGI->generaltexth->zelp[633].second; - boost::replace_first(text, "%s", CGI->generaltexth->levels[0]); - boost::replace_first(text, "%s", CGI->generaltexth->skillName[SKILL]); - boost::replace_first(text, "%d", "2000"); - - confirm= new CAdventureMapButton(hoverText, text, std::bind(&CUnivConfirmWindow::makeDeal, this, SKILL), - 148,299,"IBY6432.DEF",SDLK_RETURN); - confirm->block(!available); - - cancel = new CAdventureMapButton(CGI->generaltexth->zelp[631],std::bind(&CUnivConfirmWindow::close, this), - 252,299,"ICANCEL.DEF",SDLK_ESCAPE); - bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); -} - -void CUnivConfirmWindow::makeDeal(int skill) -{ - LOCPLINT->cb->trade(parent->market->o, EMarketMode::RESOURCE_SKILL, 6, skill, 1, parent->hero); - close(); -} - -CHillFortWindow::CHillFortWindow(const CGHeroInstance *visitor, const CGObjectInstance *object): - CWindowObject(PLAYER_COLORED, "APHLFTBK"), - fort(object), - hero(visitor) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - slotsCount=7; - resources = CDefHandler::giveDefEss("SMALRES.DEF"); - - new CLabel(325, 32, FONT_BIG, CENTER, Colors::YELLOW, fort->getObjectName());//Hill Fort - - heroPic = new CHeroArea(30, 60, hero); - - currState.resize(slotsCount+1); - costs.resize(slotsCount); - totalSumm.resize(GameConstants::RESOURCE_QUANTITY); - std::vector files; - files += "APHLF1R.DEF", "APHLF1Y.DEF", "APHLF1G.DEF"; - for (int i=0; iblock(currState[i] == -1); - } - files.clear(); - files += "APHLF4R.DEF", "APHLF4Y.DEF", "APHLF4G.DEF"; - currState[slotsCount] = getState(SlotID(slotsCount)); - upgradeAll = new CAdventureMapButton(CGI->generaltexth->allTexts[432],"",std::bind(&CHillFortWindow::makeDeal, this, SlotID(slotsCount)), - 30, 231, "", SDLK_0, &files); - quit = new CAdventureMapButton("","",std::bind(&CHillFortWindow::close, this), 294, 275, "IOKAY.DEF", SDLK_RETURN); - bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); - - garr = new CGarrisonInt(108, 60, 18, Point(),background->bg,Point(108,60),hero,nullptr); - updateGarrisons(); -} - -void CHillFortWindow::updateGarrisons() -{ - for (int i=0; icb->getUpgradeInfo(hero, SlotID(i), info); - if (info.newID.size())//we have upgrades here - update costs - { - costs[i] = info.cost[0] * hero->getStackCount(SlotID(i)); - totalSumm += costs[i]; - } - } - - currState[i] = newState; - upgrade[i]->setIndex(newState); - upgrade[i]->block(currState[i] == -1); - upgrade[i]->hoverTexts[0] = getTextForSlot(SlotID(i)); - } - - int newState = getState(SlotID(slotsCount)); - currState[slotsCount] = newState; - upgradeAll->setIndex(newState); - garr->recreateSlots(); -} - -void CHillFortWindow::makeDeal(SlotID slot) -{ - assert(slot.getNum()>=0); - int offset = (slot.getNum() == slotsCount)?2:0; - switch (currState[slot.getNum()]) - { - case 0: - LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[314 + offset], - std::vector(), soundBase::sound_todo); - break; - case 1: - LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[313 + offset], - std::vector(), soundBase::sound_todo); - break; - case 2: - for (int i=0; icb->getUpgradeInfo(hero, SlotID(i), info); - LOCPLINT->cb->upgradeCreature(hero, SlotID(i), info.newID[0]); - } - break; - - } -} - -void CHillFortWindow::showAll (SDL_Surface *to) -{ - CWindowObject::showAll(to); - - for ( int i=0; i= 0; j--) - { - int val = costs[i][j]; - if(!val) continue; - - blitAtLoc(resources->ourImages[j].bitmap, 104+76*i, curY, to); - printToLoc(boost::lexical_cast(val), 168+76*i, curY+16, FONT_SMALL, Colors::WHITE, to); - curY += 20; - } - } - else//free upgrade - print gold image and "Free" text - { - blitAtLoc(resources->ourImages[6].bitmap, 104+76*i, 128, to); - printToLoc(CGI->generaltexth->allTexts[344], 168+76*i, 144, FONT_SMALL, Colors::WHITE, to); - } - } - } - for (int i=0; iourImages[i].bitmap, 104+76*i, 237, to); - printToLoc(boost::lexical_cast(totalSumm[i]), 166+76*i, 253, FONT_SMALL, Colors::WHITE, to); - } - } -} - -std::string CHillFortWindow::getTextForSlot(SlotID slot) -{ - if ( !hero->getCreature(slot) )//we don`t have creature here - return ""; - - std::string str = CGI->generaltexth->allTexts[318]; - int amount = hero->getStackCount(slot); - if ( amount == 1 ) - boost::algorithm::replace_first(str,"%s",hero->getCreature(slot)->nameSing); - else - boost::algorithm::replace_first(str,"%s",hero->getCreature(slot)->namePl); - - return str; -} - -int CHillFortWindow::getState(SlotID slot) -{ - TResources myRes = LOCPLINT->cb->getResourceAmount(); - if ( slot.getNum() == slotsCount )//"Upgrade all" slot - { - bool allUpgraded = true;//All creatures are upgraded? - for (int i=0; islotEmpty(slot))//no creature here - return -1; - - UpgradeInfo info; - LOCPLINT->cb->getUpgradeInfo(hero, slot, info); - if (!info.newID.size())//already upgraded - return 1; - - if(!(info.cost[0] * hero->getStackCount(slot)).canBeAfforded(myRes)) - return 0; - - return 2;//can upgrade -} - -CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner): - CWindowObject(PLAYER_COLORED | BORDERED, "TpRank"), - owner(_owner) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - type |= BLOCK_ADV_HOTKEYS; - - SThievesGuildInfo tgi; //info to be displayed - LOCPLINT->cb->getThievesGuildInfo(tgi, owner); - - exitb = new CAdventureMapButton (CGI->generaltexth->allTexts[600], "", std::bind(&CThievesGuildWindow::close,this), 748, 556, "TPMAGE1", SDLK_RETURN); - exitb->assignedKeys.insert(SDLK_ESCAPE); - statusBar = new CGStatusBar(3, 555, "TStatBar.bmp", 742); - - resdatabar = new CMinorResDataBar(); - resdatabar->pos.x += pos.x; - resdatabar->pos.y += pos.y; - - //data for information table: - // fields[row][column] = list of id's of players for this box - static std::vector< std::vector< PlayerColor > > SThievesGuildInfo::* fields[] = - { &SThievesGuildInfo::numOfTowns, &SThievesGuildInfo::numOfHeroes, &SThievesGuildInfo::gold, - &SThievesGuildInfo::woodOre, &SThievesGuildInfo::mercSulfCrystGems, &SThievesGuildInfo::obelisks, - &SThievesGuildInfo::artifacts, &SThievesGuildInfo::army, &SThievesGuildInfo::income }; - - //printing texts & descriptions to background - - for(int g=0; g<12; ++g) - { - int posY[] = {400, 460, 510}; - int y; - if(g < 9) - y = 52 + 32*g; - else - y = posY[g-9]; - - std::string text = CGI->generaltexth->jktexts[24+g]; - boost::algorithm::trim_if(text,boost::algorithm::is_any_of("\"")); - new CLabel(135, y, FONT_MEDIUM, CENTER, Colors::YELLOW, text); - } - - for(int g=1; ggeneraltexth->jktexts[16+g]); - - //printing flags - for(int g = 0; g < ARRAY_COUNT(fields); ++g) //by lines - { - for(int b=0; b<(tgi .* fields[g]).size(); ++b) //by places (1st, 2nd, ...) - { - std::vector &players = (tgi .* fields[g])[b]; //get players with this place in this line - - //position of box - int xpos = 259 + 66 * b; - int ypos = 41 + 32 * g; - - size_t rowLength[2]; //size of each row - rowLength[0] = std::min(players.size(), 4); - rowLength[1] = players.size() - rowLength[0]; - - for (size_t j=0; j< 2; j++) - { - // origin of this row | offset for 2nd row| shift right for short rows - //if we have 2 rows, start either from mid or beginning (depending on count), otherwise center the flags - int rowStartX = xpos + (j ? 6 + (rowLength[j] < 3 ? 12 : 0) : 24 - 6 * rowLength[j]); - int rowStartY = ypos + (j ? 4 : 0); - - for (size_t i=0; i< rowLength[j]; i++) - { - new CAnimImage("itgflags", players[i + j*4].getNum(), 0, rowStartX + i*12, rowStartY); - } - } - } - } - - static const std::string colorToBox[] = {"PRRED.BMP", "PRBLUE.BMP", "PRTAN.BMP", "PRGREEN.BMP", "PRORANGE.BMP", "PRPURPLE.BMP", "PRTEAL.BMP", "PRROSE.bmp"}; - - //printing best hero - int counter = 0; - for(auto & iter : tgi.colorToBestHero) - { - if(iter.second.portrait >= 0) - { - new CPicture(colorToBox[iter.first.getNum()], 253 + 66 * counter, 334); - new CAnimImage("PortraitsSmall", iter.second.portrait, 0, 260 + 66 * counter, 360); - //TODO: r-click info: - // - r-click on hero - // - r-click on primary skill label - if(iter.second.details) - { - new CTextBox(CGI->generaltexth->allTexts[184], Rect(260 + 66*counter, 396, 52, 64), - 0, FONT_TINY, TOPLEFT, Colors::WHITE); - for (int i=0; iprimskills.size(); ++i) - { - new CLabel(310 + 66 * counter, 407 + 11*i, FONT_TINY, BOTTOMRIGHT, Colors::WHITE, - boost::lexical_cast(iter.second.details->primskills[i])); - } - } - } - counter++; - } - - //printing best creature - counter = 0; - for(auto & it : tgi.bestCreature) - { - if(it.second >= 0) - new CAnimImage("TWCRPORT", it.second+2, 0, 255 + 66 * counter, 479); - counter++; - } - - //printing personality - counter = 0; - for(auto & it : tgi.personality) - { - std::string text; - if(it.second == EAiTactic::NONE) - { - text = CGI->generaltexth->arraytxt[172]; - } - else if(it.second != EAiTactic::RANDOM) - { - text = CGI->generaltexth->arraytxt[168 + it.second]; - } - - new CLabel(283 + 66*counter, 459, FONT_SMALL, CENTER, Colors::WHITE, text); - - counter++; - } -} - -void MoraleLuckBox::set(const IBonusBearer *node) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - const int textId[] = {62, 88}; //eg %s \n\n\n {Current Luck Modifiers:} - const int noneTxtId = 108; //Russian version uses same text for neutral morale\luck - const int neutralDescr[] = {60, 86}; //eg {Neutral Morale} \n\n Neutral morale means your armies will neither be blessed with extra attacks or freeze in combat. - const int componentType[] = {CComponent::luck, CComponent::morale}; - const int hoverTextBase[] = {7, 4}; - const Bonus::BonusType bonusType[] = {Bonus::LUCK, Bonus::MORALE}; - int (IBonusBearer::*getValue[])() const = {&IBonusBearer::LuckVal, &IBonusBearer::MoraleVal}; - - int mrlt = -9; - TModDescr mrl; - - if (node) - { - node->getModifiersWDescr(mrl, bonusType[morale]); - bonusValue = (node->*getValue[morale])(); - } - else - bonusValue = 0; - - mrlt = (bonusValue>0)-(bonusValue<0); //signum: -1 - bad luck / morale, 0 - neutral, 1 - good - hoverText = CGI->generaltexth->heroscrn[hoverTextBase[morale] - mrlt]; - baseType = componentType[morale]; - text = CGI->generaltexth->arraytxt[textId[morale]]; - boost::algorithm::replace_first(text,"%s",CGI->generaltexth->arraytxt[neutralDescr[morale]-mrlt]); - if (!mrl.size()) - text += CGI->generaltexth->arraytxt[noneTxtId]; - else - { - //it's a creature window - if ((morale && node->hasBonusOfType(Bonus::UNDEAD)) || - node->hasBonusOfType(Bonus::BLOCK_MORALE) || node->hasBonusOfType(Bonus::NON_LIVING)) - { - text += CGI->generaltexth->arraytxt[113]; //unaffected by morale - } - else - { - for(auto & elem : mrl) - { - if (elem.first) //no bonuses with value 0 - text += "\n" + elem.second; - } - } - } - - std::string imageName; - if (small) - imageName = morale ? "IMRL30": "ILCK30"; - else - imageName = morale ? "IMRL42" : "ILCK42"; - - delete image; - image = new CAnimImage(imageName, bonusValue + 3); - image->moveBy(Point(pos.w/2 - image->pos.w/2, pos.h/2 - image->pos.h/2));//center icon -} - -MoraleLuckBox::MoraleLuckBox(bool Morale, const Rect &r, bool Small): - image(nullptr), - morale(Morale), - small(Small) -{ - bonusValue = 0; - pos = r + pos; -} - -CArtifactHolder::CArtifactHolder() -{ -} - -void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation &artLoc) -{ - for(CArtifactsOfHero *aoh : artSets) - aoh->artifactRemoved(artLoc); -} - -void CWindowWithArtifacts::artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc) -{ - CArtifactsOfHero *destaoh = nullptr; - for(CArtifactsOfHero *aoh : artSets) - { - aoh->artifactMoved(artLoc, destLoc); - aoh->redraw(); - if(destLoc.isHolder(aoh->getHero())) - destaoh = aoh; - } - - //Make sure the status bar is updated so it does not display old text - if(destaoh != nullptr && destaoh->getArtPlace(destLoc.slot) != nullptr) - { - destaoh->getArtPlace(destLoc.slot)->hover(true); - } -} - -void CWindowWithArtifacts::artifactDisassembled(const ArtifactLocation &artLoc) -{ - for(CArtifactsOfHero *aoh : artSets) - aoh->artifactDisassembled(artLoc); -} - -void CWindowWithArtifacts::artifactAssembled(const ArtifactLocation &artLoc) -{ - for(CArtifactsOfHero *aoh : artSets) - aoh->artifactAssembled(artLoc); -} - -void CArtifactsOfHero::SCommonPart::Artpos::clear() -{ - slotID = ArtifactPosition::PRE_FIRST; - AOH = nullptr; - art = nullptr; -} - -CArtifactsOfHero::SCommonPart::Artpos::Artpos() -{ - clear(); -} - -void CArtifactsOfHero::SCommonPart::Artpos::setTo(const CArtPlace *place, bool dontTakeBackpack) -{ - slotID = place->slotID; - AOH = place->ourOwner; - - if(slotID >= 19 && dontTakeBackpack) - art = nullptr; - else - art = place->ourArt; -} - -bool CArtifactsOfHero::SCommonPart::Artpos::operator==(const ArtifactLocation &al) const -{ - if(!AOH) - return false; - bool ret = al.isHolder(AOH->curHero) && al.slot == slotID; - - //assert(al.getArt() == art); - return ret; -} - -bool CArtifactsOfHero::SCommonPart::Artpos::valid() -{ - assert(AOH && art); - return art == AOH->curHero->getArt(slotID); -} - -void CRClickPopup::clickRight(tribool down, bool previousState) -{ - if(down) - return; - close(); -} - -void CRClickPopup::close() -{ - GH.popIntTotally(this); -} - -void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCompsInfo &comps) -{ - PlayerColor player = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1); //if no player, then use blue - - CSimpleWindow * temp = new CInfoWindow(txt, player, comps); - temp->center(Point(GH.current->motion)); //center on mouse - temp->fitToScreen(10); - auto rcpi = new CRClickPopupInt(temp,true); - GH.pushInt(rcpi); -} - -void CRClickPopup::createAndPush(const std::string &txt, CComponent * component) -{ - CInfoWindow::TCompsInfo intComps; - intComps.push_back(component); - - createAndPush(txt, intComps); -} - -Point CInfoBoxPopup::toScreen(Point p) -{ - vstd::abetween(p.x, adventureInt->terrain.pos.x + 100, adventureInt->terrain.pos.x + adventureInt->terrain.pos.w - 100); - vstd::abetween(p.y, adventureInt->terrain.pos.y + 100, adventureInt->terrain.pos.y + adventureInt->terrain.pos.h - 100); - - return p; -} - -CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town): - CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "TOWNQVBK", toScreen(position)) -{ - InfoAboutTown iah; - LOCPLINT->cb->getTownInfo(town, iah); - - OBJ_CONSTRUCTION_CAPTURING_ALL; - new CTownTooltip(Point(9, 10), iah); -} - -CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero): - CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "HEROQVBK", toScreen(position)) -{ - InfoAboutHero iah; - LOCPLINT->cb->getHeroInfo(hero, iah); - - OBJ_CONSTRUCTION_CAPTURING_ALL; - new CHeroTooltip(Point(9, 10), iah); -} - -CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr): - CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "TOWNQVBK", toScreen(position)) -{ - InfoAboutTown iah; - LOCPLINT->cb->getTownInfo(garr, iah); - - OBJ_CONSTRUCTION_CAPTURING_ALL; - new CArmyTooltip(Point(9, 10), iah); -} - -CIntObject * CRClickPopup::createInfoWin(Point position, const CGObjectInstance * specific) //specific=0 => draws info about selected town/hero -{ - if(!specific) - specific = adventureInt->selection; - - assert(specific); - - switch(specific->ID) - { - case Obj::HERO: - return new CInfoBoxPopup(position, dynamic_cast(specific)); - case Obj::TOWN: - return new CInfoBoxPopup(position, dynamic_cast(specific)); - case Obj::GARRISON: - case Obj::GARRISON2: - return new CInfoBoxPopup(position, dynamic_cast(specific)); - default: - return nullptr; - } -} - -void CRClickPopup::createAndPush(const CGObjectInstance *obj, const Point &p, EAlignment alignment /*= BOTTOMRIGHT*/) -{ - CIntObject *iWin = createInfoWin(p, obj); //try get custom infowindow for this obj - if(iWin) - GH.pushInt(iWin); - else - { - if (adventureInt->curHero()) - CRClickPopup::createAndPush(obj->getHoverText(adventureInt->curHero())); - else - CRClickPopup::createAndPush(obj->getHoverText(LOCPLINT->playerID)); - } -} - -CRClickPopup::CRClickPopup() -{ - addUsedEvents(RCLICK); -} - -CRClickPopup::~CRClickPopup() -{ -} - -void CRClickPopupInt::show(SDL_Surface * to) -{ - inner->show(to); -} - -CRClickPopupInt::CRClickPopupInt( IShowActivatable *our, bool deleteInt ) -{ - CCS->curh->hide(); - inner = our; - delInner = deleteInt; -} - -CRClickPopupInt::~CRClickPopupInt() -{ - // //workaround for hero window issue - if it's our interface, call dispose to properly reset it's state - // //TODO? it might be better to rewrite hero window so it will bee newed/deleted on opening / closing (not effort-worthy now, but on some day...?) - // if(LOCPLINT && inner == adventureInt->heroWindow) - // adventureInt->heroWindow->dispose(); - - if(delInner) - delete inner; - - CCS->curh->show(); -} - -void CRClickPopupInt::showAll(SDL_Surface * to) -{ - inner->showAll(to); -} diff --git a/client/GUIClasses.h b/client/GUIClasses.h deleted file mode 100644 index 77c9ca378..000000000 --- a/client/GUIClasses.h +++ /dev/null @@ -1,1202 +0,0 @@ -#pragma once - -#include "CAnimation.h" -#include "../lib/FunctionList.h" -#include "../lib/ResourceSet.h" -#include "../lib/CConfigHandler.h" -#include "../lib/GameConstants.h" -#include "gui/CIntObject.h" -#include "gui/CIntObjectClasses.h" -#include "../lib/GameConstants.h" - -#ifdef max -#undef max -#endif -#ifdef min -#undef min -#endif - -/* - * GUIClasses.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 - * - */ - -struct ArtifactLocation; -class CStackBasicDescriptor; -class CBonusSystemNode; -class CArtifact; -class CDefEssential; -class CAdventureMapButton; -class CHighlightableButtonsGroup; -class CDefHandler; -struct HeroMoveDetails; -class CDefEssential; -class CGHeroInstance; -class CAdvMapInt; -class CCastleInterface; -class CBattleInterface; -class CStack; -class CComponent; -class CCreature; -struct SDL_Surface; -struct CPath; -class CCreatureAnim; -class CSelectableComponent; -class CCreatureSet; -class CGObjectInstance; -class CGDwelling; -class CSlider; -struct UpgradeInfo; -template struct CondSh; -class CInGameConsole; -class CGarrisonInt; -class CInGameConsole; -struct Component; -class CArmedInstance; -class CGTownInstance; -class StackState; -class CPlayerInterface; -class CHeroWindow; -class CArtifact; -class CArtifactsOfHero; -class CCreatureArtifactSet; -class CResDataBar; -struct SPuzzleInfo; -class CGGarrison; -class CStackInstance; -class IMarket; -class CTextBox; -class CArtifactInstance; -class IBonusBearer; -class CArtPlace; -class CAnimImage; -struct InfoAboutArmy; -struct InfoAboutHero; -struct InfoAboutTown; - -/// text + comp. + ok button -class CInfoWindow : public CSimpleWindow -{ //window able to delete its components when closed - bool delComps; //whether comps will be deleted - -public: - typedef std::vector > > TButtonsInfo; - typedef std::vector TCompsInfo; - QueryID ID; //for identification - CTextBox *text; - std::vector buttons; - std::vector components; - CSlider *slider; - - void setDelComps(bool DelComps); - virtual void close(); - - void show(SDL_Surface * to); - void showAll(SDL_Surface * to); - void sliderMoved(int to); - - CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo &comps = TCompsInfo(), const TButtonsInfo &Buttons = TButtonsInfo(), bool delComps = true); //c-tor - CInfoWindow(); //c-tor - ~CInfoWindow(); //d-tor - - //use only before the game starts! (showYesNoDialog in LOCPLINT must be used then) - static void showInfoDialog( const std::string & text, const std::vector *components, bool DelComps = true, PlayerColor player = PlayerColor(1)); - static void showOkDialog(const std::string & text, const std::vector *components, const std::function & onOk, bool delComps = true, PlayerColor player = PlayerColor(1)); - static void showYesNoDialog( const std::string & text, const std::vector *components, const CFunctionList &onYes, const CFunctionList &onNo, bool DelComps = true, PlayerColor player = PlayerColor(1)); - static CInfoWindow *create(const std::string &text, PlayerColor playerID = PlayerColor(1), const std::vector *components = nullptr, bool DelComps = false); - - /// create text from title and description: {title}\n\n description - static std::string genText(std::string title, std::string description); -}; - -/// component selection window -class CSelWindow : public CInfoWindow -{ //warning - this window deletes its components by closing! -public: - void selectionChange(unsigned to); - void madeChoice(); //looks for selected component and calls callback - CSelWindow(const std::string& text, PlayerColor player, int charperline ,const std::vector &comps, const std::vector > > &Buttons, QueryID askID); //c-tor - CSelWindow(){}; //c-tor - //notification - this class inherits important destructor from CInfoWindow -}; - -/// popup displayed on R-click -class CRClickPopup : public CIntObject -{ -public: - virtual void close(); - void clickRight(tribool down, bool previousState); - - CRClickPopup(); - virtual ~CRClickPopup(); //d-tor - - static CIntObject* createInfoWin(Point position, const CGObjectInstance * specific); - static void createAndPush(const std::string &txt, const CInfoWindow::TCompsInfo &comps = CInfoWindow::TCompsInfo()); - static void createAndPush(const std::string &txt, CComponent * component); - static void createAndPush(const CGObjectInstance *obj, const Point &p, EAlignment alignment = BOTTOMRIGHT); -}; - -/// popup displayed on R-click -class CRClickPopupInt : public CRClickPopup -{ -public: - IShowActivatable *inner; - bool delInner; - - void show(SDL_Surface * to); - void showAll(SDL_Surface * to); - CRClickPopupInt(IShowActivatable *our, bool deleteInt); //c-tor - virtual ~CRClickPopupInt(); //d-tor -}; - -class CInfoPopup : public CRClickPopup -{ -public: - bool free; //TODO: comment me - SDL_Surface * bitmap; //popup background - void close(); - void show(SDL_Surface * to); - CInfoPopup(SDL_Surface * Bitmap, int x, int y, bool Free=false); //c-tor - CInfoPopup(SDL_Surface * Bitmap, const Point &p, EAlignment alignment, bool Free=false); //c-tor - CInfoPopup(SDL_Surface * Bitmap = nullptr, bool Free = false); //default c-tor - - void init(int x, int y); - ~CInfoPopup(); //d-tor -}; - -/// popup on adventure map for town\hero objects -class CInfoBoxPopup : public CWindowObject -{ - Point toScreen(Point pos); -public: - CInfoBoxPopup(Point position, const CGTownInstance * town); - CInfoBoxPopup(Point position, const CGHeroInstance * hero); - CInfoBoxPopup(Point position, const CGGarrison * garr); -}; - -/// common popup window component -class CComponent : public virtual CIntObject -{ -public: - enum Etype - { - primskill, secskill, resource, creature, artifact, experience, spell, morale, luck, building, hero, flag, typeInvalid - }; - - //NOTE: not all types have exact these sizes or have less than 4 of them. In such cases closest one will be used - enum ESize - { - tiny, // ~22-24px - small, // ~30px - medium,// ~42px - large, // ~82px - sizeInvalid - }; - -private: - size_t getIndex(); - const std::vector getFileName(); - void setSurface(std::string defName, int imgPos); - std::string getSubtitleInternal(); - - void init(Etype Type, int Subtype, int Val, ESize imageSize); - -public: - CAnimImage *image; //our image - - Etype compType; //component type - ESize size; //component size. - int subtype; //type-dependant subtype. See getSomething methods for details - int val; // value \ strength \ amount of component. See getSomething methods for details - bool perDay; // add "per day" text to subtitle - - std::string getDescription(); - std::string getSubtitle(); - - CComponent(Etype Type, int Subtype, int Val = 0, ESize imageSize=large);//c-tor - CComponent(const Component &c); //c-tor - - void clickRight(tribool down, bool previousState); //call-in -}; - -/// component that can be selected or deselected -class CSelectableComponent : public CComponent, public CKeyShortcut -{ - void init(); -public: - bool selected; //if true, this component is selected - std::function onSelect; //function called on selection change - - void showAll(SDL_Surface * to); - void select(bool on); - - void clickLeft(tribool down, bool previousState); //call-in - CSelectableComponent(Etype Type, int Sub, int Val, ESize imageSize=large, std::function OnSelect = nullptr); //c-tor - CSelectableComponent(const Component &c, std::function OnSelect = nullptr); //c-tor -}; - -/// box with multiple components (up to 8?) -/// will take ownership on components and delete them afterwards -class CComponentBox : public CIntObject -{ - std::vector components; - - CSelectableComponent * selected; - std::function onSelect; - - void selectionChanged(CSelectableComponent * newSelection); - - //get position of "or" text between these comps - //it will place "or" equidistant to both images - Point getOrTextPos(CComponent *left, CComponent * right); - - //get distance between these copmonents - int getDistance(CComponent *left, CComponent * right); - void placeComponents(bool selectable); - -public: - /// return index of selected item - int selectedIndex(); - - /// constructor for quite common 1-components popups - /// if position width or height are 0 then it will be determined automatically - CComponentBox(CComponent * components, Rect position); - /// constructor for non-selectable components - CComponentBox(std::vector components, Rect position); - - /// constructor for selectable components - /// will also create "or" labels between components - /// onSelect - optional function that will be called every time on selection change - CComponentBox(std::vector components, Rect position, std::function onSelect = nullptr); -}; - -//////////////////////////////////////////////////////////////////////////////// - -/// base class for hero/town/garrison tooltips -class CArmyTooltip : public CIntObject -{ - void init(const InfoAboutArmy &army); -public: - CArmyTooltip(Point pos, const InfoAboutArmy &army); - CArmyTooltip(Point pos, const CArmedInstance * army); -}; - -/// Class for hero tooltip. Does not have any background! -/// background for infoBox: ADSTATHR -/// background for tooltip: HEROQVBK -class CHeroTooltip : public CArmyTooltip -{ - void init(const InfoAboutHero &hero); -public: - CHeroTooltip(Point pos, const InfoAboutHero &hero); - CHeroTooltip(Point pos, const CGHeroInstance * hero); -}; - -/// Class for town tooltip. Does not have any background! -/// background for infoBox: ADSTATCS -/// background for tooltip: TOWNQVBK -class CTownTooltip : public CArmyTooltip -{ - void init(const InfoAboutTown &town); -public: - CTownTooltip(Point pos, const InfoAboutTown &town); - CTownTooltip(Point pos, const CGTownInstance * town); -}; - -/////////////////////////////////////////////////////////////////////////////// - -class CGarrisonInt; - -/// A single garrison slot which holds one creature of a specific amount -class CGarrisonSlot : public CIntObject -{ - SlotID ID; //for identification - CGarrisonInt *owner; - const CStackInstance *myStack; //nullptr if slot is empty - const CCreature *creature; - int upg; //0 - up garrison, 1 - down garrison - - CAnimImage * creatureImage; - CAnimImage * selectionImage; // image for selection, not always visible - CLabel * stackCount; - - void setHighlight(bool on); -public: - virtual void hover (bool on); //call-in - const CArmedInstance * getObj() const; - bool our() const; - void clickRight(tribool down, bool previousState); - void clickLeft(tribool down, bool previousState); - void update(); - CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, int Upg=0, const CStackInstance * Creature=nullptr); - - friend class CGarrisonInt; -}; - -/// Class which manages slots of upper and lower garrison, splitting of units -class CGarrisonInt :public CIntObject -{ - CGarrisonSlot *highlighted; //chosen slot. Should be changed only via selectSlot - bool inSplittingMode; - -public: - void selectSlot(CGarrisonSlot * slot); //null = deselect - const CGarrisonSlot * getSelection(); - - void setSplittingMode(bool on); - bool getSplittingMode(); - - int interx; //space between slots - Point garOffset; //offset between garrisons (not used if only one hero) - std::vector splitButtons; //may be empty if no buttons - - SlotID p2; //TODO: comment me - int shiftPos;//1st slot of the second row, set shiftPoint for effect - bool pb, - smallIcons, //true - 32x32 imgs, false - 58x64 - removableUnits,//player can remove units from up - twoRows,//slots will be placed in 2 rows - owned[2];//player owns up or down army [0] upper, [1] lower - -// const CCreatureSet *set1; //top set of creatures -// const CCreatureSet *set2; //bottom set of creatures - - std::vector slotsUp, slotsDown; //slots of upper and lower garrison - const CArmedInstance *armedObjs[2]; //[0] is upper, [1] is down - //const CArmedInstance *oup, *odown; //upper and lower garrisons (heroes or towns) - - void setArmy(const CArmedInstance *army, bool bottomGarrison); - void addSplitBtn(CAdventureMapButton * button); - void createSet(std::vector &ret, const CCreatureSet * set, int posX, int distance, int posY, int Upg ); - - void createSlots(); - void recreateSlots(); - - void splitClick(); //handles click on split button - void splitStacks(int amountLeft, int amountRight); //TODO: comment me - //x, y - position; - //inx - distance between slots; - //pomsur, SurOffset - UNUSED - //s1, s2 - top and bottom armies; - //removableUnits - you can take units from top; - //smallImgs - units images size 64x58 or 32x32; - //twoRows - display slots in 2 row (1st row = 4 slots, 2nd = 3 slots) - CGarrisonInt(int x, int y, int inx, const Point &garsOffset, SDL_Surface *pomsur, const Point &SurOffset, const CArmedInstance *s1, const CArmedInstance *s2=nullptr, bool _removableUnits = true, bool smallImgs = false, bool _twoRows=false); //c-tor -}; - -/// draws picture with creature on background, use Animated=true to get animation -class CCreaturePic : public CIntObject -{ -private: - CPicture *bg; - CCreatureAnim *anim; //displayed animation - -public: - CCreaturePic(int x, int y, const CCreature *cre, bool Big=true, bool Animated=true); //c-tor -}; - -/// Recruitment window where you can recruit creatures -class CRecruitmentWindow : public CWindowObject -{ - class CCreatureCard : public CIntObject - { - CRecruitmentWindow * parent; - CCreaturePic *pic; //creature's animation - bool selected; - - void clickLeft(tribool down, bool previousState); - void clickRight(tribool down, bool previousState); - void showAll(SDL_Surface *to); - public: - const CCreature * creature; - si32 amount; - - void select(bool on); - - CCreatureCard(CRecruitmentWindow * window, const CCreature *crea, int totalAmount); - }; - - /// small class to display creature costs - class CCostBox : public CIntObject - { - std::map > resources; - public: - //res - resources to show - void set(TResources res); - //res - visible resources - CCostBox(Rect position, std::string title); - void createItems(TResources res); - }; - - std::function onRecruit; //void (int ID, int amount) <-- call to recruit creatures - - int level; - const CArmedInstance *dst; - - CCreatureCard * selected; - std::vector cards; - - CSlider *slider; //for selecting amount - CAdventureMapButton *maxButton, *buyButton, *cancelButton; - //labels for visible values - CLabel * title; - CLabel * availableValue; - CLabel * toRecruitValue; - CCostBox * costPerTroopValue; - CCostBox * totalCostValue; - - void select(CCreatureCard * card); - void buy(); - void sliderMoved(int to); - - void showAll(SDL_Surface *to); -public: - const CGDwelling * const dwelling; - CRecruitmentWindow(const CGDwelling *Dwelling, int Level, const CArmedInstance *Dst, const std::function & Recruit, int y_offset = 0); //creatures - pairs //c-tor - void availableCreaturesChanged(); -}; - -/// Split window where creatures can be split up into two single unit stacks -class CSplitWindow : public CWindowObject -{ - std::function callback; - int leftAmount; - int rightAmount; - - int leftMin; - int rightMin; - - CSlider *slider; - CCreaturePic *animLeft, *animRight; //creature's animation - CAdventureMapButton *ok, *cancel; - - CTextInput *leftInput, *rightInput; - void setAmountText(std::string text, bool left); - void setAmount(int value, bool left); - void sliderMoved(int value); - void apply(); - -public: - /** - * creature - displayed creature - * callback(leftAmount, rightAmount) - function to call on close - * leftMin, rightMin - minimal amount of creatures in each stack - * leftAmount, rightAmount - amount of creatures in each stack - */ - CSplitWindow(const CCreature * creature, std::function callback, - int leftMin, int rightMin, int leftAmount, int rightAmount); -}; - -/// Raised up level windowe where you can select one out of two skills -class CLevelWindow : public CWindowObject -{ - CComponentBox * box; //skills to select - std::function cb; - - void selectionChanged(unsigned to); -public: - - CLevelWindow(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector &skills, std::function callback); //c-tor - ~CLevelWindow(); //d-tor - -}; - -/// Resource bar like that at the bottom of the adventure map screen -class CMinorResDataBar : public CIntObject -{ -public: - SDL_Surface *bg; //background bitmap - void show(SDL_Surface * to); - void showAll(SDL_Surface * to); - CMinorResDataBar(); //c-tor - ~CMinorResDataBar(); //d-tor -}; - -/// Town portal, castle gate window -class CObjectListWindow : public CWindowObject -{ - class CItem : public CIntObject - { - CObjectListWindow *parent; - CLabel *text; - CPicture *border; - public: - const size_t index; - CItem(CObjectListWindow *parent, size_t id, std::string text); - - void select(bool on); - void clickLeft(tribool down, bool previousState); - }; - - std::function onSelect;//called when OK button is pressed, returns id of selected item. - CLabel * title; - CLabel * descr; - - CListBox * list; - CIntObject * titleImage;//title image (castle gate\town portal picture) - CAdventureMapButton *ok, *exit; - - std::vector< std::pair > items;//all items present in list - - void init(CIntObject * titlePic, std::string _title, std::string _descr); -public: - size_t selected;//index of currently selected item - /// Callback will be called when OK button is pressed, returns id of selected item. initState = initially selected item - /// Image can be nullptr - ///item names will be taken from map objects - CObjectListWindow(const std::vector &_items, CIntObject * titlePic, std::string _title, std::string _descr, - std::function Callback); - CObjectListWindow(const std::vector &_items, CIntObject * titlePic, std::string _title, std::string _descr, - std::function Callback); - - CIntObject *genItem(size_t index); - void elementSelected();//call callback and close this window - void changeSelection(size_t which); - void keyPressed (const SDL_KeyboardEvent & key); -}; - -class CArtifactHolder -{ -public: - CArtifactHolder(); - - virtual void artifactRemoved(const ArtifactLocation &artLoc)=0; - virtual void artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc)=0; - virtual void artifactDisassembled(const ArtifactLocation &artLoc)=0; - virtual void artifactAssembled(const ArtifactLocation &artLoc)=0; -}; - -class CWindowWithArtifacts : public CArtifactHolder -{ -public: - std::vector artSets; - - void artifactRemoved(const ArtifactLocation &artLoc); - void artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc); - void artifactDisassembled(const ArtifactLocation &artLoc); - void artifactAssembled(const ArtifactLocation &artLoc); -}; - -class CTradeWindow : public CWindowObject, public CWindowWithArtifacts //base for markets and altar of sacrifice -{ -public: - enum EType - { - RESOURCE, PLAYER, ARTIFACT_TYPE, CREATURE, CREATURE_PLACEHOLDER, ARTIFACT_PLACEHOLDER, ARTIFACT_INSTANCE - }; - class CTradeableItem : public CIntObject - { - CAnimImage * image; - - std::string getFilename(); - int getIndex(); - public: - const CArtifactInstance *hlp; //holds ptr to artifact instance id type artifact - EType type; - int id; - const int serial; - const bool left; - std::string subtitle; //empty if default - - void setType(EType newType); - void setID(int newID); - - const CArtifactInstance *getArtInstance() const; - void setArtInstance(const CArtifactInstance *art); - - CFunctionList callback; - bool downSelection; - - void showAllAt(const Point &dstPos, const std::string &customSub, SDL_Surface * to); - - void clickRight(tribool down, bool previousState); - void hover (bool on); - void showAll(SDL_Surface * to); - void clickLeft(tribool down, bool previousState); - std::string getName(int number = -1) const; - CTradeableItem(Point pos, EType Type, int ID, bool Left, int Serial); - }; - - const IMarket *market; - const CGHeroInstance *hero; - - CArtifactsOfHero *arts; - //all indexes: 1 = left, 0 = right - std::vector items[2]; - CTradeableItem *hLeft, *hRight; //highlighted items (nullptr if no highlight) - EType itemsType[2]; - - EMarketMode::EMarketMode mode;//0 - res<->res; 1 - res<->plauer; 2 - buy artifact; 3 - sell artifact - CAdventureMapButton *ok, *max, *deal; - CSlider *slider; //for choosing amount to be exchanged - bool readyToTrade; - - CTradeWindow(std::string bgName, const IMarket *Market, const CGHeroInstance *Hero, EMarketMode::EMarketMode Mode); //c - - void showAll(SDL_Surface * to); - - void initSubs(bool Left); - void initTypes(); - void initItems(bool Left); - std::vector *getItemsIds(bool Left); //nullptr if default - void getPositionsFor(std::vector &poss, bool Left, EType type) const; - void removeItems(const std::set &toRemove); - void removeItem(CTradeableItem * t); - void getEmptySlots(std::set &toRemove); - void setMode(EMarketMode::EMarketMode Mode); //mode setter - - void artifactSelected(CArtPlace *slot); //used when selling artifacts -> called when user clicked on artifact slot - - virtual void getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const = 0; - virtual void selectionChanged(bool side) = 0; //true == left - virtual Point selectionOffset(bool Left) const = 0; - virtual std::string selectionSubtitle(bool Left) const = 0; - virtual void garrisonChanged() = 0; - virtual void artifactsChanged(bool left) = 0; -}; - -class CMarketplaceWindow : public CTradeWindow -{ - bool printButtonFor(EMarketMode::EMarketMode M) const; - - std::string getBackgroundForMode(EMarketMode::EMarketMode mode); -public: - int r1, r2; //suggested amounts of traded resources - bool madeTransaction; //if player made at least one transaction - CTextBox *traderText; - - void setMax(); - void sliderMoved(int to); - void makeDeal(); - void selectionChanged(bool side); //true == left - CMarketplaceWindow(const IMarket *Market, const CGHeroInstance *Hero = nullptr, EMarketMode::EMarketMode Mode = EMarketMode::RESOURCE_RESOURCE); //c-tor - ~CMarketplaceWindow(); //d-tor - - Point selectionOffset(bool Left) const; - std::string selectionSubtitle(bool Left) const; - - - void garrisonChanged(); //removes creatures with count 0 from the list (apparently whole stack has been sold) - void artifactsChanged(bool left); - void resourceChanged(int type, int val); - - void getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const; - void updateTraderText(); -}; - -class CAltarWindow : public CTradeWindow -{ - CAnimImage * artIcon; -public: - CAltarWindow(const IMarket *Market, const CGHeroInstance *Hero, EMarketMode::EMarketMode Mode); //c-tor - - void getExpValues(); - ~CAltarWindow(); //d-tor - - std::vector sacrificedUnits, //[slot_nr] -> how many creatures from that slot will be sacrificed - expPerUnit; - - CAdventureMapButton *sacrificeAll, *sacrificeBackpack; - CLabel *expToLevel, *expOnAltar; - - - void selectionChanged(bool side); //true == left - void SacrificeAll(); - void SacrificeBackpack(); - - void putOnAltar(int backpackIndex); - bool putOnAltar(CTradeableItem* altarSlot, const CArtifactInstance *art); - void makeDeal(); - void showAll(SDL_Surface * to); - - void blockTrade(); - void sliderMoved(int to); - void getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const; - void mimicCres(); - - Point selectionOffset(bool Left) const; - std::string selectionSubtitle(bool Left) const; - void garrisonChanged(); - void artifactsChanged(bool left); - void calcTotalExp(); - void setExpToLevel(); - void updateRight(CTradeableItem *toUpdate); - - void artifactPicked(); - int firstFreeSlot(); - void moveFromSlotToAltar(ArtifactPosition slotID, CTradeableItem* altarSlot, const CArtifactInstance *art); -}; - -class CSystemOptionsWindow : public CWindowObject -{ -private: - CLabel *title; - CLabelGroup *leftGroup; - CLabelGroup *rightGroup; - CAdventureMapButton *load, *save, *restart, *mainMenu, *quitGame, *backToMap; //load and restart are not used yet - CHighlightableButtonsGroup * heroMoveSpeed; - CHighlightableButtonsGroup * mapScrollSpeed; - CHighlightableButtonsGroup * musicVolume, * effectsVolume; - - //CHighlightableButton * showPath; - CHighlightableButton * showReminder; - CHighlightableButton * quickCombat; - CHighlightableButton * spellbookAnim; - CHighlightableButton * newCreatureWin; - CHighlightableButton * fullscreen; - - CAdventureMapButton *gameResButton; - CLabel *gameResLabel; - - SettingsListener onFullscreenChanged; - - void setMusicVolume( int newVolume ); - void setSoundVolume( int newVolume ); - void setHeroMoveSpeed( int newSpeed ); - void setMapScrollingSpeed( int newSpeed ); - - //functions bound to buttons - void bloadf(); //load game - void bsavef(); //save game - void bquitf(); //quit game - void breturnf(); //return to game - void brestartf(); //restart game - void bmainmenuf(); //return to main menu - - //functions for checkboxes - void toggleReminder(bool on); - void toggleQuickCombat(bool on); - void toggleSpellbookAnim(bool on); - void toggleCreatureWin(bool on); - void toggleFullscreen(bool on); - - void selectGameRes(); - void setGameRes(int index); - void closeAndPushEvent(int eventType, int code = 0); - -public: - CSystemOptionsWindow(); //c-tor -}; - -class CTavernWindow : public CWindowObject -{ -public: - class HeroPortrait : public CIntObject - { - public: - std::string hoverName; - std::string description; // "XXX is a level Y ZZZ with N artifacts" - const CGHeroInstance *h; - - void clickLeft(tribool down, bool previousState); - void clickRight(tribool down, bool previousState); - void hover (bool on); - HeroPortrait(int &sel, int id, int x, int y, const CGHeroInstance *H); - - private: - int *_sel; - const int _id; - - } *h1, *h2; //recruitable heroes - - CGStatusBar *bar; //tavern's internal status bar - int selected;//0 (left) or 1 (right) - int oldSelected;//0 (left) or 1 (right) - - CAdventureMapButton *thiefGuild, *cancel, *recruit; - const CGObjectInstance *tavernObj; - - CTavernWindow(const CGObjectInstance *TavernObj); //c-tor - ~CTavernWindow(); //d-tor - - void recruitb(); - void thievesguildb(); - void show(SDL_Surface * to); -}; - -class CInGameConsole : public CIntObject -{ -private: - std::list< std::pair< std::string, int > > texts; //list - boost::mutex texts_mx; // protects texts - std::vector< std::string > previouslyEntered; //previously entered texts, for up/down arrows to work - int prevEntDisp; //displayed entry from previouslyEntered - if none it's -1 - int defaultTimeout; //timeout for new texts (in ms) - int maxDisplayedTexts; //hiw many texts can be displayed simultaneously -public: - std::string enteredText; - void show(SDL_Surface * to); - void print(const std::string &txt); - void keyPressed (const SDL_KeyboardEvent & key); //call-in - -#ifndef VCMI_SDL1 - void textInputed(const SDL_TextInputEvent & event) override; - void textEdited(const SDL_TextEditingEvent & event) override; -#endif // VCMI_SDL1 - - void startEnteringText(); - void endEnteringText(bool printEnteredText); - void refreshEnteredText(); - - CInGameConsole(); //c-tor -}; - -/// Can interact on left and right mouse clicks -class LRClickableAreaWTextComp: public LRClickableAreaWText -{ -public: - int baseType; - int bonusValue, type; - virtual void clickLeft(tribool down, bool previousState); - virtual void clickRight(tribool down, bool previousState); - - LRClickableAreaWTextComp(const Rect &Pos = Rect(0,0,0,0), int BaseType = -1); - CComponent * createComponent() const; -}; - -class MoraleLuckBox : public LRClickableAreaWTextComp -{ - CAnimImage *image; -public: - bool morale; //true if morale, false if luck - bool small; - - void set(const IBonusBearer *node); - - MoraleLuckBox(bool Morale, const Rect &r, bool Small=false); -}; - -/// Opens hero window by left-clicking on it -class CHeroArea: public CIntObject -{ - const CGHeroInstance * hero; -public: - - CHeroArea(int x, int y, const CGHeroInstance * _hero); - - void clickLeft(tribool down, bool previousState); - void clickRight(tribool down, bool previousState); - void hover(bool on); -}; - -/// Opens town screen by left-clicking on it -class LRClickableAreaOpenTown: public LRClickableAreaWTextComp -{ -public: - const CGTownInstance * town; - void clickLeft(tribool down, bool previousState); - void clickRight(tribool down, bool previousState); - LRClickableAreaOpenTown(); -}; - -/// Artifacts can be placed there. Gets shown at the hero window -class CArtPlace: public LRClickableAreaWTextComp -{ - CAnimImage *image; - CAnimImage *selection; - - void createImage(); - -public: - // consider these members as const - change them only with appropriate methods e.g. lockSlot() - bool locked; - bool picked; - bool marked; - - ArtifactPosition slotID; //Arts::EPOS enum + backpack starting from Arts::BACKPACK_START - - void lockSlot(bool on); - void pickSlot(bool on); - void selectSlot(bool on); - - CArtifactsOfHero * ourOwner; - const CArtifactInstance * ourArt; // should be changed only with setArtifact() - - CArtPlace(Point position, const CArtifactInstance * Art = nullptr); //c-tor - void clickLeft(tribool down, bool previousState); - void clickRight(tribool down, bool previousState); - void select (); - void deselect (); - void showAll(SDL_Surface * to); - bool fitsHere (const CArtifactInstance * art) const; //returns true if given artifact can be placed here - - void setMeAsDest(bool backpackAsVoid = true); - void setArtifact(const CArtifactInstance *art); -}; - -/// Contains artifacts of hero. Distincts which artifacts are worn or backpacked -class CArtifactsOfHero : public CIntObject -{ - const CGHeroInstance * curHero; - - std::vector artWorn; // 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 - std::vector backpack; //hero's visible backpack (only 5 elements!) - int backpackPos; //number of first art visible in backpack (in hero's vector) - -public: - struct SCommonPart - { - struct Artpos - { - ArtifactPosition slotID; - const CArtifactsOfHero *AOH; - const CArtifactInstance *art; - - Artpos(); - void clear(); - void setTo(const CArtPlace *place, bool dontTakeBackpack); - bool valid(); - bool operator==(const ArtifactLocation &al) const; - } src, dst; - - std::set participants; // Needed to mark slots. - - void reset(); - } * commonInfo; //when we have more than one CArtifactsOfHero in one window with exchange possibility, we use this (eg. in exchange window); to be provided externally - - bool updateState; // Whether the commonInfo should be updated on setHero or not. - - CAdventureMapButton * leftArtRoll, * rightArtRoll; - bool allowedAssembling; - std::multiset artifactsOnAltar; //artifacts id that are technically present in backpack but in GUI are moved to the altar - they'll be omitted in backpack slots - std::function highlightModeCallback; //if set, clicking on art place doesn't pick artifact but highlights the slot and calls this function - - void realizeCurrentTransaction(); //calls callback with parameters stored in commonInfo - void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst); - void artifactRemoved(const ArtifactLocation &al); - void artifactAssembled(const ArtifactLocation &al); - void artifactDisassembled(const ArtifactLocation &al); - CArtPlace *getArtPlace(int slot); - - void setHero(const CGHeroInstance * hero); - const CGHeroInstance *getHero() const; - void dispose(); //free resources not needed after closing windows and reset state - void scrollBackpack(int dir); //dir==-1 => to left; dir==1 => to right - - void safeRedraw(); - void markPossibleSlots(const CArtifactInstance* art); - void unmarkSlots(bool withRedraw = true); //unmarks slots in all visible AOHs - void unmarkLocalSlots(bool withRedraw = true); //unmarks slots in that particular AOH - void setSlotData (CArtPlace* artPlace, ArtifactPosition slotID); - void updateWornSlots (bool redrawParent = true); - - void updateSlot(ArtifactPosition i); - void eraseSlotData (CArtPlace* artPlace, ArtifactPosition slotID); - - CArtifactsOfHero(const Point& position, bool createCommonPart = false); - //Alternative constructor, used if custom artifacts positioning required (Kingdom interface) - CArtifactsOfHero(std::vector ArtWorn, std::vector Backpack, - CAdventureMapButton *leftScroll, CAdventureMapButton *rightScroll, bool createCommonPart = false); - ~CArtifactsOfHero(); //d-tor - void updateParentWindow(); - friend class CArtPlace; -}; - -class CGarrisonHolder -{ -public: - CGarrisonHolder(); - virtual void updateGarrisons()=0; -}; - -class CWindowWithGarrison : public virtual CGarrisonHolder -{ -public: - CGarrisonInt *garr; - virtual void updateGarrisons(); -}; - -/// Garrison window where you can take creatures out of the hero to place it on the garrison -class CGarrisonWindow : public CWindowObject, public CWindowWithGarrison -{ -public: - CAdventureMapButton * quit; - - CGarrisonWindow(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits); //c-tor -}; - -class CExchangeWindow : public CWindowObject, public CWindowWithGarrison, public CWindowWithArtifacts -{ - CGStatusBar * ourBar; //internal statusbar - - CAdventureMapButton * quit, * questlogButton[2]; - - std::vector secSkillAreas[2], primSkillAreas; - - MoraleLuckBox *morale[2], *luck[2]; - - LRClickableAreaWText *specialty[2]; - LRClickableAreaWText *experience[2]; - LRClickableAreaWText *spellPoints[2]; - CHeroArea *portrait[2]; - -public: - - const CGHeroInstance* heroInst[2]; - CArtifactsOfHero * artifs[2]; - - void questlog(int whichHero); //questlog button callback; whichHero: 0 - left, 1 - right - - void prepareBackground(); //prepares or redraws bg - - CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID); //c-tor - ~CExchangeWindow(); //d-tor -}; - -/// Here you can buy ships -class CShipyardWindow : public CWindowObject -{ -public: - CGStatusBar *bar; - CPicture *bgWater; - - CLabel *title; - CLabel *costLabel; - - CAnimImage *woodPic, *goldPic; - CLabel *woodCost, *goldCost; - - CAnimImage *bgShip; - CAdventureMapButton *build, *quit; - - CGStatusBar * statusBar; - - CShipyardWindow(const std::vector &cost, int state, int boatType, const std::function &onBuy); -}; - -/// Puzzle screen which gets uncovered when you visit obilisks -class CPuzzleWindow : public CWindowObject -{ -private: - int3 grailPos; - - CAdventureMapButton * quitb; - - std::vector piecesToRemove; - ui8 currentAlpha; - -public: - void showAll(SDL_Surface * to); - void show(SDL_Surface * to); - - CPuzzleWindow(const int3 &grailPos, double discoveredRatio); -}; - -/// Creature transformer window -class CTransformerWindow : public CWindowObject, public CGarrisonHolder -{ -public: - class CItem : public CIntObject - { - public: - int id;//position of creature in hero army - bool left;//position of the item - int size; //size of creature stack - CTransformerWindow * parent; - CAnimImage *icon; - - void move(); - void clickLeft(tribool down, bool previousState); - void update(); - CItem(CTransformerWindow * parent, int size, int id); - }; - - const CArmedInstance *army;//object with army for transforming (hero or town) - const CGHeroInstance *hero;//only if we have hero in town - const CGTownInstance *town;//market, town garrison is used if hero == nullptr - std::vector items; - - CAdventureMapButton *all, *convert, *cancel; - CGStatusBar *bar; - void makeDeal(); - void addAll(); - void updateGarrisons(); - CTransformerWindow(const CGHeroInstance * _hero, const CGTownInstance * _town); //c-tor -}; - -class CUniversityWindow : public CWindowObject -{ - class CItem : public CAnimImage - { - public: - int ID;//id of selected skill - CUniversityWindow * parent; - - void showAll(SDL_Surface * to); - void clickLeft(tribool down, bool previousState); - void clickRight(tribool down, bool previousState); - void hover(bool on); - int state();//0=can't learn, 1=learned, 2=can learn - CItem(CUniversityWindow * _parent, int _ID, int X, int Y); - }; - -public: - const CGHeroInstance *hero; - const IMarket * market; - - CPicture * green, * yellow, * red;//colored bars near skills - std::vector items; - - CAdventureMapButton *cancel; - CGStatusBar *bar; - - CUniversityWindow(const CGHeroInstance * _hero, const IMarket * _market); //c-tor -}; - -/// Confirmation window for University -class CUnivConfirmWindow : public CWindowObject -{ -public: - CUniversityWindow * parent; - CGStatusBar *bar; - CAdventureMapButton *confirm, *cancel; - - CUnivConfirmWindow(CUniversityWindow * PARENT, int SKILL, bool available); //c-tor - void makeDeal(int skill); -}; - -/// Hill fort is the building where you can upgrade units -class CHillFortWindow : public CWindowObject, public CWindowWithGarrison -{ -public: - - int slotsCount;//=7; - CGStatusBar * bar; - CDefEssential *resources; - CHeroArea *heroPic;//clickable hero image - CAdventureMapButton *quit,//closes window - *upgradeAll,//upgrade all creatures - *upgrade[7];//upgrade single creature - - const CGObjectInstance * fort; - const CGHeroInstance * hero; - std::vector currState;//current state of slot - to avoid calls to getState or updating buttons - std::vector costs;// costs [slot ID] [resource ID] = resource count for upgrade - TResources totalSumm; // totalSum[resource ID] = value - - CHillFortWindow(const CGHeroInstance *visitor, const CGObjectInstance *object); //c-tor - - void showAll (SDL_Surface *to); - std::string getDefForSlot(SlotID slot);//return def name for this slot - std::string getTextForSlot(SlotID slot);//return hover text for this slot - void makeDeal(SlotID slot);//-1 for upgrading all creatures - int getState(SlotID slot); //-1 = no creature 0=can't upgrade, 1=upgraded, 2=can upgrade - void updateGarrisons();//update buttons after garrison changes -}; - -class CThievesGuildWindow : public CWindowObject -{ - const CGObjectInstance * owner; - - CGStatusBar * statusBar; - CAdventureMapButton * exitb; - CMinorResDataBar * resdatabar; - -public: - CThievesGuildWindow(const CGObjectInstance * _owner); -}; diff --git a/client/Graphics.cpp b/client/Graphics.cpp index d7acf1dc9..764c8c560 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -21,7 +21,6 @@ #include "../lib/vcmi_endian.h" #include "../lib/GameConstants.h" #include "../lib/CStopWatch.h" -#include "CAnimation.h" #include "../lib/mapObjects/CObjectClassesHandler.h" using namespace boost::assign; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 27f8ea74d..b0c159217 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -17,7 +17,7 @@ #include "../lib/CSoundBase.h" #include "../lib/StartInfo.h" #include "mapHandler.h" -#include "GUIClasses.h" +#include "windows/GUIClasses.h" #include "../lib/CConfigHandler.h" #include "gui/SDL_Extensions.h" #include "battle/CBattleInterface.h" @@ -26,6 +26,8 @@ #include "../lib/BattleState.h" #include "../lib/GameConstants.h" #include "gui/CGuiHandler.h" +#include "widgets/MiscWidgets.h" +#include "widgets/AdventureMapClasses.h" #include "CMT.h" //macros to avoid code duplication - calls given method with given arguments if interface for specific player is present diff --git a/client/battle/CBattleAnimations.cpp b/client/battle/CBattleAnimations.cpp index 001150e3f..2bb7f0da1 100644 --- a/client/battle/CBattleAnimations.cpp +++ b/client/battle/CBattleAnimations.cpp @@ -19,6 +19,7 @@ #include "../../CCallback.h" #include "../../lib/BattleState.h" #include "../../lib/CTownHandler.h" +#include "../../lib/mapObjects/CGTownInstance.h" /* * CBattleAnimations.cpp, part of VCMI engine diff --git a/client/battle/CBattleAnimations.h b/client/battle/CBattleAnimations.h index 482320860..5ca8d6b50 100644 --- a/client/battle/CBattleAnimations.h +++ b/client/battle/CBattleAnimations.h @@ -1,7 +1,7 @@ #pragma once -#include "../CAnimation.h" #include "../../lib/BattleHex.h" +#include "../widgets/Images.h" class CBattleInterface; class CStack; diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 12dfcbbdc..c582cbec4 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -1,40 +1,38 @@ #include "StdInc.h" #include "CBattleInterface.h" -#include "../CGameInfo.h" -#include "../gui/SDL_Extensions.h" -#include "../CAdvmapInterface.h" -#include "../CAnimation.h" -#include "../CBitmapHandler.h" -#include "../../lib/CHeroHandler.h" -# include "../CDefHandler.h" -#include "../../lib/CSpellHandler.h" -#include "../CMusicHandler.h" -#include "../CMessage.h" -#include "../../CCallback.h" -#include "../../lib/BattleState.h" -#include "../../lib/CGeneralTextHandler.h" -#include "CCreatureAnimation.h" -#include "../Graphics.h" -#include "../CSpellWindow.h" -#include "../../lib/CConfigHandler.h" -#include "../../lib/CondSh.h" -#include "../../lib/NetPacks.h" -#include "../CPlayerInterface.h" -#include "../CCreatureWindow.h" -#include "../CVideoHandler.h" -#include "../../lib/CTownHandler.h" -#include "../../lib/mapping/CMap.h" -#include "../../lib/CRandomGenerator.h" - #include "CBattleAnimations.h" #include "CBattleInterfaceClasses.h" +#include "CCreatureAnimation.h" +#include "../CBitmapHandler.h" +#include "../CDefHandler.h" +#include "../CGameInfo.h" +#include "../CMessage.h" +#include "../CMT.h" +#include "../CMusicHandler.h" +#include "../CPlayerInterface.h" +#include "../CVideoHandler.h" +#include "../Graphics.h" #include "../gui/CCursorHandler.h" #include "../gui/CGuiHandler.h" -#include "../CMT.h" - +#include "../gui/SDL_Extensions.h" +#include "../windows/CAdvmapInterface.h" +#include "../windows/CCreatureWindow.h" +#include "../windows/CSpellWindow.h" +#include "../../CCallback.h" +#include "../../lib/BattleState.h" +#include "../../lib/CConfigHandler.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CHeroHandler.h" +#include "../../lib/CondSh.h" +#include "../../lib/CRandomGenerator.h" +#include "../../lib/CSpellHandler.h" +#include "../../lib/CTownHandler.h" +#include "../../lib/CGameState.h" +#include "../../lib/mapping/CMap.h" +#include "../../lib/NetPacks.h" #include "../../lib/UnlockGuard.h" using namespace boost::assign; @@ -224,17 +222,17 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe // blitAt(menu, pos.x, 556 + pos.y); //preparing buttons and console - bOptions = new CAdventureMapButton (CGI->generaltexth->zelp[381].first, CGI->generaltexth->zelp[381].second, std::bind(&CBattleInterface::bOptionsf,this), 3, 561, "icm003.def", SDLK_o); - bSurrender = new CAdventureMapButton (CGI->generaltexth->zelp[379].first, CGI->generaltexth->zelp[379].second, std::bind(&CBattleInterface::bSurrenderf,this), 54, 561, "icm001.def", SDLK_s); - bFlee = new CAdventureMapButton (CGI->generaltexth->zelp[380].first, CGI->generaltexth->zelp[380].second, std::bind(&CBattleInterface::bFleef,this), 105, 561, "icm002.def", SDLK_r); - bAutofight = new CAdventureMapButton (CGI->generaltexth->zelp[382].first, CGI->generaltexth->zelp[382].second, std::bind(&CBattleInterface::bAutofightf,this), 157, 561, "icm004.def", SDLK_a); - bSpell = new CAdventureMapButton (CGI->generaltexth->zelp[385].first, CGI->generaltexth->zelp[385].second, std::bind(&CBattleInterface::bSpellf,this), 645, 561, "icm005.def", SDLK_c); - bWait = new CAdventureMapButton (CGI->generaltexth->zelp[386].first, CGI->generaltexth->zelp[386].second, std::bind(&CBattleInterface::bWaitf,this), 696, 561, "icm006.def", SDLK_w); - bDefence = new CAdventureMapButton (CGI->generaltexth->zelp[387].first, CGI->generaltexth->zelp[387].second, std::bind(&CBattleInterface::bDefencef,this), 747, 561, "icm007.def", SDLK_d); + bOptions = new CButton (Point( 3, 561), "icm003.def", CGI->generaltexth->zelp[381], std::bind(&CBattleInterface::bOptionsf,this), SDLK_o); + bSurrender = new CButton (Point( 54, 561), "icm001.def", CGI->generaltexth->zelp[379], std::bind(&CBattleInterface::bSurrenderf,this), SDLK_s); + bFlee = new CButton (Point(105, 561), "icm002.def", CGI->generaltexth->zelp[380], std::bind(&CBattleInterface::bFleef,this), SDLK_r); + bAutofight = new CButton (Point(157, 561), "icm004.def", CGI->generaltexth->zelp[382], std::bind(&CBattleInterface::bAutofightf,this), SDLK_a); + bSpell = new CButton (Point(645, 561), "icm005.def", CGI->generaltexth->zelp[385], std::bind(&CBattleInterface::bSpellf,this), SDLK_c); + bWait = new CButton (Point(696, 561), "icm006.def", CGI->generaltexth->zelp[386], std::bind(&CBattleInterface::bWaitf,this), SDLK_w); + bDefence = new CButton (Point(747, 561), "icm007.def", CGI->generaltexth->zelp[387], std::bind(&CBattleInterface::bDefencef,this), SDLK_d); bDefence->assignedKeys.insert(SDLK_SPACE); - bConsoleUp = new CAdventureMapButton (std::string(), std::string(), std::bind(&CBattleInterface::bConsoleUpf,this), 624, 561, "ComSlide.def", SDLK_UP); - bConsoleDown = new CAdventureMapButton (std::string(), std::string(), std::bind(&CBattleInterface::bConsoleDownf,this), 624, 580, "ComSlide.def", SDLK_DOWN); - bConsoleDown->setOffset(2); + bConsoleUp = new CButton (Point(624, 561), "ComSlide.def", std::make_pair("", ""), std::bind(&CBattleInterface::bConsoleUpf,this), SDLK_UP); + bConsoleDown = new CButton (Point(624, 580), "ComSlide.def", std::make_pair("", ""), std::bind(&CBattleInterface::bConsoleDownf,this), SDLK_DOWN); + bConsoleDown->setImageOrder(2, 3, 4, 5); console = new CBattleConsole(); console->pos.x += 211; console->pos.y += 560; @@ -242,8 +240,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe console->pos.h = 38; if(tacticsMode) { - btactNext = new CAdventureMapButton(std::string(), std::string(), std::bind(&CBattleInterface::bTacticNextStack,this, (CStack*)nullptr), 213, 560, "icm011.def", SDLK_SPACE); - btactEnd = new CAdventureMapButton(std::string(), std::string(), std::bind(&CBattleInterface::bEndTacticPhase,this), 419, 560, "icm012.def", SDLK_RETURN); + btactNext = new CButton(Point(213, 560), "icm011.def", std::make_pair("", ""), [&]{ bTacticNextStack(nullptr);}, SDLK_SPACE); + btactEnd = new CButton(Point(419, 560), "icm012.def", std::make_pair("", ""), [&]{ bEndTacticPhase();}, SDLK_RETURN); menu = BitmapHandler::loadBitmap("COPLACBR.BMP"); } else @@ -2432,7 +2430,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) { cursorFrame = ECursor::COMBAT_QUERY; consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str(); - realizeAction = [=]{ GH.pushInt(createCreWindow(shere, true)); }; + realizeAction = [=]{ GH.pushInt(new CStackWindow(shere, false)); }; break; } } diff --git a/client/battle/CBattleInterface.h b/client/battle/CBattleInterface.h index 1672180f0..cbb11ad94 100644 --- a/client/battle/CBattleInterface.h +++ b/client/battle/CBattleInterface.h @@ -1,10 +1,10 @@ #pragma once -#include "../../lib/CCreatureSet.h" +//#include "../../lib/CCreatureSet.h" #include "../../lib/ConstTransitivePtr.h" //may be reundant -#include "../CAnimation.h" #include "../../lib/GameConstants.h" + #include "CBattleAnimations.h" /* @@ -23,9 +23,9 @@ class CGHeroInstance; class CDefHandler; class CStack; class CCallback; -class CAdventureMapButton; -class CHighlightableButton; -class CHighlightableButtonsGroup; +class CButton; +class CToggleButton; +class CToggleGroup; struct BattleResult; struct BattleSpellCast; struct CObstacleInstance; @@ -120,7 +120,7 @@ class CBattleInterface : public CIntObject }; private: SDL_Surface * background, * menu, * amountNormal, * amountNegative, * amountPositive, * amountEffNeutral, * cellBorders, * backgroundWithHexes; - CAdventureMapButton * bOptions, * bSurrender, * bFlee, * bAutofight, * bSpell, + CButton * bOptions, * bSurrender, * bFlee, * bAutofight, * bSpell, * bWait, * bDefence, * bConsoleUp, * bConsoleDown, *btactNext, *btactEnd; CBattleConsole * console; CBattleHero * attackingHero, * defendingHero; //fighting heroes @@ -338,7 +338,7 @@ public: InfoAboutHero enemyHero() const; friend class CPlayerInterface; - friend class CAdventureMapButton; + friend class CButton; friend class CInGameConsole; friend class CBattleResultWindow; diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index 28560cd75..b5123b07a 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -1,29 +1,34 @@ #include "StdInc.h" #include "CBattleInterfaceClasses.h" -#include "../gui/SDL_Extensions.h" #include "CBattleInterface.h" -#include "../CGameInfo.h" -#include "../CDefHandler.h" -#include "../gui/CCursorHandler.h" -#include "../CPlayerInterface.h" -#include "../../CCallback.h" -#include "../CSpellWindow.h" -#include "../Graphics.h" -#include "../../lib/CConfigHandler.h" -#include "../gui/CGuiHandler.h" -#include "../gui/CIntObjectClasses.h" -#include "../../lib/CGeneralTextHandler.h" -#include "../../lib/NetPacks.h" -#include "../../lib/CCreatureHandler.h" -#include "../../lib/BattleState.h" -#include "../../lib/StartInfo.h" -#include "../CMusicHandler.h" -#include "../CVideoHandler.h" -#include "../../lib/CTownHandler.h" + #include "../CBitmapHandler.h" -#include "../CCreatureWindow.h" +#include "../CDefHandler.h" +#include "../CGameInfo.h" #include "../CMessage.h" +#include "../CMusicHandler.h" +#include "../CPlayerInterface.h" +#include "../CVideoHandler.h" +#include "../Graphics.h" +#include "../gui/CCursorHandler.h" +#include "../gui/CGuiHandler.h" +#include "../gui/SDL_Extensions.h" +#include "../widgets/Buttons.h" +#include "../widgets/TextControls.h" +#include "../windows/CCreatureWindow.h" +#include "../windows/CSpellWindow.h" + +#include "../../CCallback.h" +#include "../../lib/BattleState.h" +#include "../../lib/CConfigHandler.h" +#include "../../lib/CCreatureHandler.h" +#include "../../lib/CGameState.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CTownHandler.h" +#include "../../lib/NetPacks.h" +#include "../../lib/StartInfo.h" +#include "../../lib/CondSh.h" /* * CBattleInterfaceClasses.cpp, part of VCMI engine @@ -258,26 +263,23 @@ CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInt background = new CPicture("comopbck.bmp"); background->colorize(owner->getCurrentPlayerInterface()->playerID); - viewGrid = new CHighlightableButton(std::bind(&CBattleInterface::setPrintCellBorders, owner, true), std::bind(&CBattleInterface::setPrintCellBorders, owner, false), boost::assign::map_list_of(0,CGI->generaltexth->zelp[427].first)(3,CGI->generaltexth->zelp[427].first), CGI->generaltexth->zelp[427].second, false, "sysopchk.def", nullptr, 25, 56, false); - viewGrid->select(settings["battle"]["cellBorders"].Bool()); - movementShadow = new CHighlightableButton(std::bind(&CBattleInterface::setPrintStackRange, owner, true), std::bind(&CBattleInterface::setPrintStackRange, owner, false), boost::assign::map_list_of(0,CGI->generaltexth->zelp[428].first)(3,CGI->generaltexth->zelp[428].first), CGI->generaltexth->zelp[428].second, false, "sysopchk.def", nullptr, 25, 89, false); - movementShadow->select(settings["battle"]["stackRange"].Bool()); - mouseShadow = new CHighlightableButton(std::bind(&CBattleInterface::setPrintMouseShadow, owner, true), std::bind(&CBattleInterface::setPrintMouseShadow, owner, false), boost::assign::map_list_of(0,CGI->generaltexth->zelp[429].first)(3,CGI->generaltexth->zelp[429].first), CGI->generaltexth->zelp[429].second, false, "sysopchk.def", nullptr, 25, 122, false); - mouseShadow->select(settings["battle"]["mouseShadow"].Bool()); + viewGrid = new CToggleButton(Point(25, 56), "sysopchk.def", CGI->generaltexth->zelp[427], [&](bool on){owner->setPrintCellBorders(on);} ); + viewGrid->setSelected(settings["battle"]["cellBorders"].Bool()); + movementShadow = new CToggleButton(Point(25, 89), "sysopchk.def", CGI->generaltexth->zelp[428], [&](bool on){owner->setPrintStackRange(on);}); + movementShadow->setSelected(settings["battle"]["stackRange"].Bool()); + mouseShadow = new CToggleButton(Point(25, 122), "sysopchk.def", CGI->generaltexth->zelp[429], [&](bool on){owner->setPrintMouseShadow(on);}); + mouseShadow->setSelected(settings["battle"]["mouseShadow"].Bool()); - animSpeeds = new CHighlightableButtonsGroup(0); - animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[422].first),CGI->generaltexth->zelp[422].second, "sysopb9.def", 28, 225, 40); - animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[423].first),CGI->generaltexth->zelp[423].second, "sysob10.def", 92, 225, 63); - animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[424].first),CGI->generaltexth->zelp[424].second, "sysob11.def",156, 225, 100); - animSpeeds->select(owner->getAnimSpeed(), 1); - animSpeeds->onChange = std::bind(&CBattleInterface::setAnimSpeed, owner, _1); + animSpeeds = new CToggleGroup([&](int value){ owner->setAnimSpeed(value);}); + animSpeeds->addToggle(40, new CToggleButton(Point( 28, 225), "sysopb9.def", CGI->generaltexth->zelp[422])); + animSpeeds->addToggle(63, new CToggleButton(Point( 92, 225), "sysob10.def", CGI->generaltexth->zelp[423])); + animSpeeds->addToggle(100, new CToggleButton(Point(156, 225), "sysob11.def", CGI->generaltexth->zelp[424])); + animSpeeds->setSelected(owner->getAnimSpeed()); - setToDefault = new CAdventureMapButton (CGI->generaltexth->zelp[393], std::bind(&CBattleOptionsWindow::bDefaultf,this), 246, 359, "codefaul.def"); - setToDefault->swappedImages = true; - setToDefault->update(); - exit = new CAdventureMapButton (CGI->generaltexth->zelp[392], std::bind(&CBattleOptionsWindow::bExitf,this), 357, 359, "soretrn.def",SDLK_RETURN); - exit->swappedImages = true; - exit->update(); + setToDefault = new CButton (Point(246, 359), "codefaul.def", CGI->generaltexth->zelp[393], [&]{ bDefaultf(); }); + setToDefault->setImageOrder(1, 0, 2, 3); + exit = new CButton (Point(357, 359), "soretrn.def", CGI->generaltexth->zelp[392], [&]{ bExitf();}, SDLK_RETURN); + exit->setImageOrder(1, 0, 2, 3); //creating labels labels.push_back(new CLabel(242, 32, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[392]));//window title @@ -307,6 +309,7 @@ CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInt void CBattleOptionsWindow::bDefaultf() { + //TODO: implement } void CBattleOptionsWindow::bExitf() @@ -322,9 +325,8 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult &br, const SDL_Rect CPicture * bg = new CPicture("CPRESULT"); bg->colorize(owner.playerID); - exit = new CAdventureMapButton ("", "", std::bind(&CBattleResultWindow::bExitf,this), 384, 505, "iok6432.def", SDLK_RETURN); + exit = new CButton (Point(384, 505), "iok6432.def", std::make_pair("", ""), [&]{ bExitf();}, SDLK_RETURN); exit->borderColor = Colors::METALLIC_GOLD; - exit->borderEnabled = true; if(br.winner==0) //attacker won { @@ -606,7 +608,7 @@ void CClickableHex::clickRight(tribool down, bool previousState) if(!myst->alive()) return; if(down) { - GH.pushInt(createCreWindow(myst)); + GH.pushInt(new CStackWindow(myst, true)); } } } @@ -701,7 +703,7 @@ CStackQueue::StackBox::StackBox(bool small): small(small) { OBJ_CONSTRUCTION_CAPTURING_ALL; - bg = new CPicture(small ? "StackQueueBgSmall" : "StackQueueBgBig" ); + bg = new CPicture(small ? "StackQueueSmall" : "StackQueueLarge" ); if (small) { diff --git a/client/battle/CBattleInterfaceClasses.h b/client/battle/CBattleInterfaceClasses.h index 055e1a672..3f20c7206 100644 --- a/client/battle/CBattleInterfaceClasses.h +++ b/client/battle/CBattleInterfaceClasses.h @@ -8,9 +8,9 @@ class CDefHandler; class CGHeroInstance; class CBattleInterface; class CPicture; -class CAdventureMapButton; -class CHighlightableButton; -class CHighlightableButtonsGroup; +class CButton; +class CToggleButton; +class CToggleGroup; class CLabel; struct BattleResult; class CStack; @@ -72,9 +72,9 @@ class CBattleOptionsWindow : public CIntObject { private: CPicture * background; - CAdventureMapButton * setToDefault, * exit; - CHighlightableButton * viewGrid, * movementShadow, * mouseShadow; - CHighlightableButtonsGroup * animSpeeds; + CButton * setToDefault, * exit; + CToggleButton * viewGrid, * movementShadow, * mouseShadow; + CToggleGroup * animSpeeds; std::vector labels; public: @@ -88,7 +88,7 @@ public: class CBattleResultWindow : public CIntObject { private: - CAdventureMapButton *exit; + CButton *exit; CPlayerInterface &owner; public: CBattleResultWindow(const BattleResult & br, const SDL_Rect & pos, CPlayerInterface &_owner); //c-tor @@ -152,4 +152,4 @@ public: void update(); void showAll(SDL_Surface *to); void blitBg(SDL_Surface * to); -}; \ No newline at end of file +}; diff --git a/client/battle/CCreatureAnimation.cpp b/client/battle/CCreatureAnimation.cpp index 483ae7192..bbca7129f 100644 --- a/client/battle/CCreatureAnimation.cpp +++ b/client/battle/CCreatureAnimation.cpp @@ -1,16 +1,16 @@ #include "StdInc.h" #include "CCreatureAnimation.h" +#include "../../lib/vcmi_endian.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CCreatureHandler.h" -#include "../../lib/vcmi_endian.h" -#include "../gui/SDL_Extensions.h" -#include "../gui/SDL_Pixels.h" - #include "../../lib/filesystem/Filesystem.h" #include "../../lib/filesystem/CBinaryReader.h" #include "../../lib/filesystem/CMemoryStream.h" +#include "../gui/SDL_Extensions.h" +#include "../gui/SDL_Pixels.h" + /* * CCreatureAnimation.cpp, part of VCMI engine * diff --git a/client/battle/CCreatureAnimation.h b/client/battle/CCreatureAnimation.h index a0fe3f24b..6896ee196 100644 --- a/client/battle/CCreatureAnimation.h +++ b/client/battle/CCreatureAnimation.h @@ -1,7 +1,8 @@ #pragma once #include "../../lib/FunctionList.h" -#include "../CAnimation.h" +#include "../gui/SDL_Extensions.h" +#include "../widgets/Images.h" /* * CCreatureAnimation.h, part of VCMI engine diff --git a/client/CAnimation.cpp b/client/gui/CAnimation.cpp similarity index 77% rename from client/CAnimation.cpp rename to client/gui/CAnimation.cpp index fa9b86a06..0ff4ca00b 100644 --- a/client/CAnimation.cpp +++ b/client/gui/CAnimation.cpp @@ -1,17 +1,18 @@ #include "StdInc.h" +#include "CAnimation.h" + #include +#include "../CBitmapHandler.h" +#include "../Graphics.h" +#include "../gui/SDL_Extensions.h" +#include "../gui/SDL_Pixels.h" + #include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/ISimpleResourceLoader.h" #include "../lib/JsonNode.h" #include "../lib/CRandomGenerator.h" -#include "CBitmapHandler.h" -#include "Graphics.h" -#include "CAnimation.h" -#include "gui/SDL_Extensions.h" -#include "gui/SDL_Pixels.h" - /* * CAnimation.cpp, part of VCMI engine * @@ -1221,319 +1222,3 @@ void CAnimation::getAnimInfo() logGlobal->errorStream()<<", "<images.begin()->second.size()<<" image loaded in group "<< anim->images.begin()->first; } } - -CAnimImage::CAnimImage(std::string name, size_t Frame, size_t Group, int x, int y, ui8 Flags): - frame(Frame), - group(Group), - player(-1), - flags(Flags) -{ - pos.x += x; - pos.y += y; - anim = new CAnimation(name); - init(); -} - -CAnimImage::CAnimImage(CAnimation *Anim, size_t Frame, size_t Group, int x, int y, ui8 Flags): - anim(Anim), - frame(Frame), - group(Group), - player(-1), - flags(Flags) -{ - pos.x += x; - pos.y += y; - init(); -} - -size_t CAnimImage::size() -{ - return anim->size(group); -} - -void CAnimImage::init() -{ - anim->load(frame, group); - if (flags & CShowableAnim::BASE) - anim->load(0,group); - - IImage *img = anim->getImage(frame, group); - if (img) - { - pos.w = img->width(); - pos.h = img->height(); - } -} - -CAnimImage::~CAnimImage() -{ - anim->unload(frame, group); - if (flags & CShowableAnim::BASE) - anim->unload(0,group); - delete anim; -} - -void CAnimImage::showAll(SDL_Surface * to) -{ - IImage *img; - - if ( flags & CShowableAnim::BASE && frame != 0) - if ((img = anim->getImage(0, group))) - img->draw(to, pos.x, pos.y); - - if ((img = anim->getImage(frame, group))) - img->draw(to, pos.x, pos.y); -} - -void CAnimImage::setFrame(size_t Frame, size_t Group) -{ - if (frame == Frame && group==Group) - return; - if (anim->size(Group) > Frame) - { - anim->load(Frame, Group); - anim->unload(frame, group); - frame = Frame; - group = Group; - IImage *img = anim->getImage(frame, group); - if (img) - { - if (flags & CShowableAnim::PLAYER_COLORED) - img->playerColored(player); - pos.w = img->width(); - pos.h = img->height(); - } - } - else - logGlobal->errorStream() << "Error: accessing unavailable frame " << Group << ":" << Frame << " in CAnimation!"; -} - -void CAnimImage::playerColored(PlayerColor currPlayer) -{ - player = currPlayer; - flags |= CShowableAnim::PLAYER_COLORED; - anim->getImage(frame, group)->playerColored(player); - if (flags & CShowableAnim::BASE) - anim->getImage(0, group)->playerColored(player); -} - -CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Delay, size_t Group): - anim(name, Flags & USE_RLE), - group(Group), - frame(0), - first(0), - frameDelay(Delay), - value(0), - flags(Flags), - xOffset(0), - yOffset(0), - alpha(255) -{ - anim.loadGroup(group); - last = anim.size(group); - - pos.w = anim.getImage(0, group)->width(); - pos.h = anim.getImage(0, group)->height(); - pos.x+= x; - pos.y+= y; -} - -CShowableAnim::~CShowableAnim() -{ - anim.unloadGroup(group); -} - -void CShowableAnim::setAlpha(ui32 alphaValue) -{ - alpha = std::min(alphaValue, 255); -} - -bool CShowableAnim::set(size_t Group, size_t from, size_t to) -{ - size_t max = anim.size(Group); - - if (to < max) - max = to; - - if (max < from || max == 0) - return false; - - anim.load(Group); - anim.unload(group); - group = Group; - frame = first = from; - last = max; - value = 0; - return true; -} - -bool CShowableAnim::set(size_t Group) -{ - if (anim.size(Group)== 0) - return false; - if (group != Group) - { - anim.loadGroup(Group); - anim.unloadGroup(group); - first = 0; - group = Group; - last = anim.size(Group); - } - frame = value = 0; - return true; -} - -void CShowableAnim::reset() -{ - value = 0; - frame = first; - - if (callback) - callback(); -} - -void CShowableAnim::clipRect(int posX, int posY, int width, int height) -{ - xOffset = posX; - yOffset = posY; - pos.w = width; - pos.h = height; -} - -void CShowableAnim::show(SDL_Surface * to) -{ - if ( flags & BASE )// && frame != first) // FIXME: results in graphical glytch in Fortress, upgraded hydra's dwelling - blitImage(first, group, to); - blitImage(frame, group, to); - - if ((flags & PLAY_ONCE) && frame + 1 == last) - return; - - if ( ++value == frameDelay ) - { - value = 0; - if ( ++frame >= last) - reset(); - } -} - -void CShowableAnim::showAll(SDL_Surface * to) -{ - if ( flags & BASE )// && frame != first) - blitImage(first, group, to); - blitImage(frame, group, to); -} - -void CShowableAnim::blitImage(size_t frame, size_t group, SDL_Surface *to) -{ - assert(to); - Rect src( xOffset, yOffset, pos.w, pos.h); - IImage * img = anim.getImage(frame, group); - if (img) - img->draw(to, pos.x-xOffset, pos.y-yOffset, &src, alpha); -} - -void CShowableAnim::rotate(bool on, bool vertical) -{ - ui8 flag = vertical? VERTICAL_FLIP:HORIZONTAL_FLIP; - if (on) - flags |= flag; - else - flags &= ~flag; -} - -CCreatureAnim::CCreatureAnim(int x, int y, std::string name, Rect picPos, ui8 flags, EAnimType type): - CShowableAnim(x,y,name,flags,4,type) -{ - xOffset = picPos.x; - yOffset = picPos.y; - if (picPos.w) - pos.w = picPos.w; - if (picPos.h) - pos.h = picPos.h; -}; - -void CCreatureAnim::loopPreview(bool warMachine) -{ - std::vector available; - - static const EAnimType creaPreviewList[] = {HOLDING, HITTED, DEFENCE, ATTACK_FRONT, CAST_FRONT}; - static const EAnimType machPreviewList[] = {HOLDING, MOVING, SHOOT_UP, SHOOT_FRONT, SHOOT_DOWN}; - auto & previewList = warMachine ? machPreviewList : creaPreviewList; - - for (auto & elem : previewList) - if (anim.size(elem)) - available.push_back(elem); - - size_t rnd = CRandomGenerator::getDefault().nextInt(available.size() * 2 - 1); - - if (rnd >= available.size()) - { - EAnimType type; - if ( anim.size(MOVING) == 0 )//no moving animation present - type = HOLDING; - else - type = MOVING; - - //display this anim for ~1 second (time is random, but it looks good) - for (size_t i=0; i< 12/anim.size(type) + 1; i++) - addLast(type); - } - else - addLast(available[rnd]); -} - -void CCreatureAnim::addLast(EAnimType newType) -{ - if (type != MOVING && newType == MOVING)//starting moving - play init sequence - { - queue.push( MOVE_START ); - } - else if (type == MOVING && newType != MOVING )//previous anim was moving - finish it - { - queue.push( MOVE_END ); - } - if (newType == TURN_L || newType == TURN_R) - queue.push(newType); - - queue.push(newType); -} - -void CCreatureAnim::reset() -{ - //if we are in the middle of rotation - set flag - if (type == TURN_L && !queue.empty() && queue.front() == TURN_L) - rotate(true); - if (type == TURN_R && !queue.empty() && queue.front() == TURN_R) - rotate(false); - - while (!queue.empty()) - { - EAnimType at = queue.front(); - queue.pop(); - if (set(at)) - return; - } - if (callback) - callback(); - while (!queue.empty()) - { - EAnimType at = queue.front(); - queue.pop(); - if (set(at)) - return; - } - set(HOLDING); -} - -void CCreatureAnim::startPreview(bool warMachine) -{ - callback = std::bind(&CCreatureAnim::loopPreview, this, warMachine); -} - -void CCreatureAnim::clearAndSet(EAnimType type) -{ - while (!queue.empty()) - queue.pop(); - set(type); -} diff --git a/client/CAnimation.h b/client/gui/CAnimation.h similarity index 56% rename from client/CAnimation.h rename to client/gui/CAnimation.h index 1f3e10dc8..8f2d4b092 100644 --- a/client/CAnimation.h +++ b/client/gui/CAnimation.h @@ -1,7 +1,8 @@ #pragma once -#include "../lib/vcmi_endian.h" -#include "gui/CIntObject.h" +#include "../../lib/vcmi_endian.h" +#include "gui/Geometries.h" +#include "../../lib/GameConstants.h" /* * CAnimation.h, part of VCMI engine @@ -219,162 +220,3 @@ public: //total count of frames in group (including not loaded) size_t size(size_t group=0) const; }; - - -/// Class for displaying one image from animation -class CAnimImage: public CIntObject -{ -private: - CAnimation* anim; - //displayed frame/group - size_t frame; - size_t group; - PlayerColor player; - ui8 flags; - - void init(); - -public: - CAnimImage(std::string name, size_t Frame, size_t Group=0, int x=0, int y=0, ui8 Flags=0); - CAnimImage(CAnimation* anim, size_t Frame, size_t Group=0, int x=0, int y=0, ui8 Flags=0); - ~CAnimImage();//d-tor - - //size of animation - size_t size(); - - //change displayed frame on this one - void setFrame(size_t Frame, size_t Group=0); - - //makes image player-colored - void playerColored(PlayerColor player); - - void showAll(SDL_Surface * to); -}; - -/// Base class for displaying animation, used as superclass for different animations -class CShowableAnim: public CIntObject -{ -public: - enum EFlags - { - BASE=1, //base frame will be blitted before current one - HORIZONTAL_FLIP=2, //TODO: will be displayed rotated - VERTICAL_FLIP=4, //TODO: will be displayed rotated - USE_RLE=8, //RLE-d version, support full alpha-channel for 8-bit images - PLAYER_COLORED=16, //TODO: all loaded images will be player-colored - PLAY_ONCE=32 //play animation only once and stop at last frame - }; -protected: - CAnimation anim; - - size_t group, frame;//current frame - - size_t first, last; //animation range - - //TODO: replace with time delay(needed for battles) - ui32 frameDelay;//delay in frames of each image - ui32 value;//how many times current frame was showed - - ui8 flags;//Flags from EFlags enum - - //blit image with optional rotation, fitting into rect, etc - void blitImage(size_t frame, size_t group, SDL_Surface *to); - - //For clipping in rect, offsets of picture coordinates - int xOffset, yOffset; - - ui8 alpha; - -public: - //called when next animation sequence is required - std::function callback; - - //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(); - - //set animation to group or part of group - bool set(size_t Group); - bool set(size_t Group, size_t from, size_t to=-1); - - //set rotation flags - void rotate(bool on, bool vertical=false); - - //move displayed part of picture (if picture is clipped to rect) - void clipRect(int posX, int posY, int width, int height); - - //set frame to first, call callback - virtual void reset(); - - //show current frame and increase counter - void show(SDL_Surface * to); - void showAll(SDL_Surface * to); -}; - -/// Creature-dependend animations like attacking, moving,... -class CCreatureAnim: public CShowableAnim -{ -public: - - enum EHeroAnimType - { - HERO_HOLDING = 0, - HERO_IDLE = 1, // idling movement that happens from time to time - HERO_DEFEAT = 2, // played when army loses stack or on friendly fire - HERO_VICTORY = 3, // when enemy stack killed or huge damage is dealt - HERO_CAST_SPELL = 4 // spellcasting - }; - - enum EAnimType // list of creature animations, numbers were taken from def files - { - MOVING=0, - MOUSEON=1, - HOLDING=2, - HITTED=3, - DEFENCE=4, - DEATH=5, - //DEATH2=6, //unused? - TURN_L=7, - TURN_R=8, //same - //TURN_L2=9, //identical to previous? - //TURN_R2=10, - ATTACK_UP=11, - ATTACK_FRONT=12, - ATTACK_DOWN=13, - SHOOT_UP=14, - SHOOT_FRONT=15, - SHOOT_DOWN=16, - CAST_UP=17, - CAST_FRONT=18, - CAST_DOWN=19, - MOVE_START=20, - MOVE_END=21, - DEAD = 22 // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here - - }; - -private: - //queue of animations waiting to be displayed - std::queue queue; - - //this function is used as callback if preview flag was set during construction - void loopPreview(bool warMachine); - -public: - //change anim to next if queue is not empty, call callback othervice - void reset(); - - //add sequence to the end of queue - void addLast(EAnimType newType); - - void startPreview(bool warMachine); - - //clear queue and set animation to this sequence - void clearAndSet(EAnimType type); - - CCreatureAnim(int x, int y, std::string name, Rect picPos, - ui8 flags= USE_RLE, EAnimType = HOLDING ); - -}; diff --git a/client/gui/CCursorHandler.cpp b/client/gui/CCursorHandler.cpp index 5fd52e7df..5b7c27650 100644 --- a/client/gui/CCursorHandler.cpp +++ b/client/gui/CCursorHandler.cpp @@ -2,9 +2,11 @@ #include "CCursorHandler.h" #include + #include "SDL_Extensions.h" -#include "../CAnimation.h" #include "CGuiHandler.h" +#include "widgets/Images.h" + #include "../CMT.h" /* diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index 2244e6e96..f2de051c6 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -1,10 +1,12 @@ #include "StdInc.h" #include "CGuiHandler.h" +#include #include "CIntObject.h" -#include "../CGameInfo.h" #include "CCursorHandler.h" + +#include "../CGameInfo.h" #include "../../lib/CThreadHelper.h" #include "../../lib/CConfigHandler.h" #include "../CMT.h" diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index 262797841..09b3e3b8d 100644 --- a/client/gui/CGuiHandler.h +++ b/client/gui/CGuiHandler.h @@ -1,6 +1,6 @@ #pragma once -#include "../../lib/CStopWatch.h" +//#include "../../lib/CStopWatch.h" #include "Geometries.h" #include "SDL_Extensions.h" diff --git a/client/gui/CIntObject.cpp b/client/gui/CIntObject.cpp index b952ae9de..06c9159ad 100644 --- a/client/gui/CIntObject.cpp +++ b/client/gui/CIntObject.cpp @@ -1,9 +1,15 @@ #include "StdInc.h" #include "CIntObject.h" + #include "CGuiHandler.h" #include "SDL_Extensions.h" #include "../CMessage.h" +IShowActivatable::IShowActivatable() +{ + type = 0; +} + void ILockedUpdatable::runLocked(std::function cb) { boost::unique_lock lock(updateGuard); @@ -317,6 +323,19 @@ bool CIntObject::captureThisEvent(const SDL_KeyboardEvent & key) return captureAllKeys; } +CKeyShortcut::CKeyShortcut() +{} + +CKeyShortcut::CKeyShortcut(int key) +{ + if (key != SDLK_UNKNOWN) + assignedKeys.insert(key); +} + +CKeyShortcut::CKeyShortcut(std::set Keys) + :assignedKeys(Keys) +{} + void CKeyShortcut::keyPressed(const SDL_KeyboardEvent & key) { if(vstd::contains(assignedKeys,key.keysym.sym) diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index 2e2d42ccc..307e2ec7d 100644 --- a/client/gui/CIntObject.h +++ b/client/gui/CIntObject.h @@ -18,6 +18,8 @@ struct SDL_Surface; class CPicture; class CGuiHandler; +struct SDL_KeyboardEvent; + using boost::logic::tribool; // Defines a activate/deactive method @@ -216,8 +218,8 @@ class CKeyShortcut : public virtual CIntObject { public: std::set assignedKeys; - CKeyShortcut(){}; //c-tor - CKeyShortcut(int key){assignedKeys.insert(key);}; //c-tor - CKeyShortcut(std::set Keys):assignedKeys(Keys){}; //c-tor + CKeyShortcut(); + CKeyShortcut(int key); + CKeyShortcut(std::set Keys); virtual void keyPressed(const SDL_KeyboardEvent & key); //call-in }; diff --git a/client/gui/CIntObjectClasses.cpp b/client/gui/CIntObjectClasses.cpp deleted file mode 100644 index 7b6cd3994..000000000 --- a/client/gui/CIntObjectClasses.cpp +++ /dev/null @@ -1,2011 +0,0 @@ -#include "StdInc.h" -#include "CIntObjectClasses.h" - -#include "../CBitmapHandler.h" -#include "SDL_Pixels.h" -#include "SDL_Extensions.h" -#include "../Graphics.h" -#include "../CAnimation.h" -#include "CCursorHandler.h" -#include "../CGameInfo.h" -#include "../../CCallback.h" -#include "../../lib/CConfigHandler.h" -#include "../battle/CBattleInterface.h" -#include "../battle/CBattleInterfaceClasses.h" -#include "../CPlayerInterface.h" -#include "../CMessage.h" -#include "../CMusicHandler.h" -#include "../GUIClasses.h" -#include "CGuiHandler.h" -#include "../CAdvmapInterface.h" -#include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff - -CPicture::CPicture( SDL_Surface *BG, int x, int y, bool Free ) -{ - init(); - bg = BG; - freeSurf = Free; - pos.x += x; - pos.y += y; - pos.w = BG->w; - pos.h = BG->h; -} - -CPicture::CPicture( const std::string &bmpname, int x, int y ) -{ - init(); - bg = BitmapHandler::loadBitmap(bmpname); - freeSurf = true;; - pos.x += x; - pos.y += y; - if(bg) - { - pos.w = bg->w; - pos.h = bg->h; - } - else - { - pos.w = pos.h = 0; - } -} - -CPicture::CPicture(const Rect &r, const SDL_Color &color, bool screenFormat /*= false*/) -{ - init(); - createSimpleRect(r, screenFormat, SDL_MapRGB(bg->format, color.r, color.g,color.b)); -} - -CPicture::CPicture(const Rect &r, ui32 color, bool screenFormat /*= false*/) -{ - init(); - createSimpleRect(r, screenFormat, color); -} - -CPicture::CPicture(SDL_Surface *BG, const Rect &SrcRect, int x /*= 0*/, int y /*= 0*/, bool free /*= false*/) -{ - needRefresh = false; - srcRect = new Rect(SrcRect); - pos.x += x; - pos.y += y; - pos.w = srcRect->w; - pos.h = srcRect->h; - bg = BG; - freeSurf = free; -} - -void CPicture::setSurface(SDL_Surface *to) -{ - bg = to; - if (srcRect) - { - pos.w = srcRect->w; - pos.h = srcRect->h; - } - else - { - pos.w = bg->w; - pos.h = bg->h; - } -} - -CPicture::~CPicture() -{ - if(freeSurf) - SDL_FreeSurface(bg); - delete srcRect; -} - -void CPicture::init() -{ - needRefresh = false; - srcRect = nullptr; -} - -void CPicture::show(SDL_Surface * to) -{ - if (needRefresh) - showAll(to); -} - -void CPicture::showAll(SDL_Surface * to) -{ - if(bg) - { - if(srcRect) - { - SDL_Rect srcRectCpy = *srcRect; - SDL_Rect dstRect = srcRectCpy; - dstRect.x = pos.x; - dstRect.y = pos.y; - - CSDL_Ext::blitSurface(bg, &srcRectCpy, to, &dstRect); - } - else - blitAt(bg, pos, to); - } -} - -void CPicture::convertToScreenBPP() -{ - SDL_Surface *hlp = bg; - bg = SDL_ConvertSurface(hlp,screen->format,0); - CSDL_Ext::setDefaultColorKey(bg); - SDL_FreeSurface(hlp); -} - -void CPicture::setAlpha(int value) -{ - #ifdef VCMI_SDL1 - SDL_SetAlpha(bg, SDL_SRCALPHA, value); - #else - SDL_SetSurfaceAlphaMod(bg,value); - #endif // 0 -} - -void CPicture::scaleTo(Point size) -{ - SDL_Surface * scaled = CSDL_Ext::scaleSurface(bg, size.x, size.y); - - if(freeSurf) - SDL_FreeSurface(bg); - - setSurface(scaled); - freeSurf = false; -} - -void CPicture::createSimpleRect(const Rect &r, bool screenFormat, ui32 color) -{ - pos += r; - pos.w = r.w; - pos.h = r.h; - if(screenFormat) - bg = CSDL_Ext::newSurface(r.w, r.h); - else - bg = SDL_CreateRGBSurface(SDL_SWSURFACE, r.w, r.h, 8, 0, 0, 0, 0); - - SDL_FillRect(bg, nullptr, color); - freeSurf = true; -} - -void CPicture::colorizeAndConvert(PlayerColor player) -{ - assert(bg); - colorize(player); - convertToScreenBPP(); -} - -void CPicture::colorize(PlayerColor player) -{ - assert(bg); - graphics->blueToPlayersAdv(bg, player); -} - -CFilledTexture::CFilledTexture(std::string imageName, Rect position): - CIntObject(0, position.topLeft()), - texture(BitmapHandler::loadBitmap(imageName)) -{ - pos.w = position.w; - pos.h = position.h; -} - -CFilledTexture::~CFilledTexture() -{ - SDL_FreeSurface(texture); -} - -void CFilledTexture::showAll(SDL_Surface *to) -{ - CSDL_Ext::CClipRectGuard guard(to, pos); - CSDL_Ext::fillTexture(to, texture); -} - -CButtonBase::CButtonBase() -{ - swappedImages = keepFrame = false; - bitmapOffset = 0; - state=NORMAL; - image = nullptr; - text = nullptr; -} - -CButtonBase::~CButtonBase() -{ - -} - -void CButtonBase::update() -{ - if (text) - { - if (state == PRESSED) - text->moveTo(Point(pos.x+pos.w/2+1, pos.y+pos.h/2+1)); - else - text->moveTo(Point(pos.x+pos.w/2, pos.y+pos.h/2)); - } - - int newPos = (int)state + bitmapOffset; - if (newPos < 0) - newPos = 0; - - if (state == HIGHLIGHTED && image->size() < 4) - newPos = image->size()-1; - - if (swappedImages) - { - if (newPos == 0) newPos = 1; - else if (newPos == 1) newPos = 0; - } - - if (!keepFrame) - image->setFrame(newPos); - - if (active) - redraw(); -} - -void CButtonBase::addTextOverlay( const std::string &Text, EFonts font, SDL_Color color) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - delete text; - text = new CLabel(pos.w/2, pos.h/2, font, CENTER, color, Text); - update(); -} - -void CButtonBase::setOffset(int newOffset) -{ - if (bitmapOffset == newOffset) - return; - bitmapOffset = newOffset; - update(); -} - -void CButtonBase::setState(ButtonState newState) -{ - if (state == newState) - return; - state = newState; - update(); -} - -CButtonBase::ButtonState CButtonBase::getState() -{ - return state; -} - -bool CButtonBase::isBlocked() -{ - return state == BLOCKED; -} - -bool CButtonBase::isHighlighted() -{ - return state == HIGHLIGHTED; -} - -void CButtonBase::block(bool on) -{ - setState(on?BLOCKED:NORMAL); -} - -CAdventureMapButton::CAdventureMapButton () -{ - hoverable = actOnDown = borderEnabled = soundDisabled = false; - CSDL_Ext::colorSetAlpha(borderColor,1);// represents a transparent color, used for HighlightableButton - addUsedEvents(LCLICK | RCLICK | HOVER | KEYBOARD); -} - -CAdventureMapButton::CAdventureMapButton( const std::string &Name, const std::string &HelpBox, const CFunctionList &Callback, int x, int y, const std::string &defName,int key, std::vector * add, bool playerColoredButton ) -{ - std::map pom; - pom[0] = Name; - init(Callback, pom, HelpBox, playerColoredButton, defName, add, x, y, key); -} - -CAdventureMapButton::CAdventureMapButton( const std::string &Name, const std::string &HelpBox, const CFunctionList &Callback, config::ButtonInfo *info, int key/*=0*/ ) -{ - std::map pom; - pom[0] = Name; - init(Callback, pom, HelpBox, info->playerColoured, info->defName, &info->additionalDefs, info->x, info->y, key); -} - -CAdventureMapButton::CAdventureMapButton( const std::pair &help, const CFunctionList &Callback, int x, int y, const std::string &defName, int key/*=0*/, std::vector * add /*= nullptr*/, bool playerColoredButton /*= false */ ) -{ - std::map pom; - pom[0] = help.first; - init(Callback, pom, help.second, playerColoredButton, defName, add, x, y, key); -} - -void CAdventureMapButton::onButtonClicked() -{ - // debug logging to figure out pressed button (and as result - player actions) in case of crash - logAnim->traceStream() << "Button clicked at " << pos.x << "x" << pos.y; - CIntObject * parent = this->parent; - std::string prefix = "Parent is"; - while (parent) - { - logAnim->traceStream() << prefix << typeid(*parent).name() << " at " << parent->pos.x << "x" << parent->pos.y; - parent = parent->parent; - prefix = '\t' + prefix; - } - callback(); -} - -void CAdventureMapButton::clickLeft(tribool down, bool previousState) -{ - if(isBlocked()) - return; - - if (down) - { - if (!soundDisabled) - CCS->soundh->playSound(soundBase::button); - setState(PRESSED); - } - else if(hoverable && hovered) - setState(HIGHLIGHTED); - else - setState(NORMAL); - - if (actOnDown && down) - { - onButtonClicked(); - } - else if (!actOnDown && previousState && (down==false)) - { - onButtonClicked(); - } -} - -void CAdventureMapButton::clickRight(tribool down, bool previousState) -{ - if(down && helpBox.size()) //there is no point to show window with nothing inside... - CRClickPopup::createAndPush(helpBox); -} - -void CAdventureMapButton::hover (bool on) -{ - if(hoverable) - { - if(on) - setState(HIGHLIGHTED); - else - setState(NORMAL); - } - - if(pressedL && on) - setState(PRESSED); - - std::string *name = (vstd::contains(hoverTexts,getState())) - ? (&hoverTexts[getState()]) - : (vstd::contains(hoverTexts,0) ? (&hoverTexts[0]) : nullptr); - if(name && name->size() && !isBlocked()) //if there is no name, there is nohing to display also - { - if (LOCPLINT && LOCPLINT->battleInt) //for battle buttons - { - if(on && LOCPLINT->battleInt->console->alterTxt == "") - { - LOCPLINT->battleInt->console->alterTxt = *name; - LOCPLINT->battleInt->console->whoSetAlter = 1; - } - else if (LOCPLINT->battleInt->console->alterTxt == *name) - { - LOCPLINT->battleInt->console->alterTxt = ""; - LOCPLINT->battleInt->console->whoSetAlter = 0; - } - } - else if(GH.statusbar) //for other buttons - { - if (on) - GH.statusbar->setText(*name); - else if ( GH.statusbar->getText()==(*name) ) - GH.statusbar->clear(); - } - } -} - -void CAdventureMapButton::init(const CFunctionList &Callback, const std::map &Name, const std::string &HelpBox, bool playerColoredButton, const std::string &defName, std::vector * add, int x, int y, int key) -{ - currentImage = -1; - addUsedEvents(LCLICK | RCLICK | HOVER | KEYBOARD); - callback = Callback; - hoverable = actOnDown = borderEnabled = soundDisabled = false; - CSDL_Ext::colorSetAlpha(borderColor,1);// represents a transparent color, used for HighlightableButton - hoverTexts = Name; - helpBox=HelpBox; - - if (key != SDLK_UNKNOWN) - assignedKeys.insert(key); - - pos.x += x; - pos.y += y; - - if (!defName.empty()) - imageNames.push_back(defName); - if (add) - for (auto & elem : *add) - imageNames.push_back(elem); - setIndex(0, playerColoredButton); -} - -void CAdventureMapButton::setIndex(size_t index, bool playerColoredButton) -{ - if (index == currentImage || index>=imageNames.size()) - return; - currentImage = index; - setImage(new CAnimation(imageNames[index]), playerColoredButton); -} - -void CAdventureMapButton::setImage(CAnimation* anim, bool playerColoredButton, int animFlags) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - delete image; - image = new CAnimImage(anim, getState(), 0, 0, 0, animFlags); - if (playerColoredButton) - image->playerColored(LOCPLINT->playerID); - - pos.w = image->pos.w; - pos.h = image->pos.h; -} - -void CAdventureMapButton::setPlayerColor(PlayerColor player) -{ - if (image) - image->playerColored(player); -} - -void CAdventureMapButton::showAll(SDL_Surface * to) -{ - CIntObject::showAll(to); - - #ifdef VCMI_SDL1 - if (borderEnabled && borderColor.unused == 0) - CSDL_Ext::drawBorder(to, pos.x-1, pos.y-1, pos.w+2, pos.h+2, int3(borderColor.r, borderColor.g, borderColor.b)); - #else - if (borderEnabled && borderColor.a == 0) - CSDL_Ext::drawBorder(to, pos.x-1, pos.y-1, pos.w+2, pos.h+2, int3(borderColor.r, borderColor.g, borderColor.b)); - #endif // 0 -} - -void CHighlightableButton::select(bool on) -{ - selected = on; - if (on) - { - borderEnabled = true; - setState(HIGHLIGHTED); - callback(); - } - else - { - borderEnabled = false; - setState(NORMAL); - callback2(); - } - - if(hoverTexts.size()>1) - { - hover(false); - hover(true); - } -} - -void CHighlightableButton::clickLeft(tribool down, bool previousState) -{ - if(isBlocked()) - return; - - if (down && !(onlyOn && isHighlighted())) - { - CCS->soundh->playSound(soundBase::button); - setState(PRESSED); - } - - if(previousState)//mouse up - { - if(down == false && getState() == PRESSED) - select(!selected); - else - setState(selected?HIGHLIGHTED:NORMAL); - } -} - -CHighlightableButton::CHighlightableButton( const CFunctionList &onSelect, const CFunctionList &onDeselect, const std::map &Name, const std::string &HelpBox, bool playerColoredButton, const std::string &defName, std::vector * add, int x, int y, int key) -: onlyOn(false), selected(false), callback2(onDeselect) -{ - init(onSelect,Name,HelpBox,playerColoredButton,defName,add,x,y,key); -} - -CHighlightableButton::CHighlightableButton( const std::pair &help, const CFunctionList &onSelect, int x, int y, const std::string &defName, int myid, int key/*=0*/, std::vector * add /*= nullptr*/, bool playerColoredButton /*= false */ ) -: onlyOn(false), selected(false) // TODO: callback2(???) -{ - ID = myid; - std::map pom; - pom[0] = help.first; - init(onSelect, pom, help.second, playerColoredButton, defName, add, x, y, key); -} - -CHighlightableButton::CHighlightableButton( const std::string &Name, const std::string &HelpBox, const CFunctionList &onSelect, int x, int y, const std::string &defName, int myid, int key/*=0*/, std::vector * add /*= nullptr*/, bool playerColoredButton /*= false */ ) -: onlyOn(false), selected(false) // TODO: callback2(???) -{ - ID = myid; - std::map pom; - pom[0] = Name; - init(onSelect, pom,HelpBox, playerColoredButton, defName, add, x, y, key); -} - -void CHighlightableButtonsGroup::addButton(CHighlightableButton* bt) -{ - if (bt->parent) - bt->parent->removeChild(bt); - addChild(bt); - bt->recActions = defActions;//FIXME: not needed? - - bt->callback += std::bind(&CHighlightableButtonsGroup::selectionChanged,this,bt->ID); - bt->onlyOn = true; - buttons.push_back(bt); -} - -void CHighlightableButtonsGroup::addButton(const std::map &tooltip, const std::string &HelpBox, const std::string &defName, int x, int y, int uid, const CFunctionList &OnSelect, int key) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - CHighlightableButton *bt = new CHighlightableButton(OnSelect, 0, tooltip, HelpBox, false, defName, nullptr, x, y, key); - if(musicLike) - { - bt->setOffset(buttons.size()-3); - } - bt->ID = uid; - bt->callback += std::bind(&CHighlightableButtonsGroup::selectionChanged,this,bt->ID); - bt->onlyOn = true; - buttons.push_back(bt); -} - -CHighlightableButtonsGroup::CHighlightableButtonsGroup(const CFunctionList &OnChange, bool musicLikeButtons) -: onChange(OnChange), musicLike(musicLikeButtons) -{} - -CHighlightableButtonsGroup::~CHighlightableButtonsGroup() -{ - -} - -void CHighlightableButtonsGroup::select(int id, bool mode) -{ - assert(!buttons.empty()); - - CHighlightableButton *bt = buttons.front(); - if(mode) - { - for(auto btn : buttons) - if (btn->ID == id) - bt = btn; - } - else - { - bt = buttons[id]; - } - bt->select(true); - selectionChanged(bt->ID); -} - -void CHighlightableButtonsGroup::selectionChanged(int to) -{ - for(auto & elem : buttons) - if(elem->ID!=to && elem->isHighlighted()) - elem->select(false); - onChange(to); - if (parent) - parent->redraw(); -} - -void CHighlightableButtonsGroup::show(SDL_Surface * to) -{ - if (musicLike) - { - for(auto & elem : buttons) - if(elem->isHighlighted()) - elem->show(to); - } - else - CIntObject::show(to); -} - -void CHighlightableButtonsGroup::showAll(SDL_Surface * to) -{ - if (musicLike) - { - for(auto & elem : buttons) - if(elem->isHighlighted()) - elem->showAll(to); - } - else - CIntObject::showAll(to); -} - -void CHighlightableButtonsGroup::block( ui8 on ) -{ - for(auto & elem : buttons) - { - elem->block(on); - } -} - -void CSlider::sliderClicked() -{ - if(!(active & MOVE)) - addUsedEvents(MOVE); -} - -void CSlider::mouseMoved (const SDL_MouseMotionEvent & sEvent) -{ - double v = 0; - if(horizontal) - { - if( std::abs(sEvent.y-(pos.y+pos.h/2)) > pos.h/2+40 || std::abs(sEvent.x-(pos.x+pos.w/2)) > pos.w/2 ) - return; - v = sEvent.x - pos.x - 24; - v *= positions; - v /= (pos.w - 48); - } - else - { - if(std::abs(sEvent.x-(pos.x+pos.w/2)) > pos.w/2+40 || std::abs(sEvent.y-(pos.y+pos.h/2)) > pos.h/2 ) - return; - v = sEvent.y - pos.y - 24; - v *= positions; - v /= (pos.h - 48); - } - v += 0.5; - if(v!=value) - { - moveTo(v); - redrawSlider(); - } -} - -void CSlider::redrawSlider() -{ - //slider->show(screenBuf); -} - -void CSlider::moveLeft() -{ - moveTo(value-1); -} - -void CSlider::moveRight() -{ - moveTo(value+1); -} - -void CSlider::moveTo(int to) -{ - vstd::amax(to, 0); - vstd::amin(to, positions); - - //same, old position? - if(value == to) - return; - - value = to; - if(horizontal) - { - if(positions) - { - double part = static_cast(to) / positions; - part*=(pos.w-48); - int newPos = part + pos.x + 16 - slider->pos.x; - slider->moveBy(Point(newPos, 0)); - } - else - slider->moveTo(Point(pos.x+16, pos.y)); - } - else - { - if(positions) - { - double part = static_cast(to) / positions; - part*=(pos.h-48); - int newPos = part + pos.y + 16 - slider->pos.y; - slider->moveBy(Point(0, newPos)); - } - else - slider->moveTo(Point(pos.x, pos.y+16)); - } - - if(moved) - moved(to); -} - -void CSlider::clickLeft(tribool down, bool previousState) -{ - if(down && !slider->isBlocked()) - { - double pw = 0; - double rw = 0; - if(horizontal) - { - pw = GH.current->motion.x-pos.x-25; - rw = pw / static_cast(pos.w - 48); - } - else - { - pw = GH.current->motion.y-pos.y-24; - rw = pw / (pos.h-48); - } - if(pw < -8 || pw > (horizontal ? pos.w : pos.h) - 40) - return; - // if (rw>1) return; - // if (rw<0) return; - slider->clickLeft(true, slider->pressedL); - moveTo(rw * positions + 0.5); - return; - } - if(active & MOVE) - removeUsedEvents(MOVE); -} - -CSlider::~CSlider() -{ - -} - -CSlider::CSlider(int x, int y, int totalw, std::function Moved, int Capacity, int Amount, int Value, bool Horizontal, int style): - capacity(Capacity), - amount(Amount), - scrollStep(1), - horizontal(Horizontal), - moved(Moved) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - setAmount(amount); - - addUsedEvents(LCLICK | KEYBOARD | WHEEL); - strongInterest = true; - - - left = new CAdventureMapButton(); - right = new CAdventureMapButton(); - slider = new CAdventureMapButton(); - - pos.x += x; - pos.y += y; - - if(horizontal) - { - left->pos.y = slider->pos.y = right->pos.y = pos.y; - left->pos.x = pos.x; - right->pos.x = pos.x + totalw - 16; - } - else - { - left->pos.x = slider->pos.x = right->pos.x = pos.x; - left->pos.y = pos.y; - right->pos.y = pos.y + totalw - 16; - } - - left->callback = std::bind(&CSlider::moveLeft,this); - right->callback = std::bind(&CSlider::moveRight,this); - slider->callback = std::bind(&CSlider::sliderClicked,this); - left->pos.w = left->pos.h = right->pos.w = right->pos.h = slider->pos.w = slider->pos.h = 16; - if(horizontal) - { - pos.h = 16; - pos.w = totalw; - } - else - { - pos.w = 16; - pos.h = totalw; - } - - if(style == 0) - { - std::string name = horizontal?"IGPCRDIV.DEF":"OVBUTN2.DEF"; - //NOTE: this images do not have "blocked" frames. They should be implemented somehow (e.g. palette transform or something...) - - //use source def to create custom animations. Format "name.def:123" will load this frame from def file - auto animLeft = new CAnimation(); - animLeft->setCustom(name + ":0", 0); - animLeft->setCustom(name + ":1", 1); - left->setImage(animLeft); - - auto animRight = new CAnimation(); - animRight->setCustom(name + ":2", 0); - animRight->setCustom(name + ":3", 1); - right->setImage(animRight); - - auto animSlider = new CAnimation(); - animSlider->setCustom(name + ":4", 0); - slider->setImage(animSlider); - } - else - { - left->setImage(new CAnimation(horizontal ? "SCNRBLF.DEF" : "SCNRBUP.DEF")); - right->setImage(new CAnimation(horizontal ? "SCNRBRT.DEF" : "SCNRBDN.DEF")); - slider->setImage(new CAnimation("SCNRBSL.DEF")); - } - slider->actOnDown = true; - slider->soundDisabled = true; - left->soundDisabled = true; - right->soundDisabled = true; - - value = -1; - moveTo(Value); -} - -void CSlider::block( bool on ) -{ - left->block(on); - right->block(on); - slider->block(on); -} - -void CSlider::setAmount( int to ) -{ - amount = to; - positions = to - capacity; - vstd::amax(positions, 0); -} - -void CSlider::showAll(SDL_Surface * to) -{ - CSDL_Ext::fillRectBlack(to, &pos); - CIntObject::showAll(to); -} - -void CSlider::wheelScrolled(bool down, bool in) -{ - moveTo(value + 3 * (down ? +scrollStep : -scrollStep)); -} - -void CSlider::keyPressed(const SDL_KeyboardEvent & key) -{ - if(key.state != SDL_PRESSED) return; - - int moveDest = 0; - switch(key.keysym.sym) - { - case SDLK_UP: - case SDLK_LEFT: - moveDest = value - scrollStep; - break; - case SDLK_DOWN: - case SDLK_RIGHT: - moveDest = value + scrollStep; - break; - case SDLK_PAGEUP: - moveDest = value - capacity + scrollStep; - break; - case SDLK_PAGEDOWN: - moveDest = value + capacity - scrollStep; - break; - case SDLK_HOME: - moveDest = 0; - break; - case SDLK_END: - moveDest = amount - capacity; - break; - default: - return; - } - - moveTo(moveDest); -} - -void CSlider::moveToMax() -{ - moveTo(amount); -} - -static void intDeleter(CIntObject* object) -{ - delete object; -} - -CObjectList::CObjectList(CreateFunc create, DestroyFunc destroy): -createObject(create), -destroyObject(destroy) -{ - if (!destroyObject) - destroyObject = intDeleter; -} - -void CObjectList::deleteItem(CIntObject* item) -{ - if (!item) - return; - removeChild(item); - destroyObject(item); -} - -CIntObject* CObjectList::createItem(size_t index) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - CIntObject * item = createObject(index); - if (item == nullptr) - item = new CIntObject(); - - item->recActions = defActions; - - addChild(item); - return item; -} - -CTabbedInt::CTabbedInt(CreateFunc create, DestroyFunc destroy, Point position, size_t ActiveID): -CObjectList(create, destroy), -activeTab(nullptr), -activeID(ActiveID) -{ - pos += position; - reset(); -} - -void CTabbedInt::setActive(size_t which) -{ - if (which != activeID) - { - activeID = which; - reset(); - } -} - -void CTabbedInt::reset() -{ - deleteItem(activeTab); - activeTab = createItem(activeID); - activeTab->moveTo(pos.topLeft()); - - if (active) - redraw(); -} - -CIntObject * CTabbedInt::getItem() -{ - return activeTab; -} - -CListBox::CListBox(CreateFunc create, DestroyFunc destroy, Point Pos, Point ItemOffset, size_t VisibleSize, - size_t TotalSize, size_t InitialPos, int Slider, Rect SliderPos): - CObjectList(create, destroy), - first(InitialPos), - totalSize(TotalSize), - itemOffset(ItemOffset), - slider(nullptr) -{ - pos += Pos; - items.resize(VisibleSize, nullptr); - - if (Slider & 1) - { - OBJ_CONSTRUCTION_CAPTURING_ALL; - slider = new CSlider(SliderPos.x, SliderPos.y, SliderPos.w, std::bind(&CListBox::moveToPos, this, _1), - VisibleSize, TotalSize, InitialPos, Slider & 2, Slider & 4); - } - reset(); -} - -// Used to move active items after changing list position -void CListBox::updatePositions() -{ - Point itemPos = pos.topLeft(); - for (auto & elem : items) - { - (elem)->moveTo(itemPos); - itemPos += itemOffset; - } - if (active) - { - redraw(); - if (slider) - slider->moveTo(first); - } -} - -void CListBox::reset() -{ - size_t current = first; - for (auto & elem : items) - { - deleteItem(elem); - elem = createItem(current++); - } - updatePositions(); -} - -void CListBox::resize(size_t newSize) -{ - totalSize = newSize; - if (slider) - slider->setAmount(totalSize); - reset(); -} - -size_t CListBox::size() -{ - return totalSize; -} - -CIntObject * CListBox::getItem(size_t which) -{ - if (which < first || which > first + items.size() || which > totalSize) - return nullptr; - - size_t i=first; - for (auto iter = items.begin(); iter != items.end(); iter++, i++) - if( i == which) - return *iter; - return nullptr; -} - -size_t CListBox::getIndexOf(CIntObject *item) -{ - size_t i=first; - for (auto iter = items.begin(); iter != items.end(); iter++, i++) - if(*iter == item) - return i; - return size_t(-1); -} - -void CListBox::scrollTo(size_t which) -{ - //scroll up - if (first > which) - moveToPos(which); - //scroll down - else if (first + items.size() <= which && which < totalSize) - moveToPos(which - items.size() + 1); -} - -void CListBox::moveToPos(size_t which) -{ - //Calculate new position - size_t maxPossible; - if (totalSize > items.size()) - maxPossible = totalSize - items.size(); - else - maxPossible = 0; - - size_t newPos = std::min(which, maxPossible); - - //If move distance is 1 (most of calls from Slider) - use faster shifts instead of resetting all items - if (first - newPos == 1) - moveToPrev(); - else if (newPos - first == 1) - moveToNext(); - else if (newPos != first) - { - first = newPos; - reset(); - } -} - -void CListBox::moveToNext() -{ - //Remove front item and insert new one to end - if (first + items.size() < totalSize) - { - first++; - deleteItem(items.front()); - items.pop_front(); - items.push_back(createItem(first+items.size())); - updatePositions(); - } -} - -void CListBox::moveToPrev() -{ - //Remove last item and insert new one at start - if (first) - { - first--; - deleteItem(items.back()); - items.pop_back(); - items.push_front(createItem(first)); - updatePositions(); - } -} - -size_t CListBox::getPos() -{ - return first; -} - -const std::list &CListBox::getItems() -{ - return items; -} - -void CSimpleWindow::show(SDL_Surface * to) -{ - if(bitmap) - blitAt(bitmap,pos.x,pos.y,to); -} -CSimpleWindow::~CSimpleWindow() -{ - if (bitmap) - { - SDL_FreeSurface(bitmap); - bitmap=nullptr; - } -} - -void CHoverableArea::hover (bool on) -{ - if (on) - GH.statusbar->setText(hoverText); - else if (GH.statusbar->getText()==hoverText) - GH.statusbar->clear(); -} - -CHoverableArea::CHoverableArea() -{ - addUsedEvents(HOVER); -} - -CHoverableArea::~CHoverableArea() -{ -} - -void LRClickableAreaWText::clickLeft(tribool down, bool previousState) -{ - if(!down && previousState) - { - LOCPLINT->showInfoDialog(text); - } -} -void LRClickableAreaWText::clickRight(tribool down, bool previousState) -{ - adventureInt->handleRightClick(text, down); -} - -LRClickableAreaWText::LRClickableAreaWText() -{ - init(); -} - -LRClickableAreaWText::LRClickableAreaWText(const Rect &Pos, const std::string &HoverText /*= ""*/, const std::string &ClickText /*= ""*/) -{ - init(); - pos = Pos + pos; - hoverText = HoverText; - text = ClickText; -} - -LRClickableAreaWText::~LRClickableAreaWText() -{ -} - -void LRClickableAreaWText::init() -{ - addUsedEvents(LCLICK | RCLICK | HOVER); -} - -std::string CLabel::visibleText() -{ - return text; -} - -void CLabel::showAll(SDL_Surface * to) -{ - CIntObject::showAll(to); - - if(!visibleText().empty()) - blitLine(to, pos, visibleText()); - -} - -CLabel::CLabel(int x, int y, EFonts Font /*= FONT_SMALL*/, EAlignment Align, const SDL_Color &Color /*= Colors::WHITE*/, const std::string &Text /*= ""*/) -:CTextContainer(Align, Font, Color), text(Text) -{ - type |= REDRAW_PARENT; - autoRedraw = true; - pos.x += x; - pos.y += y; - pos.w = pos.h = 0; - bg = nullptr; - - if (alignment == TOPLEFT) // causes issues for MIDDLE - { - pos.w = graphics->fonts[font]->getStringWidth(visibleText().c_str()); - pos.h = graphics->fonts[font]->getLineHeight(); - } -} - -Point CLabel::getBorderSize() -{ - return Point(0, 0); -} - -std::string CLabel::getText() -{ - return text; -} - -void CLabel::setText(const std::string &Txt) -{ - text = Txt; - if(autoRedraw) - { - if(bg || !parent) - redraw(); - else - parent->redraw(); - } -} - -CMultiLineLabel::CMultiLineLabel(Rect position, EFonts Font, EAlignment Align, const SDL_Color &Color, const std::string &Text): - CLabel(position.x, position.y, Font, Align, Color, Text), - visibleSize(0, 0, position.w, position.h) -{ - pos.w = position.w; - pos.h = position.h; - splitText(Text); -} - -void CMultiLineLabel::setVisibleSize(Rect visibleSize) -{ - this->visibleSize = visibleSize; - redraw(); -} - -void CMultiLineLabel::scrollTextBy(int distance) -{ - scrollTextTo(visibleSize.y + distance); -} - -void CMultiLineLabel::scrollTextTo(int distance) -{ - Rect size = visibleSize; - size.y = distance; - setVisibleSize(size); -} - -void CMultiLineLabel::setText(const std::string &Txt) -{ - splitText(Txt); - CLabel::setText(Txt); -} - -void CTextContainer::blitLine(SDL_Surface *to, Rect destRect, std::string what) -{ - const IFont * f = graphics->fonts[font]; - Point where = destRect.topLeft(); - - // input is rect in which given text should be placed - // calculate proper position for top-left corner of the text - if (alignment == TOPLEFT) - { - where.x += getBorderSize().x; - where.y += getBorderSize().y; - } - - if (alignment == CENTER) - { - where.x += (int(destRect.w) - int(f->getStringWidth(what))) / 2; - where.y += (int(destRect.h) - int(f->getLineHeight())) / 2; - } - - if (alignment == BOTTOMRIGHT) - { - where.x += getBorderSize().x + destRect.w - f->getStringWidth(what); - where.y += getBorderSize().y + destRect.h - f->getLineHeight(); - } - - size_t begin = 0; - std::string delimeters = "{}"; - size_t currDelimeter = 0; - - do - { - size_t end = what.find_first_of(delimeters[currDelimeter % 2], begin); - if (begin != end) - { - std::string toPrint = what.substr(begin, end - begin); - - if (currDelimeter % 2) // Enclosed in {} text - set to yellow - f->renderTextLeft(to, toPrint, Colors::YELLOW, where); - else // Non-enclosed text, use default color - f->renderTextLeft(to, toPrint, color, where); - begin = end; - - where.x += f->getStringWidth(toPrint); - } - currDelimeter++; - } - while (begin++ != std::string::npos); -} - -CTextContainer::CTextContainer(EAlignment alignment, EFonts font, SDL_Color color): - alignment(alignment), - font(font), - color(color) -{} - -void CMultiLineLabel::showAll(SDL_Surface * to) -{ - CIntObject::showAll(to); - - const IFont * f = graphics->fonts[font]; - - // calculate which lines should be visible - int totalLines = lines.size(); - int beginLine = visibleSize.y; - int endLine = getTextLocation().h + visibleSize.y; - - if (beginLine < 0) - beginLine = 0; - else - beginLine /= f->getLineHeight(); - - if (endLine < 0) - endLine = 0; - else - endLine /= f->getLineHeight(); - endLine++; - - // and where they should be displayed - Point lineStart = getTextLocation().topLeft() - visibleSize + Point(0, beginLine * f->getLineHeight()); - Point lineSize = Point(getTextLocation().w, f->getLineHeight()); - - CSDL_Ext::CClipRectGuard guard(to, getTextLocation()); // to properly trim text that is too big to fit - - for (int i = beginLine; i < std::min(totalLines, endLine); i++) - { - if (!lines[i].empty()) //non-empty line - blitLine(to, Rect(lineStart, lineSize), lines[i]); - - lineStart.y += f->getLineHeight(); - } -} - -void CMultiLineLabel::splitText(const std::string &Txt) -{ - lines.clear(); - - const IFont * f = graphics->fonts[font]; - int lineHeight = f->getLineHeight(); - - lines = CMessage::breakText(Txt, pos.w, font); - - textSize.y = lineHeight * lines.size(); - textSize.x = 0; - for(const std::string &line : lines) - vstd::amax( textSize.x, f->getStringWidth(line.c_str())); - redraw(); -} - -Rect CMultiLineLabel::getTextLocation() -{ - // this method is needed for vertical alignment alignment of text - // when height of available text is smaller than height of widget - // in this case - we should add proper offset to display text at required position - if (pos.h <= textSize.y) - return pos; - - Point textSize(pos.w, graphics->fonts[font]->getLineHeight() * lines.size()); - Point textOffset(pos.w - textSize.x, pos.h - textSize.y); - - switch(alignment) - { - case TOPLEFT: return Rect(pos.topLeft(), textSize); - case CENTER: return Rect(pos.topLeft() + textOffset / 2, textSize); - case BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSize); - } - assert(0); - return Rect(); -} - -CLabelGroup::CLabelGroup(EFonts Font, EAlignment Align, const SDL_Color &Color): - font(Font), align(Align), color(Color) -{} - -void CLabelGroup::add(int x, int y, const std::string &text) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - new CLabel(x, y, font, align, color, text); -} - -CTextBox::CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font /*= FONT_SMALL*/, EAlignment Align /*= TOPLEFT*/, const SDL_Color &Color /*= Colors::WHITE*/): - sliderStyle(SliderStyle), - slider(nullptr) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - label = new CMultiLineLabel(rect, Font, Align, Color); - - type |= REDRAW_PARENT; - pos.x += rect.x; - pos.y += rect.y; - pos.h = rect.h; - pos.w = rect.w; - - assert(pos.w >= 40); //we need some space - setText(Text); -} - -void CTextBox::sliderMoved(int to) -{ - label->scrollTextTo(to); -} - -void CTextBox::resize(Point newSize) -{ - pos.w = newSize.x; - pos.h = newSize.y; - label->pos.w = pos.w; - label->pos.h = pos.h; - if (slider) - vstd::clear_pointer(slider); // will be recreated if needed later - - setText(label->getText()); // force refresh -} - -void CTextBox::setText(const std::string &text) -{ - label->setText(text); - if (label->textSize.y <= label->pos.h && slider) - { - // slider is no longer needed - vstd::clear_pointer(slider); - label->pos.w = pos.w; - label->setText(text); - } - else if (label->textSize.y > label->pos.h && !slider) - { - // create slider and update widget - label->pos.w = pos.w - 32; - label->setText(text); - - OBJ_CONSTRUCTION_CAPTURING_ALL; - slider = new CSlider(pos.w - 32, 0, pos.h, std::bind(&CTextBox::sliderMoved, this, _1), - label->pos.h, label->textSize.y, 0, false, sliderStyle); - slider->scrollStep = graphics->fonts[label->font]->getLineHeight(); - } -} - -void CGStatusBar::setText(const std::string & Text) -{ - if(!textLock) - CLabel::setText(Text); -} - -void CGStatusBar::clear() -{ - setText(""); -} - -CGStatusBar::CGStatusBar(CPicture *BG, EFonts Font /*= FONT_SMALL*/, EAlignment Align /*= CENTER*/, const SDL_Color &Color /*= Colors::WHITE*/) -: CLabel(BG->pos.x, BG->pos.y, Font, Align, Color, "") -{ - init(); - bg = BG; - addChild(bg); - pos = bg->pos; - getBorderSize(); - textLock = false; -} - -CGStatusBar::CGStatusBar(int x, int y, std::string name/*="ADROLLVR.bmp"*/, int maxw/*=-1*/) -: CLabel(x, y, FONT_SMALL, CENTER) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - init(); - bg = new CPicture(name); - pos = bg->pos; - if((unsigned int)maxw < pos.w) - { - vstd::amin(pos.w, maxw); - bg->srcRect = new Rect(0, 0, maxw, pos.h); - } - textLock = false; -} - -CGStatusBar::~CGStatusBar() -{ - GH.statusbar = oldStatusBar; -} - -void CGStatusBar::show(SDL_Surface * to) -{ - showAll(to); -} - -void CGStatusBar::init() -{ - oldStatusBar = GH.statusbar; - GH.statusbar = this; -} - -Point CGStatusBar::getBorderSize() -{ - //Width of borders where text should not be printed - static const Point borderSize(5,1); - - switch(alignment) - { - case TOPLEFT: return Point(borderSize.x, borderSize.y); - case CENTER: return Point(pos.w/2, pos.h/2); - case BOTTOMRIGHT: return Point(pos.w - borderSize.x, pos.h - borderSize.y); - } - assert(0); - return Point(); -} - -void CGStatusBar::lock(bool shouldLock) -{ - textLock = shouldLock; -} - -CTextInput::CTextInput(const Rect &Pos, EFonts font, const CFunctionList &CB): - CLabel(Pos.x, Pos.y, font, CENTER), - cb(CB) -{ - type |= REDRAW_PARENT; - focus = false; - pos.h = Pos.h; - pos.w = Pos.w; - captureAllKeys = true; - bg = nullptr; - addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT); - giveFocus(); -} - -CTextInput::CTextInput( const Rect &Pos, const Point &bgOffset, const std::string &bgName, const CFunctionList &CB ) -:cb(CB) -{ - focus = false; - pos += Pos; - captureAllKeys = true; - OBJ_CONSTRUCTION; - bg = new CPicture(bgName, bgOffset.x, bgOffset.y); - addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT); - giveFocus(); -} - -CTextInput::CTextInput(const Rect &Pos, SDL_Surface *srf) -{ - focus = false; - pos += Pos; - captureAllKeys = true; - OBJ_CONSTRUCTION; - bg = new CPicture(Pos, 0, true); - Rect hlp = Pos; - if(srf) - CSDL_Ext::blitSurface(srf, &hlp, *bg, nullptr); - else - SDL_FillRect(*bg, nullptr, 0); - pos.w = bg->pos.w; - pos.h = bg->pos.h; - bg->pos = pos; - addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT); - giveFocus(); -} - -void CTextInput::focusGot() -{ - CSDL_Ext::startTextInput(&pos); -} - -void CTextInput::focusLost() -{ - CSDL_Ext::stopTextInput(); -} - - -std::string CTextInput::visibleText() -{ - return focus ? text + newText + "_" : text; -} - -void CTextInput::clickLeft( tribool down, bool previousState ) -{ - if(down && !focus) - giveFocus(); -} - -void CTextInput::keyPressed( const SDL_KeyboardEvent & key ) -{ - - if(!focus || key.state != SDL_PRESSED) - return; - - if(key.keysym.sym == SDLK_TAB) - { - moveFocus(); - GH.breakEventHandling(); - return; - } - - bool redrawNeeded = false; - #ifdef VCMI_SDL1 - std::string oldText = text; - #endif // 0 - switch(key.keysym.sym) - { - case SDLK_DELETE: // have index > ' ' so it won't be filtered out by default section - return; - case SDLK_BACKSPACE: - if(!newText.empty()) - { - Unicode::trimRight(newText); - redrawNeeded = true; - } - else if(!text.empty()) - { - Unicode::trimRight(text); - redrawNeeded = true; - } - break; - default: - #ifdef VCMI_SDL1 - if (key.keysym.unicode < ' ') - return; - else - { - text += key.keysym.unicode; //TODO 16-/>8 - redrawNeeded = true; - } - #endif // 0 - break; - } - #ifdef VCMI_SDL1 - filters(text, oldText); - #endif // 0 - if (redrawNeeded) - { - redraw(); - cb(text); - } -} - -void CTextInput::setText( const std::string &nText, bool callCb ) -{ - CLabel::setText(nText); - if(callCb) - cb(text); -} - -bool CTextInput::captureThisEvent(const SDL_KeyboardEvent & key) -{ - if(key.keysym.sym == SDLK_RETURN || key.keysym.sym == SDLK_KP_ENTER) - return false; - - #ifdef VCMI_SDL1 - //this should allow all non-printable keys to go through (for example arrows) - if (key.keysym.unicode < ' ') - return false; - - return true; - #else - return false; - #endif -} - -#ifndef VCMI_SDL1 -void CTextInput::textInputed(const SDL_TextInputEvent & event) -{ - if(!focus) - return; - std::string oldText = text; - - text += event.text; - - filters(text,oldText); - if (text != oldText) - { - redraw(); - cb(text); - } - newText = ""; -} - -void CTextInput::textEdited(const SDL_TextEditingEvent & event) -{ - if(!focus) - return; - - newText = event.text; - redraw(); - cb(text+newText); -} - -#endif - - -void CTextInput::filenameFilter(std::string & text, const std::string &) -{ - static const std::string forbiddenChars = "<>:\"/\\|?*\r\n"; //if we are entering a filename, some special characters won't be allowed - size_t pos; - while ((pos = text.find_first_of(forbiddenChars)) != std::string::npos) - text.erase(pos, 1); -} - -void CTextInput::numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue) -{ - assert(minValue < maxValue); - - if (text.empty()) - text = "0"; - - size_t pos = 0; - if (text[0] == '-') //allow '-' sign as first symbol only - pos++; - - while (pos < text.size()) - { - if (text[pos] < '0' || text[pos] > '9') - { - text = oldText; - return; //new text is not number. - } - pos++; - } - try - { - int value = boost::lexical_cast(text); - if (value < minValue) - text = boost::lexical_cast(minValue); - else if (value > maxValue) - text = boost::lexical_cast(maxValue); - } - catch(boost::bad_lexical_cast &) - { - //Should never happen. Unless I missed some cases - logGlobal->warnStream() << "Warning: failed to convert "<< text << " to number!"; - text = oldText; - } -} - -CFocusable::CFocusable() -{ - focusables.push_back(this); -} - -CFocusable::~CFocusable() -{ - if(inputWithFocus == this) - { - focusLost(); - inputWithFocus = nullptr; - } - - focusables -= this; -} -void CFocusable::giveFocus() -{ - if(inputWithFocus) - { - inputWithFocus->focus = false; - inputWithFocus->focusLost(); - inputWithFocus->redraw(); - } - - focus = true; - inputWithFocus = this; - focusGot(); - redraw(); -} - -void CFocusable::moveFocus() -{ - auto i = vstd::find(focusables, this), - ourIt = i; - for(i++; i != ourIt; i++) - { - if(i == focusables.end()) - i = focusables.begin(); - - if((*i)->active) - { - (*i)->giveFocus(); - break;; - } - } -} - -CWindowObject::CWindowObject(int options_, std::string imageName, Point centerAt): - CIntObject(getUsedEvents(options_), Point()), - shadow(nullptr), - options(options_), - background(createBg(imageName, options & PLAYER_COLORED)) -{ - assert(parent == nullptr); //Safe to remove, but windows should not have parent - - if (options & RCLICK_POPUP) - CCS->curh->hide(); - - if (background) - pos = background->center(centerAt); - else - center(centerAt); - - if (!(options & SHADOW_DISABLED)) - setShadow(true); -} - -CWindowObject::CWindowObject(int options_, std::string imageName): - CIntObject(getUsedEvents(options_), Point()), - shadow(nullptr), - options(options_), - background(createBg(imageName, options & PLAYER_COLORED)) -{ - assert(parent == nullptr); //Safe to remove, but windows should not have parent - - if (options & RCLICK_POPUP) - CCS->curh->hide(); - - if (background) - pos = background->center(); - else - center(Point(screen->w/2, screen->h/2)); - - if (!(options & SHADOW_DISABLED)) - setShadow(true); -} - -CWindowObject::~CWindowObject() -{ - setShadow(false); -} - -CPicture * CWindowObject::createBg(std::string imageName, bool playerColored) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - if (imageName.empty()) - return nullptr; - - auto image = new CPicture(imageName); - if (playerColored) - image->colorize(LOCPLINT->playerID); - return image; -} - -void CWindowObject::setBackground(std::string filename) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL; - - delete background; - background = createBg(filename, options & PLAYER_COLORED); - - if (background) - pos = background->center(Point(pos.w/2 + pos.x, pos.h/2 + pos.y)); - - updateShadow(); -} - -int CWindowObject::getUsedEvents(int options) -{ - if (options & RCLICK_POPUP) - return RCLICK; - return 0; -} - -void CWindowObject::updateShadow() -{ - setShadow(false); - if (!(options & SHADOW_DISABLED)) - setShadow(true); -} - -void CWindowObject::setShadow(bool on) -{ - //size of shadow - static const int size = 8; - - if (on == bool(shadow)) - return; - - vstd::clear_pointer(shadow); - - //object too small to cast shadow - if (pos.h <= size || pos.w <= size) - return; - - if (on) - { - - //helper to set last row - auto blitAlphaRow = [](SDL_Surface *surf, size_t row) - { - Uint8 * ptr = (Uint8*)surf->pixels + surf->pitch * (row); - - for (size_t i=0; i< surf->w; i++) - { - Channels::px<4>::a.set(ptr, 128); - ptr+=4; - } - }; - - // helper to set last column - auto blitAlphaCol = [](SDL_Surface *surf, size_t col) - { - Uint8 * ptr = (Uint8*)surf->pixels + 4 * (col); - - for (size_t i=0; i< surf->h; i++) - { - Channels::px<4>::a.set(ptr, 128); - ptr+= surf->pitch; - } - }; - - static SDL_Surface * shadowCornerTempl = nullptr; - static SDL_Surface * shadowBottomTempl = nullptr; - static SDL_Surface * shadowRightTempl = nullptr; - - //one-time initialization - if (!shadowCornerTempl) - { - //create "template" surfaces - shadowCornerTempl = CSDL_Ext::createSurfaceWithBpp<4>(size, size); - shadowBottomTempl = CSDL_Ext::createSurfaceWithBpp<4>(1, size); - shadowRightTempl = CSDL_Ext::createSurfaceWithBpp<4>(size, 1); - - Uint32 shadowColor = SDL_MapRGBA(shadowCornerTempl->format, 0, 0, 0, 192); - - //fill with shadow body color - SDL_FillRect(shadowCornerTempl, nullptr, shadowColor); - SDL_FillRect(shadowBottomTempl, nullptr, shadowColor); - SDL_FillRect(shadowRightTempl, nullptr, shadowColor); - - //fill last row and column with more transparent color - blitAlphaCol(shadowRightTempl , size-1); - blitAlphaCol(shadowCornerTempl, size-1); - blitAlphaRow(shadowBottomTempl, size-1); - blitAlphaRow(shadowCornerTempl, size-1); - } - - OBJ_CONSTRUCTION_CAPTURING_ALL; - - //FIXME: do something with this points - Point shadowStart; - if (options & BORDERED) - shadowStart = Point(size - 14, size - 14); - else - shadowStart = Point(size, size); - - Point shadowPos; - if (options & BORDERED) - shadowPos = Point(pos.w + 14, pos.h + 14); - else - shadowPos = Point(pos.w, pos.h); - - Point fullsize; - if (options & BORDERED) - fullsize = Point(pos.w + 28, pos.h + 29); - else - fullsize = Point(pos.w, pos.h); - - //create base 8x8 piece of shadow - SDL_Surface * shadowCorner = CSDL_Ext::copySurface(shadowCornerTempl); - SDL_Surface * shadowBottom = CSDL_Ext::scaleSurfaceFast(shadowBottomTempl, fullsize.x - size, size); - SDL_Surface * shadowRight = CSDL_Ext::scaleSurfaceFast(shadowRightTempl, size, fullsize.y - size); - - blitAlphaCol(shadowBottom, 0); - blitAlphaRow(shadowRight, 0); - - //generate "shadow" object with these 3 pieces in it - shadow = new CIntObject; - shadow->addChild(new CPicture(shadowCorner, shadowPos.x, shadowPos.y)); - shadow->addChild(new CPicture(shadowRight, shadowPos.x, shadowStart.y)); - shadow->addChild(new CPicture(shadowBottom, shadowStart.x, shadowPos.y)); - } -} - -void CWindowObject::showAll(SDL_Surface *to) -{ - CIntObject::showAll(to); - if ((options & BORDERED) && (pos.h != to->h || pos.w != to->w)) - CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to, pos.w+28, pos.h+29, pos.x-14, pos.y-15); -} - -void CWindowObject::close() -{ - GH.popIntTotally(this); -} - -void CWindowObject::clickRight(tribool down, bool previousState) -{ - close(); - CCS->curh->show(); -} diff --git a/client/gui/CIntObjectClasses.h b/client/gui/CIntObjectClasses.h deleted file mode 100644 index e5df4d2ec..000000000 --- a/client/gui/CIntObjectClasses.h +++ /dev/null @@ -1,557 +0,0 @@ -#pragma once - -#include "CIntObject.h" -#include "SDL_Extensions.h" -#include "../../lib/FunctionList.h" - -struct SDL_Surface; -struct Rect; -class CAnimImage; -class CLabel; -class CAnimation; -class CDefHandler; - -/* - * CPicture.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 - * - */ - -// Window GUI class -class CSimpleWindow : public CIntObject -{ -public: - SDL_Surface * bitmap; //background - virtual void show(SDL_Surface * to); - CSimpleWindow():bitmap(nullptr){}; //c-tor - virtual ~CSimpleWindow(); //d-tor -}; - -// Image class -class CPicture : public CIntObject -{ - void setSurface(SDL_Surface *to); -public: - SDL_Surface * bg; - Rect * srcRect; //if nullptr then whole surface will be used - bool freeSurf; //whether surface will be freed upon CPicture destruction - bool needRefresh;//Surface needs to be displayed each frame - - operator SDL_Surface*() - { - return bg; - } - - CPicture(const Rect & r, const SDL_Color & color, bool screenFormat = false); //rect filled with given color - CPicture(const Rect & r, ui32 color, bool screenFormat = false); //rect filled with given color - CPicture(SDL_Surface * BG, int x = 0, int y=0, bool Free = true); //wrap existing SDL_Surface - CPicture(const std::string &bmpname, int x=0, int y=0); - CPicture(SDL_Surface *BG, const Rect &SrcRext, int x = 0, int y = 0, bool free = false); //wrap subrect of given surface - ~CPicture(); - void init(); - - //set alpha value for whole surface. Note: may be messed up if surface is shared - // 0=transparent, 255=opaque - void setAlpha(int value); - - void scaleTo(Point size); - void createSimpleRect(const Rect &r, bool screenFormat, ui32 color); - void show(SDL_Surface * to); - void showAll(SDL_Surface * to); - void convertToScreenBPP(); - void colorizeAndConvert(PlayerColor player); - void colorize(PlayerColor player); -}; - -/// area filled with specific texture -class CFilledTexture : CIntObject -{ - SDL_Surface * texture; - -public: - CFilledTexture(std::string imageName, Rect position); - ~CFilledTexture(); - void showAll(SDL_Surface *to); -}; - -namespace config{struct ButtonInfo;} - -/// Base class for buttons. -class CButtonBase : public CKeyShortcut -{ -public: - enum ButtonState - { - NORMAL=0, - PRESSED=1, - BLOCKED=2, - HIGHLIGHTED=3 - }; -private: - int bitmapOffset; // base offset of visible bitmap from animation - ButtonState state;//current state of button from enum - -public: - bool swappedImages,//fix for some buttons: normal and pressed image are swapped - keepFrame; // don't change visual representation - - void addTextOverlay(const std::string &Text, EFonts font, SDL_Color color = Colors::WHITE); - void update();//to refresh button after image or text change - - void setOffset(int newOffset); - void setState(ButtonState newState); - ButtonState getState(); - - //just to make code clearer - void block(bool on); - bool isBlocked(); - bool isHighlighted(); - - CAnimImage * image; //image for this button - CLabel * text;//text overlay - - CButtonBase(); //c-tor - virtual ~CButtonBase(); //d-tor -}; - -/// Typical Heroes 3 button which can be inactive or active and can -/// hold further information if you right-click it -class CAdventureMapButton : public CButtonBase -{ - std::vector imageNames;//store list of images that can be used by this button - size_t currentImage; - - void onButtonClicked(); // calls callback -public: - std::map hoverTexts; //text for statusbar - std::string helpBox; //for right-click help - CFunctionList callback; - bool actOnDown,//runs when mouse is pressed down over it, not when up - hoverable,//if true, button will be highlighted when hovered - borderEnabled, - soundDisabled; - SDL_Color borderColor; - - void clickRight(tribool down, bool previousState); - virtual void clickLeft(tribool down, bool previousState); - void hover (bool on); - - CAdventureMapButton(); //c-tor - CAdventureMapButton( const std::string &Name, const std::string &HelpBox, const CFunctionList &Callback, int x, int y, const std::string &defName, int key=0, std::vector * add = nullptr, bool playerColoredButton = false );//c-tor - CAdventureMapButton( const std::pair &help, const CFunctionList &Callback, int x, int y, const std::string &defName, int key=0, std::vector * add = nullptr, bool playerColoredButton = false );//c-tor - CAdventureMapButton( const std::string &Name, const std::string &HelpBox, const CFunctionList &Callback, config::ButtonInfo *info, int key=0);//c-tor - - void init(const CFunctionList &Callback, const std::map &Name, const std::string &HelpBox, bool playerColoredButton, const std::string &defName, std::vector * add, int x, int y, int key ); - - void setIndex(size_t index, bool playerColoredButton=false); - void setImage(CAnimation* anim, bool playerColoredButton=false, int animFlags=0); - void setPlayerColor(PlayerColor player); - void showAll(SDL_Surface * to); -}; - -/// A button which can be selected/deselected -class CHighlightableButton - : public CAdventureMapButton -{ -public: - CHighlightableButton(const CFunctionList &onSelect, const CFunctionList &onDeselect, const std::map &Name, const std::string &HelpBox, bool playerColoredButton, const std::string &defName, std::vector * add, int x, int y, int key=0); - CHighlightableButton(const std::pair &help, const CFunctionList &onSelect, int x, int y, const std::string &defName, int myid, int key=0, std::vector * add = nullptr, bool playerColoredButton = false );//c-tor - CHighlightableButton(const std::string &Name, const std::string &HelpBox, const CFunctionList &onSelect, int x, int y, const std::string &defName, int myid, int key=0, std::vector * add = nullptr, bool playerColoredButton = false );//c-tor - bool onlyOn;//button can not be de-selected - bool selected;//state of highlightable button - int ID; //for identification - CFunctionList callback2; //when de-selecting - void select(bool on); - void clickLeft(tribool down, bool previousState); -}; - -/// A group of buttons where one button can be selected -class CHighlightableButtonsGroup : public CIntObject -{ -public: - CFunctionList onChange; //called when changing selected button with new button's id - std::vector buttons; - bool musicLike; //determines the behaviour of this group - - //void addButton(const std::map &tooltip, const std::string &HelpBox, const std::string &defName, int x, int y, int uid); - void addButton(CHighlightableButton* bt);//add existing button, it'll be deleted by CHighlightableButtonsGroup destructor - void addButton(const std::map &tooltip, const std::string &HelpBox, const std::string &defName, int x, int y, int uid, const CFunctionList &OnSelect=0, int key=0); //creates new button - CHighlightableButtonsGroup(const CFunctionList & OnChange, bool musicLikeButtons = false); - ~CHighlightableButtonsGroup(); - void select(int id, bool mode); //mode==0: id is serial; mode==1: id is unique button id - void selectionChanged(int to); - void show(SDL_Surface * to); - void showAll(SDL_Surface * to); - void block(ui8 on); -}; - -/// A typical slider which can be orientated horizontally/vertically. -class CSlider : public CIntObject -{ -public: - CAdventureMapButton *left, *right, *slider; //if vertical then left=up - int capacity;//how many elements can be active at same time (e.g. hero list = 5) - int amount; //total amount of elements (e.g. hero list = 0-8) - int positions; //number of highest position (0 if there is only one) - int value; //first active element - int scrollStep; // how many elements will be scrolled via one click, default = 1 - bool horizontal; - bool wheelScrolling; - bool keyScrolling; - - std::function moved; - - void redrawSlider(); - void sliderClicked(); - void moveLeft(); - void moveRight(); - void moveTo(int to); - void block(bool on); - void setAmount(int to); - - void keyPressed(const SDL_KeyboardEvent & key); - void wheelScrolled(bool down, bool in); - void clickLeft(tribool down, bool previousState); - void mouseMoved (const SDL_MouseMotionEvent & sEvent); - void showAll(SDL_Surface * to); - - CSlider(int x, int y, int totalw, std::function Moved, int Capacity, int Amount, - int Value=0, bool Horizontal=true, int style = 0); //style 0 - brown, 1 - blue - ~CSlider(); - void moveToMax(); -}; - -/// Used as base for Tabs and List classes -class CObjectList : public CIntObject -{ -public: - typedef std::function CreateFunc; - typedef std::function DestroyFunc; - -private: - CreateFunc createObject; - DestroyFunc destroyObject; - -protected: - //Internal methods for safe creation of items (Children capturing and activation/deactivation if needed) - void deleteItem(CIntObject* item); - CIntObject* createItem(size_t index); - - CObjectList(CreateFunc create, DestroyFunc destroy = DestroyFunc());//Protected constructor -}; - -/// Window element with multiple tabs -class CTabbedInt : public CObjectList -{ -private: - CIntObject * activeTab; - size_t activeID; - -public: - //CreateFunc, DestroyFunc - see CObjectList - //Pos - position of object, all tabs will be moved to this position - //ActiveID - ID of initially active tab - CTabbedInt(CreateFunc create, DestroyFunc destroy = DestroyFunc(), Point position=Point(), size_t ActiveID=0); - - void setActive(size_t which); - //recreate active tab - void reset(); - - //return currently active item - CIntObject * getItem(); -}; - -/// List of IntObjects with optional slider -class CListBox : public CObjectList -{ -private: - std::list< CIntObject* > items; - size_t first; - size_t totalSize; - - Point itemOffset; - CSlider * slider; - - void updatePositions(); -public: - //CreateFunc, DestroyFunc - see CObjectList - //Pos - position of first item - //ItemOffset - distance between items in the list - //VisibleSize - maximal number of displayable at once items - //TotalSize - //Slider - slider style, bit field: 1 = present(disabled), 2=horisontal(vertical), 4=blue(brown) - //SliderPos - position of slider, if present - CListBox(CreateFunc create, DestroyFunc destroy, Point Pos, Point ItemOffset, size_t VisibleSize, - size_t TotalSize, size_t InitialPos=0, int Slider=0, Rect SliderPos=Rect() ); - - //recreate all visible items - void reset(); - - //change or get total amount of items in the list - void resize(size_t newSize); - size_t size(); - - //return item with index which or null if not present - CIntObject * getItem(size_t which); - - //return currently active items - const std::list< CIntObject * > & getItems(); - - //get index of this item. -1 if not found - size_t getIndexOf(CIntObject * item); - - //scroll list to make item which visible - void scrollTo(size_t which); - - //scroll list to specified position - void moveToPos(size_t which); - void moveToNext(); - void moveToPrev(); - - size_t getPos(); -}; - -/// Small helper class to manage group of similar labels -class CLabelGroup : public CIntObject -{ - std::list labels; - EFonts font; - EAlignment align; - SDL_Color color; -public: - CLabelGroup(EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE); - void add(int x=0, int y=0, const std::string &text = ""); -}; - -/// Base class for all text-related widgets. -/// Controls text blitting-related options -class CTextContainer : public virtual CIntObject -{ -protected: - /// returns size of border, for left- or right-aligned text - virtual Point getBorderSize() = 0; - /// do actual blitting of line. Text "what" will be placed at "where" and aligned according to alignment - void blitLine(SDL_Surface * to, Rect where, std::string what); - - CTextContainer(EAlignment alignment, EFonts font, SDL_Color color); - -public: - EAlignment alignment; - EFonts font; - SDL_Color color; // default font color. Can be overridden by placing "{}" into the string -}; - -/// Label which shows text -class CLabel : public CTextContainer -{ -protected: - Point getBorderSize() override; - virtual std::string visibleText(); - - CPicture *bg; -public: - - std::string text; - bool autoRedraw; //whether control will redraw itself on setTxt - - std::string getText(); - virtual void setText(const std::string &Txt); - - CLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, - const SDL_Color &Color = Colors::WHITE, const std::string &Text = ""); - void showAll(SDL_Surface * to); //shows statusbar (with current text) -}; - -/// Multi-line label that can display multiple lines of text -/// If text is too big to fit into requested area remaining part will not be visible -class CMultiLineLabel : public CLabel -{ - // text to blit, split into lines that are no longer than widget width - std::vector lines; - - // area of text that actually will be printed, default is widget size - Rect visibleSize; - - void splitText(const std::string &Txt); - Rect getTextLocation(); -public: - // total size of text, x = longest line of text, y = total height of lines - Point textSize; - - CMultiLineLabel(Rect position, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE, const std::string &Text = ""); - - void setText(const std::string &Txt); - void showAll(SDL_Surface * to); - - void setVisibleSize(Rect visibleSize); - // scrolls text visible in widget. Positive value will move text up - void scrollTextTo(int distance); - void scrollTextBy(int distance); -}; - -/// a multi-line label that tries to fit text with given available width and height; -/// if not possible, it creates a slider for scrolling text -class CTextBox : public CIntObject -{ - int sliderStyle; -public: - CMultiLineLabel * label; - CSlider *slider; - - CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE); - - void resize(Point newSize); - void setText(const std::string &Txt); - void sliderMoved(int to); -}; - -/// Status bar which is shown at the bottom of the in-game screens -class CGStatusBar : public CLabel -{ - bool textLock; //Used for blocking changes to the text - void init(); - - CGStatusBar *oldStatusBar; -protected: - Point getBorderSize() override; - -public: - - void clear();//clears statusbar and refreshes - void setText(const std::string & Text) override; //prints text and refreshes statusbar - - void show(SDL_Surface * to); //shows statusbar (with current text) - - CGStatusBar(CPicture *BG, EFonts Font = FONT_SMALL, EAlignment Align = CENTER, const SDL_Color &Color = Colors::WHITE); //given CPicture will be captured by created sbar and it's pos will be used as pos for sbar - CGStatusBar(int x, int y, std::string name, int maxw=-1); - ~CGStatusBar(); - - void lock(bool shouldLock); //If true, current text cannot be changed until lock(false) is called -}; - -/// UIElement which can get input focus -class CFocusable : public virtual CIntObject -{ -protected: - virtual void focusGot(){}; - virtual void focusLost(){}; -public: - bool focus; //only one focusable control can have focus at one moment - - void giveFocus(); //captures focus - void moveFocus(); //moves focus to next active control (may be used for tab switching) - - static std::list focusables; //all existing objs - static CFocusable *inputWithFocus; //who has focus now - CFocusable(); - ~CFocusable(); -}; - -/// Text input box where players can enter text -class CTextInput : public CLabel, public CFocusable -{ - std::string newText; -protected: - std::string visibleText() override; - - void focusGot() override; - void focusLost() override; -public: - CFunctionList cb; - CFunctionList filters; - void setText(const std::string &nText, bool callCb = false); - - CTextInput(const Rect &Pos, EFonts font, const CFunctionList &CB); - CTextInput(const Rect &Pos, const Point &bgOffset, const std::string &bgName, const CFunctionList &CB); - CTextInput(const Rect &Pos, SDL_Surface *srf = nullptr); - - void clickLeft(tribool down, bool previousState) override; - void keyPressed(const SDL_KeyboardEvent & key) override; - bool captureThisEvent(const SDL_KeyboardEvent & key) override; - -#ifndef VCMI_SDL1 - void textInputed(const SDL_TextInputEvent & event) override; - void textEdited(const SDL_TextEditingEvent & event) override; - - -#endif // VCMI_SDL1 - - //Filter that will block all characters not allowed in filenames - static void filenameFilter(std::string &text, const std::string & oldText); - //Filter that will allow only input of numbers in range min-max (min-max are allowed) - //min-max should be set via something like std::bind - static void numberFilter(std::string &text, const std::string & oldText, int minValue, int maxValue); -}; - -/// Shows a text by moving the mouse cursor over the object -class CHoverableArea: public virtual CIntObject -{ -public: - std::string hoverText; - - virtual void hover (bool on); - - CHoverableArea(); - virtual ~CHoverableArea(); -}; - -/// Can interact on left and right mouse clicks, plus it shows a text when by hovering over it -class LRClickableAreaWText: public CHoverableArea -{ -public: - std::string text; - - LRClickableAreaWText(); - LRClickableAreaWText(const Rect &Pos, const std::string &HoverText = "", const std::string &ClickText = ""); - virtual ~LRClickableAreaWText(); - void init(); - - virtual void clickLeft(tribool down, bool previousState); - virtual void clickRight(tribool down, bool previousState); -}; - -/// Basic class for windows -class CWindowObject : public CIntObject -{ - CPicture * createBg(std::string imageName, bool playerColored); - int getUsedEvents(int options); - - CIntObject *shadow; - void setShadow(bool on); - - int options; - -protected: - CPicture * background; - - //Simple function with call to GH.popInt - void close(); - //Used only if RCLICK_POPUP was set - void clickRight(tribool down, bool previousState); - //To display border - void showAll(SDL_Surface *to); - //change or set background image - void setBackground(std::string filename); - void updateShadow(); -public: - enum EOptions - { - PLAYER_COLORED=1, //background will be player-colored - RCLICK_POPUP=2, // window will behave as right-click popup - BORDERED=4, // window will have border if current resolution is bigger than size of window - SHADOW_DISABLED=8 //this window won't display any shadow - }; - - /* - * options - EOpions enum - * imageName - name for background image, can be empty - * centerAt - position of window center. Default - center of the screen - */ - CWindowObject(int options, std::string imageName, Point centerAt); - CWindowObject(int options, std::string imageName = ""); - ~CWindowObject(); -}; diff --git a/client/gui/Geometries.cpp b/client/gui/Geometries.cpp index c56999e75..367d9aac4 100644 --- a/client/gui/Geometries.cpp +++ b/client/gui/Geometries.cpp @@ -1,6 +1,11 @@ #include "StdInc.h" #include "Geometries.h" #include "../CMT.h" +#include + +Point::Point(const SDL_MouseMotionEvent &a) + :x(a.x),y(a.y) +{} Rect Rect::createCentered( int w, int h ) { @@ -15,4 +20,4 @@ Rect Rect::around(const Rect &r, int width /*= 1*/) /*creates rect around anothe Rect Rect::centerIn(const Rect &r) { return Rect(r.x + (r.w - w) / 2, r.y + (r.h - h) / 2, w, h); -} \ No newline at end of file +} diff --git a/client/gui/Geometries.h b/client/gui/Geometries.h index 8e56c1e8a..af67813ec 100644 --- a/client/gui/Geometries.h +++ b/client/gui/Geometries.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include "../../lib/int3.h" /* @@ -21,6 +20,7 @@ #undef min #endif +struct SDL_MouseMotionEvent; // A point with x/y coordinate, used mostly for graphic rendering struct Point @@ -38,9 +38,7 @@ struct Point Point(const int3 &a) :x(a.x),y(a.y) {} - Point(const SDL_MouseMotionEvent &a) - :x(a.x),y(a.y) - {} + Point(const SDL_MouseMotionEvent &a); template Point operator+(const T &b) const @@ -265,4 +263,4 @@ struct Rect : public SDL_Rect ret.h = y2 -ret.y; return ret; } -}; \ No newline at end of file +}; diff --git a/client/gui/SDL_Extensions.cpp b/client/gui/SDL_Extensions.cpp index f84cd07e9..1732d449a 100644 --- a/client/gui/SDL_Extensions.cpp +++ b/client/gui/SDL_Extensions.cpp @@ -2,7 +2,6 @@ #include "SDL_Extensions.h" #include "SDL_Pixels.h" -#include #include "../CGameInfo.h" #include "../CMessage.h" #include "../CDefHandler.h" diff --git a/client/gui/SDL_Extensions.h b/client/gui/SDL_Extensions.h index e74dc8cb0..9681c0062 100644 --- a/client/gui/SDL_Extensions.h +++ b/client/gui/SDL_Extensions.h @@ -16,10 +16,11 @@ #endif #include -#include +#include #include "../../lib/int3.h" -#include "../Graphics.h" +//#include "../Graphics.h" #include "Geometries.h" +#include "../../lib/GameConstants.h" //A macro to force inlining some of our functions. Compiler (at least MSVC) is not so smart here-> without that displaying is MUCH slower @@ -141,15 +142,16 @@ typename boost::enable_if_c::type, T>::type abs(T arg) } template -std::string makeNumberShort(IntType number) //the output is a string containing at most 5 characters [4 if positive] (eg. intead 10000 it gives 10k) +std::string makeNumberShort(IntType number, IntType maxLength = 3) //the output is a string containing at most 5 characters [4 if positive] (eg. intead 10000 it gives 10k) { - if (abs(number) < 1000) + IntType max = pow(10, maxLength); + if (abs(number) < max) return boost::lexical_cast(number); - std::string symbols = "kMGTPE"; + std::string symbols = " kMGTPE"; auto iter = symbols.begin(); - while (number >= 1000) + while (number >= max) { number /= 1000; iter++; diff --git a/client/gui/SDL_Pixels.h b/client/gui/SDL_Pixels.h index b35cb5e83..971054c89 100644 --- a/client/gui/SDL_Pixels.h +++ b/client/gui/SDL_Pixels.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "SDL_Extensions.h" /* diff --git a/client/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp similarity index 77% rename from client/AdventureMapClasses.cpp rename to client/widgets/AdventureMapClasses.cpp index cd60e118c..053eedd54 100644 --- a/client/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -1,30 +1,43 @@ #include "StdInc.h" #include "AdventureMapClasses.h" -#include "../CCallback.h" -#include "../lib/JsonNode.h" -#include "../lib/filesystem/Filesystem.h" -#include "../lib/mapping/CMap.h" -#include "../lib/CModHandler.h" -#include "../lib/mapObjects/CGHeroInstance.h" -#include "../lib/CGameState.h" -#include "../lib/CGeneralTextHandler.h" -#include "../lib/CTownHandler.h" -#include "../lib/NetPacksBase.h" -#include "../lib/CHeroHandler.h" -#include "../lib/StringConstants.h" -#include "CAdvmapInterface.h" -#include "CAnimation.h" -#include "CGameInfo.h" -#include "CPlayerInterface.h" -#include "CMusicHandler.h" -#include "Graphics.h" -#include "GUIClasses.h" -#include "gui/CGuiHandler.h" -#include "gui/SDL_Pixels.h" +#include + +#include "MiscWidgets.h" +#include "CComponent.h" + +#include "../CGameInfo.h" +#include "../CMusicHandler.h" +#include "../CPlayerInterface.h" +#include "../CPreGame.h" +#include "../Graphics.h" + +#include "../gui/CGuiHandler.h" +#include "../gui/SDL_Pixels.h" + +#include "../windows/InfoWindows.h" +#include "../windows/CAdvmapInterface.h" +#include "../windows/GUIClasses.h" + +#include "../battle/CBattleInterfaceClasses.h" +#include "../battle/CBattleInterface.h" + +#include "../../CCallback.h" +#include "../../lib/StartInfo.h" +#include "../../lib/CGameState.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CHeroHandler.h" +#include "../../lib/CModHandler.h" +#include "../../lib/CTownHandler.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" /* - * CAdventureMapClasses.h, part of VCMI engine + * CAdventureMapClasses.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -93,15 +106,15 @@ CList::CList(int Size, Point position, std::string btnUp, std::string btnDown, s selected(nullptr) { OBJ_CONSTRUCTION_CAPTURING_ALL; - scrollUp = new CAdventureMapButton(CGI->generaltexth->zelp[helpUp], 0, 0, 0, btnUp); + scrollUp = new CButton(Point(0, 0), btnUp, CGI->generaltexth->zelp[helpUp]); list = new CListBox(create, destroy, Point(1,scrollUp->pos.h), Point(0, 32), size, listAmount); //assign callback only after list was created - scrollUp->callback = std::bind(&CListBox::moveToPrev, list); - scrollDown = new CAdventureMapButton(CGI->generaltexth->zelp[helpDown], std::bind(&CListBox::moveToNext, list), 0, scrollUp->pos.h + 32*size, btnDown); + scrollUp->addCallback(std::bind(&CListBox::moveToPrev, list)); + scrollDown = new CButton(Point(0, scrollUp->pos.h + 32*size), btnDown, CGI->generaltexth->zelp[helpDown], std::bind(&CListBox::moveToNext, list)); - scrollDown->callback += std::bind(&CList::update, this); - scrollUp->callback += std::bind(&CList::update, this); + scrollDown->addCallback(std::bind(&CList::update, this)); + scrollUp->addCallback(std::bind(&CList::update, this)); update(); } @@ -950,3 +963,248 @@ void CInfoBar::showGameStatus() setTimer(3000); redraw(); } + +void CInGameConsole::show(SDL_Surface * to) +{ + int number = 0; + + std::vector >::iterator> toDel; + + boost::unique_lock lock(texts_mx); + for(auto it = texts.begin(); it != texts.end(); ++it, ++number) + { + Point leftBottomCorner(0, screen->h); + if(LOCPLINT->battleInt) + { + leftBottomCorner = LOCPLINT->battleInt->pos.bottomLeft(); + } + graphics->fonts[FONT_MEDIUM]->renderTextLeft(to, it->first, Colors::GREEN, + Point(leftBottomCorner.x + 50, leftBottomCorner.y - texts.size() * 20 - 80 + number*20)); + + if(SDL_GetTicks() - it->second > defaultTimeout) + { + toDel.push_back(it); + } + } + + for(auto & elem : toDel) + { + texts.erase(elem); + } +} + +void CInGameConsole::print(const std::string &txt) +{ + boost::unique_lock lock(texts_mx); + int lineLen = conf.go()->ac.outputLineLength; + + if(txt.size() < lineLen) + { + texts.push_back(std::make_pair(txt, SDL_GetTicks())); + if(texts.size() > maxDisplayedTexts) + { + texts.pop_front(); + } + } + else + { + assert(lineLen); + for(int g=0; g maxDisplayedTexts) + { + texts.pop_front(); + } + } + } +} + +void CInGameConsole::keyPressed (const SDL_KeyboardEvent & key) +{ + if(key.type != SDL_KEYDOWN) return; + + if(!captureAllKeys && key.keysym.sym != SDLK_TAB) return; //because user is not entering any text + + switch(key.keysym.sym) + { + case SDLK_TAB: + case SDLK_ESCAPE: + { + if(captureAllKeys) + { + captureAllKeys = false; + endEnteringText(false); + } + else if(SDLK_TAB) + { + captureAllKeys = true; + startEnteringText(); + } + break; + } + case SDLK_RETURN: //enter key + { + if(enteredText.size() > 0 && captureAllKeys) + { + captureAllKeys = false; + endEnteringText(true); + CCS->soundh->playSound("CHAT"); + } + break; + } + case SDLK_BACKSPACE: + { + if(enteredText.size() > 1) + { + Unicode::trimRight(enteredText,2); + enteredText += '_'; + refreshEnteredText(); + } + break; + } + case SDLK_UP: //up arrow + { + if(previouslyEntered.size() == 0) + break; + + if(prevEntDisp == -1) + { + prevEntDisp = previouslyEntered.size() - 1; + enteredText = previouslyEntered[prevEntDisp] + "_"; + refreshEnteredText(); + } + else if( prevEntDisp > 0) + { + --prevEntDisp; + enteredText = previouslyEntered[prevEntDisp] + "_"; + refreshEnteredText(); + } + break; + } + case SDLK_DOWN: //down arrow + { + if(prevEntDisp != -1 && prevEntDisp+1 < previouslyEntered.size()) + { + ++prevEntDisp; + enteredText = previouslyEntered[prevEntDisp] + "_"; + refreshEnteredText(); + } + else if(prevEntDisp+1 == previouslyEntered.size()) //useful feature + { + prevEntDisp = -1; + enteredText = "_"; + refreshEnteredText(); + } + break; + } + default: + { + #ifdef VCMI_SDL1 + if(enteredText.size() > 0 && enteredText.size() < conf.go()->ac.inputLineLength) + { + if( key.keysym.unicode < 0x80 && key.keysym.unicode > 0 ) + { + enteredText[enteredText.size()-1] = (char)key.keysym.unicode; + enteredText += "_"; + refreshEnteredText(); + } + } + #endif // VCMI_SDL1 + break; + } + } +} + +#ifndef VCMI_SDL1 + +void CInGameConsole::textInputed(const SDL_TextInputEvent & event) +{ + if(!captureAllKeys || enteredText.size() == 0) + return; + enteredText.resize(enteredText.size()-1); + + enteredText += event.text; + enteredText += "_"; + + refreshEnteredText(); +} + +void CInGameConsole::textEdited(const SDL_TextEditingEvent & event) +{ + //do nothing here +} + +#endif // VCMI_SDL1 + +void CInGameConsole::startEnteringText() +{ + CSDL_Ext::startTextInput(&pos); + + enteredText = "_"; + if(GH.topInt() == adventureInt) + { + GH.statusbar->alignment = TOPLEFT; + GH.statusbar->setText(enteredText); + + //Prevent changes to the text from mouse interaction with the adventure map + GH.statusbar->lock(true); + } + else if(LOCPLINT->battleInt) + { + LOCPLINT->battleInt->console->ingcAlter = enteredText; + } +} + +void CInGameConsole::endEnteringText(bool printEnteredText) +{ + CSDL_Ext::stopTextInput(); + + prevEntDisp = -1; + if(printEnteredText) + { + std::string txt = enteredText.substr(0, enteredText.size()-1); + LOCPLINT->cb->sendMessage(txt); + previouslyEntered.push_back(txt); + //print(txt); + } + enteredText = ""; + if(GH.topInt() == adventureInt) + { + GH.statusbar->alignment = CENTER; + GH.statusbar->lock(false); + GH.statusbar->clear(); + } + else if(LOCPLINT->battleInt) + { + LOCPLINT->battleInt->console->ingcAlter = ""; + } +} + +void CInGameConsole::refreshEnteredText() +{ + if(GH.topInt() == adventureInt) + { + GH.statusbar->lock(false); + GH.statusbar->clear(); + GH.statusbar->setText(enteredText); + GH.statusbar->lock(true); + } + else if(LOCPLINT->battleInt) + { + LOCPLINT->battleInt->console->ingcAlter = enteredText; + } +} + +CInGameConsole::CInGameConsole() : prevEntDisp(-1), defaultTimeout(10000), maxDisplayedTexts(10) +{ + #ifdef VCMI_SDL1 + addUsedEvents(KEYBOARD); + #else + addUsedEvents(KEYBOARD | TEXTINPUT); + #endif +} diff --git a/client/AdventureMapClasses.h b/client/widgets/AdventureMapClasses.h similarity index 88% rename from client/AdventureMapClasses.h rename to client/widgets/AdventureMapClasses.h index 8a37758cf..84652db71 100644 --- a/client/AdventureMapClasses.h +++ b/client/widgets/AdventureMapClasses.h @@ -1,7 +1,7 @@ #pragma once -#include "gui/CIntObject.h" -#include "gui/CIntObjectClasses.h" +#include "ObjectLists.h" +#include "../../lib/FunctionList.h" class CArmedInstance; class CShowableAnim; @@ -9,6 +9,7 @@ class CGGarrison; class CGObjectInstance; class CGHeroInstance; class CGTownInstance; +class CButton; struct Component; struct InfoAboutArmy; struct InfoAboutHero; @@ -82,8 +83,8 @@ protected: public: - CAdventureMapButton * scrollUp; - CAdventureMapButton * scrollDown; + CButton * scrollUp; + CButton * scrollDown; /// functions that will be called when selection changes CFunctionList onSelect; @@ -313,3 +314,30 @@ public: /// for 3 seconds shows amount of town halls and players status void showGameStatus(); }; + +class CInGameConsole : public CIntObject +{ +private: + std::list< std::pair< std::string, int > > texts; //list + boost::mutex texts_mx; // protects texts + std::vector< std::string > previouslyEntered; //previously entered texts, for up/down arrows to work + int prevEntDisp; //displayed entry from previouslyEntered - if none it's -1 + int defaultTimeout; //timeout for new texts (in ms) + int maxDisplayedTexts; //hiw many texts can be displayed simultaneously +public: + std::string enteredText; + void show(SDL_Surface * to); + void print(const std::string &txt); + void keyPressed (const SDL_KeyboardEvent & key); //call-in + +#ifndef VCMI_SDL1 + void textInputed(const SDL_TextInputEvent & event) override; + void textEdited(const SDL_TextEditingEvent & event) override; +#endif // VCMI_SDL1 + + void startEnteringText(); + void endEnteringText(bool printEnteredText); + void refreshEnteredText(); + + CInGameConsole(); //c-tor +}; diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp new file mode 100644 index 000000000..f4cd0dcbe --- /dev/null +++ b/client/widgets/Buttons.cpp @@ -0,0 +1,742 @@ +#include "StdInc.h" +#include "Buttons.h" + +#include "Images.h" +#include "TextControls.h" + +#include "../CMusicHandler.h" +#include "../CGameInfo.h" +#include "../CPlayerInterface.h" +#include "../battle/CBattleInterface.h" +#include "../battle/CBattleInterfaceClasses.h" +#include "../gui/CAnimation.h" +#include "../gui/CGuiHandler.h" +#include "../windows/InfoWindows.h" +#include "../../lib/CConfigHandler.h" + +/* + * Buttons.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 + * + */ + +ClickableArea::ClickableArea(CIntObject * object, CFunctionList callback): + callback(callback), + area(nullptr) +{ + if (object) + pos = object->pos; + setArea(object); +} + +void ClickableArea::addCallback(std::function callback) +{ + this->callback += callback; +} + +void ClickableArea::setArea(CIntObject * object) +{ + delete area; + addChild(area); + pos.w = object->pos.w; + pos.h = object->pos.h; +} + +void ClickableArea::onClick() +{ + callback(); +} + +void ClickableArea::clickLeft(tribool down, bool previousState) +{ + if (down) + onClick(); +} + +void CButton::update() +{ + if (overlay) + { + if (state == PRESSED) + overlay->moveTo(overlay->pos.centerIn(pos).topLeft() + Point(1,1)); + else + overlay->moveTo(overlay->pos.centerIn(pos).topLeft()); + } + + int newPos = stateToIndex[int(state)]; + if (newPos < 0) + newPos = 0; + + if (state == HIGHLIGHTED && image->size() < 4) + newPos = image->size()-1; + image->setFrame(newPos); + + if (active) + redraw(); +} + +void CButton::addCallback(std::function callback) +{ + this->callback += callback; +} + +void CButton::addTextOverlay( const std::string &Text, EFonts font, SDL_Color color) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + addOverlay(new CLabel(pos.w/2, pos.h/2, font, CENTER, color, Text)); + update(); +} + +void CButton::addOverlay(CIntObject *newOverlay) +{ + delete overlay; + overlay = newOverlay; + addChild(newOverlay); + overlay->moveTo(overlay->pos.centerIn(pos).topLeft()); + update(); +} + +void CButton::addImage(std::string filename) +{ + imageNames.push_back(filename); +} + +void CButton::addHoverText(ButtonState state, std::string text) +{ + hoverTexts[state] = text; +} + +void CButton::setImageOrder(int state1, int state2, int state3, int state4) +{ + stateToIndex[0] = state1; + stateToIndex[1] = state2; + stateToIndex[2] = state3; + stateToIndex[3] = state4; + update(); +} + +void CButton::setState(ButtonState newState) +{ + if (state == newState) + return; + state = newState; + update(); +} + +CButton::ButtonState CButton::getState() +{ + return state; +} + +bool CButton::isBlocked() +{ + return state == BLOCKED; +} + +bool CButton::isHighlighted() +{ + return state == HIGHLIGHTED; +} + +void CButton::block(bool on) +{ + setState(on?BLOCKED:NORMAL); +} + +void CButton::onButtonClicked() +{ + // debug logging to figure out pressed button (and as result - player actions) in case of crash + logAnim->traceStream() << "Button clicked at " << pos.x << "x" << pos.y << ", " << callback.funcs.size() << " functions"; + CIntObject * parent = this->parent; + std::string prefix = "Parent is"; + while (parent) + { + logAnim->traceStream() << prefix << typeid(*parent).name() << " at " << parent->pos.x << "x" << parent->pos.y; + parent = parent->parent; + prefix = '\t' + prefix; + } + callback(); +} + +void CButton::clickLeft(tribool down, bool previousState) +{ + if(isBlocked()) + return; + + if (down) + { + if (!soundDisabled) + CCS->soundh->playSound(soundBase::button); + setState(PRESSED); + } + else if(hoverable && hovered) + setState(HIGHLIGHTED); + else + setState(NORMAL); + + if (actOnDown && down) + { + onButtonClicked(); + } + else if (!actOnDown && previousState && (down==false)) + { + onButtonClicked(); + } +} + +void CButton::clickRight(tribool down, bool previousState) +{ + if(down && helpBox.size()) //there is no point to show window with nothing inside... + CRClickPopup::createAndPush(helpBox); +} + +void CButton::hover (bool on) +{ + if(hoverable && !isBlocked()) + { + if(on) + setState(HIGHLIGHTED); + else + setState(NORMAL); + } + + /*if(pressedL && on) // WTF is this? When this is used? + setState(PRESSED);*/ + + std::string name = hoverTexts[getState()].empty() + ? hoverTexts[getState()] + : hoverTexts[0]; + + if(!name.empty() && !isBlocked()) //if there is no name, there is nohing to display also + { + if (LOCPLINT && LOCPLINT->battleInt) //for battle buttons + { + if(on && LOCPLINT->battleInt->console->alterTxt == "") + { + LOCPLINT->battleInt->console->alterTxt = name; + LOCPLINT->battleInt->console->whoSetAlter = 1; + } + else if (LOCPLINT->battleInt->console->alterTxt == name) + { + LOCPLINT->battleInt->console->alterTxt = ""; + LOCPLINT->battleInt->console->whoSetAlter = 0; + } + } + else if(GH.statusbar) //for other buttons + { + if (on) + GH.statusbar->setText(name); + else if ( GH.statusbar->getText()==(name) ) + GH.statusbar->clear(); + } + } +} + +CButton::CButton(Point position, const std::string &defName, const std::pair &help, CFunctionList Callback, int key, bool playerColoredButton): + CKeyShortcut(key), + callback(Callback) +{ + addUsedEvents(LCLICK | RCLICK | HOVER | KEYBOARD); + + stateToIndex[0] = 0; + stateToIndex[1] = 1; + stateToIndex[2] = 2; + stateToIndex[3] = 3; + + state=NORMAL; + image = nullptr; + overlay = nullptr; + + currentImage = -1; + hoverable = actOnDown = soundDisabled = false; + hoverTexts[0] = help.first; + helpBox=help.second; + + pos.x += position.x; + pos.y += position.y; + + if (!defName.empty()) + { + imageNames.push_back(defName); + setIndex(0, playerColoredButton); + } +} + +void CButton::setIndex(size_t index, bool playerColoredButton) +{ + if (index == currentImage || index>=imageNames.size()) + return; + currentImage = index; + setImage(new CAnimation(imageNames[index]), playerColoredButton); +} + +void CButton::setImage(CAnimation* anim, bool playerColoredButton, int animFlags) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + image = new CAnimImage(anim, getState(), 0, 0, 0, animFlags); + if (playerColoredButton) + image->playerColored(LOCPLINT->playerID); + pos = image->pos; +} + +void CButton::setPlayerColor(PlayerColor player) +{ + if (image) + image->playerColored(player); +} + +void CButton::showAll(SDL_Surface * to) +{ + CIntObject::showAll(to); + + #ifdef VCMI_SDL1 + if (borderColor && borderColor->unused == 0) + CSDL_Ext::drawBorder(to, pos.x-1, pos.y-1, pos.w+2, pos.h+2, int3(borderColor->r, borderColor->g, borderColor->b)); + #else + if (borderColor && borderColor->a == 0) + CSDL_Ext::drawBorder(to, pos.x-1, pos.y-1, pos.w+2, pos.h+2, int3(borderColor->r, borderColor->g, borderColor->b)); + #endif // 0 +} + +std::pair CButton::tooltip() +{ + return std::pair(); +} + +std::pair CButton::tooltip(const JsonNode & localizedTexts) +{ + return std::make_pair(localizedTexts["label"].String(), localizedTexts["help"].String()); +} + +std::pair CButton::tooltip(const std::string & hover, const std::string & help) +{ + return std::make_pair(hover, help); +} + +CToggleBase::CToggleBase(CFunctionList callback): + callback(callback), + selected(false), + allowDeselection(true) +{ +} + +CToggleBase::~CToggleBase() +{ +} + +void CToggleBase::doSelect(bool on) +{ + // for overrides +} + +void CToggleBase::setSelected(bool on) +{ + bool changed = (on != selected); + selected = on; + doSelect(on); + if (changed) + callback(on); +} + +bool CToggleBase::canActivate() +{ + if (selected && !allowDeselection) + return false; + return true; +} + +void CToggleBase::addCallback(std::function function) +{ + callback.add(function); +} + +CToggleButton::CToggleButton(Point position, const std::string &defName, const std::pair &help, + CFunctionList callback, int key, bool playerColoredButton): + CButton(position, defName, help, 0, key, playerColoredButton), + CToggleBase(callback) +{ + allowDeselection = true; +} + +void CToggleButton::doSelect(bool on) +{ + if (on) + { + setState(HIGHLIGHTED); + } + else + { + setState(NORMAL); + } +} + +void CToggleButton::clickLeft(tribool down, bool previousState) +{ + // force refresh + hover(false); + hover(true); + + if(isBlocked()) + return; + + if (down && canActivate()) + { + CCS->soundh->playSound(soundBase::button); + setState(PRESSED); + } + + if(previousState)//mouse up + { + if(down == false && getState() == PRESSED && canActivate()) + { + onButtonClicked(); + setSelected(!selected); + } + else + doSelect(selected); // restore + } +} + +void CToggleGroup::addCallback(std::function callback) +{ + onChange += callback; +} + +void CToggleGroup::addToggle(int identifier, CToggleBase* bt) +{ + if (auto intObj = dynamic_cast(bt)) // hack-ish workagound to avoid diamond problem with inheritance + { + if (intObj->parent) + intObj->parent->removeChild(intObj); + addChild(intObj); + } + + bt->addCallback([=] (bool on) { if (on) selectionChanged(identifier);}); + bt->allowDeselection = false; + + assert(buttons[identifier] == nullptr); + buttons[identifier] = bt; +} + +CToggleGroup::CToggleGroup(const CFunctionList &OnChange, bool musicLikeButtons) +: onChange(OnChange), musicLike(musicLikeButtons) +{} + +void CToggleGroup::setSelected(int id) +{ + selectionChanged(id); +} + +void CToggleGroup::selectionChanged(int to) +{ + if (to == selectedID) + return; + + int oldSelection = selectedID; + selectedID = to; + + if (buttons.count(oldSelection)) + buttons[oldSelection]->setSelected(false); + + if (buttons.count(to)) + buttons[to]->setSelected(true); + + onChange(to); + if (parent) + parent->redraw(); +} + +void CToggleGroup::show(SDL_Surface * to) +{ + if (musicLike) + { + if (auto intObj = dynamic_cast(buttons[selectedID])) // hack-ish workagound to avoid diamond problem with inheritance + intObj->show(to); + } + else + CIntObject::show(to); +} + +void CToggleGroup::showAll(SDL_Surface * to) +{ + if (musicLike) + { + if (auto intObj = dynamic_cast(buttons[selectedID])) // hack-ish workagound to avoid diamond problem with inheritance + intObj->showAll(to); + } + else + CIntObject::showAll(to); +} + +void CSlider::sliderClicked() +{ + if(!(active & MOVE)) + addUsedEvents(MOVE); +} + +void CSlider::mouseMoved (const SDL_MouseMotionEvent & sEvent) +{ + double v = 0; + if(horizontal) + { + if( std::abs(sEvent.y-(pos.y+pos.h/2)) > pos.h/2+40 || std::abs(sEvent.x-(pos.x+pos.w/2)) > pos.w/2 ) + return; + v = sEvent.x - pos.x - 24; + v *= positions; + v /= (pos.w - 48); + } + else + { + if(std::abs(sEvent.x-(pos.x+pos.w/2)) > pos.w/2+40 || std::abs(sEvent.y-(pos.y+pos.h/2)) > pos.h/2 ) + return; + v = sEvent.y - pos.y - 24; + v *= positions; + v /= (pos.h - 48); + } + v += 0.5; + if(v!=value) + { + moveTo(v); + } +} + +void CSlider::setScrollStep(int to) +{ + scrollStep = to; +} + +int CSlider::getAmount() +{ + return amount; +} + +int CSlider::getValue() +{ + return value; +} + +void CSlider::moveLeft() +{ + moveTo(value-1); +} + +void CSlider::moveRight() +{ + moveTo(value+1); +} + +void CSlider::moveBy(int amount) +{ + moveTo(value + amount); +} + +void CSlider::updateSliderPos() +{ + if(horizontal) + { + if(positions) + { + double part = static_cast(value) / positions; + part*=(pos.w-48); + int newPos = part + pos.x + 16 - slider->pos.x; + slider->moveBy(Point(newPos, 0)); + } + else + slider->moveTo(Point(pos.x+16, pos.y)); + } + else + { + if(positions) + { + double part = static_cast(value) / positions; + part*=(pos.h-48); + int newPos = part + pos.y + 16 - slider->pos.y; + slider->moveBy(Point(0, newPos)); + } + else + slider->moveTo(Point(pos.x, pos.y+16)); + } +} + +void CSlider::moveTo(int to) +{ + vstd::amax(to, 0); + vstd::amin(to, positions); + + //same, old position? + if(value == to) + return; + value = to; + + updateSliderPos(); + + moved(to); +} + +void CSlider::clickLeft(tribool down, bool previousState) +{ + if(down && !slider->isBlocked()) + { + double pw = 0; + double rw = 0; + if(horizontal) + { + pw = GH.current->motion.x-pos.x-25; + rw = pw / static_cast(pos.w - 48); + } + else + { + pw = GH.current->motion.y-pos.y-24; + rw = pw / (pos.h-48); + } + if(pw < -8 || pw > (horizontal ? pos.w : pos.h) - 40) + return; + // if (rw>1) return; + // if (rw<0) return; + slider->clickLeft(true, slider->pressedL); + moveTo(rw * positions + 0.5); + return; + } + if(active & MOVE) + removeUsedEvents(MOVE); +} + +CSlider::~CSlider() +{ + +} + +CSlider::CSlider(Point position, int totalw, std::function Moved, int Capacity, int Amount, int Value, bool Horizontal, CSlider::EStyle style): + capacity(Capacity), + horizontal(Horizontal), + amount(Amount), + value(Value), + scrollStep(1), + moved(Moved) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + setAmount(amount); + vstd::amax(value, 0); + vstd::amin(value, positions); + + addUsedEvents(LCLICK | KEYBOARD | WHEEL); + strongInterest = true; + + pos.x += position.x; + pos.y += position.y; + + if(style == BROWN) + { + std::string name = horizontal?"IGPCRDIV.DEF":"OVBUTN2.DEF"; + //NOTE: this images do not have "blocked" frames. They should be implemented somehow (e.g. palette transform or something...) + + left = new CButton(Point(), name, CButton::tooltip()); + right = new CButton(Point(), name, CButton::tooltip()); + slider = new CButton(Point(), name, CButton::tooltip()); + + left->setImageOrder(0, 1, 1, 1); + right->setImageOrder(2, 3, 3, 3); + slider->setImageOrder(4, 4, 4, 4); + } + else + { + left = new CButton(Point(), horizontal ? "SCNRBLF.DEF" : "SCNRBUP.DEF", CButton::tooltip()); + right = new CButton(Point(), horizontal ? "SCNRBRT.DEF" : "SCNRBDN.DEF", CButton::tooltip()); + slider = new CButton(Point(), "SCNRBSL.DEF", CButton::tooltip()); + } + slider->actOnDown = true; + slider->soundDisabled = true; + left->soundDisabled = true; + right->soundDisabled = true; + + if (horizontal) + right->moveBy(Point(totalw - right->pos.w, 0)); + else + right->moveBy(Point(0, totalw - right->pos.h)); + + left->addCallback(std::bind(&CSlider::moveLeft,this)); + right->addCallback(std::bind(&CSlider::moveRight,this)); + slider->addCallback(std::bind(&CSlider::sliderClicked,this)); + + if(horizontal) + { + pos.h = slider->pos.h; + pos.w = totalw; + } + else + { + pos.w = slider->pos.w; + pos.h = totalw; + } + + updateSliderPos(); +} + +void CSlider::block( bool on ) +{ + left->block(on); + right->block(on); + slider->block(on); +} + +void CSlider::setAmount( int to ) +{ + amount = to; + positions = to - capacity; + vstd::amax(positions, 0); +} + +void CSlider::showAll(SDL_Surface * to) +{ + CSDL_Ext::fillRectBlack(to, &pos); + CIntObject::showAll(to); +} + +void CSlider::wheelScrolled(bool down, bool in) +{ + moveTo(value + 3 * (down ? +scrollStep : -scrollStep)); +} + +void CSlider::keyPressed(const SDL_KeyboardEvent & key) +{ + if(key.state != SDL_PRESSED) return; + + int moveDest = 0; + switch(key.keysym.sym) + { + case SDLK_UP: + case SDLK_LEFT: + moveDest = value - scrollStep; + break; + case SDLK_DOWN: + case SDLK_RIGHT: + moveDest = value + scrollStep; + break; + case SDLK_PAGEUP: + moveDest = value - capacity + scrollStep; + break; + case SDLK_PAGEDOWN: + moveDest = value + capacity - scrollStep; + break; + case SDLK_HOME: + moveDest = 0; + break; + case SDLK_END: + moveDest = amount - capacity; + break; + default: + return; + } + + moveTo(moveDest); +} + +void CSlider::moveToMax() +{ + moveTo(amount); +} diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h new file mode 100644 index 000000000..7fcd4336f --- /dev/null +++ b/client/widgets/Buttons.h @@ -0,0 +1,261 @@ +#pragma once + +#include "../gui/CIntObject.h" +#include "../gui/SDL_Extensions.h" + +#include "../../lib/FunctionList.h" + +struct SDL_Surface; +struct Rect; +class CAnimImage; +class CLabel; +class CAnimation; +class CDefHandler; + +namespace config +{ + struct ButtonInfo; +} + +/* + * Buttons.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 + * + */ + +class ClickableArea : public CIntObject //TODO: derive from LRCLickableArea? Or somehow use its right-click/hover data? +{ + CFunctionList callback; + + CIntObject * area; + +protected: + void onClick(); + +public: + ClickableArea(CIntObject * object, CFunctionList callback); + + void addCallback(std::function callback); + void setArea(CIntObject * object); + + void clickLeft(tribool down, bool previousState) override; +}; + +/// Typical Heroes 3 button which can be inactive or active and can +/// hold further information if you right-click it +class CButton : public CKeyShortcut +{ + CFunctionList callback; + +public: + enum ButtonState + { + NORMAL=0, + PRESSED=1, + BLOCKED=2, + HIGHLIGHTED=3 + }; +private: + std::vector imageNames;//store list of images that can be used by this button + size_t currentImage; + + ButtonState state;//current state of button from enum + + std::array stateToIndex; // mapping of button state to index of frame in animation + std::array hoverTexts; //texts for statusbar, if empty - first entry will be used + std::string helpBox; //for right-click help + + CAnimImage * image; //image for this button + CIntObject * overlay;//object-overlay, can be null + +protected: + void onButtonClicked(); // calls callback + void update();//to refresh button after image or text change + + // internal method to change state. Public change can be done only via block() + void setState(ButtonState newState); + ButtonState getState(); + +public: + bool actOnDown,//runs when mouse is pressed down over it, not when up + hoverable,//if true, button will be highlighted when hovered (e.g. main menu) + soundDisabled; + + // if set, button will have 1-px border around it with this color + boost::optional borderColor; + + /// adds one more callback to on-click actions + void addCallback(std::function callback); + + /// adds overlay on top of button image. Only one overlay can be active at once + void addOverlay(CIntObject * newOverlay); + void addTextOverlay(const std::string &Text, EFonts font, SDL_Color color = Colors::WHITE); + + void addImage(std::string filename); + void addHoverText(ButtonState state, std::string text); + + void setImageOrder(int state1, int state2, int state3, int state4); + void block(bool on); + + /// State modifiers + bool isBlocked(); + bool isHighlighted(); + + /// Constructor + CButton(Point position, const std::string &defName, const std::pair &help, + CFunctionList Callback = 0, int key=0, bool playerColoredButton = false ); + + /// Appearance modifiers + void setIndex(size_t index, bool playerColoredButton=false); + void setImage(CAnimation* anim, bool playerColoredButton=false, int animFlags=0); + void setPlayerColor(PlayerColor player); + + /// CIntObject overrides + void clickRight(tribool down, bool previousState) override; + void clickLeft(tribool down, bool previousState) override; + void hover (bool on) override; + void showAll(SDL_Surface * to) override; + + /// generates tooltip that can be passed into constructor + static std::pair tooltip(); + static std::pair tooltip(const JsonNode & localizedTexts); + static std::pair tooltip(const std::string & hover, const std::string & help = ""); +}; + +class CToggleBase +{ + CFunctionList callback; +protected: + + bool selected; + + // internal method for overrides + virtual void doSelect(bool on); + + // returns true if toggle can change its state + bool canActivate(); + +public: + /// if set to false - button can not be deselected normally + bool allowDeselection; + + CToggleBase(CFunctionList callback); + virtual ~CToggleBase(); + + /// Changes selection to "on", and calls callback + void setSelected(bool on); + + void addCallback(std::function callback); +}; + +class ClickableToggle : public ClickableArea, public CToggleBase +{ +public: + ClickableToggle(CIntObject * object, CFunctionList selectFun, CFunctionList deselectFun); + void clickLeft(tribool down, bool previousState) override; +}; + +/// A button which can be selected/deselected, checkbox +class CToggleButton : public CButton, public CToggleBase +{ + void doSelect(bool on) override; +public: + CToggleButton(Point position, const std::string &defName, const std::pair &help, + CFunctionList Callback = 0, int key=0, bool playerColoredButton = false ); + void clickLeft(tribool down, bool previousState) override; + + // bring overrides into scope + //using CButton::addCallback; + using CToggleBase::addCallback; +}; + +class CToggleGroup : public CIntObject +{ + CFunctionList onChange; //called when changing selected button with new button's id + + int selectedID; + bool musicLike; //determines the behaviour of this group + void selectionChanged(int to); +public: + std::map buttons; + + CToggleGroup(const CFunctionList & OnChange, bool musicLikeButtons = false); + + void addCallback(std::function callback); + + /// add one toggle/button into group + void addToggle(int index, CToggleBase * button); + /// Changes selection to specific value. Will select toggle with this ID, if present + void setSelected(int id); + + void show(SDL_Surface * to); + void showAll(SDL_Surface * to); +}; + +/// A typical slider which can be orientated horizontally/vertically. +class CSlider : public CIntObject +{ + CButton *left, *right, *slider; //if vertical then left=up + int capacity;//how many elements can be active at same time (e.g. hero list = 5) + int positions; //number of highest position (0 if there is only one) + bool horizontal; + bool wheelScrolling; + bool keyScrolling; + + int amount; //total amount of elements (e.g. hero list = 0-8) + int value; //first active element + int scrollStep; // how many elements will be scrolled via one click, default = 1 + CFunctionList moved; + + void updateSliderPos(); + void sliderClicked(); + +public: + enum EStyle { + BROWN, + BLUE + }; + + void block(bool on); + + /// Controls how many items wil be scrolled via one click + void setScrollStep(int to); + + /// Value modifiers + void moveLeft(); + void moveRight(); + void moveTo(int value); + void moveBy(int amount); + void moveToMax(); + + /// Amount modifier + void setAmount(int to); + + /// Accessors + int getAmount(); + int getValue(); + + void addCallback(std::function callback); + + void keyPressed(const SDL_KeyboardEvent & key); + void wheelScrolled(bool down, bool in); + void clickLeft(tribool down, bool previousState); + void mouseMoved (const SDL_MouseMotionEvent & sEvent); + void showAll(SDL_Surface * to); + + /** + * @param position, coordinates of slider + * @param length, length of slider ribbon, including left/right buttons + * @param Moved, function that will be called whenever slider moves + * @param Capacity, maximal number of visible at once elements + * @param Amount, total amount of elements, including not visible + * @param Value, starting position + */ + CSlider(Point position, int length, std::function Moved, int Capacity, int Amount, + int Value=0, bool Horizontal=true, EStyle style = BROWN); + ~CSlider(); +}; diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp new file mode 100644 index 000000000..1af60d50f --- /dev/null +++ b/client/widgets/CArtifactHolder.cpp @@ -0,0 +1,1017 @@ +#include "StdInc.h" +#include "CArtifactHolder.h" + +#include "../gui/CGuiHandler.h" +#include "../gui/CCursorHandler.h" + +#include "Buttons.h" +#include "CComponent.h" + +#include "../windows/CHeroWindow.h" +#include "../windows/CSpellWindow.h" +#include "../windows/GUIClasses.h" +#include "../CPlayerInterface.h" +#include "../CGameInfo.h" + +#include "../../CCallback.h" + +#include "../../lib/CArtHandler.h" +#include "../../lib/CSpellHandler.h" +#include "../../lib/CGeneralTextHandler.h" + +#include "../../lib/mapObjects/CGHeroInstance.h" + +/* + * CArtifactHolder.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 + * + */ + +CArtPlace::CArtPlace(Point position, const CArtifactInstance * Art): + locked(false), picked(false), marked(false), ourArt(Art) +{ + pos += position; + pos.w = pos.h = 44; + createImage(); +} + +void CArtPlace::createImage() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + int graphic = 0; + if (ourArt) + graphic = ourArt->artType->iconIndex; + if (locked) + graphic = ArtifactID::ART_LOCK; + + image = new CAnimImage("artifact", graphic); + if (!ourArt) + image->disable(); + + selection = new CAnimImage("artifact", ArtifactID::ART_SELECTION); + selection->disable(); +} + +void CArtPlace::lockSlot(bool on) +{ + if (locked == on) + return; + + locked = on; + + if (on) + image->setFrame(ArtifactID::ART_LOCK); + else + image->setFrame(ourArt->artType->iconIndex); +} + +void CArtPlace::pickSlot(bool on) +{ + if (picked == on) + return; + + picked = on; + if (on) + image->disable(); + else + image->enable(); +} + +void CArtPlace::selectSlot(bool on) +{ + if (marked == on) + return; + + marked = on; + if (on) + selection->enable(); + else + selection->disable(); +} + +void CArtPlace::clickLeft(tribool down, bool previousState) +{ + //LRClickableAreaWTextComp::clickLeft(down); + bool inBackpack = slotID >= GameConstants::BACKPACK_START, + srcInBackpack = ourOwner->commonInfo->src.slotID >= GameConstants::BACKPACK_START, + srcInSameHero = ourOwner->commonInfo->src.AOH == ourOwner; + + if(ourOwner->highlightModeCallback && ourArt) + { + if(down) + { + if(ourArt->artType->id < 7) //War Machine or Spellbook + { + LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21]); //This item can't be traded. + } + else + { + ourOwner->unmarkSlots(false); + selectSlot(true); + ourOwner->highlightModeCallback(this); + } + } + return; + } + + // If clicked on spellbook, open it only if no artifact is held at the moment. + if(ourArt && !down && previousState && !ourOwner->commonInfo->src.AOH) + { + if(ourArt->artType->id == 0) + { + auto spellWindow = new CSpellWindow(genRect(595, 620, (screen->w - 620)/2, (screen->h - 595)/2), ourOwner->curHero, LOCPLINT, LOCPLINT->battleInt); + GH.pushInt(spellWindow); + } + } + + if (!down && previousState) + { + if(ourArt && ourArt->artType->id == 0) //spellbook + return; //this is handled separately + + if(!ourOwner->commonInfo->src.AOH) //nothing has been clicked + { + if(ourArt //to prevent selecting empty slots (bugfix to what GrayFace reported) + && ourOwner->curHero->tempOwner == LOCPLINT->playerID)//can't take art from another player + { + if(ourArt->artType->id == 3) //catapult cannot be highlighted + { + std::vector catapult(1, new CComponent(CComponent::artifact, 3, 0)); + LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[312], catapult); //The Catapult must be equipped. + return; + } + select(); + } + } + else if(ourArt == ourOwner->commonInfo->src.art) //restore previously picked artifact + { + deselect(); + } + else //perform artifact transition + { + if(inBackpack) // Backpack destination. + { + if(srcInBackpack && slotID == ourOwner->commonInfo->src.slotID + 1) //next slot (our is not visible, so visually same as "old" place) to the art -> make nothing, return artifact to slot + { + deselect(); + } + else + { + const CArtifact * const cur = ourOwner->commonInfo->src.art->artType; + + switch(cur->id) + { + case ArtifactID::CATAPULT: + //should not happen, catapult cannot be selected + assert(cur->id != ArtifactID::CATAPULT); + break; + case ArtifactID::BALLISTA: case ArtifactID::AMMO_CART: case ArtifactID::FIRST_AID_TENT: //war machines cannot go to backpack + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->Name())); + break; + default: + setMeAsDest(); + vstd::amin(ourOwner->commonInfo->dst.slotID, ArtifactPosition( + ourOwner->curHero->artifactsInBackpack.size() + GameConstants::BACKPACK_START)); + if(srcInBackpack && srcInSameHero) + { + if(!ourArt //cannot move from backpack to AFTER backpack -> combined with vstd::amin above it will guarantee that dest is at most the last artifact + || ourOwner->commonInfo->src.slotID < ourOwner->commonInfo->dst.slotID) //rearranging arts in backpack after taking src artifact, the dest id will be shifted + vstd::advance(ourOwner->commonInfo->dst.slotID, -1); + } + if(srcInSameHero && ourOwner->commonInfo->dst.slotID == ourOwner->commonInfo->src.slotID) //we came to src == dst + deselect(); + else + ourOwner->realizeCurrentTransaction(); + break; + } + } + } + //check if swap is possible + else if (fitsHere(ourOwner->commonInfo->src.art) && + (!ourArt || ourOwner->curHero->tempOwner == LOCPLINT->playerID)) + { + setMeAsDest(); +// +// // Special case when the dest artifact can't be fit into the src slot. +// //CGI->arth->unequipArtifact(ourOwner->curHero->artifWorn, slotID); +// const CArtifactsOfHero* srcAOH = ourOwner->commonInfo->src.AOH; +// ui16 srcSlotID = ourOwner->commonInfo->src.slotID; +// if (ourArt && srcSlotID < 19 && !ourArt->canBePutAt(ArtifactLocation(srcAOH->curHero, srcSlotID))) +// { +// // Put dest artifact into owner's backpack. +// ourOwner->commonInfo->src.AOH = ourOwner; +// ourOwner->commonInfo->src.slotID = ourOwner->curHero->artifacts.size() + 19; +// } + + ourOwner->realizeCurrentTransaction(); + } + } + } +} + +void CArtPlace::clickRight(tribool down, bool previousState) +{ + if(down && ourArt && !locked && text.size() && !picked) //if there is no description or it's a lock, do nothing ;] + { + if (slotID < GameConstants::BACKPACK_START) + { + if(ourOwner->allowedAssembling) + { + std::vector assemblyPossibilities = ourArt->assemblyPossibilities(ourOwner->curHero); + + // If the artifact can be assembled, display dialog. + for(const CArtifact *combination : assemblyPossibilities) + { + LOCPLINT->showArtifactAssemblyDialog( + ourArt->artType->id, + combination->id, + true, + std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), ourOwner->curHero, slotID, true, combination->id), + 0); + + if(assemblyPossibilities.size() > 2) + { + logGlobal->warnStream() << "More than one possibility of assembling... taking only first"; + break; + } + return; + } + + // Otherwise if the artifact can be diasassembled, display dialog. + if(ourArt->canBeDisassembled()) + { + LOCPLINT->showArtifactAssemblyDialog( + ourArt->artType->id, + 0, + false, + std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), ourOwner->curHero, slotID, false, ArtifactID()), + 0); + return; + } + } + } + + // Lastly just show the artifact description. + LRClickableAreaWTextComp::clickRight(down, previousState); + } +} + +/** + * Selects artifact slot so that the containing artifact looks like it's picked up. + */ +void CArtPlace::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++) + { + CArtPlace *ap = ourOwner->getArtPlace(i); + ap->pickSlot(ourArt->isPart(ap->ourArt)); + } + } + + //int backpackCorrection = -(slotID - Arts::BACKPACK_START < ourOwner->backpackPos); + + CCS->curh->dragAndDropCursor(new CAnimImage("artifact", ourArt->artType->iconIndex)); + ourOwner->commonInfo->src.setTo(this, false); + ourOwner->markPossibleSlots(ourArt); + + if(slotID >= GameConstants::BACKPACK_START) + ourOwner->scrollBackpack(0); //will update slots + + ourOwner->updateParentWindow(); + ourOwner->safeRedraw(); +} + +/** + * Deselects the artifact slot. FIXME: Not used. Maybe it should? + */ +void CArtPlace::deselect () +{ + pickSlot(false); + if(ourArt && ourArt->canBeDisassembled()) //combined art returned to its slot -> restore locks + { + for(int i = 0; i < GameConstants::BACKPACK_START; i++) + ourOwner->getArtPlace(i)->pickSlot(false); + } + + CCS->curh->dragAndDropCursor(nullptr); + ourOwner->unmarkSlots(); + ourOwner->commonInfo->src.clear(); + if(slotID >= GameConstants::BACKPACK_START) + ourOwner->scrollBackpack(0); //will update slots + + + ourOwner->updateParentWindow(); + ourOwner->safeRedraw(); +} + +void CArtPlace::showAll(SDL_Surface * to) +{ + if (ourArt && !picked && ourArt == ourOwner->curHero->getArt(slotID, false)) //last condition is needed for disassembling -> artifact may be gone, but we don't know yet TODO: real, nice solution + { + CIntObject::showAll(to); + } + + if(marked && active) + { + // Draw vertical bars. + for (int i = 0; i < pos.h; ++i) + { + CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x, pos.y + i, 240, 220, 120); + CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x + pos.w - 1, pos.y + i, 240, 220, 120); + } + + // Draw horizontal bars. + for (int i = 0; i < pos.w; ++i) + { + CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x + i, pos.y, 240, 220, 120); + CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x + i, pos.y + pos.h - 1, 240, 220, 120); + } + } +} + +bool CArtPlace::fitsHere(const CArtifactInstance * art) const +{ + // You can place 'no artifact' anywhere. + if(!art) + return true; + + // Anything can but War Machines can be placed in backpack. + if (slotID >= GameConstants::BACKPACK_START) + return !CGI->arth->isBigArtifact(art->artType->id); + + return art->canBePutAt(ArtifactLocation(ourOwner->curHero, slotID), true); +} + +void CArtPlace::setMeAsDest(bool backpackAsVoid /*= true*/) +{ + ourOwner->commonInfo->dst.setTo(this, backpackAsVoid); +} + +void CArtPlace::setArtifact(const CArtifactInstance *art) +{ + baseType = -1; //by default we don't store any component + ourArt = art; + if(!art) + { + image->disable(); + text = std::string(); + hoverText = CGI->generaltexth->allTexts[507]; + } + else + { + image->enable(); + image->setFrame(locked ? ArtifactID::ART_LOCK : art->artType->iconIndex); + + std::string artDesc = ourArt->artType->Description(); + if (vstd::contains (artDesc, '{')) + text = artDesc; + else + text = '{' + ourArt->artType->Name() + "}\n\n" + artDesc; //workaround for new artifacts with single name, turns it to H3-style + + if(art->artType->id == 1) //spell scroll + { + // we expect scroll description to be like this: This scroll contains the [spell name] spell which is added into your spell book for as long as you carry the scroll. + // so we want to replace text in [...] with a spell name + // however other language versions don't have name placeholder at all, so we have to be careful + int spellID = art->getGivenSpellID(); + size_t nameStart = text.find_first_of('['); + size_t nameEnd = text.find_first_of(']', nameStart); + if(spellID >= 0) + { + if(nameStart != std::string::npos && nameEnd != std::string::npos) + text = text.replace(nameStart, nameEnd - nameStart + 1, CGI->spellh->objects[spellID]->name); + + //add spell component info (used to provide a pic in r-click popup) + baseType = CComponent::spell; + type = spellID; + bonusValue = 0; + } + } + else + { + baseType = CComponent::artifact; + type = art->artType->id; + bonusValue = 0; + } + if (art->artType->constituents) //display info about components of combined artifact + { + //TODO + } + else if (art->artType->constituentOf.size()) //display info about set + { + std::string artList; + auto combinedArt = art->artType->constituentOf[0]; + text += "\n\n"; + text += "{" + combinedArt->Name() + "}"; + int wornArtifacts = 0; + for (auto a : *combinedArt->constituents) //TODO: can the artifact be a part of more than one set? + { + artList += "\n" + a->Name(); + if (ourOwner->curHero->hasArt(a->id, true)) + wornArtifacts++; + } + text += " (" + boost::str(boost::format("%d") % wornArtifacts) + " / " + + boost::str(boost::format("%d") % combinedArt->constituents->size()) + ")" + artList; + //TODO: fancy colors and fonts for this text + } + + if (locked) // Locks should appear as empty. + hoverText = CGI->generaltexth->allTexts[507]; + else + hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->Name()); + } +} + +void CArtifactsOfHero::SCommonPart::reset() +{ + src.clear(); + dst.clear(); + CCS->curh->dragAndDropCursor(nullptr); +} + +void CArtifactsOfHero::setHero(const CGHeroInstance * hero) +{ +// // An update is made, rather than initialization. +// if (curHero && curHero->id == hero->id) +// { +// if(curHero != hero) +// { +// //delete curHero; +// curHero = hero; //was: creating a copy +// } +// +// // Compensate backpack pos if an artifact was insertad before it. +// if (commonInfo->dst.slotID >= 19 && commonInfo->destAOH == this +// && commonInfo->dst.slotID - 19 < backpackPos) +// { +// backpackPos++; +// } +// +// if (updateState && commonInfo->srcAOH == this) +// { +// // A swap was made, make the replaced artifact the current selected. +// if (commonInfo->dst.slotID < 19 && commonInfo->destArtifact) +// { +// // // Temporarily remove artifact from hero. +// // if (commonInfo->srcSlotID < 19) +// // CGI->arth->unequipArtifact(curHero->artifWorn, commonInfo->srcSlotID); +// // else +// // curHero->artifacts.erase(curHero->artifacts.begin() + (commonInfo->srcSlotID - 19)); +// +// updateParentWindow(); //TODO: evil! but does the thing +// +// // Source <- Dest +// commonInfo->srcArtifact = commonInfo->destArtifact; +// +// // Reset destination parameters. +// commonInfo->dst.clear(); +// +// CCS->curh->dragAndDropCursor(graphics->artDefs->ourImages[commonInfo->srcArtifact->id].bitmap); +// markPossibleSlots(commonInfo->srcArtifact); +// } +// else if (commonInfo->destAOH != nullptr) +// { +// // Reset all parameters. +// commonInfo->reset(); +// unmarkSlots(); +// } +// } +// } +// else +// { +// commonInfo->reset(); +// } +// +// if(hero != curHero) +// { +// // delete curHero; +// // curHero = new CGHeroInstance(*hero); +// curHero = hero; //was: creating a copy +// } + + curHero = hero; + if (curHero->artifactsInBackpack.size() > 0) + backpackPos %= curHero->artifactsInBackpack.size(); + else + backpackPos = 0; + + // Fill the slots for worn artifacts and backpack. + for (int g = 0; g < artWorn.size() ; g++) + setSlotData(artWorn[g], ArtifactPosition(g)); + scrollBackpack(0); +} + +void CArtifactsOfHero::dispose() +{ + //vstd::clear_pointer(curHero); + //unmarkSlots(false); + CCS->curh->dragAndDropCursor(nullptr); +} + +void CArtifactsOfHero::scrollBackpack(int dir) +{ + int artsInBackpack = curHero->artifactsInBackpack.size(); + backpackPos += dir; + if(backpackPos < 0)// No guarantee of modulus behavior with negative operands -> we keep it positive + backpackPos += artsInBackpack; + + if(artsInBackpack) + backpackPos %= artsInBackpack; + + std::multiset toOmit = artifactsOnAltar; + if(commonInfo->src.art) //if we picked an art from backapck, its slot has to be omitted + toOmit.insert(commonInfo->src.art); + + int omitedSoFar = 0; + + //set new data + size_t s = 0; + for( ; s < artsInBackpack; ++s) + { + + if (s < artsInBackpack) + { + auto slotID = ArtifactPosition(GameConstants::BACKPACK_START + (s + backpackPos)%artsInBackpack); + const CArtifactInstance *art = curHero->getArt(slotID); + assert(art); + if(!vstd::contains(toOmit, art)) + { + if(s - omitedSoFar < backpack.size()) + setSlotData(backpack[s-omitedSoFar], slotID); + } + else + { + toOmit -= art; + omitedSoFar++; + continue; + } + } + } + for( ; s - omitedSoFar < backpack.size(); s++) + eraseSlotData(backpack[s-omitedSoFar], ArtifactPosition(GameConstants::BACKPACK_START + s)); + + //in artifact merchant selling artifacts we may have highlight on one of backpack artifacts -> market needs update, cause artifact under highlight changed + if(highlightModeCallback) + { + for(auto & elem : backpack) + { + if(elem->marked) + { + highlightModeCallback(elem); + break; + } + } + } + + //blocking scrolling if there is not enough artifacts to scroll + bool scrollingPossible = artsInBackpack - omitedSoFar > backpack.size(); + leftArtRoll->block(!scrollingPossible); + rightArtRoll->block(!scrollingPossible); + + safeRedraw(); + +} + +/** + * Marks possible slots where a given artifact can be placed, except backpack. + * + * @param art Artifact checked against. + */ +void CArtifactsOfHero::markPossibleSlots(const CArtifactInstance* art) +{ + for(CArtifactsOfHero *aoh : commonInfo->participants) + for(CArtPlace *place : aoh->artWorn) + place->selectSlot(art->canBePutAt(ArtifactLocation(aoh->curHero, place->slotID), true)); + + safeRedraw(); +} + +/** + * Unamarks all slots. + */ +void CArtifactsOfHero::unmarkSlots(bool withRedraw /*= true*/) +{ + if(commonInfo) + for(CArtifactsOfHero *aoh : commonInfo->participants) + aoh->unmarkLocalSlots(false); + else + unmarkLocalSlots(false);\ + + if(withRedraw) + safeRedraw(); +} + +void CArtifactsOfHero::unmarkLocalSlots(bool withRedraw /*= true*/) +{ + for(CArtPlace *place : artWorn) + place->selectSlot(false); + for(CArtPlace *place : backpack) + place->selectSlot(false); + + if(withRedraw) + safeRedraw(); +} + +/** + * Assigns an artifacts to an artifact place depending on it's new slot ID. + */ +void CArtifactsOfHero::setSlotData(CArtPlace* artPlace, ArtifactPosition slotID) +{ + if(!artPlace && slotID >= GameConstants::BACKPACK_START) //spurious call from artifactMoved in attempt to update hidden backpack slot + { + return; + } + + artPlace->pickSlot(false); + artPlace->slotID = slotID; + + if(const ArtSlotInfo *asi = curHero->getSlot(slotID)) + { + artPlace->setArtifact(asi->artifact); + artPlace->lockSlot(asi->locked); + } + else + artPlace->setArtifact(nullptr); +} + +/** + * Makes given artifact slot appear as empty with a certain slot ID. + */ +void CArtifactsOfHero::eraseSlotData (CArtPlace* artPlace, ArtifactPosition slotID) +{ + artPlace->pickSlot(false); + artPlace->slotID = slotID; + artPlace->setArtifact(nullptr); +} + +CArtifactsOfHero::CArtifactsOfHero(std::vector ArtWorn, std::vector Backpack, + CButton *leftScroll, CButton *rightScroll, bool createCommonPart): + + curHero(nullptr), + artWorn(ArtWorn), backpack(Backpack), + backpackPos(0), commonInfo(nullptr), updateState(false), + leftArtRoll(leftScroll), rightArtRoll(rightScroll), + allowedAssembling(true), highlightModeCallback(nullptr) +{ + if(createCommonPart) + { + commonInfo = new CArtifactsOfHero::SCommonPart; + commonInfo->participants.insert(this); + } + + // Init slots for worn artifacts. + for (size_t g = 0; g < artWorn.size() ; g++) + { + artWorn[g]->ourOwner = this; + eraseSlotData(artWorn[g], ArtifactPosition(g)); + } + + // Init slots for the backpack. + for(size_t s=0; sourOwner = this; + eraseSlotData(backpack[s], ArtifactPosition(GameConstants::BACKPACK_START + s)); + } + + leftArtRoll->addCallback(std::bind(&CArtifactsOfHero::scrollBackpack,this,-1)); + rightArtRoll->addCallback(std::bind(&CArtifactsOfHero::scrollBackpack,this,+1)); +} + +CArtifactsOfHero::CArtifactsOfHero(const Point& position, bool createCommonPart /*= false*/) + : curHero(nullptr), backpackPos(0), commonInfo(nullptr), updateState(false), allowedAssembling(true), highlightModeCallback(nullptr) +{ + using namespace boost::assign; + + if(createCommonPart) + { + commonInfo = new CArtifactsOfHero::SCommonPart; + commonInfo->participants.insert(this); + } + + OBJ_CONSTRUCTION_CAPTURING_ALL; + pos += position; + artWorn.resize(19); + + std::vector slotPos; + slotPos += Point(509,30), Point(567,240), Point(509,80), + Point(383,68), Point(564,183), Point(509,130), + Point(431,68), Point(610,183), Point(515,295), + Point(383,143), Point(399,194), Point(415,245), + Point(431,296), Point(564,30), Point(610,30), + Point(610,76), Point(610,122), Point(610,310), + Point(381,296); + + // Create slots for worn artifacts. + for (size_t g = 0; g < GameConstants::BACKPACK_START ; g++) + { + artWorn[g] = new CArtPlace(slotPos[g]); + artWorn[g]->ourOwner = this; + eraseSlotData(artWorn[g], ArtifactPosition(g)); + } + + // Create slots for the backpack. + for(size_t s=0; s<5; ++s) + { + auto add = new CArtPlace(Point(403 + 46 * s, 365)); + + add->ourOwner = this; + eraseSlotData(add, ArtifactPosition(GameConstants::BACKPACK_START + s)); + + backpack.push_back(add); + } + + leftArtRoll = new CButton(Point(379, 364), "hsbtns3.def", CButton::tooltip(), [&]{ scrollBackpack(-1);}, SDLK_LEFT); + rightArtRoll = new CButton(Point(632, 364), "hsbtns5.def", CButton::tooltip(), [&]{ scrollBackpack(+1);}, SDLK_RIGHT); +} + +CArtifactsOfHero::~CArtifactsOfHero() +{ + dispose(); +} + +void CArtifactsOfHero::updateParentWindow() +{ + if (CHeroWindow* chw = dynamic_cast(GH.topInt())) + { + if(updateState) + chw->curHero = curHero; + else + chw->update(curHero, true); + } + else if(CExchangeWindow* cew = dynamic_cast(GH.topInt())) + { + + //use our copy of hero to draw window + if(cew->heroInst[0]->id == curHero->id) + cew->heroInst[0] = curHero; + else + cew->heroInst[1] = curHero; + + if(!updateState) + { + cew->deactivate(); +// for(int g=0; gheroInst); ++g) +// { +// if(cew->heroInst[g] == curHero) +// { +// cew->artifs[g]->setHero(curHero); +// } +// } + + + cew->prepareBackground(); + cew->redraw(); + cew->activate(); + } + } +} + +void CArtifactsOfHero::safeRedraw() +{ + if (active) + { + if(parent) + parent->redraw(); + else + redraw(); + } +} + +void CArtifactsOfHero::realizeCurrentTransaction() +{ + assert(commonInfo->src.AOH); + assert(commonInfo->dst.AOH); + LOCPLINT->cb->swapArtifacts(ArtifactLocation(commonInfo->src.AOH->curHero, commonInfo->src.slotID), + ArtifactLocation(commonInfo->dst.AOH->curHero, commonInfo->dst.slotID)); +} + +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) + updateSlot(src.slot); + if(isCurHeroDst && dst.slot >= GameConstants::BACKPACK_START) + updateSlot(dst.slot); + if(isCurHeroSrc || isCurHeroDst) //we need to update all slots, artifact might be combined and affect more slots + updateWornSlots(false); + + if (!src.isHolder(curHero) && !isCurHeroDst) + return; + + if(commonInfo->src == src) //artifact was taken from us + { + 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) + || dst.getHolderArtSet()->bearerType() != ArtBearer::HERO); + commonInfo->reset(); + unmarkSlots(); + } + else if(commonInfo->dst == src) //the dest artifact was moved -> we are picking it + { + assert(dst.slot >= GameConstants::BACKPACK_START); + commonInfo->reset(); + + CArtPlace *ap = nullptr; + for(CArtifactsOfHero *aoh : commonInfo->participants) + { + if(dst.isHolder(aoh->curHero)) + { + commonInfo->src.AOH = aoh; + if((ap = aoh->getArtPlace(dst.slot))) + break; + } + } + + if(ap) + { + ap->select(); + } + else + { + commonInfo->src.art = dst.getArt(); + commonInfo->src.slotID = dst.slot; + assert(commonInfo->src.AOH); + CCS->curh->dragAndDropCursor(new CAnimImage("artifact", dst.getArt()->artType->iconIndex)); + 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. + } + + 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 +} + +void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al) +{ + if(al.isHolder(curHero)) + { + if(al.slot < GameConstants::BACKPACK_START) + updateWornSlots(0); + else + scrollBackpack(0); //update backpack slots + } +} + +CArtPlace * CArtifactsOfHero::getArtPlace(int slot) +{ + if(slot < GameConstants::BACKPACK_START) + { + return artWorn[slot]; + } + else + { + for(CArtPlace *ap : backpack) + if(ap->slotID == slot) + return ap; + } + + return nullptr; +} + +void CArtifactsOfHero::artifactAssembled(const ArtifactLocation &al) +{ + if(al.isHolder(curHero)) + updateWornSlots(); +} + +void CArtifactsOfHero::artifactDisassembled(const ArtifactLocation &al) +{ + if(al.isHolder(curHero)) + updateWornSlots(); +} + +void CArtifactsOfHero::updateWornSlots(bool redrawParent /*= true*/) +{ + for(int i = 0; i < artWorn.size(); i++) + updateSlot(ArtifactPosition(i)); + + + if(redrawParent) + updateParentWindow(); +} + +const CGHeroInstance * CArtifactsOfHero::getHero() const +{ + return curHero; +} + +void CArtifactsOfHero::updateSlot(ArtifactPosition slotID) +{ + setSlotData(getArtPlace(slotID), slotID); +} + +CArtifactHolder::CArtifactHolder() +{ +} + +void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation &artLoc) +{ + for(CArtifactsOfHero *aoh : artSets) + aoh->artifactRemoved(artLoc); +} + +void CWindowWithArtifacts::artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc) +{ + CArtifactsOfHero *destaoh = nullptr; + for(CArtifactsOfHero *aoh : artSets) + { + aoh->artifactMoved(artLoc, destLoc); + aoh->redraw(); + if(destLoc.isHolder(aoh->getHero())) + destaoh = aoh; + } + + //Make sure the status bar is updated so it does not display old text + if(destaoh != nullptr && destaoh->getArtPlace(destLoc.slot) != nullptr) + { + destaoh->getArtPlace(destLoc.slot)->hover(true); + } +} + +void CWindowWithArtifacts::artifactDisassembled(const ArtifactLocation &artLoc) +{ + for(CArtifactsOfHero *aoh : artSets) + aoh->artifactDisassembled(artLoc); +} + +void CWindowWithArtifacts::artifactAssembled(const ArtifactLocation &artLoc) +{ + for(CArtifactsOfHero *aoh : artSets) + aoh->artifactAssembled(artLoc); +} + +void CArtifactsOfHero::SCommonPart::Artpos::clear() +{ + slotID = ArtifactPosition::PRE_FIRST; + AOH = nullptr; + art = nullptr; +} + +CArtifactsOfHero::SCommonPart::Artpos::Artpos() +{ + clear(); +} + +void CArtifactsOfHero::SCommonPart::Artpos::setTo(const CArtPlace *place, bool dontTakeBackpack) +{ + slotID = place->slotID; + AOH = place->ourOwner; + + if(slotID >= 19 && dontTakeBackpack) + art = nullptr; + else + art = place->ourArt; +} + +bool CArtifactsOfHero::SCommonPart::Artpos::operator==(const ArtifactLocation &al) const +{ + if(!AOH) + return false; + bool ret = al.isHolder(AOH->curHero) && al.slot == slotID; + + //assert(al.getArt() == art); + return ret; +} + +bool CArtifactsOfHero::SCommonPart::Artpos::valid() +{ + assert(AOH && art); + return art == AOH->curHero->getArt(slotID); +} diff --git a/client/widgets/CArtifactHolder.h b/client/widgets/CArtifactHolder.h new file mode 100644 index 000000000..89ea5e7a6 --- /dev/null +++ b/client/widgets/CArtifactHolder.h @@ -0,0 +1,145 @@ +#pragma once + +//#include "CComponent.h" +#include "MiscWidgets.h" + +/* + * CArtifactHolder.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 + * + */ + +class CArtifactsOfHero; +class CAnimImage; +class CButton; + +struct ArtifactLocation; + +class CArtifactHolder +{ +public: + CArtifactHolder(); + + virtual void artifactRemoved(const ArtifactLocation &artLoc)=0; + virtual void artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc)=0; + virtual void artifactDisassembled(const ArtifactLocation &artLoc)=0; + virtual void artifactAssembled(const ArtifactLocation &artLoc)=0; +}; + +class CWindowWithArtifacts : public CArtifactHolder +{ +public: + std::vector artSets; + + void artifactRemoved(const ArtifactLocation &artLoc); + void artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc); + void artifactDisassembled(const ArtifactLocation &artLoc); + void artifactAssembled(const ArtifactLocation &artLoc); +}; + +/// Artifacts can be placed there. Gets shown at the hero window +class CArtPlace: public LRClickableAreaWTextComp +{ + CAnimImage *image; + CAnimImage *selection; + + void createImage(); + +public: + // consider these members as const - change them only with appropriate methods e.g. lockSlot() + bool locked; + bool picked; + bool marked; + + ArtifactPosition slotID; //Arts::EPOS enum + backpack starting from Arts::BACKPACK_START + + void lockSlot(bool on); + void pickSlot(bool on); + void selectSlot(bool on); + + CArtifactsOfHero * ourOwner; + const CArtifactInstance * ourArt; // should be changed only with setArtifact() + + CArtPlace(Point position, const CArtifactInstance * Art = nullptr); //c-tor + void clickLeft(tribool down, bool previousState); + void clickRight(tribool down, bool previousState); + void select (); + void deselect (); + void showAll(SDL_Surface * to); + bool fitsHere (const CArtifactInstance * art) const; //returns true if given artifact can be placed here + + void setMeAsDest(bool backpackAsVoid = true); + void setArtifact(const CArtifactInstance *art); +}; + +/// Contains artifacts of hero. Distincts which artifacts are worn or backpacked +class CArtifactsOfHero : public CIntObject +{ + const CGHeroInstance * curHero; + + std::vector artWorn; // 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 + std::vector backpack; //hero's visible backpack (only 5 elements!) + int backpackPos; //number of first art visible in backpack (in hero's vector) + +public: + struct SCommonPart + { + struct Artpos + { + ArtifactPosition slotID; + const CArtifactsOfHero *AOH; + const CArtifactInstance *art; + + Artpos(); + void clear(); + void setTo(const CArtPlace *place, bool dontTakeBackpack); + bool valid(); + bool operator==(const ArtifactLocation &al) const; + } src, dst; + + std::set participants; // Needed to mark slots. + + void reset(); + } * commonInfo; //when we have more than one CArtifactsOfHero in one window with exchange possibility, we use this (eg. in exchange window); to be provided externally + + bool updateState; // Whether the commonInfo should be updated on setHero or not. + + CButton * leftArtRoll, * rightArtRoll; + bool allowedAssembling; + std::multiset artifactsOnAltar; //artifacts id that are technically present in backpack but in GUI are moved to the altar - they'll be omitted in backpack slots + std::function highlightModeCallback; //if set, clicking on art place doesn't pick artifact but highlights the slot and calls this function + + void realizeCurrentTransaction(); //calls callback with parameters stored in commonInfo + void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst); + void artifactRemoved(const ArtifactLocation &al); + void artifactAssembled(const ArtifactLocation &al); + void artifactDisassembled(const ArtifactLocation &al); + CArtPlace *getArtPlace(int slot); + + void setHero(const CGHeroInstance * hero); + const CGHeroInstance *getHero() const; + void dispose(); //free resources not needed after closing windows and reset state + void scrollBackpack(int dir); //dir==-1 => to left; dir==1 => to right + + void safeRedraw(); + void markPossibleSlots(const CArtifactInstance* art); + void unmarkSlots(bool withRedraw = true); //unmarks slots in all visible AOHs + void unmarkLocalSlots(bool withRedraw = true); //unmarks slots in that particular AOH + void setSlotData (CArtPlace* artPlace, ArtifactPosition slotID); + void updateWornSlots (bool redrawParent = true); + + void updateSlot(ArtifactPosition i); + void eraseSlotData (CArtPlace* artPlace, ArtifactPosition slotID); + + CArtifactsOfHero(const Point& position, bool createCommonPart = false); + //Alternative constructor, used if custom artifacts positioning required (Kingdom interface) + CArtifactsOfHero(std::vector ArtWorn, std::vector Backpack, + CButton *leftScroll, CButton *rightScroll, bool createCommonPart = false); + ~CArtifactsOfHero(); //d-tor + void updateParentWindow(); + friend class CArtPlace; +}; diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp new file mode 100644 index 000000000..24febce0f --- /dev/null +++ b/client/widgets/CComponent.cpp @@ -0,0 +1,453 @@ +#include "StdInc.h" +#include "CComponent.h" + +#include "../gui/CGuiHandler.h" +#include "../gui/CCursorHandler.h" + +#include "../CMessage.h" +#include "../CGameInfo.h" +#include "../widgets/Images.h" +#include "../windows/CAdvmapInterface.h" + +#include "../../lib/CArtHandler.h" +#include "../../lib/CTownHandler.h" +#include "../../lib/CCreatureHandler.h" +#include "../../lib/CSpellHandler.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/NetPacksBase.h" + +/* + * CComponent.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 + * + */ + +CComponent::CComponent(Etype Type, int Subtype, int Val, ESize imageSize): + image(nullptr), + perDay(false) +{ + addUsedEvents(RCLICK); + init(Type, Subtype, Val, imageSize); +} + +CComponent::CComponent(const Component &c): + image(nullptr), + perDay(false) +{ + addUsedEvents(RCLICK); + + if(c.id == Component::RESOURCE && c.when==-1) + perDay = true; + + init((Etype)c.id,c.subtype,c.val, large); +} + +void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + compType = Type; + subtype = Subtype; + val = Val; + size = imageSize; + + assert(compType < typeInvalid); + assert(size < sizeInvalid); + + setSurface(getFileName()[size], getIndex()); + + pos.w = image->pos.w; + pos.h = image->pos.h; + + EFonts font = FONT_SMALL; + if (imageSize < small) + font = FONT_TINY; //other sizes? + + pos.h += 4; //distance between text and image + + std::vector textLines = CMessage::breakText(getSubtitle(), std::max(80, pos.w), font); + for(auto & line : textLines) + { + int height = graphics->fonts[font]->getLineHeight(); + auto label = new CLabel(pos.w/2, pos.h + height/2, font, CENTER, Colors::WHITE, line); + + pos.h += height; + if (label->pos.w > pos.w) + { + pos.x -= (label->pos.w - pos.w)/2; + pos.w = label->pos.w; + } + } +} + +const std::vector CComponent::getFileName() +{ + static const std::string primSkillsArr [] = {"PSKIL32", "PSKIL32", "PSKIL42", "PSKILL"}; + static const std::string secSkillsArr [] = {"SECSK32", "SECSK32", "SECSKILL", "SECSK82"}; + static const std::string resourceArr [] = {"SMALRES", "RESOURCE", "RESOUR82", "RESOUR82"}; + static const std::string creatureArr [] = {"CPRSMALL", "CPRSMALL", "TWCRPORT", "TWCRPORT"}; + static const std::string artifactArr[] = {"Artifact", "Artifact", "Artifact", "Artifact"}; + static const std::string spellsArr [] = {"SpellInt", "SpellInt", "SPELLSCR", "SPELLSCR"}; + static const std::string moraleArr [] = {"IMRL22", "IMRL30", "IMRL42", "imrl82"}; + static const std::string luckArr [] = {"ILCK22", "ILCK30", "ILCK42", "ilck82"}; + static const std::string heroArr [] = {"PortraitsSmall", "PortraitsSmall", "PortraitsLarge", "PortraitsLarge"}; + static const std::string flagArr [] = {"CREST58", "CREST58", "CREST58", "CREST58"}; + + auto gen = [](const std::string * arr) + { + return std::vector(arr, arr + 4); + }; + + switch(compType) + { + case primskill: return gen(primSkillsArr); + case secskill: return gen(secSkillsArr); + case resource: return gen(resourceArr); + case creature: return gen(creatureArr); + case artifact: return gen(artifactArr); + case experience: return gen(primSkillsArr); + case spell: return gen(spellsArr); + case morale: return gen(moraleArr); + case luck: return gen(luckArr); + case building: return std::vector(4, CGI->townh->factions[subtype]->town->clientInfo.buildingsIcons); + case hero: return gen(heroArr); + case flag: return gen(flagArr); + } + assert(0); + return std::vector(); +} + +size_t CComponent::getIndex() +{ + switch(compType) + { + case primskill: return subtype; + case secskill: return subtype*3 + 3 + val - 1; + case resource: return subtype; + case creature: return CGI->creh->creatures[subtype]->iconIndex; + case artifact: return CGI->arth->artifacts[subtype]->iconIndex; + case experience: return 4; + case spell: return subtype; + case morale: return val+3; + case luck: return val+3; + case building: return val; + case hero: return subtype; + case flag: return subtype; + } + assert(0); + return 0; +} + +std::string CComponent::getDescription() +{ + switch (compType) + { + case primskill: return (subtype < 4)? CGI->generaltexth->arraytxt[2+subtype] //Primary skill + : CGI->generaltexth->allTexts[149]; //mana + case secskill: return CGI->generaltexth->skillInfoTexts[subtype][val-1]; + case resource: return CGI->generaltexth->allTexts[242]; + case creature: return ""; + case artifact: return CGI->arth->artifacts[subtype]->Description(); + case experience: return CGI->generaltexth->allTexts[241]; + case spell: return CGI->spellh->objects[subtype]->getLevelInfo(val).description; + case morale: return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)]; + case luck: return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)]; + case building: return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Description(); + case hero: return ""; + case flag: return ""; + } + assert(0); + return ""; +} + +std::string CComponent::getSubtitle() +{ + if (!perDay) + return getSubtitleInternal(); + + std::string ret = CGI->generaltexth->allTexts[3]; + boost::replace_first(ret, "%d", getSubtitleInternal()); + return ret; +} + +std::string CComponent::getSubtitleInternal() +{ + //FIXME: some of these are horrible (e.g creature) + switch(compType) + { + case primskill: return boost::str(boost::format("%+d %s") % val % (subtype < 4 ? CGI->generaltexth->primarySkillNames[subtype] : CGI->generaltexth->allTexts[387])); + case secskill: return CGI->generaltexth->levels[val-1] + "\n" + CGI->generaltexth->skillName[subtype]; + case resource: return boost::lexical_cast(val); + case creature: return (val? boost::lexical_cast(val) + " " : "") + CGI->creh->creatures[subtype]->*(val != 1 ? &CCreature::namePl : &CCreature::nameSing); + case artifact: return CGI->arth->artifacts[subtype]->Name(); + case experience: + { + if (subtype == 1) //+1 level - tree of knowledge + { + std::string level = CGI->generaltexth->allTexts[442]; + boost::replace_first(level, "1", boost::lexical_cast(val)); + return level; + } + else + return boost::lexical_cast(val); //amount of experience OR level required for seer hut; + } + case spell: return CGI->spellh->objects[subtype]->name; + case morale: return ""; + case luck: return ""; + case building: return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Name(); + case hero: return ""; + case flag: return CGI->generaltexth->capColors[subtype]; + } + assert(0); + return ""; +} + +void CComponent::setSurface(std::string defName, int imgPos) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + vstd::clear_pointer(image); + image = new CAnimImage(defName, imgPos); +} + +void CComponent::clickRight(tribool down, bool previousState) +{ + if(!getDescription().empty()) + adventureInt->handleRightClick(getDescription(), down); +} + +void CSelectableComponent::clickLeft(tribool down, bool previousState) +{ + if (down) + { + if(onSelect) + onSelect(); + } +} + +void CSelectableComponent::init() +{ + selected = false; +} + +CSelectableComponent::CSelectableComponent(const Component &c, std::function OnSelect): + CComponent(c),onSelect(OnSelect) +{ + type |= REDRAW_PARENT; + addUsedEvents(LCLICK | KEYBOARD); + init(); +} + +CSelectableComponent::CSelectableComponent(Etype Type, int Sub, int Val, ESize imageSize, std::function OnSelect): + CComponent(Type,Sub,Val, imageSize),onSelect(OnSelect) +{ + type |= REDRAW_PARENT; + addUsedEvents(LCLICK | KEYBOARD); + init(); +} + +void CSelectableComponent::select(bool on) +{ + if(on != selected) + { + selected = on; + redraw(); + } +} + +void CSelectableComponent::showAll(SDL_Surface * to) +{ + CComponent::showAll(to); + if(selected) + { + CSDL_Ext::drawBorder(to, Rect::around(image->pos), int3(239,215,123)); + } +} + +void CComponentBox::selectionChanged(CSelectableComponent * newSelection) +{ + if (newSelection == selected) + return; + + if (selected) + selected->select(false); + + selected = newSelection; + if (onSelect) + onSelect(selectedIndex()); + + if (selected) + selected->select(true); +} + +int CComponentBox::selectedIndex() +{ + if (selected) + return std::find(components.begin(), components.end(), selected) - components.begin(); + return -1; +} + +Point CComponentBox::getOrTextPos(CComponent *left, CComponent *right) +{ + int leftSubtitle = ( left->pos.w - left->image->pos.w) / 2; + int rightSubtitle = (right->pos.w - right->image->pos.w) / 2; + int fullDistance = getDistance(left, right) + leftSubtitle + rightSubtitle; + + return Point(fullDistance/2 - leftSubtitle, (left->image->pos.h + right->image->pos.h) / 4); +} + +int CComponentBox::getDistance(CComponent *left, CComponent *right) +{ + static const int betweenImagesMin = 20; + static const int betweenSubtitlesMin = 10; + + int leftSubtitle = ( left->pos.w - left->image->pos.w) / 2; + int rightSubtitle = (right->pos.w - right->image->pos.w) / 2; + int subtitlesOffset = leftSubtitle + rightSubtitle; + + return std::max(betweenSubtitlesMin, betweenImagesMin - subtitlesOffset); +} + +void CComponentBox::placeComponents(bool selectable) +{ + static const int betweenRows = 22; + + OBJ_CONSTRUCTION_CAPTURING_ALL; + if (components.empty()) + return; + + //prepare components + for(auto & comp : components) + { + addChild(comp); + comp->moveTo(Point(pos.x, pos.y)); + } + + struct RowData + { + size_t comps; + int width; + int height; + RowData (size_t Comps, int Width, int Height): + comps(Comps), width (Width), height (Height){}; + }; + std::vector rows; + rows.push_back (RowData (0,0,0)); + + //split components in rows + CComponent * prevComp = nullptr; + + for(CComponent * comp : components) + { + //make sure that components are smaller than our width + //assert(pos.w == 0 || pos.w < comp->pos.w); + + const int distance = prevComp ? getDistance(prevComp, comp) : 0; + + //start next row + if ((pos.w != 0 && rows.back().width + comp->pos.w + distance > pos.w) // row is full + || rows.back().comps >= 4) // no more than 4 comps per row + { + prevComp = nullptr; + rows.push_back (RowData (0,0,0)); + } + + if (prevComp) + rows.back().width += distance; + + rows.back().comps++; + rows.back().width += comp->pos.w; + + vstd::amax(rows.back().height, comp->pos.h); + prevComp = comp; + } + + if (pos.w == 0) + { + for(auto & row : rows) + vstd::amax(pos.w, row.width); + } + + int height = (rows.size() - 1) * betweenRows; + for(auto & row : rows) + height += row.height; + + //assert(pos.h == 0 || pos.h < height); + if (pos.h == 0) + pos.h = height; + + auto iter = components.begin(); + int currentY = (pos.h - height) / 2; + + //move components to their positions + for (auto & rows_row : rows) + { + // amount of free space we may add on each side of every component + int freeSpace = (pos.w - rows_row.width) / (rows_row.comps * 2); + prevComp = nullptr; + + int currentX = 0; + for (size_t col = 0; col < rows_row.comps; col++) + { + currentX += freeSpace; + if (prevComp) + { + if (selectable) + { + Point orPos = Point(currentX - freeSpace, currentY) + getOrTextPos(prevComp, *iter); + + new CLabel(orPos.x, orPos.y, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[4]); + } + currentX += getDistance(prevComp, *iter); + } + + (*iter)->moveBy(Point(currentX, currentY)); + currentX += (*iter)->pos.w; + currentX += freeSpace; + + prevComp = *(iter++); + } + currentY += rows_row.height + betweenRows; + } +} + +CComponentBox::CComponentBox(CComponent * _components, Rect position): + components(1, _components), + selected(nullptr) +{ + type |= REDRAW_PARENT; + pos = position + pos; + placeComponents(false); +} + +CComponentBox::CComponentBox(std::vector _components, Rect position): + components(_components), + selected(nullptr) +{ + type |= REDRAW_PARENT; + pos = position + pos; + placeComponents(false); +} + +CComponentBox::CComponentBox(std::vector _components, Rect position, std::function _onSelect): + components(_components.begin(), _components.end()), + selected(nullptr), + onSelect(_onSelect) +{ + type |= REDRAW_PARENT; + pos = position + pos; + placeComponents(true); + + assert(!components.empty()); + + int key = SDLK_1; + for(auto & comp : _components) + { + comp->onSelect = std::bind(&CComponentBox::selectionChanged, this, comp); + comp->assignedKeys.insert(key++); + } + selectionChanged(_components.front()); +} diff --git a/client/widgets/CComponent.h b/client/widgets/CComponent.h new file mode 100644 index 000000000..65fa11ebc --- /dev/null +++ b/client/widgets/CComponent.h @@ -0,0 +1,112 @@ +#pragma once + +#include "../gui/CIntObject.h" + +/* + * CComponent.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 + * + */ + +struct Component; +class CAnimImage; + +/// common popup window component +class CComponent : public virtual CIntObject +{ +public: + enum Etype + { + primskill, secskill, resource, creature, artifact, experience, spell, morale, luck, building, hero, flag, typeInvalid + }; + + //NOTE: not all types have exact these sizes or have less than 4 of them. In such cases closest one will be used + enum ESize + { + tiny, // ~22-24px + small, // ~30px + medium,// ~42px + large, // ~82px + sizeInvalid + }; + +private: + size_t getIndex(); + const std::vector getFileName(); + void setSurface(std::string defName, int imgPos); + std::string getSubtitleInternal(); + + void init(Etype Type, int Subtype, int Val, ESize imageSize); + +public: + CAnimImage *image; //our image + + Etype compType; //component type + ESize size; //component size. + int subtype; //type-dependant subtype. See getSomething methods for details + int val; // value \ strength \ amount of component. See getSomething methods for details + bool perDay; // add "per day" text to subtitle + + std::string getDescription(); + std::string getSubtitle(); + + CComponent(Etype Type, int Subtype, int Val = 0, ESize imageSize=large);//c-tor + CComponent(const Component &c); //c-tor + + void clickRight(tribool down, bool previousState); //call-in +}; + +/// component that can be selected or deselected +class CSelectableComponent : public CComponent, public CKeyShortcut +{ + void init(); +public: + bool selected; //if true, this component is selected + std::function onSelect; //function called on selection change + + void showAll(SDL_Surface * to); + void select(bool on); + + void clickLeft(tribool down, bool previousState); //call-in + CSelectableComponent(Etype Type, int Sub, int Val, ESize imageSize=large, std::function OnSelect = nullptr); //c-tor + CSelectableComponent(const Component &c, std::function OnSelect = nullptr); //c-tor +}; + +/// box with multiple components (up to 8?) +/// will take ownership on components and delete them afterwards +class CComponentBox : public CIntObject +{ + std::vector components; + + CSelectableComponent * selected; + std::function onSelect; + + void selectionChanged(CSelectableComponent * newSelection); + + //get position of "or" text between these comps + //it will place "or" equidistant to both images + Point getOrTextPos(CComponent *left, CComponent * right); + + //get distance between these copmonents + int getDistance(CComponent *left, CComponent * right); + void placeComponents(bool selectable); + +public: + /// return index of selected item + int selectedIndex(); + + /// constructor for quite common 1-components popups + /// if position width or height are 0 then it will be determined automatically + CComponentBox(CComponent * components, Rect position); + /// constructor for non-selectable components + CComponentBox(std::vector components, Rect position); + + /// constructor for selectable components + /// will also create "or" labels between components + /// onSelect - optional function that will be called every time on selection change + CComponentBox(std::vector components, Rect position, std::function onSelect = nullptr); +}; diff --git a/client/widgets/CGarrisonInt.cpp b/client/widgets/CGarrisonInt.cpp new file mode 100644 index 000000000..51ef8af9a --- /dev/null +++ b/client/widgets/CGarrisonInt.cpp @@ -0,0 +1,512 @@ +#include "StdInc.h" +#include "CGarrisonInt.h" + +#include "../gui/CGuiHandler.h" + +#include "../CGameInfo.h" +#include "../CPlayerInterface.h" +#include "../widgets/Buttons.h" +#include "../widgets/TextControls.h" +#include "../windows/CCreatureWindow.h" +#include "../windows/GUIClasses.h" + +#include "../../CCallback.h" + +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CCreatureHandler.h" +#include "../../lib/mapObjects/CGHeroInstance.h" + +#include "../../lib/CGameState.h" + +/* + * CGarrisonInt.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 + * + */ + +void CGarrisonSlot::setHighlight(bool on) +{ + if (on) + selectionImage->enable(); //show + else + selectionImage->disable(); //hide +} + +void CGarrisonSlot::hover (bool on) +{ + ////Hoverable::hover(on); + if(on) + { + std::string temp; + if(creature) + { + if(owner->getSelection()) + { + if(owner->getSelection() == this) + { + temp = CGI->generaltexth->tcommands[4]; //View %s + boost::algorithm::replace_first(temp,"%s",creature->nameSing); + } + else if (owner->getSelection()->creature == creature) + { + temp = CGI->generaltexth->tcommands[2]; //Combine %s armies + boost::algorithm::replace_first(temp,"%s",creature->nameSing); + } + else if (owner->getSelection()->creature) + { + temp = CGI->generaltexth->tcommands[7]; //Exchange %s with %s + boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->nameSing); + boost::algorithm::replace_first(temp,"%s",creature->nameSing); + } + else + { + logGlobal->warnStream() << "Warning - shouldn't be - highlighted void slot "<getSelection(); + logGlobal->warnStream() << "Highlighted set to nullptr"; + owner->selectSlot(nullptr); + } + } + else + { + if(upg) + { + temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting) + } + else if(owner->armedObjs[0] && owner->armedObjs[0]->ID == Obj::TOWN) + { + temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison) + } + else + { + temp = CGI->generaltexth->allTexts[481]; //Select %s + } + boost::algorithm::replace_first(temp,"%s",creature->nameSing); + }; + } + else + { + if(owner->getSelection()) + { + const CArmedInstance *highl = owner->getSelection()->getObj(); + if( highl->needsLastStack() //we are moving stack from hero's + && highl->stacksCount() == 1 //it's only stack + && owner->getSelection()->upg != upg //we're moving it to the other garrison + ) + { + temp = CGI->generaltexth->tcommands[5]; //Cannot move last army to garrison + } + else + { + temp = CGI->generaltexth->tcommands[6]; //Move %s + boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->nameSing); + } + } + else + { + temp = CGI->generaltexth->tcommands[11]; //Empty + } + } + GH.statusbar->setText(temp); + } + else + { + GH.statusbar->clear(); + } +} + +const CArmedInstance * CGarrisonSlot::getObj() const +{ + return (!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]); +} + +bool CGarrisonSlot::our() const +{ + return upg?(owner->owned[1]):(owner->owned[0]); +} + +void CGarrisonSlot::clickRight(tribool down, bool previousState) +{ + if(down && creature) + { + GH.pushInt(new CStackWindow(myStack, true)); + } +} +void CGarrisonSlot::clickLeft(tribool down, bool previousState) +{ + if(down) + { + bool refr = false; + if(owner->getSelection()) + { + if(owner->getSelection() == this) //view info + { + UpgradeInfo pom; + LOCPLINT->cb->getUpgradeInfo(getObj(), ID, pom); + + bool canUpgrade = getObj()->tempOwner == LOCPLINT->playerID && pom.oldID>=0; //upgrade is possible + bool canDismiss = getObj()->tempOwner == LOCPLINT->playerID && (getObj()->stacksCount()>1 || !getObj()->needsLastStack()); + std::function upgr = nullptr; + std::function dism = nullptr; + if(canUpgrade) upgr = [=] (CreatureID newID) { LOCPLINT->cb->upgradeCreature(getObj(), ID, newID); }; + if(canDismiss) dism = [=] { LOCPLINT->cb->dismissCreature(getObj(), ID); }; + + owner->selectSlot(nullptr); + owner->setSplittingMode(false); + + for(auto & elem : owner->splitButtons) + elem->block(true); + + redraw(); + refr = true; + GH.pushInt(new CStackWindow(myStack, dism, pom, upgr)); + } + else + { + // Only allow certain moves if troops aren't removable or not ours. + if ( ( owner->getSelection()->our()//our creature is selected + || owner->getSelection()->creature == creature )//or we are rebalancing army + && ( owner->removableUnits + || (upg == 0 && ( owner->getSelection()->upg == 1 && !creature ) ) + || (upg == 1 && owner->getSelection()->upg == 1 ) ) ) + { + //we want to split + if((owner->getSplittingMode() || LOCPLINT->shiftPressed()) + && (!creature + || (creature == owner->getSelection()->creature))) + { + owner->p2 = ID; //store the second stack pos + owner->pb = upg;//store the second stack owner (up or down army) + owner->setSplittingMode(false); + + int minLeft=0, minRight=0; + + if(upg != owner->getSelection()->upg) //not splitting within same army + { + if(owner->getSelection()->getObj()->stacksCount() == 1 //we're splitting away the last stack + && owner->getSelection()->getObj()->needsLastStack() ) + { + minLeft = 1; + } + if(getObj()->stacksCount() == 1 //destination army can't be emptied, unless we're rebalancing two stacks of same creature + && owner->getSelection()->creature == creature + && getObj()->needsLastStack() ) + { + minRight = 1; + } + } + + int countLeft = owner->getSelection()->myStack ? owner->getSelection()->myStack->count : 0; + int countRight = myStack ? myStack->count : 0; + + GH.pushInt(new CSplitWindow(owner->getSelection()->creature, std::bind(&CGarrisonInt::splitStacks, owner, _1, _2), + minLeft, minRight, countLeft, countRight)); + refr = true; + } + else if(creature != owner->getSelection()->creature) //swap + { + LOCPLINT->cb->swapCreatures( + (!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]), + (!owner->getSelection()->upg)?(owner->armedObjs[0]):(owner->armedObjs[1]), + ID,owner->getSelection()->ID); + } + else //merge + { + LOCPLINT->cb->mergeStacks( + (!owner->getSelection()->upg)?(owner->armedObjs[0]):(owner->armedObjs[1]), + (!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]), + owner->getSelection()->ID,ID); + } + } + else // Highlight + { + if(creature) + owner->selectSlot(this); + redraw(); + refr = true; + } + } + } + else //highlight or drop artifact + { + bool artSelected = false; + if (CWindowWithArtifacts* chw = dynamic_cast(GH.topInt())) //dirty solution + { + const CArtifactsOfHero::SCommonPart *commonInfo = chw->artSets.front()->commonInfo; + if (const CArtifactInstance *art = commonInfo->src.art) + { + const CGHeroInstance *srcHero = commonInfo->src.AOH->getHero(); + artSelected = true; + ArtifactLocation src(srcHero, commonInfo->src.slotID); + ArtifactLocation dst(myStack, ArtifactPosition::CREATURE_SLOT); + if (art->canBePutAt(dst, true)) + { //equip clicked stack + if(dst.getArt()) + { + //creature can wear only one active artifact + //if we are placing a new one, the old one will be returned to the hero's backpack + LOCPLINT->cb->swapArtifacts(dst, ArtifactLocation(srcHero, dst.getArt()->firstBackpackSlot(srcHero))); + } + LOCPLINT->cb->swapArtifacts(src, dst); + } + } + } + if (!artSelected && creature) + { + owner->selectSlot(this); + if(creature) + { + for(auto & elem : owner->splitButtons) + elem->block(false); + } + } + redraw(); + refr = true; + } + if(refr) {hover(false); hover(true); } //to refresh statusbar + } +} + +void CGarrisonSlot::update() +{ + if (getObj() != nullptr) + { + addUsedEvents(LCLICK | RCLICK | HOVER); + myStack = getObj()->getStackPtr(ID); + creature = myStack ? myStack->type : nullptr; + } + else + { + removeUsedEvents(LCLICK | RCLICK | HOVER); + myStack = nullptr; + creature = nullptr; + } + + if (creature) + { + creatureImage->enable(); + creatureImage->setFrame(creature->iconIndex); + + stackCount->enable(); + stackCount->setText(boost::lexical_cast(myStack->count)); + } + else + { + creatureImage->disable(); + stackCount->disable(); + } +} + +CGarrisonSlot::CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, int Upg, const CStackInstance * Creature): + ID(IID), + owner(Owner), + myStack(Creature), + creature(Creature ? Creature->type : nullptr), + upg(Upg) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + if (getObj()) + addUsedEvents(LCLICK | RCLICK | HOVER); + pos.x += x; + pos.y += y; + + std::string imgName = owner->smallIcons ? "cprsmall" : "TWCRPORT"; + + creatureImage = new CAnimImage(imgName, creature ? creature->iconIndex : 0); + if (!creature) + creatureImage->disable(); + + selectionImage = new CAnimImage(imgName, 1); + selectionImage->disable(); + + if(Owner->smallIcons) + { + pos.w = 32; + pos.h = 32; + } + else + { + pos.w = 58; + pos.h = 64; + } + + stackCount = new CLabel(pos.w, pos.h, owner->smallIcons ? FONT_TINY : FONT_MEDIUM, BOTTOMRIGHT, Colors::WHITE); + if (!creature) + stackCount->disable(); + else + stackCount->setText(boost::lexical_cast(myStack->count)); +} + +void CGarrisonInt::addSplitBtn(CButton * button) +{ + addChild(button); + button->recActions = defActions; + splitButtons.push_back(button); + button->block(getSelection() == nullptr); +} + +void CGarrisonInt::createSet(std::vector &ret, const CCreatureSet * set, int posX, int posY, int distance, int Upg ) +{ + ret.resize(7); + + if (set) + { + for(auto & elem : set->Slots()) + { + ret[elem.first.getNum()] = new CGarrisonSlot(this, posX + (elem.first.getNum()*distance), posY, elem.first, Upg, elem.second); + } + } + + for(int i=0; ipos.x -= 126; + ret[i]->pos.y += 37; + }; +} + +void CGarrisonInt::createSlots() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + int width = smallIcons? 32 : 58; + + createSet(slotsUp, armedObjs[0], 0, 0, width+interx, 0); + createSet(slotsDown, armedObjs[1], garOffset.x, garOffset.y, width+interx, 1); +} + +void CGarrisonInt::recreateSlots() +{ + selectSlot(nullptr); + setSplittingMode(false); + + for(auto & elem : splitButtons) + elem->block(true); + + + for(CGarrisonSlot * slot : slotsUp) + slot->update(); + + for(CGarrisonSlot * slot : slotsDown) + slot->update(); +} + +void CGarrisonInt::splitClick() +{ + if(!getSelection()) + return; + setSplittingMode(!getSplittingMode()); + redraw(); +} +void CGarrisonInt::splitStacks(int, int amountRight) +{ + LOCPLINT->cb->splitStack(armedObjs[getSelection()->upg], armedObjs[pb], getSelection()->ID, p2, amountRight); +} + +CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point &garsOffset, + SDL_Surface *pomsur, const Point& SurOffset, + const CArmedInstance *s1, const CArmedInstance *s2, + bool _removableUnits, bool smallImgs, bool _twoRows ) : + highlighted(nullptr), + inSplittingMode(false), + interx(inx), + garOffset(garsOffset), + smallIcons(smallImgs), + removableUnits(_removableUnits), + twoRows(_twoRows) +{ + setArmy(s1, false); + setArmy(s2, true); + pos.x += x; + pos.y += y; + createSlots(); +} + +const CGarrisonSlot * CGarrisonInt::getSelection() +{ + return highlighted; +} + +void CGarrisonInt::selectSlot(CGarrisonSlot *slot) +{ + if (slot != highlighted) + { + if (highlighted) + highlighted->setHighlight(false); + + highlighted = slot; + for (auto button : splitButtons) + button->block(highlighted == nullptr); + + if (highlighted) + highlighted->setHighlight(true); + } +} + +void CGarrisonInt::setSplittingMode(bool on) +{ + assert(on == false || highlighted != nullptr); //can't be in splitting mode without selection + + if (inSplittingMode || on) + { + for(CGarrisonSlot * slot : slotsUp) + slot->setHighlight( ( on && (slot->creature == nullptr || slot->creature == getSelection()->creature))); + + for(CGarrisonSlot * slot : slotsDown) + slot->setHighlight( ( on && (slot->creature == nullptr || slot->creature == getSelection()->creature))); + inSplittingMode = on; + } +} + +bool CGarrisonInt::getSplittingMode() +{ + return inSplittingMode; +} + +void CGarrisonInt::setArmy(const CArmedInstance *army, bool bottomGarrison) +{ + owned[bottomGarrison] = army ? (army->tempOwner == LOCPLINT->playerID || army->tempOwner == PlayerColor::UNFLAGGABLE) : false; + armedObjs[bottomGarrison] = army; +} + +CGarrisonWindow::CGarrisonWindow( const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits ): + CWindowObject(PLAYER_COLORED, "GARRISON") +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + garr = new CGarrisonInt(92, 127, 4, Point(0,96), background->bg, Point(93,127), up, down, removableUnits); + { + CButton *split = new CButton(Point(88, 314), "IDV6432.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3], ""), [&]{ garr->splitClick(); } ); + removeChild(split); + garr->addSplitBtn(split); + } + quit = new CButton(Point(399, 314), "IOK6432.DEF", CButton::tooltip(CGI->generaltexth->tcommands[8], ""), [&]{ close(); }, SDLK_RETURN); + + std::string titleText; + if (garr->armedObjs[1]->tempOwner == garr->armedObjs[0]->tempOwner) + titleText = CGI->generaltexth->allTexts[709]; + else + { + titleText = CGI->generaltexth->allTexts[35]; + boost::algorithm::replace_first(titleText, "%s", garr->armedObjs[0]->Slots().begin()->second->type->namePl); + } + new CLabel(275, 30, FONT_BIG, CENTER, Colors::YELLOW, titleText); + + new CAnimImage("CREST58", garr->armedObjs[0]->getOwner().getNum(), 0, 28, 124); + new CAnimImage("PortraitsLarge", dynamic_cast(garr->armedObjs[1])->portrait, 0, 29, 222); +} + +CGarrisonHolder::CGarrisonHolder() +{ +} + +void CWindowWithGarrison::updateGarrisons() +{ + garr->recreateSlots(); +} diff --git a/client/widgets/CGarrisonInt.h b/client/widgets/CGarrisonInt.h new file mode 100644 index 000000000..df32cf424 --- /dev/null +++ b/client/widgets/CGarrisonInt.h @@ -0,0 +1,122 @@ +#pragma once + +#include "../windows/CWindowObject.h" + +/* + * CGarrisonInt.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 + * + */ + +class CGarrisonInt; +class CButton; +class CArmedInstance; +class CAnimImage; +class CCreatureSet; +class CGarrisonSlot; +class CStackInstance; +class CLabel; + +/// A single garrison slot which holds one creature of a specific amount +class CGarrisonSlot : public CIntObject +{ + SlotID ID; //for identification + CGarrisonInt *owner; + const CStackInstance *myStack; //nullptr if slot is empty + const CCreature *creature; + int upg; //0 - up garrison, 1 - down garrison + + CAnimImage * creatureImage; + CAnimImage * selectionImage; // image for selection, not always visible + CLabel * stackCount; + + void setHighlight(bool on); +public: + virtual void hover (bool on); //call-in + const CArmedInstance * getObj() const; + bool our() const; + void clickRight(tribool down, bool previousState); + void clickLeft(tribool down, bool previousState); + void update(); + CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, int Upg=0, const CStackInstance * Creature=nullptr); + + friend class CGarrisonInt; +}; + +/// Class which manages slots of upper and lower garrison, splitting of units +class CGarrisonInt :public CIntObject +{ + CGarrisonSlot *highlighted; //chosen slot. Should be changed only via selectSlot + bool inSplittingMode; + +public: + void selectSlot(CGarrisonSlot * slot); //null = deselect + const CGarrisonSlot * getSelection(); + + void setSplittingMode(bool on); + bool getSplittingMode(); + + int interx; //space between slots + Point garOffset; //offset between garrisons (not used if only one hero) + std::vector splitButtons; //may be empty if no buttons + + SlotID p2; //TODO: comment me + int shiftPos;//1st slot of the second row, set shiftPoint for effect + bool pb, + smallIcons, //true - 32x32 imgs, false - 58x64 + removableUnits,//player can remove units from up + twoRows,//slots will be placed in 2 rows + owned[2];//player owns up or down army [0] upper, [1] lower + +// const CCreatureSet *set1; //top set of creatures +// const CCreatureSet *set2; //bottom set of creatures + + std::vector slotsUp, slotsDown; //slots of upper and lower garrison + const CArmedInstance *armedObjs[2]; //[0] is upper, [1] is down + //const CArmedInstance *oup, *odown; //upper and lower garrisons (heroes or towns) + + void setArmy(const CArmedInstance *army, bool bottomGarrison); + void addSplitBtn(CButton * button); + void createSet(std::vector &ret, const CCreatureSet * set, int posX, int distance, int posY, int Upg ); + + void createSlots(); + void recreateSlots(); + + void splitClick(); //handles click on split button + void splitStacks(int amountLeft, int amountRight); //TODO: comment me + //x, y - position; + //inx - distance between slots; + //pomsur, SurOffset - UNUSED + //s1, s2 - top and bottom armies; + //removableUnits - you can take units from top; + //smallImgs - units images size 64x58 or 32x32; + //twoRows - display slots in 2 row (1st row = 4 slots, 2nd = 3 slots) + CGarrisonInt(int x, int y, int inx, const Point &garsOffset, SDL_Surface *pomsur, const Point &SurOffset, const CArmedInstance *s1, const CArmedInstance *s2=nullptr, bool _removableUnits = true, bool smallImgs = false, bool _twoRows=false); //c-tor +}; + +class CGarrisonHolder +{ +public: + CGarrisonHolder(); + virtual void updateGarrisons()=0; +}; + +class CWindowWithGarrison : public virtual CGarrisonHolder +{ +public: + CGarrisonInt *garr; + virtual void updateGarrisons(); +}; + +/// Garrison window where you can take creatures out of the hero to place it on the garrison +class CGarrisonWindow : public CWindowObject, public CWindowWithGarrison +{ +public: + CButton * quit; + + CGarrisonWindow(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits); //c-tor +}; diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp new file mode 100644 index 000000000..7ba42a1f4 --- /dev/null +++ b/client/widgets/Images.cpp @@ -0,0 +1,533 @@ +#include "StdInc.h" +#include "Images.h" + +#include "MiscWidgets.h" + +#include "../gui/CAnimation.h" +#include "../gui/SDL_Pixels.h" +#include "../gui/SDL_Extensions.h" +#include "../gui/CGuiHandler.h" +#include "../gui/CCursorHandler.h" + +#include "../battle/CBattleInterface.h" +#include "../battle/CBattleInterfaceClasses.h" + +#include "../CBitmapHandler.h" +#include "../Graphics.h" +#include "../CGameInfo.h" +#include "../CPlayerInterface.h" +#include "../CMessage.h" +#include "../CMusicHandler.h" +#include "../windows/CAdvmapInterface.h" + +#include "../../CCallback.h" + +#include "../../lib/CConfigHandler.h" +#include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff +#include "../../lib/CRandomGenerator.h" + +/* + * Images.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 + * + */ + +CPicture::CPicture( SDL_Surface *BG, int x, int y, bool Free ) +{ + init(); + bg = BG; + freeSurf = Free; + pos.x += x; + pos.y += y; + pos.w = BG->w; + pos.h = BG->h; +} + +CPicture::CPicture( const std::string &bmpname, int x, int y ) +{ + init(); + bg = BitmapHandler::loadBitmap(bmpname); + freeSurf = true;; + pos.x += x; + pos.y += y; + if(bg) + { + pos.w = bg->w; + pos.h = bg->h; + } + else + { + pos.w = pos.h = 0; + } +} + +CPicture::CPicture(const Rect &r, const SDL_Color &color, bool screenFormat /*= false*/) +{ + init(); + createSimpleRect(r, screenFormat, SDL_MapRGB(bg->format, color.r, color.g,color.b)); +} + +CPicture::CPicture(const Rect &r, ui32 color, bool screenFormat /*= false*/) +{ + init(); + createSimpleRect(r, screenFormat, color); +} + +CPicture::CPicture(SDL_Surface *BG, const Rect &SrcRect, int x /*= 0*/, int y /*= 0*/, bool free /*= false*/) +{ + needRefresh = false; + srcRect = new Rect(SrcRect); + pos.x += x; + pos.y += y; + pos.w = srcRect->w; + pos.h = srcRect->h; + bg = BG; + freeSurf = free; +} + +void CPicture::setSurface(SDL_Surface *to) +{ + bg = to; + if (srcRect) + { + pos.w = srcRect->w; + pos.h = srcRect->h; + } + else + { + pos.w = bg->w; + pos.h = bg->h; + } +} + +CPicture::~CPicture() +{ + if(freeSurf) + SDL_FreeSurface(bg); + delete srcRect; +} + +void CPicture::init() +{ + needRefresh = false; + srcRect = nullptr; +} + +void CPicture::show(SDL_Surface * to) +{ + if (needRefresh) + showAll(to); +} + +void CPicture::showAll(SDL_Surface * to) +{ + if(bg) + { + if(srcRect) + { + SDL_Rect srcRectCpy = *srcRect; + SDL_Rect dstRect = srcRectCpy; + dstRect.x = pos.x; + dstRect.y = pos.y; + + CSDL_Ext::blitSurface(bg, &srcRectCpy, to, &dstRect); + } + else + blitAt(bg, pos, to); + } +} + +void CPicture::convertToScreenBPP() +{ + SDL_Surface *hlp = bg; + bg = SDL_ConvertSurface(hlp,screen->format,0); + CSDL_Ext::setDefaultColorKey(bg); + SDL_FreeSurface(hlp); +} + +void CPicture::setAlpha(int value) +{ + #ifdef VCMI_SDL1 + SDL_SetAlpha(bg, SDL_SRCALPHA, value); + #else + SDL_SetSurfaceAlphaMod(bg,value); + #endif // 0 +} + +void CPicture::scaleTo(Point size) +{ + SDL_Surface * scaled = CSDL_Ext::scaleSurface(bg, size.x, size.y); + + if(freeSurf) + SDL_FreeSurface(bg); + + setSurface(scaled); + freeSurf = false; +} + +void CPicture::createSimpleRect(const Rect &r, bool screenFormat, ui32 color) +{ + pos += r; + pos.w = r.w; + pos.h = r.h; + if(screenFormat) + bg = CSDL_Ext::newSurface(r.w, r.h); + else + bg = SDL_CreateRGBSurface(SDL_SWSURFACE, r.w, r.h, 8, 0, 0, 0, 0); + + SDL_FillRect(bg, nullptr, color); + freeSurf = true; +} + +void CPicture::colorizeAndConvert(PlayerColor player) +{ + assert(bg); + colorize(player); + convertToScreenBPP(); +} + +void CPicture::colorize(PlayerColor player) +{ + assert(bg); + graphics->blueToPlayersAdv(bg, player); +} + +CFilledTexture::CFilledTexture(std::string imageName, Rect position): + CIntObject(0, position.topLeft()), + texture(BitmapHandler::loadBitmap(imageName)) +{ + pos.w = position.w; + pos.h = position.h; +} + +CFilledTexture::~CFilledTexture() +{ + SDL_FreeSurface(texture); +} + +void CFilledTexture::showAll(SDL_Surface *to) +{ + CSDL_Ext::CClipRectGuard guard(to, pos); + CSDL_Ext::fillTexture(to, texture); +} + +CAnimImage::CAnimImage(std::string name, size_t Frame, size_t Group, int x, int y, ui8 Flags): + frame(Frame), + group(Group), + player(-1), + flags(Flags) +{ + pos.x += x; + pos.y += y; + anim = new CAnimation(name); + init(); +} + +CAnimImage::CAnimImage(CAnimation *Anim, size_t Frame, size_t Group, int x, int y, ui8 Flags): + anim(Anim), + frame(Frame), + group(Group), + player(-1), + flags(Flags) +{ + pos.x += x; + pos.y += y; + init(); +} + +size_t CAnimImage::size() +{ + return anim->size(group); +} + +void CAnimImage::init() +{ + anim->load(frame, group); + if (flags & CShowableAnim::BASE) + anim->load(0,group); + + IImage *img = anim->getImage(frame, group); + if (img) + { + pos.w = img->width(); + pos.h = img->height(); + } +} + +CAnimImage::~CAnimImage() +{ + anim->unload(frame, group); + if (flags & CShowableAnim::BASE) + anim->unload(0,group); + delete anim; +} + +void CAnimImage::showAll(SDL_Surface * to) +{ + IImage *img; + + if ( flags & CShowableAnim::BASE && frame != 0) + if ((img = anim->getImage(0, group))) + img->draw(to, pos.x, pos.y); + + if ((img = anim->getImage(frame, group))) + img->draw(to, pos.x, pos.y); +} + +void CAnimImage::setFrame(size_t Frame, size_t Group) +{ + if (frame == Frame && group==Group) + return; + if (anim->size(Group) > Frame) + { + anim->load(Frame, Group); + anim->unload(frame, group); + frame = Frame; + group = Group; + IImage *img = anim->getImage(frame, group); + if (img) + { + if (flags & CShowableAnim::PLAYER_COLORED) + img->playerColored(player); + pos.w = img->width(); + pos.h = img->height(); + } + } + else + logGlobal->errorStream() << "Error: accessing unavailable frame " << Group << ":" << Frame << " in CAnimation!"; +} + +void CAnimImage::playerColored(PlayerColor currPlayer) +{ + player = currPlayer; + flags |= CShowableAnim::PLAYER_COLORED; + anim->getImage(frame, group)->playerColored(player); + if (flags & CShowableAnim::BASE) + anim->getImage(0, group)->playerColored(player); +} + +CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Delay, size_t Group): + anim(new CAnimation(name, Flags & USE_RLE)), + group(Group), + frame(0), + first(0), + frameDelay(Delay), + value(0), + flags(Flags), + xOffset(0), + yOffset(0), + alpha(255) +{ + anim->loadGroup(group); + last = anim->size(group); + + pos.w = anim->getImage(0, group)->width(); + pos.h = anim->getImage(0, group)->height(); + pos.x+= x; + pos.y+= y; +} + +CShowableAnim::~CShowableAnim() +{ + anim->unloadGroup(group); + delete anim; +} + +void CShowableAnim::setAlpha(ui32 alphaValue) +{ + alpha = std::min(alphaValue, 255); +} + +bool CShowableAnim::set(size_t Group, size_t from, size_t to) +{ + size_t max = anim->size(Group); + + if (to < max) + max = to; + + if (max < from || max == 0) + return false; + + anim->load(Group); + anim->unload(group); + group = Group; + frame = first = from; + last = max; + value = 0; + return true; +} + +bool CShowableAnim::set(size_t Group) +{ + if (anim->size(Group)== 0) + return false; + if (group != Group) + { + anim->loadGroup(Group); + anim->unloadGroup(group); + first = 0; + group = Group; + last = anim->size(Group); + } + frame = value = 0; + return true; +} + +void CShowableAnim::reset() +{ + value = 0; + frame = first; + + if (callback) + callback(); +} + +void CShowableAnim::clipRect(int posX, int posY, int width, int height) +{ + xOffset = posX; + yOffset = posY; + pos.w = width; + pos.h = height; +} + +void CShowableAnim::show(SDL_Surface * to) +{ + if ( flags & BASE )// && frame != first) // FIXME: results in graphical glytch in Fortress, upgraded hydra's dwelling + blitImage(first, group, to); + blitImage(frame, group, to); + + if ((flags & PLAY_ONCE) && frame + 1 == last) + return; + + if ( ++value == frameDelay ) + { + value = 0; + if ( ++frame >= last) + reset(); + } +} + +void CShowableAnim::showAll(SDL_Surface * to) +{ + if ( flags & BASE )// && frame != first) + blitImage(first, group, to); + blitImage(frame, group, to); +} + +void CShowableAnim::blitImage(size_t frame, size_t group, SDL_Surface *to) +{ + assert(to); + Rect src( xOffset, yOffset, pos.w, pos.h); + IImage * img = anim->getImage(frame, group); + if (img) + img->draw(to, pos.x-xOffset, pos.y-yOffset, &src, alpha); +} + +void CShowableAnim::rotate(bool on, bool vertical) +{ + ui8 flag = vertical? VERTICAL_FLIP:HORIZONTAL_FLIP; + if (on) + flags |= flag; + else + flags &= ~flag; +} + +CCreatureAnim::CCreatureAnim(int x, int y, std::string name, Rect picPos, ui8 flags, EAnimType type): + CShowableAnim(x,y,name,flags,4,type) +{ + xOffset = picPos.x; + yOffset = picPos.y; + if (picPos.w) + pos.w = picPos.w; + if (picPos.h) + pos.h = picPos.h; +}; + +void CCreatureAnim::loopPreview(bool warMachine) +{ + std::vector available; + + static const EAnimType creaPreviewList[] = {HOLDING, HITTED, DEFENCE, ATTACK_FRONT, CAST_FRONT}; + static const EAnimType machPreviewList[] = {HOLDING, MOVING, SHOOT_UP, SHOOT_FRONT, SHOOT_DOWN}; + auto & previewList = warMachine ? machPreviewList : creaPreviewList; + + for (auto & elem : previewList) + if (anim->size(elem)) + available.push_back(elem); + + size_t rnd = CRandomGenerator::getDefault().nextInt(available.size() * 2 - 1); + + if (rnd >= available.size()) + { + EAnimType type; + if ( anim->size(MOVING) == 0 )//no moving animation present + type = HOLDING; + else + type = MOVING; + + //display this anim for ~1 second (time is random, but it looks good) + for (size_t i=0; i< 12/anim->size(type) + 1; i++) + addLast(type); + } + else + addLast(available[rnd]); +} + +void CCreatureAnim::addLast(EAnimType newType) +{ + if (type != MOVING && newType == MOVING)//starting moving - play init sequence + { + queue.push( MOVE_START ); + } + else if (type == MOVING && newType != MOVING )//previous anim was moving - finish it + { + queue.push( MOVE_END ); + } + if (newType == TURN_L || newType == TURN_R) + queue.push(newType); + + queue.push(newType); +} + +void CCreatureAnim::reset() +{ + //if we are in the middle of rotation - set flag + if (type == TURN_L && !queue.empty() && queue.front() == TURN_L) + rotate(true); + if (type == TURN_R && !queue.empty() && queue.front() == TURN_R) + rotate(false); + + while (!queue.empty()) + { + EAnimType at = queue.front(); + queue.pop(); + if (set(at)) + return; + } + if (callback) + callback(); + while (!queue.empty()) + { + EAnimType at = queue.front(); + queue.pop(); + if (set(at)) + return; + } + set(HOLDING); +} + +void CCreatureAnim::startPreview(bool warMachine) +{ + callback = std::bind(&CCreatureAnim::loopPreview, this, warMachine); +} + +void CCreatureAnim::clearAndSet(EAnimType type) +{ + while (!queue.empty()) + queue.pop(); + set(type); +} diff --git a/client/widgets/Images.h b/client/widgets/Images.h new file mode 100644 index 000000000..bdc981ced --- /dev/null +++ b/client/widgets/Images.h @@ -0,0 +1,226 @@ +#pragma once + +#include "../gui/CIntObject.h" +#include "../gui/SDL_Extensions.h" + +struct SDL_Surface; +struct Rect; +class CAnimImage; +class CLabel; +class CAnimation; +class CDefHandler; + +/* + * Images.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 + * + */ + +// Image class +class CPicture : public CIntObject +{ + void setSurface(SDL_Surface *to); +public: + SDL_Surface * bg; + Rect * srcRect; //if nullptr then whole surface will be used + bool freeSurf; //whether surface will be freed upon CPicture destruction + bool needRefresh;//Surface needs to be displayed each frame + + operator SDL_Surface*() + { + return bg; + } + + CPicture(const Rect & r, const SDL_Color & color, bool screenFormat = false); //rect filled with given color + CPicture(const Rect & r, ui32 color, bool screenFormat = false); //rect filled with given color + CPicture(SDL_Surface * BG, int x = 0, int y=0, bool Free = true); //wrap existing SDL_Surface + CPicture(const std::string &bmpname, int x=0, int y=0); + CPicture(SDL_Surface *BG, const Rect &SrcRext, int x = 0, int y = 0, bool free = false); //wrap subrect of given surface + ~CPicture(); + void init(); + + //set alpha value for whole surface. Note: may be messed up if surface is shared + // 0=transparent, 255=opaque + void setAlpha(int value); + + void scaleTo(Point size); + void createSimpleRect(const Rect &r, bool screenFormat, ui32 color); + void show(SDL_Surface * to); + void showAll(SDL_Surface * to); + void convertToScreenBPP(); + void colorizeAndConvert(PlayerColor player); + void colorize(PlayerColor player); +}; + +/// area filled with specific texture +class CFilledTexture : CIntObject +{ + SDL_Surface * texture; + +public: + CFilledTexture(std::string imageName, Rect position); + ~CFilledTexture(); + void showAll(SDL_Surface *to); +}; + +/// Class for displaying one image from animation +class CAnimImage: public CIntObject +{ +private: + CAnimation* anim; + //displayed frame/group + size_t frame; + size_t group; + PlayerColor player; + ui8 flags; + + void init(); + +public: + CAnimImage(std::string name, size_t Frame, size_t Group=0, int x=0, int y=0, ui8 Flags=0); + CAnimImage(CAnimation* anim, size_t Frame, size_t Group=0, int x=0, int y=0, ui8 Flags=0); + ~CAnimImage();//d-tor + + //size of animation + size_t size(); + + //change displayed frame on this one + void setFrame(size_t Frame, size_t Group=0); + + //makes image player-colored + void playerColored(PlayerColor player); + + void showAll(SDL_Surface * to); +}; + +/// Base class for displaying animation, used as superclass for different animations +class CShowableAnim: public CIntObject +{ +public: + enum EFlags + { + BASE=1, //base frame will be blitted before current one + HORIZONTAL_FLIP=2, //TODO: will be displayed rotated + VERTICAL_FLIP=4, //TODO: will be displayed rotated + USE_RLE=8, //RLE-d version, support full alpha-channel for 8-bit images + PLAYER_COLORED=16, //TODO: all loaded images will be player-colored + PLAY_ONCE=32 //play animation only once and stop at last frame + }; +protected: + CAnimation * anim; + + size_t group, frame;//current frame + + size_t first, last; //animation range + + //TODO: replace with time delay(needed for battles) + ui32 frameDelay;//delay in frames of each image + ui32 value;//how many times current frame was showed + + ui8 flags;//Flags from EFlags enum + + //blit image with optional rotation, fitting into rect, etc + void blitImage(size_t frame, size_t group, SDL_Surface *to); + + //For clipping in rect, offsets of picture coordinates + int xOffset, yOffset; + + ui8 alpha; + +public: + //called when next animation sequence is required + std::function callback; + + //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(); + + //set animation to group or part of group + bool set(size_t Group); + bool set(size_t Group, size_t from, size_t to=-1); + + //set rotation flags + void rotate(bool on, bool vertical=false); + + //move displayed part of picture (if picture is clipped to rect) + void clipRect(int posX, int posY, int width, int height); + + //set frame to first, call callback + virtual void reset(); + + //show current frame and increase counter + void show(SDL_Surface * to); + void showAll(SDL_Surface * to); +}; + +/// Creature-dependend animations like attacking, moving,... +class CCreatureAnim: public CShowableAnim +{ +public: + + enum EHeroAnimType + { + HERO_HOLDING = 0, + HERO_IDLE = 1, // idling movement that happens from time to time + HERO_DEFEAT = 2, // played when army loses stack or on friendly fire + HERO_VICTORY = 3, // when enemy stack killed or huge damage is dealt + HERO_CAST_SPELL = 4 // spellcasting + }; + + enum EAnimType // list of creature animations, numbers were taken from def files + { + MOVING=0, + MOUSEON=1, + HOLDING=2, + HITTED=3, + DEFENCE=4, + DEATH=5, + //DEATH2=6, //unused? + TURN_L=7, + TURN_R=8, //same + //TURN_L2=9, //identical to previous? + //TURN_R2=10, + ATTACK_UP=11, + ATTACK_FRONT=12, + ATTACK_DOWN=13, + SHOOT_UP=14, + SHOOT_FRONT=15, + SHOOT_DOWN=16, + CAST_UP=17, + CAST_FRONT=18, + CAST_DOWN=19, + MOVE_START=20, + MOVE_END=21, + DEAD = 22 // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here + + }; + +private: + //queue of animations waiting to be displayed + std::queue queue; + + //this function is used as callback if preview flag was set during construction + void loopPreview(bool warMachine); + +public: + //change anim to next if queue is not empty, call callback othervice + void reset(); + + //add sequence to the end of queue + void addLast(EAnimType newType); + + void startPreview(bool warMachine); + + //clear queue and set animation to this sequence + void clearAndSet(EAnimType type); + + CCreatureAnim(int x, int y, std::string name, Rect picPos, + ui8 flags= USE_RLE, EAnimType = HOLDING ); + +}; diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp new file mode 100644 index 000000000..b6970ed57 --- /dev/null +++ b/client/widgets/MiscWidgets.cpp @@ -0,0 +1,442 @@ +#include "StdInc.h" +#include "MiscWidgets.h" + +#include "CComponent.h" + +#include "../gui/CGuiHandler.h" +#include "../gui/CCursorHandler.h" + +#include "../CBitmapHandler.h" +#include "../CPlayerInterface.h" +#include "../CMessage.h" +#include "../CGameInfo.h" +#include "../windows/CAdvmapInterface.h" +#include "../windows/CCastleInterface.h" +#include "../windows/InfoWindows.h" + +#include "../../CCallback.h" + +#include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CModHandler.h" +#include "../../lib/CGameState.h" + +/* + * MiscWidgets.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 + * + */ + +void CHoverableArea::hover (bool on) +{ + if (on) + GH.statusbar->setText(hoverText); + else if (GH.statusbar->getText()==hoverText) + GH.statusbar->clear(); +} + +CHoverableArea::CHoverableArea() +{ + addUsedEvents(HOVER); +} + +CHoverableArea::~CHoverableArea() +{ +} + +void LRClickableAreaWText::clickLeft(tribool down, bool previousState) +{ + if(!down && previousState && !text.empty()) + { + LOCPLINT->showInfoDialog(text); + } +} +void LRClickableAreaWText::clickRight(tribool down, bool previousState) +{ + if (!text.empty()) + adventureInt->handleRightClick(text, down); +} + +LRClickableAreaWText::LRClickableAreaWText() +{ + init(); +} + +LRClickableAreaWText::LRClickableAreaWText(const Rect &Pos, const std::string &HoverText /*= ""*/, const std::string &ClickText /*= ""*/) +{ + init(); + pos = Pos + pos; + hoverText = HoverText; + text = ClickText; +} + +LRClickableAreaWText::~LRClickableAreaWText() +{ +} + +void LRClickableAreaWText::init() +{ + addUsedEvents(LCLICK | RCLICK | HOVER); +} + +void LRClickableAreaWTextComp::clickLeft(tribool down, bool previousState) +{ + if((!down) && previousState) + { + std::vector comp(1, createComponent()); + LOCPLINT->showInfoDialog(text, comp); + } +} + +LRClickableAreaWTextComp::LRClickableAreaWTextComp(const Rect &Pos, int BaseType) + : LRClickableAreaWText(Pos), baseType(BaseType), bonusValue(-1) +{ +} + +CComponent * LRClickableAreaWTextComp::createComponent() const +{ + if(baseType >= 0) + return new CComponent(CComponent::Etype(baseType), type, bonusValue); + else + return nullptr; +} + +void LRClickableAreaWTextComp::clickRight(tribool down, bool previousState) +{ + if(down) + { + if(CComponent *comp = createComponent()) + { + CRClickPopup::createAndPush(text, CInfoWindow::TCompsInfo(1, comp)); + return; + } + } + + LRClickableAreaWText::clickRight(down, previousState); //only if with-component variant not occurred +} + +CHeroArea::CHeroArea(int x, int y, const CGHeroInstance * _hero):hero(_hero) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + addUsedEvents(LCLICK | RCLICK | HOVER); + pos.x += x; pos.w = 58; + pos.y += y; pos.h = 64; + + if (hero) + new CAnimImage("PortraitsLarge", hero->portrait); +} + +void CHeroArea::clickLeft(tribool down, bool previousState) +{ + if((!down) && previousState && hero) + LOCPLINT->openHeroWindow(hero); +} + +void CHeroArea::clickRight(tribool down, bool previousState) +{ + if((!down) && previousState && hero) + LOCPLINT->openHeroWindow(hero); +} + +void CHeroArea::hover(bool on) +{ + if (on && hero) + GH.statusbar->setText(hero->getObjectName()); + else + GH.statusbar->clear(); +} + +void LRClickableAreaOpenTown::clickLeft(tribool down, bool previousState) +{ + if((!down) && previousState && town) + { + LOCPLINT->openTownWindow(town); + if ( type == 2 ) + LOCPLINT->castleInt->builds->buildingClicked(BuildingID::VILLAGE_HALL); + else if ( type == 3 && town->fortLevel() ) + LOCPLINT->castleInt->builds->buildingClicked(BuildingID::FORT); + } +} + +void LRClickableAreaOpenTown::clickRight(tribool down, bool previousState) +{ + if((!down) && previousState && town) + LOCPLINT->openTownWindow(town);//TODO: popup? +} + +LRClickableAreaOpenTown::LRClickableAreaOpenTown() + : LRClickableAreaWTextComp(Rect(0,0,0,0), -1) +{ +} + +void CMinorResDataBar::show(SDL_Surface * to) +{ +} + +void CMinorResDataBar::showAll(SDL_Surface * to) +{ + blitAt(bg,pos.x,pos.y,to); + for (Res::ERes i=Res::WOOD; i<=Res::GOLD; vstd::advance(i, 1)) + { + std::string text = boost::lexical_cast(LOCPLINT->cb->getResourceAmount(i)); + + graphics->fonts[FONT_SMALL]->renderTextCenter(to, text, Colors::WHITE, Point(pos.x + 50 + 76 * i, pos.y + pos.h/2)); + } + std::vector temp; + + temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(Date::MONTH))); + temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(Date::WEEK))); + temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK))); + + std::string datetext = CGI->generaltexth->allTexts[62]+": %s, " + CGI->generaltexth->allTexts[63] + + ": %s, " + CGI->generaltexth->allTexts[64] + ": %s"; + + graphics->fonts[FONT_SMALL]->renderTextCenter(to, CSDL_Ext::processStr(datetext,temp), Colors::WHITE, Point(pos.x+545+(pos.w-545)/2,pos.y+pos.h/2)); +} + +CMinorResDataBar::CMinorResDataBar() +{ + bg = BitmapHandler::loadBitmap("KRESBAR.bmp"); + CSDL_Ext::setDefaultColorKey(bg); + graphics->blueToPlayersAdv(bg,LOCPLINT->playerID); + pos.x = 7; + pos.y = 575; + pos.w = bg->w; + pos.h = bg->h; +} + +CMinorResDataBar::~CMinorResDataBar() +{ + SDL_FreeSurface(bg); +} + +void CArmyTooltip::init(const InfoAboutArmy &army) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + new CLabel(66, 2, FONT_SMALL, TOPLEFT, Colors::WHITE, army.name); + + std::vector slotsPos; + slotsPos.push_back(Point(36,73)); + slotsPos.push_back(Point(72,73)); + slotsPos.push_back(Point(108,73)); + slotsPos.push_back(Point(18,122)); + slotsPos.push_back(Point(54,122)); + slotsPos.push_back(Point(90,122)); + slotsPos.push_back(Point(126,122)); + + for(auto & slot : army.army) + { + if(slot.first.getNum() >= GameConstants::ARMY_SIZE) + { + logGlobal->warnStream() << "Warning: " << army.name << " has stack in slot " << slot.first; + continue; + } + + new CAnimImage("CPRSMALL", slot.second.type->iconIndex, 0, slotsPos[slot.first.getNum()].x, slotsPos[slot.first.getNum()].y); + + std::string subtitle; + if(army.army.isDetailed) + subtitle = boost::lexical_cast(slot.second.count); + else + { + //if =0 - we have no information about stack size at all + if (slot.second.count) + subtitle = CGI->generaltexth->arraytxt[171 + 3*(slot.second.count)]; + } + + new CLabel(slotsPos[slot.first.getNum()].x + 17, slotsPos[slot.first.getNum()].y + 41, FONT_TINY, CENTER, Colors::WHITE, subtitle); + } + +} + +CArmyTooltip::CArmyTooltip(Point pos, const InfoAboutArmy &army): + CIntObject(0, pos) +{ + init(army); +} + +CArmyTooltip::CArmyTooltip(Point pos, const CArmedInstance * army): + CIntObject(0, pos) +{ + init(InfoAboutArmy(army, true)); +} + +void CHeroTooltip::init(const InfoAboutHero &hero) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + new CAnimImage("PortraitsLarge", hero.portrait, 0, 3, 2); + + if(hero.details) + { + for (size_t i = 0; i < hero.details->primskills.size(); i++) + new CLabel(75 + 28 * i, 58, FONT_SMALL, CENTER, Colors::WHITE, + boost::lexical_cast(hero.details->primskills[i])); + + new CLabel(158, 98, FONT_TINY, CENTER, Colors::WHITE, + boost::lexical_cast(hero.details->mana)); + + new CAnimImage("IMRL22", hero.details->morale + 3, 0, 5, 74); + new CAnimImage("ILCK22", hero.details->luck + 3, 0, 5, 91); + } +} + +CHeroTooltip::CHeroTooltip(Point pos, const InfoAboutHero &hero): + CArmyTooltip(pos, hero) +{ + init(hero); +} + +CHeroTooltip::CHeroTooltip(Point pos, const CGHeroInstance * hero): + CArmyTooltip(pos, InfoAboutHero(hero, true)) +{ + init(InfoAboutHero(hero, true)); +} + +void CTownTooltip::init(const InfoAboutTown &town) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + //order of icons in def: fort, citadel, castle, no fort + size_t fortIndex = town.fortLevel ? town.fortLevel - 1 : 3; + + new CAnimImage("ITMCLS", fortIndex, 0, 105, 31); + + assert(town.tType); + + size_t iconIndex = town.tType->clientInfo.icons[town.fortLevel > 0][town.built >= CGI->modh->settings.MAX_BUILDING_PER_TURN]; + + new CAnimImage("itpt", iconIndex, 0, 3, 2); + + if(town.details) + { + new CAnimImage("ITMTLS", town.details->hallLevel, 0, 67, 31); + + if (town.details->goldIncome) + new CLabel(157, 58, FONT_TINY, CENTER, Colors::WHITE, + boost::lexical_cast(town.details->goldIncome)); + + if(town.details->garrisonedHero) //garrisoned hero icon + new CPicture("TOWNQKGH", 149, 76); + + if(town.details->customRes)//silo is built + { + if (town.tType->primaryRes == Res::WOOD_AND_ORE )// wood & ore + { + new CAnimImage("SMALRES", Res::WOOD, 0, 7, 75); + new CAnimImage("SMALRES", Res::ORE , 0, 7, 88); + } + else + new CAnimImage("SMALRES", town.tType->primaryRes, 0, 7, 81); + } + } +} + +CTownTooltip::CTownTooltip(Point pos, const InfoAboutTown &town): + CArmyTooltip(pos, town) +{ + init(town); +} + +CTownTooltip::CTownTooltip(Point pos, const CGTownInstance * town): + CArmyTooltip(pos, InfoAboutTown(town, true)) +{ + init(InfoAboutTown(town, true)); +} + + +void MoraleLuckBox::set(const IBonusBearer *node) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + const int textId[] = {62, 88}; //eg %s \n\n\n {Current Luck Modifiers:} + const int noneTxtId = 108; //Russian version uses same text for neutral morale\luck + const int neutralDescr[] = {60, 86}; //eg {Neutral Morale} \n\n Neutral morale means your armies will neither be blessed with extra attacks or freeze in combat. + const int componentType[] = {CComponent::luck, CComponent::morale}; + const int hoverTextBase[] = {7, 4}; + const Bonus::BonusType bonusType[] = {Bonus::LUCK, Bonus::MORALE}; + int (IBonusBearer::*getValue[])() const = {&IBonusBearer::LuckVal, &IBonusBearer::MoraleVal}; + + int mrlt = -9; + TModDescr mrl; + + if (node) + { + node->getModifiersWDescr(mrl, bonusType[morale]); + bonusValue = (node->*getValue[morale])(); + } + else + bonusValue = 0; + + mrlt = (bonusValue>0)-(bonusValue<0); //signum: -1 - bad luck / morale, 0 - neutral, 1 - good + hoverText = CGI->generaltexth->heroscrn[hoverTextBase[morale] - mrlt]; + baseType = componentType[morale]; + text = CGI->generaltexth->arraytxt[textId[morale]]; + boost::algorithm::replace_first(text,"%s",CGI->generaltexth->arraytxt[neutralDescr[morale]-mrlt]); + if (!mrl.size()) + text += CGI->generaltexth->arraytxt[noneTxtId]; + else + { + //it's a creature window + if ((morale && node->hasBonusOfType(Bonus::UNDEAD)) || + node->hasBonusOfType(Bonus::BLOCK_MORALE) || node->hasBonusOfType(Bonus::NON_LIVING)) + { + text += CGI->generaltexth->arraytxt[113]; //unaffected by morale + } + else + { + for(auto & elem : mrl) + { + if (elem.first) //no bonuses with value 0 + text += "\n" + elem.second; + } + } + } + + std::string imageName; + if (small) + imageName = morale ? "IMRL30": "ILCK30"; + else + imageName = morale ? "IMRL42" : "ILCK42"; + + delete image; + image = new CAnimImage(imageName, bonusValue + 3); + image->moveBy(Point(pos.w/2 - image->pos.w/2, pos.h/2 - image->pos.h/2));//center icon +} + +MoraleLuckBox::MoraleLuckBox(bool Morale, const Rect &r, bool Small): + image(nullptr), + morale(Morale), + small(Small) +{ + bonusValue = 0; + pos = r + pos; +} + +CCreaturePic::CCreaturePic(int x, int y, const CCreature *cre, bool Big, bool Animated) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + pos.x+=x; + pos.y+=y; + + TFaction faction = cre->faction; + + assert(CGI->townh->factions.size() > faction); + + if(Big) + bg = new CPicture(CGI->townh->factions[faction]->creatureBg130); + else + bg = new CPicture(CGI->townh->factions[faction]->creatureBg120); + bg->needRefresh = true; + anim = new CCreatureAnim(0, 0, cre->animDefName, Rect()); + anim->clipRect(cre->isDoubleWide()?170:150, 155, bg->pos.w, bg->pos.h); + anim->startPreview(cre->hasBonusOfType(Bonus::SIEGE_WEAPON)); + + pos.w = bg->pos.w; + pos.h = bg->pos.h; +} diff --git a/client/widgets/MiscWidgets.h b/client/widgets/MiscWidgets.h new file mode 100644 index 000000000..65afecd7b --- /dev/null +++ b/client/widgets/MiscWidgets.h @@ -0,0 +1,150 @@ +#pragma once + +#include "../gui/CIntObject.h" + +/* + * MiscWidgets.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 + * + */ + +class CCreatureAnim; +class CComponent; +class CGGarrison; +class CSelectableComponent; +class InfoAboutArmy; +class CArmedInstance; +class IBonusBearer; +class CAnimImage; + +/// Shows a text by moving the mouse cursor over the object +class CHoverableArea: public virtual CIntObject +{ +public: + std::string hoverText; + + virtual void hover (bool on); + + CHoverableArea(); + virtual ~CHoverableArea(); +}; + +/// Can interact on left and right mouse clicks, plus it shows a text when by hovering over it +class LRClickableAreaWText: public CHoverableArea +{ +public: + std::string text; + + LRClickableAreaWText(); + LRClickableAreaWText(const Rect &Pos, const std::string &HoverText = "", const std::string &ClickText = ""); + virtual ~LRClickableAreaWText(); + void init(); + + virtual void clickLeft(tribool down, bool previousState); + virtual void clickRight(tribool down, bool previousState); +}; + +/// base class for hero/town/garrison tooltips +class CArmyTooltip : public CIntObject +{ + void init(const InfoAboutArmy &army); +public: + CArmyTooltip(Point pos, const InfoAboutArmy &army); + CArmyTooltip(Point pos, const CArmedInstance * army); +}; + +/// Class for hero tooltip. Does not have any background! +/// background for infoBox: ADSTATHR +/// background for tooltip: HEROQVBK +class CHeroTooltip : public CArmyTooltip +{ + void init(const InfoAboutHero &hero); +public: + CHeroTooltip(Point pos, const InfoAboutHero &hero); + CHeroTooltip(Point pos, const CGHeroInstance * hero); +}; + +/// Class for town tooltip. Does not have any background! +/// background for infoBox: ADSTATCS +/// background for tooltip: TOWNQVBK +class CTownTooltip : public CArmyTooltip +{ + void init(const InfoAboutTown &town); +public: + CTownTooltip(Point pos, const InfoAboutTown &town); + CTownTooltip(Point pos, const CGTownInstance * town); +}; + +/// draws picture with creature on background, use Animated=true to get animation +class CCreaturePic : public CIntObject +{ +private: + CPicture *bg; + CCreatureAnim *anim; //displayed animation + +public: + CCreaturePic(int x, int y, const CCreature *cre, bool Big=true, bool Animated=true); //c-tor +}; + +/// Resource bar like that at the bottom of the adventure map screen +class CMinorResDataBar : public CIntObject +{ +public: + SDL_Surface *bg; //background bitmap + void show(SDL_Surface * to); + void showAll(SDL_Surface * to); + CMinorResDataBar(); //c-tor + ~CMinorResDataBar(); //d-tor +}; + +/// Opens hero window by left-clicking on it +class CHeroArea: public CIntObject +{ + const CGHeroInstance * hero; +public: + + CHeroArea(int x, int y, const CGHeroInstance * _hero); + + void clickLeft(tribool down, bool previousState); + void clickRight(tribool down, bool previousState); + void hover(bool on); +}; + +/// Can interact on left and right mouse clicks +class LRClickableAreaWTextComp: public LRClickableAreaWText +{ +public: + int baseType; + int bonusValue, type; + virtual void clickLeft(tribool down, bool previousState); + virtual void clickRight(tribool down, bool previousState); + + LRClickableAreaWTextComp(const Rect &Pos = Rect(0,0,0,0), int BaseType = -1); + CComponent * createComponent() const; +}; + +/// Opens town screen by left-clicking on it +class LRClickableAreaOpenTown: public LRClickableAreaWTextComp +{ +public: + const CGTownInstance * town; + void clickLeft(tribool down, bool previousState); + void clickRight(tribool down, bool previousState); + LRClickableAreaOpenTown(); +}; + +class MoraleLuckBox : public LRClickableAreaWTextComp +{ + CAnimImage *image; +public: + bool morale; //true if morale, false if luck + bool small; + + void set(const IBonusBearer *node); + + MoraleLuckBox(bool Morale, const Rect &r, bool Small=false); +}; diff --git a/client/widgets/ObjectLists.cpp b/client/widgets/ObjectLists.cpp new file mode 100644 index 000000000..5c65f0006 --- /dev/null +++ b/client/widgets/ObjectLists.cpp @@ -0,0 +1,234 @@ +#include "StdInc.h" +#include "ObjectLists.h" + +#include "../gui/CGuiHandler.h" +#include "Buttons.h" + +/* + * ObjectLists.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 + * + */ + + +static void intDeleter(CIntObject* object) +{ + delete object; +} + +CObjectList::CObjectList(CreateFunc create, DestroyFunc destroy): +createObject(create), +destroyObject(destroy) +{ + if (!destroyObject) + destroyObject = intDeleter; +} + +void CObjectList::deleteItem(CIntObject* item) +{ + if (!item) + return; + removeChild(item); + destroyObject(item); +} + +CIntObject* CObjectList::createItem(size_t index) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + CIntObject * item = createObject(index); + if (item == nullptr) + item = new CIntObject(); + + item->recActions = defActions; + + addChild(item); + return item; +} + +CTabbedInt::CTabbedInt(CreateFunc create, DestroyFunc destroy, Point position, size_t ActiveID): +CObjectList(create, destroy), +activeTab(nullptr), +activeID(ActiveID) +{ + pos += position; + reset(); +} + +void CTabbedInt::setActive(size_t which) +{ + if (which != activeID) + { + activeID = which; + reset(); + } +} + +void CTabbedInt::reset() +{ + deleteItem(activeTab); + activeTab = createItem(activeID); + activeTab->moveTo(pos.topLeft()); + + if (active) + redraw(); +} + +CIntObject * CTabbedInt::getItem() +{ + return activeTab; +} + +CListBox::CListBox(CreateFunc create, DestroyFunc destroy, Point Pos, Point ItemOffset, size_t VisibleSize, + size_t TotalSize, size_t InitialPos, int Slider, Rect SliderPos): + CObjectList(create, destroy), + first(InitialPos), + totalSize(TotalSize), + itemOffset(ItemOffset), + slider(nullptr) +{ + pos += Pos; + items.resize(VisibleSize, nullptr); + + if (Slider & 1) + { + OBJ_CONSTRUCTION_CAPTURING_ALL; + slider = new CSlider(SliderPos.topLeft(), SliderPos.w, std::bind(&CListBox::moveToPos, this, _1), + VisibleSize, TotalSize, InitialPos, Slider & 2, Slider & 4 ? CSlider::BLUE : CSlider::BROWN); + } + reset(); +} + +// Used to move active items after changing list position +void CListBox::updatePositions() +{ + Point itemPos = pos.topLeft(); + for (auto & elem : items) + { + (elem)->moveTo(itemPos); + itemPos += itemOffset; + } + if (active) + { + redraw(); + if (slider) + slider->moveTo(first); + } +} + +void CListBox::reset() +{ + size_t current = first; + for (auto & elem : items) + { + deleteItem(elem); + elem = createItem(current++); + } + updatePositions(); +} + +void CListBox::resize(size_t newSize) +{ + totalSize = newSize; + if (slider) + slider->setAmount(totalSize); + reset(); +} + +size_t CListBox::size() +{ + return totalSize; +} + +CIntObject * CListBox::getItem(size_t which) +{ + if (which < first || which > first + items.size() || which > totalSize) + return nullptr; + + size_t i=first; + for (auto iter = items.begin(); iter != items.end(); iter++, i++) + if( i == which) + return *iter; + return nullptr; +} + +size_t CListBox::getIndexOf(CIntObject *item) +{ + size_t i=first; + for (auto iter = items.begin(); iter != items.end(); iter++, i++) + if(*iter == item) + return i; + return size_t(-1); +} + +void CListBox::scrollTo(size_t which) +{ + //scroll up + if (first > which) + moveToPos(which); + //scroll down + else if (first + items.size() <= which && which < totalSize) + moveToPos(which - items.size() + 1); +} + +void CListBox::moveToPos(size_t which) +{ + //Calculate new position + size_t maxPossible; + if (totalSize > items.size()) + maxPossible = totalSize - items.size(); + else + maxPossible = 0; + + size_t newPos = std::min(which, maxPossible); + + //If move distance is 1 (most of calls from Slider) - use faster shifts instead of resetting all items + if (first - newPos == 1) + moveToPrev(); + else if (newPos - first == 1) + moveToNext(); + else if (newPos != first) + { + first = newPos; + reset(); + } +} + +void CListBox::moveToNext() +{ + //Remove front item and insert new one to end + if (first + items.size() < totalSize) + { + first++; + deleteItem(items.front()); + items.pop_front(); + items.push_back(createItem(first+items.size())); + updatePositions(); + } +} + +void CListBox::moveToPrev() +{ + //Remove last item and insert new one at start + if (first) + { + first--; + deleteItem(items.back()); + items.pop_back(); + items.push_front(createItem(first)); + updatePositions(); + } +} + +size_t CListBox::getPos() +{ + return first; +} + +const std::list &CListBox::getItems() +{ + return items; +} diff --git a/client/widgets/ObjectLists.h b/client/widgets/ObjectLists.h new file mode 100644 index 000000000..d7ded46b9 --- /dev/null +++ b/client/widgets/ObjectLists.h @@ -0,0 +1,111 @@ +#pragma once + +#include "../gui/CIntObject.h" + +struct SDL_Surface; +struct Rect; +class CAnimImage; +class CSlider; +class CLabel; +class CAnimation; +class CDefHandler; + +/* + * ObjectLists.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 + * + */ + +/// Used as base for Tabs and List classes +class CObjectList : public CIntObject +{ +public: + typedef std::function CreateFunc; + typedef std::function DestroyFunc; + +private: + CreateFunc createObject; + DestroyFunc destroyObject; + +protected: + //Internal methods for safe creation of items (Children capturing and activation/deactivation if needed) + void deleteItem(CIntObject* item); + CIntObject* createItem(size_t index); + + CObjectList(CreateFunc create, DestroyFunc destroy = DestroyFunc());//Protected constructor +}; + +/// Window element with multiple tabs +class CTabbedInt : public CObjectList +{ +private: + CIntObject * activeTab; + size_t activeID; + +public: + //CreateFunc, DestroyFunc - see CObjectList + //Pos - position of object, all tabs will be moved to this position + //ActiveID - ID of initially active tab + CTabbedInt(CreateFunc create, DestroyFunc destroy = DestroyFunc(), Point position=Point(), size_t ActiveID=0); + + void setActive(size_t which); + //recreate active tab + void reset(); + + //return currently active item + CIntObject * getItem(); +}; + +/// List of IntObjects with optional slider +class CListBox : public CObjectList +{ +private: + std::list< CIntObject* > items; + size_t first; + size_t totalSize; + + Point itemOffset; + CSlider * slider; + + void updatePositions(); +public: + //CreateFunc, DestroyFunc - see CObjectList + //Pos - position of first item + //ItemOffset - distance between items in the list + //VisibleSize - maximal number of displayable at once items + //TotalSize + //Slider - slider style, bit field: 1 = present(disabled), 2=horisontal(vertical), 4=blue(brown) + //SliderPos - position of slider, if present + CListBox(CreateFunc create, DestroyFunc destroy, Point Pos, Point ItemOffset, size_t VisibleSize, + size_t TotalSize, size_t InitialPos=0, int Slider=0, Rect SliderPos=Rect() ); + + //recreate all visible items + void reset(); + + //change or get total amount of items in the list + void resize(size_t newSize); + size_t size(); + + //return item with index which or null if not present + CIntObject * getItem(size_t which); + + //return currently active items + const std::list< CIntObject * > & getItems(); + + //get index of this item. -1 if not found + size_t getIndexOf(CIntObject * item); + + //scroll list to make item which visible + void scrollTo(size_t which); + + //scroll list to specified position + void moveToPos(size_t which); + void moveToNext(); + void moveToPrev(); + + size_t getPos(); +}; diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp new file mode 100644 index 000000000..e229e23e4 --- /dev/null +++ b/client/widgets/TextControls.cpp @@ -0,0 +1,642 @@ +#include "StdInc.h" +#include "TextControls.h" + +#include "Buttons.h" +#include "Images.h" + +#include "../CMessage.h" +#include "../gui/CGuiHandler.h" + +#include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff + +/* + * TextControls.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 + * + */ + +std::string CLabel::visibleText() +{ + return text; +} + +void CLabel::showAll(SDL_Surface * to) +{ + CIntObject::showAll(to); + + if(!visibleText().empty()) + blitLine(to, pos, visibleText()); + +} + +CLabel::CLabel(int x, int y, EFonts Font /*= FONT_SMALL*/, EAlignment Align, const SDL_Color &Color /*= Colors::WHITE*/, const std::string &Text /*= ""*/) +:CTextContainer(Align, Font, Color), text(Text) +{ + type |= REDRAW_PARENT; + autoRedraw = true; + pos.x += x; + pos.y += y; + pos.w = pos.h = 0; + bg = nullptr; + + if (alignment == TOPLEFT) // causes issues for MIDDLE + { + pos.w = graphics->fonts[font]->getStringWidth(visibleText().c_str()); + pos.h = graphics->fonts[font]->getLineHeight(); + } +} + +Point CLabel::getBorderSize() +{ + return Point(0, 0); +} + +std::string CLabel::getText() +{ + return text; +} + +void CLabel::setText(const std::string &Txt) +{ + text = Txt; + if(autoRedraw) + { + if(bg || !parent) + redraw(); + else + parent->redraw(); + } +} + +CMultiLineLabel::CMultiLineLabel(Rect position, EFonts Font, EAlignment Align, const SDL_Color &Color, const std::string &Text): + CLabel(position.x, position.y, Font, Align, Color, Text), + visibleSize(0, 0, position.w, position.h) +{ + pos.w = position.w; + pos.h = position.h; + splitText(Text); +} + +void CMultiLineLabel::setVisibleSize(Rect visibleSize) +{ + this->visibleSize = visibleSize; + redraw(); +} + +void CMultiLineLabel::scrollTextBy(int distance) +{ + scrollTextTo(visibleSize.y + distance); +} + +void CMultiLineLabel::scrollTextTo(int distance) +{ + Rect size = visibleSize; + size.y = distance; + setVisibleSize(size); +} + +void CMultiLineLabel::setText(const std::string &Txt) +{ + splitText(Txt); + CLabel::setText(Txt); +} + +void CTextContainer::blitLine(SDL_Surface *to, Rect destRect, std::string what) +{ + const IFont * f = graphics->fonts[font]; + Point where = destRect.topLeft(); + + // input is rect in which given text should be placed + // calculate proper position for top-left corner of the text + if (alignment == TOPLEFT) + { + where.x += getBorderSize().x; + where.y += getBorderSize().y; + } + + if (alignment == CENTER) + { + where.x += (int(destRect.w) - int(f->getStringWidth(what))) / 2; + where.y += (int(destRect.h) - int(f->getLineHeight())) / 2; + } + + if (alignment == BOTTOMRIGHT) + { + where.x += getBorderSize().x + destRect.w - f->getStringWidth(what); + where.y += getBorderSize().y + destRect.h - f->getLineHeight(); + } + + size_t begin = 0; + std::string delimeters = "{}"; + size_t currDelimeter = 0; + + do + { + size_t end = what.find_first_of(delimeters[currDelimeter % 2], begin); + if (begin != end) + { + std::string toPrint = what.substr(begin, end - begin); + + if (currDelimeter % 2) // Enclosed in {} text - set to yellow + f->renderTextLeft(to, toPrint, Colors::YELLOW, where); + else // Non-enclosed text, use default color + f->renderTextLeft(to, toPrint, color, where); + begin = end; + + where.x += f->getStringWidth(toPrint); + } + currDelimeter++; + } + while (begin++ != std::string::npos); +} + +CTextContainer::CTextContainer(EAlignment alignment, EFonts font, SDL_Color color): + alignment(alignment), + font(font), + color(color) +{} + +void CMultiLineLabel::showAll(SDL_Surface * to) +{ + CIntObject::showAll(to); + + const IFont * f = graphics->fonts[font]; + + // calculate which lines should be visible + int totalLines = lines.size(); + int beginLine = visibleSize.y; + int endLine = getTextLocation().h + visibleSize.y; + + if (beginLine < 0) + beginLine = 0; + else + beginLine /= f->getLineHeight(); + + if (endLine < 0) + endLine = 0; + else + endLine /= f->getLineHeight(); + endLine++; + + // and where they should be displayed + Point lineStart = getTextLocation().topLeft() - visibleSize + Point(0, beginLine * f->getLineHeight()); + Point lineSize = Point(getTextLocation().w, f->getLineHeight()); + + CSDL_Ext::CClipRectGuard guard(to, getTextLocation()); // to properly trim text that is too big to fit + + for (int i = beginLine; i < std::min(totalLines, endLine); i++) + { + if (!lines[i].empty()) //non-empty line + blitLine(to, Rect(lineStart, lineSize), lines[i]); + + lineStart.y += f->getLineHeight(); + } +} + +void CMultiLineLabel::splitText(const std::string &Txt) +{ + lines.clear(); + + const IFont * f = graphics->fonts[font]; + int lineHeight = f->getLineHeight(); + + lines = CMessage::breakText(Txt, pos.w, font); + + textSize.y = lineHeight * lines.size(); + textSize.x = 0; + for(const std::string &line : lines) + vstd::amax( textSize.x, f->getStringWidth(line.c_str())); + redraw(); +} + +Rect CMultiLineLabel::getTextLocation() +{ + // this method is needed for vertical alignment alignment of text + // when height of available text is smaller than height of widget + // in this case - we should add proper offset to display text at required position + if (pos.h <= textSize.y) + return pos; + + Point textSize(pos.w, graphics->fonts[font]->getLineHeight() * lines.size()); + Point textOffset(pos.w - textSize.x, pos.h - textSize.y); + + switch(alignment) + { + case TOPLEFT: return Rect(pos.topLeft(), textSize); + case CENTER: return Rect(pos.topLeft() + textOffset / 2, textSize); + case BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSize); + } + assert(0); + return Rect(); +} + +CLabelGroup::CLabelGroup(EFonts Font, EAlignment Align, const SDL_Color &Color): + font(Font), align(Align), color(Color) +{} + +void CLabelGroup::add(int x, int y, const std::string &text) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + new CLabel(x, y, font, align, color, text); +} + +CTextBox::CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font /*= FONT_SMALL*/, EAlignment Align /*= TOPLEFT*/, const SDL_Color &Color /*= Colors::WHITE*/): + sliderStyle(SliderStyle), + slider(nullptr) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + label = new CMultiLineLabel(rect, Font, Align, Color); + + type |= REDRAW_PARENT; + pos.x += rect.x; + pos.y += rect.y; + pos.h = rect.h; + pos.w = rect.w; + + assert(pos.w >= 40); //we need some space + setText(Text); +} + +void CTextBox::sliderMoved(int to) +{ + label->scrollTextTo(to); +} + +void CTextBox::resize(Point newSize) +{ + pos.w = newSize.x; + pos.h = newSize.y; + label->pos.w = pos.w; + label->pos.h = pos.h; + if (slider) + vstd::clear_pointer(slider); // will be recreated if needed later + + setText(label->getText()); // force refresh +} + +void CTextBox::setText(const std::string &text) +{ + label->setText(text); + if (label->textSize.y <= label->pos.h && slider) + { + // slider is no longer needed + vstd::clear_pointer(slider); + label->pos.w = pos.w; + label->setText(text); + } + else if (label->textSize.y > label->pos.h && !slider) + { + // create slider and update widget + label->pos.w = pos.w - 32; + label->setText(text); + + OBJ_CONSTRUCTION_CAPTURING_ALL; + slider = new CSlider(Point(pos.w - 32, 0), pos.h, std::bind(&CTextBox::sliderMoved, this, _1), + label->pos.h, label->textSize.y, 0, false, CSlider::EStyle(sliderStyle)); + slider->setScrollStep(graphics->fonts[label->font]->getLineHeight()); + } +} + +void CGStatusBar::setText(const std::string & Text) +{ + if(!textLock) + CLabel::setText(Text); +} + +void CGStatusBar::clear() +{ + setText(""); +} + +CGStatusBar::CGStatusBar(CPicture *BG, EFonts Font /*= FONT_SMALL*/, EAlignment Align /*= CENTER*/, const SDL_Color &Color /*= Colors::WHITE*/) +: CLabel(BG->pos.x, BG->pos.y, Font, Align, Color, "") +{ + init(); + bg = BG; + addChild(bg); + pos = bg->pos; + getBorderSize(); + textLock = false; +} + +CGStatusBar::CGStatusBar(int x, int y, std::string name/*="ADROLLVR.bmp"*/, int maxw/*=-1*/) +: CLabel(x, y, FONT_SMALL, CENTER) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + init(); + bg = new CPicture(name); + pos = bg->pos; + if((unsigned int)maxw < pos.w) + { + vstd::amin(pos.w, maxw); + bg->srcRect = new Rect(0, 0, maxw, pos.h); + } + textLock = false; +} + +CGStatusBar::~CGStatusBar() +{ + GH.statusbar = oldStatusBar; +} + +void CGStatusBar::show(SDL_Surface * to) +{ + showAll(to); +} + +void CGStatusBar::init() +{ + oldStatusBar = GH.statusbar; + GH.statusbar = this; +} + +Point CGStatusBar::getBorderSize() +{ + //Width of borders where text should not be printed + static const Point borderSize(5,1); + + switch(alignment) + { + case TOPLEFT: return Point(borderSize.x, borderSize.y); + case CENTER: return Point(pos.w/2, pos.h/2); + case BOTTOMRIGHT: return Point(pos.w - borderSize.x, pos.h - borderSize.y); + } + assert(0); + return Point(); +} + +void CGStatusBar::lock(bool shouldLock) +{ + textLock = shouldLock; +} + +CTextInput::CTextInput(const Rect &Pos, EFonts font, const CFunctionList &CB): + CLabel(Pos.x, Pos.y, font, CENTER), + cb(CB) +{ + type |= REDRAW_PARENT; + focus = false; + pos.h = Pos.h; + pos.w = Pos.w; + captureAllKeys = true; + bg = nullptr; + addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT); + giveFocus(); +} + +CTextInput::CTextInput( const Rect &Pos, const Point &bgOffset, const std::string &bgName, const CFunctionList &CB ) +:cb(CB) +{ + focus = false; + pos += Pos; + captureAllKeys = true; + OBJ_CONSTRUCTION; + bg = new CPicture(bgName, bgOffset.x, bgOffset.y); + addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT); + giveFocus(); +} + +CTextInput::CTextInput(const Rect &Pos, SDL_Surface *srf) +{ + focus = false; + pos += Pos; + captureAllKeys = true; + OBJ_CONSTRUCTION; + bg = new CPicture(Pos, 0, true); + Rect hlp = Pos; + if(srf) + CSDL_Ext::blitSurface(srf, &hlp, *bg, nullptr); + else + SDL_FillRect(*bg, nullptr, 0); + pos.w = bg->pos.w; + pos.h = bg->pos.h; + bg->pos = pos; + addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT); + giveFocus(); +} + +void CTextInput::focusGot() +{ + CSDL_Ext::startTextInput(&pos); +} + +void CTextInput::focusLost() +{ + CSDL_Ext::stopTextInput(); +} + + +std::string CTextInput::visibleText() +{ + return focus ? text + newText + "_" : text; +} + +void CTextInput::clickLeft( tribool down, bool previousState ) +{ + if(down && !focus) + giveFocus(); +} + +void CTextInput::keyPressed( const SDL_KeyboardEvent & key ) +{ + + if(!focus || key.state != SDL_PRESSED) + return; + + if(key.keysym.sym == SDLK_TAB) + { + moveFocus(); + GH.breakEventHandling(); + return; + } + + bool redrawNeeded = false; + #ifdef VCMI_SDL1 + std::string oldText = text; + #endif // 0 + switch(key.keysym.sym) + { + case SDLK_DELETE: // have index > ' ' so it won't be filtered out by default section + return; + case SDLK_BACKSPACE: + if(!newText.empty()) + { + Unicode::trimRight(newText); + redrawNeeded = true; + } + else if(!text.empty()) + { + Unicode::trimRight(text); + redrawNeeded = true; + } + break; + default: + #ifdef VCMI_SDL1 + if (key.keysym.unicode < ' ') + return; + else + { + text += key.keysym.unicode; //TODO 16-/>8 + redrawNeeded = true; + } + #endif // 0 + break; + } + #ifdef VCMI_SDL1 + filters(text, oldText); + #endif // 0 + if (redrawNeeded) + { + redraw(); + cb(text); + } +} + +void CTextInput::setText( const std::string &nText, bool callCb ) +{ + CLabel::setText(nText); + if(callCb) + cb(text); +} + +bool CTextInput::captureThisEvent(const SDL_KeyboardEvent & key) +{ + if(key.keysym.sym == SDLK_RETURN || key.keysym.sym == SDLK_KP_ENTER) + return false; + + #ifdef VCMI_SDL1 + //this should allow all non-printable keys to go through (for example arrows) + if (key.keysym.unicode < ' ') + return false; + + return true; + #else + return false; + #endif +} + +#ifndef VCMI_SDL1 +void CTextInput::textInputed(const SDL_TextInputEvent & event) +{ + if(!focus) + return; + std::string oldText = text; + + text += event.text; + + filters(text,oldText); + if (text != oldText) + { + redraw(); + cb(text); + } + newText = ""; +} + +void CTextInput::textEdited(const SDL_TextEditingEvent & event) +{ + if(!focus) + return; + + newText = event.text; + redraw(); + cb(text+newText); +} + +#endif + + +void CTextInput::filenameFilter(std::string & text, const std::string &) +{ + static const std::string forbiddenChars = "<>:\"/\\|?*\r\n"; //if we are entering a filename, some special characters won't be allowed + size_t pos; + while ((pos = text.find_first_of(forbiddenChars)) != std::string::npos) + text.erase(pos, 1); +} + +void CTextInput::numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue) +{ + assert(minValue < maxValue); + + if (text.empty()) + text = "0"; + + size_t pos = 0; + if (text[0] == '-') //allow '-' sign as first symbol only + pos++; + + while (pos < text.size()) + { + if (text[pos] < '0' || text[pos] > '9') + { + text = oldText; + return; //new text is not number. + } + pos++; + } + try + { + int value = boost::lexical_cast(text); + if (value < minValue) + text = boost::lexical_cast(minValue); + else if (value > maxValue) + text = boost::lexical_cast(maxValue); + } + catch(boost::bad_lexical_cast &) + { + //Should never happen. Unless I missed some cases + logGlobal->warnStream() << "Warning: failed to convert "<< text << " to number!"; + text = oldText; + } +} + +CFocusable::CFocusable() +{ + focusables.push_back(this); +} + +CFocusable::~CFocusable() +{ + if(inputWithFocus == this) + { + focusLost(); + inputWithFocus = nullptr; + } + + focusables -= this; +} +void CFocusable::giveFocus() +{ + if(inputWithFocus) + { + inputWithFocus->focus = false; + inputWithFocus->focusLost(); + inputWithFocus->redraw(); + } + + focus = true; + inputWithFocus = this; + focusGot(); + redraw(); +} + +void CFocusable::moveFocus() +{ + auto i = vstd::find(focusables, this), + ourIt = i; + for(i++; i != ourIt; i++) + { + if(i == focusables.end()) + i = focusables.begin(); + + if((*i)->active) + { + (*i)->giveFocus(); + break;; + } + } +} diff --git a/client/widgets/TextControls.h b/client/widgets/TextControls.h new file mode 100644 index 000000000..0039a0688 --- /dev/null +++ b/client/widgets/TextControls.h @@ -0,0 +1,189 @@ +#pragma once + +#include "../gui/CIntObject.h" +#include "../gui/SDL_Extensions.h" +#include "../../lib/FunctionList.h" + +/* + * TextControls.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 + * + */ + +class CSlider; + +/// Base class for all text-related widgets. +/// Controls text blitting-related options +class CTextContainer : public virtual CIntObject +{ +protected: + /// returns size of border, for left- or right-aligned text + virtual Point getBorderSize() = 0; + /// do actual blitting of line. Text "what" will be placed at "where" and aligned according to alignment + void blitLine(SDL_Surface * to, Rect where, std::string what); + + CTextContainer(EAlignment alignment, EFonts font, SDL_Color color); + +public: + EAlignment alignment; + EFonts font; + SDL_Color color; // default font color. Can be overridden by placing "{}" into the string +}; + +/// Label which shows text +class CLabel : public CTextContainer +{ +protected: + Point getBorderSize() override; + virtual std::string visibleText(); + + CPicture *bg; +public: + + std::string text; + bool autoRedraw; //whether control will redraw itself on setTxt + + std::string getText(); + virtual void setText(const std::string &Txt); + + CLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, + const SDL_Color &Color = Colors::WHITE, const std::string &Text = ""); + void showAll(SDL_Surface * to); //shows statusbar (with current text) +}; + +/// Small helper class to manage group of similar labels +class CLabelGroup : public CIntObject +{ + std::list labels; + EFonts font; + EAlignment align; + SDL_Color color; +public: + CLabelGroup(EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE); + void add(int x=0, int y=0, const std::string &text = ""); +}; + +/// Multi-line label that can display multiple lines of text +/// If text is too big to fit into requested area remaining part will not be visible +class CMultiLineLabel : public CLabel +{ + // text to blit, split into lines that are no longer than widget width + std::vector lines; + + // area of text that actually will be printed, default is widget size + Rect visibleSize; + + void splitText(const std::string &Txt); + Rect getTextLocation(); +public: + // total size of text, x = longest line of text, y = total height of lines + Point textSize; + + CMultiLineLabel(Rect position, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE, const std::string &Text = ""); + + void setText(const std::string &Txt); + void showAll(SDL_Surface * to); + + void setVisibleSize(Rect visibleSize); + // scrolls text visible in widget. Positive value will move text up + void scrollTextTo(int distance); + void scrollTextBy(int distance); +}; + +/// a multi-line label that tries to fit text with given available width and height; +/// if not possible, it creates a slider for scrolling text +class CTextBox : public CIntObject +{ + int sliderStyle; +public: + CMultiLineLabel * label; + CSlider *slider; + + CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE); + + void resize(Point newSize); + void setText(const std::string &Txt); + void sliderMoved(int to); +}; + +/// Status bar which is shown at the bottom of the in-game screens +class CGStatusBar : public CLabel +{ + bool textLock; //Used for blocking changes to the text + void init(); + + CGStatusBar *oldStatusBar; +protected: + Point getBorderSize() override; + +public: + + void clear();//clears statusbar and refreshes + void setText(const std::string & Text) override; //prints text and refreshes statusbar + + void show(SDL_Surface * to); //shows statusbar (with current text) + + CGStatusBar(CPicture *BG, EFonts Font = FONT_SMALL, EAlignment Align = CENTER, const SDL_Color &Color = Colors::WHITE); //given CPicture will be captured by created sbar and it's pos will be used as pos for sbar + CGStatusBar(int x, int y, std::string name, int maxw=-1); + ~CGStatusBar(); + + void lock(bool shouldLock); //If true, current text cannot be changed until lock(false) is called +}; + +/// UIElement which can get input focus +class CFocusable : public virtual CIntObject +{ +protected: + virtual void focusGot(){}; + virtual void focusLost(){}; +public: + bool focus; //only one focusable control can have focus at one moment + + void giveFocus(); //captures focus + void moveFocus(); //moves focus to next active control (may be used for tab switching) + + static std::list focusables; //all existing objs + static CFocusable *inputWithFocus; //who has focus now + CFocusable(); + ~CFocusable(); +}; + +/// Text input box where players can enter text +class CTextInput : public CLabel, public CFocusable +{ + std::string newText; +protected: + std::string visibleText() override; + + void focusGot() override; + void focusLost() override; +public: + CFunctionList cb; + CFunctionList filters; + void setText(const std::string &nText, bool callCb = false); + + CTextInput(const Rect &Pos, EFonts font, const CFunctionList &CB); + CTextInput(const Rect &Pos, const Point &bgOffset, const std::string &bgName, const CFunctionList &CB); + CTextInput(const Rect &Pos, SDL_Surface *srf = nullptr); + + void clickLeft(tribool down, bool previousState) override; + void keyPressed(const SDL_KeyboardEvent & key) override; + bool captureThisEvent(const SDL_KeyboardEvent & key) override; + +#ifndef VCMI_SDL1 + void textInputed(const SDL_TextInputEvent & event) override; + void textEdited(const SDL_TextEditingEvent & event) override; + + +#endif // VCMI_SDL1 + + //Filter that will block all characters not allowed in filenames + static void filenameFilter(std::string &text, const std::string & oldText); + //Filter that will allow only input of numbers in range min-max (min-max are allowed) + //min-max should be set via something like std::bind + static void numberFilter(std::string &text, const std::string & oldText, int minValue, int maxValue); +}; diff --git a/client/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp similarity index 85% rename from client/CAdvmapInterface.cpp rename to client/windows/CAdvmapInterface.cpp index d4fe34aba..46b32a183 100644 --- a/client/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1,36 +1,43 @@ #include "StdInc.h" #include "CAdvmapInterface.h" -#include "../CCallback.h" #include "CCastleInterface.h" -#include "gui/CCursorHandler.h" -#include "CGameInfo.h" #include "CHeroWindow.h" #include "CKingdomInterface.h" -#include "CMessage.h" -#include "CPlayerInterface.h" -#include "gui/SDL_Extensions.h" -#include "CBitmapHandler.h" -#include "../lib/CConfigHandler.h" #include "CSpellWindow.h" -#include "Graphics.h" -#include "CDefHandler.h" -#include "../lib/CGeneralTextHandler.h" -#include "../lib/CHeroHandler.h" -#include "../lib/mapObjects/CGHeroInstance.h" -#include "../lib/CTownHandler.h" -#include "../lib/mapping/CMap.h" -#include "../lib/JsonNode.h" -#include "mapHandler.h" -#include "CPreGame.h" -#include "../lib/VCMI_Lib.h" -#include "../lib/CSpellHandler.h" -#include "../lib/CSoundBase.h" -#include "../lib/CGameState.h" -#include "CMusicHandler.h" -#include "gui/CGuiHandler.h" -#include "gui/CIntObjectClasses.h" -#include "../lib/UnlockGuard.h" +#include "GUIClasses.h" +#include "CTradeWindow.h" + +#include "../CBitmapHandler.h" +#include "../CDefHandler.h" +#include "../CGameInfo.h" +#include "../CMessage.h" +#include "../CMusicHandler.h" +#include "../CPlayerInterface.h" +#include "../CPreGame.h" +#include "../Graphics.h" +#include "../mapHandler.h" + +#include "../gui/CCursorHandler.h" +#include "../gui/CGuiHandler.h" +#include "../gui/SDL_Extensions.h" +#include "../widgets/MiscWidgets.h" +#include "../windows/InfoWindows.h" + +#include "../../CCallback.h" + +#include "../../lib/CConfigHandler.h" +#include "../../lib/CGameState.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CHeroHandler.h" +#include "../../lib/CSoundBase.h" +#include "../../lib/CSpellHandler.h" +#include "../../lib/CTownHandler.h" +#include "../../lib/JsonNode.h" +#include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/mapping/CMap.h" +#include "../../lib/UnlockGuard.h" +#include "../../lib/VCMI_Lib.h" #ifdef _MSC_VER #pragma warning (disable : 4355) @@ -363,36 +370,6 @@ void CResDataBar::showAll(SDL_Surface * to) CAdvMapInt::CAdvMapInt(): minimap(Rect(ADVOPT.minimapX, ADVOPT.minimapY, ADVOPT.minimapW, ADVOPT.minimapH)), statusbar(ADVOPT.statusbarX,ADVOPT.statusbarY,ADVOPT.statusbarG), -kingOverview(CGI->generaltexth->zelp[293].first,CGI->generaltexth->zelp[293].second, - std::bind(&CAdvMapInt::fshowOverview,this),&ADVOPT.kingOverview, SDLK_k), - -underground(CGI->generaltexth->zelp[294].first,CGI->generaltexth->zelp[294].second, - std::bind(&CAdvMapInt::fswitchLevel,this),&ADVOPT.underground, SDLK_u), - -questlog(CGI->generaltexth->zelp[295].first,CGI->generaltexth->zelp[295].second, - std::bind(&CAdvMapInt::fshowQuestlog,this),&ADVOPT.questlog, SDLK_q), - -sleepWake(CGI->generaltexth->zelp[296].first,CGI->generaltexth->zelp[296].second, - std::bind(&CAdvMapInt::fsleepWake,this), &ADVOPT.sleepWake, SDLK_w), - -moveHero(CGI->generaltexth->zelp[297].first,CGI->generaltexth->zelp[297].second, - std::bind(&CAdvMapInt::fmoveHero,this), &ADVOPT.moveHero, SDLK_m), - -spellbook(CGI->generaltexth->zelp[298].first,CGI->generaltexth->zelp[298].second, - std::bind(&CAdvMapInt::fshowSpellbok,this), &ADVOPT.spellbook, SDLK_c), - -advOptions(CGI->generaltexth->zelp[299].first,CGI->generaltexth->zelp[299].second, - std::bind(&CAdvMapInt::fadventureOPtions,this), &ADVOPT.advOptions, SDLK_a), - -sysOptions(CGI->generaltexth->zelp[300].first,CGI->generaltexth->zelp[300].second, - std::bind(&CAdvMapInt::fsystemOptions,this), &ADVOPT.sysOptions, SDLK_o), - -nextHero(CGI->generaltexth->zelp[301].first,CGI->generaltexth->zelp[301].second, - std::bind(&CAdvMapInt::fnextHero,this), &ADVOPT.nextHero, SDLK_h), - -endTurn(CGI->generaltexth->zelp[302].first,CGI->generaltexth->zelp[302].second, - std::bind(&CAdvMapInt::fendTurn,this), &ADVOPT.endTurn, SDLK_e), - heroList(ADVOPT.hlistSize, Point(ADVOPT.hlistX, ADVOPT.hlistY), ADVOPT.hlistAU, ADVOPT.hlistAD), townList(ADVOPT.tlistSize, Point(ADVOPT.tlistX, ADVOPT.tlistY), ADVOPT.tlistAU, ADVOPT.tlistAD), infoBar(Rect(ADVOPT.infoboxX, ADVOPT.infoboxY, 192, 192) ) @@ -420,9 +397,28 @@ infoBar(Rect(ADVOPT.infoboxX, ADVOPT.infoboxY, 192, 192) ) gems.push_back(CDefHandler::giveDef(ADVOPT.gemG[g])); } + auto makeButton = [&] (int textID, std::function callback, config::ButtonInfo info, int key) -> CButton * + { + auto button = new CButton(Point(info.x, info.y), info.defName, CGI->generaltexth->zelp[textID], callback, key, info.playerColoured); + + for (auto image : info.additionalDefs) + button->addImage(image); + return button; + }; + + kingOverview = makeButton(293, std::bind(&CAdvMapInt::fshowOverview,this), ADVOPT.kingOverview, SDLK_k); + underground = makeButton(294, std::bind(&CAdvMapInt::fswitchLevel,this), ADVOPT.underground, SDLK_u); + questlog = makeButton(295, std::bind(&CAdvMapInt::fshowQuestlog,this), ADVOPT.questlog, SDLK_q); + sleepWake = makeButton(296, std::bind(&CAdvMapInt::fsleepWake,this), ADVOPT.sleepWake, SDLK_w); + moveHero = makeButton(297, std::bind(&CAdvMapInt::fmoveHero,this), ADVOPT.moveHero, SDLK_m); + spellbook = makeButton(298, std::bind(&CAdvMapInt::fshowSpellbok,this), ADVOPT.spellbook, SDLK_c); + advOptions = makeButton(299, std::bind(&CAdvMapInt::fadventureOPtions,this), ADVOPT.advOptions, SDLK_a); + sysOptions = makeButton(300, std::bind(&CAdvMapInt::fsystemOptions,this), ADVOPT.sysOptions, SDLK_o); + nextHero = makeButton(301, std::bind(&CAdvMapInt::fnextHero,this), ADVOPT.nextHero, SDLK_h); + endTurn = makeButton(302, std::bind(&CAdvMapInt::fendTurn,this), ADVOPT.endTurn, SDLK_e); setPlayer(LOCPLINT->playerID); - underground.block(!CGI->mh->map->twoLevel); + underground->block(!CGI->mh->map->twoLevel); addUsedEvents(MOVE); } @@ -445,14 +441,14 @@ void CAdvMapInt::fswitchLevel() if (position.z) { position.z--; - underground.setIndex(0,true); - underground.showAll(screenBuf); + underground->setIndex(0,true); + underground->showAll(screenBuf); } else { - underground.setIndex(1,true); + underground->setIndex(1,true); position.z++; - underground.showAll(screenBuf); + underground->showAll(screenBuf); } updateScreen = true; minimap.setLevel(position.z); @@ -537,14 +533,13 @@ void CAdvMapInt::fendTurn() void CAdvMapInt::updateSleepWake(const CGHeroInstance *h) { - sleepWake.block(!h); + sleepWake->block(!h); if (!h) return; bool state = isHeroSleeping(h); - sleepWake.setIndex(state ? 1 : 0, true); - sleepWake.assignedKeys.clear(); - sleepWake.assignedKeys.insert(state ? SDLK_w : SDLK_z); - sleepWake.update(); + sleepWake->setIndex(state ? 1 : 0, true); + sleepWake->assignedKeys.clear(); + sleepWake->assignedKeys.insert(state ? SDLK_w : SDLK_z); } void CAdvMapInt::updateMoveHero(const CGHeroInstance *h, tribool hasPath) @@ -554,10 +549,10 @@ void CAdvMapInt::updateMoveHero(const CGHeroInstance *h, tribool hasPath) hasPath = LOCPLINT->paths[h].nodes.size() ? true : false; if (!h) { - moveHero.block(true); + moveHero->block(true); return; } - moveHero.block(!hasPath || (h->movement == 0)); + moveHero->block(!hasPath || (h->movement == 0)); } int CAdvMapInt::getNextHeroIndex(int startIndex) @@ -587,12 +582,12 @@ void CAdvMapInt::updateNextHero(const CGHeroInstance *h) int next = getNextHeroIndex(start); if (next < 0) { - nextHero.block(true); + nextHero->block(true); return; } const CGHeroInstance *nextH = LOCPLINT->wanderingHeroes[next]; bool noActiveHeroes = (next == start) && ((nextH->movement == 0) || isHeroSleeping(nextH)); - nextHero.block(noActiveHeroes); + nextHero->block(noActiveHeroes); } void CAdvMapInt::activate() @@ -605,16 +600,16 @@ void CAdvMapInt::activate() GH.statusbar = &statusbar; if(!duringAITurn) { - kingOverview.activate(); - underground.activate(); - questlog.activate(); - sleepWake.activate(); - moveHero.activate(); - spellbook.activate(); - sysOptions.activate(); - advOptions.activate(); - nextHero.activate(); - endTurn.activate(); + kingOverview->activate(); + underground->activate(); + questlog->activate(); + sleepWake->activate(); + moveHero->activate(); + spellbook->activate(); + sysOptions->activate(); + advOptions->activate(); + nextHero->activate(); + endTurn->activate(); minimap.activate(); heroList.activate(); @@ -635,16 +630,16 @@ void CAdvMapInt::deactivate() scrollingDir = 0; CCS->curh->changeGraphic(ECursor::ADVENTURE,0); - kingOverview.deactivate(); - underground.deactivate(); - questlog.deactivate(); - sleepWake.deactivate(); - moveHero.deactivate(); - spellbook.deactivate(); - advOptions.deactivate(); - sysOptions.deactivate(); - nextHero.deactivate(); - endTurn.deactivate(); + kingOverview->deactivate(); + underground->deactivate(); + questlog->deactivate(); + sleepWake->deactivate(); + moveHero->deactivate(); + spellbook->deactivate(); + advOptions->deactivate(); + sysOptions->deactivate(); + nextHero->deactivate(); + endTurn->deactivate(); minimap.deactivate(); heroList.deactivate(); townList.deactivate(); @@ -661,16 +656,16 @@ void CAdvMapInt::showAll(SDL_Surface * to) if(state != INGAME) return; - kingOverview.showAll(to); - underground.showAll(to); - questlog.showAll(to); - sleepWake.showAll(to); - moveHero.showAll(to); - spellbook.showAll(to); - advOptions.showAll(to); - sysOptions.showAll(to); - nextHero.showAll(to); - endTurn.showAll(to); + kingOverview->showAll(to); + underground->showAll(to); + questlog->showAll(to); + sleepWake->showAll(to); + moveHero->showAll(to); + spellbook->showAll(to); + advOptions->showAll(to); + sysOptions->showAll(to); + nextHero->showAll(to); + endTurn->showAll(to); minimap.showAll(to); heroList.showAll(to); @@ -781,8 +776,8 @@ void CAdvMapInt::centerOn(int3 on) position = on; updateScreen=true; - underground.setIndex(on.z,true); //change underground switch button image - underground.redraw(); + underground->setIndex(on.z,true); //change underground switch button image + underground->redraw(); if (switchedLevels) minimap.setLevel(position.z); } @@ -1096,16 +1091,16 @@ void CAdvMapInt::setPlayer(PlayerColor Player) player = Player; graphics->blueToPlayersAdv(bg,player); - kingOverview.setPlayerColor(player); - underground.setPlayerColor(player); - questlog.setPlayerColor(player); - sleepWake.setPlayerColor(player); - moveHero.setPlayerColor(player); - spellbook.setPlayerColor(player); - sysOptions.setPlayerColor(player); - advOptions.setPlayerColor(player); - nextHero.setPlayerColor(player); - endTurn.setPlayerColor(player); + kingOverview->setPlayerColor(player); + underground->setPlayerColor(player); + questlog->setPlayerColor(player); + sleepWake->setPlayerColor(player); + moveHero->setPlayerColor(player); + spellbook->setPlayerColor(player); + sysOptions->setPlayerColor(player); + advOptions->setPlayerColor(player); + nextHero->setPlayerColor(player); + endTurn->setPlayerColor(player); graphics->blueToPlayersAdv(resdatabar.bg,player); //heroList.updateHList(); @@ -1525,23 +1520,24 @@ void CAdvMapInt::adjustActiveness(bool aiTurnStart) } CAdventureOptions::CAdventureOptions(): - CWindowObject(PLAYER_COLORED, "ADVOPTS") + CWindowObject(PLAYER_COLORED, "ADVOPTS") { OBJ_CONSTRUCTION_CAPTURING_ALL; - exit = new CAdventureMapButton("","",std::bind(&CAdventureOptions::close, this), 204, 313, "IOK6432.DEF",SDLK_RETURN); + exit = new CButton(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&CAdventureOptions::close, this), SDLK_RETURN); exit->assignedKeys.insert(SDLK_ESCAPE); - scenInfo = new CAdventureMapButton("","", std::bind(&CAdventureOptions::close, this), 24, 198, "ADVINFO.DEF",SDLK_i); - scenInfo->callback += CAdventureOptions::showScenarioInfo; - //viewWorld = new CAdventureMapButton("","",std::bind(&CGuiHandler::popIntTotally, &GH, this), 204, 313, "IOK6432.DEF",SDLK_RETURN); + scenInfo = new CButton(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&]{ close(); }, SDLK_i); + scenInfo->addCallback(CAdventureOptions::showScenarioInfo); - puzzle = new CAdventureMapButton("","", std::bind(&CAdventureOptions::close, this), 24, 81, "ADVPUZ.DEF"); - puzzle->callback += std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT); + //viewWorld = new CButton("","",std::bind(&CGuiHandler::popIntTotally, &GH, this), 204, 313, "IOK6432.DEF",SDLK_RETURN); - dig = new CAdventureMapButton("","", std::bind(&CAdventureOptions::close, this), 24, 139, "ADVDIG.DEF"); + puzzle = new CButton(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&]{ close(); }, SDLK_p); + puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT)); + + dig = new CButton(Point(24, 139), "ADVDIG.DEF", CButton::tooltip(), [&]{ close(); }, SDLK_d); if(const CGHeroInstance *h = adventureInt->curHero()) - dig->callback += std::bind(&CPlayerInterface::tryDiggging, LOCPLINT, h); + dig->addCallback(std::bind(&CPlayerInterface::tryDiggging, LOCPLINT, h)); else dig->block(true); } diff --git a/client/CAdvmapInterface.h b/client/windows/CAdvmapInterface.h similarity index 87% rename from client/CAdvmapInterface.h rename to client/windows/CAdvmapInterface.h index cf7f785a9..6a77061b9 100644 --- a/client/CAdvmapInterface.h +++ b/client/windows/CAdvmapInterface.h @@ -1,11 +1,10 @@ #pragma once -#include +#include "../widgets/AdventureMapClasses.h" +#include "CWindowObject.h" -#include "SDL.h" -#include "gui/CIntObjectClasses.h" -#include "GUIClasses.h" -#include "AdventureMapClasses.h" +#include "../widgets/TextControls.h" +#include "../widgets/Buttons.h" class CDefHandler; class CCallback; @@ -33,7 +32,7 @@ class IShipyard; class CAdventureOptions : public CWindowObject { public: - CAdventureMapButton *exit, *viewWorld, *puzzle, *dig, *scenInfo, *replay; + CButton *exit, *viewWorld, *puzzle, *dig, *scenInfo, *replay; CAdventureOptions(); static void showScenarioInfo(); @@ -111,16 +110,16 @@ public: CMinimap minimap; CGStatusBar statusbar; - CAdventureMapButton kingOverview,//- kingdom overview - underground,//- underground switch - questlog,//- questlog - sleepWake, //- sleep/wake hero - moveHero, //- move hero - spellbook,//- spellbook - advOptions, //- adventure options - sysOptions,//- system options - nextHero, //- next hero - endTurn;//- end turn + CButton * kingOverview; + CButton * underground; + CButton * questlog; + CButton * sleepWake; + CButton * moveHero; + CButton * spellbook; + CButton * advOptions; + CButton * sysOptions; + CButton * nextHero; + CButton * endTurn; CTerrainRect terrain; //visible terrain CResDataBar resdatabar; diff --git a/client/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp similarity index 92% rename from client/CCastleInterface.cpp rename to client/windows/CCastleInterface.cpp index 0a386b232..38702805a 100644 --- a/client/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1,29 +1,36 @@ #include "StdInc.h" #include "CCastleInterface.h" -#include "../CCallback.h" -#include "../lib/CArtHandler.h" -#include "../lib/CBuildingHandler.h" -#include "../lib/CCreatureHandler.h" -#include "../lib/CGeneralTextHandler.h" -#include "../lib/CModHandler.h" -#include "../lib/mapObjects/CGHeroInstance.h" -#include "../lib/CSpellHandler.h" -#include "../lib/CTownHandler.h" #include "CAdvmapInterface.h" -#include "CAnimation.h" -#include "CBitmapHandler.h" -#include "CDefHandler.h" -#include "CGameInfo.h" #include "CHeroWindow.h" -#include "CMessage.h" -#include "CMusicHandler.h" -#include "CPlayerInterface.h" -#include "Graphics.h" -#include "gui/SDL_Extensions.h" -#include "../lib/GameConstants.h" -#include "gui/CGuiHandler.h" -#include "gui/CIntObjectClasses.h" +#include "CTradeWindow.h" +#include "GUIClasses.h" + +#include "../CBitmapHandler.h" +#include "../CDefHandler.h" +#include "../CGameInfo.h" +#include "../CMessage.h" +#include "../CMusicHandler.h" +#include "../CPlayerInterface.h" +#include "../Graphics.h" + +#include "../gui/CGuiHandler.h" +#include "../gui/SDL_Extensions.h" +#include "../windows/InfoWindows.h" +#include "../widgets/MiscWidgets.h" +#include "../widgets/CComponent.h" + +#include "../../CCallback.h" +#include "../../lib/CArtHandler.h" +#include "../../lib/CBuildingHandler.h" +#include "../../lib/CCreatureHandler.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CModHandler.h" +#include "../../lib/CSpellHandler.h" +#include "../../lib/CTownHandler.h" +#include "../../lib/GameConstants.h" +#include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/mapObjects/CGTownInstance.h" using namespace boost::assign; @@ -836,7 +843,7 @@ void CCastleBuildings::enterTownHall() else { LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[673]); - (dynamic_cast(GH.topInt()))->buttons[0]->callback += std::bind(&CCastleBuildings::openTownHall, this); + dynamic_cast(GH.topInt())->buttons[0]->addCallback(std::bind(&CCastleBuildings::openTownHall, this)); } } else @@ -881,12 +888,12 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst income = new CLabel(195, 443, FONT_SMALL, CENTER); icon = new CAnimImage("ITPT", 0, 0, 15, 387); - exit = new CAdventureMapButton(CGI->generaltexth->tcommands[8], "", std::bind(&CCastleInterface::close,this), 744, 544, "TSBTNS", SDLK_RETURN); + exit = new CButton(Point(744, 544), "TSBTNS", CButton::tooltip(CGI->generaltexth->tcommands[8]), [&]{close();}, SDLK_RETURN); exit->assignedKeys.insert(SDLK_ESCAPE); - exit->setOffset(4); + exit->setImageOrder(4, 5, 6, 7); - split = new CAdventureMapButton(CGI->generaltexth->tcommands[3], "", std::bind(&CGarrisonInt::splitClick,garr), 744, 382, "TSBTNS.DEF"); - split->callback += std::bind(&HeroSlots::splitClicked, heroes); + split = new CButton(Point(744, 382), "TSBTNS.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3]), [&]{garr->splitClick();}); + split->addCallback(std::bind(&HeroSlots::splitClicked, heroes)); garr->addSplitBtn(split); Rect barRect(9, 182, 732, 18); @@ -1304,8 +1311,7 @@ CHallInterface::CHallInterface(const CGTownInstance *Town): statusBar = new CGStatusBar(new CPicture(*background, barRect, 5, 556, false)); title = new CLabel(399, 12, FONT_MEDIUM, CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->Name()); - exit = new CAdventureMapButton(CGI->generaltexth->hcommands[8], "", - std::bind(&CHallInterface::close,this), 748, 556, "TPMAGE1.DEF", SDLK_RETURN); + exit = new CButton(Point(748, 556), "TPMAGE1.DEF", CButton::tooltip(CGI->generaltexth->hcommands[8]), [&]{close();}, SDLK_RETURN); exit->assignedKeys.insert(SDLK_ESCAPE); auto & boxList = town->town->clientInfo.hallSlots; @@ -1403,15 +1409,14 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin if(!rightClick) { //normal window - buy = new CAdventureMapButton(boost::str(boost::format(CGI->generaltexth->allTexts[595]) % building->Name()), - "", std::bind(&CBuildWindow::buyFunc,this), 45, 446,"IBUY30", SDLK_RETURN); - buy->borderColor = Colors::METALLIC_GOLD; - buy->borderEnabled = true; + std::string tooltipYes = boost::str(boost::format(CGI->generaltexth->allTexts[595]) % building->Name()); + std::string tooltipNo = boost::str(boost::format(CGI->generaltexth->allTexts[596]) % building->Name()); - cancel = new CAdventureMapButton(boost::str(boost::format(CGI->generaltexth->allTexts[596]) % building->Name()), - "", std::bind(&CBuildWindow::close,this), 290, 445, "ICANCEL", SDLK_ESCAPE); + buy = new CButton(Point(45, 446), "IBUY30", CButton::tooltip(tooltipYes), [&]{ buyFunc(); }, SDLK_RETURN); + buy->borderColor = Colors::METALLIC_GOLD; + + cancel = new CButton(Point(290, 445), "ICANCEL", CButton::tooltip(tooltipNo), [&] { close();}, SDLK_ESCAPE); cancel->borderColor = Colors::METALLIC_GOLD; - cancel->borderEnabled = true; buy->block(state!=7 || LOCPLINT->playerID != town->tempOwner); } } @@ -1441,7 +1446,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town): title = new CLabel(400, 12, FONT_BIG, CENTER, Colors::WHITE, fortBuilding->Name()); std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % fortBuilding->Name()); - exit = new CAdventureMapButton(text, "", std::bind(&CFortScreen::close,this) ,748, 556, "TPMAGE1", SDLK_RETURN); + exit = new CButton(Point(748, 556), "TPMAGE1", CButton::tooltip(text), [&]{ close(); }, SDLK_RETURN); exit->assignedKeys.insert(SDLK_ESCAPE); std::vector positions; @@ -1640,7 +1645,6 @@ void CFortScreen::RecruitArea::clickRight(tribool down, bool previousState) CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner,std::string imagem) :CWindowObject(BORDERED,imagem) { - OBJ_CONSTRUCTION_CAPTURING_ALL; window = new CPicture(owner->town->town->clientInfo.guildWindow , 332, 76); @@ -1651,7 +1655,7 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner,std::string imagem) Rect barRect(7, 556, 737, 18); statusBar = new CGStatusBar(new CPicture(*background, barRect, 7, 556, false)); - exit = new CAdventureMapButton(CGI->generaltexth->allTexts[593],"",std::bind(&CMageGuildScreen::close,this), 748, 556,"TPMAGE1.DEF",SDLK_RETURN); + exit = new CButton(Point(748, 556), "TPMAGE1.DEF", CButton::tooltip(CGI->generaltexth->allTexts[593]), [&]{ close(); }, SDLK_RETURN); exit->assignedKeys.insert(SDLK_ESCAPE); std::vector > positions; @@ -1728,13 +1732,13 @@ CBlacksmithDialog::CBlacksmithDialog(bool possible, CreatureID creMachineID, Art boost::lexical_cast(CGI->arth->artifacts[aid]->price)); std::string text = boost::str(boost::format(CGI->generaltexth->allTexts[595]) % creature->nameSing); - buy = new CAdventureMapButton(text,"",std::bind(&CBlacksmithDialog::close, this), 42, 312,"IBUY30.DEF",SDLK_RETURN); + buy = new CButton(Point(42, 312), "IBUY30.DEF", CButton::tooltip(text), [&]{ close(); }, SDLK_RETURN); text = boost::str(boost::format(CGI->generaltexth->allTexts[596]) % creature->nameSing); - cancel = new CAdventureMapButton(text,"",std::bind(&CBlacksmithDialog::close, this), 224, 312,"ICANCEL.DEF",SDLK_ESCAPE); + cancel = new CButton(Point(224, 312), "ICANCEL.DEF", CButton::tooltip(text), [&]{ close(); }, SDLK_ESCAPE); if(possible) - buy->callback += [=]{ LOCPLINT->cb->buyArtifact(LOCPLINT->cb->getHero(hid),aid); }; + buy->addCallback([=]{ LOCPLINT->cb->buyArtifact(LOCPLINT->cb->getHero(hid),aid); }); else buy->block(true); diff --git a/client/CCastleInterface.h b/client/windows/CCastleInterface.h similarity index 93% rename from client/CCastleInterface.h rename to client/windows/CCastleInterface.h index b9b25ca0f..b8e9775c5 100644 --- a/client/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -1,10 +1,9 @@ #pragma once +#include "../widgets/CGarrisonInt.h" +#include "../widgets/Images.h" -#include "CAnimation.h" -#include "GUIClasses.h" - -class CAdventureMapButton; +class CButton; class CBuilding; class CCastleBuildings; class CCreaturePic; @@ -204,8 +203,8 @@ class CCastleInterface : public CWindowObject, public CWindowWithGarrison CTownInfo *hall, *fort; - CAdventureMapButton *exit; - CAdventureMapButton *split; + CButton *exit; + CButton *split; std::vector creainfo;//small icons of creatures (bottom-left corner); @@ -261,7 +260,7 @@ class CHallInterface : public CWindowObject CLabel *title; CGStatusBar *statusBar; CMinorResDataBar * resdatabar; - CAdventureMapButton *exit; + CButton *exit; public: CHallInterface(const CGTownInstance * Town); //c-tor @@ -273,8 +272,8 @@ class CBuildWindow: public CWindowObject const CGTownInstance *town; const CBuilding *building; - CAdventureMapButton *buy; - CAdventureMapButton *cancel; + CButton *buy; + CButton *cancel; std::string getTextForState(int state); void buyFunc(); @@ -329,7 +328,7 @@ class CFortScreen : public CWindowObject std::vector recAreas; CMinorResDataBar * resdatabar; CGStatusBar *statusBar; - CAdventureMapButton *exit; + CButton *exit; std::string getBgName(const CGTownInstance *town); @@ -354,7 +353,7 @@ class CMageGuildScreen : public CWindowObject void hover(bool on); }; CPicture *window; - CAdventureMapButton *exit; + CButton *exit; std::vector spells; CMinorResDataBar * resdatabar; CGStatusBar *statusBar; @@ -366,7 +365,7 @@ public: /// The blacksmith window where you can buy available in town war machine class CBlacksmithDialog : public CWindowObject { - CAdventureMapButton *buy, *cancel; + CButton *buy, *cancel; CPicture *animBG; CCreatureAnim * anim; CLabel * title; diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp new file mode 100644 index 000000000..890ccde7d --- /dev/null +++ b/client/windows/CCreatureWindow.cpp @@ -0,0 +1,866 @@ +#include "StdInc.h" +#include "CCreatureWindow.h" + +#include "../CGameInfo.h" +#include "../CPlayerInterface.h" +#include "../widgets/Buttons.h" +#include "../widgets/CComponent.h" +#include "../widgets/Images.h" +#include "../widgets/TextControls.h" +#include "../widgets/ObjectLists.h" +#include "../gui/CGuiHandler.h" + +#include "../../CCallback.h" +#include "../../lib/BattleState.h" +#include "../../lib/CBonusTypeHandler.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CModHandler.h" +#include "../../lib/CHeroHandler.h" +#include "../../lib/CSpellHandler.h" +#include "../../lib/CGameState.h" + +using namespace CSDL_Ext; + +class CCreatureArtifactInstance; +class CSelectableSkill; + +/* + * CCreatureWindow.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 + * + */ + +struct StackWindowInfo +{ + // helper structs + struct CommanderLevelInfo + { + std::vector skills; + std::function callback; + }; + struct StackDismissInfo + { + std::function callback; + }; + struct StackUpgradeInfo + { + UpgradeInfo info; + std::function callback; + }; + + // pointers to permament objects in game state + const CCreature * creature; + const CCommanderInstance * commander; + const CStackInstance * stackNode; + const CGHeroInstance * owner; + + // temporary objects which should be kept as copy if needed + boost::optional levelupInfo; + boost::optional dismissInfo; + boost::optional upgradeInfo; + + // misc fields + unsigned int creatureCount; + bool popupWindow; + + StackWindowInfo(); +}; + +namespace +{ + namespace EStat + { + enum EStat + { + ATTACK, + DEFENCE, + SHOTS, + DAMAGE, + HEALTH, + HEALTH_LEFT, + SPEED, + MANA + }; + } +} + +StackWindowInfo::StackWindowInfo(): + creature(nullptr), + commander(nullptr), + stackNode(nullptr), + owner(nullptr), + creatureCount(0), + popupWindow(false) +{ +} + +void CStackWindow::CWindowSection::createBackground(std::string path) +{ + background = new CPicture("stackWindow/" + path); + pos = background->pos; +} + +void CStackWindow::CWindowSection::printStatString(int index, std::string name, std::string value) +{ + new CLabel(145, 32 + index*19, FONT_SMALL, TOPLEFT, Colors::WHITE, name); + new CLabel(307, 48 + index*19, FONT_SMALL, BOTTOMRIGHT, Colors::WHITE, value); +} + +void CStackWindow::CWindowSection::printStatRange(int index, std::string name, int min, int max) +{ + if(min != max) + printStatString(index, name, boost::str(boost::format("%d - %d") % min % max)); + else + printStatString(index, name, boost::str(boost::format("%d") % min)); +} + +void CStackWindow::CWindowSection::printStatBase(int index, std::string name, int base, int current) +{ + if(base != current) + printStatString(index, name, boost::str(boost::format("%d (%d)") % base % current)); + else + printStatString(index, name, boost::str(boost::format("%d") % base)); +} + +void CStackWindow::CWindowSection::printStat(int index, std::string name, int value) +{ + printStatBase(index, name, value, value); +} + +std::string CStackWindow::generateStackExpDescription() +{ + const CStackInstance * stack = info->stackNode; + const CCreature * creature = info->creature; + + int tier = stack->type->level; + int rank = stack->getExpRank(); + if (!vstd::iswithin(tier, 1, 7)) + tier = 0; + int number; + std::string expText = CGI->generaltexth->zcrexp[325]; + boost::replace_first (expText, "%s", creature->namePl); + boost::replace_first (expText, "%s", CGI->generaltexth->zcrexp[rank]); + boost::replace_first (expText, "%i", boost::lexical_cast(rank)); + boost::replace_first (expText, "%i", boost::lexical_cast(stack->experience)); + number = CGI->creh->expRanks[tier][rank] - stack->experience; + boost::replace_first (expText, "%i", boost::lexical_cast(number)); + + number = CGI->creh->maxExpPerBattle[tier]; //percent + boost::replace_first (expText, "%i%", boost::lexical_cast(number)); + number *= CGI->creh->expRanks[tier].back() / 100; //actual amount + boost::replace_first (expText, "%i", boost::lexical_cast(number)); + + boost::replace_first (expText, "%i", boost::lexical_cast(stack->count)); //Number of Creatures in stack + + int expmin = std::max(CGI->creh->expRanks[tier][std::max(rank-1, 0)], (ui32)1); + number = (stack->count * (stack->experience - expmin)) / expmin; //Maximum New Recruits without losing current Rank + boost::replace_first (expText, "%i", boost::lexical_cast(number)); //TODO + + boost::replace_first (expText, "%.2f", boost::lexical_cast(1)); //TODO Experience Multiplier + number = CGI->creh->expAfterUpgrade; + boost::replace_first (expText, "%.2f", boost::lexical_cast(number) + "%"); //Upgrade Multiplier + + expmin = CGI->creh->expRanks[tier][9]; + int expmax = CGI->creh->expRanks[tier][10]; + number = expmax - expmin; + boost::replace_first (expText, "%i", boost::lexical_cast(number)); //Experience after Rank 10 + number = (stack->count * (expmax - expmin)) / expmin; + boost::replace_first (expText, "%i", boost::lexical_cast(number)); //Maximum New Recruits to remain at Rank 10 if at Maximum Experience + + return expText; +} + +void CStackWindow::removeStackArtifact(ArtifactPosition pos) +{ + auto art = info->stackNode->getArt(ArtifactPosition::CREATURE_SLOT); + LOCPLINT->cb->swapArtifacts(ArtifactLocation(info->stackNode, pos), + ArtifactLocation(info->owner, art->firstBackpackSlot(info->owner))); + delete stackArtifactButton; + delete stackArtifactHelp; + delete stackArtifactIcon; +} + +void CStackWindow::setStackArtifact(const CArtifactInstance * art, Point artPos) +{ + if (art) + { + stackArtifactIcon = new CAnimImage("ARTIFACT", art->artType->iconIndex, 0, pos.x, pos.y); + stackArtifactHelp = new LRClickableAreaWTextComp(Rect(artPos, Point(44, 44)), CComponent::artifact); + stackArtifactHelp->type = art->artType->id; + + const JsonNode & text = VLC->generaltexth->localizedTexts["creatureWindow"]["returnArtifact"]; + + if (info->owner) + { + stackArtifactButton = new CButton(Point(artPos.x - 2 , artPos.y + 46), "stackWindow/cancelButton", + CButton::tooltip(text), + [=]{ removeStackArtifact(ArtifactPosition::CREATURE_SLOT); }); + } + } + +} + +void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + if (showExp && showArt) + createBackground("info-panel-2"); + else if (showExp || showArt) + createBackground("info-panel-1"); + else + createBackground("info-panel-0"); + + new CCreaturePic(5, 41, parent->info->creature); + + std::string visibleName; + if (parent->info->commander != nullptr) + visibleName = parent->info->commander->type->nameSing; + else + visibleName = parent->info->creature->namePl; + new CLabel(215, 12, FONT_SMALL, CENTER, Colors::YELLOW, visibleName); + + int dmgMultiply = 1; + if(parent->info->owner && parent->info->stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON)) + dmgMultiply += parent->info->owner->Attack(); + + new CPicture("stackWindow/icons", 117, 32); + printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), parent->info->stackNode->Attack()); + printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE), parent->info->stackNode->Defense()); + printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, parent->info->stackNode->getMaxDamage() * dmgMultiply); + printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->valOfBonuses(Bonus::STACK_HEALTH), parent->info->stackNode->valOfBonuses(Bonus::STACK_HEALTH)); + printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), parent->info->stackNode->Speed()); + + const CStack * battleStack = dynamic_cast(parent->info->stackNode); + bool shooter = parent->info->stackNode->hasBonusOfType(Bonus::SHOOTER) && parent->info->stackNode->valOfBonuses(Bonus::SHOTS); + bool caster = parent->info->stackNode->valOfBonuses(Bonus::CASTS); + + if (battleStack != nullptr) // in battle + { + if (shooter) + printStatBase(EStat::SHOTS, CGI->generaltexth->allTexts[198], battleStack->valOfBonuses(Bonus::SHOTS), battleStack->shots); + if (caster) + printStatBase(EStat::MANA, CGI->generaltexth->allTexts[399], battleStack->valOfBonuses(Bonus::CASTS), battleStack->casts); + printStat(EStat::HEALTH_LEFT, CGI->generaltexth->allTexts[200], battleStack->firstHPleft); + } + else + { + if (shooter) + printStat(EStat::SHOTS, CGI->generaltexth->allTexts[198], parent->info->stackNode->valOfBonuses(Bonus::SHOTS)); + if (caster) + printStat(EStat::MANA, CGI->generaltexth->allTexts[399], parent->info->stackNode->valOfBonuses(Bonus::CASTS)); + } + + auto morale = new MoraleLuckBox(true, genRect(42, 42, 321, 110)); + morale->set(parent->info->stackNode); + auto luck = new MoraleLuckBox(false, genRect(42, 42, 375, 110)); + luck->set(parent->info->stackNode); + + if (showArt) + { + Point pos = showExp ? Point(375, 32) : Point(347, 32); + parent->setStackArtifact(parent->info->stackNode->getArt(ArtifactPosition::CREATURE_SLOT), pos); + } + + if (showExp) + { + const CStackInstance * stack = parent->info->stackNode; + Point pos = showArt ? Point(321, 32) : Point(347, 32); + if (parent->info->commander) + { + const CCommanderInstance * commander = parent->info->commander; + new CAnimImage("PSKIL42", 4, 0, pos.x, pos.y); // experience icon + + auto expArea = new LRClickableAreaWTextComp(Rect(pos.x, pos.y, 44, 44), CComponent::experience); + expArea->text = CGI->generaltexth->allTexts[2]; + expArea->bonusValue = commander->getExpRank(); + boost::replace_first(expArea->text, "%d", boost::lexical_cast(commander->getExpRank())); + boost::replace_first(expArea->text, "%d", boost::lexical_cast(CGI->heroh->reqExp(commander->getExpRank()+1))); + boost::replace_first(expArea->text, "%d", boost::lexical_cast(commander->experience)); + } + else + { + new CAnimImage("stackWindow/levels", stack->getExpRank(), 0, pos.x, pos.y); + auto expArea = new LRClickableAreaWText(Rect(pos.x, pos.y, 44, 44)); + expArea->text = parent->generateStackExpDescription(); + } + new CLabel(pos.x + 21, pos.y + 52, FONT_SMALL, CENTER, Colors::WHITE, makeNumberShort(stack->experience, 6)); + } +} + +void CStackWindow::CWindowSection::createActiveSpells() +{ + static const Point firstPos(7 ,4); // position of 1st spell box + static const Point offset(54, 0); // offset of each spell box from previous + + OBJ_CONSTRUCTION_CAPTURING_ALL; + createBackground("spell-effects"); + + const CStack * battleStack = dynamic_cast(parent->info->stackNode); + + assert(battleStack); // Section should be created only for battles + + //spell effects + int printed=0; //how many effect pics have been printed + std::vector spells = battleStack->activeSpells(); + for(si32 effect : spells) + { + std::string spellText; + if (effect < 77) //not all effects have graphics (for eg. Acid Breath) + { + spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds." + boost::replace_first (spellText, "%s", CGI->spellh->objects[effect]->name); + int duration = battleStack->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain; + boost::replace_first (spellText, "%d", boost::lexical_cast(duration)); + + new CAnimImage("SpellInt", effect + 1, 0, firstPos.x + offset.x * printed, firstPos.x + offset.y * printed); + new LRClickableAreaWText(Rect(firstPos + offset * printed, Point(50, 38)), spellText, spellText); + if (++printed >= 8) // interface limit reached + break; + } + } +} + +void CStackWindow::CWindowSection::createCommanderSection() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + auto onCreate = [=](size_t index) -> CIntObject * + { + return parent->switchTab(index); + }; + auto onDestroy = [=](CIntObject * obj) + { + delete obj; + }; + parent->commanderTab = new CTabbedInt(onCreate, onDestroy, Point(0,0), 0); + pos.w = parent->pos.w; + pos.h = 177; // height of commander info + if (parent->info->levelupInfo) + pos.h += 59; // height of abilities selection +} + +static std::string skillToFile (int skill, int level, bool selected) +{ + // FIXME: is this a correct hadling? + // level 0 = skill not present, use image with "no" suffix + // level 1-5 = skill available, mapped to images indexed as 0-4 + // selecting skill means that it will appear one level higher (as if alredy upgraded) + std::string file = "zvs/Lib1.res/_"; + switch (skill) + { + case ECommander::ATTACK: + file += "AT"; + break; + case ECommander::DEFENSE: + file += "DF"; + break; + case ECommander::HEALTH: + file += "HP"; + break; + case ECommander::DAMAGE: + file += "DM"; + break; + case ECommander::SPEED: + file += "SP"; + break; + case ECommander::SPELL_POWER: + file += "MP"; + break; + } + std::string sufix; + if (selected) + level++; // UI will display resulting level + if (level == 0) + sufix = "no"; //not avaliable - no number + else + sufix = boost::lexical_cast(level-1); + if (selected) + sufix += "="; //level-up highlight + + return file + sufix + ".bmp"; +} + +void CStackWindow::CWindowSection::createCommander() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + createBackground("commander-bg"); + + auto getSkillPos = [&](int index) + { + return Point(10 + 80 * (index%3), 20 + 80 * (index/3)); + }; + + auto getSkillImage = [this](int skillIndex) -> std::string + { + bool selected = ((parent->selectedSkill == skillIndex) && parent->info->levelupInfo ); + return skillToFile(skillIndex, parent->info->commander->secondarySkills[skillIndex], selected); + }; + + for (int index = ECommander::ATTACK; index <= ECommander::SPELL_POWER; ++index) + { + Point skillPos = getSkillPos(index); + + auto icon = new CClickableObject(new CPicture(getSkillImage(index), skillPos.x, skillPos.y), [=]{}); + + if (parent->selectedSkill == index) + parent->selectedIcon = icon; + + if (parent->info->levelupInfo && vstd::contains(parent->info->levelupInfo->skills, index)) // can be upgraded - enable selection switch + { + icon->callback = [=] + { + parent->setSelection(index, icon); + }; + } + } + //TODO: commander artifacts +} + +CIntObject * CStackWindow::createSkillEntry(int index) +{ + for (auto skillID : info->levelupInfo->skills) + { + if (index == 0 && skillID >= 100) + { + const Bonus *bonus = CGI->creh->skillRequirements[skillID-100].first; + const CStackInstance *stack = info->commander; + CClickableObject * icon = new CClickableObject(new CPicture(stack->bonusToGraphics(bonus)), []{}); + icon->callback = [=] + { + setSelection(skillID, icon); + }; + icon->text = stack->bonusToString(bonus, true); + icon->hoverText = stack->bonusToString(bonus, false); + return icon; + } + if (skillID >= 100) + index--; + } + return nullptr; +} + +void CStackWindow::CWindowSection::createCommanderAbilities() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + auto bg2 = new CPicture("stackWindow/commander-abilities.png"); + bg2->moveBy(Point(0, pos.h)); + size_t abilitiesCount = boost::range::count_if(parent->info->levelupInfo->skills, [](ui32 skillID) + { + return skillID >= 100; + }); + + auto list = new CListBox([=] (int index) + { + return parent->createSkillEntry(index); + }, + [=] (CIntObject * elem) + { + delete elem; + }, + Point(38, 3+pos.h), Point(63, 0), 6, abilitiesCount); + + auto leftBtn = new CButton(Point(10, pos.h + 6), "hsbtns3.def", CButton::tooltip(), [=]{ list->moveToPrev(); }, SDLK_LEFT); + auto rightBtn = new CButton(Point(411, pos.h + 6), "hsbtns5.def", CButton::tooltip(), [=]{ list->moveToNext(); }, SDLK_RIGHT); + + if (abilitiesCount <= 6) + { + leftBtn->block(true); + rightBtn->block(true); + } +} + +void CStackWindow::setSelection(si32 newSkill, CClickableObject * newIcon) +{ + auto getSkillImage = [this](int skillIndex) -> std::string + { + bool selected = ((selectedSkill == skillIndex) && info->levelupInfo ); + return skillToFile(skillIndex, info->commander->secondarySkills[skillIndex], selected); + }; + + OBJ_CONSTRUCTION_CAPTURING_ALL; + int oldSelection = selectedSkill; // update selection + selectedSkill = newSkill; + + if (selectedIcon && oldSelection < 100) // recreate image on old selection, only for skills + selectedIcon->setObject(new CPicture(getSkillImage(oldSelection))); + + selectedIcon = newIcon; // update new selection + if (newSkill < 100) + newIcon->setObject(new CPicture(getSkillImage(newSkill))); +} + +void CStackWindow::CWindowSection::createBonuses(boost::optional preferredSize) +{ + // size of single image for an item + static const int itemHeight = 59; + OBJ_CONSTRUCTION_CAPTURING_ALL; + size_t totalSize = (parent->activeBonuses.size() + 1) / 2; + size_t visibleSize = preferredSize ? preferredSize.get() : std::min(3, totalSize); + + pos.w = parent->pos.w; + pos.h = itemHeight * visibleSize; + + auto onCreate = [=](size_t index) -> CIntObject * + { + return parent->createBonusEntry(index); + }; + auto onDestroy = [=](CIntObject * obj) + { + delete obj; + }; + new CListBox(onCreate, onDestroy, Point(0, 0), Point(0, itemHeight), visibleSize, totalSize, 0, 1, Rect(pos.w - 15, 0, pos.h, pos.h)); +} + +void CStackWindow::CWindowSection::createButtonPanel() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + createBackground("button-panel"); + + if (parent->info->dismissInfo) + { + auto onDismiss = [=]() + { + parent->info->dismissInfo->callback(); + parent->close(); + }; + auto onClick = [=] () + { + LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[12], onDismiss, 0, false, std::vector()); + }; + new CButton(Point(5, 5),"IVIEWCR2.DEF", CGI->generaltexth->zelp[445], onClick, SDLK_d); + } + if (parent->info->upgradeInfo) + { + // used space overlaps with commander switch button + // besides - should commander really be upgradeable? + assert(!parent->info->commander); + + StackWindowInfo::StackUpgradeInfo & upgradeInfo = parent->info->upgradeInfo.get(); + size_t buttonsToCreate = std::min(upgradeInfo.info.newID.size(), 3); // no more than 3 windows on UI - space limit + + for (size_t i=0; iinfo->creatureCount; + + auto onUpgrade = [=]() + { + upgradeInfo.callback(upgradeInfo.info.newID[i]); + parent->close(); + }; + auto onClick = [=]() + { + std::vector resComps; + for(TResources::nziterator i(totalCost); i.valid(); i++) + { + resComps.push_back(new CComponent(CComponent::resource, i->resType, i->resVal)); + } + LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[207], onUpgrade, nullptr, true, resComps); + }; + auto upgradeBtn = new CButton(Point(221 + i * 40, 5), "stackWindow/upgradeButton", CGI->generaltexth->zelp[446], onClick, SDLK_1); + + upgradeBtn->addOverlay(new CAnimImage("CPRSMALL", VLC->creh->creatures[upgradeInfo.info.newID[i]]->iconIndex)); + + if (!LOCPLINT->cb->getResourceAmount().canAfford(totalCost)) + upgradeBtn->block(true); + } + } + + if (parent->info->commander) + { + for (size_t i=0; i<2; i++) + { + std::string btnIDs[2] = { "showSkills", "showBonuses" }; + auto onSwitch = [&, i]() + { + parent->switchButtons[parent->activeTab]->enable(); + parent->commanderTab->setActive(i); + parent->switchButtons[i]->disable(); + parent->redraw(); // FIXME: enable/disable don't redraw screen themselves + }; + + const JsonNode & text = VLC->generaltexth->localizedTexts["creatureWindow"][btnIDs[i]]; + parent->switchButtons[i] = new CButton(Point(302 + i*40, 5), "stackWindow/upgradeButton", CButton::tooltip(text), onSwitch); + parent->switchButtons[i]->addOverlay(new CAnimImage("stackWindow/switchModeIcons", i)); + } + parent->switchButtons[parent->activeTab]->disable(); + } + + auto exitBtn = new CButton(Point(382, 5), "hsbtns.def", CGI->generaltexth->zelp[445], [=]{ parent->close(); }, SDLK_RETURN); + exitBtn->assignedKeys.insert(SDLK_ESCAPE); +} + +CStackWindow::CWindowSection::CWindowSection(CStackWindow * parent): + parent(parent) +{ +} + +CClickableObject::CClickableObject(CIntObject *object, std::function callback): + object(nullptr), + callback(callback) +{ + pos = object->pos; + setObject(object); +} + +void CClickableObject::setObject(CIntObject *newObject) +{ + delete object; + object = newObject; + addChild(object); + object->moveTo(pos.topLeft()); + redraw(); +} + +void CClickableObject::clickLeft(tribool down, bool previousState) +{ + if (down) + callback(); +} + +CIntObject * CStackWindow::createBonusEntry(size_t index) +{ + auto section = new CWindowSection(this); + section->createBonusEntry(index); + return section; +} + +void CStackWindow::CWindowSection::createBonusEntry(size_t index) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + createBackground("bonus-effects"); + createBonusItem(index * 2, Point(6, 4)); + createBonusItem(index * 2 + 1, Point(214, 4)); +} + +void CStackWindow::CWindowSection::createBonusItem(size_t index, Point position) +{ + if (parent->activeBonuses.size() > index) + { + BonusInfo & bi = parent->activeBonuses[index]; + new CPicture(bi.imagePath, position.x, position.y); + new CLabel(position.x + 60, position.y + 2, FONT_SMALL, TOPLEFT, Colors::WHITE, bi.name); + new CLabel(position.x + 60, position.y + 25, FONT_SMALL, TOPLEFT, Colors::WHITE, bi.description); + } +} + +CIntObject * CStackWindow::switchTab(size_t index) +{ + switch (index) + { + case 0: + { + activeTab = 0; + auto ret = new CWindowSection(this); + ret->createCommander(); + if (info->levelupInfo) + ret->createCommanderAbilities(); + return ret; + } + case 1: + { + activeTab = 1; + auto ret = new CWindowSection(this); + if (info->levelupInfo) + ret->createBonuses(4); + else + ret->createBonuses(3); + return ret; + } + default: + { + return nullptr; + } + } +} + +void CStackWindow::initSections() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + CWindowSection * currentSection; + + bool showArt = CGI->modh->modules.STACK_ARTIFACT && info->commander == nullptr && info->stackNode; + bool showExp = (CGI->modh->modules.STACK_EXP || info->commander != nullptr) && info->stackNode; + currentSection = new CWindowSection(this); + currentSection->createStackInfo(showExp, showArt); + pos.w = currentSection->pos.w; + pos.h += currentSection->pos.h; + + if (dynamic_cast(info->stackNode)) // in battle + { + currentSection = new CWindowSection(this); + currentSection->pos.y += pos.h; + currentSection->createActiveSpells(); + pos.h += currentSection->pos.h; + } + if (info->commander) + { + currentSection = new CWindowSection(this); + currentSection->pos.y += pos.h; + currentSection->createCommanderSection(); + pos.h += currentSection->pos.h; + } + if (!info->commander && !activeBonuses.empty()) + { + currentSection = new CWindowSection(this); + currentSection->pos.y += pos.h; + currentSection->createBonuses(); + pos.h += currentSection->pos.h; + } + + if (!info->popupWindow) + { + currentSection = new CWindowSection(this); + currentSection->pos.y += pos.h; + currentSection->createButtonPanel(); + pos.h += currentSection->pos.h; + //FIXME: add status bar to image? + } + updateShadow(); + pos = center(pos); +} + +void CStackWindow::initBonusesList() +{ + BonusList output, input; + input = *(info->stackNode->getBonuses(Selector::durationType(Bonus::PERMANENT).And(Selector::anyRange()))); + + while (!input.empty()) + { + Bonus * b = input.front(); + + output.push_back(new Bonus(*b)); + output.back()->val = input.valOfBonuses(Selector::typeSubtype(b->type, b->subtype)); //merge multiple bonuses into one + input.remove_if (Selector::typeSubtype(b->type, b->subtype)); //remove used bonuses + } + + BonusInfo bonusInfo; + for(Bonus* b : output) + { + bonusInfo.name = info->stackNode->bonusToString(b, false); + bonusInfo.imagePath = info->stackNode->bonusToGraphics(b); + + //if it's possible to give any description or image for this kind of bonus + //TODO: figure out why half of bonuses don't have proper description + if (!bonusInfo.name.empty() || !bonusInfo.imagePath.empty()) + activeBonuses.push_back(bonusInfo); + } + + //handle Magic resistance separately :/ + int magicResistance = info->stackNode->magicResistance(); + + if (magicResistance) + { + BonusInfo bonusInfo; + Bonus b; + b.type = Bonus::MAGIC_RESISTANCE; + + bonusInfo.name = VLC->getBth()->bonusToString(&b, info->stackNode, false); + bonusInfo.description = VLC->getBth()->bonusToString(&b, info->stackNode, true); + bonusInfo.imagePath = info->stackNode->bonusToGraphics(&b); + activeBonuses.push_back(bonusInfo); + } +} + +void CStackWindow::init() +{ + if (!info->stackNode) + info->stackNode = new CStackInstance(info->creature, 1);// FIXME: free data + + stackArtifactHelp = nullptr; + stackArtifactIcon = nullptr; + stackArtifactButton = nullptr; + + selectedIcon = nullptr; + selectedSkill = 0; + if (info->levelupInfo) + selectedSkill = info->levelupInfo->skills.front(); + + commanderTab = nullptr; + activeTab = 0; + + initBonusesList(); + initSections(); +} + +CStackWindow::CStackWindow(const CStack * stack, bool popup): + CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)), + info(new StackWindowInfo()) +{ + info->stackNode = stack->base; + info->creature = stack->type; + info->creatureCount = stack->count; + info->popupWindow = popup; + init(); +} + +CStackWindow::CStackWindow(const CCreature * creature, bool popup): + CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)), + info(new StackWindowInfo()) +{ + info->creature = creature; + info->popupWindow = popup; + init(); +} + +CStackWindow::CStackWindow(const CStackInstance * stack, bool popup): + CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)), + info(new StackWindowInfo()) +{ + info->stackNode = stack; + info->creature = stack->type; + info->creatureCount = stack->count; + info->popupWindow = popup; + init(); +} + +CStackWindow::CStackWindow(const CStackInstance * stack, std::function dismiss, const UpgradeInfo & upgradeInfo, std::function callback): + CWindowObject(BORDERED), + info(new StackWindowInfo()) +{ + info->stackNode = stack; + info->creature = stack->type; + info->creatureCount = stack->count; + + info->upgradeInfo = StackWindowInfo::StackUpgradeInfo(); + info->dismissInfo = StackWindowInfo::StackDismissInfo(); + info->upgradeInfo->info = upgradeInfo; + info->upgradeInfo->callback = callback; + info->dismissInfo->callback = dismiss; + init(); +} + +CStackWindow::CStackWindow(const CCommanderInstance * commander, bool popup): + CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)), + info(new StackWindowInfo()) +{ + info->stackNode = commander; + info->creature = commander->type; + info->commander = commander; + info->creatureCount = 1; + info->popupWindow = popup; + init(); +} + +CStackWindow::CStackWindow(const CCommanderInstance * commander, std::vector &skills, std::function callback): + CWindowObject(BORDERED), + info(new StackWindowInfo()) +{ + info->stackNode = commander; + info->creature = commander->type; + info->commander = commander; + info->creatureCount = 1; + info->levelupInfo = StackWindowInfo::CommanderLevelInfo(); + info->levelupInfo->skills = skills; + info->levelupInfo->callback = callback; + init(); +} + +CStackWindow::~CStackWindow() +{ + if (info->levelupInfo) + info->levelupInfo->callback(vstd::find_pos(info->levelupInfo->skills, selectedSkill)); +} diff --git a/client/windows/CCreatureWindow.h b/client/windows/CCreatureWindow.h new file mode 100644 index 000000000..eac271535 --- /dev/null +++ b/client/windows/CCreatureWindow.h @@ -0,0 +1,120 @@ +#pragma once + +#include "../../lib/HeroBonus.h" +#include "../widgets/MiscWidgets.h" +#include "CWindowObject.h" + +/* + * CCreatureWindow.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 + * + */ + +class StackWindowInfo; +class CCommanderInstance; +class CStackInstance; +class CStack; +struct UpgradeInfo; +class CTabbedInt; +class CButton; + +class CClickableObject : public LRClickableAreaWText +{ + CIntObject * object; // passive object that will be used to determine clickable area +public: + CClickableObject(CIntObject * object, std::function callback); + + std::function callback; //TODO: create more generic clickable class than AdvMapButton? + + void clickLeft(tribool down, bool previousState) override; + //void clickRight(tribool down, bool previousState){}; + + void setObject(CIntObject * object); +}; + +class CStackWindow : public CWindowObject +{ + struct BonusInfo + { + std::string name; + std::string description; + std::string imagePath; + }; + + class CWindowSection : public CIntObject + { + CStackWindow * parent; + + CPicture * background; + + void createBackground(std::string path); + void createBonusItem(size_t index, Point position); + + void printStatString(int index, std::string name, std::string value); + void printStatRange(int index, std::string name, int min, int max); + void printStatBase(int index, std::string name, int base, int current); + void printStat(int index, std::string name, int value); + public: + void createStackInfo(bool showExp, bool showArt); + void createActiveSpells(); + void createCommanderSection(); + void createCommander(); + void createCommanderAbilities(); + void createBonuses(boost::optional size = boost::optional()); + void createBonusEntry(size_t index); + void createButtonPanel(); + + CWindowSection(CStackWindow * parent); + }; + + CAnimImage * stackArtifactIcon; + LRClickableAreaWTextComp * stackArtifactHelp; + CButton * stackArtifactButton; + + std::unique_ptr info; + std::vector activeBonuses; + size_t activeTab; + CTabbedInt * commanderTab; + + std::map switchButtons; + + void setSelection(si32 newSkill, CClickableObject * newIcon); + CClickableObject * selectedIcon; + si32 selectedSkill; + + CIntObject * createBonusEntry(size_t index); + CIntObject * switchTab(size_t index); + + void removeStackArtifact(ArtifactPosition pos); + void setStackArtifact(const CArtifactInstance * art, Point artPos); + + void initSections(); + void initBonusesList(); + + void init(); + + std::string generateStackExpDescription(); + + CIntObject * createSkillEntry(int index); + +public: + // for battles + CStackWindow(const CStack * stack, bool popup); + + // for non-existing stacks, e.g. recruit screen + CStackWindow(const CCreature * creature, bool popup); + + // for normal stacks in armies + CStackWindow(const CStackInstance * stack, bool popup); + CStackWindow(const CStackInstance * stack, std::function dismiss, const UpgradeInfo & info, std::function callback); + + // for commanders & commander level-up dialog + CStackWindow(const CCommanderInstance * commander, bool popup); + CStackWindow(const CCommanderInstance * commander, std::vector &skills, std::function callback); + + ~CStackWindow(); +}; diff --git a/client/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp similarity index 79% rename from client/CHeroWindow.cpp rename to client/windows/CHeroWindow.cpp index 6208e3bec..ebcb107cc 100644 --- a/client/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -1,34 +1,34 @@ #include "StdInc.h" - -#include "CAnimation.h" -#include "CAdvmapInterface.h" -#include "../CCallback.h" -#include "CGameInfo.h" #include "CHeroWindow.h" -#include "CMessage.h" -#include "CKingdomInterface.h" + +#include "CAdvmapInterface.h" #include "CCreatureWindow.h" -#include "SDL.h" -#include "gui/SDL_Extensions.h" -#include "CBitmapHandler.h" -#include "Graphics.h" +#include "CKingdomInterface.h" #include "CSpellWindow.h" -#include "../lib/CConfigHandler.h" -#include "CPlayerInterface.h" +#include "GUIClasses.h" + +#include "../CBitmapHandler.h" +#include "../CDefHandler.h" +#include "../CGameInfo.h" +#include "../CMessage.h" +#include "../CMT.h" +#include "../CPlayerInterface.h" +#include "../Graphics.h" + +#include "../gui/SDL_Extensions.h" +#include "../gui/CGuiHandler.h" +#include "../widgets/MiscWidgets.h" +#include "../widgets/CComponent.h" + +#include "../../CCallback.h" #include "../lib/CArtHandler.h" -#include "CDefHandler.h" +#include "../lib/CConfigHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/NetPacksBase.h" -#include "gui/CGuiHandler.h" -#include "gui/CIntObjectClasses.h" -#include "CMT.h" - -#undef min - /* * CHeroWindow.cpp, part of VCMI engine * @@ -93,8 +93,11 @@ CHeroWindow::CHeroWindow(const CGHeroInstance *hero): CWindowObject(PLAYER_COLORED, "HeroScr4"), heroWArt(this, hero) { + auto & heroscrn = CGI->generaltexth->heroscrn; + OBJ_CONSTRUCTION_CAPTURING_ALL; garr = nullptr; + tacticsButton = nullptr; curHero = hero; listSelection = nullptr; @@ -103,23 +106,21 @@ CHeroWindow::CHeroWindow(const CGHeroInstance *hero): //artifs = new CArtifactsOfHero(pos.topLeft(), true); ourBar = new CGStatusBar(7, 559, "ADROLLVR.bmp", 660); // new CStatusBar(pos.x+72, pos.y+567, "ADROLLVR.bmp", 660); - quitButton = new CAdventureMapButton(CGI->generaltexth->heroscrn[17], std::string(),std::bind(&CHeroWindow::close,this), 609, 516, "hsbtns.def", SDLK_RETURN); + quitButton = new CButton(Point(609, 516), "hsbtns.def", CButton::tooltip(heroscrn[17]), [&]{ close(); }, SDLK_RETURN); quitButton->assignedKeys.insert(SDLK_ESCAPE); - dismissButton = new CAdventureMapButton(std::string(), CGI->generaltexth->heroscrn[28], std::bind(&CHeroWindow::dismissCurrent,this), 454, 429, "hsbtns2.def", SDLK_d); - questlogButton = new CAdventureMapButton(CGI->generaltexth->heroscrn[0], std::string(), std::bind(&CHeroWindow::questlog,this), 314, 429, "hsbtns4.def", SDLK_q); + dismissButton = new CButton(Point(454, 429), "hsbtns2.def", CButton::tooltip(heroscrn[28]), [&]{ dismissCurrent(); }, SDLK_d); + questlogButton = new CButton(Point(314, 429), "hsbtns4.def", CButton::tooltip(heroscrn[0]), [&]{ questlog(); }, SDLK_q); - formations = new CHighlightableButtonsGroup(0); - formations->addButton(map_list_of(0,CGI->generaltexth->heroscrn[23]),CGI->generaltexth->heroscrn[29], "hsbtns6.def", 481, 483, 0, 0, SDLK_t); - formations->addButton(map_list_of(0,CGI->generaltexth->heroscrn[24]),CGI->generaltexth->heroscrn[30], "hsbtns7.def", 481, 519, 1, 0, SDLK_l); - - tacticsButton = new CHighlightableButton(0, 0, map_list_of(0,CGI->generaltexth->heroscrn[26])(3,CGI->generaltexth->heroscrn[25]), CGI->generaltexth->heroscrn[31], false, "hsbtns8.def", nullptr, 539, 483, SDLK_b); + formations = new CToggleGroup(0); + formations->addToggle(0, new CToggleButton(Point(481, 483), "hsbtns6.def", std::make_pair(heroscrn[23], heroscrn[29]), 0, SDLK_t)); + formations->addToggle(1, new CToggleButton(Point(481, 519), "hsbtns7.def", std::make_pair(heroscrn[24], heroscrn[30]), 0, SDLK_l)); if (hero->commander) { - commanderButton = new CAdventureMapButton ("Commander", "Commander info", std::bind(&CHeroWindow::commanderWindow, this), 317, 18, "chftke.def", SDLK_c, nullptr, false); + auto texts = CGI->generaltexth->localizedTexts["heroWindow"]["openCommander"]; + commanderButton = new CButton (Point(317, 18), "buttons/commander", CButton::tooltip(texts), [&]{ commanderWindow(); }, SDLK_c); } - //right list of heroes for(int i=0; i < std::min(LOCPLINT->cb->howManyHeroes(false), 8); i++) heroList.push_back(new CHeroSwitcher(Point(612, 87 + i * 54), LOCPLINT->cb->getHeroBySerial(i, false))); @@ -180,7 +181,9 @@ CHeroWindow::CHeroWindow(const CGHeroInstance *hero): } void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= false*/) -{ +{ + auto & heroscrn = CGI->generaltexth->heroscrn; + if(!hero) //something strange... no hero? it shouldn't happen { logGlobal->errorStream() << "Set nullptr hero? no way..."; @@ -192,10 +195,11 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= fals specArea->text = curHero->type->specDescr; specImage->setFrame(curHero->type->imageIndex); - tacticsButton->callback.clear(); - tacticsButton->callback2.clear(); + delete tacticsButton; + tacticsButton = new CToggleButton(Point(539, 483), "hsbtns8.def", std::make_pair(heroscrn[26], heroscrn[31]), 0, SDLK_b); + tacticsButton->addHoverText(CButton::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]); - dismissButton->hoverTexts[0] = boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->name % curHero->type->heroClass->name); + dismissButton->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->name % curHero->type->heroClass->name)); portraitArea->hoverText = boost::str(boost::format(CGI->generaltexth->allTexts[15]) % curHero->name % curHero->type->heroClass->name); portraitArea->text = curHero->getBiography(); portraitImage->setFrame(curHero->portrait); @@ -204,10 +208,11 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= fals OBJ_CONSTRUCTION_CAPTURING_ALL; if(!garr) { + std::string helpBox = heroscrn[32]; + boost::algorithm::replace_first(helpBox, "%s", CGI->generaltexth->allTexts[43]); + garr = new CGarrisonInt(15, 485, 8, Point(), background->bg, Point(15,485), curHero); - auto split = new CAdventureMapButton(CGI->generaltexth->allTexts[256], CGI->generaltexth->heroscrn[32], - std::bind(&CGarrisonInt::splitClick,garr), 539, 519, "hsbtns9.def", false, nullptr, false); //deleted by garrison destructor - boost::algorithm::replace_first(split->hoverTexts[0],"%s",CGI->generaltexth->allTexts[43]); + auto split = new CButton(Point(539, 519), "hsbtns9.def", CButton::tooltip(CGI->generaltexth->allTexts[256], helpBox), [&]{ garr->splitClick(); }); garr->addSplitBtn(split); } @@ -239,7 +244,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= fals secSkillAreas[g]->type = skill; secSkillAreas[g]->bonusValue = level; secSkillAreas[g]->text = CGI->generaltexth->skillInfoTexts[skill][level-1]; - secSkillAreas[g]->hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[21]) % CGI->generaltexth->levels[level-1] % CGI->generaltexth->skillName[skill]); + secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % CGI->generaltexth->levels[level-1] % CGI->generaltexth->skillName[skill]); secSkillImages[g]->setFrame(skill*3 + level + 2); } @@ -274,14 +279,13 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= fals else { tacticsButton->block(false); - tacticsButton->callback = vstd::assigno(curHero->tacticFormationEnabled,true); - tacticsButton->callback2 = vstd::assigno(curHero->tacticFormationEnabled,false); + tacticsButton->addCallback( [&](bool on) {curHero->tacticFormationEnabled = on;}); + } //setting formations - formations->onChange = 0; - formations->select(curHero->formation,true); - formations->onChange = std::bind(&CCallback::setFormation, LOCPLINT->cb.get(), curHero, _1); + formations->setSelected(curHero->formation); + formations->addCallback([&] (int value) { LOCPLINT->cb->setFormation(curHero, value); }); morale->set(&heroWArt); luck->set(&heroWArt); @@ -328,7 +332,7 @@ void CHeroWindow::commanderWindow() } } else - GH.pushInt(new CCreatureWindow (curHero->commander)); + GH.pushInt(new CStackWindow(curHero->commander, false)); } diff --git a/client/CHeroWindow.h b/client/windows/CHeroWindow.h similarity index 82% rename from client/CHeroWindow.h rename to client/windows/CHeroWindow.h index 3d922d0c4..79af11e3b 100644 --- a/client/CHeroWindow.h +++ b/client/windows/CHeroWindow.h @@ -1,8 +1,8 @@ #pragma once -#include "../lib/HeroBonus.h" -#include "gui/CIntObjectClasses.h" -#include "GUIClasses.h" +#include "../../lib/HeroBonus.h" +#include "../widgets/CArtifactHolder.h" +#include "../widgets/CGarrisonInt.h" /* * CHeroWindow.h, part of VCMI engine @@ -14,7 +14,7 @@ * */ -class CAdventureMapButton; +class CButton; struct SDL_Surface; class CGHeroInstance; class CDefHandler; @@ -24,6 +24,10 @@ class LClickableAreaHero; class LRClickableAreaWText; class LRClickableAreaWTextComp; class CArtifactsOfHero; +class MoraleLuckBox; +class CToggleButton; +class CToggleGroup; +class CGStatusBar; /// Button which switches hero selection class CHeroSwitcher : public CIntObject @@ -52,7 +56,7 @@ class CHeroWindow: public CWindowObject, public CWindowWithGarrison, public CWin CGStatusBar * ourBar; //heroWindow's statusBar //buttons - //CAdventureMapButton * gar4button; //splitting + //CButton * gar4button; //splitting std::vector heroList; //list of heroes CPicture * listSelection; //selection border @@ -70,10 +74,10 @@ class CHeroWindow: public CWindowObject, public CWindowWithGarrison, public CWin std::vector secSkillImages; CHeroWithMaybePickedArtifact heroWArt; - CAdventureMapButton * quitButton, * dismissButton, * questlogButton, * commanderButton; //general + CButton * quitButton, * dismissButton, * questlogButton, * commanderButton; //general - CHighlightableButton *tacticsButton; //garrison / formation handling; - CHighlightableButtonsGroup *formations; + CToggleButton *tacticsButton; //garrison / formation handling; + CToggleGroup *formations; public: const CGHeroInstance * curHero; diff --git a/client/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp similarity index 89% rename from client/CKingdomInterface.cpp rename to client/windows/CKingdomInterface.cpp index 5a0266972..c7859e26b 100644 --- a/client/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -1,22 +1,28 @@ #include "StdInc.h" #include "CKingdomInterface.h" -#include "../CCallback.h" -#include "../lib/CCreatureHandler.h" //creatures name for objects list -#include "../lib/CGeneralTextHandler.h" -#include "../lib/CModHandler.h" //for buildings per turn -#include "../lib/mapObjects/CGHeroInstance.h" -#include "../lib/CHeroHandler.h" // only for calculating required xp? worth it? -#include "../lib/CTownHandler.h" -#include "CAnimation.h" //CAnimImage -#include "CAdvmapInterface.h" //CResDataBar -#include "CCastleInterface.h" //various town-specific classes -#include "../lib/CConfigHandler.h" -#include "CGameInfo.h" -#include "CPlayerInterface.h" //LOCPLINT -#include "gui/CGuiHandler.h" -#include "gui/CIntObjectClasses.h" -#include "CMT.h" +#include "CAdvmapInterface.h" +#include "CCastleInterface.h" + +#include "../CGameInfo.h" +#include "../CMT.h" +#include "../CPlayerInterface.h" +#include "../gui/CGuiHandler.h" +#include "../widgets/CComponent.h" +#include "../widgets/MiscWidgets.h" +#include "../windows/InfoWindows.h" + +#include "../../CCallback.h" + +#include "../../lib/CConfigHandler.h" +#include "../../lib/CCreatureHandler.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CHeroHandler.h" +#include "../../lib/CModHandler.h" +#include "../../lib/CTownHandler.h" +#include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/mapObjects/MiscObjects.h" /* * CKingdomInterface.cpp, part of VCMI engine @@ -602,33 +608,29 @@ void CKingdomInterface::generateButtons() ui32 footerPos = conf.go()->ac.overviewSize * 116; //Main control buttons - btnHeroes = new CAdventureMapButton (CGI->generaltexth->overview[11], CGI->generaltexth->overview[6], - std::bind(&CKingdomInterface::activateTab, this, 0),748,28+footerPos,"OVBUTN1.DEF", SDLK_h); + btnHeroes = new CButton (Point(748, 28+footerPos), "OVBUTN1.DEF", CButton::tooltip(CGI->generaltexth->overview[11], CGI->generaltexth->overview[6]), + std::bind(&CKingdomInterface::activateTab, this, 0), SDLK_h); btnHeroes->block(true); - btnTowns = new CAdventureMapButton (CGI->generaltexth->overview[12], CGI->generaltexth->overview[7], - std::bind(&CKingdomInterface::activateTab, this, 1),748,64+footerPos,"OVBUTN6.DEF", SDLK_t); + btnTowns = new CButton (Point(748, 64+footerPos), "OVBUTN6.DEF", CButton::tooltip(CGI->generaltexth->overview[12], CGI->generaltexth->overview[7]), + std::bind(&CKingdomInterface::activateTab, this, 1), SDLK_t); - btnExit = new CAdventureMapButton (CGI->generaltexth->allTexts[600],"", - std::bind(&CKingdomInterface::close, this),748,99+footerPos,"OVBUTN1.DEF", SDLK_RETURN); + btnExit = new CButton (Point(748,99+footerPos), "OVBUTN1.DEF", CButton::tooltip(CGI->generaltexth->allTexts[600]), + std::bind(&CKingdomInterface::close, this), SDLK_RETURN); btnExit->assignedKeys.insert(SDLK_ESCAPE); - btnExit->setOffset(3); + btnExit->setImageOrder(3, 4, 5, 6); //Object list control buttons - dwellTop = new CAdventureMapButton ("", "", std::bind(&CListBox::moveToPos, dwellingsList, 0), - 733, 4, "OVBUTN4.DEF"); + dwellTop = new CButton (Point(733, 4), "OVBUTN4.DEF", CButton::tooltip(), [&]{ dwellingsList->moveToPos(0);}); - dwellBottom = new CAdventureMapButton ("", "", std::bind(&CListBox::moveToPos, dwellingsList, -1), - 733, footerPos+2, "OVBUTN4.DEF"); - dwellBottom->setOffset(2); + dwellBottom = new CButton (Point(733, footerPos+2), "OVBUTN4.DEF", CButton::tooltip(), [&]{ dwellingsList->moveToPos(-1); }); + dwellBottom->setImageOrder(2, 3, 4, 5); - dwellUp = new CAdventureMapButton ("", "", std::bind(&CListBox::moveToPrev, dwellingsList), - 733, 24, "OVBUTN4.DEF"); - dwellUp->setOffset(4); + dwellUp = new CButton (Point(733, 24), "OVBUTN4.DEF", CButton::tooltip(), [&]{ dwellingsList->moveToPrev(); }); + dwellUp->setImageOrder(4, 5, 6, 7); - dwellDown = new CAdventureMapButton ("", "", std::bind(&CListBox::moveToNext, dwellingsList), - 733, footerPos-18, "OVBUTN4.DEF"); - dwellDown->setOffset(6); + dwellDown = new CButton (Point(733, footerPos-18), "OVBUTN4.DEF", CButton::tooltip(), [&]{ dwellingsList->moveToNext(); }); + dwellDown->setImageOrder(6, 7, 8, 9); } void CKingdomInterface::activateTab(size_t which) @@ -845,16 +847,16 @@ class BackpackTab : public CIntObject public: CAnimImage * background; std::vector arts; - CAdventureMapButton *btnLeft; - CAdventureMapButton *btnRight; + CButton *btnLeft; + CButton *btnRight; BackpackTab() { OBJ_CONSTRUCTION_CAPTURING_ALL; background = new CAnimImage("OVSLOT", 5); pos = background->pos; - btnLeft = new CAdventureMapButton(std::string(), std::string(), CFunctionList(), 269, 66, "HSBTNS3"); - btnRight = new CAdventureMapButton(std::string(), std::string(), CFunctionList(), 675, 66, "HSBTNS5"); + btnLeft = new CButton(Point(269, 66), "HSBTNS3", CButton::tooltip(), 0); + btnRight = new CButton(Point(675, 66), "HSBTNS5", CButton::tooltip(), 0); for (size_t i=0; i<8; i++) arts.push_back(new CArtPlace(Point(295+i*48, 65))); } @@ -888,21 +890,21 @@ CHeroItem::CHeroItem(const CGHeroInstance* Hero, CArtifactsOfHero::SCommonPart * artsTabs = new CTabbedInt(std::bind(&CHeroItem::onTabSelected, this, _1), std::bind(&CHeroItem::onTabDeselected, this, _1)); - artButtons = new CHighlightableButtonsGroup(0); + artButtons = new CToggleGroup(0); for (size_t it = 0; it<3; it++) { int stringID[3] = {259, 261, 262}; - std::map tooltip; - tooltip[0] = CGI->generaltexth->overview[13+it]; + std::string hover = CGI->generaltexth->overview[13+it]; std::string overlay = CGI->generaltexth->overview[8+it]; - artButtons->addButton(tooltip, overlay, "OVBUTN3",364+it*112, 46, it); - artButtons->buttons[it]->addTextOverlay(CGI->generaltexth->allTexts[stringID[it]], FONT_SMALL, Colors::YELLOW); + auto button = new CToggleButton(Point(364+it*112, 46), "OVBUTN3", CButton::tooltip(hover, overlay), 0); + button->addTextOverlay(CGI->generaltexth->allTexts[stringID[it]], FONT_SMALL, Colors::YELLOW); + artButtons->addToggle(it, button); } - artButtons->onChange += std::bind(&CTabbedInt::setActive, artsTabs, _1); - artButtons->onChange += std::bind(&CHeroItem::onArtChange, this, _1); - artButtons->select(0,0); + artButtons->addCallback(std::bind(&CTabbedInt::setActive, artsTabs, _1)); + artButtons->addCallback(std::bind(&CHeroItem::onArtChange, this, _1)); + artButtons->setSelected(0); garr = new CGarrisonInt(6, 78, 4, Point(), nullptr, Point(), hero, nullptr, true, true); diff --git a/client/CKingdomInterface.h b/client/windows/CKingdomInterface.h similarity index 91% rename from client/CKingdomInterface.h rename to client/windows/CKingdomInterface.h index 9b42984a0..f4f6f5e53 100644 --- a/client/CKingdomInterface.h +++ b/client/windows/CKingdomInterface.h @@ -1,16 +1,23 @@ #pragma once +#include "../widgets/CArtifactHolder.h" +#include "../widgets/CGarrisonInt.h" -#include "GUIClasses.h" - -class CAdventureMapButton; +class CButton; class CAnimImage; -class CHighlightableButtonsGroup; +class CToggleGroup; class CResDataBar; class CSlider; class CTownInfo; class CCreaInfo; class HeroSlots; +class LRClickableAreaOpenTown; +class CComponent; +class CHeroArea; +class MoraleLuckBox; +class CListBox; +class CTabbedInt; +class CGStatusBar; /* * CKingdomInterface.h, part of VCMI engine @@ -213,13 +220,13 @@ private: CTabbedInt * tabArea; //Main buttons - CAdventureMapButton *btnTowns; - CAdventureMapButton *btnHeroes; - CAdventureMapButton *btnExit; + CButton *btnTowns; + CButton *btnHeroes; + CButton *btnExit; //Buttons for scrolling dwellings list - CAdventureMapButton *dwellUp, *dwellDown; - CAdventureMapButton *dwellTop, *dwellBottom; + CButton *dwellUp, *dwellDown; + CButton *dwellTop, *dwellBottom; InfoBox * minesBox[7]; @@ -288,7 +295,7 @@ class CHeroItem : public CIntObject, public CWindowWithGarrison CLabel *artsText; CTabbedInt *artsTabs; - CHighlightableButtonsGroup *artButtons; + CToggleGroup *artButtons; std::vector heroInfo; MoraleLuckBox * morale, * luck; diff --git a/client/CQuestLog.cpp b/client/windows/CQuestLog.cpp similarity index 72% rename from client/CQuestLog.cpp rename to client/windows/CQuestLog.cpp index 64c433cf1..ea4003102 100644 --- a/client/CQuestLog.cpp +++ b/client/windows/CQuestLog.cpp @@ -1,24 +1,23 @@ #include "StdInc.h" #include "CQuestLog.h" -#include "CGameInfo.h" -#include "../lib/CGeneralTextHandler.h" -#include "../CCallback.h" +#include "CAdvmapInterface.h" -#include -#include "gui/SDL_Extensions.h" -#include "CBitmapHandler.h" -#include "CDefHandler.h" -#include "Graphics.h" -#include "CPlayerInterface.h" -#include "../lib/CConfigHandler.h" +#include "../CBitmapHandler.h" +#include "../CDefHandler.h" +#include "../CGameInfo.h" +#include "../CPlayerInterface.h" +#include "../Graphics.h" -#include "../lib/CGameState.h" -#include "../lib/CArtHandler.h" -#include "../lib/NetPacksBase.h" +#include "../gui/CGuiHandler.h" +#include "../gui/SDL_Extensions.h" -#include "gui/CGuiHandler.h" -#include "gui/CIntObjectClasses.h" +#include "../../CCallback.h" +#include "../../lib/CArtHandler.h" +#include "../../lib/CConfigHandler.h" +#include "../../lib/CGameState.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/NetPacksBase.h" /* * CQuestLog.cpp, part of VCMI engine @@ -116,7 +115,7 @@ void CQuestMinimap::showAll(SDL_Surface * to) } CQuestLog::CQuestLog (const std::vector & Quests) : - CWindowObject(PLAYER_COLORED, "QuestLog.pcx"), + CWindowObject(PLAYER_COLORED | BORDERED, "questDialog.pcx"), questIndex(0), currentQuest(nullptr), quests (Quests), @@ -128,12 +127,12 @@ CQuestLog::CQuestLog (const std::vector & Quests) : void CQuestLog::init() { - minimap = new CQuestMinimap (Rect (47, 33, 144, 144)); - description = new CTextBox ("", Rect(245, 33, 350, 355), 1, FONT_MEDIUM, TOPLEFT, Colors::WHITE); - ok = new CAdventureMapButton("",CGI->generaltexth->zelp[445].second, std::bind(&CQuestLog::close,this), 547, 401, "IOKAY.DEF", SDLK_RETURN); + minimap = new CQuestMinimap (Rect (33, 18, 144, 144)); + description = new CTextBox ("", Rect(221, 18, 350, 355), 1, FONT_MEDIUM, TOPLEFT, Colors::WHITE); + ok = new CButton(Point(533, 386), "IOKAY.DEF", CGI->generaltexth->zelp[445], boost::bind(&CQuestLog::close,this), SDLK_RETURN); if (quests.size() > QUEST_COUNT) - slider = new CSlider(203, 199, 230, std::bind (&CQuestLog::sliderMoved, this, _1), QUEST_COUNT, quests.size(), false, 0); + slider = new CSlider(Point(189, 184), 230, std::bind (&CQuestLog::sliderMoved, this, _1), QUEST_COUNT, quests.size(), false, CSlider::BROWN); for (int i = 0; i < quests.size(); ++i) { @@ -141,8 +140,8 @@ void CQuestLog::init() quests[i].quest->getRolloverText (text, false); if (quests[i].obj) text.addReplacement (quests[i].obj->getObjectName()); //get name of the object - CQuestLabel * label = new CQuestLabel (Rect(28, 199 + i * 24, 172,30), FONT_SMALL, TOPLEFT, Colors::WHITE, text.toString()); - label->callback = std::bind(&CQuestLog::selectQuest, this, i); + CQuestLabel * label = new CQuestLabel (Rect(14, 184 + i * 24, 172,30), FONT_SMALL, TOPLEFT, Colors::WHITE, text.toString()); + label->callback = boost::bind(&CQuestLog::selectQuest, this, i); labels.push_back(label); } @@ -151,7 +150,7 @@ void CQuestLog::init() void CQuestLog::showAll(SDL_Surface * to) { - CIntObject::showAll (to); + CWindowObject::showAll (to); for (auto label : labels) { label->show(to); //shows only if active @@ -169,7 +168,7 @@ void CQuestLog::recreateQuestList (int newpos) { for (int i = 0; i < labels.size(); ++i) { - labels[i]->pos = Rect (pos.x + 28, pos.y + 207 + (i-newpos) * 25, 173, 23); + labels[i]->pos = Rect (pos.x + 14, pos.y + 192 + (i-newpos) * 25, 173, 23); if (i >= newpos && i < newpos + QUEST_COUNT) { labels[i]->activate(); diff --git a/client/CQuestLog.h b/client/windows/CQuestLog.h similarity index 86% rename from client/CQuestLog.h rename to client/windows/CQuestLog.h index e4765b4f3..c14b0c32e 100644 --- a/client/CQuestLog.h +++ b/client/windows/CQuestLog.h @@ -1,9 +1,10 @@ -#include "gui/CIntObject.h" -#include "AdventureMapClasses.h" -#include "CAdvmapInterface.h" -#include "GUIClasses.h" +#pragma once -#include "../lib/CGameState.h" +#include "../widgets/AdventureMapClasses.h" +#include "../widgets/TextControls.h" +#include "../widgets/MiscWidgets.h" +#include "../widgets/Images.h" +#include "CWindowObject.h" /* * CQuestLog.h, part of VCMI engine @@ -17,11 +18,11 @@ class CCreature; class CStackInstance; -class CAdventureMapButton; +class CButton; class CGHeroInstance; class CComponent; class LRClickableAreaWText; -class CAdventureMapButton; +class CButton; class CPicture; class CCreaturePic; class LRClickableAreaWTextComp; @@ -29,8 +30,6 @@ class CSlider; class CLabel; struct QuestInfo; -extern CAdvMapInt *adventureInt; - const int QUEST_COUNT = 9; class CQuestLabel : public LRClickableAreaWText, public CMultiLineLabel @@ -86,7 +85,7 @@ class CQuestLog : public CWindowObject CTextBox * description; CQuestMinimap * minimap; CSlider * slider; //scrolls quests - CAdventureMapButton *ok; + CButton *ok; void init (); public: @@ -101,4 +100,4 @@ public: void sliderMoved (int newpos); void recreateQuestList (int pos); void showAll (SDL_Surface * to); -}; \ No newline at end of file +}; diff --git a/client/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp similarity index 93% rename from client/CSpellWindow.cpp rename to client/windows/CSpellWindow.cpp index 5459ab57a..9308a9c81 100644 --- a/client/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -1,24 +1,36 @@ #include "StdInc.h" #include "CSpellWindow.h" -#include "Graphics.h" -#include "CDefHandler.h" -#include "../lib/CSpellHandler.h" -#include "../lib/CGeneralTextHandler.h" -#include "CVideoHandler.h" #include "CAdvmapInterface.h" -#include "battle/CBattleInterface.h" -#include "CGameInfo.h" -#include "gui/SDL_Extensions.h" -#include "CMessage.h" -#include "CPlayerInterface.h" -#include "../CCallback.h" -#include "CBitmapHandler.h" -#include "../lib/CHeroHandler.h" -#include "../lib/BattleState.h" -#include "../lib/GameConstants.h" -#include "gui/CGuiHandler.h" -#include "CMT.h" +#include "GUIClasses.h" +#include "InfoWindows.h" + +#include "../CBitmapHandler.h" +#include "../CDefHandler.h" +#include "../CGameInfo.h" +#include "../CMessage.h" +#include "../CMT.h" +#include "../CPlayerInterface.h" +#include "../CVideoHandler.h" +#include "../Graphics.h" + +#include "../battle/CBattleInterface.h" +#include "../gui/CAnimation.h" +#include "../gui/CGuiHandler.h" +#include "../gui/SDL_Extensions.h" +#include "../widgets/MiscWidgets.h" +#include "../widgets/CComponent.h" + +#include "../../CCallback.h" + +#include "../../lib/BattleState.h" +#include "../../lib/CConfigHandler.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CHeroHandler.h" +#include "../../lib/CSpellHandler.h" +#include "../../lib/GameConstants.h" +#include "../../lib/CGameState.h" +#include "../../lib/mapObjects/CGTownInstance.h" /* * CSpellWindow.cpp, part of VCMI engine diff --git a/client/CSpellWindow.h b/client/windows/CSpellWindow.h similarity index 94% rename from client/CSpellWindow.h rename to client/windows/CSpellWindow.h index fc570c442..a18aeaa4e 100644 --- a/client/CSpellWindow.h +++ b/client/windows/CSpellWindow.h @@ -1,7 +1,6 @@ #pragma once -#include "gui/CIntObject.h" -#include "gui/CIntObjectClasses.h" +#include "CWindowObject.h" /* * CSpellWindow.h, part of VCMI engine diff --git a/client/windows/CTradeWindow.cpp b/client/windows/CTradeWindow.cpp new file mode 100644 index 000000000..c4c98af07 --- /dev/null +++ b/client/windows/CTradeWindow.cpp @@ -0,0 +1,1509 @@ +#include "StdInc.h" +#include "CTradeWindow.h" + +#include "CAdvmapInterface.h" + +#include "../gui/CGuiHandler.h" +#include "../gui/CCursorHandler.h" +#include "../widgets/Images.h" + +#include "../CGameInfo.h" +#include "../CPlayerInterface.h" + +#include "../../CCallback.h" + +#include "../../lib/VCMI_Lib.h" +#include "../../lib/CArtHandler.h" +#include "../../lib/CCreatureHandler.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CHeroHandler.h" +#include "../../lib/CGameState.h" +#include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/mapObjects/CGMarket.h" + + +/* + * CTradeWindow.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 + * + */ + +CTradeWindow::CTradeableItem::CTradeableItem(Point pos, EType Type, int ID, bool Left, int Serial): + CIntObject(LCLICK | HOVER | RCLICK, pos), + type(EType(-1)),// set to invalid, will be corrected in setType + id(ID), + serial(Serial), + left(Left) +{ + downSelection = false; + hlp = nullptr; + image = nullptr; + setType(Type); +} + +void CTradeWindow::CTradeableItem::setType(EType newType) +{ + if (type != newType) + { + OBJ_CONSTRUCTION_CAPTURING_ALL; + type = newType; + delete image; + + if (getIndex() < 0) + { + image = new CAnimImage(getFilename(), 0); + image->disable(); + } + else + image = new CAnimImage(getFilename(), getIndex()); + } +} + +void CTradeWindow::CTradeableItem::setID(int newID) +{ + if (id != newID) + { + id = newID; + if (image) + { + int index = getIndex(); + if (index < 0) + image->disable(); + else + { + image->enable(); + image->setFrame(index); + } + } + } +} + +std::string CTradeWindow::CTradeableItem::getFilename() +{ + switch(type) + { + case RESOURCE: + return "RESOURCE"; + case PLAYER: + return "CREST58"; + case ARTIFACT_TYPE: + case ARTIFACT_PLACEHOLDER: + case ARTIFACT_INSTANCE: + return "artifact"; + case CREATURE: + return "TWCRPORT"; + default: + return ""; + } +} + +int CTradeWindow::CTradeableItem::getIndex() +{ + if (id < 0) + return -1; + + switch(type) + { + case RESOURCE: + case PLAYER: + return id; + case ARTIFACT_TYPE: + case ARTIFACT_INSTANCE: + case ARTIFACT_PLACEHOLDER: + return VLC->arth->artifacts[id]->iconIndex; + case CREATURE: + return VLC->creh->creatures[id]->iconIndex; + default: + return -1; + } +} + +void CTradeWindow::CTradeableItem::showAll(SDL_Surface * to) +{ + Point posToBitmap; + Point posToSubCenter; + + switch(type) + { + case RESOURCE: + posToBitmap = Point(19,9); + posToSubCenter = Point(36, 59); + break; + case CREATURE_PLACEHOLDER: + case CREATURE: + posToSubCenter = Point(29, 76); + if(downSelection) + posToSubCenter.y += 5; + break; + case PLAYER: + posToSubCenter = Point(31, 76); + break; + case ARTIFACT_PLACEHOLDER: + case ARTIFACT_INSTANCE: + posToSubCenter = Point(19, 55); + if(downSelection) + posToSubCenter.y += 8; + break; + case ARTIFACT_TYPE: + posToSubCenter = Point(19, 58); + break; + } + + if (image) + { + image->moveTo(pos.topLeft() + posToBitmap); + CIntObject::showAll(to); + } + + printAtMiddleLoc(subtitle, posToSubCenter, FONT_SMALL, Colors::WHITE, to); +} + +void CTradeWindow::CTradeableItem::clickLeft(tribool down, bool previousState) +{ + CTradeWindow *mw = dynamic_cast(parent); + assert(mw); + if(down) + { + + if(type == ARTIFACT_PLACEHOLDER) + { + CAltarWindow *aw = static_cast(mw); + if(const CArtifactInstance *movedArt = aw->arts->commonInfo->src.art) + { + aw->moveFromSlotToAltar(aw->arts->commonInfo->src.slotID, this, movedArt); + } + else if(const CArtifactInstance *art = getArtInstance()) + { + aw->arts->commonInfo->src.AOH = aw->arts; + aw->arts->commonInfo->src.art = art; + aw->arts->commonInfo->src.slotID = aw->hero->getArtPos(art); + aw->arts->markPossibleSlots(art); + + //aw->arts->commonInfo->dst.AOH = aw->arts; + CCS->curh->dragAndDropCursor(new CAnimImage("artifact", art->artType->iconIndex)); + + aw->arts->artifactsOnAltar.erase(art); + setID(-1); + subtitle = ""; + aw->deal->block(!aw->arts->artifactsOnAltar.size()); + } + + aw->calcTotalExp(); + return; + } + if(left) + { + if(mw->hLeft != this) + mw->hLeft = this; + else + return; + } + else + { + if(mw->hRight != this) + mw->hRight = this; + else + return; + } + mw->selectionChanged(left); + } +} + +void CTradeWindow::CTradeableItem::showAllAt(const Point &dstPos, const std::string &customSub, SDL_Surface * to) +{ + Rect oldPos = pos; + std::string oldSub = subtitle; + downSelection = true; + + moveTo(dstPos); + subtitle = customSub; + showAll(to); + + downSelection = false; + moveTo(oldPos.topLeft()); + subtitle = oldSub; +} + +void CTradeWindow::CTradeableItem::hover(bool on) +{ + if(!on) + { + GH.statusbar->clear(); + return; + } + + switch(type) + { + case CREATURE: + case CREATURE_PLACEHOLDER: + GH.statusbar->setText(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->creatures[id]->namePl)); + break; + case ARTIFACT_PLACEHOLDER: + if(id < 0) + GH.statusbar->setText(CGI->generaltexth->zelp[582].first); + else + GH.statusbar->setText(CGI->arth->artifacts[id]->Name()); + break; + } +} + +void CTradeWindow::CTradeableItem::clickRight(tribool down, bool previousState) +{ + if(down) + { + switch(type) + { + case CREATURE: + case CREATURE_PLACEHOLDER: + //GH.statusbar->print(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->creatures[id]->namePl)); + break; + case ARTIFACT_TYPE: + case ARTIFACT_PLACEHOLDER: + if(id >= 0) + adventureInt->handleRightClick(CGI->arth->artifacts[id]->Description(), down); + break; + } + } +} + +std::string CTradeWindow::CTradeableItem::getName(int number /*= -1*/) const +{ + switch(type) + { + case PLAYER: + return CGI->generaltexth->capColors[id]; + case RESOURCE: + return CGI->generaltexth->restypes[id]; + case CREATURE: + if(number == 1) + return CGI->creh->creatures[id]->nameSing; + else + return CGI->creh->creatures[id]->namePl; + case ARTIFACT_TYPE: + case ARTIFACT_INSTANCE: + return CGI->arth->artifacts[id]->Name(); + } + assert(0); + return ""; +} + +const CArtifactInstance * CTradeWindow::CTradeableItem::getArtInstance() const +{ + switch(type) + { + case ARTIFACT_PLACEHOLDER: + case ARTIFACT_INSTANCE: + return (const CArtifactInstance *)hlp; + default: + return nullptr; + } +} + +void CTradeWindow::CTradeableItem::setArtInstance(const CArtifactInstance *art) +{ + assert(type == ARTIFACT_PLACEHOLDER || type == ARTIFACT_INSTANCE); + hlp = art; + if(art) + setID(art->artType->id); + else + setID(-1); +} + +CTradeWindow::CTradeWindow(std::string bgName, const IMarket *Market, const CGHeroInstance *Hero, EMarketMode::EMarketMode Mode): + CWindowObject(PLAYER_COLORED, bgName), + market(Market), + hero(Hero), + arts(nullptr), + hLeft(nullptr), + hRight(nullptr), + readyToTrade(false) +{ + type |= BLOCK_ADV_HOTKEYS; + mode = Mode; + initTypes(); +} + +void CTradeWindow::initTypes() +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + itemsType[1] = RESOURCE; + itemsType[0] = RESOURCE; + break; + case EMarketMode::RESOURCE_PLAYER: + itemsType[1] = RESOURCE; + itemsType[0] = PLAYER; + break; + case EMarketMode::CREATURE_RESOURCE: + itemsType[1] = CREATURE; + itemsType[0] = RESOURCE; + break; + case EMarketMode::RESOURCE_ARTIFACT: + itemsType[1] = RESOURCE; + itemsType[0] = ARTIFACT_TYPE; + break; + case EMarketMode::ARTIFACT_RESOURCE: + itemsType[1] = ARTIFACT_INSTANCE; + itemsType[0] = RESOURCE; + break; + case EMarketMode::CREATURE_EXP: + itemsType[1] = CREATURE; + itemsType[0] = CREATURE_PLACEHOLDER; + break; + case EMarketMode::ARTIFACT_EXP: + itemsType[1] = ARTIFACT_TYPE; + itemsType[0] = ARTIFACT_PLACEHOLDER; + break; + } +} + +void CTradeWindow::initItems(bool Left) +{ + if(Left && (itemsType[1] == ARTIFACT_TYPE || itemsType[1] == ARTIFACT_INSTANCE)) + { + int xOffset = 0, yOffset = 0; + if(mode == EMarketMode::ARTIFACT_RESOURCE) + { + xOffset = -361; + yOffset = +46; + + auto hlp = new CTradeableItem(Point(137, 469), itemsType[Left], -1, 1, 0); + hlp->recActions &= ~(UPDATE | SHOWALL); + items[Left].push_back(hlp); + } + else //ARTIFACT_EXP + { + xOffset = -363; + yOffset = -12; + } + + BLOCK_CAPTURING; + arts = new CArtifactsOfHero(Point(pos.x+xOffset, pos.y+yOffset)); + arts->commonInfo = new CArtifactsOfHero::SCommonPart; + arts->commonInfo->participants.insert(arts); + arts->recActions = 255; + arts->setHero(hero); + arts->allowedAssembling = false; + addChild(arts); + artSets.push_back(arts); + + if(mode == EMarketMode::ARTIFACT_RESOURCE) + arts->highlightModeCallback = std::bind(&CTradeWindow::artifactSelected, this, _1); + return; + } + + std::vector *ids = getItemsIds(Left); + std::vector pos; + int amount = -1; + + getPositionsFor(pos, Left, itemsType[Left]); + + if(Left || !ids) + amount = 7; + else + amount = ids->size(); + + if(ids) + vstd::amin(amount, ids->size()); + + for(int j=0; jsize()>j) ? (*ids)[j] : j; + if(id < 0 && mode != EMarketMode::ARTIFACT_EXP) //when sacrificing artifacts we need to prepare empty slots + continue; + + auto hlp = new CTradeableItem(pos[j].topLeft(), itemsType[Left], id, Left, j); + hlp->pos = pos[j] + this->pos.topLeft(); + items[Left].push_back(hlp); + } + + initSubs(Left); +} + +std::vector *CTradeWindow::getItemsIds(bool Left) +{ + std::vector *ids = nullptr; + + if(mode == EMarketMode::ARTIFACT_EXP) + return new std::vector(22, -1); + + if(Left) + { + switch(itemsType[1]) + { + case CREATURE: + ids = new std::vector; + for(int i = 0; i < 7; i++) + { + if(const CCreature *c = hero->getCreature(SlotID(i))) + ids->push_back(c->idNumber); + else + ids->push_back(-1); + } + break; + } + } + else + { + switch(itemsType[0]) + { + case PLAYER: + ids = new std::vector; + for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) + if(PlayerColor(i) != LOCPLINT->playerID && LOCPLINT->cb->getPlayerStatus(PlayerColor(i)) == EPlayerStatus::INGAME) + ids->push_back(i); + break; + + case ARTIFACT_TYPE: + ids = new std::vector(market->availableItemsIds(mode)); + break; + } + } + + return ids; +} + +void CTradeWindow::getPositionsFor(std::vector &poss, bool Left, EType type) const +{ + using namespace boost::assign; + + if(mode == EMarketMode::ARTIFACT_EXP && !Left) + { + //22 boxes, 5 in row, last row: two boxes centered + int h, w, x, y, dx, dy; + h = w = 44; + x = 317; + y = 53; + dx = 54; + dy = 70; + for (int i = 0; i < 4 ; i++) + for (int j = 0; j < 5 ; j++) + poss += Rect(x + dx*j, y + dy*i, w, h); + + poss += Rect(x + dx*1.5, y + dy*4, w, h); + poss += Rect(x + dx*2.5, y + dy*4, w, h); + } + else + { + //seven boxes: + // X X X + // X X X + // X + int h, w, x, y, dx, dy; + int leftToRightOffset; + getBaseForPositions(type, dx, dy, x, y, h, w, !Left, leftToRightOffset); + + poss += genRect(h, w, x, y), genRect(h, w, x + dx, y), genRect(h, w, x + 2*dx, y), + genRect(h, w, x, y + dy), genRect(h, w, x + dx, y + dy), genRect(h, w, x + 2*dx, y + dy), + genRect(h, w, x + dx, y + 2*dy); + + if(!Left) + { + for(Rect &r : poss) + r.x += leftToRightOffset; + } + } +} + +void CTradeWindow::initSubs(bool Left) +{ + for(CTradeableItem *t : items[Left]) + { + if(Left) + { + switch(itemsType[1]) + { + case CREATURE: + t->subtitle = boost::lexical_cast(hero->getStackCount(SlotID(t->serial))); + break; + case RESOURCE: + t->subtitle = boost::lexical_cast(LOCPLINT->cb->getResourceAmount(static_cast(t->serial))); + break; + } + } + else //right side + { + if(itemsType[0] == PLAYER) + { + t->subtitle = CGI->generaltexth->capColors[t->id]; + } + else if(hLeft)//artifact, creature + { + int h1, h2; //hlp variables for getting offer + market->getOffer(hLeft->id, t->id, h1, h2, mode); + if(t->id != hLeft->id || mode != EMarketMode::RESOURCE_RESOURCE) //don't allow exchanging same resources + { + std::ostringstream oss; + oss << h2; + if(h1!=1) + oss << "/" << h1; + t->subtitle = oss.str(); + } + else + t->subtitle = CGI->generaltexth->allTexts[164]; // n/a + } + else + t->subtitle = ""; + } + } +} + +void CTradeWindow::showAll(SDL_Surface * to) +{ + CWindowObject::showAll(to); + + if(hRight) + CSDL_Ext::drawBorder(to,hRight->pos.x-1,hRight->pos.y-1,hRight->pos.w+2,hRight->pos.h+2,int3(255,231,148)); + if(hLeft && hLeft->type != ARTIFACT_INSTANCE) + CSDL_Ext::drawBorder(to,hLeft->pos.x-1,hLeft->pos.y-1,hLeft->pos.w+2,hLeft->pos.h+2,int3(255,231,148)); + + if(readyToTrade) + { + hLeft->showAllAt(pos.topLeft() + selectionOffset(true), selectionSubtitle(true), to); + hRight->showAllAt(pos.topLeft() + selectionOffset(false), selectionSubtitle(false), to); + } +} + +void CTradeWindow::removeItems(const std::set &toRemove) +{ + for(CTradeableItem *t : toRemove) + removeItem(t); +} + +void CTradeWindow::removeItem(CTradeableItem * t) +{ + items[t->left] -= t; + delete t; + + if(hRight == t) + { + hRight = nullptr; + selectionChanged(false); + } +} + +void CTradeWindow::getEmptySlots(std::set &toRemove) +{ + for(CTradeableItem *t : items[1]) + if(!hero->getStackCount(SlotID(t->serial))) + toRemove.insert(t); +} + +void CTradeWindow::setMode(EMarketMode::EMarketMode Mode) +{ + const IMarket *m = market; + const CGHeroInstance *h = hero; + CTradeWindow *nwindow = nullptr; + + GH.popIntTotally(this); + + switch(Mode) + { + case EMarketMode::CREATURE_EXP: + case EMarketMode::ARTIFACT_EXP: + nwindow = new CAltarWindow(m, h, Mode); + break; + default: + nwindow = new CMarketplaceWindow(m, h, Mode); + break; + } + + GH.pushInt(nwindow); +} + +void CTradeWindow::artifactSelected(CArtPlace *slot) +{ + assert(mode == EMarketMode::ARTIFACT_RESOURCE); + items[1][0]->setArtInstance(slot->ourArt); + if(slot->ourArt) + hLeft = items[1][0]; + else + hLeft = nullptr; + + selectionChanged(true); +} + +std::string CMarketplaceWindow::getBackgroundForMode(EMarketMode::EMarketMode mode) +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + return "TPMRKRES.bmp"; + case EMarketMode::RESOURCE_PLAYER: + return "TPMRKPTS.bmp"; + case EMarketMode::CREATURE_RESOURCE: + return "TPMRKCRS.bmp"; + case EMarketMode::RESOURCE_ARTIFACT: + return "TPMRKABS.bmp"; + case EMarketMode::ARTIFACT_RESOURCE: + return "TPMRKASS.bmp"; + } + assert(0); + return ""; +} + +CMarketplaceWindow::CMarketplaceWindow(const IMarket *Market, const CGHeroInstance *Hero, EMarketMode::EMarketMode Mode) + : CTradeWindow(getBackgroundForMode(Mode), Market, Hero, Mode) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + madeTransaction = false; + bool sliderNeeded = true; + + new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); + + std::string title; + + if (market->o->ID == Obj::TOWN) + { + switch (mode) + { + break; case EMarketMode::CREATURE_RESOURCE: + title = CGI->townh->factions[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->Name(); + + break; case EMarketMode::RESOURCE_ARTIFACT: + title = CGI->townh->factions[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name(); + sliderNeeded = false; + + break; case EMarketMode::ARTIFACT_RESOURCE: + title = CGI->townh->factions[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name(); + sliderNeeded = false; + + break; default: + title = CGI->generaltexth->allTexts[158]; + } + } + else + { + switch (market->o->ID) + { + break; case Obj::BLACK_MARKET: title = CGI->generaltexth->allTexts[349]; + break; case Obj::TRADING_POST: title = CGI->generaltexth->allTexts[159]; + break; case Obj::TRADING_POST_SNOW: title = CGI->generaltexth->allTexts[159]; + break; default: title = market->o->getObjectName(); + } + } + + new CLabel(300, 27, FONT_BIG, CENTER, Colors::YELLOW, title); + + initItems(false); + initItems(true); + + ok = new CButton(Point(516, 520), "IOK6432.DEF", CGI->generaltexth->zelp[600], [&]{ close(); }, SDLK_RETURN); + ok->assignedKeys.insert(SDLK_ESCAPE); + deal = new CButton(Point(307, 520), "TPMRKB.DEF", CGI->generaltexth->zelp[595], [&] { makeDeal(); } ); + deal->block(true); + + if(sliderNeeded) + { + slider = new CSlider(Point(231, 490),137, std::bind(&CMarketplaceWindow::sliderMoved,this,_1),0,0); + max = new CButton(Point(229, 520), "IRCBTNS.DEF", CGI->generaltexth->zelp[596], [&] { setMax(); }); + max->block(true); + } + else + { + slider = nullptr; + max = nullptr; + deal->moveBy(Point(-30, 0)); + } + + Rect traderTextRect; + + //left side + switch(Mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::RESOURCE_PLAYER: + case EMarketMode::RESOURCE_ARTIFACT: + new CLabel(154, 148, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[270]); + break; + + case EMarketMode::CREATURE_RESOURCE: + //%s's Creatures + new CLabel(152, 102, FONT_SMALL, CENTER, Colors::WHITE, + boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)); + break; + case EMarketMode::ARTIFACT_RESOURCE: + //%s's Artifacts + new CLabel(152, 102, FONT_SMALL, CENTER, Colors::WHITE, + boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)); + break; + } + + //right side + switch(Mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::CREATURE_RESOURCE: + case EMarketMode::RESOURCE_ARTIFACT: + case EMarketMode::ARTIFACT_RESOURCE: + new CLabel(445, 148, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[168]); + traderTextRect = Rect(316, 48, 260, 75); + break; + case EMarketMode::RESOURCE_PLAYER: + new CLabel(445, 55, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[169]); + traderTextRect = Rect(28, 48, 260, 75); + break; + } + + traderText = new CTextBox("", traderTextRect, 0, FONT_SMALL, CENTER); + int specialOffset = mode == EMarketMode::ARTIFACT_RESOURCE ? 35 : 0; //in selling artifacts mode we need to move res-res and art-res buttons down + + if(printButtonFor(EMarketMode::RESOURCE_PLAYER)) + new CButton(Point(18, 520),"TPMRKBU1.DEF", CGI->generaltexth->zelp[612], [&] { setMode(EMarketMode::RESOURCE_PLAYER);}); + if(printButtonFor(EMarketMode::RESOURCE_RESOURCE)) + new CButton(Point(516, 450 + specialOffset),"TPMRKBU5.DEF", CGI->generaltexth->zelp[605], [&] { setMode(EMarketMode::RESOURCE_RESOURCE);}); + if(printButtonFor(EMarketMode::CREATURE_RESOURCE)) + new CButton(Point(516, 485),"TPMRKBU4.DEF", CGI->generaltexth->zelp[599], [&] { setMode(EMarketMode::CREATURE_RESOURCE);}); + if(printButtonFor(EMarketMode::RESOURCE_ARTIFACT)) + new CButton(Point(18, 450 + specialOffset),"TPMRKBU2.DEF", CGI->generaltexth->zelp[598], [&] { setMode(EMarketMode::RESOURCE_ARTIFACT);}); + if(printButtonFor(EMarketMode::ARTIFACT_RESOURCE)) + new CButton(Point(18, 485),"TPMRKBU3.DEF", CGI->generaltexth->zelp[613], [&] { setMode(EMarketMode::ARTIFACT_RESOURCE);}); + + updateTraderText(); +} + +CMarketplaceWindow::~CMarketplaceWindow() +{ + hLeft = hRight = nullptr; + for(auto & elem : items[1]) + delete elem; + for(auto & elem : items[0]) + delete elem; + + items[1].clear(); + items[0].clear(); +} + + + +void CMarketplaceWindow::setMax() +{ + slider->moveToMax(); +} + +void CMarketplaceWindow::makeDeal() +{ + int sliderValue = 0; + if(slider) + sliderValue = slider->getValue(); + else + sliderValue = !deal->isBlocked(); //should always be 1 + + if(!sliderValue) + return; + + int leftIdToSend = -1; + switch (mode) + { + case EMarketMode::CREATURE_RESOURCE: + leftIdToSend = hLeft->serial; + break; + case EMarketMode::ARTIFACT_RESOURCE: + leftIdToSend = hLeft->getArtInstance()->id.getNum(); + break; + default: + leftIdToSend = hLeft->id; + break; + } + + if(slider) + { + LOCPLINT->cb->trade(market->o, mode, leftIdToSend, hRight->id, slider->getValue()*r1, hero); + slider->moveTo(0); + } + else + { + LOCPLINT->cb->trade(market->o, mode, leftIdToSend, hRight->id, r2, hero); + } + madeTransaction = true; + + hLeft = nullptr; + hRight = nullptr; + selectionChanged(true); +} + +void CMarketplaceWindow::sliderMoved( int to ) +{ + redraw(); +} + +void CMarketplaceWindow::selectionChanged(bool side) +{ + readyToTrade = hLeft && hRight; + if(mode == EMarketMode::RESOURCE_RESOURCE) + readyToTrade = readyToTrade && (hLeft->id != hRight->id); //for resource trade, two DIFFERENT resources must be selected + + if(mode == EMarketMode::ARTIFACT_RESOURCE && !hLeft) + arts->unmarkSlots(false); + + if(readyToTrade) + { + int soldItemId = hLeft->id; + market->getOffer(soldItemId, hRight->id, r1, r2, mode); + + if(slider) + { + int newAmount = -1; + if(itemsType[1] == RESOURCE) + newAmount = LOCPLINT->cb->getResourceAmount(static_cast(soldItemId)); + else if(itemsType[1] == CREATURE) + newAmount = hero->getStackCount(SlotID(hLeft->serial)) - (hero->Slots().size() == 1 && hero->needsLastStack()); + else + assert(0); + + slider->setAmount(newAmount / r1); + slider->moveTo(0); + max->block(false); + deal->block(false); + } + else if(itemsType[1] == RESOURCE) //buying -> check if we can afford transaction + { + deal->block(LOCPLINT->cb->getResourceAmount(static_cast(soldItemId)) < r1); + } + else + deal->block(false); + } + else + { + if(slider) + { + max->block(true); + slider->setAmount(0); + slider->moveTo(0); + } + deal->block(true); + } + + if(side && itemsType[0] != PLAYER) //items[1] selection changed, recalculate offers + initSubs(false); + + updateTraderText(); + redraw(); +} + +bool CMarketplaceWindow::printButtonFor(EMarketMode::EMarketMode M) const +{ + return market->allowsTrade(M) && M != mode && (hero || ( M != EMarketMode::CREATURE_RESOURCE && M != EMarketMode::RESOURCE_ARTIFACT && M != EMarketMode::ARTIFACT_RESOURCE )); +} + +void CMarketplaceWindow::garrisonChanged() +{ + if(mode != EMarketMode::CREATURE_RESOURCE) + return; + + std::set toRemove; + getEmptySlots(toRemove); + + + removeItems(toRemove); + initSubs(true); +} + +void CMarketplaceWindow::artifactsChanged(bool Left) +{ + assert(!Left); + if(mode != EMarketMode::RESOURCE_ARTIFACT) + return; + + std::vector available = market->availableItemsIds(mode); + std::set toRemove; + for(CTradeableItem *t : items[0]) + if(!vstd::contains(available, t->id)) + toRemove.insert(t); + + removeItems(toRemove); + redraw(); +} + +std::string CMarketplaceWindow::selectionSubtitle(bool Left) const +{ + if(Left) + { + switch(itemsType[1]) + { + case RESOURCE: + case CREATURE: + { + int val = slider + ? slider->getValue() * r1 + : (((deal->isBlocked())) ? 0 : r1); + + return boost::lexical_cast(val); + } + case ARTIFACT_INSTANCE: + return ((deal->isBlocked()) ? "0" : "1"); + } + } + else + { + switch(itemsType[0]) + { + case RESOURCE: + if(slider) + return boost::lexical_cast( slider->getValue() * r2 ); + else + return boost::lexical_cast(r2); + case ARTIFACT_TYPE: + return ((deal->isBlocked()) ? "0" : "1"); + case PLAYER: + return (hRight ? CGI->generaltexth->capColors[hRight->id] : ""); + } + } + + return "???"; +} + +Point CMarketplaceWindow::selectionOffset(bool Left) const +{ + if(Left) + { + switch(itemsType[1]) + { + case RESOURCE: + return Point(122, 446); + case CREATURE: + return Point(128, 450); + case ARTIFACT_INSTANCE: + return Point(134, 466); + } + } + else + { + switch(itemsType[0]) + { + case RESOURCE: + if(mode == EMarketMode::ARTIFACT_RESOURCE) + return Point(410, 469); + else + return Point(410, 446); + case ARTIFACT_TYPE: + return Point(425, 447); + case PLAYER: + return Point(417, 451); + } + } + + assert(0); + return Point(0,0); +} + +void CMarketplaceWindow::resourceChanged(int type, int val) +{ + initSubs(true); +} + +void CMarketplaceWindow::getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const +{ + switch(type) + { + case RESOURCE: + dx = 82; + dy = 79; + x = 39; + y = 180; + h = 66; + w = 74; + break; + case PLAYER: + dx = 83; + dy = 118; + h = 64; + w = 58; + x = 44; + y = 83; + assert(Right); + break; + case CREATURE://45,123 + x = 45; + y = 123; + w = 58; + h = 64; + dx = 83; + dy = 98; + assert(!Right); + break; + case ARTIFACT_TYPE://45,123 + x = 340-289; + y = 180; + w = 44; + h = 44; + dx = 83; + dy = 79; + break; + } + + leftToRightOffset = 289; +} + +void CMarketplaceWindow::updateTraderText() +{ + if(readyToTrade) + { + if(mode == EMarketMode::RESOURCE_PLAYER) + { + //I can give %s to the %s player. + traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[165]) % hLeft->getName() % hRight->getName())); + } + else if(mode == EMarketMode::RESOURCE_ARTIFACT) + { + //I can offer you the %s for %d %s of %s. + traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[267]) % hRight->getName() % r1 % CGI->generaltexth->allTexts[160 + (r1==1)] % hLeft->getName())); + } + else if(mode == EMarketMode::RESOURCE_RESOURCE) + { + //I can offer you %d %s of %s for %d %s of %s. + traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[157]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % r1 % CGI->generaltexth->allTexts[160 + (r1==1)] % hLeft->getName())); + } + else if(mode == EMarketMode::CREATURE_RESOURCE) + { + //I can offer you %d %s of %s for %d %s. + traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[269]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % r1 % hLeft->getName(r1))); + } + else if(mode == EMarketMode::ARTIFACT_RESOURCE) + { + //I can offer you %d %s of %s for your %s. + traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[268]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % hLeft->getName(r1))); + } + return; + } + + int gnrtxtnr = -1; + if(madeTransaction) + { + if(mode == EMarketMode::RESOURCE_PLAYER) + gnrtxtnr = 166; //Are there any other resources you'd like to give away? + else + gnrtxtnr = 162; //You have received quite a bargain. I expect to make no profit on the deal. Can I interest you in any of my other wares? + } + else + { + if(mode == EMarketMode::RESOURCE_PLAYER) + gnrtxtnr = 167; //If you'd like to give any of your resources to another player, click on the item you wish to give and to whom. + else + gnrtxtnr = 163; //Please inspect our fine wares. If you feel like offering a trade, click on the items you wish to trade with and for. + } + traderText->setText(CGI->generaltexth->allTexts[gnrtxtnr]); +} + +CAltarWindow::CAltarWindow(const IMarket *Market, const CGHeroInstance *Hero /*= nullptr*/, EMarketMode::EMarketMode Mode) + :CTradeWindow((Mode == EMarketMode::CREATURE_EXP ? "ALTARMON.bmp" : "ALTRART2.bmp"), Market, Hero, Mode) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + if(Mode == EMarketMode::CREATURE_EXP) + { + //%s's Creatures + new CLabel(155, 30, FONT_SMALL, CENTER, Colors::YELLOW, + boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)); + + //Altar of Sacrifice + new CLabel(450, 30, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[479]); + + //To sacrifice creatures, move them from your army on to the Altar and click Sacrifice + new CTextBox(CGI->generaltexth->allTexts[480], Rect(320, 56, 256, 40), 0, FONT_SMALL, CENTER, Colors::YELLOW); + + slider = new CSlider(Point(231,481),137,std::bind(&CAltarWindow::sliderMoved,this,_1),0,0); + max = new CButton(Point(147, 520), "IRCBTNS.DEF", CGI->generaltexth->zelp[578], std::bind(&CSlider::moveToMax, slider)); + + sacrificedUnits.resize(GameConstants::ARMY_SIZE, 0); + sacrificeAll = new CButton(Point(393, 520), "ALTARMY.DEF", CGI->generaltexth->zelp[579], std::bind(&CAltarWindow::SacrificeAll,this)); + sacrificeBackpack = nullptr; + + initItems(true); + mimicCres(); + artIcon = nullptr; + } + else + { + //Sacrifice artifacts for experience + new CLabel(450, 34, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[477]); + //%s's Creatures + new CLabel(302, 423, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[478]); + + sacrificeAll = new CButton(Point(393, 520), "ALTFILL.DEF", CGI->generaltexth->zelp[571], std::bind(&CAltarWindow::SacrificeAll,this)); + sacrificeAll->block(hero->artifactsInBackpack.empty() && hero->artifactsWorn.empty()); + sacrificeBackpack = new CButton(Point(147, 520), "ALTEMBK.DEF", CGI->generaltexth->zelp[570], std::bind(&CAltarWindow::SacrificeBackpack,this)); + sacrificeBackpack->block(hero->artifactsInBackpack.empty()); + + slider = nullptr; + max = nullptr; + + initItems(true); + initItems(false); + artIcon = new CAnimImage("ARTIFACT", 0, 0, 281, 442); + artIcon->disable(); + } + + //Experience needed to reach next level + new CTextBox(CGI->generaltexth->allTexts[475], Rect(15, 415, 125, 50), 0, FONT_SMALL, CENTER, Colors::YELLOW); + //Total experience on the Altar + new CTextBox(CGI->generaltexth->allTexts[476], Rect(15, 495, 125, 40), 0, FONT_SMALL, CENTER, Colors::YELLOW); + + new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); + + ok = new CButton(Point(516, 520), "IOK6432.DEF", CGI->generaltexth->zelp[568], [&]{ close();}, SDLK_RETURN); + ok->assignedKeys.insert(SDLK_ESCAPE); + + deal = new CButton(Point(269, 520), "ALTSACR.DEF", CGI->generaltexth->zelp[585], std::bind(&CAltarWindow::makeDeal,this)); + + if(Hero->getAlignment() != ::EAlignment::EVIL && Mode == EMarketMode::CREATURE_EXP) + new CButton(Point(516, 421), "ALTART.DEF", CGI->generaltexth->zelp[580], std::bind(&CTradeWindow::setMode,this, EMarketMode::ARTIFACT_EXP)); + if(Hero->getAlignment() != ::EAlignment::GOOD && Mode == EMarketMode::ARTIFACT_EXP) + new CButton(Point(516, 421), "ALTSACC.DEF", CGI->generaltexth->zelp[572], std::bind(&CTradeWindow::setMode,this, EMarketMode::CREATURE_EXP)); + + expPerUnit.resize(GameConstants::ARMY_SIZE, 0); + getExpValues(); + + expToLevel = new CLabel(73, 475, FONT_SMALL, CENTER); + expOnAltar = new CLabel(73, 543, FONT_SMALL, CENTER); + + setExpToLevel(); + calcTotalExp(); + blockTrade(); +} + +CAltarWindow::~CAltarWindow() +{ + +} + +void CAltarWindow::getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const +{ + leftToRightOffset = 289; + x = 45; + y = 110; + w = 58; + h = 64; + dx = 83; + dy = 98; +} + +void CAltarWindow::sliderMoved(int to) +{ + sacrificedUnits[hLeft->serial] = to; + updateRight(hRight); + deal->block(!to); + calcTotalExp(); + redraw(); +} + +void CAltarWindow::makeDeal() +{ + if(mode == EMarketMode::CREATURE_EXP) + { + blockTrade(); + slider->moveTo(0); + + std::vector toSacrifice = sacrificedUnits; + for (int i = 0; i < toSacrifice.size(); i++) + { + if(toSacrifice[i]) + LOCPLINT->cb->trade(market->o, mode, i, 0, toSacrifice[i], hero); + } + + for(int& val : sacrificedUnits) + val = 0; + + for(CTradeableItem *t : items[0]) + { + t->setType(CREATURE_PLACEHOLDER); + t->subtitle = ""; + } + } + else + { + for(const CArtifactInstance *art : arts->artifactsOnAltar) //sacrifice each artifact on the list + { + LOCPLINT->cb->trade(market->o, mode, hero->getArtPos(art), -1, 1, hero); + } + arts->artifactsOnAltar.clear(); + + for(CTradeableItem *t : items[0]) + { + t->setID(-1); + t->subtitle = ""; + } + + arts->commonInfo->reset(); + //arts->scrollBackpack(0); + deal->block(true); + } + + calcTotalExp(); +} + +void CAltarWindow::SacrificeAll() +{ + if(mode == EMarketMode::CREATURE_EXP) + { + bool movedAnything = false; + for(CTradeableItem *t : items[1]) + sacrificedUnits[t->serial] = hero->getStackCount(SlotID(t->serial)); + + sacrificedUnits[items[1].front()->serial]--; + + for(CTradeableItem *t : items[0]) + { + updateRight(t); + if(t->type == CREATURE) + movedAnything = true; + } + + deal->block(!movedAnything); + calcTotalExp(); + } + else + { + for(auto i = hero->artifactsWorn.cbegin(); i != hero->artifactsWorn.cend(); i++) + { + if(i->second.artifact->artType->id != ArtifactID::ART_LOCK) //ignore locks from assembled artifacts + moveFromSlotToAltar(i->first, nullptr, i->second.artifact); + } + + SacrificeBackpack(); + } + redraw(); +} + +void CAltarWindow::selectionChanged(bool side) +{ + if(mode != EMarketMode::CREATURE_EXP) + return; + + CTradeableItem *&selected = side ? hLeft : hRight; + CTradeableItem *&theOther = side ? hRight : hLeft; + + theOther = *std::find_if(items[!side].begin(), items[!side].end(), [&](const CTradeableItem * item) + { + return item->serial == selected->serial; + }); + + int stackCount = 0; + for (int i = 0; i < GameConstants::ARMY_SIZE; i++) + if(hero->getStackCount(SlotID(i)) > sacrificedUnits[i]) + stackCount++; + + slider->setAmount(hero->getStackCount(SlotID(hLeft->serial)) - (stackCount == 1)); + slider->block(!slider->getAmount()); + slider->moveTo(sacrificedUnits[hLeft->serial]); + max->block(!slider->getAmount()); + readyToTrade = true; + redraw(); +} + +void CAltarWindow::mimicCres() +{ + std::vector positions; + getPositionsFor(positions, false, CREATURE); + + for(CTradeableItem *t : items[1]) + { + auto hlp = new CTradeableItem(positions[t->serial].topLeft(), CREATURE_PLACEHOLDER, t->id, false, t->serial); + hlp->pos = positions[t->serial] + this->pos.topLeft(); + items[0].push_back(hlp); + } +} + +Point CAltarWindow::selectionOffset(bool Left) const +{ + if(Left) + return Point(150, 421); + else + return Point(396, 421); +} + +std::string CAltarWindow::selectionSubtitle(bool Left) const +{ + if(Left && slider && hLeft) + return boost::lexical_cast(slider->getValue()); + else if(!Left && hRight) + return hRight->subtitle; + else + return ""; +} + +void CAltarWindow::artifactsChanged(bool left) +{ + +} + +void CAltarWindow::garrisonChanged() +{ + if(mode != EMarketMode::CREATURE_EXP) + return; + + std::set empty; + getEmptySlots(empty); + + for(CTradeableItem *t : empty) + { + removeItem(*std::find_if(items[0].begin(), items[0].end(), [&](const CTradeableItem * item) + { + return item->serial == t->serial; + })); + } + + initSubs(true); + getExpValues(); +} + +void CAltarWindow::getExpValues() +{ + int dump; + for(CTradeableItem *t : items[1]) + if(t->id >= 0) + market->getOffer(t->id, 0, dump, expPerUnit[t->serial], EMarketMode::CREATURE_EXP); +} + +void CAltarWindow::calcTotalExp() +{ + int val = 0; + if(mode == EMarketMode::CREATURE_EXP) + { + for (int i = 0; i < sacrificedUnits.size(); i++) + { + val += expPerUnit[i] * sacrificedUnits[i]; + } + } + else + { + for(const CArtifactInstance *art : arts->artifactsOnAltar) + { + int dmp, valOfArt; + market->getOffer(art->artType->id, 0, dmp, valOfArt, mode); + val += valOfArt; //WAS val += valOfArt * arts->artifactsOnAltar.count(*i); + } + } + val = hero->calculateXp(val); + expOnAltar->setText(boost::lexical_cast(val)); +} + +void CAltarWindow::setExpToLevel() +{ + expToLevel->setText(boost::lexical_cast(CGI->heroh->reqExp(CGI->heroh->level(hero->exp)+1) - hero->exp)); +} + +void CAltarWindow::blockTrade() +{ + hLeft = hRight = nullptr; + readyToTrade = false; + if(slider) + { + slider->block(true); + max->block(true); + } + deal->block(true); +} + +void CAltarWindow::updateRight(CTradeableItem *toUpdate) +{ + int val = sacrificedUnits[toUpdate->serial]; + toUpdate->setType(val ? CREATURE : CREATURE_PLACEHOLDER); + toUpdate->subtitle = val ? boost::str(boost::format(CGI->generaltexth->allTexts[122]) % boost::lexical_cast(val * expPerUnit[toUpdate->serial])) : ""; //%s exp +} + +int CAltarWindow::firstFreeSlot() +{ + int ret = -1; + while(items[0][++ret]->id >= 0 && ret + 1 < items[0].size()); + return ret < items[0].size() ? ret : -1; +} + +void CAltarWindow::SacrificeBackpack() +{ + std::multiset toOmmit = arts->artifactsOnAltar; + + for (auto & elem : hero->artifactsInBackpack) + { + + if(vstd::contains(toOmmit, elem.artifact)) + { + toOmmit -= elem.artifact; + continue; + } + + putOnAltar(nullptr, elem.artifact); + } + + arts->scrollBackpack(0); + calcTotalExp(); +} + +void CAltarWindow::artifactPicked() +{ + redraw(); +} + +void CAltarWindow::showAll(SDL_Surface * to) +{ + CTradeWindow::showAll(to); + if(mode == EMarketMode::ARTIFACT_EXP && arts && arts->commonInfo->src.art) + { + artIcon->setFrame(arts->commonInfo->src.art->artType->iconIndex); + artIcon->showAll(to); + + int dmp, val; + market->getOffer(arts->commonInfo->src.art->artType->id, 0, dmp, val, EMarketMode::ARTIFACT_EXP); + printAtMiddleLoc(boost::lexical_cast(val), 304, 498, FONT_SMALL, Colors::WHITE, to); + } +} + +bool CAltarWindow::putOnAltar(CTradeableItem* altarSlot, const CArtifactInstance *art) +{ + int artID = art->artType->id; + if(artID != 1 && artID < 7) //special art + { + logGlobal->warnStream() << "Cannot put special artifact on altar!"; + return false; + } + + if(!altarSlot) + { + int slotIndex = firstFreeSlot(); + if(slotIndex < 0) + { + logGlobal->warnStream() << "No free slots on altar!"; + return false; + } + altarSlot = items[0][slotIndex]; + } + + int dmp, val; + market->getOffer(artID, 0, dmp, val, EMarketMode::ARTIFACT_EXP); + + arts->artifactsOnAltar.insert(art); + altarSlot->setArtInstance(art); + altarSlot->subtitle = boost::lexical_cast(val); + + deal->block(false); + return true; +} + +void CAltarWindow::moveFromSlotToAltar(ArtifactPosition slotID, CTradeableItem* altarSlot, const CArtifactInstance *art) +{ + auto freeBackpackSlot = ArtifactPosition(hero->artifactsInBackpack.size() + GameConstants::BACKPACK_START); + if(arts->commonInfo->src.art) + { + arts->commonInfo->dst.slotID = freeBackpackSlot; + arts->commonInfo->dst.AOH = arts; + } + + if(putOnAltar(altarSlot, art)) + { + if(slotID < GameConstants::BACKPACK_START) + LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero, slotID), ArtifactLocation(hero, freeBackpackSlot)); + else + { + arts->commonInfo->src.clear(); + arts->commonInfo->dst.clear(); + CCS->curh->dragAndDropCursor(nullptr); + arts->unmarkSlots(false); + } + } +} diff --git a/client/windows/CTradeWindow.h b/client/windows/CTradeWindow.h new file mode 100644 index 000000000..fde6da502 --- /dev/null +++ b/client/windows/CTradeWindow.h @@ -0,0 +1,169 @@ +#pragma once + +#include "../widgets/CArtifactHolder.h" +#include "CWindowObject.h" +#include "../../lib/FunctionList.h" + +/* + * CTradeWindow.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 + * + */ + +class IMarket; +class CSlider; +class CTextBox; + +class CTradeWindow : public CWindowObject, public CWindowWithArtifacts //base for markets and altar of sacrifice +{ +public: + enum EType + { + RESOURCE, PLAYER, ARTIFACT_TYPE, CREATURE, CREATURE_PLACEHOLDER, ARTIFACT_PLACEHOLDER, ARTIFACT_INSTANCE + }; + class CTradeableItem : public CIntObject + { + CAnimImage * image; + + std::string getFilename(); + int getIndex(); + public: + const CArtifactInstance *hlp; //holds ptr to artifact instance id type artifact + EType type; + int id; + const int serial; + const bool left; + std::string subtitle; //empty if default + + void setType(EType newType); + void setID(int newID); + + const CArtifactInstance *getArtInstance() const; + void setArtInstance(const CArtifactInstance *art); + + CFunctionList callback; + bool downSelection; + + void showAllAt(const Point &dstPos, const std::string &customSub, SDL_Surface * to); + + void clickRight(tribool down, bool previousState); + void hover (bool on); + void showAll(SDL_Surface * to); + void clickLeft(tribool down, bool previousState); + std::string getName(int number = -1) const; + CTradeableItem(Point pos, EType Type, int ID, bool Left, int Serial); + }; + + const IMarket *market; + const CGHeroInstance *hero; + + CArtifactsOfHero *arts; + //all indexes: 1 = left, 0 = right + std::vector items[2]; + CTradeableItem *hLeft, *hRight; //highlighted items (nullptr if no highlight) + EType itemsType[2]; + + EMarketMode::EMarketMode mode;//0 - res<->res; 1 - res<->plauer; 2 - buy artifact; 3 - sell artifact + CButton *ok, *max, *deal; + CSlider *slider; //for choosing amount to be exchanged + bool readyToTrade; + + CTradeWindow(std::string bgName, const IMarket *Market, const CGHeroInstance *Hero, EMarketMode::EMarketMode Mode); //c + + void showAll(SDL_Surface * to); + + void initSubs(bool Left); + void initTypes(); + void initItems(bool Left); + std::vector *getItemsIds(bool Left); //nullptr if default + void getPositionsFor(std::vector &poss, bool Left, EType type) const; + void removeItems(const std::set &toRemove); + void removeItem(CTradeableItem * t); + void getEmptySlots(std::set &toRemove); + void setMode(EMarketMode::EMarketMode Mode); //mode setter + + void artifactSelected(CArtPlace *slot); //used when selling artifacts -> called when user clicked on artifact slot + + virtual void getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const = 0; + virtual void selectionChanged(bool side) = 0; //true == left + virtual Point selectionOffset(bool Left) const = 0; + virtual std::string selectionSubtitle(bool Left) const = 0; + virtual void garrisonChanged() = 0; + virtual void artifactsChanged(bool left) = 0; +}; + +class CMarketplaceWindow : public CTradeWindow +{ + bool printButtonFor(EMarketMode::EMarketMode M) const; + + std::string getBackgroundForMode(EMarketMode::EMarketMode mode); +public: + int r1, r2; //suggested amounts of traded resources + bool madeTransaction; //if player made at least one transaction + CTextBox *traderText; + + void setMax(); + void sliderMoved(int to); + void makeDeal(); + void selectionChanged(bool side); //true == left + CMarketplaceWindow(const IMarket *Market, const CGHeroInstance *Hero = nullptr, EMarketMode::EMarketMode Mode = EMarketMode::RESOURCE_RESOURCE); //c-tor + ~CMarketplaceWindow(); //d-tor + + Point selectionOffset(bool Left) const; + std::string selectionSubtitle(bool Left) const; + + + void garrisonChanged(); //removes creatures with count 0 from the list (apparently whole stack has been sold) + void artifactsChanged(bool left); + void resourceChanged(int type, int val); + + void getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const; + void updateTraderText(); +}; + +class CAltarWindow : public CTradeWindow +{ + CAnimImage * artIcon; +public: + CAltarWindow(const IMarket *Market, const CGHeroInstance *Hero, EMarketMode::EMarketMode Mode); //c-tor + + void getExpValues(); + ~CAltarWindow(); //d-tor + + std::vector sacrificedUnits, //[slot_nr] -> how many creatures from that slot will be sacrificed + expPerUnit; + + CButton *sacrificeAll, *sacrificeBackpack; + CLabel *expToLevel, *expOnAltar; + + + void selectionChanged(bool side); //true == left + void SacrificeAll(); + void SacrificeBackpack(); + + void putOnAltar(int backpackIndex); + bool putOnAltar(CTradeableItem* altarSlot, const CArtifactInstance *art); + void makeDeal(); + void showAll(SDL_Surface * to); + + void blockTrade(); + void sliderMoved(int to); + void getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const; + void mimicCres(); + + Point selectionOffset(bool Left) const; + std::string selectionSubtitle(bool Left) const; + void garrisonChanged(); + void artifactsChanged(bool left); + void calcTotalExp(); + void setExpToLevel(); + void updateRight(CTradeableItem *toUpdate); + + void artifactPicked(); + int firstFreeSlot(); + void moveFromSlotToAltar(ArtifactPosition slotID, CTradeableItem* altarSlot, const CArtifactInstance *art); +}; diff --git a/client/windows/CWindowObject.cpp b/client/windows/CWindowObject.cpp new file mode 100644 index 000000000..e31e27c0e --- /dev/null +++ b/client/windows/CWindowObject.cpp @@ -0,0 +1,242 @@ +#include "StdInc.h" +#include "CWindowObject.h" + +#include "../widgets/MiscWidgets.h" + +#include "../gui/SDL_Pixels.h" +#include "../gui/SDL_Extensions.h" +#include "../gui/CGuiHandler.h" +#include "../gui/CCursorHandler.h" + +#include "../battle/CBattleInterface.h" +#include "../battle/CBattleInterfaceClasses.h" + +#include "../CBitmapHandler.h" +#include "../Graphics.h" +#include "../CGameInfo.h" +#include "../CPlayerInterface.h" +#include "../CMessage.h" +#include "../CMusicHandler.h" +#include "../windows/CAdvmapInterface.h" + +#include "../../CCallback.h" + +#include "../../lib/CConfigHandler.h" +#include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff + +/* + * CWindowObject.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 + * + */ + +CWindowObject::CWindowObject(int options_, std::string imageName, Point centerAt): + CIntObject(getUsedEvents(options_), Point()), + shadow(nullptr), + options(options_), + background(createBg(imageName, options & PLAYER_COLORED)) +{ + assert(parent == nullptr); //Safe to remove, but windows should not have parent + + if (options & RCLICK_POPUP) + CCS->curh->hide(); + + if (background) + pos = background->center(centerAt); + else + center(centerAt); + + if (!(options & SHADOW_DISABLED)) + setShadow(true); +} + +CWindowObject::CWindowObject(int options_, std::string imageName): + CIntObject(getUsedEvents(options_), Point()), + shadow(nullptr), + options(options_), + background(createBg(imageName, options & PLAYER_COLORED)) +{ + assert(parent == nullptr); //Safe to remove, but windows should not have parent + + if (options & RCLICK_POPUP) + CCS->curh->hide(); + + if (background) + pos = background->center(); + else + center(Point(screen->w/2, screen->h/2)); + + if (!(options & SHADOW_DISABLED)) + setShadow(true); +} + +CWindowObject::~CWindowObject() +{ + setShadow(false); +} + +CPicture * CWindowObject::createBg(std::string imageName, bool playerColored) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + if (imageName.empty()) + return nullptr; + + auto image = new CPicture(imageName); + if (playerColored) + image->colorize(LOCPLINT->playerID); + return image; +} + +void CWindowObject::setBackground(std::string filename) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + delete background; + background = createBg(filename, options & PLAYER_COLORED); + + if (background) + pos = background->center(Point(pos.w/2 + pos.x, pos.h/2 + pos.y)); + + updateShadow(); +} + +int CWindowObject::getUsedEvents(int options) +{ + if (options & RCLICK_POPUP) + return RCLICK; + return 0; +} + +void CWindowObject::updateShadow() +{ + setShadow(false); + if (!(options & SHADOW_DISABLED)) + setShadow(true); +} + +void CWindowObject::setShadow(bool on) +{ + //size of shadow + static const int size = 8; + + if (on == bool(shadow)) + return; + + vstd::clear_pointer(shadow); + + //object too small to cast shadow + if (pos.h <= size || pos.w <= size) + return; + + if (on) + { + + //helper to set last row + auto blitAlphaRow = [](SDL_Surface *surf, size_t row) + { + Uint8 * ptr = (Uint8*)surf->pixels + surf->pitch * (row); + + for (size_t i=0; i< surf->w; i++) + { + Channels::px<4>::a.set(ptr, 128); + ptr+=4; + } + }; + + // helper to set last column + auto blitAlphaCol = [](SDL_Surface *surf, size_t col) + { + Uint8 * ptr = (Uint8*)surf->pixels + 4 * (col); + + for (size_t i=0; i< surf->h; i++) + { + Channels::px<4>::a.set(ptr, 128); + ptr+= surf->pitch; + } + }; + + static SDL_Surface * shadowCornerTempl = nullptr; + static SDL_Surface * shadowBottomTempl = nullptr; + static SDL_Surface * shadowRightTempl = nullptr; + + //one-time initialization + if (!shadowCornerTempl) + { + //create "template" surfaces + shadowCornerTempl = CSDL_Ext::createSurfaceWithBpp<4>(size, size); + shadowBottomTempl = CSDL_Ext::createSurfaceWithBpp<4>(1, size); + shadowRightTempl = CSDL_Ext::createSurfaceWithBpp<4>(size, 1); + + Uint32 shadowColor = SDL_MapRGBA(shadowCornerTempl->format, 0, 0, 0, 192); + + //fill with shadow body color + SDL_FillRect(shadowCornerTempl, nullptr, shadowColor); + SDL_FillRect(shadowBottomTempl, nullptr, shadowColor); + SDL_FillRect(shadowRightTempl, nullptr, shadowColor); + + //fill last row and column with more transparent color + blitAlphaCol(shadowRightTempl , size-1); + blitAlphaCol(shadowCornerTempl, size-1); + blitAlphaRow(shadowBottomTempl, size-1); + blitAlphaRow(shadowCornerTempl, size-1); + } + + OBJ_CONSTRUCTION_CAPTURING_ALL; + + //FIXME: do something with this points + Point shadowStart; + if (options & BORDERED) + shadowStart = Point(size - 14, size - 14); + else + shadowStart = Point(size, size); + + Point shadowPos; + if (options & BORDERED) + shadowPos = Point(pos.w + 14, pos.h + 14); + else + shadowPos = Point(pos.w, pos.h); + + Point fullsize; + if (options & BORDERED) + fullsize = Point(pos.w + 28, pos.h + 29); + else + fullsize = Point(pos.w, pos.h); + + //create base 8x8 piece of shadow + SDL_Surface * shadowCorner = CSDL_Ext::copySurface(shadowCornerTempl); + SDL_Surface * shadowBottom = CSDL_Ext::scaleSurfaceFast(shadowBottomTempl, fullsize.x - size, size); + SDL_Surface * shadowRight = CSDL_Ext::scaleSurfaceFast(shadowRightTempl, size, fullsize.y - size); + + blitAlphaCol(shadowBottom, 0); + blitAlphaRow(shadowRight, 0); + + //generate "shadow" object with these 3 pieces in it + shadow = new CIntObject; + shadow->addChild(new CPicture(shadowCorner, shadowPos.x, shadowPos.y)); + shadow->addChild(new CPicture(shadowRight, shadowPos.x, shadowStart.y)); + shadow->addChild(new CPicture(shadowBottom, shadowStart.x, shadowPos.y)); + } +} + +void CWindowObject::showAll(SDL_Surface *to) +{ + CIntObject::showAll(to); + if ((options & BORDERED) && (pos.h != to->h || pos.w != to->w)) + CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to, pos.w+28, pos.h+29, pos.x-14, pos.y-15); +} + +void CWindowObject::close() +{ + GH.popIntTotally(this); +} + +void CWindowObject::clickRight(tribool down, bool previousState) +{ + close(); + CCS->curh->show(); +} diff --git a/client/windows/CWindowObject.h b/client/windows/CWindowObject.h new file mode 100644 index 000000000..eae6503e9 --- /dev/null +++ b/client/windows/CWindowObject.h @@ -0,0 +1,65 @@ +#pragma once + +#include "../gui/CIntObject.h" +//#include "../gui/SDL_Extensions.h" + +//#include "../../lib/FunctionList.h" + +struct SDL_Surface; +struct Rect; +class CAnimImage; +class CLabel; +class CAnimation; +class CDefHandler; + +/* + * CWindowObject.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 + * + */ + +/// Basic class for windows +class CWindowObject : public CIntObject +{ + CPicture * createBg(std::string imageName, bool playerColored); + int getUsedEvents(int options); + + CIntObject *shadow; + void setShadow(bool on); + + int options; + +protected: + CPicture * background; + + //Simple function with call to GH.popInt + void close(); + //Used only if RCLICK_POPUP was set + void clickRight(tribool down, bool previousState); + //To display border + void showAll(SDL_Surface *to); + //change or set background image + void setBackground(std::string filename); + void updateShadow(); +public: + enum EOptions + { + PLAYER_COLORED=1, //background will be player-colored + RCLICK_POPUP=2, // window will behave as right-click popup + BORDERED=4, // window will have border if current resolution is bigger than size of window + SHADOW_DISABLED=8 //this window won't display any shadow + }; + + /* + * options - EOpions enum + * imageName - name for background image, can be empty + * centerAt - position of window center. Default - center of the screen + */ + CWindowObject(int options, std::string imageName, Point centerAt); + CWindowObject(int options, std::string imageName = ""); + ~CWindowObject(); +}; diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp new file mode 100644 index 000000000..c91702bef --- /dev/null +++ b/client/windows/GUIClasses.cpp @@ -0,0 +1,1829 @@ +#include "StdInc.h" +#include "GUIClasses.h" + +#include "CAdvmapInterface.h" +#include "CCastleInterface.h" +#include "CCreatureWindow.h" +#include "CHeroWindow.h" +#include "CSpellWindow.h" + +#include "../CBitmapHandler.h" +#include "../CDefHandler.h" +#include "../CGameInfo.h" +#include "../CMessage.h" +#include "../CMusicHandler.h" +#include "../CPlayerInterface.h" +#include "../CPreGame.h" +#include "../CVideoHandler.h" +#include "../Graphics.h" +#include "../mapHandler.h" + +#include "../battle/CBattleInterfaceClasses.h" +#include "../battle/CBattleInterface.h" +#include "../battle/CCreatureAnimation.h" + +#include "../gui/CGuiHandler.h" +#include "../gui/SDL_Extensions.h" +#include "../gui/CCursorHandler.h" + +#include "../widgets/CComponent.h" +#include "../widgets/MiscWidgets.h" +#include "../windows/InfoWindows.h" + +#include "../../CCallback.h" + +#include "../lib/BattleState.h" +#include "../lib/CArtHandler.h" +#include "../lib/CBuildingHandler.h" +#include "../lib/CConfigHandler.h" +#include "../lib/CCreatureHandler.h" +#include "../lib/CGameState.h" +#include "../lib/CGeneralTextHandler.h" +#include "../lib/CHeroHandler.h" +#include "../lib/CModHandler.h" +#include "../lib/CondSh.h" +#include "../lib/CSpellHandler.h" +#include "../lib/CStopWatch.h" +#include "../lib/CTownHandler.h" +#include "../lib/GameConstants.h" +#include "../lib/HeroBonus.h" +#include "../lib/mapping/CMap.h" +#include "../lib/NetPacksBase.h" +#include "../lib/StartInfo.h" + +/* + * GUIClasses.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 + * + */ + +using namespace boost::assign; +using namespace CSDL_Ext; + +std::list CFocusable::focusables; +CFocusable * CFocusable::inputWithFocus; + +#undef min +#undef max + +CRecruitmentWindow::CCreatureCard::CCreatureCard(CRecruitmentWindow *window, const CCreature *crea, int totalAmount): + CIntObject(LCLICK | RCLICK), + parent(window), + selected(false), + creature(crea), + amount(totalAmount) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + pic = new CCreaturePic(1,1, creature, true, true); + // 1 + 1 px for borders + pos.w = pic->pos.w + 2; + pos.h = pic->pos.h + 2; +} + +void CRecruitmentWindow::CCreatureCard::select(bool on) +{ + selected = on; + redraw(); +} + +void CRecruitmentWindow::CCreatureCard::clickLeft(tribool down, bool previousState) +{ + if (down) + parent->select(this); +} + +void CRecruitmentWindow::CCreatureCard::clickRight(tribool down, bool previousState) +{ + if (down) + GH.pushInt(new CStackWindow(creature, true)); +} + +void CRecruitmentWindow::CCreatureCard::showAll(SDL_Surface * to) +{ + CIntObject::showAll(to); + if (selected) + drawBorder(to, pos, int3(248, 0, 0)); + else + drawBorder(to, pos, int3(232, 212, 120)); +} + +CRecruitmentWindow::CCostBox::CCostBox(Rect position, std::string title) +{ + type |= REDRAW_PARENT; + pos = position + pos; + OBJ_CONSTRUCTION_CAPTURING_ALL; + new CLabel(pos.w/2, 10, FONT_SMALL, CENTER, Colors::WHITE, title); +} + +void CRecruitmentWindow::CCostBox::set(TResources res) +{ + //just update values + for(auto & item : resources) + { + item.second.first->setText(boost::lexical_cast(res[item.first])); + } +} + +void CRecruitmentWindow::CCostBox::createItems(TResources res) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + for(auto & curr : resources) + { + delete curr.second.first; + delete curr.second.second; + } + resources.clear(); + + TResources::nziterator iter(res); + while (iter.valid()) + { + CAnimImage * image = new CAnimImage("RESOURCE", iter->resType); + CLabel * text = new CLabel(15, 43, FONT_SMALL, CENTER, Colors::WHITE, "0"); + + resources.insert(std::make_pair(iter->resType, std::make_pair(text, image))); + iter++; + } + + if (!resources.empty()) + { + int curx = pos.w / 2 - (16 * resources.size()) - (8 * (resources.size() - 1)); + //reverse to display gold as first resource + for (auto & res : boost::adaptors::reverse(resources)) + { + res.second.first->moveBy(Point(curx, 22)); + res.second.second->moveBy(Point(curx, 22)); + curx += 48; + } + } + redraw(); +} + +void CRecruitmentWindow::select(CCreatureCard *card) +{ + if (card == selected) + return; + + if (selected) + selected->select(false); + + selected = card; + + if (selected) + selected->select(true); + + if (card) + { + si32 maxAmount = card->creature->maxAmount(LOCPLINT->cb->getResourceAmount()); + + vstd::amin(maxAmount, card->amount); + + slider->setAmount(maxAmount); + + if(slider->getValue() != maxAmount) + slider->moveTo(maxAmount); + else // if slider already at 0 - emulate call to sliderMoved() + sliderMoved(maxAmount); + + costPerTroopValue->createItems(card->creature->cost); + totalCostValue->createItems(card->creature->cost); + + costPerTroopValue->set(card->creature->cost); + totalCostValue->set(card->creature->cost * maxAmount); + + //Recruit %s + title->setText(boost::str(boost::format(CGI->generaltexth->tcommands[21]) % card->creature->namePl)); + + maxButton->block(maxAmount == 0); + slider->block(maxAmount == 0); + } +} + +void CRecruitmentWindow::buy() +{ + CreatureID crid = selected->creature->idNumber; + SlotID dstslot = dst-> getSlotFor(crid); + + if(!dstslot.validSlot() && !vstd::contains(CGI->arth->bigArtifacts,CGI->arth->creatureToMachineID(crid))) //no available slot + { + std::string txt; + if(dst->ID == Obj::HERO) + { + txt = CGI->generaltexth->allTexts[425]; //The %s would join your hero, but there aren't enough provisions to support them. + boost::algorithm::replace_first(txt, "%s", slider->getValue() > 1 ? CGI->creh->creatures[crid]->namePl : CGI->creh->creatures[crid]->nameSing); + } + else + { + txt = CGI->generaltexth->allTexts[17]; //There is no room in the garrison for this army. + } + + LOCPLINT->showInfoDialog(txt); + return; + } + + onRecruit(crid, slider->getValue()); + if(level >= 0) + close(); +} + +void CRecruitmentWindow::showAll(SDL_Surface * to) +{ + CWindowObject::showAll(to); + + // recruit\total values + drawBorder(to, pos.x + 172, pos.y + 222, 67, 42, int3(239,215,123)); + drawBorder(to, pos.x + 246, pos.y + 222, 67, 42, int3(239,215,123)); + + //cost boxes + drawBorder(to, pos.x + 64, pos.y + 222, 99, 76, int3(239,215,123)); + drawBorder(to, pos.x + 322, pos.y + 222, 99, 76, int3(239,215,123)); + + //buttons borders + drawBorder(to, pos.x + 133, pos.y + 312, 66, 34, int3(173,142,66)); + drawBorder(to, pos.x + 211, pos.y + 312, 66, 34, int3(173,142,66)); + drawBorder(to, pos.x + 289, pos.y + 312, 66, 34, int3(173,142,66)); +} + +CRecruitmentWindow::CRecruitmentWindow(const CGDwelling *Dwelling, int Level, const CArmedInstance *Dst, const std::function &Recruit, int y_offset): + CWindowObject(PLAYER_COLORED, "TPRCRT"), + onRecruit(Recruit), + level(Level), + dst(Dst), + selected(nullptr), + dwelling(Dwelling) +{ + moveBy(Point(0, y_offset)); + + OBJ_CONSTRUCTION_CAPTURING_ALL; + new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); + + slider = new CSlider(Point(176,279),135,std::bind(&CRecruitmentWindow::sliderMoved,this, _1),0,0,0,true); + + maxButton = new CButton(Point(134, 313), "IRCBTNS.DEF", CGI->generaltexth->zelp[553], std::bind(&CSlider::moveToMax,slider), SDLK_m); + buyButton = new CButton(Point(212, 313), "IBY6432.DEF", CGI->generaltexth->zelp[554], std::bind(&CRecruitmentWindow::buy,this), SDLK_RETURN); + cancelButton = new CButton(Point(290, 313), "ICN6432.DEF", CGI->generaltexth->zelp[555], std::bind(&CRecruitmentWindow::close,this), SDLK_ESCAPE); + + title = new CLabel(243, 32, FONT_BIG, CENTER, Colors::YELLOW); + availableValue = new CLabel(205, 253, FONT_SMALL, CENTER, Colors::WHITE); + toRecruitValue = new CLabel(279, 253, FONT_SMALL, CENTER, Colors::WHITE); + + costPerTroopValue = new CCostBox(Rect(65, 222, 97, 74), CGI->generaltexth->allTexts[346]); + totalCostValue = new CCostBox(Rect(323, 222, 97, 74), CGI->generaltexth->allTexts[466]); + + new CLabel(205, 233, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[465]); //available t + new CLabel(279, 233, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[16]); //recruit t + + availableCreaturesChanged(); +} + +void CRecruitmentWindow::availableCreaturesChanged() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + size_t selectedIndex = 0; + + if (!cards.empty() && selected) // find position of selected item + selectedIndex = std::find(cards.begin(), cards.end(), selected) - cards.begin(); + + //deselect card + select(nullptr); + + //delete old cards + for(auto & card : cards) + delete card; + cards.clear(); + + for(int i=0; icreatures.size(); i++) + { + //find appropriate level + if(level >= 0 && i != level) + continue; + + int amount = dwelling->creatures[i].first; + + //create new cards + for(auto & creature : boost::adaptors::reverse(dwelling->creatures[i].second)) + cards.push_back(new CCreatureCard(this, CGI->creh->creatures[creature], amount)); + } + + assert(!cards.empty()); + + const int creatureWidth = 102; + + //normal distance between cards - 18px + int requiredSpace = 18; + //maximum distance we can use without reaching window borders + int availableSpace = pos.w - 50 - creatureWidth * cards.size(); + + if (cards.size() > 1) // avoid division by zero + availableSpace /= cards.size() - 1; + else + availableSpace = 0; + + assert(availableSpace >= 0); + + const int spaceBetween = std::min(requiredSpace, availableSpace); + const int totalCreatureWidth = spaceBetween + creatureWidth; + + //now we know total amount of cards and can move them to correct position + int curx = pos.w / 2 - (creatureWidth*cards.size()/2) - (spaceBetween*(cards.size()-1)/2); + for(auto & card : cards) + { + card->moveBy(Point(curx, 64)); + curx += totalCreatureWidth; + } + + //restore selection + select(cards[selectedIndex]); + + if(slider->getValue() == slider->getAmount()) + slider->moveToMax(); + else // if slider already at 0 - emulate call to sliderMoved() + sliderMoved(slider->getAmount()); +} + +void CRecruitmentWindow::sliderMoved(int to) +{ + if (!selected) + return; + + buyButton->block(!to); + availableValue->setText(boost::lexical_cast(selected->amount - to)); + toRecruitValue->setText(boost::lexical_cast(to)); + + totalCostValue->set(selected->creature->cost * to); +} + +CSplitWindow::CSplitWindow(const CCreature * creature, std::function callback_, + int leftMin_, int rightMin_, int leftAmount_, int rightAmount_): + CWindowObject(PLAYER_COLORED, "GPUCRDIV"), + callback(callback_), + leftAmount(leftAmount_), + rightAmount(rightAmount_), + leftMin(leftMin_), + rightMin(rightMin_), + slider(nullptr) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + int total = leftAmount + rightAmount; + int leftMax = total - rightMin; + int rightMax = total - leftMin; + + ok = new CButton(Point(20, 263), "IOK6432", CButton::tooltip(), std::bind(&CSplitWindow::apply, this), SDLK_RETURN); + cancel = new CButton(Point(214, 263), "ICN6432", CButton::tooltip(), std::bind(&CSplitWindow::close, this), SDLK_ESCAPE); + + int sliderPositions = total - leftMin - rightMin; + + leftInput = new CTextInput(Rect(20, 218, 100, 36), FONT_BIG, std::bind(&CSplitWindow::setAmountText, this, _1, true)); + rightInput = new CTextInput(Rect(176, 218, 100, 36), FONT_BIG, std::bind(&CSplitWindow::setAmountText, this, _1, false)); + + //add filters to allow only number input + leftInput->filters.add(std::bind(&CTextInput::numberFilter, _1, _2, leftMin, leftMax)); + rightInput->filters.add(std::bind(&CTextInput::numberFilter, _1, _2, rightMin, rightMax)); + + leftInput->setText(boost::lexical_cast(leftAmount), false); + rightInput->setText(boost::lexical_cast(rightAmount), false); + + animLeft = new CCreaturePic(20, 54, creature, true, false); + animRight = new CCreaturePic(177, 54,creature, true, false); + + slider = new CSlider(Point(21, 194), 257, std::bind(&CSplitWindow::sliderMoved, this, _1), 0, sliderPositions, rightAmount - rightMin, true); + + std::string title = CGI->generaltexth->allTexts[256]; + boost::algorithm::replace_first(title,"%s", creature->namePl); + new CLabel(150, 34, FONT_BIG, CENTER, Colors::YELLOW, title); +} + +void CSplitWindow::setAmountText(std::string text, bool left) +{ + try + { + setAmount(boost::lexical_cast(text), left); + slider->moveTo(rightAmount - rightMin); + } + catch(boost::bad_lexical_cast &) + { + } +} + +void CSplitWindow::setAmount(int value, bool left) +{ + int total = leftAmount + rightAmount; + leftAmount = left ? value : total - value; + rightAmount = left ? total - value : value; + + leftInput->setText(boost::lexical_cast(leftAmount)); + rightInput->setText(boost::lexical_cast(rightAmount)); +} + +void CSplitWindow::apply() +{ + callback(leftAmount, rightAmount); + close(); +} + +void CSplitWindow::sliderMoved(int to) +{ + setAmount(rightMin + to, false); +} + +CLevelWindow::CLevelWindow(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector &skills, std::function callback): + CWindowObject(PLAYER_COLORED, "LVLUPBKG"), + cb(callback) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + LOCPLINT->showingDialog->setn(true); + + new CAnimImage("PortraitsLarge", hero->portrait, 0, 170, 66); + new CButton(Point(297, 413), "IOKAY", CButton::tooltip(), std::bind(&CLevelWindow::close, this), SDLK_RETURN); + + //%s has gained a level. + new CLabel(192, 33, FONT_MEDIUM, CENTER, Colors::WHITE, + boost::str(boost::format(CGI->generaltexth->allTexts[444]) % hero->name)); + + //%s is now a level %d %s. + new CLabel(192, 162, FONT_MEDIUM, CENTER, Colors::WHITE, + boost::str(boost::format(CGI->generaltexth->allTexts[445]) % hero->name % hero->level % hero->type->heroClass->name)); + + new CAnimImage("PSKIL42", pskill, 0, 174, 190); + + new CLabel(192, 253, FONT_MEDIUM, CENTER, Colors::WHITE, + CGI->generaltexth->primarySkillNames[pskill] + " +1"); + + if (!skills.empty()) + { + std::vector comps; + + for(auto & skill : skills) + { + comps.push_back(new CSelectableComponent( + CComponent::secskill, + skill, + hero->getSecSkillLevel( SecondarySkill(skill) )+1, + CComponent::medium)); + } + box = new CComponentBox(comps, Rect(75, 300, pos.w - 150, 100)); + } + else + box = nullptr; +} + +CLevelWindow::~CLevelWindow() +{ + //FIXME: call callback if there was nothing to select? + if (box && box->selectedIndex() != -1) + cb(box->selectedIndex()); + + LOCPLINT->showingDialog->setn(false); +} + +static void setIntSetting(std::string group, std::string field, int value) +{ + Settings entry = settings.write[group][field]; + entry->Float() = value; +} + +static void setBoolSetting(std::string group, std::string field, bool value) +{ + Settings fullscreen = settings.write[group][field]; + fullscreen->Bool() = value; +} + +CSystemOptionsWindow::CSystemOptionsWindow(): + CWindowObject(PLAYER_COLORED, "SysOpBck"), + onFullscreenChanged(settings.listen["video"]["fullscreen"]) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + title = new CLabel(242, 32, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[568]); + + const JsonNode & texts = CGI->generaltexth->localizedTexts["systemOptions"]; + + //left window section + leftGroup = new CLabelGroup(FONT_MEDIUM, CENTER, Colors::YELLOW); + leftGroup->add(122, 64, CGI->generaltexth->allTexts[569]); + leftGroup->add(122, 130, CGI->generaltexth->allTexts[570]); + leftGroup->add(122, 196, CGI->generaltexth->allTexts[571]); + leftGroup->add(122, 262, texts["resolutionButton"]["label"].String()); + leftGroup->add(122, 347, CGI->generaltexth->allTexts[394]); + leftGroup->add(122, 412, CGI->generaltexth->allTexts[395]); + + //right section + rightGroup = new CLabelGroup(FONT_MEDIUM, TOPLEFT, Colors::WHITE); + rightGroup->add(282, 57, CGI->generaltexth->allTexts[572]); + rightGroup->add(282, 89, CGI->generaltexth->allTexts[573]); + rightGroup->add(282, 121, CGI->generaltexth->allTexts[574]); + rightGroup->add(282, 153, CGI->generaltexth->allTexts[577]); + rightGroup->add(282, 185, texts["creatureWindowButton"]["label"].String()); + rightGroup->add(282, 217, texts["fullscreenButton"]["label"].String()); + + //setting up buttons + load = new CButton (Point(246, 298), "SOLOAD.DEF", CGI->generaltexth->zelp[321], [&] { bloadf(); }, SDLK_l); + load->setImageOrder(1, 0, 2, 3); + + save = new CButton (Point(357, 298), "SOSAVE.DEF", CGI->generaltexth->zelp[322], [&] { bsavef(); }, SDLK_s); + save->setImageOrder(1, 0, 2, 3); + + restart = new CButton (Point(246, 357), "SORSTRT", CGI->generaltexth->zelp[323], [&] { brestartf(); }, SDLK_r); + restart->setImageOrder(1, 0, 2, 3); + + mainMenu = new CButton (Point(357, 357), "SOMAIN.DEF", CGI->generaltexth->zelp[320], [&] { bmainmenuf(); }, SDLK_m); + mainMenu->setImageOrder(1, 0, 2, 3); + + quitGame = new CButton (Point(246, 415), "soquit.def", CGI->generaltexth->zelp[324], [&] { bquitf(); }, SDLK_q); + quitGame->setImageOrder(1, 0, 2, 3); + + backToMap = new CButton ( Point(357, 415), "soretrn.def", CGI->generaltexth->zelp[325], [&] { breturnf(); }, SDLK_RETURN); + backToMap->setImageOrder(1, 0, 2, 3); + backToMap->assignedKeys.insert(SDLK_ESCAPE); + + heroMoveSpeed = new CToggleGroup(0); + heroMoveSpeed->addToggle(1, new CToggleButton(Point( 28, 77), "sysopb1.def", CGI->generaltexth->zelp[349])); + heroMoveSpeed->addToggle(2, new CToggleButton(Point( 76, 77), "sysopb2.def", CGI->generaltexth->zelp[350])); + heroMoveSpeed->addToggle(4, new CToggleButton(Point(124, 77), "sysopb3.def", CGI->generaltexth->zelp[351])); + heroMoveSpeed->addToggle(8, new CToggleButton(Point(172, 77), "sysopb4.def", CGI->generaltexth->zelp[352])); + heroMoveSpeed->setSelected(settings["adventure"]["heroSpeed"].Float()); + heroMoveSpeed->addCallback(std::bind(&setIntSetting, "adventure", "heroSpeed", _1)); + + enemyMoveSpeed = new CToggleGroup(0); + enemyMoveSpeed->addToggle(2, new CToggleButton(Point( 28, 144), "sysopb5.def", CGI->generaltexth->zelp[353])); + enemyMoveSpeed->addToggle(4, new CToggleButton(Point( 76, 144), "sysopb6.def", CGI->generaltexth->zelp[354])); + enemyMoveSpeed->addToggle(8, new CToggleButton(Point(124, 144), "sysopb7.def", CGI->generaltexth->zelp[355])); + enemyMoveSpeed->addToggle(0, new CToggleButton(Point(172, 144), "sysopb8.def", CGI->generaltexth->zelp[356])); + enemyMoveSpeed->setSelected(settings["adventure"]["enemySpeed"].Float()); + enemyMoveSpeed->addCallback(std::bind(&setIntSetting, "adventure", "enemySpeed", _1)); + + mapScrollSpeed = new CToggleGroup(0); + mapScrollSpeed->addToggle(1, new CToggleButton(Point( 28, 210), "sysopb9.def", CGI->generaltexth->zelp[357])); + mapScrollSpeed->addToggle(2, new CToggleButton(Point( 92, 210), "sysob10.def", CGI->generaltexth->zelp[358])); + mapScrollSpeed->addToggle(4, new CToggleButton(Point(156, 210), "sysob11.def", CGI->generaltexth->zelp[359])); + mapScrollSpeed->setSelected(settings["adventure"]["scrollSpeed"].Float()); + mapScrollSpeed->addCallback(std::bind(&setIntSetting, "adventure", "scrollSpeed", _1)); + + musicVolume = new CToggleGroup(0, true); + for(int i=0; i<10; ++i) + musicVolume->addToggle(i*11, new CToggleButton(Point(29 + 19*i, 359), "syslb.def", CGI->generaltexth->zelp[326+i])); + + musicVolume->setSelected(CCS->musich->getVolume()); + musicVolume->addCallback(std::bind(&setIntSetting, "general", "music", _1)); + + effectsVolume = new CToggleGroup(0, true); + for(int i=0; i<10; ++i) + effectsVolume->addToggle(i*11, new CToggleButton(Point(29 + 19*i, 425), "syslb.def", CGI->generaltexth->zelp[336+i])); + + effectsVolume->setSelected(CCS->soundh->getVolume()); + effectsVolume->addCallback(std::bind(&setIntSetting, "general", "sound", _1)); + + showReminder = new CToggleButton(Point(246, 87), "sysopchk.def", CGI->generaltexth->zelp[361], + [&] (bool value) { setBoolSetting("adventure", "heroReminder", value); }); + + quickCombat = new CToggleButton(Point(246, 87+32), "sysopchk.def", CGI->generaltexth->zelp[362], + [&] (bool value) { setBoolSetting("adventure", "quickCombat", value); }); + + spellbookAnim = new CToggleButton(Point(246, 87+64), "sysopchk.def", CGI->generaltexth->zelp[364], + [&] (bool value) { setBoolSetting("video", "spellbookAnimation", value); }); + + fullscreen = new CToggleButton(Point(246, 215), "sysopchk.def", CButton::tooltip(texts["fullscreenButton"]), + [&] (bool value) { setBoolSetting("video", "fullscreen", value); }); + + showReminder->setSelected(settings["adventure"]["heroReminder"].Bool()); + quickCombat->setSelected(settings["adventure"]["quickCombat"].Bool()); + spellbookAnim->setSelected(settings["video"]["spellbookAnimation"].Bool()); + fullscreen->setSelected(settings["video"]["fullscreen"].Bool()); + + onFullscreenChanged([&](const JsonNode &newState){ fullscreen->setSelected(newState.Bool());}); + + gameResButton = new CButton(Point(28, 275),"buttons/resolution", CButton::tooltip(texts["resolutionButton"]), + std::bind(&CSystemOptionsWindow::selectGameRes, this), SDLK_g); + + std::string resText; + resText += boost::lexical_cast(settings["video"]["screenRes"]["width"].Float()); + resText += "x"; + resText += boost::lexical_cast(settings["video"]["screenRes"]["height"].Float()); + gameResLabel = new CLabel(170, 292, FONT_MEDIUM, CENTER, Colors::YELLOW, resText); + +} + +void CSystemOptionsWindow::selectGameRes() +{ + std::vector items; + const JsonNode & texts = CGI->generaltexth->localizedTexts["systemOptions"]["resolutionMenu"]; + + for( config::CConfigHandler::GuiOptionsMap::value_type& value : conf.guiOptions) + { + std::string resX = boost::lexical_cast(value.first.first); + std::string resY = boost::lexical_cast(value.first.second); + items.push_back(resX + 'x' + resY); + } + + GH.pushInt(new CObjectListWindow(items, nullptr, texts["label"].String(), texts["help"].String(), + std::bind(&CSystemOptionsWindow::setGameRes, this, _1))); +} + +void CSystemOptionsWindow::setGameRes(int index) +{ + auto iter = conf.guiOptions.begin(); + std::advance(iter, index); + + //do not set resolution to illegal one (0x0) + assert(iter!=conf.guiOptions.end() && iter->first.first > 0 && iter->first.second > 0); + + Settings gameRes = settings.write["video"]["screenRes"]; + gameRes["width"].Float() = iter->first.first; + gameRes["height"].Float() = iter->first.second; + + std::string resText; + resText += boost::lexical_cast(iter->first.first); + resText += "x"; + resText += boost::lexical_cast(iter->first.second); + gameResLabel->setText(resText); +} + +void CSystemOptionsWindow::bquitf() +{ + LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this]{ closeAndPushEvent(SDL_QUIT); }, 0); +} + +void CSystemOptionsWindow::breturnf() +{ + GH.popIntTotally(this); +} + +void CSystemOptionsWindow::bmainmenuf() +{ + LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this]{ closeAndPushEvent(SDL_USEREVENT, RETURN_TO_MAIN_MENU); }, 0); +} + +void CSystemOptionsWindow::bloadf() +{ + GH.popIntTotally(this); + LOCPLINT->proposeLoadingGame(); +} + +void CSystemOptionsWindow::bsavef() +{ + GH.popIntTotally(this); + GH.pushInt(new CSavingScreen(CPlayerInterface::howManyPeople > 1)); +} + +void CSystemOptionsWindow::brestartf() +{ + LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [this]{ closeAndPushEvent(SDL_USEREVENT, RESTART_GAME); }, 0); +} + +void CSystemOptionsWindow::closeAndPushEvent(int eventType, int code /*= 0*/) +{ + GH.popIntTotally(this); + GH.pushSDLEvent(eventType, code); +} + +CTavernWindow::CTavernWindow(const CGObjectInstance *TavernObj): + CWindowObject(PLAYER_COLORED, "TPTAVERN"), + tavernObj(TavernObj) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + std::vector h = LOCPLINT->cb->getAvailableHeroes(TavernObj); + if(h.size() < 2) + h.resize(2, nullptr); + + h1 = new HeroPortrait(selected,0,72,299,h[0]); + h2 = new HeroPortrait(selected,1,162,299,h[1]); + + selected = 0; + if (!h[0]) + selected = 1; + if (!h[0] && !h[1]) + selected = -1; + oldSelected = -1; + + new CLabel(200, 35, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[37]); + new CLabel(320, 328, FONT_SMALL, CENTER, Colors::WHITE, "2500"); + new CTextBox(LOCPLINT->cb->getTavernGossip(tavernObj), Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE); + + new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); + cancel = new CButton(Point(310, 428), "ICANCEL.DEF", CButton::tooltip(CGI->generaltexth->tavernInfo[7]), std::bind(&CTavernWindow::close, this), SDLK_ESCAPE); + recruit = new CButton(Point(272, 355), "TPTAV01.DEF", CButton::tooltip(), std::bind(&CTavernWindow::recruitb, this), SDLK_RETURN); + thiefGuild = new CButton(Point(22, 428), "TPTAV02.DEF", CButton::tooltip(CGI->generaltexth->tavernInfo[5]), std::bind(&CTavernWindow::thievesguildb, this), SDLK_t); + + if(LOCPLINT->cb->getResourceAmount(Res::GOLD) < 2500) //not enough gold + { + recruit->addHoverText(CButton::NORMAL, CGI->generaltexth->tavernInfo[0]); //Cannot afford a Hero + recruit->block(true); + } + else if(LOCPLINT->castleInt && LOCPLINT->cb->howManyHeroes(true) >= VLC->modh->settings.MAX_HEROES_AVAILABLE_PER_PLAYER) + { + //Cannot recruit. You already have %d Heroes. + recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(true))); + recruit->block(true); + } + else if((!LOCPLINT->castleInt) && LOCPLINT->cb->howManyHeroes(false) >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER) + { + //Cannot recruit. You already have %d Heroes. + recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(false))); + recruit->block(true); + } + else if(LOCPLINT->castleInt && LOCPLINT->castleInt->town->visitingHero) + { + recruit->addHoverText(CButton::NORMAL, CGI->generaltexth->tavernInfo[2]); //Cannot recruit. You already have a Hero in this town. + recruit->block(true); + } + else + { + if(selected == -1) + recruit->block(true); + } + if (LOCPLINT->castleInt) + CCS->videoh->open(LOCPLINT->castleInt->town->town->clientInfo.tavernVideo); + else + CCS->videoh->open("TAVERN.BIK"); +} + +void CTavernWindow::recruitb() +{ + const CGHeroInstance *toBuy = (selected ? h2 : h1)->h; + const CGObjectInstance *obj = tavernObj; + close(); + LOCPLINT->cb->recruitHero(obj, toBuy); +} + +void CTavernWindow::thievesguildb() +{ + GH.pushInt( new CThievesGuildWindow(tavernObj) ); +} + +CTavernWindow::~CTavernWindow() +{ + CCS->videoh->close(); +} + +void CTavernWindow::show(SDL_Surface * to) +{ + CWindowObject::show(to); + + CCS->videoh->update(pos.x+70, pos.y+56, to, true, false); + if(selected >= 0) + { + HeroPortrait *sel = selected ? h2 : h1; + + if (selected != oldSelected && !recruit->isBlocked()) + { + // Selected hero just changed. Update RECRUIT button hover text if recruitment is allowed. + oldSelected = selected; + + //Recruit %s the %s + recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->name % sel->h->type->heroClass->name)); + } + + printAtMiddleWBLoc(sel->description, 146, 395, FONT_SMALL, 200, Colors::WHITE, to); + CSDL_Ext::drawBorder(to,sel->pos.x-2,sel->pos.y-2,sel->pos.w+4,sel->pos.h+4,int3(247,223,123)); + } +} + +void CTavernWindow::HeroPortrait::clickLeft(tribool down, bool previousState) +{ + if(previousState && !down && h) + *_sel = _id; +} + +void CTavernWindow::HeroPortrait::clickRight(tribool down, bool previousState) +{ + if(down && h) + { + GH.pushInt(new CRClickPopupInt(new CHeroWindow(h), true)); + } +} + +CTavernWindow::HeroPortrait::HeroPortrait(int &sel, int id, int x, int y, const CGHeroInstance *H) +: h(H), _sel(&sel), _id(id) +{ + addUsedEvents(LCLICK | RCLICK | HOVER); + OBJ_CONSTRUCTION_CAPTURING_ALL; + h = H; + pos.x += x; + pos.y += y; + pos.w = 58; + pos.h = 64; + + if(H) + { + hoverName = CGI->generaltexth->tavernInfo[4]; + boost::algorithm::replace_first(hoverName,"%s",H->name); + + int artifs = h->artifactsWorn.size() + h->artifactsInBackpack.size(); + for(int i=13; i<=17; i++) //war machines and spellbook don't count + if(vstd::contains(h->artifactsWorn, ArtifactPosition(i))) + artifs--; + + description = CGI->generaltexth->allTexts[215]; + boost::algorithm::replace_first(description, "%s", h->name); + boost::algorithm::replace_first(description, "%d", boost::lexical_cast(h->level)); + boost::algorithm::replace_first(description, "%s", h->type->heroClass->name); + boost::algorithm::replace_first(description, "%d", boost::lexical_cast(artifs)); + + new CAnimImage("portraitsLarge", h->portrait); + } +} + +void CTavernWindow::HeroPortrait::hover( bool on ) +{ + //Hoverable::hover(on); + if(on) + GH.statusbar->setText(hoverName); + else + GH.statusbar->clear(); +} + +void CExchangeWindow::questlog(int whichHero) +{ + CCS->curh->dragAndDropCursor(nullptr); +} + +void CExchangeWindow::prepareBackground() +{ + //printing heroes' names and levels + auto genTitle = [](const CGHeroInstance *h) + { + return boost::str(boost::format(CGI->generaltexth->allTexts[138]) + % h->name % h->level % h->type->heroClass->name); + }; + + new CLabel(147, 25, FONT_SMALL, CENTER, Colors::WHITE, genTitle(heroInst[0])); + new CLabel(653, 25, FONT_SMALL, CENTER, Colors::WHITE, genTitle(heroInst[1])); + + //printing primary skills + for(int g=0; g<4; ++g) + new CAnimImage("PSKIL32", g, 0, 385, 19 + 36*g); + + //heroes related thing + for(int b=0; b(heroWArt.getPrimSkillLevel(static_cast(m)))); + + //printing secondary skills + for(int m=0; msecSkills.size(); ++m) + { + int id = heroInst[b]->secSkills[m].first; + int level = heroInst[b]->secSkills[m].second; + new CAnimImage("SECSK32", id*3 + level + 2 , 0, 32 + 36 * m + 454 * b, 88); + } + + //hero's specialty + new CAnimImage("UN32", heroInst[b]->type->imageIndex, 0, 67 + 490*b, 45); + + //experience + new CAnimImage("PSKIL32", 4, 0, 103 + 490*b, 45); + new CLabel(119 + 490*b, 71, FONT_SMALL, CENTER, Colors::WHITE, makeNumberShort(heroInst[b]->exp)); + + //mana points + new CAnimImage("PSKIL32", 5, 0, 139 + 490*b, 45); + new CLabel(155 + 490*b, 71, FONT_SMALL, CENTER, Colors::WHITE, makeNumberShort(heroInst[b]->mana)); + } + + //printing portraits + new CAnimImage("PortraitsLarge", heroInst[0]->portrait, 0, 257, 13); + new CAnimImage("PortraitsLarge", heroInst[1]->portrait, 0, 485, 13); +} + +CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID): + CWindowObject(PLAYER_COLORED | BORDERED, "TRADE2") +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + heroInst[0] = LOCPLINT->cb->getHero(hero1); + heroInst[1] = LOCPLINT->cb->getHero(hero2); + + prepareBackground(); + + artifs[0] = new CArtifactsOfHero(Point(-334, 150)); + artifs[0]->commonInfo = new CArtifactsOfHero::SCommonPart; + artifs[0]->commonInfo->participants.insert(artifs[0]); + artifs[0]->setHero(heroInst[0]); + artifs[1] = new CArtifactsOfHero(Point(96, 150)); + artifs[1]->commonInfo = artifs[0]->commonInfo; + artifs[1]->commonInfo->participants.insert(artifs[1]); + artifs[1]->setHero(heroInst[1]); + + artSets.push_back(artifs[0]); + artSets.push_back(artifs[1]); + + //primary skills + for(int g=0; g<4; ++g) + { + //primary skill's clickable areas + primSkillAreas.push_back(new LRClickableAreaWTextComp()); + primSkillAreas[g]->pos = genRect(32, 140, pos.x + 329, pos.y + 19 + 36 * g); + primSkillAreas[g]->text = CGI->generaltexth->arraytxt[2+g]; + primSkillAreas[g]->type = g; + primSkillAreas[g]->bonusValue = -1; + primSkillAreas[g]->baseType = 0; + primSkillAreas[g]->hoverText = CGI->generaltexth->heroscrn[1]; + boost::replace_first(primSkillAreas[g]->hoverText, "%s", CGI->generaltexth->primarySkillNames[g]); + } + + //heroes related thing + for(int b=0; bsecSkills.size(); ++g) + { + int skill = heroInst[b]->secSkills[g].first, + level = heroInst[b]->secSkills[g].second; // <1, 3> + secSkillAreas[b].push_back(new LRClickableAreaWTextComp()); + secSkillAreas[b][g]->pos = genRect(32, 32, pos.x + 32 + g*36 + b*454 , pos.y + 88); + secSkillAreas[b][g]->baseType = 1; + + secSkillAreas[b][g]->type = skill; + secSkillAreas[b][g]->bonusValue = level; + secSkillAreas[b][g]->text = CGI->generaltexth->skillInfoTexts[skill][level-1]; + + secSkillAreas[b][g]->hoverText = CGI->generaltexth->heroscrn[21]; + boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->levels[level - 1]); + boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->skillName[skill]); + } + + portrait[b] = new CHeroArea(257 + 228*b, 13, heroInst[b]); + + specialty[b] = new LRClickableAreaWText(); + specialty[b]->pos = genRect(32, 32, pos.x + 69 + 490*b, pos.y + 45); + specialty[b]->hoverText = CGI->generaltexth->heroscrn[27]; + specialty[b]->text = heroInst[b]->type->specDescr; + + experience[b] = new LRClickableAreaWText(); + experience[b]->pos = genRect(32, 32, pos.x + 105 + 490*b, pos.y + 45); + experience[b]->hoverText = CGI->generaltexth->heroscrn[9]; + experience[b]->text = CGI->generaltexth->allTexts[2].c_str(); + boost::algorithm::replace_first(experience[b]->text, "%d", boost::lexical_cast(heroInst[b]->level)); + boost::algorithm::replace_first(experience[b]->text, "%d", boost::lexical_cast(CGI->heroh->reqExp(heroInst[b]->level+1))); + boost::algorithm::replace_first(experience[b]->text, "%d", boost::lexical_cast(heroInst[b]->exp)); + + spellPoints[b] = new LRClickableAreaWText(); + spellPoints[b]->pos = genRect(32, 32, pos.x + 141 + 490*b, pos.y + 45); + spellPoints[b]->hoverText = CGI->generaltexth->heroscrn[22]; + spellPoints[b]->text = CGI->generaltexth->allTexts[205]; + boost::algorithm::replace_first(spellPoints[b]->text, "%s", heroInst[b]->name); + boost::algorithm::replace_first(spellPoints[b]->text, "%d", boost::lexical_cast(heroInst[b]->mana)); + boost::algorithm::replace_first(spellPoints[b]->text, "%d", boost::lexical_cast(heroInst[b]->manaLimit())); + + //setting morale + morale[b] = new MoraleLuckBox(true, genRect(32, 32, 176 + 490*b, 39), true); + morale[b]->set(heroInst[b]); + //setting luck + luck[b] = new MoraleLuckBox(false, genRect(32, 32, 212 + 490*b, 39), true); + luck[b]->set(heroInst[b]); + } + + //buttons + quit = new CButton(Point(732, 567), "IOKAY.DEF", CGI->generaltexth->zelp[600], std::bind(&CExchangeWindow::close, this), SDLK_RETURN); + if(queryID.getNum() > 0) + quit->addCallback([=]{ LOCPLINT->cb->selectionMade(0, queryID); }); + + questlogButton[0] = new CButton(Point( 10, 44), "hsbtns4.def", CButton::tooltip(CGI->generaltexth->heroscrn[0]), std::bind(&CExchangeWindow::questlog,this, 0)); + questlogButton[1] = new CButton(Point(740, 44), "hsbtns4.def", CButton::tooltip(CGI->generaltexth->heroscrn[0]), std::bind(&CExchangeWindow::questlog,this, 1)); + + Rect barRect(5, 578, 725, 18); + ourBar = new CGStatusBar(new CPicture(*background, barRect, 5, 578, false)); + + //garrison interface + garr = new CGarrisonInt(69, 131, 4, Point(418,0), *background, Point(69,131), heroInst[0],heroInst[1], true, true); + garr->addSplitBtn(new CButton( Point( 10, 132), "TSBTNS.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3]), std::bind(&CGarrisonInt::splitClick, garr))); + garr->addSplitBtn(new CButton( Point(740, 132), "TSBTNS.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3]), std::bind(&CGarrisonInt::splitClick, garr))); +} + +CExchangeWindow::~CExchangeWindow() //d-tor +{ + delete artifs[0]->commonInfo; + artifs[0]->commonInfo = nullptr; + artifs[1]->commonInfo = nullptr; +} + +CShipyardWindow::CShipyardWindow(const std::vector &cost, int state, int boatType, const std::function &onBuy): + CWindowObject(PLAYER_COLORED, "TPSHIP") +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + bgWater = new CPicture("TPSHIPBK", 100, 69); + + std::string boatFilenames[3] = {"AB01_", "AB02_", "AB03_"}; + + Point waterCenter = Point(bgWater->pos.x+bgWater->pos.w/2, bgWater->pos.y+bgWater->pos.h/2); + bgShip = new CAnimImage(boatFilenames[boatType], 0, 7, 120, 96, CShowableAnim::USE_RLE); + bgShip->center(waterCenter); + + // Create resource icons and costs. + std::string goldValue = boost::lexical_cast(cost[Res::GOLD]); + std::string woodValue = boost::lexical_cast(cost[Res::WOOD]); + + goldCost = new CLabel(118, 294, FONT_SMALL, CENTER, Colors::WHITE, goldValue); + woodCost = new CLabel(212, 294, FONT_SMALL, CENTER, Colors::WHITE, woodValue); + + goldPic = new CAnimImage("RESOURCE", Res::GOLD, 0, 100, 244); + woodPic = new CAnimImage("RESOURCE", Res::WOOD, 0, 196, 244); + + quit = new CButton( Point(224, 312), "ICANCEL", CButton::tooltip(CGI->generaltexth->allTexts[599]), std::bind(&CShipyardWindow::close, this), SDLK_RETURN); + build = new CButton( Point( 42, 312), "IBUY30", CButton::tooltip(CGI->generaltexth->allTexts[598]), std::bind(&CShipyardWindow::close, this),SDLK_RETURN); + build->addCallback(onBuy); + + for(Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1)) + { + if(cost[i] > LOCPLINT->cb->getResourceAmount(i)) + { + build->block(true); + break; + } + } + + statusBar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); + + title = new CLabel(164, 27, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[13]); + costLabel = new CLabel(164, 220, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->jktexts[14]); +} + +CPuzzleWindow::CPuzzleWindow(const int3 &GrailPos, double discoveredRatio): + CWindowObject(PLAYER_COLORED | BORDERED, "PUZZLE"), + grailPos(GrailPos), + currentAlpha(SDL_ALPHA_OPAQUE) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + CCS->soundh->playSound(soundBase::OBELISK); + + quitb = new CButton(Point(670, 538), "IOK6432.DEF", CButton::tooltip(CGI->generaltexth->allTexts[599]), std::bind(&CPuzzleWindow::close, this), SDLK_RETURN); + quitb->assignedKeys.insert(SDLK_ESCAPE); + quitb->borderColor = Colors::METALLIC_GOLD; + + new CPicture("PUZZLOGO", 607, 3); + new CLabel(700, 95, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[463]); + new CResDataBar("ARESBAR.bmp", 3, 575, 32, 2, 85, 85); + + int faction = LOCPLINT->cb->getStartInfo()->playerInfos.find(LOCPLINT->playerID)->second.castle; + + auto & puzzleMap = CGI->townh->factions[faction]->puzzleMap; + + for(auto & elem : puzzleMap) + { + const SPuzzleInfo & info = elem; + + auto piece = new CPicture(info.filename, info.x, info.y); + + //piece that will slowly disappear + if(info.whenUncovered <= GameConstants::PUZZLE_MAP_PIECES * discoveredRatio) + { + piecesToRemove.push_back(piece); + piece->needRefresh = true; + piece->recActions = piece->recActions & ~SHOWALL; + #ifndef VCMI_SDL1 + SDL_SetSurfaceBlendMode(piece->bg,SDL_BLENDMODE_BLEND); + #endif // VCMI_SDL1 + } + } +} + +void CPuzzleWindow::showAll(SDL_Surface * to) +{ + int3 moveInt = int3(8, 9, 0); + Rect mapRect = genRect(544, 591, pos.x + 8, pos.y + 7); + + CGI->mh->terrainRect + (grailPos - moveInt, adventureInt->anim, + &LOCPLINT->cb->getVisibilityMap(), true, adventureInt->heroAnim, + to, &mapRect, 0, 0, true, moveInt); + + CWindowObject::showAll(to); +} + +void CPuzzleWindow::show(SDL_Surface * to) +{ + static int animSpeed = 2; + + if (currentAlpha < animSpeed) + { + //animation done + for(auto & piece : piecesToRemove) + delete piece; + piecesToRemove.clear(); + } + else + { + //update disappearing puzzles + for(auto & piece : piecesToRemove) + piece->setAlpha(currentAlpha); + currentAlpha -= animSpeed; + } + CWindowObject::show(to); +} + +void CTransformerWindow::CItem::move() +{ + if (left) + moveBy(Point(289, 0)); + else + moveBy(Point(-289, 0)); + left = !left; +} + +void CTransformerWindow::CItem::clickLeft(tribool down, bool previousState) +{ + if(previousState && (!down)) + { + move(); + parent->showAll(screen2); + } +} + +void CTransformerWindow::CItem::update() +{ + icon->setFrame(parent->army->getCreature(SlotID(id))->idNumber + 2); +} + +CTransformerWindow::CItem::CItem(CTransformerWindow * parent, int size, int id): + id(id), size(size), parent(parent) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + addUsedEvents(LCLICK); + left = true; + pos.w = 58; + pos.h = 64; + + pos.x += 45 + (id%3)*83 + id/6*83; + pos.y += 109 + (id/3)*98; + icon = new CAnimImage("TWCRPORT", parent->army->getCreature(SlotID(id))->idNumber + 2); + new CLabel(28, 76,FONT_SMALL, CENTER, Colors::WHITE, boost::lexical_cast(size));//stack size +} + +void CTransformerWindow::makeDeal() +{ + for (auto & elem : items) + if (!elem->left) + LOCPLINT->cb->trade(town, EMarketMode::CREATURE_UNDEAD, elem->id, 0, 0, hero); +} + +void CTransformerWindow::addAll() +{ + for (auto & elem : items) + if (elem->left) + elem->move(); + showAll(screen2); +} + +void CTransformerWindow::updateGarrisons() +{ + for(auto & item : items) + { + item->update(); + } +} + +CTransformerWindow::CTransformerWindow(const CGHeroInstance * _hero, const CGTownInstance * _town): + CWindowObject(PLAYER_COLORED, "SKTRNBK"), + hero(_hero), + town(_town) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + if (hero) + army = hero; + else + army = town; + + for (int i=0; igetCreature(SlotID(i)) ) + items.push_back(new CItem(this, army->getStackCount(SlotID(i)), i)); + + all = new CButton(Point(146, 416), "ALTARMY.DEF", CGI->generaltexth->zelp[590], [&] { addAll(); }, SDLK_a); + convert = new CButton(Point(269, 416), "ALTSACR.DEF", CGI->generaltexth->zelp[591], [&] { makeDeal(); }, SDLK_RETURN); + cancel = new CButton(Point(392, 416), "ICANCEL.DEF", CGI->generaltexth->zelp[592], [&] { close(); },SDLK_ESCAPE); + bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); + + new CLabel(153, 29,FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[485]);//holding area + new CLabel(153+295, 29, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[486]);//transformer + new CTextBox(CGI->generaltexth->allTexts[487], Rect(26, 56, 255, 40), 0, FONT_MEDIUM, CENTER, Colors::YELLOW);//move creatures to create skeletons + new CTextBox(CGI->generaltexth->allTexts[488], Rect(320, 56, 255, 40), 0, FONT_MEDIUM, CENTER, Colors::YELLOW);//creatures here will become skeletons + +} + +void CUniversityWindow::CItem::clickLeft(tribool down, bool previousState) +{ + if(previousState && (!down)) + { + if ( state() != 2 ) + return; + auto win = new CUnivConfirmWindow(parent, ID, LOCPLINT->cb->getResourceAmount(Res::GOLD) >= 2000); + GH.pushInt(win); + } +} + +void CUniversityWindow::CItem::clickRight(tribool down, bool previousState) +{ + if(down) + { + CRClickPopup::createAndPush(CGI->generaltexth->skillInfoTexts[ID][0], + new CComponent(CComponent::secskill, ID, 1)); + } +} + +void CUniversityWindow::CItem::hover(bool on) +{ + if (on) + GH.statusbar->setText(CGI->generaltexth->skillName[ID]); + else + GH.statusbar->clear(); +} + +int CUniversityWindow::CItem::state() +{ + if (parent->hero->getSecSkillLevel(SecondarySkill(ID)))//hero know this skill + return 1; + if (!parent->hero->canLearnSkill())//can't learn more skills + return 0; + if (parent->hero->type->heroClass->secSkillProbability[ID]==0)//can't learn this skill (like necromancy for most of non-necros) + return 0; + return 2; +} + +void CUniversityWindow::CItem::showAll(SDL_Surface * to) +{ + CPicture * bar; + switch (state()) + { + case 0: bar = parent->red; + break; + case 1: bar = parent->yellow; + break; + case 2: bar = parent->green; + break; + default:bar = nullptr; + break; + } + assert(bar); + + blitAtLoc(bar->bg, -28, -22, to); + blitAtLoc(bar->bg, -28, 48, to); + printAtMiddleLoc (CGI->generaltexth->skillName[ID], 22, -13, FONT_SMALL, Colors::WHITE,to);//Name + printAtMiddleLoc (CGI->generaltexth->levels[0], 22, 57, FONT_SMALL, Colors::WHITE,to);//Level(always basic) + + CAnimImage::showAll(to); +} + +CUniversityWindow::CItem::CItem(CUniversityWindow * _parent, int _ID, int X, int Y): + CAnimImage ("SECSKILL", _ID*3+3, 0, X, Y), + ID(_ID), + parent(_parent) +{ + addUsedEvents(LCLICK | RCLICK | HOVER); +} + +CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket * _market): + CWindowObject(PLAYER_COLORED, "UNIVERS1"), + hero(_hero), + market(_market) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + green = new CPicture("UNIVGREN.PCX"); + yellow = new CPicture("UNIVGOLD.PCX");//bars + red = new CPicture("UNIVRED.PCX"); + + green->recActions = + yellow->recActions = + red->recActions = DISPOSE; + + CIntObject * titlePic = nullptr; + + if (market->o->ID == Obj::TOWN) + titlePic = new CAnimImage(CGI->townh->factions[ETownType::CONFLUX]->town->clientInfo.buildingsIcons, BuildingID::MAGIC_UNIVERSITY); + else + titlePic = new CPicture("UNIVBLDG"); + + titlePic->center(Point(232 + pos.x, 76 + pos.y)); + + //Clerk speech + new CTextBox(CGI->generaltexth->allTexts[603], Rect(24, 129, 413, 70), 0, FONT_SMALL, CENTER, Colors::WHITE); + + //University + new CLabel(231, 26, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[602]); + + std::vector list = market->availableItemsIds(EMarketMode::RESOURCE_SKILL); + + assert(list.size() == 4); + + for (int i=0; igeneraltexth->zelp[632], [&]{ close(); }, SDLK_RETURN); + + bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); +} + +CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * PARENT, int SKILL, bool available ): + CWindowObject(PLAYER_COLORED, "UNIVERS2.PCX"), + parent(PARENT) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + std::string text = CGI->generaltexth->allTexts[608]; + boost::replace_first(text, "%s", CGI->generaltexth->levels[0]); + boost::replace_first(text, "%s", CGI->generaltexth->skillName[SKILL]); + boost::replace_first(text, "%d", "2000"); + + new CTextBox(text, Rect(24, 129, 413, 70), 0, FONT_SMALL, CENTER, Colors::WHITE);//Clerk speech + + new CLabel(230, 37, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth-> skillName[SKILL]);//Skill name + new CAnimImage("SECSKILL", SKILL*3+3, 0, 211, 51);//skill + new CLabel(230, 107, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->levels[1]);//Skill level + + new CAnimImage("RESOURCE", Res::GOLD, 0, 210, 210);//gold + new CLabel(230, 267, FONT_SMALL, CENTER, Colors::WHITE, "2000");//Cost + + std::string hoverText = CGI->generaltexth->allTexts[609]; + boost::replace_first(hoverText, "%s", CGI->generaltexth->levels[0]+ " " + CGI->generaltexth->skillName[SKILL]); + + text = CGI->generaltexth->zelp[633].second; + boost::replace_first(text, "%s", CGI->generaltexth->levels[0]); + boost::replace_first(text, "%s", CGI->generaltexth->skillName[SKILL]); + boost::replace_first(text, "%d", "2000"); + + confirm= new CButton(Point(148, 299), "IBY6432.DEF", CButton::tooltip(hoverText, text), [&]{makeDeal(SKILL);}, SDLK_RETURN); + confirm->block(!available); + + cancel = new CButton(Point(252,299), "ICANCEL.DEF", CGI->generaltexth->zelp[631], [&]{ close(); }, SDLK_ESCAPE); + bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); +} + +void CUnivConfirmWindow::makeDeal(int skill) +{ + LOCPLINT->cb->trade(parent->market->o, EMarketMode::RESOURCE_SKILL, 6, skill, 1, parent->hero); + close(); +} + +CHillFortWindow::CHillFortWindow(const CGHeroInstance *visitor, const CGObjectInstance *object): + CWindowObject(PLAYER_COLORED, "APHLFTBK"), + fort(object), + hero(visitor) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + slotsCount=7; + resources = CDefHandler::giveDefEss("SMALRES.DEF"); + + new CLabel(325, 32, FONT_BIG, CENTER, Colors::YELLOW, fort->getObjectName());//Hill Fort + + heroPic = new CHeroArea(30, 60, hero); + + currState.resize(slotsCount+1); + costs.resize(slotsCount); + totalSumm.resize(GameConstants::RESOURCE_QUANTITY); + + for (int i=0; iblock(currState[i] == -1); + for (auto image : { "APHLF1R.DEF", "APHLF1Y.DEF", "APHLF1G.DEF" }) + upgrade[i]->addImage(image); + } + currState[slotsCount] = getState(SlotID(slotsCount)); + upgradeAll = new CButton(Point(30, 231), "", CButton::tooltip(CGI->generaltexth->allTexts[432]), [&]{ makeDeal(SlotID(slotsCount));}, SDLK_0); + for (auto image : { "APHLF4R.DEF", "APHLF4Y.DEF", "APHLF4G.DEF" }) + upgradeAll->addImage(image); + + quit = new CButton(Point(294, 275), "IOKAY.DEF", CButton::tooltip(), std::bind(&CHillFortWindow::close, this), SDLK_RETURN); + bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); + + garr = new CGarrisonInt(108, 60, 18, Point(),background->bg,Point(108,60),hero,nullptr); + updateGarrisons(); +} + +void CHillFortWindow::updateGarrisons() +{ + for (int i=0; icb->getUpgradeInfo(hero, SlotID(i), info); + if (info.newID.size())//we have upgrades here - update costs + { + costs[i] = info.cost[0] * hero->getStackCount(SlotID(i)); + totalSumm += costs[i]; + } + } + + currState[i] = newState; + upgrade[i]->setIndex(newState); + upgrade[i]->block(currState[i] == -1); + upgrade[i]->addHoverText(CButton::NORMAL, getTextForSlot(SlotID(i))); + } + + int newState = getState(SlotID(slotsCount)); + currState[slotsCount] = newState; + upgradeAll->setIndex(newState); + garr->recreateSlots(); +} + +void CHillFortWindow::makeDeal(SlotID slot) +{ + assert(slot.getNum()>=0); + int offset = (slot.getNum() == slotsCount)?2:0; + switch (currState[slot.getNum()]) + { + case 0: + LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[314 + offset], + std::vector(), soundBase::sound_todo); + break; + case 1: + LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[313 + offset], + std::vector(), soundBase::sound_todo); + break; + case 2: + for (int i=0; icb->getUpgradeInfo(hero, SlotID(i), info); + LOCPLINT->cb->upgradeCreature(hero, SlotID(i), info.newID[0]); + } + break; + + } +} + +void CHillFortWindow::showAll (SDL_Surface *to) +{ + CWindowObject::showAll(to); + + for ( int i=0; i= 0; j--) + { + int val = costs[i][j]; + if(!val) continue; + + blitAtLoc(resources->ourImages[j].bitmap, 104+76*i, curY, to); + printToLoc(boost::lexical_cast(val), 168+76*i, curY+16, FONT_SMALL, Colors::WHITE, to); + curY += 20; + } + } + else//free upgrade - print gold image and "Free" text + { + blitAtLoc(resources->ourImages[6].bitmap, 104+76*i, 128, to); + printToLoc(CGI->generaltexth->allTexts[344], 168+76*i, 144, FONT_SMALL, Colors::WHITE, to); + } + } + } + for (int i=0; iourImages[i].bitmap, 104+76*i, 237, to); + printToLoc(boost::lexical_cast(totalSumm[i]), 166+76*i, 253, FONT_SMALL, Colors::WHITE, to); + } + } +} + +std::string CHillFortWindow::getTextForSlot(SlotID slot) +{ + if ( !hero->getCreature(slot) )//we don`t have creature here + return ""; + + std::string str = CGI->generaltexth->allTexts[318]; + int amount = hero->getStackCount(slot); + if ( amount == 1 ) + boost::algorithm::replace_first(str,"%s",hero->getCreature(slot)->nameSing); + else + boost::algorithm::replace_first(str,"%s",hero->getCreature(slot)->namePl); + + return str; +} + +int CHillFortWindow::getState(SlotID slot) +{ + TResources myRes = LOCPLINT->cb->getResourceAmount(); + if ( slot.getNum() == slotsCount )//"Upgrade all" slot + { + bool allUpgraded = true;//All creatures are upgraded? + for (int i=0; islotEmpty(slot))//no creature here + return -1; + + UpgradeInfo info; + LOCPLINT->cb->getUpgradeInfo(hero, slot, info); + if (!info.newID.size())//already upgraded + return 1; + + if(!(info.cost[0] * hero->getStackCount(slot)).canBeAfforded(myRes)) + return 0; + + return 2;//can upgrade +} + +CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner): + CWindowObject(PLAYER_COLORED | BORDERED, "TpRank"), + owner(_owner) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + type |= BLOCK_ADV_HOTKEYS; + + SThievesGuildInfo tgi; //info to be displayed + LOCPLINT->cb->getThievesGuildInfo(tgi, owner); + + exitb = new CButton (Point(748, 556), "TPMAGE1", CButton::tooltip(CGI->generaltexth->allTexts[600]), [&]{ close();}, SDLK_RETURN); + exitb->assignedKeys.insert(SDLK_ESCAPE); + statusBar = new CGStatusBar(3, 555, "TStatBar.bmp", 742); + + resdatabar = new CMinorResDataBar(); + resdatabar->pos.x += pos.x; + resdatabar->pos.y += pos.y; + + //data for information table: + // fields[row][column] = list of id's of players for this box + static std::vector< std::vector< PlayerColor > > SThievesGuildInfo::* fields[] = + { &SThievesGuildInfo::numOfTowns, &SThievesGuildInfo::numOfHeroes, &SThievesGuildInfo::gold, + &SThievesGuildInfo::woodOre, &SThievesGuildInfo::mercSulfCrystGems, &SThievesGuildInfo::obelisks, + &SThievesGuildInfo::artifacts, &SThievesGuildInfo::army, &SThievesGuildInfo::income }; + + //printing texts & descriptions to background + + for(int g=0; g<12; ++g) + { + int posY[] = {400, 460, 510}; + int y; + if(g < 9) + y = 52 + 32*g; + else + y = posY[g-9]; + + std::string text = CGI->generaltexth->jktexts[24+g]; + boost::algorithm::trim_if(text,boost::algorithm::is_any_of("\"")); + new CLabel(135, y, FONT_MEDIUM, CENTER, Colors::YELLOW, text); + } + + for(int g=1; ggeneraltexth->jktexts[16+g]); + + //printing flags + for(int g = 0; g < ARRAY_COUNT(fields); ++g) //by lines + { + for(int b=0; b<(tgi .* fields[g]).size(); ++b) //by places (1st, 2nd, ...) + { + std::vector &players = (tgi .* fields[g])[b]; //get players with this place in this line + + //position of box + int xpos = 259 + 66 * b; + int ypos = 41 + 32 * g; + + size_t rowLength[2]; //size of each row + rowLength[0] = std::min(players.size(), 4); + rowLength[1] = players.size() - rowLength[0]; + + for (size_t j=0; j< 2; j++) + { + // origin of this row | offset for 2nd row| shift right for short rows + //if we have 2 rows, start either from mid or beginning (depending on count), otherwise center the flags + int rowStartX = xpos + (j ? 6 + (rowLength[j] < 3 ? 12 : 0) : 24 - 6 * rowLength[j]); + int rowStartY = ypos + (j ? 4 : 0); + + for (size_t i=0; i< rowLength[j]; i++) + { + new CAnimImage("itgflags", players[i + j*4].getNum(), 0, rowStartX + i*12, rowStartY); + } + } + } + } + + static const std::string colorToBox[] = {"PRRED.BMP", "PRBLUE.BMP", "PRTAN.BMP", "PRGREEN.BMP", "PRORANGE.BMP", "PRPURPLE.BMP", "PRTEAL.BMP", "PRROSE.bmp"}; + + //printing best hero + int counter = 0; + for(auto & iter : tgi.colorToBestHero) + { + if(iter.second.portrait >= 0) + { + new CPicture(colorToBox[iter.first.getNum()], 253 + 66 * counter, 334); + new CAnimImage("PortraitsSmall", iter.second.portrait, 0, 260 + 66 * counter, 360); + //TODO: r-click info: + // - r-click on hero + // - r-click on primary skill label + if(iter.second.details) + { + new CTextBox(CGI->generaltexth->allTexts[184], Rect(260 + 66*counter, 396, 52, 64), + 0, FONT_TINY, TOPLEFT, Colors::WHITE); + for (int i=0; iprimskills.size(); ++i) + { + new CLabel(310 + 66 * counter, 407 + 11*i, FONT_TINY, BOTTOMRIGHT, Colors::WHITE, + boost::lexical_cast(iter.second.details->primskills[i])); + } + } + } + counter++; + } + + //printing best creature + counter = 0; + for(auto & it : tgi.bestCreature) + { + if(it.second >= 0) + new CAnimImage("TWCRPORT", it.second+2, 0, 255 + 66 * counter, 479); + counter++; + } + + //printing personality + counter = 0; + for(auto & it : tgi.personality) + { + std::string text; + if(it.second == EAiTactic::NONE) + { + text = CGI->generaltexth->arraytxt[172]; + } + else if(it.second != EAiTactic::RANDOM) + { + text = CGI->generaltexth->arraytxt[168 + it.second]; + } + + new CLabel(283 + 66*counter, 459, FONT_SMALL, CENTER, Colors::WHITE, text); + + counter++; + } +} + +CObjectListWindow::CItem::CItem(CObjectListWindow *_parent, size_t _id, std::string _text): + parent(_parent), + index(_id) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + border = new CPicture("TPGATES"); + pos = border->pos; + addUsedEvents(LCLICK); + type |= REDRAW_PARENT; + + text = new CLabel(pos.w/2, pos.h/2, FONT_SMALL, CENTER, Colors::WHITE, _text); + select(index == parent->selected); +} + +void CObjectListWindow::CItem::select(bool on) +{ + if (on) + border->recActions = 255; + else + border->recActions = ~(UPDATE | SHOWALL); + redraw(); +} + +void CObjectListWindow::CItem::clickLeft(tribool down, bool previousState) +{ + if( previousState && !down) + parent->changeSelection(index); +} + +CObjectListWindow::CObjectListWindow(const std::vector &_items, CIntObject * titlePic, std::string _title, std::string _descr, + std::function Callback): + CWindowObject(PLAYER_COLORED, "TPGATE"), + onSelect(Callback) +{ + items.reserve(_items.size()); + for(int id : _items) + { + items.push_back(std::make_pair(id, CGI->mh->map->objects[id]->getObjectName())); + } + + init(titlePic, _title, _descr); +} + +CObjectListWindow::CObjectListWindow(const std::vector &_items, CIntObject * titlePic, std::string _title, std::string _descr, + std::function Callback): + CWindowObject(PLAYER_COLORED, "TPGATE"), + onSelect(Callback) +{ + items.reserve(_items.size()); + + for (size_t i=0; i<_items.size(); i++) + items.push_back(std::make_pair(int(i), _items[i])); + + init(titlePic, _title, _descr); +} + +void CObjectListWindow::init(CIntObject * titlePic, std::string _title, std::string _descr) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + title = new CLabel(152, 27, FONT_BIG, CENTER, Colors::YELLOW, _title); + descr = new CLabel(145, 133, FONT_SMALL, CENTER, Colors::WHITE, _descr); + + ok = new CButton(Point(15, 402), "IOKAY.DEF", CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), SDLK_RETURN); + ok->block(true); + exit = new CButton( Point(228, 402), "ICANCEL.DEF", CButton::tooltip(), std::bind(&CGuiHandler::popIntTotally,&GH, this), SDLK_ESCAPE); + + if (titlePic) + { + titleImage = titlePic; + addChild(titleImage); + titleImage->recActions = defActions; + titleImage->pos.x = pos.w/2 + pos.x - titleImage->pos.w/2; + titleImage->pos.y =75 + pos.y - titleImage->pos.h/2; + } + list = new CListBox(std::bind(&CObjectListWindow::genItem, this, _1), CListBox::DestroyFunc(), + Point(14, 151), Point(0, 25), 9, items.size(), 0, 1, Rect(262, -32, 256, 256) ); + list->type |= REDRAW_PARENT; +} + +CIntObject * CObjectListWindow::genItem(size_t index) +{ + if (index < items.size()) + return new CItem(this, index, items[index].second); + return nullptr; +} + +void CObjectListWindow::elementSelected() +{ + std::function toCall = onSelect;//save + int where = items[selected].first; //required variables + GH.popIntTotally(this);//then destroy window + toCall(where);//and send selected object +} + +void CObjectListWindow::changeSelection(size_t which) +{ + ok->block(false); + if (selected == which) + return; + + std::list< CIntObject * > elements = list->getItems(); + for(CIntObject * element : elements) + { + CItem *item; + if ( (item = dynamic_cast(element)) ) + { + if (item->index == selected) + item->select(false); + if (item->index == which) + item->select(true); + } + } + selected = which; +} + +void CObjectListWindow::keyPressed (const SDL_KeyboardEvent & key) +{ + if(key.state != SDL_PRESSED) + return; + + int sel = selected; + + switch(key.keysym.sym) + { + break; case SDLK_UP: + sel -=1; + + break; case SDLK_DOWN: + sel +=1; + + break; case SDLK_PAGEUP: + sel -=9; + + break; case SDLK_PAGEDOWN: + sel +=9; + + break; case SDLK_HOME: + sel = 0; + + break; case SDLK_END: + sel = items.size(); + + break; default: + return; + } + + vstd::abetween(sel, 0, items.size()-1); + list->scrollTo(sel); + changeSelection(sel); +} diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h new file mode 100644 index 000000000..f692d1a18 --- /dev/null +++ b/client/windows/GUIClasses.h @@ -0,0 +1,439 @@ +#pragma once + +#include "../lib/GameConstants.h" +#include "../lib/ResourceSet.h" +#include "../lib/CConfigHandler.h" +#include "../widgets/CArtifactHolder.h" +#include "../widgets/CGarrisonInt.h" +#include "../widgets/Images.h" +#include "../windows/CWindowObject.h" + +/* + * GUIClasses.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 + * + */ + +class CGDwelling; +class IMarket; +class CCreaturePic; +class MoraleLuckBox; +class CHeroArea; +class CMinorResDataBar; +class CSlider; +class CComponentBox; +class CTextInput; +class CListBox; +class CLabelGroup; +class CToggleButton; +class CToggleGroup; +class CGStatusBar; + +/// Recruitment window where you can recruit creatures +class CRecruitmentWindow : public CWindowObject +{ + class CCreatureCard : public CIntObject + { + CRecruitmentWindow * parent; + CCreaturePic *pic; //creature's animation + bool selected; + + void clickLeft(tribool down, bool previousState); + void clickRight(tribool down, bool previousState); + void showAll(SDL_Surface *to); + public: + const CCreature * creature; + si32 amount; + + void select(bool on); + + CCreatureCard(CRecruitmentWindow * window, const CCreature *crea, int totalAmount); + }; + + /// small class to display creature costs + class CCostBox : public CIntObject + { + std::map > resources; + public: + //res - resources to show + void set(TResources res); + //res - visible resources + CCostBox(Rect position, std::string title); + void createItems(TResources res); + }; + + std::function onRecruit; //void (int ID, int amount) <-- call to recruit creatures + + int level; + const CArmedInstance *dst; + + CCreatureCard * selected; + std::vector cards; + + CSlider *slider; //for selecting amount + CButton *maxButton, *buyButton, *cancelButton; + //labels for visible values + CLabel * title; + CLabel * availableValue; + CLabel * toRecruitValue; + CCostBox * costPerTroopValue; + CCostBox * totalCostValue; + + void select(CCreatureCard * card); + void buy(); + void sliderMoved(int to); + + void showAll(SDL_Surface *to); +public: + const CGDwelling * const dwelling; + CRecruitmentWindow(const CGDwelling *Dwelling, int Level, const CArmedInstance *Dst, const std::function & Recruit, int y_offset = 0); //creatures - pairs //c-tor + void availableCreaturesChanged(); +}; + +/// Split window where creatures can be split up into two single unit stacks +class CSplitWindow : public CWindowObject +{ + std::function callback; + int leftAmount; + int rightAmount; + + int leftMin; + int rightMin; + + CSlider *slider; + CCreaturePic *animLeft, *animRight; //creature's animation + CButton *ok, *cancel; + + CTextInput *leftInput, *rightInput; + void setAmountText(std::string text, bool left); + void setAmount(int value, bool left); + void sliderMoved(int value); + void apply(); + +public: + /** + * creature - displayed creature + * callback(leftAmount, rightAmount) - function to call on close + * leftMin, rightMin - minimal amount of creatures in each stack + * leftAmount, rightAmount - amount of creatures in each stack + */ + CSplitWindow(const CCreature * creature, std::function callback, + int leftMin, int rightMin, int leftAmount, int rightAmount); +}; + +/// Raised up level windowe where you can select one out of two skills +class CLevelWindow : public CWindowObject +{ + CComponentBox * box; //skills to select + std::function cb; + + void selectionChanged(unsigned to); +public: + + CLevelWindow(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector &skills, std::function callback); //c-tor + ~CLevelWindow(); //d-tor + +}; + +/// Town portal, castle gate window +class CObjectListWindow : public CWindowObject +{ + class CItem : public CIntObject + { + CObjectListWindow *parent; + CLabel *text; + CPicture *border; + public: + const size_t index; + CItem(CObjectListWindow *parent, size_t id, std::string text); + + void select(bool on); + void clickLeft(tribool down, bool previousState); + }; + + std::function onSelect;//called when OK button is pressed, returns id of selected item. + CLabel * title; + CLabel * descr; + + CListBox * list; + CIntObject * titleImage;//title image (castle gate\town portal picture) + CButton *ok, *exit; + + std::vector< std::pair > items;//all items present in list + + void init(CIntObject * titlePic, std::string _title, std::string _descr); +public: + size_t selected;//index of currently selected item + /// Callback will be called when OK button is pressed, returns id of selected item. initState = initially selected item + /// Image can be nullptr + ///item names will be taken from map objects + CObjectListWindow(const std::vector &_items, CIntObject * titlePic, std::string _title, std::string _descr, + std::function Callback); + CObjectListWindow(const std::vector &_items, CIntObject * titlePic, std::string _title, std::string _descr, + std::function Callback); + + CIntObject *genItem(size_t index); + void elementSelected();//call callback and close this window + void changeSelection(size_t which); + void keyPressed (const SDL_KeyboardEvent & key); +}; + +class CSystemOptionsWindow : public CWindowObject +{ +private: + CLabel *title; + CLabelGroup *leftGroup; + CLabelGroup *rightGroup; + CButton *load, *save, *restart, *mainMenu, *quitGame, *backToMap; //load and restart are not used yet + CToggleGroup * heroMoveSpeed; + CToggleGroup * enemyMoveSpeed; + CToggleGroup * mapScrollSpeed; + CToggleGroup * musicVolume, * effectsVolume; + + //CHighlightableButton * showPath; + CToggleButton * showReminder; + CToggleButton * quickCombat; + CToggleButton * spellbookAnim; + CToggleButton * fullscreen; + + CButton *gameResButton; + CLabel *gameResLabel; + + SettingsListener onFullscreenChanged; + + //functions bound to buttons + void bloadf(); //load game + void bsavef(); //save game + void bquitf(); //quit game + void breturnf(); //return to game + void brestartf(); //restart game + void bmainmenuf(); //return to main menu + + void selectGameRes(); + void setGameRes(int index); + void closeAndPushEvent(int eventType, int code = 0); + +public: + CSystemOptionsWindow(); //c-tor +}; + +class CTavernWindow : public CWindowObject +{ +public: + class HeroPortrait : public CIntObject + { + public: + std::string hoverName; + std::string description; // "XXX is a level Y ZZZ with N artifacts" + const CGHeroInstance *h; + + void clickLeft(tribool down, bool previousState); + void clickRight(tribool down, bool previousState); + void hover (bool on); + HeroPortrait(int &sel, int id, int x, int y, const CGHeroInstance *H); + + private: + int *_sel; + const int _id; + + } *h1, *h2; //recruitable heroes + + CGStatusBar *bar; //tavern's internal status bar + int selected;//0 (left) or 1 (right) + int oldSelected;//0 (left) or 1 (right) + + CButton *thiefGuild, *cancel, *recruit; + const CGObjectInstance *tavernObj; + + CTavernWindow(const CGObjectInstance *TavernObj); //c-tor + ~CTavernWindow(); //d-tor + + void recruitb(); + void thievesguildb(); + void show(SDL_Surface * to); +}; + +class CExchangeWindow : public CWindowObject, public CWindowWithGarrison, public CWindowWithArtifacts +{ + CGStatusBar * ourBar; //internal statusbar + + CButton * quit, * questlogButton[2]; + + std::vector secSkillAreas[2], primSkillAreas; + + MoraleLuckBox *morale[2], *luck[2]; + + LRClickableAreaWText *specialty[2]; + LRClickableAreaWText *experience[2]; + LRClickableAreaWText *spellPoints[2]; + CHeroArea *portrait[2]; + +public: + + const CGHeroInstance* heroInst[2]; + CArtifactsOfHero * artifs[2]; + + void questlog(int whichHero); //questlog button callback; whichHero: 0 - left, 1 - right + + void prepareBackground(); //prepares or redraws bg + + CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID); //c-tor + ~CExchangeWindow(); //d-tor +}; + +/// Here you can buy ships +class CShipyardWindow : public CWindowObject +{ +public: + CGStatusBar *bar; + CPicture *bgWater; + + CLabel *title; + CLabel *costLabel; + + CAnimImage *woodPic, *goldPic; + CLabel *woodCost, *goldCost; + + CAnimImage *bgShip; + CButton *build, *quit; + + CGStatusBar * statusBar; + + CShipyardWindow(const std::vector &cost, int state, int boatType, const std::function &onBuy); +}; + +/// Puzzle screen which gets uncovered when you visit obilisks +class CPuzzleWindow : public CWindowObject +{ +private: + int3 grailPos; + + CButton * quitb; + + std::vector piecesToRemove; + ui8 currentAlpha; + +public: + void showAll(SDL_Surface * to); + void show(SDL_Surface * to); + + CPuzzleWindow(const int3 &grailPos, double discoveredRatio); +}; + +/// Creature transformer window +class CTransformerWindow : public CWindowObject, public CGarrisonHolder +{ +public: + class CItem : public CIntObject + { + public: + int id;//position of creature in hero army + bool left;//position of the item + int size; //size of creature stack + CTransformerWindow * parent; + CAnimImage *icon; + + void move(); + void clickLeft(tribool down, bool previousState); + void update(); + CItem(CTransformerWindow * parent, int size, int id); + }; + + const CArmedInstance *army;//object with army for transforming (hero or town) + const CGHeroInstance *hero;//only if we have hero in town + const CGTownInstance *town;//market, town garrison is used if hero == nullptr + std::vector items; + + CButton *all, *convert, *cancel; + CGStatusBar *bar; + void makeDeal(); + void addAll(); + void updateGarrisons(); + CTransformerWindow(const CGHeroInstance * _hero, const CGTownInstance * _town); //c-tor +}; + +class CUniversityWindow : public CWindowObject +{ + class CItem : public CAnimImage + { + public: + int ID;//id of selected skill + CUniversityWindow * parent; + + void showAll(SDL_Surface * to); + void clickLeft(tribool down, bool previousState); + void clickRight(tribool down, bool previousState); + void hover(bool on); + int state();//0=can't learn, 1=learned, 2=can learn + CItem(CUniversityWindow * _parent, int _ID, int X, int Y); + }; + +public: + const CGHeroInstance *hero; + const IMarket * market; + + CPicture * green, * yellow, * red;//colored bars near skills + std::vector items; + + CButton *cancel; + CGStatusBar *bar; + + CUniversityWindow(const CGHeroInstance * _hero, const IMarket * _market); //c-tor +}; + +/// Confirmation window for University +class CUnivConfirmWindow : public CWindowObject +{ +public: + CUniversityWindow * parent; + CGStatusBar *bar; + CButton *confirm, *cancel; + + CUnivConfirmWindow(CUniversityWindow * PARENT, int SKILL, bool available); //c-tor + void makeDeal(int skill); +}; + +/// Hill fort is the building where you can upgrade units +class CHillFortWindow : public CWindowObject, public CWindowWithGarrison +{ +public: + + int slotsCount;//=7; + CGStatusBar * bar; + CDefEssential *resources; + CHeroArea *heroPic;//clickable hero image + CButton *quit,//closes window + *upgradeAll,//upgrade all creatures + *upgrade[7];//upgrade single creature + + const CGObjectInstance * fort; + const CGHeroInstance * hero; + std::vector currState;//current state of slot - to avoid calls to getState or updating buttons + std::vector costs;// costs [slot ID] [resource ID] = resource count for upgrade + TResources totalSumm; // totalSum[resource ID] = value + + CHillFortWindow(const CGHeroInstance *visitor, const CGObjectInstance *object); //c-tor + + void showAll (SDL_Surface *to); + std::string getDefForSlot(SlotID slot);//return def name for this slot + std::string getTextForSlot(SlotID slot);//return hover text for this slot + void makeDeal(SlotID slot);//-1 for upgrading all creatures + int getState(SlotID slot); //-1 = no creature 0=can't upgrade, 1=upgraded, 2=can upgrade + void updateGarrisons();//update buttons after garrison changes +}; + +class CThievesGuildWindow : public CWindowObject +{ + const CGObjectInstance * owner; + + CGStatusBar * statusBar; + CButton * exitb; + CMinorResDataBar * resdatabar; + +public: + CThievesGuildWindow(const CGObjectInstance * _owner); +}; diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp new file mode 100644 index 000000000..99e7b67c6 --- /dev/null +++ b/client/windows/InfoWindows.cpp @@ -0,0 +1,455 @@ +#include "StdInc.h" +#include "InfoWindows.h" + +#include "../CBitmapHandler.h" +#include "../Graphics.h" +#include "../CGameInfo.h" +#include "../CPlayerInterface.h" +#include "../CMessage.h" +#include "../CMusicHandler.h" + +#include "../windows/CAdvmapInterface.h" +#include "../widgets/CComponent.h" +#include "../widgets/MiscWidgets.h" + +#include "../gui/SDL_Pixels.h" +#include "../gui/SDL_Extensions.h" +#include "../gui/CGuiHandler.h" +#include "../gui/CCursorHandler.h" + +#include "../battle/CBattleInterface.h" +#include "../battle/CBattleInterfaceClasses.h" + +#include "../../CCallback.h" + +#include "../../lib/CGameState.h" +#include "../../lib/CConfigHandler.h" +#include "../../lib/CondSh.h" +#include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff +#include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/mapObjects/MiscObjects.h" + +/* + * InfoWindows.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 + * + */ + +void CSimpleWindow::show(SDL_Surface * to) +{ + if(bitmap) + blitAt(bitmap,pos.x,pos.y,to); +} +CSimpleWindow::~CSimpleWindow() +{ + if (bitmap) + { + SDL_FreeSurface(bitmap); + bitmap=nullptr; + } +} + +void CSelWindow::selectionChange(unsigned to) +{ + for (unsigned i=0;i(components[i]); + if (!pom) + continue; + pom->select(i==to); + } + redraw(); +} + +CSelWindow::CSelWindow(const std::string &Text, PlayerColor player, int charperline, const std::vector &comps, const std::vector > > &Buttons, QueryID askID) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + ID = askID; + for(int i=0;i= 0) + buttons.back()->addCallback(std::bind(&CSelWindow::madeChoice,this)); + buttons[i]->addCallback(std::bind(&CInfoWindow::close,this)); //each button will close the window apart from call-defined actions + } + + text = new CTextBox(Text, Rect(0, 0, 250, 100), 0, FONT_MEDIUM, CENTER, Colors::WHITE); + + buttons.front()->assignedKeys.insert(SDLK_RETURN); //first button - reacts on enter + buttons.back()->assignedKeys.insert(SDLK_ESCAPE); //last button - reacts on escape + + if(buttons.size() > 1 && askID.getNum() >= 0) //cancel button functionality + buttons.back()->addCallback(std::bind(&CCallback::selectionMade,LOCPLINT->cb.get(),0,askID)); + + for(int i=0;irecActions = 255; + addChild(comps[i]); + components.push_back(comps[i]); + comps[i]->onSelect = std::bind(&CSelWindow::selectionChange,this,i); + if(i<9) + comps[i]->assignedKeys.insert(SDLK_1+i); + } + CMessage::drawIWindow(this, Text, player); +} + +void CSelWindow::madeChoice() +{ + if(ID.getNum() < 0) + return; + int ret = -1; + for (int i=0;i(components[i])->selected) + { + ret = i; + } + } + LOCPLINT->cb->selectionMade(ret+1,ID); +} + +CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo &comps, const TButtonsInfo &Buttons, bool delComps) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL; + + type |= BLOCK_ADV_HOTKEYS; + ID = QueryID(-1); + for(auto & Button : Buttons) + { + CButton *button = new CButton(Point(0,0), Button.first, CButton::tooltip(), std::bind(&CInfoWindow::close,this)); + button->borderColor = Colors::METALLIC_GOLD; + button->addCallback(Button.second); //each button will close the window apart from call-defined actions + buttons.push_back(button); + } + + text = new CTextBox(Text, Rect(0, 0, 250, 100), 0, FONT_MEDIUM, CENTER, Colors::WHITE); + if(!text->slider) + { + text->resize(text->label->textSize); + } + + if(buttons.size()) + { + buttons.front()->assignedKeys.insert(SDLK_RETURN); //first button - reacts on enter + buttons.back()->assignedKeys.insert(SDLK_ESCAPE); //last button - reacts on escape + } + + for(auto & comp : comps) + { + comp->recActions = 0xff; + addChild(comp); + comp->recActions &= ~(SHOWALL | UPDATE); + components.push_back(comp); + } + setDelComps(delComps); + CMessage::drawIWindow(this,Text,player); +} + +CInfoWindow::CInfoWindow() +{ + ID = QueryID(-1); + setDelComps(false); + text = nullptr; +} + +void CInfoWindow::close() +{ + GH.popIntTotally(this); + if(LOCPLINT) + LOCPLINT->showingDialog->setn(false); +} + +void CInfoWindow::show(SDL_Surface * to) +{ + CIntObject::show(to); +} + +CInfoWindow::~CInfoWindow() +{ + if(!delComps) + { + for (auto & elem : components) + removeChild(elem); + } +} + +void CInfoWindow::showAll(SDL_Surface * to) +{ + CSimpleWindow::show(to); + CIntObject::showAll(to); +} + +void CInfoWindow::showInfoDialog(const std::string &text, const std::vector *components, bool DelComps, PlayerColor player) +{ + CInfoWindow * window = CInfoWindow::create(text, player, components, DelComps); + GH.pushInt(window); +} + +void CInfoWindow::showYesNoDialog(const std::string & text, const std::vector *components, const CFunctionList &onYes, const CFunctionList &onNo, bool DelComps, PlayerColor player) +{ + assert(!LOCPLINT || LOCPLINT->showingDialog->get()); + std::vector > > pom; + pom.push_back(std::pair >("IOKAY.DEF",0)); + pom.push_back(std::pair >("ICANCEL.DEF",0)); + CInfoWindow * temp = new CInfoWindow(text, player, components ? *components : std::vector(), pom, DelComps); + for(auto & elem : onYes.funcs) + temp->buttons[0]->addCallback(elem); + for(auto & elem : onNo.funcs) + temp->buttons[1]->addCallback(elem); + + GH.pushInt(temp); +} + +void CInfoWindow::showOkDialog(const std::string & text, const std::vector *components, const std::function & onOk, bool delComps, PlayerColor player) +{ + std::vector > > pom; + pom.push_back(std::pair >("IOKAY.DEF",0)); + CInfoWindow * temp = new CInfoWindow(text, player, *components, pom, delComps); + temp->buttons[0]->addCallback(onOk); + + GH.pushInt(temp); +} + +CInfoWindow * CInfoWindow::create(const std::string &text, PlayerColor playerID /*= 1*/, const std::vector *components /*= nullptr*/, bool DelComps) +{ + std::vector > > pom; + pom.push_back(std::pair >("IOKAY.DEF",0)); + CInfoWindow * ret = new CInfoWindow(text, playerID, components ? *components : std::vector(), pom, DelComps); + return ret; +} + +std::string CInfoWindow::genText(std::string title, std::string description) +{ + return std::string("{") + title + "}" + "\n\n" + description; +} + +void CInfoWindow::setDelComps(bool DelComps) +{ + delComps = DelComps; + for(CComponent *comp : components) + { + if(delComps) + comp->recActions |= DISPOSE; + else + comp->recActions &= ~DISPOSE; + } +} + +CInfoPopup::CInfoPopup(SDL_Surface * Bitmap, int x, int y, bool Free) + :free(Free),bitmap(Bitmap) +{ + init(x, y); +} + + +CInfoPopup::CInfoPopup(SDL_Surface * Bitmap, const Point &p, EAlignment alignment, bool Free/*=false*/) + : free(Free),bitmap(Bitmap) +{ + switch(alignment) + { + case BOTTOMRIGHT: + init(p.x - Bitmap->w, p.y - Bitmap->h); + break; + case CENTER: + init(p.x - Bitmap->w/2, p.y - Bitmap->h/2); + break; + case TOPLEFT: + init(p.x, p.y); + break; + default: + assert(0); //not implemented + } +} + +CInfoPopup::CInfoPopup(SDL_Surface *Bitmap, bool Free) +{ + CCS->curh->hide(); + + free=Free; + bitmap=Bitmap; + + if(bitmap) + { + pos.x = screen->w/2 - bitmap->w/2; + pos.y = screen->h/2 - bitmap->h/2; + pos.h = bitmap->h; + pos.w = bitmap->w; + } +} + +void CInfoPopup::close() +{ + if(free) + SDL_FreeSurface(bitmap); + GH.popIntTotally(this); +} + +void CInfoPopup::show(SDL_Surface * to) +{ + blitAt(bitmap,pos.x,pos.y,to); +} + +CInfoPopup::~CInfoPopup() +{ + CCS->curh->show(); +} + +void CInfoPopup::init(int x, int y) +{ + CCS->curh->hide(); + + pos.x = x; + pos.y = y; + pos.h = bitmap->h; + pos.w = bitmap->w; + + // Put the window back on screen if necessary + vstd::amax(pos.x, 0); + vstd::amax(pos.y, 0); + vstd::amin(pos.x, screen->w - bitmap->w); + vstd::amin(pos.y, screen->h - bitmap->h); +} + + +void CRClickPopup::clickRight(tribool down, bool previousState) +{ + if(down) + return; + close(); +} + +void CRClickPopup::close() +{ + GH.popIntTotally(this); +} + +void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCompsInfo &comps) +{ + PlayerColor player = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1); //if no player, then use blue + + CSimpleWindow * temp = new CInfoWindow(txt, player, comps); + temp->center(Point(GH.current->motion)); //center on mouse + temp->fitToScreen(10); + auto rcpi = new CRClickPopupInt(temp,true); + GH.pushInt(rcpi); +} + +void CRClickPopup::createAndPush(const std::string &txt, CComponent * component) +{ + CInfoWindow::TCompsInfo intComps; + intComps.push_back(component); + + createAndPush(txt, intComps); +} + +void CRClickPopup::createAndPush(const CGObjectInstance *obj, const Point &p, EAlignment alignment /*= BOTTOMRIGHT*/) +{ + CIntObject *iWin = createInfoWin(p, obj); //try get custom infowindow for this obj + if(iWin) + GH.pushInt(iWin); + else + { + if (adventureInt->curHero()) + CRClickPopup::createAndPush(obj->getHoverText(adventureInt->curHero())); + else + CRClickPopup::createAndPush(obj->getHoverText(LOCPLINT->playerID)); + } +} + +CRClickPopup::CRClickPopup() +{ + addUsedEvents(RCLICK); +} + +CRClickPopup::~CRClickPopup() +{ +} + +void CRClickPopupInt::show(SDL_Surface * to) +{ + inner->show(to); +} + +CRClickPopupInt::CRClickPopupInt( IShowActivatable *our, bool deleteInt ) +{ + CCS->curh->hide(); + inner = our; + delInner = deleteInt; +} + +CRClickPopupInt::~CRClickPopupInt() +{ + if(delInner) + delete inner; + + CCS->curh->show(); +} + +void CRClickPopupInt::showAll(SDL_Surface * to) +{ + inner->showAll(to); +} + +Point CInfoBoxPopup::toScreen(Point p) +{ + vstd::abetween(p.x, adventureInt->terrain.pos.x + 100, adventureInt->terrain.pos.x + adventureInt->terrain.pos.w - 100); + vstd::abetween(p.y, adventureInt->terrain.pos.y + 100, adventureInt->terrain.pos.y + adventureInt->terrain.pos.h - 100); + + return p; +} + +CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town): + CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "TOWNQVBK", toScreen(position)) +{ + InfoAboutTown iah; + LOCPLINT->cb->getTownInfo(town, iah); + + OBJ_CONSTRUCTION_CAPTURING_ALL; + new CTownTooltip(Point(9, 10), iah); +} + +CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero): + CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "HEROQVBK", toScreen(position)) +{ + InfoAboutHero iah; + LOCPLINT->cb->getHeroInfo(hero, iah); + + OBJ_CONSTRUCTION_CAPTURING_ALL; + new CHeroTooltip(Point(9, 10), iah); +} + +CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr): + CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "TOWNQVBK", toScreen(position)) +{ + InfoAboutTown iah; + LOCPLINT->cb->getTownInfo(garr, iah); + + OBJ_CONSTRUCTION_CAPTURING_ALL; + new CArmyTooltip(Point(9, 10), iah); +} + +CIntObject * CRClickPopup::createInfoWin(Point position, const CGObjectInstance * specific) //specific=0 => draws info about selected town/hero +{ + if(!specific) + specific = adventureInt->selection; + + assert(specific); + + switch(specific->ID) + { + case Obj::HERO: + return new CInfoBoxPopup(position, dynamic_cast(specific)); + case Obj::TOWN: + return new CInfoBoxPopup(position, dynamic_cast(specific)); + case Obj::GARRISON: + case Obj::GARRISON2: + return new CInfoBoxPopup(position, dynamic_cast(specific)); + default: + return nullptr; + } +} diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h new file mode 100644 index 000000000..540480d09 --- /dev/null +++ b/client/windows/InfoWindows.h @@ -0,0 +1,137 @@ +#pragma once + +#include "CWindowObject.h" +//#include "../gui/SDL_Extensions.h" +#include "../../lib/FunctionList.h" + +struct SDL_Surface; +struct Rect; +class CAnimImage; +class CLabel; +class CAnimation; +class CDefHandler; +class CComponent; +class CSelectableComponent; +class CGGarrison; +class CTextBox; +class CButton; +class CSlider; + +/* + * InfoWindows.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 + * + */ +// Window GUI class +class CSimpleWindow : public CIntObject +{ +public: + SDL_Surface * bitmap; //background + virtual void show(SDL_Surface * to); + CSimpleWindow():bitmap(nullptr){}; //c-tor + virtual ~CSimpleWindow(); //d-tor +}; + +/// text + comp. + ok button +class CInfoWindow : public CSimpleWindow +{ //window able to delete its components when closed + bool delComps; //whether comps will be deleted + +public: + typedef std::vector > > TButtonsInfo; + typedef std::vector TCompsInfo; + QueryID ID; //for identification + CTextBox *text; + std::vector buttons; + std::vector components; + CSlider *slider; + + void setDelComps(bool DelComps); + virtual void close(); + + void show(SDL_Surface * to); + void showAll(SDL_Surface * to); + void sliderMoved(int to); + + CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo &comps = TCompsInfo(), const TButtonsInfo &Buttons = TButtonsInfo(), bool delComps = true); //c-tor + CInfoWindow(); //c-tor + ~CInfoWindow(); //d-tor + + //use only before the game starts! (showYesNoDialog in LOCPLINT must be used then) + static void showInfoDialog( const std::string & text, const std::vector *components, bool DelComps = true, PlayerColor player = PlayerColor(1)); + static void showOkDialog(const std::string & text, const std::vector *components, const std::function & onOk, bool delComps = true, PlayerColor player = PlayerColor(1)); + static void showYesNoDialog( const std::string & text, const std::vector *components, const CFunctionList &onYes, const CFunctionList &onNo, bool DelComps = true, PlayerColor player = PlayerColor(1)); + static CInfoWindow *create(const std::string &text, PlayerColor playerID = PlayerColor(1), const std::vector *components = nullptr, bool DelComps = false); + + /// create text from title and description: {title}\n\n description + static std::string genText(std::string title, std::string description); +}; + +/// popup displayed on R-click +class CRClickPopup : public CIntObject +{ +public: + virtual void close(); + void clickRight(tribool down, bool previousState); + + CRClickPopup(); + virtual ~CRClickPopup(); //d-tor + + static CIntObject* createInfoWin(Point position, const CGObjectInstance * specific); + static void createAndPush(const std::string &txt, const CInfoWindow::TCompsInfo &comps = CInfoWindow::TCompsInfo()); + static void createAndPush(const std::string &txt, CComponent * component); + static void createAndPush(const CGObjectInstance *obj, const Point &p, EAlignment alignment = BOTTOMRIGHT); +}; + +/// popup displayed on R-click +class CRClickPopupInt : public CRClickPopup +{ +public: + IShowActivatable *inner; + bool delInner; + + void show(SDL_Surface * to); + void showAll(SDL_Surface * to); + CRClickPopupInt(IShowActivatable *our, bool deleteInt); //c-tor + virtual ~CRClickPopupInt(); //d-tor +}; + +class CInfoPopup : public CRClickPopup +{ +public: + bool free; //TODO: comment me + SDL_Surface * bitmap; //popup background + void close(); + void show(SDL_Surface * to); + CInfoPopup(SDL_Surface * Bitmap, int x, int y, bool Free=false); //c-tor + CInfoPopup(SDL_Surface * Bitmap, const Point &p, EAlignment alignment, bool Free=false); //c-tor + CInfoPopup(SDL_Surface * Bitmap = nullptr, bool Free = false); //default c-tor + + void init(int x, int y); + ~CInfoPopup(); //d-tor +}; + +/// popup on adventure map for town\hero objects +class CInfoBoxPopup : public CWindowObject +{ + Point toScreen(Point pos); +public: + CInfoBoxPopup(Point position, const CGTownInstance * town); + CInfoBoxPopup(Point position, const CGHeroInstance * hero); + CInfoBoxPopup(Point position, const CGGarrison * garr); +}; + +/// component selection window +class CSelWindow : public CInfoWindow +{ //warning - this window deletes its components by closing! +public: + void selectionChange(unsigned to); + void madeChoice(); //looks for selected component and calls callback + CSelWindow(const std::string& text, PlayerColor player, int charperline ,const std::vector &comps, const std::vector > > &Buttons, QueryID askID); //c-tor + CSelWindow(){}; //c-tor + //notification - this class inherits important destructor from CInfoWindow +}; diff --git a/config/objects/creatureBanks.json b/config/objects/creatureBanks.json index 27270927e..e453a30c0 100644 --- a/config/objects/creatureBanks.json +++ b/config/objects/creatureBanks.json @@ -538,11 +538,11 @@ { "chance": 30, "guards": [ - { "amount": 6, "type": "dragonFly" }, - { "amount": 6, "type": "dragonFly" }, - { "amount": 6, "type": "dragonFly" }, - { "amount": 6, "type": "dragonFly" }, - { "amount": 6, "type": "dragonFly" } + { "amount": 6, "type": "serpentFly" }, + { "amount": 6, "type": "serpentFly" }, + { "amount": 6, "type": "serpentFly" }, + { "amount": 6, "type": "serpentFly" }, + { "amount": 6, "type": "serpentFly" } ], "combat_value": 154, "reward" : { @@ -553,11 +553,11 @@ { "chance": 30, "guards": [ - { "amount": 9, "type": "dragonFly" }, - { "amount": 9, "type": "dragonFly" }, - { "amount": 9, "type": "dragonFly" }, - { "amount": 9, "type": "dragonFly" }, - { "amount": 9, "type": "dragonFly" } + { "amount": 9, "type": "serpentFly" }, + { "amount": 9, "type": "serpentFly" }, + { "amount": 9, "type": "serpentFly" }, + { "amount": 9, "type": "serpentFly" }, + { "amount": 9, "type": "serpentFly" } ], "combat_value": 230, "reward" : { @@ -568,11 +568,11 @@ { "chance": 30, "guards": [ - { "amount": 12, "type": "dragonFly" }, - { "amount": 12, "type": "dragonFly" }, - { "amount": 12, "type": "dragonFly" }, - { "amount": 12, "type": "dragonFly" }, - { "amount": 12, "type": "dragonFly" } + { "amount": 12, "type": "serpentFly" }, + { "amount": 12, "type": "serpentFly" }, + { "amount": 12, "type": "serpentFly" }, + { "amount": 12, "type": "serpentFly" }, + { "amount": 12, "type": "serpentFly" } ], "combat_value": 307, "reward" : { @@ -583,11 +583,11 @@ { "chance": 10, "guards": [ - { "amount": 18, "type": "dragonFly" }, - { "amount": 18, "type": "dragonFly" }, - { "amount": 18, "type": "dragonFly" }, - { "amount": 18, "type": "dragonFly" }, - { "amount": 18, "type": "dragonFly" } + { "amount": 18, "type": "serpentFly" }, + { "amount": 18, "type": "serpentFly" }, + { "amount": 18, "type": "serpentFly" }, + { "amount": 18, "type": "serpentFly" }, + { "amount": 18, "type": "serpentFly" } ], "combat_value": 461, "reward" : { diff --git a/config/resolutions.json b/config/resolutions.json index bedcbaa8b..a1ffb906d 100644 --- a/config/resolutions.json +++ b/config/resolutions.json @@ -1,311 +1,32 @@ { "GUISettings": - [ - { - "resolution": { "x": 800, "y": 600 }, - "InGameConsole": { "maxInputPerLine": 60, "maxOutputPerLine": 39 }, - "AdvMap": { "x": 7, "y": 7, "width": 594, "height": 546, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 605, "y": 389 }, - "gem0": { "x": 6, "y": 508, "graphic": "agemLL.def" }, - "gem1": { "x": 556, "y": 508, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 556, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap.bmp", - "HeroList": { "size": 5, "x": 609, "y": 196, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 5, "x": 747, "y": 196, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 630, "y": 26 }, - "Overview": { "pics": 4, "size": 4, "graphic": "OvCast.pcx" }, - "Statusbar": { "x": 7, "y": 556, "graphic": "AdRollvr.bmp" }, - "ResDataBar": { "x": 3, "y": 575, "graphic": "ARESBAR.bmp", "offsetX": 32, "offsetY": 2, "resSpace": 85, "resDateSpace": 85 }, - "ButtonKingdomOv": { "x": 679, "y": 196, "graphic": "IAM002.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 711, "y": 196, "graphic": "IAM010.DEF", "playerColoured": 1, "additionalDefs": [ "IAM003.DEF" ] }, - "ButtonQuestLog": { "x": 679, "y": 228, "graphic": "IAM004.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 711, "y": 228, "graphic": "IAM005.DEF", "playerColoured": 1, "additionalDefs":["IAM011.DEF"] }, - "ButtonMoveHero": { "x": 679, "y": 260, "graphic": "IAM006.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 711, "y": 260, "graphic": "IAM007.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 679, "y": 292, "graphic": "IAM008.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 711, "y": 292, "graphic": "IAM009.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 679, "y": 324, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 679, "y": 356, "graphic": "IAM001.DEF", "playerColoured": 1 } - }, - - { - "resolution": { "x": 1024, "y": 600 }, - "InGameConsole": { "maxInputPerLine": 70, "maxOutputPerLine": 50 }, - "AdvMap": { "x": 7, "y": 7, "width": 818, "height": 546, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 829, "y": 389 }, - "gem0": { "x": 6, "y": 508, "graphic": "agemLL.def" }, - "gem1": { "x": 780, "y": 508, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 780, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap1024x600.pcx", - "HeroList": { "size": 5, "x": 833, "y": 196, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 5, "x": 971, "y": 196, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 854, "y": 26 }, - "Overview": { "pics": 4, "size": 4, "graphic": "OvCast.pcx" }, - "Statusbar": { "x": 8, "y": 556, "graphic": "AdRollvr1024.pcx" }, - "ResDataBar": { "x": 0, "y": 575, "graphic": "ZResBar1024.pcx", "offsetX": 66, "offsetY": 2, "resSpace": 109, "resDateSpace": 133 }, - "ButtonKingdomOv": { "x": 903, "y": 196, "graphic": "IAM002.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 935, "y": 196, "graphic": "IAM010.DEF", "playerColoured": 1, "additionalDefs": [ "IAM003.DEF" ] }, - "ButtonQuestLog": { "x": 903, "y": 228, "graphic": "IAM004.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 935, "y": 228, "graphic": "IAM005.DEF", "playerColoured": 1, "additionalDefs":["IAM011.DEF"] }, - "ButtonMoveHero": { "x": 903, "y": 260, "graphic": "IAM006.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 935, "y": 260, "graphic": "IAM007.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 903, "y": 292, "graphic": "IAM008.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 935, "y": 292, "graphic": "IAM009.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 903, "y": 324, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 903, "y": 356, "graphic": "IAM001.DEF", "playerColoured": 1 } - }, - - { - "resolution": { "x": 1024, "y": 768 }, - "InGameConsole": { "maxInputPerLine": 70, "maxOutputPerLine": 57 }, - "AdvMap": { "x": 7, "y": 7, "width": 818, "height": 714, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 829, "y": 557 }, - "gem0": { "x": 6, "y": 676, "graphic": "agemLL.def" }, - "gem1": { "x": 780, "y": 676, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 780, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap1024x768.pcx", - "HeroList": { "size": 10, "x": 833, "y": 201, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 10, "x": 971, "y": 201, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 854, "y": 26 }, - "Overview": { "pics": 4, "size": 5, "graphic": "OvCast5.pcx" }, - "Statusbar": { "x": 8, "y": 723, "graphic": "AdRollvr1024.pcx" }, - "ResDataBar": { "x": 0, "y": 743, "graphic": "ZResBar1024.pcx", "offsetX": 66, "offsetY": 2, "resSpace": 109, "resDateSpace": 133 }, - "ButtonKingdomOv": { "x": 903, "y": 197, "graphic": "IAM002L.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 903, "y": 229, "graphic": "IAM010L.DEF", "playerColoured": 1, "additionalDefs": ["IAM003L.DEF" ] }, - "ButtonQuestLog": { "x": 903, "y": 261, "graphic": "IAM004L.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 903, "y": 293, "graphic": "IAM005L.DEF", "playerColoured": 1, "additionalDefs":["IAM011L.DEF"] }, - "ButtonMoveHero": { "x": 903, "y": 326, "graphic": "IAM006L.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 903, "y": 359, "graphic": "IAM007L.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 903, "y": 392, "graphic": "IAM008L.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 903, "y": 425, "graphic": "IAM009L.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 903, "y": 491, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 903, "y": 524, "graphic": "IAM001.DEF", "playerColoured": 1 } - }, - { - "resolution": { "x": 1280, "y": 960 }, - "InGameConsole": { "maxInputPerLine": 80, "maxOutputPerLine": 57 }, - "AdvMap": { "x": 7, "y": 7, "width": 1074, "height": 906, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 1085, "y": 749 }, - "gem0": { "x": 6, "y": 868, "graphic": "agemLL.def" }, - "gem1": { "x": 1036, "y": 868, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 1036, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap1280x960.pcx", - "HeroList": { "size": 16, "x": 1089, "y": 196, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 16, "x": 1227, "y": 196, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 1110, "y": 26 }, - "Overview": { "pics": 4, "size": 6, "graphic": "OvCast6.pcx" }, - "Statusbar": { "x": 8, "y": 916, "graphic": "AdRollvr1280.pcx" }, - "ResDataBar": { "x": 0, "y": 935, "graphic": "ZResBar1280.pcx", "offsetX": 88, "offsetY": 2, "resSpace": 142, "resDateSpace": 167 }, - "ButtonKingdomOv": { "x": 1159, "y": 197, "graphic": "IAM002L.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 1159, "y": 229, "graphic": "IAM010L.DEF", "playerColoured": 1, "additionalDefs": ["IAM003L.DEF" ] }, - "ButtonQuestLog": { "x": 1159, "y": 261, "graphic": "IAM004L.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 1159, "y": 293, "graphic": "IAM005L.DEF", "playerColoured": 1, "additionalDefs":["IAM011L.DEF"] }, - "ButtonMoveHero": { "x": 1159, "y": 326, "graphic": "IAM006L.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 1159, "y": 359, "graphic": "IAM007L.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 1159, "y": 392, "graphic": "IAM008L.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 1159, "y": 425, "graphic": "IAM009L.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 1159, "y": 491, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 1159, "y": 524, "graphic": "IAM001.DEF", "playerColoured": 1 } - }, - - { - "resolution": { "x": 1280, "y": 1024 }, - "InGameConsole": { "maxInputPerLine": 80, "maxOutputPerLine": 57 }, - "AdvMap": { "x": 7, "y": 7, "width": 1074, "height": 970, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 1085, "y": 517 }, - "gem0": { "x": 6, "y": 932, "graphic": "agemLL.def" }, - "gem1": { "x": 1036, "y": 932, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 1036, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap1280x1024.PCX", - "HeroList": { "size": 9, "x": 1089, "y": 196, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 9, "x": 1227, "y": 196, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 1110, "y": 26 }, - "Overview": { "pics": 4, "size": 7, "graphic": "OvCast7.pcx" }, - "Statusbar": { "x": 8, "y": 980, "graphic": "AdRollvr1280.pcx" }, - "ResDataBar": { "x": 0, "y": 999, "graphic": "ZResBar1280.pcx", "offsetX": 88, "offsetY": 2, "resSpace": 142, "resDateSpace": 167 }, - "ButtonKingdomOv": { "x": 1159, "y": 196, "graphic": "IAM002L.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 1159, "y": 228, "graphic": "IAM010L.DEF", "playerColoured": 1, "additionalDefs": ["IAM003L.DEF" ] }, - "ButtonQuestLog": { "x": 1159, "y": 260, "graphic": "IAM004L.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 1159, "y": 292, "graphic": "IAM005L.DEF", "playerColoured": 1, "additionalDefs":["IAM011L.DEF"] }, - "ButtonMoveHero": { "x": 1159, "y": 324, "graphic": "IAM006L.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 1159, "y": 355, "graphic": "IAM007L.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 1159, "y": 388, "graphic": "IAM008L.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 1159, "y": 420, "graphic": "IAM009L.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 1159, "y": 452, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 1159, "y": 484, "graphic": "IAM001.DEF", "playerColoured": 1 } - }, - - { - "resolution": { "x": 1366, "y": 768 }, - "InGameConsole": { "maxInputPerLine": 85, "maxOutputPerLine": 58 }, - "AdvMap": { "x": 7, "y": 7, "width": 1160, "height": 714, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 1171, "y": 557 }, - "gem0": { "x": 6, "y": 676, "graphic": "agemLL.def" }, - "gem1": { "x": 1122, "y": 676, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 1122, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap1366x768.pcx", - "HeroList": { "size": 10, "x": 1175, "y": 201, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 10, "x": 1313, "y": 201, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 1196, "y": 26 }, - "Overview": { "pics": 4, "size": 5, "graphic": "OvCast5.pcx" }, - "Statusbar": { "x": 8, "y": 723, "graphic": "AdRollvr1366.pcx" }, - "ResDataBar": { "x": 0, "y": 743, "graphic": "ZResBar1366.pcx", "offsetX": 90, "offsetY": 2, "resSpace": 153, "resDateSpace": 185 }, - "ButtonKingdomOv": { "x": 1245, "y": 197, "graphic": "IAM002L.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 1245, "y": 229, "graphic": "IAM010L.DEF", "playerColoured": 1, "additionalDefs": ["IAM003L.DEF" ] }, - "ButtonQuestLog": { "x": 1245, "y": 261, "graphic": "IAM004L.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 1245, "y": 293, "graphic": "IAM005L.DEF", "playerColoured": 1, "additionalDefs":["IAM011L.DEF"] }, - "ButtonMoveHero": { "x": 1245, "y": 326, "graphic": "IAM006L.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 1245, "y": 359, "graphic": "IAM007L.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 1245, "y": 392, "graphic": "IAM008L.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 1245, "y": 425, "graphic": "IAM009L.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 1245, "y": 491, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 1245, "y": 524, "graphic": "IAM001.DEF", "playerColoured": 1 } - }, - - { - "resolution": { "x": 1280, "y": 800 }, - "InGameConsole": { "maxInputPerLine": 90, "maxOutputPerLine": 60 }, - "AdvMap": { "x": 7, "y": 7, "width": 1074, "height": 746, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 1085, "y": 589 }, - "gem0": { "x": 6, "y": 708, "graphic": "agemLL.def" }, - "gem1": { "x": 1036, "y": 708, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 1036, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap1280x800.pcx", - "HeroList": { "size": 11, "x": 1089, "y": 196, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 11, "x": 1227, "y": 196, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 1110, "y": 26 }, - "Overview": { "pics": 4, "size": 5, "graphic": "OvCast5.pcx" }, - "Statusbar": { "x": 8, "y": 754, "graphic": "AdRollvr1280.pcx" }, - "ResDataBar": { "x": 0, "y": 775, "graphic": "ZResBar1280.pcx", "offsetX": 88, "offsetY": 2, "resSpace": 142, "resDateSpace": 167 }, - "ButtonKingdomOv": { "x": 1159, "y": 197, "graphic": "IAM002L.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 1159, "y": 229, "graphic": "IAM010L.DEF", "playerColoured": 1, "additionalDefs": ["IAM003L.DEF" ] }, - "ButtonQuestLog": { "x": 1159, "y": 261, "graphic": "IAM004L.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 1159, "y": 293, "graphic": "IAM005L.DEF", "playerColoured": 1, "additionalDefs":["IAM011L.DEF"] }, - "ButtonMoveHero": { "x": 1159, "y": 326, "graphic": "IAM006L.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 1159, "y": 359, "graphic": "IAM007L.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 1159, "y": 392, "graphic": "IAM008L.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 1159, "y": 425, "graphic": "IAM009L.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 1159, "y": 523, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 1159, "y": 556, "graphic": "IAM001.DEF", "playerColoured": 1 } - }, - - { - "resolution": { "x": 1440, "y": 900 }, - "InGameConsole": { "maxInputPerLine": 90, "maxOutputPerLine": 60 }, - "AdvMap": { "x": 7, "y": 7, "width": 1234, "height": 846, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 1245, "y": 689 }, - "gem0": { "x": 6, "y": 808, "graphic": "agemLL.def" }, - "gem1": { "x": 1196, "y": 808, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 1196, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap1440x900.pcx", - "HeroList": { "size": 14, "x": 1249, "y": 200, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 14, "x": 1387, "y": 200, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 1270, "y": 26 }, - "Overview": { "pics": 4, "size": 6, "graphic": "OvCast6.pcx" }, - "Statusbar": { "x": 8, "y": 855, "graphic": "AdRollvr1440.pcx" }, - "ResDataBar": { "x": 0, "y": 875, "graphic": "ZResBar1440.pcx", "offsetX": 100, "offsetY": 2, "resSpace": 155, "resDateSpace": 238 }, - "ButtonKingdomOv": { "x": 1319, "y": 197, "graphic": "IAM002L.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 1319, "y": 229, "graphic": "IAM010L.DEF", "playerColoured": 1, "additionalDefs": ["IAM003L.DEF" ] }, - "ButtonQuestLog": { "x": 1319, "y": 261, "graphic": "IAM004L.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 1319, "y": 293, "graphic": "IAM005L.DEF", "playerColoured": 1, "additionalDefs":["IAM011L.DEF"] }, - "ButtonMoveHero": { "x": 1319, "y": 326, "graphic": "IAM006L.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 1319, "y": 359, "graphic": "IAM007L.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 1319, "y": 392, "graphic": "IAM008L.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 1319, "y": 425, "graphic": "IAM009L.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 1319, "y": 458, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 1319, "y": 491, "graphic": "IAM001.DEF", "playerColoured": 1 } - }, - - { - "resolution": { "x": 1600, "y": 1200 }, - "InGameConsole": { "maxInputPerLine": 100, "maxOutputPerLine": 70 }, - "AdvMap": { "x": 7, "y": 7, "width": 1394, "height": 1146, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 1405, "y": 989 }, - "gem0": { "x": 6, "y": 1108, "graphic": "agemLL.def" }, - "gem1": { "x": 1356, "y": 1108, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 1356, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap1600x1200.pcx", - "HeroList": { "size": 23, "x": 1409, "y": 201, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 23, "x": 1547, "y": 201, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 1430, "y": 26 }, - "Overview": { "pics": 4, "size": 8, "graphic": "OvCast8.pcx" }, - "Statusbar": { "x": 285, "y": 1155, "graphic": "AdRollvr1600.pcx" }, - "ResDataBar": { "x": 0, "y": 1175, "graphic": "ZResBar1600.pcx", "offsetX": 65, "offsetY": 2, "resSpace": 192, "resDateSpace": 210 }, - "ButtonKingdomOv": { "x": 1479, "y": 197, "graphic": "IAM002L.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 1479, "y": 229, "graphic": "IAM010L.DEF", "playerColoured": 1, "additionalDefs": ["IAM003L.DEF" ] }, - "ButtonQuestLog": { "x": 1479, "y": 261, "graphic": "IAM004L.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 1479, "y": 293, "graphic": "IAM005L.DEF", "playerColoured": 1, "additionalDefs":["IAM011L.DEF"] }, - "ButtonMoveHero": { "x": 1479, "y": 326, "graphic": "IAM006L.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 1479, "y": 359, "graphic": "IAM007L.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 1479, "y": 392, "graphic": "IAM008L.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 1479, "y": 425, "graphic": "IAM009L.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 1479, "y": 458, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 1479, "y": 491, "graphic": "IAM001.DEF", "playerColoured": 1 } - }, - - { - "resolution": { "x": 1680, "y": 1050 }, - "InGameConsole": { "maxInputPerLine": 108, "maxOutputPerLine": 70 }, - "AdvMap": { "x": 7, "y": 7, "width": 1474, "height": 996, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 1485, "y": 839 }, - "gem0": { "x": 6, "y": 958, "graphic": "agemLL.def" }, - "gem1": { "x": 1436, "y": 958, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 1436, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap1680x1050.pcx", - "HeroList": { "size": 19, "x": 1489, "y": 198, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 19, "x": 1627, "y": 198, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 1510, "y": 26 }, - "Overview": { "pics": 4, "size": 7, "graphic": "OvCast7.pcx" }, - "Statusbar": { "x": 8, "y": 1005, "graphic": "AdRollvr1680.pcx" }, - "ResDataBar": { "x": 0, "y": 1025, "graphic": "ZResBar1680.pcx", "offsetX": 67, "offsetY": 2, "resSpace": 192, "resDateSpace": 288 }, - "ButtonKingdomOv": { "x": 1559, "y": 197, "graphic": "IAM002L.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 1559, "y": 229, "graphic": "IAM010L.DEF", "playerColoured": 1, "additionalDefs": ["IAM003L.DEF" ] }, - "ButtonQuestLog": { "x": 1559, "y": 261, "graphic": "IAM004L.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 1559, "y": 293, "graphic": "IAM005L.DEF", "playerColoured": 1, "additionalDefs":["IAM011L.DEF"] }, - "ButtonMoveHero": { "x": 1559, "y": 326, "graphic": "IAM006L.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 1559, "y": 359, "graphic": "IAM007L.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 1559, "y": 392, "graphic": "IAM008L.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 1559, "y": 425, "graphic": "IAM009L.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 1559, "y": 458, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 1559, "y": 491, "graphic": "IAM001.DEF", "playerColoured": 1 } - }, - - { - "resolution": { "x": 1920, "y": 1080 }, - "InGameConsole": { "maxInputPerLine": 115, "maxOutputPerLine": 85 }, - "AdvMap": { "x": 7, "y": 7, "width": 1714, "height": 1026, "smoothMove": 1, "puzzleSepia": 1 }, - "InfoBox": { "x": 1725, "y": 869 }, - "gem0": { "x": 6, "y": 988, "graphic": "agemLL.def" }, - "gem1": { "x": 1676, "y": 988, "graphic": "agemLR.def" }, - "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, - "gem3": { "x": 1676, "y": 6, "graphic": "agemUR.def" }, - "background": "AdvMap1920x1080.pcx", - "HeroList": { "size": 20, "x": 1729, "y": 197, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, - "TownList": { "size": 20, "x": 1867, "y": 197, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, - "Minimap": { "width": 144, "height": 144, "x": 1750, "y": 26 }, - "Overview": { "pics": 4, "size": 7, "graphic": "OvCast7.pcx" }, - "Statusbar": { "x": 8, "y": 1035, "graphic": "AdRollvr1920.pcx" }, - "ResDataBar": { "x": 0, "y": 1055, "graphic": "ZResBar1920.pcx", "offsetX": 67, "offsetY": 2, "resSpace": 192, "resDateSpace": 528 }, - "ButtonKingdomOv": { "x": 1799, "y": 197, "graphic": "IAM002L.DEF", "playerColoured": 1 }, - "ButtonUnderground": { "x": 1799, "y": 229, "graphic": "IAM010L.DEF", "playerColoured": 1, "additionalDefs": [ "IAM003L.DEF" ] }, - "ButtonQuestLog": { "x": 1799, "y": 261, "graphic": "IAM004L.DEF", "playerColoured": 1 }, - "ButtonSleepWake": { "x": 1799, "y": 293, "graphic": "IAM005L.DEF", "playerColoured": 1, "additionalDefs":["IAM011L.DEF"] }, - "ButtonMoveHero": { "x": 1799, "y": 326, "graphic": "IAM006L.DEF", "playerColoured": 1 }, - "ButtonSpellbook": { "x": 1799, "y": 359, "graphic": "IAM007L.DEF", "playerColoured": 1 }, - "ButtonAdvOptions": { "x": 1799, "y": 392, "graphic": "IAM008L.DEF", "playerColoured": 1 }, - "ButtonSysOptions": { "x": 1799, "y": 425, "graphic": "IAM009L.DEF", "playerColoured": 1 }, - "ButtonNextHero": { "x": 1799, "y": 458, "graphic": "IAM000.DEF", "playerColoured": 1 }, - "ButtonEndTurn": { "x": 1799, "y": 491, "graphic": "IAM001.DEF", "playerColoured": 1 } - } - ] + [ + { + "resolution": { "x": 800, "y": 600 }, + "InGameConsole": { "maxInputPerLine": 60, "maxOutputPerLine": 39 }, + "AdvMap": { "x": 7, "y": 7, "width": 594, "height": 546, "smoothMove": 1, "puzzleSepia": 1 }, + "InfoBox": { "x": 605, "y": 389 }, + "gem0": { "x": 6, "y": 508, "graphic": "agemLL.def" }, + "gem1": { "x": 556, "y": 508, "graphic": "agemLR.def" }, + "gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" }, + "gem3": { "x": 556, "y": 6, "graphic": "agemUR.def" }, + "background": "AdvMap.bmp", + "HeroList": { "size": 5, "x": 609, "y": 196, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" }, + "TownList": { "size": 5, "x": 747, "y": 196, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" }, + "Minimap": { "width": 144, "height": 144, "x": 630, "y": 26 }, + "Overview": { "pics": 4, "size": 4, "graphic": "OvCast.pcx" }, + "Statusbar": { "x": 7, "y": 556, "graphic": "AdRollvr.bmp" }, + "ResDataBar": { "x": 3, "y": 575, "graphic": "ARESBAR.bmp", "offsetX": 32, "offsetY": 2, "resSpace": 85, "resDateSpace": 85 }, + "ButtonKingdomOv": { "x": 679, "y": 196, "graphic": "IAM002.DEF", "playerColoured": 1 }, + "ButtonUnderground": { "x": 711, "y": 196, "graphic": "IAM010.DEF", "playerColoured": 1, "additionalDefs": [ "IAM003.DEF" ] }, + "ButtonQuestLog": { "x": 679, "y": 228, "graphic": "IAM004.DEF", "playerColoured": 1 }, + "ButtonSleepWake": { "x": 711, "y": 228, "graphic": "IAM005.DEF", "playerColoured": 1, "additionalDefs":["IAM011.DEF"] }, + "ButtonMoveHero": { "x": 679, "y": 260, "graphic": "IAM006.DEF", "playerColoured": 1 }, + "ButtonSpellbook": { "x": 711, "y": 260, "graphic": "IAM007.DEF", "playerColoured": 1 }, + "ButtonAdvOptions": { "x": 679, "y": 292, "graphic": "IAM008.DEF", "playerColoured": 1 }, + "ButtonSysOptions": { "x": 711, "y": 292, "graphic": "IAM009.DEF", "playerColoured": 1 }, + "ButtonNextHero": { "x": 679, "y": 324, "graphic": "IAM000.DEF", "playerColoured": 1 }, + "ButtonEndTurn": { "x": 679, "y": 356, "graphic": "IAM001.DEF", "playerColoured": 1 } + } + ] } diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 40e327f9d..ae753d165 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -17,12 +17,8 @@ "type" : "object", "default": {}, "additionalProperties" : false, - "required" : [ "classicCreatureWindow", "playerName", "showfps", "music", "sound", "encoding" ], + "required" : [ "playerName", "showfps", "music", "sound", "encoding" ], "properties" : { - "classicCreatureWindow" : { - "type" : "boolean", - "default" : false - }, "playerName" : { "type":"string", "default" : "Player" diff --git a/config/translate.json b/config/translate.json index a08e71333..be1135611 100644 --- a/config/translate.json +++ b/config/translate.json @@ -29,11 +29,6 @@ "label" : "Fullscreen", "help" : "{Fullscreen}\n\n If selected, VCMI will run in fullscreen mode, othervice VCMI will run in window", }, - "creatureWindowButton" : - { - "label" : "Classic creature window", - "help" : "{Classic creature window}\n\n Enable original Heroes 3 creature window instead of new window from VCMI" - }, "resolutionButton" : { "label" : "Resolution", @@ -54,5 +49,30 @@ "anyOf" : "Any of the following:", "allOf" : "All of the following:", "noneOf" : "None of the following:" + }, + "heroWindow" : { + "openCommander" : + { + "label" : "Open commander window", + "help" : "Displays information about commander of this hero" + } + }, + "creatureWindow" : + { + "showBonuses" : + { + "label" : "Switch to bonuses view", + "help" : "Displays all active bonuses of the commander" + }, + "showSkills" : + { + "label" : "Switch to skills view", + "help" : "Displays all learned skills of the commander" + }, + "returnArtifact" : + { + "label" : "Give back artifact", + "help" : "Use this button to return stack artifact back into hero backpack" + } } } diff --git a/editor/Editor.cpp b/editor/Editor.cpp index 822ac1d89..158662630 100644 --- a/editor/Editor.cpp +++ b/editor/Editor.cpp @@ -13,7 +13,7 @@ Editor::Editor(QWidget *parent) { // Setup default logging(enough for now) console = new CConsoleHandler; - CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() + "/VCMI_Editor_log.txt", console); + CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Editor_log.txt", console); logConfig.configureDefault(); preinitDLL(console); diff --git a/launcher/StdInc.h b/launcher/StdInc.h index 751c21f85..aa3a6b8fe 100644 --- a/launcher/StdInc.h +++ b/launcher/StdInc.h @@ -8,4 +8,13 @@ #include #include #include -#include \ No newline at end of file +#include + +inline QString pathToQString(const boost::filesystem::path & path) +{ +#ifdef VCMI_WINDOWS + return QString::fromStdWString(path.wstring()); +#else + return QString::fromStdString(path.string()); +#endif +} \ No newline at end of file diff --git a/launcher/launcherdirs.cpp b/launcher/launcherdirs.cpp index 6e28e273a..d7b3b453d 100644 --- a/launcher/launcherdirs.cpp +++ b/launcher/launcherdirs.cpp @@ -18,10 +18,10 @@ CLauncherDirs & CLauncherDirs::get() QString CLauncherDirs::downloadsPath() { - return QString::fromUtf8(VCMIDirs::get().userCachePath().c_str()) + "/downloads"; + return pathToQString(VCMIDirs::get().userCachePath() / "downloads"); } QString CLauncherDirs::modsPath() { - return QString::fromUtf8(VCMIDirs::get().userDataPath().c_str()) + "/Mods"; + return pathToQString(VCMIDirs::get().userDataPath() / "Mods"); } diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index f7cee2d17..3536e925a 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -13,15 +13,15 @@ void MainWindow::load() { console = new CConsoleHandler; - CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() + "/VCMI_Launcher_log.txt", console); + CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Launcher_log.txt", console); logConfig.configureDefault(); CResourceHandler::initialize(); CResourceHandler::load("config/filesystem.json"); for (auto & string : VCMIDirs::get().dataPaths()) - QDir::addSearchPath("icons", QString::fromUtf8(string.c_str()) + "/launcher/icons"); - QDir::addSearchPath("icons", QString::fromUtf8(VCMIDirs::get().userDataPath().c_str()) + "/launcher/icons"); + QDir::addSearchPath("icons", pathToQString(string / "launcher" / "icons")); + QDir::addSearchPath("icons", pathToQString(VCMIDirs::get().userDataPath() / "launcher" / "icons")); settings.init(); } @@ -46,7 +46,7 @@ MainWindow::~MainWindow() void MainWindow::on_startGameButon_clicked() { - startExecutable(QString::fromUtf8(VCMIDirs::get().clientPath().c_str())); + startExecutable(pathToQString(VCMIDirs::get().clientPath())); } void MainWindow::startExecutable(QString name) diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index 8fff2af44..40f00dcb4 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -39,7 +39,7 @@ CModManager::CModManager(CModList * modList): QString CModManager::settingsPath() { - return QString::fromUtf8(VCMIDirs::get().userConfigPath().c_str()) + "/modSettings.json"; + return pathToQString(VCMIDirs::get().userConfigPath() / "modSettings.json"); } void CModManager::loadModSettings() diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 9013d836e..9f19a70bf 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -46,9 +46,9 @@ void CSettingsView::loadSettings() for (auto entry : urls.Vector()) ui->plainTextEditRepos->appendPlainText(QString::fromUtf8(entry.String().c_str())); - ui->lineEditUserDataDir->setText(QString::fromUtf8(VCMIDirs::get().userDataPath().c_str())); - ui->lineEditGameDir->setText(QString::fromUtf8(M_DATA_DIR)); - ui->lineEditTempDir->setText(QString::fromUtf8(VCMIDirs::get().userCachePath().c_str())); + ui->lineEditUserDataDir->setText(pathToQString(VCMIDirs::get().userDataPath())); + ui->lineEditGameDir->setText(pathToQString(VCMIDirs::get().binaryPath())); + ui->lineEditTempDir->setText(pathToQString(VCMIDirs::get().userCachePath())); std::string encoding = settings["general"]["encoding"].String(); size_t encodingIndex = boost::range::find(knownEncodingsList, encoding) - knownEncodingsList; diff --git a/lib/BattleHex.cpp b/lib/BattleHex.cpp index 654197d44..659de94cb 100644 --- a/lib/BattleHex.cpp +++ b/lib/BattleHex.cpp @@ -44,18 +44,6 @@ BattleHex& BattleHex::moveInDir(EDir dir, bool hasToBeValid) return *this; } -void BattleHex::operator+=(EDir dir) -{ - moveInDir(dir); -} - -BattleHex BattleHex::operator+(EDir dir) const -{ - BattleHex ret(*this); - ret += dir; - return ret; -} - std::vector BattleHex::neighbouringTiles() const { std::vector ret; @@ -93,17 +81,18 @@ char BattleHex::getDistance(BattleHex hex1, BattleHex hex2) { int y1 = hex1.getY(), y2 = hex2.getY(); - - int x1 = hex1.getX() + y1 / 2.0, - x2 = hex2.getX() + y2 / 2.0; + + // FIXME: Omit floating point arithmetics + int x1 = (int)(hex1.getX() + y1 * 0.5), + x2 = (int)(hex2.getX() + y2 * 0.5); int xDst = x2 - x1, yDst = y2 - y1; if ((xDst >= 0 && yDst >= 0) || (xDst < 0 && yDst < 0)) return std::max(std::abs(xDst), std::abs(yDst)); - else - return std::abs(xDst) + std::abs(yDst); + + return std::abs(xDst) + std::abs(yDst); } void BattleHex::checkAndPush(BattleHex tile, std::vector & ret) diff --git a/lib/BattleHex.h b/lib/BattleHex.h index 48c53c007..26042e7b4 100644 --- a/lib/BattleHex.h +++ b/lib/BattleHex.h @@ -17,22 +17,16 @@ struct DLL_LINKAGE BattleHex { static const si16 INVALID = -1; - enum EDir{RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT, LEFT, TOP_LEFT, TOP_RIGHT}; + enum EDir { RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT, LEFT, TOP_LEFT, TOP_RIGHT }; si16 hex; BattleHex() : hex(INVALID) {} BattleHex(si16 _hex) : hex(_hex) {} - operator si16() const - { - return hex; - } + operator si16() const { return hex; } - bool isValid() const - { - return hex >= 0 && hex < GameConstants::BFIELD_SIZE; - } + bool isValid() const { return hex >= 0 && hex < GameConstants::BFIELD_SIZE; } template BattleHex(inttype x, inttype y) @@ -61,9 +55,7 @@ struct DLL_LINKAGE BattleHex void setXY(si16 x, si16 y, bool hasToBeValid = true) { if(hasToBeValid) - { - assert(x >= 0 && x < GameConstants::BFIELD_WIDTH && y >= 0 && y < GameConstants::BFIELD_HEIGHT); - } + assert(x >= 0 && x < GameConstants::BFIELD_WIDTH && y >= 0 && y < GameConstants::BFIELD_HEIGHT); hex = x + y * GameConstants::BFIELD_WIDTH; } @@ -73,28 +65,23 @@ struct DLL_LINKAGE BattleHex setXY(xy.first, xy.second); } - si16 getY() const - { - return hex / GameConstants::BFIELD_WIDTH; - } + si16 getY() const { return hex / GameConstants::BFIELD_WIDTH; } + si16 getX() const { return hex % GameConstants::BFIELD_WIDTH; } - si16 getX() const - { - int pos = hex - getY() * GameConstants::BFIELD_WIDTH; - return pos; - } - - std::pair getXY() const - { - return std::make_pair(getX(), getY()); - } + std::pair getXY() const { return std::make_pair(getX(), getY()); } //moving to direction BattleHex& moveInDir(EDir dir, bool hasToBeValid = true); - void operator+=(EDir dir); //sugar for above + BattleHex& operator+=(EDir dir) { return moveInDir(dir); } //sugar for above //generates new BattleHex moved by given dir - BattleHex operator+(EDir dir) const; + BattleHex movedInDir(EDir dir, bool hasToBeValid = true) const + { + BattleHex result(*this); + result.moveInDir(dir, hasToBeValid); + return result; + } + BattleHex operator+(EDir dir) const { return movedInDir(dir); } std::vector neighbouringTiles() const; diff --git a/lib/CConsoleHandler.cpp b/lib/CConsoleHandler.cpp index 6648386ba..29407207a 100644 --- a/lib/CConsoleHandler.cpp +++ b/lib/CConsoleHandler.cpp @@ -17,7 +17,7 @@ boost::mutex CConsoleHandler::smx; DLL_LINKAGE CConsoleHandler * console = nullptr; -#ifndef _WIN32 +#ifndef VCMI_WINDOWS typedef std::string TColor; #define CONSOLE_GREEN "\x1b[1;32m" #define CONSOLE_RED "\x1b[1;31m" @@ -27,7 +27,6 @@ DLL_LINKAGE CConsoleHandler * console = nullptr; #define CONSOLE_GRAY "\x1b[1;30m" #define CONSOLE_TEAL "\x1b[1;36m" #else - #define WIN32_LEAN_AND_MEAN //excludes rarely used stuff from windows headers - delete this line if something is missing #include #ifndef __MINGW32__ #include @@ -36,6 +35,7 @@ DLL_LINKAGE CConsoleHandler * console = nullptr; typedef WORD TColor; HANDLE handleIn; HANDLE handleOut; + HANDLE handleErr; #define CONSOLE_GREEN FOREGROUND_GREEN | FOREGROUND_INTENSITY #define CONSOLE_RED FOREGROUND_RED | FOREGROUND_INTENSITY #define CONSOLE_MAGENTA FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY @@ -43,11 +43,13 @@ DLL_LINKAGE CConsoleHandler * console = nullptr; #define CONSOLE_WHITE FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY #define CONSOLE_GRAY FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE #define CONSOLE_TEAL FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY + + static TColor defErrColor; #endif static TColor defColor; -#ifdef _WIN32 +#ifdef VCMI_WINDOWS void printWinError() { @@ -178,8 +180,11 @@ void CConsoleHandler::setColor(EConsoleTextColor::EConsoleTextColor color) colorCode = defColor; break; } -#ifdef _WIN32 +#ifdef VCMI_WINDOWS SetConsoleTextAttribute(handleOut, colorCode); + if (color == EConsoleTextColor::DEFAULT) + colorCode = defErrColor; + SetConsoleTextAttribute(handleErr, colorCode); #else std::cout << colorCode; #endif @@ -194,7 +199,7 @@ int CConsoleHandler::run() while ( std::cin.good() ) { -#ifndef _WIN32 +#ifndef VCMI_WINDOWS //check if we have some unreaded symbols if (std::cin.rdbuf()->in_avail()) { @@ -216,12 +221,17 @@ int CConsoleHandler::run() } CConsoleHandler::CConsoleHandler() : thread(nullptr) { -#ifdef _WIN32 +#ifdef VCMI_WINDOWS handleIn = GetStdHandle(STD_INPUT_HANDLE); handleOut = GetStdHandle(STD_OUTPUT_HANDLE); + handleErr = GetStdHandle(STD_ERROR_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(handleOut,&csbi); defColor = csbi.wAttributes; + + GetConsoleScreenBufferInfo(handleErr, &csbi); + defErrColor = csbi.wAttributes; #ifndef _DEBUG SetUnhandledExceptionFilter(onUnhandledException); #endif @@ -241,7 +251,7 @@ void CConsoleHandler::end() { if (thread) { -#ifndef _WIN32 +#ifndef VCMI_WINDOWS thread->interrupt(); #else TerminateThread(thread->native_handle(),0); diff --git a/lib/CConsoleHandler.h b/lib/CConsoleHandler.h index 83a5d892b..cbf342266 100644 --- a/lib/CConsoleHandler.h +++ b/lib/CConsoleHandler.h @@ -38,7 +38,7 @@ public: template void print(const T &data, bool addNewLine = false, EConsoleTextColor::EConsoleTextColor color = EConsoleTextColor::DEFAULT, bool printToStdErr = false) { TLockGuard _(smx); -#ifndef _WIN32 +#ifndef VCMI_WINDOWS // with love from ffmpeg - library is trying to print some warnings from separate thread // this results in broken console on Linux. Lock stdout to print all our data at once flockfile(stdout); @@ -70,7 +70,7 @@ public: } if(color != EConsoleTextColor::DEFAULT) setColor(EConsoleTextColor::DEFAULT); -#ifndef _WIN32 +#ifndef VCMI_WINDOWS funlockfile(stdout); #endif } diff --git a/lib/CGameInterface.cpp b/lib/CGameInterface.cpp index c438e0d55..5dcb444b2 100644 --- a/lib/CGameInterface.cpp +++ b/lib/CGameInterface.cpp @@ -4,8 +4,7 @@ #include "BattleState.h" #include "VCMIDirs.h" -#ifdef _WIN32 - #define WIN32_LEAN_AND_MEAN //excludes rarely used stuff from windows headers - delete this line if something is missing +#ifdef VCMI_WINDOWS #include //for .dll libs #else #include @@ -22,7 +21,7 @@ * */ -#ifdef __ANDROID__ +#ifdef VCMI_ANDROID // we can't use shared libraries on Android so here's a hack extern "C" DLL_EXPORT void VCAI_GetAiName(char* name); extern "C" DLL_EXPORT void VCAI_GetNewAI(shared_ptr &out); @@ -35,7 +34,7 @@ extern "C" DLL_EXPORT void BattleAI_GetNewBattleAI(shared_ptr -shared_ptr createAny(std::string dllname, std::string methodName) +shared_ptr createAny(const boost::filesystem::path& libpath, const std::string& methodName) { typedef void(*TGetAIFun)(shared_ptr&); typedef void(*TGetNameFun)(char*); @@ -45,56 +44,60 @@ shared_ptr createAny(std::string dllname, std::string methodName) TGetAIFun getAI = nullptr; TGetNameFun getName = nullptr; -#ifdef __ANDROID__ +#ifdef VCMI_ANDROID // this is awful but it seems using shared libraries on some devices is even worse - if (dllname.find("libVCAI.so") != std::string::npos) { + const std::string filename = libpath.filename().string(); + if (filename == "libVCAI.so") + { getName = (TGetNameFun)VCAI_GetAiName; getAI = (TGetAIFun)VCAI_GetNewAI; - } else if (dllname.find("libStupidAI.so") != std::string::npos) { + } + else if (filename == "libStupidAI.so") + { getName = (TGetNameFun)StupidAI_GetAiName; getAI = (TGetAIFun)StupidAI_GetNewBattleAI; - } else if (dllname.find("libBattleAI.so") != std::string::npos) { + } + else if (filename == "libBattleAI.so") + { getName = (TGetNameFun)BattleAI_GetAiName; getAI = (TGetAIFun)BattleAI_GetNewBattleAI; - } else { - throw std::runtime_error("Don't know what to do with " + dllname + " and method " + methodName); } -#else - -#ifdef _WIN32 - HINSTANCE dll = LoadLibraryA(dllname.c_str()); + else + throw std::runtime_error("Don't know what to do with " + libpath.string() + " and method " + methodName); +#else // !VCMI_ANDROID +#ifdef VCMI_WINDOWS + HMODULE dll = LoadLibraryW(libpath.c_str()); if (dll) { - getName = (TGetNameFun)GetProcAddress(dll,"GetAiName"); - getAI = (TGetAIFun)GetProcAddress(dll,methodName.c_str()); + getName = (TGetNameFun)GetProcAddress(dll, "GetAiName"); + getAI = (TGetAIFun)GetProcAddress(dll, methodName.c_str()); } -#else - void *dll = dlopen(dllname.c_str(), RTLD_LOCAL | RTLD_LAZY); +#else // !VCMI_WINDOWS + void *dll = dlopen(libpath.string().c_str(), RTLD_LOCAL | RTLD_LAZY); if (dll) { - getName = (TGetNameFun)dlsym(dll,"GetAiName"); - getAI = (TGetAIFun)dlsym(dll,methodName.c_str()); + getName = (TGetNameFun)dlsym(dll, "GetAiName"); + getAI = (TGetAIFun)dlsym(dll, methodName.c_str()); } else logGlobal->errorStream() << "Error: " << dlerror(); -#endif +#endif // VCMI_WINDOWS if (!dll) { - logGlobal->errorStream() << "Cannot open dynamic library ("<errorStream() << "Cannot open dynamic library ("<errorStream() << dllname << " does not export method " << methodName; -#ifdef _WIN32 + logGlobal->errorStream() << libpath << " does not export method " << methodName; +#ifdef VCMI_WINDOWS FreeLibrary(dll); #else dlclose(dll); #endif throw std::runtime_error("Cannot find method " + methodName); } - -#endif // __ANDROID__ +#endif // VCMI_ANDROID getName(temp); logGlobal->infoStream() << "Loaded " << temp; @@ -108,13 +111,13 @@ shared_ptr createAny(std::string dllname, std::string methodName) } template -shared_ptr createAnyAI(std::string dllname, std::string methodName) +shared_ptr createAnyAI(std::string dllname, const std::string& methodName) { - logGlobal->infoStream() << "Opening " << dllname; - std::string filename = VCMIDirs::get().libraryName(dllname); - - auto ret = createAny(VCMIDirs::get().libraryPath() + "/AI/" + filename, methodName); - ret->dllName = dllname; + logGlobal->infoStream() << "Opening " << dllname; + const boost::filesystem::path filePath = + VCMIDirs::get().libraryPath() / "AI" / VCMIDirs::get().libraryName(dllname); + auto ret = createAny(filePath, methodName); + ret->dllName = std::move(dllname); return ret; } diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index c31261a27..c236f0efc 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -459,7 +459,18 @@ CGeneralTextHandler::CGeneralTextHandler() { CLegacyConfigParser parser("DATA/ZCREXP.TXT"); parser.endLine();//header - do + for (size_t iter=0; iter<325; iter++) + { + parser.readString(); //ignore 1st column with description + zcrexp.push_back(parser.readString()); + parser.endLine(); + } + // line 325 - some weird formatting here + zcrexp.push_back(parser.readString()); + parser.readString(); + parser.endLine(); + + do // rest of file can be read normally { parser.readString(); //ignore 1st column with description zcrexp.push_back(parser.readString()); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d777b94be..c079e09c7 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -133,7 +133,7 @@ set(lib_HEADERS add_library(vcmi SHARED ${lib_SRCS} ${lib_HEADERS}) set_target_properties(vcmi PROPERTIES XCODE_ATTRIBUTE_LD_DYLIB_INSTALL_NAME "@rpath/libvcmi.dylib") set_target_properties(vcmi PROPERTIES COMPILE_DEFINITIONS "VCMI_DLL=1") -target_link_libraries(vcmi minizip ${Boost_LIBRARIES} ${SDL_LIBRARY} ${ZLIB_LIBRARIES} ${SYSTEM_LIBS}) +target_link_libraries(vcmi ${MINIZIP_LIBRARIES} ${Boost_LIBRARIES} ${SDL_LIBRARY} ${ZLIB_LIBRARIES} ${SYSTEM_LIBS}) if(WIN32) set_target_properties(vcmi PROPERTIES OUTPUT_NAME VCMI_lib) diff --git a/lib/CSoundBase.h b/lib/CSoundBase.h index c67ff1010..7ad38d2b1 100644 --- a/lib/CSoundBase.h +++ b/lib/CSoundBase.h @@ -3,6 +3,9 @@ // Use some magic to keep the list of files and their code name in sync. #define VCMI_SOUND_LIST \ +/* Sounds for map actions */ \ +VCMI_SOUND_NAME(KillFade) VCMI_SOUND_FILE(KILLFADE.wav) /* hero or monster disappears */ \ +/* Other sounds (TODO: separate out the sounds for units, spells and the rest */ \ VCMI_SOUND_NAME(AAGLAttack) VCMI_SOUND_FILE(AAGLATTK.wav) \ VCMI_SOUND_NAME(AAGLDefend) VCMI_SOUND_FILE(AAGLDFND.wav) \ VCMI_SOUND_NAME(AAGLKill) VCMI_SOUND_FILE(AAGLKILL.wav) \ @@ -515,7 +518,6 @@ VCMI_SOUND_NAME(ITRGKill) VCMI_SOUND_FILE(ITRGKILL.wav) \ VCMI_SOUND_NAME(ITRGMove) VCMI_SOUND_FILE(ITRGMOVE.wav) \ VCMI_SOUND_NAME(ITRGWNCE) VCMI_SOUND_FILE(ITRGWNCE.wav) \ VCMI_SOUND_NAME(KEEPShot) VCMI_SOUND_FILE(KEEPSHOT.wav) \ -VCMI_SOUND_NAME(KillFADE) VCMI_SOUND_FILE(KILLFADE.wav) \ VCMI_SOUND_NAME(LANDKill) VCMI_SOUND_FILE(LANDKILL.wav) \ VCMI_SOUND_NAME(LANDMINE) VCMI_SOUND_FILE(LANDMINE.wav) \ VCMI_SOUND_NAME(LCRSAttack) VCMI_SOUND_FILE(LCRSATTK.wav) \ @@ -1033,4 +1035,4 @@ public: }; #undef VCMI_SOUND_NAME #undef VCMI_SOUND_FILE -}; \ No newline at end of file +}; diff --git a/lib/CThreadHelper.cpp b/lib/CThreadHelper.cpp index 8f237b6f8..cbbeeb292 100644 --- a/lib/CThreadHelper.cpp +++ b/lib/CThreadHelper.cpp @@ -1,9 +1,9 @@ #include "StdInc.h" #include "CThreadHelper.h" -#ifdef _WIN32 +#ifdef VCMI_WINDOWS #include -#elif !defined(__APPLE__) +#elif !defined(VCMI_APPLE) #include #endif /* @@ -49,7 +49,7 @@ void CThreadHelper::processTasks() // NOTE: on *nix string will be trimmed to 16 symbols void setThreadName(const std::string &name) { -#ifdef _WIN32 +#ifdef VCMI_WINDOWS #ifndef __GNUC__ //follows http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx const DWORD MS_VC_EXCEPTION=0x406D1388; diff --git a/lib/CondSh.h b/lib/CondSh.h index fe9cac8a2..1ec163571 100644 --- a/lib/CondSh.h +++ b/lib/CondSh.h @@ -17,50 +17,48 @@ template struct CondSh boost::condition_variable cond; boost::mutex mx; - CondSh() - {} - - CondSh(T t) - { - data = t; - } + CondSh() {} + CondSh(T t) : data(t) {} + // set data void set(T t) { boost::unique_lock lock(mx); - data=t; + data = t; } - void setn(T t) //set data and notify + // set data and notify + void setn(T t) { - { - boost::unique_lock lock(mx); - data=t; - } + set(t); cond.notify_all(); }; - T get() //get stored value + // get stored value + T get() { boost::unique_lock lock(mx); return data; } - void waitWhileTrue() //waits until data is set to false + // waits until data is set to false + void waitWhileTrue() { boost::unique_lock un(mx); while(data) cond.wait(un); } - void waitWhile(const T &t) //waits while data is set to arg + // waits while data is set to arg + void waitWhile(const T & t) { boost::unique_lock un(mx); while(data == t) cond.wait(un); } - void waitUntil(const T &t) //waits until data is set to arg + // waits until data is set to arg + void waitUntil(const T & t) { boost::unique_lock un(mx); while(data != t) diff --git a/lib/Connection.cpp b/lib/Connection.cpp index 45f423861..949e3e689 100644 --- a/lib/Connection.cpp +++ b/lib/Connection.cpp @@ -347,7 +347,7 @@ void CSaveFile::putMagicBytes( const std::string &text ) write(text.c_str(), text.length()); } -CLoadFile::CLoadFile(const std::string &fname, int minimalVersion /*= version*/) +CLoadFile::CLoadFile(const boost::filesystem::path & fname, int minimalVersion /*= version*/) { registerTypes(*this); openNextFile(fname, minimalVersion); @@ -363,33 +363,33 @@ int CLoadFile::read(void * data, unsigned size) return size; } -void CLoadFile::openNextFile(const std::string &fname, int minimalVersion) +void CLoadFile::openNextFile(const boost::filesystem::path & fname, int minimalVersion) { assert(!reverseEndianess); assert(minimalVersion <= version); try { - fName = fname; - sfile = make_unique(fname, std::ios::binary); + fName = fname.string(); + sfile = make_unique(fname, std::ios::binary); sfile->exceptions(std::ifstream::failbit | std::ifstream::badbit); //we throw a lot anyway if(!(*sfile)) - THROW_FORMAT("Error: cannot open to read %s!", fname); + THROW_FORMAT("Error: cannot open to read %s!", fName); //we can read char buffer[4]; sfile->read(buffer, 4); if(std::memcmp(buffer,"VCMI",4)) - THROW_FORMAT("Error: not a VCMI file(%s)!", fname); + THROW_FORMAT("Error: not a VCMI file(%s)!", fName); *this >> fileVersion; if(fileVersion < minimalVersion) - THROW_FORMAT("Error: too old file format (%s)!", fname); + THROW_FORMAT("Error: too old file format (%s)!", fName); if(fileVersion > version) { - logGlobal->warnStream() << boost::format("Warning format version mismatch: found %d when current is %d! (file %s)\n") % fileVersion % version % fname; + logGlobal->warnStream() << boost::format("Warning format version mismatch: found %d when current is %d! (file %s)\n") % fileVersion % version % fName; auto versionptr = (char*)&fileVersion; std::reverse(versionptr, versionptr + 4); @@ -401,7 +401,7 @@ void CLoadFile::openNextFile(const std::string &fname, int minimalVersion) reverseEndianess = true; } else - THROW_FORMAT("Error: too new file format (%s)!", fname); + THROW_FORMAT("Error: too new file format (%s)!", fName); } } catch(...) diff --git a/lib/Connection.h b/lib/Connection.h index 2b976d26b..ddcebc062 100644 --- a/lib/Connection.h +++ b/lib/Connection.h @@ -1530,17 +1530,17 @@ class DLL_LINKAGE CLoadFile public: std::string fName; - unique_ptr sfile; + unique_ptr sfile; - CLoadFile(const std::string &fname, int minimalVersion = version); //throws! + CLoadFile(const boost::filesystem::path & fname, int minimalVersion = version); //throws! ~CLoadFile(); int read(void * data, unsigned size); //throws! - void openNextFile(const std::string &fname, int minimalVersion); //throws! + void openNextFile(const boost::filesystem::path & fname, int minimalVersion); //throws! void clear(); void reportState(CLogger * out); - void checkMagicBytes(const std::string &text); + void checkMagicBytes(const std::string & text); }; class DLL_LINKAGE CLoadIntegrityValidator : public CISer diff --git a/lib/VCMIDirs.cpp b/lib/VCMIDirs.cpp index 35c6ad47c..2ae9df1fc 100644 --- a/lib/VCMIDirs.cpp +++ b/lib/VCMIDirs.cpp @@ -1,6 +1,3 @@ -#include "StdInc.h" -#include "VCMIDirs.h" - /* * VCMIDirs.cpp, part of VCMI engine * @@ -11,216 +8,504 @@ * */ -static VCMIDirs VCMIDirsGlobal; +#include "StdInc.h" +#include "VCMIDirs.h" -VCMIDirs::VCMIDirs() +namespace bfs = boost::filesystem; + +bfs::path IVCMIDirs::userSavePath() const { return userDataPath() / "Saves"; } + +void IVCMIDirs::init() { - // initialize local directory and create folders to which VCMI needs write access - boost::filesystem::create_directory(userDataPath()); - boost::filesystem::create_directory(userCachePath()); - boost::filesystem::create_directory(userConfigPath()); - boost::filesystem::create_directory(userSavePath()); + // TODO: Log errors + bfs::create_directory(userDataPath()); + bfs::create_directory(userCachePath()); + bfs::create_directory(userConfigPath()); + bfs::create_directory(userSavePath()); } -VCMIDirs & VCMIDirs::get() +#ifdef VCMI_WINDOWS + +#include +#include +#include + +// Generates script file named _temp.bat in 'to' directory and runs it +// Script will: +// - Wait util 'exeName' ends. +// - Copy all files from 'from' to 'to' +// - Ask user to replace files existed in 'to'. +// - Run 'exeName' +// - Delete itself. +bool StartBatchCopyDataProgram( + const bfs::path& from, const bfs::path& to, const bfs::path& exeName, + const bfs::path& currentPath = bfs::current_path()) { - return VCMIDirsGlobal; + static const char base[] = + "@echo off" "\n" + "echo Preparing to move VCMI data system." "\n" + + ":CLIENT_RUNNING_LOOP" "\n" + "TASKLIST | FIND /I %1% > nul" "\n" + "IF ERRORLEVEL 1 (" "\n" + "GOTO CLIENT_NOT_RUNNING" "\n" + ") ELSE (" "\n" + "echo %1% is still running..." "\n" + "echo Waiting until process ends..." "\n" + "ping 1.1.1.1 -n 1 -w 3000 > nul" "\n" // Sleep ~3 seconds. I love Windows :) + "goto :CLIENT_RUNNING_LOOP" "\n" + ")" "\n" + + ":CLIENT_NOT_RUNNING" "\n" + "echo %1% turned off..." "\n" + "echo Attempt to move datas." "\n" + "echo From: %2%" "\n" + "echo To: %4%" "\n" + "echo Please resolve any conflicts..." "\n" + "move /-Y %3% %4%" "\n" // Move all files from %3% to %4%. + // /-Y ask what to do when file exists in %4% + ":REMOVE_OLD_DIR" "\n" + "rd %2% || rem" "\n" // Remove empty directory. Sets error flag if fail. + "IF ERRORLEVEL 145 (" "\n" // Directory not empty + "echo Directory %2% is not empty." "\n" + "echo Please move rest of files manually now." "\n" + "pause" "\n" // Press any key to continue... + "goto REMOVE_OLD_DIR" "\n" + ")" "\n" + "echo Game data updated succefully." "\n" + "echo Please update your shortcuts." "\n" + "echo Press any key to start a game . . ." "\n" + "pause > nul" "\n" + "%5%" "\n" + "del \"%%~f0\"&exit" "\n" // Script deletes itself + ; + + const auto startGameString = + bfs::equivalent(currentPath, from) ? + (boost::format("start \"\" %1%") % (to / exeName)) : // Start game in new path. + (boost::format("start \"\" /D %1% %2%") % currentPath % (to / exeName)); // Start game in 'currentPath" + + const bfs::path bathFilename = to / "_temp.bat"; + bfs::ofstream bathFile(bathFilename, bfs::ofstream::trunc | bfs::ofstream::out); + if (!bathFile.is_open()) + return false; + bathFile << (boost::format(base) % exeName % from % (from / "*.*") % to % startGameString.str()).str(); + bathFile.close(); + + std::system(("start \"Updating VCMI datas\" /D \"" + to.string() + "\" \"" + bathFilename.string() + '\"').c_str()); + // start won't block std::system + // /D start bat in other directory insteand of current directory. + + return true; } -//FIXME: find way to at least decrease size of this ifdef (along with cleanup in CMake) -#if defined(_WIN32) - -std::string VCMIDirs::userCachePath() const +class VCMIDirsWIN32 : public IVCMIDirs { - return userDataPath(); + public: + boost::filesystem::path userDataPath() const override; + boost::filesystem::path userCachePath() const override; + boost::filesystem::path userConfigPath() const override; + + std::vector dataPaths() const override; + + boost::filesystem::path clientPath() const override; + boost::filesystem::path serverPath() const override; + + boost::filesystem::path libraryPath() const override; + boost::filesystem::path binaryPath() const override; + + std::string libraryName(const std::string& basename) const override; + + std::string genHelpString() const override; + + void init() override; + protected: + boost::filesystem::path oldUserDataPath() const; + boost::filesystem::path oldUserSavePath() const; +}; + +void VCMIDirsWIN32::init() +{ + // Call base (init dirs) + IVCMIDirs::init(); + + // Moves one directory (from) contents to another directory (to) + // Shows user the "moving file dialog" and ask to resolve conflits. + // If necessary updates current directory. + auto moveDirIfExists = [](const bfs::path& from, const bfs::path& to) -> bool + { + if (!bfs::is_directory(from)) + return true; // Nothing to do here. Flies away. + + if (bfs::is_empty(from)) + { + if (bfs::current_path() == from) + bfs::current_path(to); + + bfs::remove(from); + return true; // Nothing to do here. Flies away. + } + + if (!bfs::is_directory(to)) + { + // IVCMIDirs::init() should create all destination directories. + // TODO: Log fact, that we shouldn't be here. + bfs::create_directory(to); + } + + // Why the hell path strings should be end with double null :/ + auto makeDoubleNulled = [](const bfs::path& path) -> std::unique_ptr + { + const std::wstring& pathStr = path.native(); + std::unique_ptr result(new wchar_t[pathStr.length() + 2]); + + size_t i = 0; + for (const wchar_t ch : pathStr) + result[i++] = ch; + result[i++] = L'\0'; + result[i++] = L'\0'; + + return result; + }; + + auto fromDNulled = makeDoubleNulled(from / L"*.*"); + auto toDNulled = makeDoubleNulled(to); + + SHFILEOPSTRUCTW fileOp; + fileOp.hwnd = GetConsoleWindow(); + fileOp.wFunc = FO_MOVE; + fileOp.pFrom = fromDNulled.get(); + fileOp.pTo = toDNulled.get(); + fileOp.fFlags = 0; + fileOp.hNameMappings = nullptr; + fileOp.lpszProgressTitle = nullptr; + + const int errorCode = SHFileOperationW(&fileOp); + if (errorCode != 0) // TODO: Log error. User should try to move files. + return false; + else if (fileOp.fAnyOperationsAborted) // TODO: Log warn. User aborted operation. User should move files. + return false; + else if (!bfs::is_empty(from)) // TODO: Log warn. Some files not moved. User should try to move files. + return false; + + if (bfs::current_path() == from) + bfs::current_path(to); + + // TODO: Log fact that we moved files succefully. + bfs::remove(from); + return true; + }; + + // Retrieves the fully qualified path for the file that contains the specified module. + // The module must have been loaded by the current process. + // If this parameter is nullptr, retrieves the path of the executable file of the current process. + auto getModulePath = [](HMODULE hModule) -> bfs::path + { + wchar_t exePathW[MAX_PATH]; + DWORD nSize = GetModuleFileNameW(hModule, exePathW, MAX_PATH); + DWORD error = GetLastError(); + // WARN: Windows XP don't set ERROR_INSUFFICIENT_BUFFER error. + if (nSize != 0 && error != ERROR_INSUFFICIENT_BUFFER) + return bfs::path(std::wstring(exePathW, nSize)); + // TODO: Error handling + return bfs::path(); + }; + + // Moves one directory contents to another directory + // Shows user the "moving file dialog" and ask to resolve conflicts. + // It takes into account that 'from' path can contain current executable. + // If necessary closes program and starts update script. + auto advancedMoveDirIfExists = [getModulePath, moveDirIfExists](const bfs::path& from, const bfs::path& to) -> bool + { + const bfs::path executablePath = getModulePath(nullptr); + + // VCMI cann't determine executable path. + // Use standard way to move directory and exit function. + if (executablePath.empty()) + return moveDirIfExists(from, to); + + const bfs::path executableName = executablePath.filename(); + + // Current executabl isn't in 'from' path. + // Use standard way to move directory and exit function. + if (!bfs::equivalent(executablePath, from / executableName)) + return moveDirIfExists(from, to); + + // Try standard way to move directory. + // I don't know how other systems, but Windows 8.1 allow to move running executable. + if (moveDirIfExists(from, to)) + return true; + + // Start copying script and exit program. + if (StartBatchCopyDataProgram(from, to, executableName)) + exit(ERROR_SUCCESS); + + // Everything failed :C + return false; + }; + + moveDirIfExists(oldUserSavePath(), userSavePath()); + advancedMoveDirIfExists(oldUserDataPath(), userDataPath()); } -std::string VCMIDirs::userConfigPath() const +bfs::path VCMIDirsWIN32::userDataPath() const { - return userDataPath() + "/config"; -} + wchar_t profileDir[MAX_PATH]; -std::string VCMIDirs::userSavePath() const -{ - return userDataPath() + "/Games"; -} - -std::string VCMIDirs::userDataPath() const -{ - const std::string homeDir = std::getenv("userprofile"); - return homeDir + "\\vcmi"; - //return dataPaths()[0]; -} - -std::string VCMIDirs::libraryPath() const -{ + if (SHGetSpecialFolderPathW(nullptr, profileDir, CSIDL_MYDOCUMENTS, FALSE) != FALSE) + return bfs::path(profileDir) / "My Games\\vcmi"; + return "."; } -std::string VCMIDirs::clientPath() const +bfs::path VCMIDirsWIN32::oldUserDataPath() const { - return libraryPath() + "\\" + "VCMI_client.exe"; + wchar_t profileDir[MAX_PATH]; + + if (SHGetSpecialFolderPathW(nullptr, profileDir, CSIDL_PROFILE, FALSE) == FALSE) // WinAPI way failed + { +#if defined(_MSC_VER) && _MSC_VER >= 1700 + wchar_t* buffer; + size_t bufferSize; + errno_t result = _wdupenv_s(&buffer, &bufferSize, L"userprofile"); + if (result == 0) + { + bfs::path result(std::wstring(buffer, bufferSize)); + free(buffer); + return result; + } +#else + const char* profileDirA; + if (profileDirA = std::getenv("userprofile")) // STL way succeed + return bfs::path(profileDirA) / "vcmi"; +#endif + else + return "."; // Every thing failed, return current directory. + } + else + return bfs::path(profileDir) / "vcmi"; + + //return dataPaths()[0] ???; +} +bfs::path VCMIDirsWIN32::oldUserSavePath() const { return userDataPath() / "Games"; } + +bfs::path VCMIDirsWIN32::userCachePath() const { return userDataPath(); } +bfs::path VCMIDirsWIN32::userConfigPath() const { return userDataPath() / "config"; } + +std::vector VCMIDirsWIN32::dataPaths() const +{ + return std::vector(1, bfs::path(".")); } -std::string VCMIDirs::serverPath() const +bfs::path VCMIDirsWIN32::clientPath() const { return binaryPath() / "VCMI_client.exe"; } +bfs::path VCMIDirsWIN32::serverPath() const { return binaryPath() / "VCMI_server.exe"; } + +bfs::path VCMIDirsWIN32::libraryPath() const { return "."; } +bfs::path VCMIDirsWIN32::binaryPath() const { return "."; } + +std::string VCMIDirsWIN32::genHelpString() const { - return libraryPath() + "\\" + "VCMI_server.exe"; + + std::vector tempVec; + for (const bfs::path& path : dataPaths()) + tempVec.push_back(path.string()); + std::string gdStringA = boost::algorithm::join(tempVec, ";"); + + + return + " game data: " + gdStringA + "\n" + " libraries: " + libraryPath().string() + "\n" + " server: " + serverPath().string() + "\n" + "\n" + " user data: " + userDataPath().string() + "\n" + " user cache: " + userCachePath().string() + "\n" + " user config: " + userConfigPath().string() + "\n" + " user saves: " + userSavePath().string() + "\n"; // Should end without new-line? } -std::vector VCMIDirs::dataPaths() const +std::string VCMIDirsWIN32::libraryName(const std::string& basename) const { return basename + ".dll"; } +#elif defined(VCMI_UNIX) +class IVCMIDirsUNIX : public IVCMIDirs { - return std::vector(1, "."); + public: + boost::filesystem::path clientPath() const override; + boost::filesystem::path serverPath() const override; + + std::string genHelpString() const override; +}; + +bfs::path IVCMIDirsUNIX::clientPath() const { return binaryPath() / "vcmiclient"; } +bfs::path IVCMIDirsUNIX::serverPath() const { return binaryPath() / "vcmiserver"; } + +std::string IVCMIDirsUNIX::genHelpString() const +{ + std::vector tempVec; + for (const bfs::path& path : dataPaths()) + tempVec.push_back(path.string()); + std::string gdStringA = boost::algorithm::join(tempVec, ":"); + + + return + " game data: " + gdStringA + "\n" + " libraries: " + libraryPath().string() + "\n" + " server: " + serverPath().string() + "\n" + "\n" + " user data: " + userDataPath().string() + "\n" + " user cache: " + userCachePath().string() + "\n" + " user config: " + userConfigPath().string() + "\n" + " user saves: " + userSavePath().string() + "\n"; // Should end without new-line? } -std::string VCMIDirs::libraryName(std::string basename) const +#ifdef VCMI_APPLE +class VCMIDirsOSX : public IVCMIDirsUNIX { - return basename + ".dll"; + public: + boost::filesystem::path userDataPath() const override; + boost::filesystem::path userCachePath() const override; + boost::filesystem::path userConfigPath() const override; + + std::vector dataPaths() const override; + + boost::filesystem::path libraryPath() const override; + boost::filesystem::path binaryPath() const override; + + std::string libraryName(const std::string& basename) const override; + + void init() override; +}; + +void VCMIDirsOSX::init() +{ + // Call base (init dirs) + IVCMIDirsUNIX::init(); + + auto moveDirIfExists = [](const bfs::path& from, const bfs::path& to) + { + if (!bfs::is_directory(from)) + return; // Nothing to do here. Flies away. + + if (bfs::is_empty(from)) + { + bfs::remove(from); + return; // Nothing to do here. Flies away. + } + + if (!bfs::is_directory(to)) + { + // IVCMIDirs::init() should create all destination directories. + // TODO: Log fact, that we shouldn't be here. + bfs::create_directory(to); + } + + for (bfs::directory_iterator file(from); file != bfs::directory_iterator(); ++file) + { + const boost::filesystem::path& srcFilePath = file->path(); + const boost::filesystem::path dstFilePath = to / srcFilePath.filename(); + + // TODO: Aplication should ask user what to do when file exists: + // replace/ignore/stop process/replace all/ignore all + if (!boost::filesystem::exists(dstFilePath)) + bfs::rename(srcFilePath, dstFilePath); + } + + if (!bfs::is_empty(from)); // TODO: Log warn. Some files not moved. User should try to move files. + else + bfs::remove(from); + }; + + moveDirIfExists(userDataPath() / "Games", userSavePath()); } -#elif defined(__APPLE__) - -std::string VCMIDirs::userCachePath() const -{ - return userDataPath(); -} - -std::string VCMIDirs::userConfigPath() const -{ - return userDataPath() + "/config"; -} - -std::string VCMIDirs::userSavePath() const -{ - return userDataPath() + "/Games"; -} - -std::string VCMIDirs::userDataPath() const +bfs::path VCMIDirsOSX::userDataPath() const { // This is Cocoa code that should be normally used to get path to Application Support folder but can't use it here for now... // NSArray* urls = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask]; // UserPath = path([urls[0] path] + "/vcmi").string(); // ...so here goes a bit of hardcode instead - std::string home_dir = "."; - if (getenv("HOME") != nullptr ) - home_dir = getenv("HOME"); - return boost::filesystem::path(home_dir + "/Library/Application Support/vcmi").string(); + const char* homeDir = getenv("HOME"); // Should be std::getenv? + if (homeDir == nullptr) + homeDir = "."; + return bfs::path(homeDir) / "Library" / "Application Support" / "vcmi"; +} +bfs::path VCMIDirsOSX::userCachePath() const { return userDataPath(); } +bfs::path VCMIDirsOSX::userConfigPath() const { return userDataPath() / "config"; } + +std::vector VCMIDirsOSX::dataPaths() const +{ + return std::vector(1, "../Data"); } -std::string VCMIDirs::libraryPath() const +bfs::path VCMIDirsOSX::libraryPath() const { return "."; } +bfs::path VCMIDirsOSX::binaryPath() const { return "."; } + +std::string libraryName(const std::string& basename) { return "lib" + basename + ".dylib"; } +#elif defined(VCMI_LINUX) +class VCMIDirsLinux : public IVCMIDirsUNIX { - return "."; +public: + boost::filesystem::path userDataPath() const override; + boost::filesystem::path userCachePath() const override; + boost::filesystem::path userConfigPath() const override; + + std::vector dataPaths() const override; + + boost::filesystem::path libraryPath() const override; + boost::filesystem::path binaryPath() const override; + + std::string libraryName(const std::string& basename) const override; +}; + +bfs::path VCMIDirsLinux::userDataPath() const +{ + // $XDG_DATA_HOME, default: $HOME/.local/share + const char* homeDir; + if ((homeDir = getenv("XDG_DATA_HOME"))) + return homeDir; + else if ((homeDir = getenv("HOME"))) + return bfs::path(homeDir) / ".local" / "share" / "vcmi"; + else + return "."; +} +bfs::path VCMIDirsLinux::userCachePath() const +{ + // $XDG_CACHE_HOME, default: $HOME/.cache + const char* tempResult; + if ((tempResult = getenv("XDG_CACHE_HOME"))) + return bfs::path(tempResult) / "vcmi"; + else if ((tempResult = getenv("HOME"))) + return bfs::path(tempResult) / ".cache" / "vcmi"; + else + return "."; +} +bfs::path VCMIDirsLinux::userConfigPath() const +{ + // $XDG_CONFIG_HOME, default: $HOME/.config + const char* tempResult; + if ((tempResult = getenv("XDG_CONFIG_HOME"))) + return bfs::path(tempResult) / "vcmi"; + else if ((tempResult = getenv("HOME"))) + return bfs::path(tempResult) / ".config" / "vcmi"; + else + return "."; } -std::string VCMIDirs::clientPath() const +std::vector VCMIDirsLinux::dataPaths() const { - return "./vcmiclient"; -} + // $XDG_DATA_DIRS, default: /usr/local/share/:/usr/share/ -std::string VCMIDirs::serverPath() const -{ - return "./vcmiserver"; -} - -std::vector VCMIDirs::dataPaths() const -{ - return std::vector(1, "../Data"); -} - -std::string VCMIDirs::libraryName(std::string basename) const -{ - return "lib" + basename + ".dylib"; -} - -#else - -std::string VCMIDirs::libraryName(std::string basename) const -{ - return "lib" + basename + ".so"; -} - -std::string VCMIDirs::libraryPath() const -{ - return M_LIB_DIR; -} - -std::string VCMIDirs::clientPath() const -{ - return std::string(M_BIN_DIR) + "/" + "vcmiclient"; -} - -std::string VCMIDirs::serverPath() const -{ - return std::string(M_BIN_DIR) + "/" + "vcmiserver"; -} - -// $XDG_DATA_HOME, default: $HOME/.local/share -std::string VCMIDirs::userDataPath() const -{ -#ifdef __ANDROID__ - // on Android HOME will be set to something like /sdcard/data/Android/is.xyz.vcmi/files/ - return std::string(getenv("HOME")); -#else - if (getenv("XDG_DATA_HOME") != nullptr ) - return std::string(getenv("XDG_DATA_HOME")) + "/vcmi"; - if (getenv("HOME") != nullptr ) - return std::string(getenv("HOME")) + "/.local/share" + "/vcmi"; - return "."; -#endif -} - -std::string VCMIDirs::userSavePath() const -{ - return userDataPath() + "/Saves"; -} - -// $XDG_CACHE_HOME, default: $HOME/.cache -std::string VCMIDirs::userCachePath() const -{ -#ifdef __ANDROID__ - return userDataPath() + "/cache"; -#else - if (getenv("XDG_CACHE_HOME") != nullptr ) - return std::string(getenv("XDG_CACHE_HOME")) + "/vcmi"; - if (getenv("HOME") != nullptr ) - return std::string(getenv("HOME")) + "/.cache" + "/vcmi"; - return "."; -#endif -} - -// $XDG_CONFIG_HOME, default: $HOME/.config -std::string VCMIDirs::userConfigPath() const -{ -#ifdef __ANDROID__ - return userDataPath() + "/config"; -#else - if (getenv("XDG_CONFIG_HOME") != nullptr ) - return std::string(getenv("XDG_CONFIG_HOME")) + "/vcmi"; - if (getenv("HOME") != nullptr ) - return std::string(getenv("HOME")) + "/.config" + "/vcmi"; - return "."; -#endif -} - -// $XDG_DATA_DIRS, default: /usr/local/share/:/usr/share/ -std::vector VCMIDirs::dataPaths() const -{ // construct list in reverse. // in specification first directory has highest priority // in vcmi fs last directory has highest priority + std::vector ret; - std::vector ret; -#ifdef __ANDROID__ - ret.push_back(userDataPath()); -#else - if (getenv("HOME") != nullptr ) // compatibility, should be removed after 0.96 - ret.push_back(std::string(getenv("HOME")) + "/.vcmi"); + const char* tempResult; ret.push_back(M_DATA_DIR); - if (getenv("XDG_DATA_DIRS") != nullptr) + if ((tempResult = getenv("XDG_DATA_DIRS")) != nullptr) { - std::string dataDirsEnv = getenv("XDG_DATA_DIRS"); + std::string dataDirsEnv = tempResult; std::vector dataDirs; boost::split(dataDirs, dataDirsEnv, boost::is_any_of(":")); for (auto & entry : boost::adaptors::reverse(dataDirs)) @@ -231,21 +516,61 @@ std::vector VCMIDirs::dataPaths() const ret.push_back("/usr/share/"); ret.push_back("/usr/local/share/"); } -#endif + return ret; } -#endif +bfs::path VCMIDirsLinux::libraryPath() const { return M_LIB_DIR; } +bfs::path VCMIDirsLinux::binaryPath() const { return M_BIN_DIR; } -std::string VCMIDirs::genHelpString() const +std::string VCMIDirsLinux::libraryName(const std::string& basename) const { return "lib" + basename + ".so"; } +#ifdef VCMI_ANDROID +class VCMIDirsAndroid : public VCMIDirsLinux { - return - " game data: " + boost::algorithm::join(dataPaths(), ":") + "\n" + - " libraries: " + libraryPath() + "\n" + - " server: " + serverPath() + "\n" + - "\n" + - " user data: " + userDataPath() + "\n" + - " user cache: " + userCachePath() + "\n" + - " user config: " + userConfigPath() + "\n" + - " user saves: " + userSavePath() + "\n"; +public: + boost::filesystem::path userDataPath() const override; + boost::filesystem::path userCachePath() const override; + boost::filesystem::path userConfigPath() const override; + + std::vector dataPaths() const override; +}; + +// on Android HOME will be set to something like /sdcard/data/Android/is.xyz.vcmi/files/ +bfs::path VCMIDirsAndroid::userDataPath() const { return getenv("HOME"); } +bfs::path VCMIDirsAndroid::userCachePath() const { return userDataPath() / "cache"; } +bfs::path VCMIDirsAndroid::userConfigPath() const { return userDataPath() / "config"; } + +std::vector VCMIDirsAndroid::dataPaths() const +{ + return std::vector(1, userDataPath()); } +#endif // VCMI_ANDROID +#endif // VCMI_APPLE, VCMI_LINUX +#endif // VCMI_WINDOWS, VCMI_UNIX + +// Getters for interfaces are separated for clarity. +namespace VCMIDirs +{ + const IVCMIDirs& get() + { + #ifdef VCMI_WINDOWS + static VCMIDirsWIN32 singleton; + #elif defined(VCMI_ANDROID) + static VCMIDirsAndroid singleton; + #elif defined(VCMI_LINUX) + static VCMIDirsLinux singleton; + #elif defined(VCMI_APPLE) + static VCMIDirsOSX singleton; + #endif + static bool initialized = false; + if (!initialized) + { + std::locale::global(boost::locale::generator().generate("en_US.UTF-8")); + boost::filesystem::path::imbue(std::locale()); + + singleton.init(); + initialized = true; + } + return singleton; + } +} \ No newline at end of file diff --git a/lib/VCMIDirs.h b/lib/VCMIDirs.h index c3f02c56c..fa1e43221 100644 --- a/lib/VCMIDirs.h +++ b/lib/VCMIDirs.h @@ -1,52 +1,58 @@ +/* +* VCMIDirs.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 -#include "GameConstants.h" - -/* - * VCMIDirs.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 - * - */ - -/// Where to find the various VCMI files. This is mostly useful for linux. -class DLL_LINKAGE VCMIDirs +class DLL_LINKAGE IVCMIDirs { -public: - VCMIDirs(); + public: + // Path to user-specific data directory + virtual boost::filesystem::path userDataPath() const = 0; - /// get singleton instance - static VCMIDirs & get(); + // Path to "cache" directory, can be used for any non-essential files + virtual boost::filesystem::path userCachePath() const = 0; - /// Path to user-specific data directory - std::string userDataPath() const; + // Path to writeable directory with user configs + virtual boost::filesystem::path userConfigPath() const = 0; - /// Path to "cache" directory, can be used for any non-essential files - std::string userCachePath() const; + // Path to saved games + virtual boost::filesystem::path userSavePath() const; - /// Path to writeable directory with user configs - std::string userConfigPath() const; + // Paths to global system-wide data directories. First items have higher priority + virtual std::vector dataPaths() const = 0; - /// Path to saved games - std::string userSavePath() const; + // Full path to client executable, including server name (e.g. /usr/bin/vcmiclient) + virtual boost::filesystem::path clientPath() const = 0; - /// Paths to global system-wide data directories. First items have higher priority - std::vector dataPaths() const; + // Full path to server executable, including server name (e.g. /usr/bin/vcmiserver) + virtual boost::filesystem::path serverPath() const = 0; - /// Full path to client executable, including server name (e.g. /usr/bin/vcmiclient) - std::string clientPath() const; + // Path where vcmi libraries can be found (in AI and Scripting subdirectories) + virtual boost::filesystem::path libraryPath() const = 0; - /// Full path to server executable, including server name (e.g. /usr/bin/vcmiserver) - std::string serverPath() const; + // Path where vcmi binaries can be found + virtual boost::filesystem::path binaryPath() const = 0; - /// Path where vcmi libraries can be found (in AI and Scripting subdirectories) - std::string libraryPath() const; + // Returns system-specific name for dynamic libraries ( StupidAI => "libStupidAI.so" or "StupidAI.dll") + virtual std::string libraryName(const std::string& basename) const = 0; + // virtual std::string libraryName(const char* basename) const = 0; ? + // virtual std::string libraryName(std::string&& basename) const = 0;? - /// Returns system-specific name for dynamic libraries ( StupidAI => "libStupidAI.so" or "StupidAI.dll") - std::string libraryName(std::string basename) const; - - std::string genHelpString() const; + virtual std::string genHelpString() const = 0; + + // Creates not existed, but required directories. + // Updates directories what change name/path between versions. + // Function called automatically. + virtual void init(); }; + +namespace VCMIDirs +{ + extern DLL_LINKAGE const IVCMIDirs& get(); +} \ No newline at end of file diff --git a/lib/filesystem/CArchiveLoader.cpp b/lib/filesystem/CArchiveLoader.cpp index e1ae5427b..fdebd315a 100644 --- a/lib/filesystem/CArchiveLoader.cpp +++ b/lib/filesystem/CArchiveLoader.cpp @@ -13,9 +13,9 @@ ArchiveEntry::ArchiveEntry() } -CArchiveLoader::CArchiveLoader(const std::string &mountPoint, const std::string & archive): - archive(archive), - mountPoint(mountPoint) +CArchiveLoader::CArchiveLoader(std::string _mountPoint, boost::filesystem::path _archive) : + archive(std::move(_archive)), + mountPoint(std::move(_mountPoint)) { // Open archive file(.snd, .vid, .lod) CFileInputStream fileStream(archive); @@ -25,28 +25,19 @@ CArchiveLoader::CArchiveLoader(const std::string &mountPoint, const std::string return; // Retrieve file extension of archive in uppercase - CFileInfo fileInfo(archive); - std::string ext = fileInfo.getExtension(); - boost::to_upper(ext); + const std::string ext = boost::to_upper_copy(archive.extension().string()); // Init the specific lod container format if(ext == ".LOD" || ext == ".PAC") - { initLODArchive(mountPoint, fileStream); - } else if(ext == ".VID") - { initVIDArchive(mountPoint, fileStream); - } else if(ext == ".SND") - { initSNDArchive(mountPoint, fileStream); - } else - { - throw std::runtime_error("LOD archive format unknown. Cannot deal with " + archive); - } - logGlobal->traceStream() << ext << "Archive loaded, " << entries.size() << " files found"; + throw std::runtime_error("LOD archive format unknown. Cannot deal with " + archive.string()); + + logGlobal->traceStream() << ext << "Archive \""<(filename), 16); @@ -90,7 +81,7 @@ void CArchiveLoader::initVIDArchive(const std::string &mountPoint, CFileInputStr std::set offsets; // Insert entries to list - for(ui32 i = 0; i < totalFiles; i++) + for(ui32 i = 0; i < totalFiles; ++i) { char filename[40]; reader.read(reinterpret_cast(filename), 40); @@ -122,7 +113,7 @@ void CArchiveLoader::initSNDArchive(const std::string &mountPoint, CFileInputStr ui32 totalFiles = reader.readUInt32(); // Insert entries to list - for(ui32 i = 0; i < totalFiles; i++) + for(ui32 i = 0; i < totalFiles; ++i) { char filename[40]; reader.read(reinterpret_cast(filename), 40); diff --git a/lib/filesystem/CArchiveLoader.h b/lib/filesystem/CArchiveLoader.h index e4300a855..7d9b5da78 100644 --- a/lib/filesystem/CArchiveLoader.h +++ b/lib/filesystem/CArchiveLoader.h @@ -55,7 +55,7 @@ public: * * @throws std::runtime_error if the archive wasn't found or if the archive isn't supported */ - explicit CArchiveLoader(const std::string & mountPoint, const std::string & archive); + CArchiveLoader(std::string mountPoint, boost::filesystem::path archive); /// Interface implementation /// @see ISimpleResourceLoader @@ -87,7 +87,7 @@ private: void initSNDArchive(const std::string &mountPoint, CFileInputStream & fileStream); /** The file path to the archive which is scanned and indexed. */ - std::string archive; + boost::filesystem::path archive; std::string mountPoint; diff --git a/lib/filesystem/CFileInfo.cpp b/lib/filesystem/CFileInfo.cpp index 59aef320d..c2347e897 100644 --- a/lib/filesystem/CFileInfo.cpp +++ b/lib/filesystem/CFileInfo.cpp @@ -41,17 +41,17 @@ std::string CFileInfo::getPath() const std::string CFileInfo::getExtension() const { // Get position of file extension dot - size_t dotPos = name.find_last_of("/."); + size_t dotPos = name.find_last_of('.'); - if(dotPos != std::string::npos && name[dotPos] == '.') + if(dotPos != std::string::npos) return name.substr(dotPos); - else - return ""; + + return ""; } std::string CFileInfo::getFilename() const { - size_t found = name.find_last_of("/\\"); + const size_t found = name.find_last_of("/\\"); return name.substr(found + 1); } @@ -60,9 +60,9 @@ std::string CFileInfo::getStem() const std::string rslt = name; // Remove file extension - size_t dotPos = name.find_last_of("/."); + const size_t dotPos = name.find_last_of('.'); - if(dotPos != std::string::npos && name[dotPos] == '.') + if(dotPos != std::string::npos) rslt.erase(dotPos); return rslt; @@ -70,18 +70,19 @@ std::string CFileInfo::getStem() const std::string CFileInfo::getBaseName() const { - size_t begin = name.find_last_of("/"); - size_t end = name.find_last_of("/."); - - if(end != std::string::npos && name[end] == '/') - end = std::string::npos; + size_t begin = name.find_last_of("/\\"); + size_t end = name.find_last_of("."); if(begin == std::string::npos) begin = 0; else - begin++; + ++begin; + + if (end < begin) + end = std::string::npos; - return name.substr(begin, end - begin); + size_t len = (end == std::string::npos ? std::string::npos : end - begin); + return name.substr(begin, len); } EResType::Type CFileInfo::getType() const diff --git a/lib/filesystem/CFileInputStream.cpp b/lib/filesystem/CFileInputStream.cpp index 66b70e11d..758948325 100644 --- a/lib/filesystem/CFileInputStream.cpp +++ b/lib/filesystem/CFileInputStream.cpp @@ -3,7 +3,7 @@ #include "CFileInfo.h" -CFileInputStream::CFileInputStream(const std::string & file, si64 start, si64 size) +CFileInputStream::CFileInputStream(const boost::filesystem::path & file, si64 start, si64 size) { open(file, start, size); } @@ -18,14 +18,12 @@ CFileInputStream::~CFileInputStream() fileStream.close(); } -void CFileInputStream::open(const std::string & file, si64 start, si64 size) +void CFileInputStream::open(const boost::filesystem::path & file, si64 start, si64 size) { - fileStream.open(file.c_str(), std::ios::in | std::ios::binary); + fileStream.open(file, std::ios::in | std::ios::binary); if (fileStream.fail()) - { - throw std::runtime_error("File " + file + " isn't available."); - } + throw std::runtime_error("File " + file.string() + " isn't available."); dataStart = start; dataSize = size; diff --git a/lib/filesystem/CFileInputStream.h b/lib/filesystem/CFileInputStream.h index a6d1d74cf..31e57f069 100644 --- a/lib/filesystem/CFileInputStream.h +++ b/lib/filesystem/CFileInputStream.h @@ -25,7 +25,7 @@ public: * * @see CFileInputStream::open */ - CFileInputStream(const std::string & file, si64 start=0, si64 size=0); + CFileInputStream(const boost::filesystem::path & file, si64 start = 0, si64 size = 0); /** * C-tor. Opens the specified file. @@ -88,11 +88,11 @@ private: * * @throws std::runtime_error if file wasn't found */ - void open(const std::string & file, si64 start, si64 size); + void open(const boost::filesystem::path & file, si64 start, si64 size); si64 dataStart; si64 dataSize; /** Native c++ input file stream object. */ - std::ifstream fileStream; + boost::filesystem::ifstream fileStream; }; diff --git a/lib/filesystem/CFilesystemLoader.cpp b/lib/filesystem/CFilesystemLoader.cpp index a35c7d892..0704c8a06 100644 --- a/lib/filesystem/CFilesystemLoader.cpp +++ b/lib/filesystem/CFilesystemLoader.cpp @@ -4,9 +4,11 @@ #include "CFileInfo.h" #include "CFileInputStream.h" -CFilesystemLoader::CFilesystemLoader(const std::string &mountPoint, const std::string & baseDirectory, size_t depth, bool initial): - baseDirectory(baseDirectory), - mountPoint(mountPoint), +namespace bfs = boost::filesystem; + +CFilesystemLoader::CFilesystemLoader(std::string _mountPoint, bfs::path baseDirectory, size_t depth, bool initial): + baseDirectory(std::move(baseDirectory)), + mountPoint(std::move(_mountPoint)), fileList(listFiles(mountPoint, depth, initial)) { logGlobal->traceStream() << "Filesystem loaded, " << fileList.size() << " files found"; @@ -16,7 +18,7 @@ std::unique_ptr CFilesystemLoader::load(const ResourceID & resourc { assert(fileList.count(resourceName)); - std::unique_ptr stream(new CFileInputStream(baseDirectory + '/' + fileList.at(resourceName))); + std::unique_ptr stream(new CFileInputStream(baseDirectory / fileList.at(resourceName))); return stream; } @@ -34,7 +36,7 @@ boost::optional CFilesystemLoader::getResourceName(const ResourceID { assert(existsResource(resourceName)); - return baseDirectory + '/' + fileList.at(resourceName); + return (baseDirectory / fileList.at(resourceName)).string(); } std::unordered_set CFilesystemLoader::getFilteredFiles(std::function filter) const @@ -45,7 +47,8 @@ std::unordered_set CFilesystemLoader::getFilteredFiles(std::function { if (filter(file.first)) foundID.insert(file.first); - } return foundID; + } + return foundID; } bool CFilesystemLoader::createResource(std::string filename, bool update) @@ -65,7 +68,7 @@ bool CFilesystemLoader::createResource(std::string filename, bool update) if (!update) { - std::ofstream newfile (baseDirectory + "/" + filename); + bfs::ofstream newfile(baseDirectory / filename); if (!newfile.good()) return false; } @@ -73,49 +76,72 @@ bool CFilesystemLoader::createResource(std::string filename, bool update) return true; } -std::unordered_map CFilesystemLoader::listFiles(const std::string &mountPoint, size_t depth, bool initial) const +std::unordered_map CFilesystemLoader::listFiles(const std::string &mountPoint, size_t depth, bool initial) const { - std::set initialTypes; - initialTypes.insert(EResType::DIRECTORY); - initialTypes.insert(EResType::TEXT); - initialTypes.insert(EResType::ARCHIVE_LOD); - initialTypes.insert(EResType::ARCHIVE_VID); - initialTypes.insert(EResType::ARCHIVE_SND); - initialTypes.insert(EResType::ARCHIVE_ZIP); + static const EResType::Type initArray[] = { + EResType::DIRECTORY, + EResType::TEXT, + EResType::ARCHIVE_LOD, + EResType::ARCHIVE_VID, + EResType::ARCHIVE_SND, + EResType::ARCHIVE_ZIP }; + static const std::set initialTypes(initArray, initArray + ARRAY_COUNT(initArray)); - assert(boost::filesystem::is_directory(baseDirectory)); - std::unordered_map fileList; + assert(bfs::is_directory(baseDirectory)); + std::unordered_map fileList; - std::vector path;//vector holding relative path to our file + std::vector path; //vector holding relative path to our file - boost::filesystem::recursive_directory_iterator enddir; - boost::filesystem::recursive_directory_iterator it(baseDirectory, boost::filesystem::symlink_option::recurse); + bfs::recursive_directory_iterator enddir; + bfs::recursive_directory_iterator it(baseDirectory, bfs::symlink_option::recurse); for(; it != enddir; ++it) { EResType::Type type; - if (boost::filesystem::is_directory(it->status())) + if (bfs::is_directory(it->status())) { - path.resize(it.level()+1); - path.back() = it->path().leaf().string(); + path.resize(it.level() + 1); + path.back() = it->path().filename(); // don't iterate into directory if depth limit reached it.no_push(depth <= it.level()); type = EResType::DIRECTORY; } else - type = EResTypeHelper::getTypeFromExtension(boost::filesystem::extension(*it)); + type = EResTypeHelper::getTypeFromExtension(it->path().extension().string()); if (!initial || vstd::contains(initialTypes, type)) { //reconstruct relative filename (not possible via boost AFAIK) - std::string filename; - for (size_t i=0; ipath().leaf().string(); + bfs::path filename; + const size_t iterations = std::min((size_t)it.level(), path.size()); + if (iterations) + { + filename = path.front(); + for (size_t i = 1; i < iterations; ++i) + filename /= path[i]; + filename /= it->path().filename(); + } + else + filename = it->path().filename(); - fileList[ResourceID(mountPoint + filename, type)] = filename; + std::string resName; + if (bfs::path::preferred_separator != '/') + { + // resource names are using UNIX slashes (/) + resName.reserve(resName.size() + filename.native().size()); + resName = mountPoint; + for (const char c : filename.string()) + if (c != bfs::path::preferred_separator) + resName.push_back(c); + else + resName.push_back('/'); + } + else + resName = mountPoint + filename.string(); + + fileList[ResourceID(resName, type)] = std::move(filename); } } diff --git a/lib/filesystem/CFilesystemLoader.h b/lib/filesystem/CFilesystemLoader.h index 03a597497..e7cfd9a81 100644 --- a/lib/filesystem/CFilesystemLoader.h +++ b/lib/filesystem/CFilesystemLoader.h @@ -30,7 +30,7 @@ public: * * @throws std::runtime_error if the base directory is not a directory or if it is not available */ - explicit CFilesystemLoader(const std::string & mountPoint, const std::string & baseDirectory, size_t depth = 16, bool initial = false); + explicit CFilesystemLoader(std::string mountPoint, boost::filesystem::path baseDirectory, size_t depth = 16, bool initial = false); /// Interface implementation /// @see ISimpleResourceLoader @@ -39,11 +39,11 @@ public: std::string getMountPoint() const override; bool createResource(std::string filename, bool update = false) override; boost::optional getResourceName(const ResourceID & resourceName) const override; - std::unordered_set getFilteredFiles(std::function filter) const; + std::unordered_set getFilteredFiles(std::function filter) const override; private: /** The base directory which is scanned and indexed. */ - std::string baseDirectory; + boost::filesystem::path baseDirectory; std::string mountPoint; @@ -51,7 +51,7 @@ private: * key = ResourceID for resource loader * value = name that can be used to access file */ - std::unordered_map fileList; + std::unordered_map fileList; /** * Returns a list of pathnames denoting the files in the directory denoted by this pathname. @@ -62,5 +62,5 @@ private: * @return a list of pathnames denoting the files and directories in the directory denoted by this pathname * The array will be empty if the directory is empty. Ptr is null if the directory doesn't exist or if it isn't a directory. */ - std::unordered_map listFiles(const std::string &mountPoint, size_t depth, bool initial) const; + std::unordered_map listFiles(const std::string &mountPoint, size_t depth, bool initial) const; }; diff --git a/lib/filesystem/CZipLoader.h b/lib/filesystem/CZipLoader.h index 397814814..cccab7007 100644 --- a/lib/filesystem/CZipLoader.h +++ b/lib/filesystem/CZipLoader.h @@ -16,7 +16,11 @@ #include "CCompressedStream.h" // Necessary here in order to get all types +#ifdef USE_SYSTEM_MINIZIP +#include +#else #include "../minizip/unzip.h" +#endif class DLL_LINKAGE CZipStream : public CBufferedStream { diff --git a/lib/filesystem/Filesystem.cpp b/lib/filesystem/Filesystem.cpp index ce49e7f9f..c9c9790e5 100644 --- a/lib/filesystem/Filesystem.cpp +++ b/lib/filesystem/Filesystem.cpp @@ -69,7 +69,7 @@ void CFilesystemGenerator::loadDirectory(const std::string &mountPoint, const Js std::string URI = prefix + config["path"].String(); int depth = 16; if (!config["depth"].isNull()) - depth = config["depth"].Float(); + depth = (int)config["depth"].Float(); ResourceID resID(URI, EResType::DIRECTORY); @@ -130,7 +130,7 @@ ISimpleResourceLoader * CResourceHandler::createInitial() auto filename = loader->getResourceName(ID); if (filename) { - auto dir = new CFilesystemLoader(URI + "/", *filename, depth, true); + auto dir = new CFilesystemLoader(URI + '/', *filename, depth, true); initialLoader->addLoader(dir, false); } } diff --git a/lib/filesystem/ResourceID.cpp b/lib/filesystem/ResourceID.cpp index 4209cb9c1..a1af79afa 100644 --- a/lib/filesystem/ResourceID.cpp +++ b/lib/filesystem/ResourceID.cpp @@ -110,6 +110,7 @@ EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension) (".AVI", EResType::VIDEO) (".MP3", EResType::MUSIC) (".OGG", EResType::MUSIC) + (".FLAC", EResType::MUSIC) (".ZIP", EResType::ARCHIVE_ZIP) (".LOD", EResType::ARCHIVE_LOD) (".PAC", EResType::ARCHIVE_LOD) diff --git a/lib/int3.h b/lib/int3.h index 41a8dd908..7ac595183 100644 --- a/lib/int3.h +++ b/lib/int3.h @@ -94,11 +94,11 @@ public: } //returns squared distance on Oxy plane (z coord is not used) - si32 dist2dSQ(const int3 & o) const + ui32 dist2dSQ(const int3 & o) const { const si32 dx = (x - o.x); const si32 dy = (y - o.y); - return dx*dx + dy*dy; + return (ui32)(dx*dx) + (ui32)(dy*dy); } //returns distance on Oxy plane (z coord is not used) double dist2d(const int3 & o) const @@ -157,15 +157,17 @@ struct ShashInt3 static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) }; -//FIXME: make sure it's container and not just any template int3 findClosestTile (Container & container, int3 dest) { - int3 result(-1,-1,-1); + static_assert(std::is_same::value, + "findClosestTile requires container."); + + int3 result(-1, -1, -1); ui32 distance = std::numeric_limits::max(); - for (int3 tile : container) + for (const int3& tile : container) { - ui32 currentDistance = dest.dist2dSQ(tile); + const ui32 currentDistance = dest.dist2dSQ(tile); if (currentDistance < distance) { result = tile; diff --git a/lib/logging/CBasicLogConfigurator.cpp b/lib/logging/CBasicLogConfigurator.cpp index 2db783475..97d0d3d35 100644 --- a/lib/logging/CBasicLogConfigurator.cpp +++ b/lib/logging/CBasicLogConfigurator.cpp @@ -3,11 +3,8 @@ #include "../CConfigHandler.h" -CBasicLogConfigurator::CBasicLogConfigurator(const std::string & filePath, CConsoleHandler * console) : filePath(filePath), - console(console), appendToLogFile(false) -{ - -} +CBasicLogConfigurator::CBasicLogConfigurator(boost::filesystem::path filePath, CConsoleHandler * const console) : + filePath(std::move(filePath)), console(console), appendToLogFile(false) {} void CBasicLogConfigurator::configureDefault() { @@ -21,7 +18,8 @@ void CBasicLogConfigurator::configure() try { const JsonNode & loggingNode = settings["logging"]; - if(loggingNode.isNull()) throw std::runtime_error("Settings haven't been loaded."); + if(loggingNode.isNull()) + throw std::runtime_error("Settings haven't been loaded."); // Configure loggers const JsonNode & loggers = loggingNode["loggers"]; @@ -87,7 +85,7 @@ void CBasicLogConfigurator::configure() logGlobal->infoStream() << "Initialized logging system based on settings successfully."; } -ELogLevel::ELogLevel CBasicLogConfigurator::getLogLevel(const std::string & level) const +ELogLevel::ELogLevel CBasicLogConfigurator::getLogLevel(const std::string & level) { static const std::map levelMap = boost::assign::map_list_of ("trace", ELogLevel::TRACE) @@ -95,18 +93,15 @@ ELogLevel::ELogLevel CBasicLogConfigurator::getLogLevel(const std::string & leve ("info", ELogLevel::INFO) ("warn", ELogLevel::WARN) ("error", ELogLevel::ERROR); + const auto & levelPair = levelMap.find(level); if(levelPair != levelMap.end()) - { return levelPair->second; - } else - { throw std::runtime_error("Log level " + level + " unknown."); - } } -EConsoleTextColor::EConsoleTextColor CBasicLogConfigurator::getConsoleColor(const std::string & colorName) const +EConsoleTextColor::EConsoleTextColor CBasicLogConfigurator::getConsoleColor(const std::string & colorName) { static const std::map colorMap = boost::assign::map_list_of ("default", EConsoleTextColor::DEFAULT) @@ -117,13 +112,10 @@ EConsoleTextColor::EConsoleTextColor CBasicLogConfigurator::getConsoleColor(cons ("white", EConsoleTextColor::WHITE) ("gray", EConsoleTextColor::GRAY) ("teal", EConsoleTextColor::TEAL); + const auto & colorPair = colorMap.find(colorName); if(colorPair != colorMap.end()) - { return colorPair->second; - } else - { throw std::runtime_error("Color " + colorName + " unknown."); - } } diff --git a/lib/logging/CBasicLogConfigurator.h b/lib/logging/CBasicLogConfigurator.h index 4c42f2ea4..498f2a32c 100644 --- a/lib/logging/CBasicLogConfigurator.h +++ b/lib/logging/CBasicLogConfigurator.h @@ -22,7 +22,7 @@ class JsonNode; class DLL_LINKAGE CBasicLogConfigurator { public: - CBasicLogConfigurator(const std::string & filePath, CConsoleHandler * console); + CBasicLogConfigurator(boost::filesystem::path filePath, CConsoleHandler * const console); /// Configures the logging system by parsing the logging settings. It adds the console target and the file target to the global logger. /// Doesn't throw, but logs on success or fault. @@ -30,12 +30,15 @@ public: /// Configures a default logging system by adding the console target and the file target to the global logger. void configureDefault(); - private: - ELogLevel::ELogLevel getLogLevel(const std::string & level) const; - EConsoleTextColor::EConsoleTextColor getConsoleColor(const std::string & colorName) const; + // Gets ELogLevel enum from string. (Should be moved to CLogger as a separate function?) + // Throws: std::runtime_error + static ELogLevel::ELogLevel getLogLevel(const std::string & level); + // Gets EConsoleTextColor enum from strings. (Should be moved to CLogger as a separate function?) + // Throws: std::runtime_error + static EConsoleTextColor::EConsoleTextColor getConsoleColor(const std::string & colorName); - std::string filePath; + boost::filesystem::path filePath; CConsoleHandler * console; bool appendToLogFile; }; diff --git a/lib/logging/CLogger.cpp b/lib/logging/CLogger.cpp index 6832defa7..63fa95b41 100644 --- a/lib/logging/CLogger.cpp +++ b/lib/logging/CLogger.cpp @@ -1,46 +1,51 @@ -#ifdef __ANDROID__ -#include -#endif - #include "StdInc.h" #include "CLogger.h" +#ifdef VCMI_ANDROID +#include + +namespace ELogLevel +{ + int toAndroid(ELogLevel logLevel) + { + switch (logLevel) + { + case TRACE: return ANDROID_LOG_VERBOSE; + case DEBUG: return ANDROID_LOG_DEBUG; + case INFO: return ANDROID_LOG_INFO; + case WARN: return ANDROID_LOG_WARN; + case ERROR: return ANDROID_LOG_ERROR; + default:; + } + return ANDROID_LOG_UNKNOWN; + } +} +#endif + const std::string CLoggerDomain::DOMAIN_GLOBAL = "global"; -CLoggerDomain::CLoggerDomain(const std::string & name) : name(name) +CLoggerDomain::CLoggerDomain(std::string name) : name(std::move(name)) { - if(name.empty()) throw std::runtime_error("Logger domain cannot be empty."); + if (this->name.empty()) + throw std::runtime_error("Logger domain cannot be empty."); } CLoggerDomain CLoggerDomain::getParent() const { - if(isGlobalDomain()) return *this; + if(isGlobalDomain()) + return *this; - size_t pos = name.find_last_of("."); + const size_t pos = name.find_last_of("."); if(pos != std::string::npos) - { return CLoggerDomain(name.substr(0, pos)); - } - else - { - return CLoggerDomain(DOMAIN_GLOBAL); - } + return CLoggerDomain(DOMAIN_GLOBAL); } -bool CLoggerDomain::isGlobalDomain() const -{ - return name == DOMAIN_GLOBAL; -} +bool CLoggerDomain::isGlobalDomain() const { return name == DOMAIN_GLOBAL; } -std::string CLoggerDomain::getName() const -{ - return name; -} +const std::string& CLoggerDomain::getName() const { return name; } -CLoggerStream::CLoggerStream(const CLogger & logger, ELogLevel::ELogLevel level) : logger(logger), level(level), sbuffer(nullptr) -{ - -} +CLoggerStream::CLoggerStream(const CLogger & logger, ELogLevel::ELogLevel level) : logger(logger), level(level), sbuffer(nullptr) {} CLoggerStream::~CLoggerStream() { @@ -67,20 +72,14 @@ CLogger * CLogger::getLogger(const CLoggerDomain & domain) TLockGuardRec _(smx); CLogger * logger = CLogManager::get().getLogger(domain); - if(logger) - { - return logger; - } - else + if(!logger) // Create new logger { logger = new CLogger(domain); if(domain.isGlobalDomain()) - { logger->setLevel(ELogLevel::TRACE); - } CLogManager::get().addLogger(logger); - return logger; } + return logger; } CLogger * CLogger::getGlobalLogger() @@ -102,62 +101,22 @@ CLogger::CLogger(const CLoggerDomain & domain) : domain(domain) } } -void CLogger::trace(const std::string & message) const -{ - log(ELogLevel::TRACE, message); -} +void CLogger::trace(const std::string & message) const { log(ELogLevel::TRACE, message); } +void CLogger::debug(const std::string & message) const { log(ELogLevel::DEBUG, message); } +void CLogger::info(const std::string & message) const { log(ELogLevel::INFO, message); } +void CLogger::warn(const std::string & message) const { log(ELogLevel::WARN, message); } +void CLogger::error(const std::string & message) const { log(ELogLevel::ERROR, message); } -CLoggerStream CLogger::traceStream() const -{ - return CLoggerStream(*this, ELogLevel::TRACE); -} - -void CLogger::debug(const std::string & message) const -{ - log(ELogLevel::DEBUG, message); -} - -CLoggerStream CLogger::debugStream() const -{ - return CLoggerStream(*this, ELogLevel::DEBUG); -} - -void CLogger::info(const std::string & message) const -{ - log(ELogLevel::INFO, message); -} - -CLoggerStream CLogger::infoStream() const -{ - return CLoggerStream(*this, ELogLevel::INFO); -} - -void CLogger::warn(const std::string & message) const -{ - log(ELogLevel::WARN, message); -} - -CLoggerStream CLogger::warnStream() const -{ - return CLoggerStream(*this, ELogLevel::WARN); -} - -void CLogger::error(const std::string & message) const -{ - log(ELogLevel::ERROR, message); -} - -CLoggerStream CLogger::errorStream() const -{ - return CLoggerStream(*this, ELogLevel::ERROR); -} +CLoggerStream CLogger::traceStream() const { return CLoggerStream(*this, ELogLevel::TRACE); } +CLoggerStream CLogger::debugStream() const { return CLoggerStream(*this, ELogLevel::DEBUG); } +CLoggerStream CLogger::infoStream() const { return CLoggerStream(*this, ELogLevel::INFO); } +CLoggerStream CLogger::warnStream() const { return CLoggerStream(*this, ELogLevel::WARN); } +CLoggerStream CLogger::errorStream() const { return CLoggerStream(*this, ELogLevel::ERROR); } void CLogger::log(ELogLevel::ELogLevel level, const std::string & message) const { if(getEffectiveLevel() <= level) - { callTargets(LogRecord(domain, level, message)); - } } ELogLevel::ELogLevel CLogger::getLevel() const @@ -169,14 +128,11 @@ ELogLevel::ELogLevel CLogger::getLevel() const void CLogger::setLevel(ELogLevel::ELogLevel level) { TLockGuard _(mx); - if(domain.isGlobalDomain() && level == ELogLevel::NOT_SET) return; - this->level = level; + if (!domain.isGlobalDomain() || level != ELogLevel::NOT_SET) + this->level = level; } -const CLoggerDomain & CLogger::getDomain() const -{ - return domain; -} +const CLoggerDomain & CLogger::getDomain() const { return domain; } void CLogger::addTarget(unique_ptr && target) { @@ -187,9 +143,8 @@ void CLogger::addTarget(unique_ptr && target) ELogLevel::ELogLevel CLogger::getEffectiveLevel() const { for(const CLogger * logger = this; logger != nullptr; logger = logger->parent) - { - if(logger->getLevel() != ELogLevel::NOT_SET) return logger->getLevel(); - } + if(logger->getLevel() != ELogLevel::NOT_SET) + return logger->getLevel(); // This shouldn't be reached, as the root logger must have set a log level return ELogLevel::INFO; @@ -199,12 +154,8 @@ void CLogger::callTargets(const LogRecord & record) const { TLockGuard _(mx); for(const CLogger * logger = this; logger != nullptr; logger = logger->parent) - { for(auto & target : logger->targets) - { target->write(record); - } - } } void CLogger::clearTargets() @@ -213,26 +164,15 @@ void CLogger::clearTargets() targets.clear(); } -bool CLogger::isDebugEnabled() const -{ - return getEffectiveLevel() <= ELogLevel::DEBUG; -} - -bool CLogger::isTraceEnabled() const -{ - return getEffectiveLevel() <= ELogLevel::TRACE; -} +bool CLogger::isDebugEnabled() const { return getEffectiveLevel() <= ELogLevel::DEBUG; } +bool CLogger::isTraceEnabled() const { return getEffectiveLevel() <= ELogLevel::TRACE; } CTraceLogger::CTraceLogger(const CLogger * logger, const std::string & beginMessage, const std::string & endMessage) : logger(logger), endMessage(endMessage) { - logger->traceStream() << beginMessage; -} - -CTraceLogger::~CTraceLogger() -{ - logger->traceStream() << endMessage; + logger->trace(beginMessage); } +CTraceLogger::~CTraceLogger() { logger->trace(std::move(endMessage)); } CLogManager & CLogManager::get() { @@ -241,17 +181,11 @@ CLogManager & CLogManager::get() return instance; } -CLogManager::CLogManager() -{ - -} - +CLogManager::CLogManager() { } CLogManager::~CLogManager() { for(auto & i : loggers) - { delete i.second; - } } void CLogManager::addLogger(CLogger * logger) @@ -265,34 +199,30 @@ CLogger * CLogManager::getLogger(const CLoggerDomain & domain) TLockGuard _(mx); auto it = loggers.find(domain.getName()); if(it != loggers.end()) - { return it->second; - } else - { return nullptr; - } } -CLogFormatter::CLogFormatter() : pattern("%m") +CLogFormatter::CLogFormatter() : CLogFormatter("%m") { } + +CLogFormatter::CLogFormatter(const std::string & pattern) : pattern(pattern) { boost::posix_time::time_facet * facet = new boost::posix_time::time_facet("%H:%M:%S"); dateStream.imbue(std::locale(dateStream.getloc(), facet)); } -CLogFormatter::CLogFormatter(const std::string & pattern) -{ - setPattern(pattern); -} +CLogFormatter::CLogFormatter(const CLogFormatter & c) : pattern(c.pattern) { } +CLogFormatter::CLogFormatter(CLogFormatter && m) : pattern(std::move(m.pattern)) { } -CLogFormatter::CLogFormatter(const CLogFormatter & other) +CLogFormatter & CLogFormatter::operator=(const CLogFormatter & c) { - *this = other; + pattern = c.pattern; + return *this; } - -CLogFormatter & CLogFormatter::operator=(const CLogFormatter & other) +CLogFormatter & CLogFormatter::operator=(CLogFormatter && m) { - pattern = other.pattern; + pattern = std::move(m.pattern); return *this; } @@ -336,15 +266,10 @@ std::string CLogFormatter::format(const LogRecord & record) const return message; } -void CLogFormatter::setPattern(const std::string & pattern) -{ - this->pattern = pattern; -} +void CLogFormatter::setPattern(const std::string & pattern) { this->pattern = pattern; } +void CLogFormatter::setPattern(std::string && pattern) { this->pattern = std::move(pattern); } -const std::string & CLogFormatter::getPattern() const -{ - return pattern; -} +const std::string & CLogFormatter::getPattern() const { return pattern; } CColorMapping::CColorMapping() { @@ -365,30 +290,24 @@ void CColorMapping::setColorFor(const CLoggerDomain & domain, ELogLevel::ELogLev EConsoleTextColor::EConsoleTextColor CColorMapping::getColorFor(const CLoggerDomain & domain, ELogLevel::ELogLevel level) const { - std::string name = domain.getName(); + CLoggerDomain currentDomain = domain; while(true) { - const auto & loggerPair = map.find(name); + const auto & loggerPair = map.find(currentDomain.getName()); if(loggerPair != map.end()) { const auto & levelMap = loggerPair->second; const auto & levelPair = levelMap.find(level); if(levelPair != levelMap.end()) - { return levelPair->second; - } } - CLoggerDomain currentDomain(name); - if(!currentDomain.isGlobalDomain()) - { - name = currentDomain.getParent().getName(); - } - else - { + if (currentDomain.isGlobalDomain()) break; - } + + currentDomain = currentDomain.getParent(); } + throw std::runtime_error("failed to find color for requested domain/level pair"); } @@ -399,103 +318,56 @@ CLogConsoleTarget::CLogConsoleTarget(CConsoleHandler * console) : console(consol void CLogConsoleTarget::write(const LogRecord & record) { - if(threshold > record.level) return; + if(threshold > record.level) + return; std::string message = formatter.format(record); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "VCMI", "%s", message.c_str()); +#ifdef VCMI_ANDROID + __android_log_write(ELogLevel::toAndroid(record.level), "VCMI", message.c_str()); #endif - bool printToStdErr = record.level >= ELogLevel::WARN; + const bool printToStdErr = record.level >= ELogLevel::WARN; if(console) { - if(coloredOutputEnabled) - { - console->print(message, true, colorMapping.getColorFor(record.domain, record.level)); - } - else - { - console->print(message, true, EConsoleTextColor::DEFAULT, printToStdErr); - } + const EConsoleTextColor::EConsoleTextColor textColor = + coloredOutputEnabled ? colorMapping.getColorFor(record.domain, record.level) : EConsoleTextColor::DEFAULT; + + console->print(message, true, textColor, printToStdErr); } else { TLockGuard _(mx); if(printToStdErr) - { std::cerr << message << std::endl; - } else - { std::cout << message << std::endl; - } } } -bool CLogConsoleTarget::isColoredOutputEnabled() const -{ - return coloredOutputEnabled; -} +bool CLogConsoleTarget::isColoredOutputEnabled() const { return coloredOutputEnabled; } +void CLogConsoleTarget::setColoredOutputEnabled(bool coloredOutputEnabled) { this->coloredOutputEnabled = coloredOutputEnabled; } -void CLogConsoleTarget::setColoredOutputEnabled(bool coloredOutputEnabled) -{ - this->coloredOutputEnabled = coloredOutputEnabled; -} +ELogLevel::ELogLevel CLogConsoleTarget::getThreshold() const { return threshold; } +void CLogConsoleTarget::setThreshold(ELogLevel::ELogLevel threshold) { this->threshold = threshold; } -ELogLevel::ELogLevel CLogConsoleTarget::getThreshold() const -{ - return threshold; -} +const CLogFormatter & CLogConsoleTarget::getFormatter() const { return formatter; } +void CLogConsoleTarget::setFormatter(const CLogFormatter & formatter) { this->formatter = formatter; } -void CLogConsoleTarget::setThreshold(ELogLevel::ELogLevel threshold) -{ - this->threshold = threshold; -} +const CColorMapping & CLogConsoleTarget::getColorMapping() const { return colorMapping; } +void CLogConsoleTarget::setColorMapping(const CColorMapping & colorMapping) { this->colorMapping = colorMapping; } -const CLogFormatter & CLogConsoleTarget::getFormatter() const -{ - return formatter; -} - -void CLogConsoleTarget::setFormatter(const CLogFormatter & formatter) -{ - this->formatter = formatter; -} - -const CColorMapping & CLogConsoleTarget::getColorMapping() const -{ - return colorMapping; -} - -void CLogConsoleTarget::setColorMapping(const CColorMapping & colorMapping) -{ - this->colorMapping = colorMapping; -} - -CLogFileTarget::CLogFileTarget(const std::string & filePath, bool append /*= true*/) - : file(filePath, append ? std::ios_base::app : std::ios_base::out) +CLogFileTarget::CLogFileTarget(boost::filesystem::path filePath, bool append /*= true*/) + : file(std::move(filePath), append ? std::ios_base::app : std::ios_base::out) { formatter.setPattern("%d %l %n [%t] - %m"); } -CLogFileTarget::~CLogFileTarget() -{ - file.close(); -} - void CLogFileTarget::write(const LogRecord & record) { TLockGuard _(mx); file << formatter.format(record) << std::endl; } -const CLogFormatter & CLogFileTarget::getFormatter() const -{ - return formatter; -} - -void CLogFileTarget::setFormatter(const CLogFormatter & formatter) -{ - this->formatter = formatter; -} +const CLogFormatter & CLogFileTarget::getFormatter() const { return formatter; } +void CLogFileTarget::setFormatter(const CLogFormatter & formatter) { this->formatter = formatter; } \ No newline at end of file diff --git a/lib/logging/CLogger.h b/lib/logging/CLogger.h index 9e5afe2db..c6a5737f0 100644 --- a/lib/logging/CLogger.h +++ b/lib/logging/CLogger.h @@ -19,15 +19,19 @@ class ILogTarget; namespace ELogLevel { -enum ELogLevel -{ - NOT_SET = 0, - TRACE, - DEBUG, - INFO, - WARN, - ERROR -}; + enum ELogLevel + { + NOT_SET = 0, + TRACE, + DEBUG, + INFO, + WARN, + ERROR + }; + + #ifdef VCMI_ANDROID + int toAndroid(ELogLevel logLevel); + #endif } /// The class CLoggerDomain provides convenient access to super domains from a sub domain. @@ -36,9 +40,9 @@ class DLL_LINKAGE CLoggerDomain public: /// Constructs a CLoggerDomain with the domain designated by name. /// Sub-domains can be specified by separating domains by a dot, e.g. "ai.battle". The global domain is named "global". - explicit CLoggerDomain(const std::string & name); + explicit CLoggerDomain(std::string name); - std::string getName() const; + const std::string& getName() const; CLoggerDomain getParent() const; bool isGlobalDomain() const; @@ -58,8 +62,11 @@ public: template CLoggerStream & operator<<(const T & data) { - if(!sbuffer) sbuffer = new std::stringstream(); + if(!sbuffer) + sbuffer = new std::stringstream(std::ios_base::out); + (*sbuffer) << data; + return *this; } @@ -84,18 +91,16 @@ public: /// Log methods for various log levels void trace(const std::string & message) const; - CLoggerStream traceStream() const; - void debug(const std::string & message) const; - CLoggerStream debugStream() const; - void info(const std::string & message) const; - CLoggerStream infoStream() const; - void warn(const std::string & message) const; - CLoggerStream warnStream() const; - void error(const std::string & message) const; + + /// Log streams for various log levels + CLoggerStream traceStream() const; + CLoggerStream debugStream() const; + CLoggerStream infoStream() const; + CLoggerStream warnStream() const; CLoggerStream errorStream() const; inline void log(ELogLevel::ELogLevel level, const std::string & message) const; @@ -184,10 +189,7 @@ struct DLL_LINKAGE LogRecord { LogRecord(const CLoggerDomain & domain, ELogLevel::ELogLevel level, const std::string & message) : domain(domain), level(level), message(message), timeStamp(boost::posix_time::second_clock::local_time()), - threadId(boost::this_thread::get_id()) - { - - } + threadId(boost::this_thread::get_id()) { } CLoggerDomain domain; ELogLevel::ELogLevel level; @@ -208,12 +210,17 @@ class DLL_LINKAGE CLogFormatter { public: CLogFormatter(); - CLogFormatter(const std::string & pattern); + CLogFormatter(const CLogFormatter & copy); + CLogFormatter(CLogFormatter && move); - CLogFormatter(const CLogFormatter & other); - CLogFormatter & operator=(const CLogFormatter & other); + CLogFormatter(const std::string & pattern); + + CLogFormatter & operator=(const CLogFormatter & copy); + CLogFormatter & operator=(CLogFormatter && move); void setPattern(const std::string & pattern); + void setPattern(std::string && pattern); + const std::string & getPattern() const; std::string format(const LogRecord & record) const; @@ -284,8 +291,7 @@ class DLL_LINKAGE CLogFileTarget : public ILogTarget public: /// Constructs a CLogFileTarget and opens the file designated by filePath. If the append parameter is true, the file /// will be appended to. Otherwise the file designated by filePath will be truncated before being opened. - explicit CLogFileTarget(const std::string & filePath, bool append = true); - ~CLogFileTarget(); + explicit CLogFileTarget(boost::filesystem::path filePath, bool append = true); const CLogFormatter & getFormatter() const; void setFormatter(const CLogFormatter & formatter); @@ -293,7 +299,7 @@ public: void write(const LogRecord & record) override; private: - std::ofstream file; + boost::filesystem::ofstream file; CLogFormatter formatter; mutable boost::mutex mx; }; diff --git a/lib/mapObjects/CArmedInstance.h b/lib/mapObjects/CArmedInstance.h index 9b58332ca..0ebe2abc1 100644 --- a/lib/mapObjects/CArmedInstance.h +++ b/lib/mapObjects/CArmedInstance.h @@ -13,7 +13,7 @@ * */ -class BattleInfo; +struct BattleInfo; class CGameState; class DLL_LINKAGE CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet diff --git a/lib/mapObjects/CBank.h b/lib/mapObjects/CBank.h index cfecdf20c..59b954116 100644 --- a/lib/mapObjects/CBank.h +++ b/lib/mapObjects/CBank.h @@ -13,7 +13,7 @@ * */ -class BankConfig; +struct BankConfig; class CBankInstanceConstructor; class DLL_LINKAGE CBank : public CArmedInstance diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 96a98826c..4eddc7f6c 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1238,6 +1238,8 @@ std::vector CGHeroInstance::getLevelUpProposedSecondarySkills() none.erase(skills.back()); } + if (skills.size() == 2) // Fix for #1868 to avoid changing logic (possibly causing bugs in process) + std::swap(skills[0], skills[1]); return skills; } diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 9f811c4b2..69c65cd06 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -19,7 +19,7 @@ class CHero; class CGBoat; class CGTownInstance; -class TerrainTile; +struct TerrainTile; class CGHeroPlaceholder : public CGObjectInstance { diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h index c2ad87dde..974351b83 100644 --- a/lib/mapObjects/CGPandoraBox.h +++ b/lib/mapObjects/CGPandoraBox.h @@ -14,7 +14,7 @@ * */ -class InfoWindow; +struct InfoWindow; class DLL_LINKAGE CGPandoraBox : public CArmedInstance { diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index a86df42d7..bde370ff7 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -34,13 +34,11 @@ void CGDwelling::initObj() if (getOwner() != PlayerColor::NEUTRAL) cb->gameState()->players[getOwner()].dwellings.push_back (this); - } - //putStack(SlotID(0), new CStackInstance(CreatureID::GOLD_GOLEM, 9)); - //putStack(SlotID(1), new CStackInstance(CreatureID::DIAMOND_GOLEM, 6)); - //putStack(SlotID(0), new CStackInstance(CreatureID::EARTH_ELEMENTAL, 12)); + assert(!creatures.empty()); + assert(!creatures[0].second.empty()); break; - + } case Obj::REFUGEE_CAMP: //is handled within newturn func break; diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 8c276e05a..f47c20a3c 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -318,6 +318,11 @@ std::string CObjectClassesHandler::getObjectName(si32 type, si32 subtype) const return getObjectName(type); } +std::string CObjectClassesHandler::getObjectHandlerName(si32 type) const +{ + return objects.at(type)->handlerName; +} + void AObjectTypeHandler::setType(si32 type, si32 subtype) { this->type = type; diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 30a4ef88f..526fd8ad1 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -221,6 +221,9 @@ public: std::string getObjectName(si32 type) const; std::string getObjectName(si32 type, si32 subtype) const; + + /// Returns handler string describing the handler (for use in client) + std::string getObjectHandlerName(si32 type) const; template void serialize(Handler &h, const int version) { diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index 0b27d759e..adc71b3d1 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -19,7 +19,7 @@ class CGHeroInstance; class IGameCallback; class CGObjectInstance; -class MetaString; +struct MetaString; struct BattleResult; class DLL_LINKAGE IObjectInterface diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index b0064f573..14eb1a11f 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -158,7 +158,9 @@ void CDwellingInstanceConstructor::initTypeData(const JsonNode & input) availableCreatures[i][j] = VLC->creh->creatures[index]; }); } + assert(!availableCreatures[i].empty()); } + guards = input["guards"]; } @@ -185,10 +187,11 @@ void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CR CGDwelling * dwelling = dynamic_cast(object); dwelling->creatures.clear(); - dwelling->creatures.resize(availableCreatures.size()); + dwelling->creatures.reserve(availableCreatures.size()); for (auto & entry : availableCreatures) { + dwelling->creatures.resize(dwelling->creatures.size() + 1); for (const CCreature * cre : entry) dwelling->creatures.back().second.push_back(cre->idNumber); } diff --git a/lib/mapObjects/JsonRandom.cpp b/lib/mapObjects/JsonRandom.cpp index be28ce5e2..1eda13ebc 100644 --- a/lib/mapObjects/JsonRandom.cpp +++ b/lib/mapObjects/JsonRandom.cpp @@ -197,7 +197,7 @@ namespace JsonRandom } const CCreature * crea = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier("creature", node["type"]).get()]; info.allowedCreatures.push_back(crea); - if (node["upgradeChance"].Float() > 0) + if (node["upgradeChance"].Float() > 0) { for (auto creaID : crea->upgrades) info.allowedCreatures.push_back(VLC->creh->creatures[creaID]); diff --git a/lib/rmg/float3.h b/lib/rmg/float3.h index 36f6feba0..402ba66b0 100644 --- a/lib/rmg/float3.h +++ b/lib/rmg/float3.h @@ -10,75 +10,103 @@ * */ +// FIXME: Class doesn't contain three float values. Update name and description. /// Class which consists of three float values. Represents position virtual RMG (0;1) area. class float3 { public: float x, y; si32 z; - inline float3():x(0),y(0),z(0){}; //c-tor, x/y/z initialized to 0 - inline float3(const float X, const float Y, const si32 Z):x(X),y(Y),z(Z){}; //c-tor - inline float3(const float3 & val) : x(val.x), y(val.y), z(val.z){} //copy c-tor - inline float3 & operator=(const float3 & val) {x = val.x; y = val.y; z = val.z; return *this;} //assignemt operator - ~float3() {} // d-tor - does nothing - inline float3 operator+(const float3 & i) const //returns float3 with coordinates increased by corresponding coordinate of given float3 - {return float3(x+i.x,y+i.y,z+i.z);} - inline float3 operator+(const float i) const //returns float3 with coordinates increased by given numer - {return float3(x+i,y+i,z+i);} - inline float3 operator-(const float3 & i) const //returns float3 with coordinates decreased by corresponding coordinate of given float3 - {return float3(x-i.x,y-i.y,z-i.z);} - inline float3 operator-(const float i) const //returns float3 with coordinates decreased by given numer - {return float3(x-i,y-i,z-i);} - inline float3 operator*(const float i) const //returns float3 with plane coordinates decreased by given numer - {return float3(x*i, y*i, z);} - inline float3 operator/(const float i) const //returns float3 with plane coordinates decreased by given numer - {return float3(x/i, y/i, z);} - inline float3 operator-() const //returns opposite position - {return float3(-x,-y,-z);} - inline double dist2d(const float3 &other) const //distance (z coord is not used) - {return std::sqrt((double)(x-other.x)*(x-other.x) + (y-other.y)*(y-other.y));} - inline bool areNeighbours(const float3 &other) const - {return dist2d(other) < 2. && z == other.z;} - inline void operator+=(const float3 & i) + + float3() : x(0), y(0), z(0) {} + float3(const float X, const float Y, const si32 Z): x(X), y(Y), z(Z) {} + float3(const float3 & copy) : x(copy.x), y(copy.y), z(copy.z) {} + float3 & operator=(const float3 & copy) { x = copy.x; y = copy.y; z = copy.z; return *this; } + + // returns float3 with coordinates increased by corresponding coordinate of given float3 + float3 operator+(const float3 & i) const { return float3(x + i.x, y + i.y, z + i.z); } + // returns float3 with coordinates increased by given numer + float3 operator+(const float i) const { return float3(x + i, y + i, z + (si32)i); } + // returns float3 with coordinates decreased by corresponding coordinate of given float3 + float3 operator-(const float3 & i) const { return float3(x - i.x, y - i.y, z - i.z); } + // returns float3 with coordinates decreased by given numer + float3 operator-(const float i) const { return float3(x - i, y - i, z - (si32)i); } + + // returns float3 with plane coordinates decreased by given numer + float3 operator*(const float i) const {return float3(x * i, y * i, z);} + // returns float3 with plane coordinates decreased by given numer + float3 operator/(const float i) const {return float3(x / i, y / i, z);} + + // returns opposite position + float3 operator-() const { return float3(-x, -y, -z); } + + // returns squared distance on Oxy plane (z coord is not used) + double dist2dSQ(const float3 & o) const { - x+=i.x; - y+=i.y; - z+=i.z; + const double dx = (x - o.x); + const double dy = (y - o.y); + return dx*dx + dy*dy; } - inline void operator+=(const float & i) + // returns distance on Oxy plane (z coord is not used) + double dist2d(const float3 &other) const { return std::sqrt(dist2dSQ(other)); } + + bool areNeighbours(const float3 &other) const { return (dist2dSQ(other) < 4.0) && z == other.z; } + + float3& operator+=(const float3 & i) { - x+=i; - y+=i; - z+=i; + x += i.x; + y += i.y; + z += i.z; + + return *this; } - inline void operator-=(const float3 & i) + float3& operator+=(const float & i) { - x-=i.x; - y-=i.y; - z-=i.z; + x += i; + y += i; + z += (si32)i; + + return *this; } - inline void operator-=(const float & i) + + float3& operator-=(const float3 & i) { - x+=i; - y+=i; - z+=i; + x -= i.x; + y -= i.y; + z -= i.z; + + return *this; } - inline void operator*=(const float & i) //scale on plane + float3& operator-=(const float & i) { - x*=i; - y*=i; - } - inline void operator/=(const float & i) //scale on plane - { - x/=i; - y/=i; + x += i; + y += i; + z += (si32)i; + + return *this; } - inline bool operator==(const float3 & i) const - {return (x==i.x) && (y==i.y) && (z==i.z);} - inline bool operator!=(const float3 & i) const - {return !(*this==i);} - inline bool operator<(const float3 & i) const + // scale on plane + float3& operator*=(const float & i) + { + x *= i; + y *= i; + + return *this; + } + // scale on plane + float3& operator/=(const float & i) + { + x /= i; + y /= i; + + return *this; + } + + bool operator==(const float3 & i) const { return (x == i.x) && (y == i.y) && (z == i.z); } + bool operator!=(const float3 & i) const { return (x != i.x) || (y != i.y) || (z != i.z); } + + bool operator<(const float3 & i) const { if (zi.x) return false; + return false; } - inline std::string operator ()() const + + std::string operator ()() const { return "(" + boost::lexical_cast(x) + " " + boost::lexical_cast(y) + " " + boost::lexical_cast(z) + ")"; } - inline bool valid() const + + bool valid() const { return z >= 0; //minimal condition that needs to be fulfilled for tiles in the map } + template void serialize(Handler &h, const float version) { h & x & y & z; } - }; + inline std::istream & operator>>(std::istream & str, float3 & dest) { - str>>dest.x>>dest.y>>dest.z; - return str; + return str >> dest.x >> dest.y >> dest.z; } inline std::ostream & operator<<(std::ostream & str, const float3 & sth) { - return str<warnStream() << "Warning: Folder " << VCMIDirs::get().dataPaths().back() << "/Data/s/ doesn't exist!"; + logGlobal->warnStream() << "Warning: Folder " << dataPath << " doesn't exist!"; return; } directory_iterator enddir; - for (directory_iterator dir(VCMIDirs::get().dataPaths().back() + "/Data/s"); dir!=enddir; dir++) + for (directory_iterator dir(dataPath); dir!=enddir; dir++) { if(is_regular(dir->status())) { - std::string name = dir->path().leaf().string(); - if( boost::algorithm::ends_with(name, ".erm") || - boost::algorithm::ends_with(name, ".verm") ) + const std::string ext = boost::to_upper_copy(dir->path().extension().string()); + if (ext == ".ERM" || ext == ".VERM") { ERMParser ep(dir->path().string()); FileInfo * finfo = new FileInfo; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 2d5ca8533..3dcc5e959 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -18,7 +18,7 @@ #include "CVCMIServer.h" #include "../lib/StartInfo.h" #include "../lib/mapping/CMap.h" -#ifndef __ANDROID__ +#ifndef VCMI_ANDROID #include "../lib/Interprocess.h" #endif #include "../lib/VCMI_Lib.h" @@ -32,7 +32,7 @@ #include "../lib/UnlockGuard.h" -#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(__ANDROID__) +#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(VCMI_ANDROID) #include #endif @@ -41,7 +41,7 @@ std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX using namespace boost; using namespace boost::asio; using namespace boost::asio::ip; -#ifndef __ANDROID__ +#ifndef VCMI_ANDROID namespace intpr = boost::interprocess; #endif bool end2 = false; @@ -393,7 +393,7 @@ void CVCMIServer::newPregame() void CVCMIServer::start() { -#ifndef __ANDROID__ +#ifndef VCMI_ANDROID ServerReady *sr = nullptr; intpr::mapped_region *mr; try @@ -416,7 +416,7 @@ void CVCMIServer::start() logNetwork->infoStream()<<"Listening for connections at port " << acceptor->local_endpoint().port(); auto s = new tcp::socket(acceptor->get_io_service()); boost::thread acc(std::bind(vaccept,acceptor,s,&error)); -#ifndef __ANDROID__ +#ifndef VCMI_ANDROID sr->setToTrueAndNotify(); delete mr; #endif @@ -560,7 +560,7 @@ static void handleCommandOptions(int argc, char *argv[]) } } -#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(__ANDROID__) +#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(VCMI_ANDROID) void handleLinuxSignal(int sig) { const int STACKTRACE_SIZE = 100; @@ -591,22 +591,22 @@ int main(int argc, char** argv) { // Installs a sig sev segmentation violation handler // to log stacktrace - #if defined(__GNUC__) && !defined (__MINGW32__) && !defined(__ANDROID__) + #if defined(__GNUC__) && !defined (__MINGW32__) && !defined(VCMI_ANDROID) signal(SIGSEGV, handleLinuxSignal); #endif console = new CConsoleHandler; - CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() + "/VCMI_Server_log.txt", console); + CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Server_log.txt", console); logConfig.configureDefault(); - preinitDLL(console); - settings.init(); - logConfig.configure(); - handleCommandOptions(argc, argv); port = cmdLineOptions["port"].as(); logNetwork->infoStream() << "Port " << port << " will be used."; + preinitDLL(console); + settings.init(); + logConfig.configure(); + loadDLLClasses(); srand ( (ui32)time(nullptr) ); try diff --git a/test/CVcmiTestConfig.cpp b/test/CVcmiTestConfig.cpp index a56c7f380..cbe0deced 100644 --- a/test/CVcmiTestConfig.cpp +++ b/test/CVcmiTestConfig.cpp @@ -22,7 +22,7 @@ CVcmiTestConfig::CVcmiTestConfig() { console = new CConsoleHandler; - CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() + "/VCMI_Test_log.txt", console); + CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Test_log.txt", console); logConfig.configureDefault(); preinitDLL(console); settings.init();