1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-19 21:10:12 +02:00

Merge with vcmi/develop

This commit is contained in:
Ivan Savenko 2023-01-17 23:02:25 +02:00
commit 05ac217b0f
86 changed files with 1519 additions and 844 deletions

View File

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,8 +14,8 @@ sudo add-apt-repository 'deb http://security.ubuntu.com/ubuntu bionic-security m
sudo apt-get install -qq nsis ninja-build libssl1.0.0 sudo apt-get install -qq nsis ninja-build libssl1.0.0
# MXE repository was too slow for Travis far too often # MXE repository was too slow for Travis far too often
wget -nv https://github.com/vcmi/vcmi-deps-mxe/releases/download/2022-12-26/mxe-i686-w64-mingw32.shared-2022-12-26.tar wget -nv https://github.com/vcmi/vcmi-deps-mxe/releases/download/2021-02-20/mxe-i686-w64-mingw32.shared-2021-01-22.tar
tar -xvf mxe-i686-w64-mingw32.shared-2022-12-26.tar tar -xvf mxe-i686-w64-mingw32.shared-2021-01-22.tar
sudo dpkg -i mxe-*.deb sudo dpkg -i mxe-*.deb
sudo apt-get install -f --yes sudo apt-get install -f --yes

View File

@ -232,7 +232,7 @@ if(MINGW OR MSVC)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 4800: implicit conversion from 'xxx' to bool. Possible information loss #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 4800: implicit conversion from 'xxx' to bool. Possible information loss
if(ENABLE_STRICT_COMPILATION) if(ENABLE_STRICT_COMPILATION)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wx") # Treats all compiler warnings as errors set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") # Treats all compiler warnings as errors
endif() endif()
if(ENABLE_MULTI_PROCESS_BUILDS) if(ENABLE_MULTI_PROCESS_BUILDS)
@ -563,6 +563,9 @@ if(WIN32)
FILES ${integration_loc} FILES ${integration_loc}
DESTINATION ${BIN_DIR}/platforms DESTINATION ${BIN_DIR}/platforms
) )
install(
FILES "$<TARGET_FILE:Qt${QT_VERSION_MAJOR}::QWindowsVistaStylePlugin>"
DESTINATION ${BIN_DIR}/styles)
endif() endif()
endif() endif()

View File

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

View File

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

View File

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

View File

@ -512,6 +512,20 @@ MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string mu
} }
MusicEntry::~MusicEntry() MusicEntry::~MusicEntry()
{ {
if (playing)
{
assert(0);
logGlobal->error("Attempt to delete music while playing!");
Mix_HaltMusic();
}
if (loop == 0 && Mix_FadingMusic() != MIX_NO_FADING)
{
assert(0);
logGlobal->error("Attempt to delete music while fading out!");
Mix_HaltMusic();
}
logGlobal->trace("Del-ing music file %s", currentName); logGlobal->trace("Del-ing music file %s", currentName);
if (music) if (music)
Mix_FreeMusic(music); Mix_FreeMusic(music);
@ -589,7 +603,7 @@ bool MusicEntry::play()
bool MusicEntry::stop(int fade_ms) bool MusicEntry::stop(int fade_ms)
{ {
if (playing) if (Mix_PlayingMusic())
{ {
playing = false; playing = false;
loop = 0; loop = 0;

View File

@ -19,7 +19,7 @@
#include "battle/BattleWindow.h" #include "battle/BattleWindow.h"
#include "../CCallback.h" #include "../CCallback.h"
#include "windows/CCastleInterface.h" #include "windows/CCastleInterface.h"
#include "gui/CCursorHandler.h" #include "gui/CursorHandler.h"
#include "windows/CKingdomInterface.h" #include "windows/CKingdomInterface.h"
#include "CGameInfo.h" #include "CGameInfo.h"
#include "windows/CHeroWindow.h" #include "windows/CHeroWindow.h"
@ -1624,7 +1624,7 @@ int CPlayerInterface::getLastIndex( std::string namePrefix)
else else
for (directory_iterator dir(gamesDir); dir != enddir; ++dir) for (directory_iterator dir(gamesDir); dir != enddir; ++dir)
{ {
if (is_regular(dir->status())) if (is_regular_file(dir->status()))
{ {
std::string name = dir->path().filename().string(); std::string name = dir->path().filename().string();
if (starts_with(name, namePrefix) && ends_with(name, ".vcgm1")) if (starts_with(name, namePrefix) && ends_with(name, ".vcgm1"))
@ -2487,6 +2487,9 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
// (i == 0) means hero went through all the path // (i == 0) means hero went through all the path
adventureInt->updateMoveHero(h, (i != 0)); adventureInt->updateMoveHero(h, (i != 0));
adventureInt->updateNextHero(h); adventureInt->updateNextHero(h);
// ugly workaround to force instant update of adventure map
adventureInt->animValHitCount = 8;
} }
setMovementStatus(false); setMovementStatus(false);

View File

@ -761,8 +761,14 @@ void CClient::reinitScripting()
#endif #endif
} }
#ifdef VCMI_ANDROID #ifdef VCMI_ANDROID
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_clientSetupJNI(JNIEnv * env, jobject cls)
{
logNetwork->info("Received clientSetupJNI");
CAndroidVMHelper::cacheVM(env);
}
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls) extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls)
{ {
logNetwork->info("Received server closed signal"); logNetwork->info("Received server closed signal");

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@
#include "../CMusicHandler.h" #include "../CMusicHandler.h"
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
#include "../gui/Canvas.h" #include "../gui/Canvas.h"
#include "../gui/CCursorHandler.h" #include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../windows/CAdvmapInterface.h" #include "../windows/CAdvmapInterface.h"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@
#include "../mapHandler.h" #include "../mapHandler.h"
#include "../gui/CAnimation.h" #include "../gui/CAnimation.h"
#include "../gui/CCursorHandler.h" #include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../gui/SDL_Extensions.h" #include "../gui/SDL_Extensions.h"
#include "../widgets/MiscWidgets.h" #include "../widgets/MiscWidgets.h"
@ -1039,7 +1039,7 @@ void CAdvMapInt::show(SDL_Surface * to)
{ {
++heroAnim; ++heroAnim;
} }
if(animValHitCount == 8) if(animValHitCount >= 8)
{ {
CGI->mh->updateWater(); CGI->mh->updateWater();
animValHitCount = 0; animValHitCount = 0;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@
}, },
"encoding" : { "encoding" : {
"type" : "string", "type" : "string",
"default" : "CP1252" "default" : "auto"
}, },
"swipe" : { "swipe" : {
"type" : "boolean", "type" : "boolean",
@ -81,7 +81,7 @@
"type" : "object", "type" : "object",
"additionalProperties" : false, "additionalProperties" : false,
"default": {}, "default": {},
"required" : [ "screenRes", "bitsPerPixel", "fullscreen", "realFullscreen", "spellbookAnimation","driver", "showIntro", "displayIndex" ], "required" : [ "screenRes", "bitsPerPixel", "fullscreen", "realFullscreen", "softwareCursor", "spellbookAnimation", "driver", "showIntro", "displayIndex" ],
"properties" : { "properties" : {
"screenRes" : { "screenRes" : {
"type" : "object", "type" : "object",
@ -105,6 +105,10 @@
"type" : "boolean", "type" : "boolean",
"default" : false "default" : false
}, },
"softwareCursor" : {
"type" : "boolean",
"default" : false
},
"showIntro" : { "showIntro" : {
"type" : "boolean", "type" : "boolean",
"default" : true "default" : true

View File

@ -68,7 +68,7 @@
"rockTerrain": "rockTerrain":
{ {
"type": "string", "type": "string",
"description": "The name of tock type terrain which will be used as borders in the underground" "description": "The name of rock type terrain which will be used as borders in the underground"
}, },
"river": "river":
{ {

View File

@ -115,7 +115,7 @@
{ {
"type": "button", "type": "button",
"name": "tacticNext", "name": "tacticNext",
"position": {"x": 213, "y": 4}, "position": {"x": 213, "y": 560},
"image": "icm011", "image": "icm011",
"callback": "tacticNext", "callback": "tacticNext",
"hotkey": "space" "hotkey": "space"
@ -124,7 +124,7 @@
{ {
"type": "button", "type": "button",
"name": "tacticEnd", "name": "tacticEnd",
"position": {"x": 419, "y": 4}, "position": {"x": 419, "y": 560},
"image": "icm012", "image": "icm012",
"callback": "tacticEnd", "callback": "tacticEnd",
"hotkey": "enter" "hotkey": "enter"

View File

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

View File

@ -128,6 +128,11 @@ enable_pch(vcmilauncher)
if(APPLE_IOS) if(APPLE_IOS)
set(ICONS_DESTINATION ${DATA_DIR}) set(ICONS_DESTINATION ${DATA_DIR})
# TODO: remove after fixing Conan's Qt recipe
if(XCODE_VERSION VERSION_GREATER_EQUAL 14.0)
target_link_libraries(vcmilauncher "-framework IOKit")
endif()
# workaround https://github.com/conan-io/conan-center-index/issues/13332 # workaround https://github.com/conan-io/conan-center-index/issues/13332
if(USING_CONAN) if(USING_CONAN)
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/QIOSIntegrationPlugin.h file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/QIOSIntegrationPlugin.h

View File

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

View File

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

View File

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

View File

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

View File

@ -53,6 +53,10 @@ MainWindow::MainWindow(QWidget * parent)
load(); // load FS before UI load(); // load FS before UI
ui->setupUi(this); ui->setupUi(this);
connect(ui->lobbyView, &Lobby::enableMod, ui->modlistView, &CModListView::enableModByName);
connect(ui->lobbyView, &Lobby::disableMod, ui->modlistView, &CModListView::disableModByName);
connect(ui->modlistView, &CModListView::modsChanged, ui->lobbyView, &Lobby::updateMods);
//load window settings //load window settings
QSettings s(Ui::teamName, Ui::appName); QSettings s(Ui::teamName, Ui::appName);

View File

@ -125,7 +125,7 @@ bool CModEntry::isCompatible() const
bool CModEntry::isEssential() const bool CModEntry::isEssential() const
{ {
return getValue("storedLocaly").toBool(); return getName() == "vcmi";
} }
bool CModEntry::isInstalled() const bool CModEntry::isInstalled() const

View File

@ -496,7 +496,14 @@ QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled)
void CModListView::on_enableButton_clicked() void CModListView::on_enableButton_clicked()
{ {
QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString(); QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
enableModByName(modName);
checkManagerErrors();
}
void CModListView::enableModByName(QString modName)
{
assert(findBlockingMods(modName).empty()); assert(findBlockingMods(modName).empty());
assert(findInvalidDependencies(modName).empty()); assert(findInvalidDependencies(modName).empty());
@ -505,17 +512,24 @@ void CModListView::on_enableButton_clicked()
if(modModel->getMod(name).isDisabled()) if(modModel->getMod(name).isDisabled())
manager->enableMod(name); manager->enableMod(name);
} }
checkManagerErrors(); emit modsChanged();
} }
void CModListView::on_disableButton_clicked() void CModListView::on_disableButton_clicked()
{ {
QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString(); QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
disableModByName(modName);
checkManagerErrors();
}
void CModListView::disableModByName(QString modName)
{
if(modModel->hasMod(modName) && modModel->getMod(modName).isEnabled()) if(modModel->hasMod(modName) && modModel->getMod(modName).isEnabled())
manager->disableMod(modName); manager->disableMod(modName);
checkManagerErrors(); emit modsChanged();
} }
void CModListView::on_updateButton_clicked() void CModListView::on_updateButton_clicked()
@ -544,6 +558,8 @@ void CModListView::on_uninstallButton_clicked()
manager->disableMod(modName); manager->disableMod(modName);
manager->uninstallMod(modName); manager->uninstallMod(modName);
} }
emit modsChanged();
checkManagerErrors(); checkManagerErrors();
} }
@ -631,6 +647,8 @@ void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFi
if(doInstallFiles) if(doInstallFiles)
installFiles(savedFiles); installFiles(savedFiles);
emit modsChanged();
} }
void CModListView::hideProgressBar() void CModListView::hideProgressBar()

View File

@ -66,6 +66,8 @@ class CModListView : public QWidget
signals: signals:
void extraResolutionsEnabledChanged(bool enabled); void extraResolutionsEnabledChanged(bool enabled);
void modsChanged();
public: public:
explicit CModListView(QWidget * parent = 0); explicit CModListView(QWidget * parent = 0);
@ -82,6 +84,10 @@ public:
bool isExtraResolutionsModEnabled() const; bool isExtraResolutionsModEnabled() const;
const CModList & getModList() const; const CModList & getModList() const;
public slots:
void enableModByName(QString modName);
void disableModByName(QString modName);
private slots: private slots:
void dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight); void dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight);

View File

@ -33,6 +33,8 @@ QString resolutionToString(const QSize & resolution)
/// Note that it is possible to specify enconding manually in settings.json /// Note that it is possible to specify enconding manually in settings.json
static const std::string knownEncodingsList[] = //TODO: remove hardcode static const std::string knownEncodingsList[] = //TODO: remove hardcode
{ {
// Asks vcmi to automatically detect encoding
"auto",
// European Windows-125X encodings // European Windows-125X encodings
"CP1250", // West European, covers mostly Slavic languages that use latin script "CP1250", // West European, covers mostly Slavic languages that use latin script
"CP1251", // Covers languages that use cyrillic scrypt "CP1251", // Covers languages that use cyrillic scrypt

View File

@ -526,6 +526,11 @@
</item> </item>
<item row="10" column="7" colspan="3"> <item row="10" column="7" colspan="3">
<widget class="QComboBox" name="comboBoxEncoding"> <widget class="QComboBox" name="comboBoxEncoding">
<item>
<property name="text">
<string>Automatic detection</string>
</property>
</item>
<item> <item>
<property name="text"> <property name="text">
<string>Central European (Windows 1250)</string> <string>Central European (Windows 1250)</string>

View File

@ -21,11 +21,6 @@ void CAndroidVMHelper::cacheVM(JNIEnv * env)
env->GetJavaVM(&vmCache); env->GetJavaVM(&vmCache);
} }
void CAndroidVMHelper::cacheVM(JavaVM * vm)
{
vmCache = vm;
}
CAndroidVMHelper::CAndroidVMHelper() CAndroidVMHelper::CAndroidVMHelper()
{ {
auto res = vmCache->GetEnv((void **) &envPtr, JNI_VERSION_1_1); auto res = vmCache->GetEnv((void **) &envPtr, JNI_VERSION_1_1);

View File

@ -42,8 +42,6 @@ public:
static void cacheVM(JNIEnv * env); static void cacheVM(JNIEnv * env);
static void cacheVM(JavaVM * vm);
static constexpr const char * NATIVE_METHODS_DEFAULT_CLASS = "eu/vcmi/vcmi/NativeMethods"; static constexpr const char * NATIVE_METHODS_DEFAULT_CLASS = "eu/vcmi/vcmi/NativeMethods";
}; };

View File

@ -824,7 +824,12 @@ bool CArtifactInstance::canBePutAt(const ArtifactLocation & al, bool assumeDestR
bool CArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactPosition slot, bool assumeDestRemoved) const bool CArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactPosition slot, bool assumeDestRemoved) const
{ {
if(slot >= GameConstants::BACKPACK_START) if(slot == ArtifactPosition::TRANSITION_POS)
{
return true;
}
if(ArtifactUtils::isSlotBackpack(slot))
{ {
if(artType->isBig()) if(artType->isBig())
return false; return false;
@ -851,7 +856,7 @@ void CArtifactInstance::putAt(ArtifactLocation al)
assert(canBePutAt(al)); assert(canBePutAt(al));
al.getHolderArtSet()->setNewArtSlot(al.slot, this, false); al.getHolderArtSet()->setNewArtSlot(al.slot, this, false);
if(!ArtifactUtils::isSlotBackpack(al.slot)) if(ArtifactUtils::isSlotEquipment(al.slot))
al.getHolderNode()->attachTo(*this); al.getHolderNode()->attachTo(*this);
} }
@ -859,7 +864,7 @@ void CArtifactInstance::removeFrom(ArtifactLocation al)
{ {
assert(al.getHolderArtSet()->getArt(al.slot) == this); assert(al.getHolderArtSet()->getArt(al.slot) == this);
al.getHolderArtSet()->eraseArtSlot(al.slot); al.getHolderArtSet()->eraseArtSlot(al.slot);
if(!ArtifactUtils::isSlotBackpack(al.slot)) if(ArtifactUtils::isSlotEquipment(al.slot))
al.getHolderNode()->detachFrom(*this); al.getHolderNode()->detachFrom(*this);
} }
@ -998,6 +1003,8 @@ bool CArtifactInstance::isPart(const CArtifactInstance *supposedPart) const
bool CCombinedArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactPosition slot, bool assumeDestRemoved) const bool CCombinedArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactPosition slot, bool assumeDestRemoved) const
{ {
if(slot == ArtifactPosition::TRANSITION_POS)
return true;
bool canMainArtifactBePlaced = CArtifactInstance::canBePutAt(artSet, slot, assumeDestRemoved); bool canMainArtifactBePlaced = CArtifactInstance::canBePutAt(artSet, slot, assumeDestRemoved);
if(!canMainArtifactBePlaced) if(!canMainArtifactBePlaced)
return false; //no is no... return false; //no is no...
@ -1070,7 +1077,11 @@ void CCombinedArtifactInstance::addAsConstituent(CArtifactInstance *art, Artifac
void CCombinedArtifactInstance::putAt(ArtifactLocation al) void CCombinedArtifactInstance::putAt(ArtifactLocation al)
{ {
if(ArtifactUtils::isSlotBackpack(al.slot)) if(al.slot == ArtifactPosition::TRANSITION_POS)
{
CArtifactInstance::putAt(al);
}
else if(ArtifactUtils::isSlotBackpack(al.slot))
{ {
CArtifactInstance::putAt(al); CArtifactInstance::putAt(al);
for(ConstituentInfo &ci : constituentsInfo) for(ConstituentInfo &ci : constituentsInfo)
@ -1108,7 +1119,7 @@ void CCombinedArtifactInstance::putAt(ArtifactLocation al)
void CCombinedArtifactInstance::removeFrom(ArtifactLocation al) void CCombinedArtifactInstance::removeFrom(ArtifactLocation al)
{ {
if(ArtifactUtils::isSlotBackpack(al.slot)) if(ArtifactUtils::isSlotBackpack(al.slot) || al.slot == ArtifactPosition::TRANSITION_POS)
{ {
CArtifactInstance::removeFrom(al); CArtifactInstance::removeFrom(al);
} }
@ -1329,6 +1340,12 @@ const CCombinedArtifactInstance *CArtifactSet::getAssemblyByConstituent(Artifact
const ArtSlotInfo * CArtifactSet::getSlot(ArtifactPosition pos) const const ArtSlotInfo * CArtifactSet::getSlot(ArtifactPosition pos) const
{ {
if(pos == ArtifactPosition::TRANSITION_POS)
{
// Always add to the end. Always take from the beginning.
assert(!artifactsTransitionPos.empty());
return &(*artifactsTransitionPos.begin());
}
if(vstd::contains(artifactsWorn, pos)) if(vstd::contains(artifactsWorn, pos))
return &artifactsWorn.at(pos); return &artifactsWorn.at(pos);
if(pos >= ArtifactPosition::AFTER_LAST ) if(pos >= ArtifactPosition::AFTER_LAST )
@ -1355,7 +1372,13 @@ ArtSlotInfo & CArtifactSet::retrieveNewArtSlot(ArtifactPosition slot)
{ {
assert(!vstd::contains(artifactsWorn, slot)); assert(!vstd::contains(artifactsWorn, slot));
if (!ArtifactUtils::isSlotBackpack(slot)) if(slot == ArtifactPosition::TRANSITION_POS)
{
// Always add to the end. Always take from the beginning.
artifactsTransitionPos.push_back(ArtSlotInfo());
return artifactsTransitionPos.back();
}
if(!ArtifactUtils::isSlotBackpack(slot))
return artifactsWorn[slot]; return artifactsWorn[slot];
ArtSlotInfo newSlot; ArtSlotInfo newSlot;
@ -1375,7 +1398,12 @@ void CArtifactSet::setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art,
void CArtifactSet::eraseArtSlot(ArtifactPosition slot) void CArtifactSet::eraseArtSlot(ArtifactPosition slot)
{ {
if(ArtifactUtils::isSlotBackpack(slot)) if(slot == ArtifactPosition::TRANSITION_POS)
{
assert(!artifactsTransitionPos.empty());
artifactsTransitionPos.erase(artifactsTransitionPos.begin());
}
else if(ArtifactUtils::isSlotBackpack(slot))
{ {
auto backpackSlot = ArtifactPosition(slot - GameConstants::BACKPACK_START); auto backpackSlot = ArtifactPosition(slot - GameConstants::BACKPACK_START);
@ -1612,4 +1640,9 @@ DLL_LINKAGE bool ArtifactUtils::isSlotBackpack(ArtifactPosition slot)
return slot >= GameConstants::BACKPACK_START; return slot >= GameConstants::BACKPACK_START;
} }
DLL_LINKAGE bool ArtifactUtils::isSlotEquipment(ArtifactPosition slot)
{
return slot < GameConstants::BACKPACK_START && slot >= 0;
}
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -317,6 +317,7 @@ class DLL_LINKAGE CArtifactSet
public: public:
std::vector<ArtSlotInfo> artifactsInBackpack; //hero's artifacts from bag std::vector<ArtSlotInfo> artifactsInBackpack; //hero's artifacts from bag
std::map<ArtifactPosition, ArtSlotInfo> artifactsWorn; //map<position,artifact_id>; positions: 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::map<ArtifactPosition, ArtSlotInfo> artifactsWorn; //map<position,artifact_id>; positions: 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<ArtSlotInfo> artifactsTransitionPos; // Used as transition position for dragAndDrop artifact exchange
ArtSlotInfo & retrieveNewArtSlot(ArtifactPosition slot); ArtSlotInfo & retrieveNewArtSlot(ArtifactPosition slot);
void setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art, bool locked); void setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art, bool locked);
@ -392,6 +393,7 @@ namespace ArtifactUtils
DLL_LINKAGE bool isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot); DLL_LINKAGE bool isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot);
DLL_LINKAGE bool checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, ArtifactID artID, ArtifactPosition slot); DLL_LINKAGE bool checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, ArtifactID artID, ArtifactPosition slot);
DLL_LINKAGE bool isSlotBackpack(ArtifactPosition slot); DLL_LINKAGE bool isSlotBackpack(ArtifactPosition slot);
DLL_LINKAGE bool isSlotEquipment(ArtifactPosition slot);
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -105,7 +105,84 @@ bool Unicode::isValidString(const char * data, size_t size)
static std::string getSelectedEncoding() static std::string getSelectedEncoding()
{ {
return settings["general"]["encoding"].String(); auto explicitSetting = settings["general"]["encoding"].String();
if (explicitSetting != "auto")
return explicitSetting;
return settings["session"]["encoding"].String();
}
/// Detects encoding of H3 text files based on matching against pregenerated footprints of H3 file
/// Can also detect language of H3 install, however right now this is not necessary
static void detectEncoding()
{
static const size_t knownCount = 6;
// "footprints" of data collected from known versions of H3
static const std::array<std::array<double, 16>, knownCount> knownFootprints =
{ {
{ { 0.0559, 0.0000, 0.1983, 0.0051, 0.0222, 0.0183, 0.4596, 0.2405, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000 } },
{ { 0.0493, 0.0000, 0.1926, 0.0047, 0.0230, 0.0121, 0.4133, 0.2780, 0.0002, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0259, 0.0008 } },
{ { 0.0534, 0.0000, 0.1705, 0.0047, 0.0418, 0.0208, 0.4775, 0.2191, 0.0001, 0.0000, 0.0000, 0.0000, 0.0000, 0.0005, 0.0036, 0.0080 } },
{ { 0.0534, 0.0000, 0.1701, 0.0067, 0.0157, 0.0133, 0.4328, 0.2540, 0.0001, 0.0043, 0.0000, 0.0244, 0.0000, 0.0000, 0.0181, 0.0071 } },
{ { 0.0548, 0.0000, 0.1744, 0.0061, 0.0031, 0.0009, 0.0046, 0.0136, 0.0000, 0.0004, 0.0000, 0.0000, 0.0227, 0.0061, 0.4882, 0.2252 } },
{ { 0.0559, 0.0000, 0.1807, 0.0059, 0.0036, 0.0013, 0.0046, 0.0134, 0.0000, 0.0004, 0.0000, 0.0487, 0.0209, 0.0060, 0.4615, 0.1972 } }
} };
// languages of known footprints
static const std::array<std::string, knownCount> knownLanguages =
{ {
"English", "French", "German", "Polish", "Russian", "Ukrainian"
} };
// encoding that should be used for known footprints
static const std::array<std::string, knownCount> knownEncodings =
{ {
"CP1252", "CP1252", "CP1252", "CP1250", "CP1251", "CP1251"
} };
// load file that will be used for footprint generation
// this is one of the most text-heavy files in game and consists solely from translated texts
auto resource = CResourceHandler::get()->load(ResourceID("DATA/GENRLTXT.TXT", EResType::TEXT));
std::array<size_t, 256> charCount;
std::array<double, 16> footprint;
std::array<double, knownCount> deviations;
boost::range::fill(charCount, 0);
boost::range::fill(footprint, 0.0);
boost::range::fill(deviations, 0.0);
auto data = resource->readAll();
// compute how often each character occurs in input file
for (si64 i = 0; i < data.second; ++i)
charCount[data.first[i]] += 1;
// and convert computed data into weights
// to reduce amount of data, group footprint data into 16-char blocks.
// While this will reduce precision, it should not affect output
// since we expect only tiny differences compared to reference footprints
for (size_t i = 0; i < 256; ++i)
footprint[i/16] += double(charCount[i]) / data.second;
logGlobal->debug("Language footprint: %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f",
footprint[0], footprint[1], footprint[2], footprint[3], footprint[4], footprint[5], footprint[6], footprint[7],
footprint[8], footprint[9], footprint[10], footprint[11], footprint[12], footprint[13], footprint[14], footprint[15]
);
for (size_t i = 0; i < deviations.size(); ++i)
{
for (size_t j = 0; j < footprint.size(); ++j)
deviations[i] += std::abs((footprint[j] - knownFootprints[i][j]));
}
size_t bestIndex = boost::range::min_element(deviations) - deviations.begin();
for (size_t i = 0; i < deviations.size(); ++i)
logGlobal->debug("Comparing to %s: %f", knownLanguages[i], deviations[i]);
Settings s = settings.write["session"]["encoding"];
s->String() = knownEncodings[bestIndex];
} }
std::string Unicode::toUnicode(const std::string &text) std::string Unicode::toUnicode(const std::string &text)
@ -362,6 +439,9 @@ CGeneralTextHandler::CGeneralTextHandler():
znpc00 (*this, "vcmi.znpc00" ), // technically - wog znpc00 (*this, "vcmi.znpc00" ), // technically - wog
qeModCommands (*this, "vcmi.quickExchange" ) qeModCommands (*this, "vcmi.quickExchange" )
{ {
if (getSelectedEncoding().empty())
detectEncoding();
readToVector("core.vcdesc", "DATA/VCDESC.TXT" ); readToVector("core.vcdesc", "DATA/VCDESC.TXT" );
readToVector("core.lcdesc", "DATA/LCDESC.TXT" ); readToVector("core.lcdesc", "DATA/LCDESC.TXT" );
readToVector("core.tcommand", "DATA/TCOMMAND.TXT" ); readToVector("core.tcommand", "DATA/TCOMMAND.TXT" );
@ -371,6 +451,8 @@ CGeneralTextHandler::CGeneralTextHandler():
readToVector("core.xtrainfo", "DATA/XTRAINFO.TXT" ); readToVector("core.xtrainfo", "DATA/XTRAINFO.TXT" );
readToVector("core.restypes", "DATA/RESTYPES.TXT" ); readToVector("core.restypes", "DATA/RESTYPES.TXT" );
readToVector("core.randsign", "DATA/RANDSIGN.TXT" ); readToVector("core.randsign", "DATA/RANDSIGN.TXT" );
readToVector("core.crgen1", "DATA/CRGEN1.TXT" );
readToVector("core.crgen4", "DATA/CRGEN4.TXT" );
readToVector("core.overview", "DATA/OVERVIEW.TXT" ); readToVector("core.overview", "DATA/OVERVIEW.TXT" );
readToVector("core.arraytxt", "DATA/ARRAYTXT.TXT" ); readToVector("core.arraytxt", "DATA/ARRAYTXT.TXT" );
readToVector("core.priskill", "DATA/PRISKILL.TXT" ); readToVector("core.priskill", "DATA/PRISKILL.TXT" );

View File

@ -1020,6 +1020,7 @@ class ArtifactPosition
public: public:
enum EArtifactPosition enum EArtifactPosition
{ {
TRANSITION_POS = -3,
FIRST_AVAILABLE = -2, FIRST_AVAILABLE = -2,
PRE_FIRST = -1, //sometimes used as error, sometimes as first free in backpack PRE_FIRST = -1, //sometimes used as error, sometimes as first free in backpack
HEAD, SHOULDERS, NECK, RIGHT_HAND, LEFT_HAND, TORSO, //5 HEAD, SHOULDERS, NECK, RIGHT_HAND, LEFT_HAND, TORSO, //5

View File

@ -112,18 +112,31 @@ std::unordered_map<ResourceID, bfs::path> CFilesystemLoader::listFiles(const std
std::vector<bfs::path> path; //vector holding relative path to our file std::vector<bfs::path> path; //vector holding relative path to our file
bfs::recursive_directory_iterator enddir; bfs::recursive_directory_iterator enddir;
#if BOOST_VERSION >= 107200 // 1.72
bfs::recursive_directory_iterator it(baseDirectory, bfs::directory_options::follow_directory_symlink);
#else
bfs::recursive_directory_iterator it(baseDirectory, bfs::symlink_option::recurse); bfs::recursive_directory_iterator it(baseDirectory, bfs::symlink_option::recurse);
#endif
for(; it != enddir; ++it) for(; it != enddir; ++it)
{ {
EResType::Type type; EResType::Type type;
#if BOOST_VERSION >= 107200
const auto currentDepth = it.depth();
#else
const auto currentDepth = it.level();
#endif
if (bfs::is_directory(it->status())) if (bfs::is_directory(it->status()))
{ {
path.resize(it.level() + 1); path.resize(currentDepth + 1);
path.back() = it->path().filename(); path.back() = it->path().filename();
// don't iterate into directory if depth limit reached // don't iterate into directory if depth limit reached
it.no_push(depth <= it.level()); #if BOOST_VERSION >= 107200
it.disable_recursion_pending(depth <= currentDepth);
#else
it.no_push(depth <= currentDepth);
#endif
type = EResType::DIRECTORY; type = EResType::DIRECTORY;
} }
@ -134,7 +147,7 @@ std::unordered_map<ResourceID, bfs::path> CFilesystemLoader::listFiles(const std
{ {
//reconstruct relative filename (not possible via boost AFAIK) //reconstruct relative filename (not possible via boost AFAIK)
bfs::path filename; bfs::path filename;
const size_t iterations = std::min((size_t)it.level(), path.size()); const size_t iterations = std::min(static_cast<size_t>(currentDepth), path.size());
if (iterations) if (iterations)
{ {
filename = path.front(); filename = path.front();

View File

@ -588,7 +588,7 @@ void CGSeerHut::initObj(CRandomGenerator & rand)
quest->progress = CQuest::NOT_ACTIVE; quest->progress = CQuest::NOT_ACTIVE;
if(quest->missionType) if(quest->missionType)
{ {
std::string questName = quest->missionName(CQuest::Emission(quest->missionType-1)); std::string questName = quest->missionName(quest->missionType);
if(!quest->isCustomFirst) if(!quest->isCustomFirst)
quest->firstVisitText = VLC->generaltexth->translate("core.seerhut.quest." + questName + "." + quest->missionState(0), quest->textOption); quest->firstVisitText = VLC->generaltexth->translate("core.seerhut.quest." + questName + "." + quest->missionState(0), quest->textOption);

View File

@ -1963,7 +1963,13 @@ void CGSirens::onHeroVisit( const CGHeroInstance * h ) const
for (auto i = h->Slots().begin(); i != h->Slots().end(); i++) for (auto i = h->Slots().begin(); i != h->Slots().end(); i++)
{ {
TQuantity drown = static_cast<TQuantity>(i->second->count * 0.3); // 1-sized stacks are not affected by sirens
if (i->second->count == 1)
continue;
// tested H3 behavior: 30% (rounded up) of stack drowns
TQuantity drown = std::ceil(i->second->count * 0.3);
if(drown) if(drown)
{ {
cb->changeStackCount(StackLocation(h, i->first), -drown); cb->changeStackCount(StackLocation(h, i->first), -drown);

BIN
mapeditor/icons/brush-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -869,6 +869,22 @@ void MainWindow::on_toolArea_clicked(bool checked)
ui->tabWidget->setCurrentIndex(0); ui->tabWidget->setCurrentIndex(0);
} }
void MainWindow::on_toolLasso_clicked(bool checked)
{
ui->toolBrush->setChecked(false);
ui->toolBrush2->setChecked(false);
ui->toolBrush4->setChecked(false);
ui->toolArea->setChecked(false);
//ui->toolLasso->setChecked(false);
if(checked)
ui->mapView->selectionTool = MapView::SelectionTool::Lasso;
else
ui->mapView->selectionTool = MapView::SelectionTool::None;
ui->tabWidget->setCurrentIndex(0);
}
void MainWindow::on_actionErase_triggered() void MainWindow::on_actionErase_triggered()
{ {
on_toolErase_clicked(); on_toolErase_clicked();

View File

@ -92,6 +92,8 @@ private slots:
void on_toolBrush2_clicked(bool checked); void on_toolBrush2_clicked(bool checked);
void on_toolBrush4_clicked(bool checked); void on_toolBrush4_clicked(bool checked);
void on_toolLasso_clicked(bool checked);
void on_inspectorWidget_itemChanged(QTableWidgetItem *item); void on_inspectorWidget_itemChanged(QTableWidgetItem *item);

View File

@ -642,7 +642,7 @@
<item row="2" column="0"> <item row="2" column="0">
<widget class="QPushButton" name="toolLasso"> <widget class="QPushButton" name="toolLasso">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>true</bool>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -663,7 +663,11 @@
</size> </size>
</property> </property>
<property name="text"> <property name="text">
<string notr="true">O</string> <string/>
</property>
<property name="icon">
<iconset>
<normaloff>icons:brush-3.png</normaloff>icons:brush-3.png</iconset>
</property> </property>
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>

View File

@ -155,6 +155,14 @@ void MapView::mouseMoveEvent(QMouseEvent *mouseEvent)
} }
sc->selectionTerrainView.draw(); sc->selectionTerrainView.draw();
break; break;
case MapView::SelectionTool::Lasso:
if(mouseEvent->buttons() == Qt::LeftButton)
{
sc->selectionTerrainView.select(tile);
sc->selectionTerrainView.draw();
}
break;
case MapView::SelectionTool::None: case MapView::SelectionTool::None:
if(mouseEvent->buttons() & Qt::RightButton) if(mouseEvent->buttons() & Qt::RightButton)
@ -247,6 +255,7 @@ void MapView::mousePressEvent(QMouseEvent *event)
break; break;
case MapView::SelectionTool::Area: case MapView::SelectionTool::Area:
case MapView::SelectionTool::Lasso:
if(event->button() == Qt::RightButton) if(event->button() == Qt::RightButton)
break; break;
@ -329,6 +338,55 @@ void MapView::mouseReleaseEvent(QMouseEvent *event)
switch(selectionTool) switch(selectionTool)
{ {
case MapView::SelectionTool::Lasso: {
if(event->button() == Qt::RightButton)
break;
//key: y position of tile
//value.first: x position of left tile
//value.second: x postiion of right tile
std::map<int, std::pair<int, int>> selectionRangeMapX, selectionRangeMapY;
for(auto & t : sc->selectionTerrainView.selection())
{
auto pairIter = selectionRangeMapX.find(t.y);
if(pairIter == selectionRangeMapX.end())
selectionRangeMapX[t.y] = std::make_pair(t.x, t.x);
else
{
pairIter->second.first = std::min(pairIter->second.first, t.x);
pairIter->second.second = std::max(pairIter->second.second, t.x);
}
pairIter = selectionRangeMapY.find(t.x);
if(pairIter == selectionRangeMapY.end())
selectionRangeMapY[t.x] = std::make_pair(t.y, t.y);
else
{
pairIter->second.first = std::min(pairIter->second.first, t.y);
pairIter->second.second = std::max(pairIter->second.second, t.y);
}
}
std::set<int3> selectionByX, selectionByY;
std::vector<int3> finalSelection;
for(auto & selectionRange : selectionRangeMapX)
{
for(int i = selectionRange.second.first; i < selectionRange.second.second; ++i)
selectionByX.insert(int3(i, selectionRange.first, sc->level));
}
for(auto & selectionRange : selectionRangeMapY)
{
for(int i = selectionRange.second.first; i < selectionRange.second.second; ++i)
selectionByY.insert(int3(selectionRange.first, i, sc->level));
}
std::set_intersection(selectionByX.begin(), selectionByX.end(), selectionByY.begin(), selectionByY.end(), std::back_inserter(finalSelection));
for(auto & lassoTile : finalSelection)
sc->selectionTerrainView.select(lassoTile);
sc->selectionTerrainView.draw();
break;
}
case MapView::SelectionTool::None: case MapView::SelectionTool::None:
if(event->button() == Qt::RightButton) if(event->button() == Qt::RightButton)
break; break;

View File

@ -33,36 +33,32 @@ void ResourceConverter::convertExtractedResourceFiles(ConversionOptions conversi
void ResourceConverter::doConvertPcxToPng(bool deleteOriginals) void ResourceConverter::doConvertPcxToPng(bool deleteOriginals)
{ {
std::string filename;
bfs::path imagesPath = VCMIDirs::get().userExtractedPath() / "IMAGES"; bfs::path imagesPath = VCMIDirs::get().userExtractedPath() / "IMAGES";
bfs::directory_iterator end_iter; bfs::directory_iterator end_iter;
for(bfs::directory_iterator dir_itr(imagesPath); dir_itr != end_iter; ++dir_itr) for(bfs::directory_iterator dir_itr(imagesPath); dir_itr != end_iter; ++dir_itr)
{ {
const auto filename = dir_itr->path().filename();
try try
{ {
if (!bfs::is_regular_file(dir_itr->status())) if (!bfs::is_regular_file(dir_itr->status()))
return; return;
std::string filePath = dir_itr->path().string(); std::string filenameLowerCase = boost::algorithm::to_lower_copy(filename.string());
std::string fileStem = dir_itr->path().stem().string();
filename = dir_itr->path().filename().string();
std::string filenameLowerCase = boost::algorithm::to_lower_copy(filename);
if(bfs::extension(filenameLowerCase) == ".pcx") if(boost::algorithm::to_lower_copy(filename.extension().string()) == ".pcx")
{ {
auto img = BitmapHandler::loadBitmap(filenameLowerCase); auto img = BitmapHandler::loadBitmap(filenameLowerCase);
bfs::path pngFilePath = imagesPath / (fileStem + ".png"); bfs::path pngFilePath = imagesPath / (dir_itr->path().stem().string() + ".png");
img.save(pathToQString(pngFilePath), "PNG"); img.save(pathToQString(pngFilePath), "PNG");
if(deleteOriginals) if(deleteOriginals)
bfs::remove(filePath); bfs::remove(dir_itr->path());
} }
} }
catch(const std::exception & ex) catch(const std::exception & ex)
{ {
logGlobal->info(filename + " " + ex.what() + "\n"); logGlobal->info(filename.string() + " " + ex.what() + "\n");
} }
} }
} }

View File

@ -3893,7 +3893,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat
// Check if src/dest slots are appropriate for the artifacts exchanged. // Check if src/dest slots are appropriate for the artifacts exchanged.
// Moving to the backpack is always allowed. // Moving to the backpack is always allowed.
if ((!srcArtifact || dst.slot < GameConstants::BACKPACK_START) if ((!srcArtifact || !ArtifactUtils::isSlotBackpack(dst.slot))
&& srcArtifact && !srcArtifact->canBePutAt(dst, true)) && srcArtifact && !srcArtifact->canBePutAt(dst, true))
COMPLAIN_RET("Cannot move artifact!"); COMPLAIN_RET("Cannot move artifact!");
@ -3908,24 +3908,37 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat
if (src.slot == ArtifactPosition::MACH4 || dst.slot == ArtifactPosition::MACH4) if (src.slot == ArtifactPosition::MACH4 || dst.slot == ArtifactPosition::MACH4)
COMPLAIN_RET("Cannot move catapult!"); COMPLAIN_RET("Cannot move catapult!");
if (dst.slot >= GameConstants::BACKPACK_START) if(ArtifactUtils::isSlotBackpack(dst.slot))
vstd::amin(dst.slot, ArtifactPosition(GameConstants::BACKPACK_START + (si32)dst.getHolderArtSet()->artifactsInBackpack.size())); vstd::amin(dst.slot, ArtifactPosition(GameConstants::BACKPACK_START + (si32)dst.getHolderArtSet()->artifactsInBackpack.size()));
if (src.slot == dst.slot && src.artHolder == dst.artHolder) if(!(src.slot == ArtifactPosition::TRANSITION_POS && dst.slot == ArtifactPosition::TRANSITION_POS))
COMPLAIN_RET("Won't move artifact: Dest same as source!");
if (dst.slot < GameConstants::BACKPACK_START && destArtifact) //moving art to another slot
{ {
//old artifact must be removed first if(src.slot == dst.slot && src.artHolder == dst.artHolder)
moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition( COMPLAIN_RET("Won't move artifact: Dest same as source!");
(si32)dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START)));
}
auto hero = boost::get<ConstTransitivePtr<CGHeroInstance>>(dst.artHolder);
if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot))
giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
MoveArtifact ma(&src, &dst); // Check if dst slot is occupied
sendAndApply(&ma); if(!ArtifactUtils::isSlotBackpack(dst.slot) && destArtifact)
{
// Previous artifact must be removed first
moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition::TRANSITION_POS));
}
try
{
auto hero = boost::get<ConstTransitivePtr<CGHeroInstance>>(dst.artHolder);
if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot))
giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
}
catch (boost::bad_get const &)
{
// object other than hero received an art - ignore
}
MoveArtifact ma(&src, &dst);
if(dst.slot == ArtifactPosition::TRANSITION_POS)
ma.askAssemble = false;
sendAndApply(&ma);
}
return true; return true;
} }

View File

@ -201,6 +201,10 @@ void CVCMIServer::run()
void CVCMIServer::establishRemoteConnections() void CVCMIServer::establishRemoteConnections()
{ {
//wait for host connection
while(connections.empty())
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
uuid = cmdLineOptions["lobby-uuid"].as<std::string>(); uuid = cmdLineOptions["lobby-uuid"].as<std::string>();
int numOfConnections = cmdLineOptions["connections"].as<ui16>(); int numOfConnections = cmdLineOptions["connections"].as<ui16>();
auto address = cmdLineOptions["lobby"].as<std::string>(); auto address = cmdLineOptions["lobby"].as<std::string>();