diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 45a26740e..2c3114e25 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -23,12 +23,22 @@ jobs: os: ubuntu-20.04 test: 0 preset: linux-gcc-release - - platform: mac - os: macos-10.15 + - platform: mac-intel + os: macos-12 test: 0 pack: 1 extension: dmg - preset: macos-ninja-release + preset: macos-conan-ninja-release + conan_profile: macos-intel + artifact_platform: intel + - platform: mac-arm + os: macos-12 + test: 0 + pack: 1 + extension: dmg + preset: macos-arm-conan-ninja-release + conan_profile: macos-arm + artifact_platform: arm - platform: mxe os: ubuntu-20.04 mxe: i686-w64-mingw32.shared @@ -59,6 +69,18 @@ jobs: MXE_TARGET: ${{ matrix.mxe }} VCMI_BUILD_PLATFORM: x64 + - name: Conan setup + if: "${{ matrix.conan_profile != '' }}" + run: | + pip3 install conan + conan profile new default --detect + conan install . \ + --install-folder=conan-generated \ + --no-imports \ + --build=never \ + --profile:build=default \ + --profile:host=CI/conan/${{ matrix.conan_profile }} + - name: Git branch name id: git-branch-name uses: EthanSK/git-branch-name-action@v1 @@ -66,6 +88,9 @@ jobs: - name: Build Number run: | source '${{github.workspace}}/CI/get_package_name.sh' + if [ '${{ matrix.artifact_platform }}' ]; then + VCMI_PACKAGE_FILE_NAME+="-${{ matrix.artifact_platform }}" + fi echo VCMI_PACKAGE_FILE_NAME="$VCMI_PACKAGE_FILE_NAME" >> $GITHUB_ENV echo VCMI_PACKAGE_NAME_SUFFIX="$VCMI_PACKAGE_NAME_SUFFIX" >> $GITHUB_ENV env: diff --git a/.gitignore b/.gitignore index 37b8cb4e1..fb947fc5c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /server/vcmiserver /launcher/vcmilauncher /launcher/vcmilauncher_automoc.cpp +/conan-generated build/ .cache/* diff --git a/CI/conan/macos-arm b/CI/conan/macos-arm new file mode 100644 index 000000000..4e58922ba --- /dev/null +++ b/CI/conan/macos-arm @@ -0,0 +1,11 @@ +[settings] +os=Macos +os.version=11.0 +arch=armv8 +compiler=apple-clang +compiler.version=13 +compiler.libcxx=libc++ +build_type=Release +[options] +[build_requires] +[env] diff --git a/CI/conan/macos-intel b/CI/conan/macos-intel new file mode 100644 index 000000000..f1717b4aa --- /dev/null +++ b/CI/conan/macos-intel @@ -0,0 +1,11 @@ +[settings] +os=Macos +os.version=10.13 +arch=x86_64 +compiler=apple-clang +compiler.version=13 +compiler.libcxx=libc++ +build_type=Release +[options] +[build_requires] +[env] diff --git a/CI/mac-arm/before_install.sh b/CI/mac-arm/before_install.sh new file mode 100755 index 000000000..c90b6a1b1 --- /dev/null +++ b/CI/mac-arm/before_install.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +DEPS_FILENAME=intel-cross-arm +. CI/mac/before_install.sh diff --git a/CI/mac-intel/before_install.sh b/CI/mac-intel/before_install.sh new file mode 100755 index 000000000..fcbcea328 --- /dev/null +++ b/CI/mac-intel/before_install.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +DEPS_FILENAME=intel +. CI/mac/before_install.sh diff --git a/CI/mac/before_install.sh b/CI/mac/before_install.sh old mode 100644 new mode 100755 index 209d3657c..62ab43c42 --- a/CI/mac/before_install.sh +++ b/CI/mac/before_install.sh @@ -1,8 +1,9 @@ -#!/bin/sh +#!/usr/bin/env bash -brew update -#brew pin python@3.9 -brew install smpeg2 libpng freetype qt5 ffmpeg ninja boost tbb luajit -brew install sdl2 sdl2_ttf sdl2_image sdl2_mixer +echo DEVELOPER_DIR=/Applications/Xcode_13.4.1.app >> $GITHUB_ENV -echo CMAKE_PREFIX_PATH="$(brew --prefix)/opt/qt5:$CMAKE_PREFIX_PATH" >> $GITHUB_ENV +brew install ninja + +mkdir ~/.conan ; cd ~/.conan +curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/latest/download/$DEPS_FILENAME.txz" \ + | tar -xf - diff --git a/CMakeLists.txt b/CMakeLists.txt index a961da04c..a8930ae30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,9 +9,6 @@ project(VCMI) # - There is problem with running fixup_bundle in main project after subdirectories. # Cmake put them after all install code of main CMakelists in cmake_install.cmake # Currently I just added extra add_subdirectory and CMakeLists.txt in osx directory to bypass that. -# - Try to fix build with RPATH. -# Currently if CMAKE_MACOSX_RPATH=1 then AI libs unable to find @rpath/libvcmi.dylib -# I tried to set few different INSTALL_RPATH for all targets in AI directory, but nothing worked. # # MXE: # - Try to implement MXE support into BundleUtilities so we can deploy deps automatically @@ -91,6 +88,7 @@ define_property( # Generate Version.cpp if(ENABLE_GITVERSION) add_custom_target(update_version ALL + BYPRODUCTS "Version.cpp" COMMAND ${CMAKE_COMMAND} -DGIT_SHA1="${GIT_SHA1}" -P "${PROJECT_SOURCE_DIR}/cmake_modules/Version.cmake" ) else() @@ -131,10 +129,6 @@ set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL MinSizeRel Release RelWithDebInfo "") # Release falls back to RelWithDebInfo, then MinSizeRel set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE Release RelWithDebInfo MinSizeRel "") -if(APPLE) - set(CMAKE_MACOSX_RPATH 0) -endif(APPLE) - if(MINGW OR MSVC) # Windows Vista or newer for FuzzyLite 6 to compile add_definitions(-D_WIN32_WINNT=0x0600) @@ -226,7 +220,13 @@ endif() ############################################ find_package(Boost 1.48.0 REQUIRED COMPONENTS date_time filesystem locale program_options system thread) + find_package(ZLIB REQUIRED) +# Conan compatibility +if(TARGET zlib::zlib) + add_library(ZLIB::ZLIB ALIAS zlib::zlib) +endif() + find_package(ffmpeg REQUIRED COMPONENTS avutil swscale avformat avcodec) option(FORCE_BUNDLED_MINIZIP "Force bundled Minizip library" OFF) if(NOT FORCE_BUNDLED_MINIZIP) @@ -291,6 +291,7 @@ elseif(APPLE) # includes lib path which determines where to install shared libraries (either /lib or /lib64) include(GNUInstallDirs) + set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks") if(ENABLE_MONOLITHIC_INSTALL) set(BIN_DIR "." CACHE STRING "Where to install binaries") set(LIB_DIR "." CACHE STRING "Where to install main library") @@ -302,7 +303,7 @@ elseif(APPLE) set(APP_BUNDLE_RESOURCES_DIR "${APP_BUNDLE_CONTENTS_DIR}/Resources") set(BIN_DIR "${APP_BUNDLE_BINARY_DIR}" CACHE STRING "Where to install binaries") - set(LIB_DIR "${APP_BUNDLE_BINARY_DIR}" CACHE STRING "Where to install main library") + set(LIB_DIR "${APP_BUNDLE_CONTENTS_DIR}/Frameworks" CACHE STRING "Where to install main library") set(DATA_DIR "${APP_BUNDLE_RESOURCES_DIR}/Data" CACHE STRING "Where to install data files") endif() else() diff --git a/CMakePresets.json b/CMakePresets.json index 8118819a9..e1735cbfd 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -11,8 +11,8 @@ "PACKAGE_FILE_NAME" : "$env{VCMI_PACKAGE_FILE_NAME}", "PACKAGE_NAME_SUFFIX" : "$env{VCMI_PACKAGE_NAME_SUFFIX}", "CMAKE_BUILD_TYPE": "RelWithDebInfo", - "FORCE_BUNDLED_FL" : "0", - "ENABLE_TEST": "0" + "FORCE_BUNDLED_FL": "OFF", + "ENABLE_TEST": "OFF" } }, { @@ -65,6 +65,26 @@ "description": "VCMI MacOS Ninja", "inherits": "default-release" }, + { + "name": "macos-conan-ninja-release", + "displayName": "Ninja+Conan release", + "description": "VCMI MacOS Ninja using Conan", + "inherits": "default-release", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/conan-generated/conan_toolchain.cmake", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "macos-arm-conan-ninja-release", + "displayName": "Ninja+Conan arm64 release", + "description": "VCMI MacOS-arm64 Ninja using Conan", + "inherits": "macos-conan-ninja-release", + "cacheVariables": { + "ENABLE_ERM": "OFF", + "ENABLE_LUA": "OFF" + } + }, { "name": "macos-xcode-release", "displayName": "XCode release", @@ -99,6 +119,16 @@ "configurePreset": "macos-ninja-release", "inherits": "default-release" }, + { + "name": "macos-conan-ninja-release", + "configurePreset": "macos-conan-ninja-release", + "inherits": "default-release" + }, + { + "name": "macos-arm-conan-ninja-release", + "configurePreset": "macos-arm-conan-ninja-release", + "inherits": "default-release" + }, { "name": "windows-msvc-release", "configurePreset": "windows-msvc-release", diff --git a/client/CMT.cpp b/client/CMT.cpp index 41e8b7396..00d7615f5 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -10,7 +10,6 @@ // CMT.cpp : Defines the entry point for the console application. // #include "StdInc.h" -#include #include @@ -160,7 +159,7 @@ static void SDLLogCallback(void* userdata, #if defined(VCMI_WINDOWS) && !defined(__GNUC__) && defined(VCMI_WITH_DEBUG_CONSOLE) int wmain(int argc, wchar_t* argv[]) -#elif defined(VCMI_APPLE) || defined(VCMI_ANDROID) +#elif defined(VCMI_ANDROID) int SDL_main(int argc, char *argv[]) #else int main(int argc, char * argv[]) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index d8cd9dbc6..7304518b4 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -142,7 +142,6 @@ set(client_HEADERS Graphics.h mapHandler.h resource.h - SDLMain.h SDLRWwrapper.h ) @@ -152,9 +151,7 @@ if(ANDROID) # android needs client/server to be libraries, not executables, so w return() endif() -if(APPLE) - set(client_SRCS ${client_SRCS} SDLMain.m) -elseif(WIN32) +if(WIN32) set(client_ICON "VCMI_client.rc") endif() @@ -175,11 +172,12 @@ if(WIN32) if(NOT ENABLE_DEBUG_CONSOLE) target_link_libraries(vcmiclient SDL2::SDL2main) endif() + target_compile_definitions(vcmiclient PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH) endif() target_link_libraries(vcmiclient PRIVATE vcmi SDL2::SDL2 SDL2::Image SDL2::Mixer SDL2::TTF - ffmpeg::swscale ffmpeg::avutil ffmpeg::avcodec ffmpeg::avformat TBB::tbb + ffmpeg::swscale ffmpeg::avutil ffmpeg::avcodec ffmpeg::avformat ) target_include_directories(vcmiclient diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 1987a9708..894b76124 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -272,13 +272,14 @@ void CSoundHandler::soundFinishedCallback(int channel) { std::map >::iterator iter; iter = callbacks.find(channel); + if (iter == callbacks.end()) + return; - assert(iter != callbacks.end()); - - if (iter->second) - iter->second(); - + auto callback = std::move(iter->second); callbacks.erase(iter); + + if (callback) + callback(); } int CSoundHandler::ambientGetRange() const diff --git a/client/SDLMain.h b/client/SDLMain.h deleted file mode 100644 index c56d90cbe..000000000 --- a/client/SDLMain.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SDLMain.m - main entry point for our Cocoa-ized SDL app - Initial Version: Darrell Walisser - Non-NIB-Code & other changes: Max Horn - - Feel free to customize this file to suit your needs -*/ - -#ifndef _SDLMain_h_ -#define _SDLMain_h_ - -#import - -@interface SDLMain : NSObject -@end - -#endif /* _SDLMain_h_ */ diff --git a/client/SDLMain.m b/client/SDLMain.m deleted file mode 100644 index b065a2009..000000000 --- a/client/SDLMain.m +++ /dev/null @@ -1,383 +0,0 @@ -/* SDLMain.m - main entry point for our Cocoa-ized SDL app - Initial Version: Darrell Walisser - Non-NIB-Code & other changes: Max Horn - - Feel free to customize this file to suit your needs -*/ - -#include "SDL.h" -#include "SDLMain.h" -#include /* for MAXPATHLEN */ -#include - -/* For some reaon, Apple removed setAppleMenu from the headers in 10.4, - but the method still is there and works. To avoid warnings, we declare - it ourselves here. */ -@interface NSApplication(SDL_Missing_Methods) -- (void)setAppleMenu:(NSMenu *)menu; -@end - -/* Use this flag to determine whether we use SDLMain.nib or not */ -#define SDL_USE_NIB_FILE 0 - -/* Use this flag to determine whether we use CPS (docking) or not */ -#define SDL_USE_CPS 1 -#ifdef SDL_USE_CPS -/* Portions of CPS.h */ -typedef struct CPSProcessSerNum -{ - UInt32 lo; - UInt32 hi; -} CPSProcessSerNum; - -extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); -extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); -extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); - -#endif /* SDL_USE_CPS */ - -static int gArgc; -static char **gArgv; -static BOOL gFinderLaunch; -static BOOL gCalledAppMainline = FALSE; - -static NSString *getApplicationName(void) -{ - const NSDictionary *dict; - NSString *appName = 0; - - /* Determine the application name */ - dict = (const NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); - if (dict) - appName = [dict objectForKey: @"CFBundleName"]; - - if (![appName length]) - appName = [[NSProcessInfo processInfo] processName]; - - return appName; -} - -#if SDL_USE_NIB_FILE -/* A helper category for NSString */ -@interface NSString (ReplaceSubString) -- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; -@end -#endif - -@interface SDLApplication : NSApplication -@end - -@implementation SDLApplication -/* Invoked from the Quit menu item */ -- (void)terminate:(id)sender -{ - /* Post a SDL_QUIT event */ - SDL_Event event; - event.type = SDL_QUIT; - SDL_PushEvent(&event); -} -@end - -/* The main class of the application, the application's delegate */ -@implementation SDLMain - -/* Set the working directory to the .app's parent directory */ -- (void) setupWorkingDirectory:(BOOL)shouldChdir -{ - if (shouldChdir) - { - char parentdir[MAXPATHLEN]; - CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); - CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); - if (CFURLGetFileSystemRepresentation(url2, 1, (UInt8 *)parentdir, MAXPATHLEN)) { - chdir(parentdir); /* chdir to the binary app's parent */ - } - CFRelease(url); - CFRelease(url2); - } -} - -#if SDL_USE_NIB_FILE - -/* Fix menu to contain the real app name instead of "SDL App" */ -- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName -{ - NSRange aRange; - NSEnumerator *enumerator; - NSMenuItem *menuItem; - - aRange = [[aMenu title] rangeOfString:@"SDL App"]; - if (aRange.length != 0) - [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; - - enumerator = [[aMenu itemArray] objectEnumerator]; - while ((menuItem = [enumerator nextObject])) - { - aRange = [[menuItem title] rangeOfString:@"SDL App"]; - if (aRange.length != 0) - [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; - if ([menuItem hasSubmenu]) - [self fixMenu:[menuItem submenu] withAppName:appName]; - } - [ aMenu sizeToFit ]; -} - -#else - -static void setApplicationMenu(void) -{ - /* warning: this code is very odd */ - NSMenu *appleMenu; - NSMenuItem *menuItem; - NSString *title; - NSString *appName; - - appName = getApplicationName(); - appleMenu = [[NSMenu alloc] initWithTitle:@""]; - - /* Add menu items */ - title = [@"About " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; - - [appleMenu addItem:[NSMenuItem separatorItem]]; - - title = [@"Hide " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; - - menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; - [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; - - [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; - - [appleMenu addItem:[NSMenuItem separatorItem]]; - - title = [@"Quit " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; - - - /* Put menu into the menubar */ - menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; - [menuItem setSubmenu:appleMenu]; - [[NSApp mainMenu] addItem:menuItem]; - - /* Tell the application object that this is now the application menu */ - [NSApp setAppleMenu:appleMenu]; - - /* Finally give up our references to the objects */ - [appleMenu release]; - [menuItem release]; -} - -/* Create a window menu */ -static void setupWindowMenu(void) -{ - NSMenu *windowMenu; - NSMenuItem *windowMenuItem; - NSMenuItem *menuItem; - - windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; - - /* "Minimize" item */ - menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; - [windowMenu addItem:menuItem]; - [menuItem release]; - - /* Put menu into the menubar */ - windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; - [windowMenuItem setSubmenu:windowMenu]; - [[NSApp mainMenu] addItem:windowMenuItem]; - - /* Tell the application object that this is now the window menu */ - [NSApp setWindowsMenu:windowMenu]; - - /* Finally give up our references to the objects */ - [windowMenu release]; - [windowMenuItem release]; -} - -/* Replacement for NSApplicationMain */ -static void CustomApplicationMain (int argc, char **argv) -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - SDLMain *sdlMain; - - /* Ensure the application object is initialised */ - [SDLApplication sharedApplication]; - -#ifdef SDL_USE_CPS - { - CPSProcessSerNum PSN; - /* Tell the dock about us */ - if (!CPSGetCurrentProcess(&PSN)) - if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) - if (!CPSSetFrontProcess(&PSN)) - [SDLApplication sharedApplication]; - } -#endif /* SDL_USE_CPS */ - - /* Set up the menubar */ - [NSApp setMainMenu:[[NSMenu alloc] init]]; - setApplicationMenu(); - setupWindowMenu(); - - /* Create SDLMain and make it the app delegate */ - sdlMain = [[SDLMain alloc] init]; - [NSApp setDelegate:sdlMain]; - - /* Start the main event loop */ - [NSApp run]; - - [sdlMain release]; - [pool release]; -} - -#endif - - -/* - * Catch document open requests...this lets us notice files when the app - * was launched by double-clicking a document, or when a document was - * dragged/dropped on the app's icon. You need to have a - * CFBundleDocumentsType section in your Info.plist to get this message, - * apparently. - * - * Files are added to gArgv, so to the app, they'll look like command line - * arguments. Previously, apps launched from the finder had nothing but - * an argv[0]. - * - * This message may be received multiple times to open several docs on launch. - * - * This message is ignored once the app's mainline has been called. - */ -- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename -{ - const char *temparg; - size_t arglen; - char *arg; - char **newargv; - - if (!gFinderLaunch) /* MacOS is passing command line args. */ - return FALSE; - - if (gCalledAppMainline) /* app has started, ignore this document. */ - return FALSE; - - temparg = [filename UTF8String]; - arglen = SDL_strlen(temparg) + 1; - arg = (char *) SDL_malloc(arglen); - if (arg == NULL) - return FALSE; - - newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); - if (newargv == NULL) - { - SDL_free(arg); - return FALSE; - } - gArgv = newargv; - - SDL_strlcpy(arg, temparg, arglen); - gArgv[gArgc++] = arg; - gArgv[gArgc] = NULL; - return TRUE; -} - - -/* Called when the internal event loop has just started running */ -- (void) applicationDidFinishLaunching: (NSNotification *) note -{ - int status; - - /* Set the working directory to the .app's parent directory */ - [self setupWorkingDirectory:gFinderLaunch]; - -#if SDL_USE_NIB_FILE - /* Set the main menu to contain the real app name instead of "SDL App" */ - [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; -#endif - - /* Hand off to main application code */ - gCalledAppMainline = TRUE; - status = SDL_main (gArgc, gArgv); - - /* We're done, thank you for playing */ - exit(status); -} -@end - - -@implementation NSString (ReplaceSubString) - -- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString -{ - unsigned int bufferSize; - unsigned int selfLen = [self length]; - unsigned int aStringLen = [aString length]; - unichar *buffer; - NSRange localRange; - NSString *result; - - bufferSize = selfLen + aStringLen - aRange.length; - buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar)); - - /* Get first part into buffer */ - localRange.location = 0; - localRange.length = aRange.location; - [self getCharacters:buffer range:localRange]; - - /* Get middle part into buffer */ - localRange.location = 0; - localRange.length = aStringLen; - [aString getCharacters:(buffer+aRange.location) range:localRange]; - - /* Get last part into buffer */ - localRange.location = aRange.location + aRange.length; - localRange.length = selfLen - localRange.location; - [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; - - /* Build output string */ - result = [NSString stringWithCharacters:buffer length:bufferSize]; - - NSDeallocateMemoryPages(buffer, bufferSize); - - return result; -} - -@end - - - -#ifdef main -# undef main -#endif - - -/* Main entry point to executable - should *not* be SDL_main! */ -int main (int argc, char **argv) -{ - /* Copy the arguments into a global variable */ - /* This is passed if we are launched by double-clicking */ - if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { - gArgv = (char **) SDL_malloc(sizeof (char *) * 2); - gArgv[0] = argv[0]; - gArgv[1] = NULL; - gArgc = 1; - gFinderLaunch = YES; - } else { - int i; - gArgc = argc; - gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); - for (i = 0; i <= argc; i++) - gArgv[i] = argv[i]; - gFinderLaunch = NO; - } - -#if SDL_USE_NIB_FILE - [SDLApplication poseAsClass:[NSApplication class]]; - NSApplicationMain (argc, argv); -#else - CustomApplicationMain (argc, argv); -#endif - return 0; -} - diff --git a/client/battle/CBattleAnimations.cpp b/client/battle/CBattleAnimations.cpp index 3ca1b7935..2a7000340 100644 --- a/client/battle/CBattleAnimations.cpp +++ b/client/battle/CBattleAnimations.cpp @@ -420,22 +420,6 @@ void CMeleeAttackAnimation::endAnim() delete this; } -bool CMovementAnimation::shouldRotate() -{ - Point begPosition = CClickableHex::getXYUnitAnim(oldPos, stack, owner); - Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack, owner); - - if((begPosition.x > endPosition.x) && owner->creDir[stack->ID] == true) - { - return true; - } - else if ((begPosition.x < endPosition.x) && owner->creDir[stack->ID] == false) - { - return true; - } - return false; -} - bool CMovementAnimation::init() { if( !isEarliest(false) ) @@ -456,7 +440,7 @@ bool CMovementAnimation::init() } //reverse unit if necessary - if(shouldRotate()) + if(owner->shouldRotate(stack, oldPos, nextHex)) { // it seems that H3 does NOT plays full rotation animation here in most situations // Logical since it takes quite a lot of time diff --git a/client/battle/CBattleAnimations.h b/client/battle/CBattleAnimations.h index c6cb6c849..93422e6d7 100644 --- a/client/battle/CBattleAnimations.h +++ b/client/battle/CBattleAnimations.h @@ -118,8 +118,6 @@ public: class CMovementAnimation : public CBattleStackAnimation { private: - bool shouldRotate(); - std::vector destTiles; //full path, includes already passed hexes ui32 curentMoveIndex; // index of nextHex in destTiles diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 6051a3679..dcabbb393 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -1561,6 +1561,19 @@ CPlayerInterface *CBattleInterface::getCurrentPlayerInterface() const return curInt.get(); } +bool CBattleInterface::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) +{ + Point begPosition = CClickableHex::getXYUnitAnim(oldPos,stack, this); + Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack, this); + + if((begPosition.x > endPosition.x) && creDir[stack->ID]) + return true; + else if((begPosition.x < endPosition.x) && !creDir[stack->ID]) + return true; + + return false; +} + void CBattleInterface::setActiveStack(const CStack *stack) { if (activeStack) // update UI @@ -1928,6 +1941,9 @@ void CBattleInterface::startAction(const BattleAction* action) { pendingAnims.push_back(std::make_pair(new CMovementStartAnimation(this, stack), false)); } + + if(shouldRotate(stack, stack->getPosition(), actionTarget.at(0).hexValue)) + pendingAnims.push_back(std::make_pair(new CReverseAnimation(this, stack, stack->getPosition(), true), false)); } redraw(); // redraw after deactivation, including proper handling of hovered hexes diff --git a/client/battle/CBattleInterface.h b/client/battle/CBattleInterface.h index a6b933c2b..ed5eb1dc7 100644 --- a/client/battle/CBattleInterface.h +++ b/client/battle/CBattleInterface.h @@ -294,6 +294,7 @@ public: void setAnimSpeed(int set); //speed of animation; range 1..100 int getAnimSpeed() const; //speed of animation; range 1..100 CPlayerInterface *getCurrentPlayerInterface() const; + bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex); std::vector> bfield; //11 lines, 17 hexes on each SDL_Surface *cellBorder, *cellShade; diff --git a/client/gui/CAnimation.cpp b/client/gui/CAnimation.cpp index 803b1811d..3828056fe 100644 --- a/client/gui/CAnimation.cpp +++ b/client/gui/CAnimation.cpp @@ -10,8 +10,6 @@ #include "StdInc.h" #include "CAnimation.h" -#include - #include "../CBitmapHandler.h" #include "../Graphics.h" #include "../gui/SDL_Extensions.h" diff --git a/client/gui/SDL_Extensions.cpp b/client/gui/SDL_Extensions.cpp index 52c33c2c9..e41b09f0c 100644 --- a/client/gui/SDL_Extensions.cpp +++ b/client/gui/SDL_Extensions.cpp @@ -16,6 +16,10 @@ #include "../Graphics.h" #include "../CMT.h" +#ifdef VCMI_APPLE +#include +#endif + const SDL_Color Colors::YELLOW = { 229, 215, 123, 0 }; const SDL_Color Colors::WHITE = { 255, 243, 222, 0 }; const SDL_Color Colors::METALLIC_GOLD = { 173, 142, 66, 0 }; @@ -786,19 +790,35 @@ SDL_Color CSDL_Ext::makeColor(ui8 r, ui8 g, ui8 b, ui8 a) void CSDL_Ext::startTextInput(SDL_Rect * where) { +#ifdef VCMI_APPLE + dispatch_async(dispatch_get_main_queue(), ^{ +#endif + if (SDL_IsTextInputActive() == SDL_FALSE) { SDL_StartTextInput(); } SDL_SetTextInputRect(where); + +#ifdef VCMI_APPLE + }); +#endif } void CSDL_Ext::stopTextInput() { +#ifdef VCMI_APPLE + dispatch_async(dispatch_get_main_queue(), ^{ +#endif + if (SDL_IsTextInputActive() == SDL_TRUE) { SDL_StopTextInput(); } + +#ifdef VCMI_APPLE + }); +#endif } STRONG_INLINE static uint32_t mapColor(SDL_Surface * surface, SDL_Color color) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 78b62736b..f369c04af 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -360,8 +360,10 @@ void CBonusSelection::updateAfterStateChange() buttonStart->disable(); buttonRestart->enable(); buttonBack->block(false); - buttonDifficultyLeft->disable(); - buttonDifficultyRight->disable(); + if(buttonDifficultyLeft) + buttonDifficultyLeft->disable(); + if(buttonDifficultyRight) + buttonDifficultyRight->disable(); } if(CSH->campaignBonus == -1) { diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index 6eca3d0dd..22a26e244 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -131,25 +131,19 @@ void CLobbyScreen::startScenario(bool allowOnlyAI) CSH->sendStartGame(allowOnlyAI); buttonStart->block(true); } - catch(ExceptionMapMissing & e) + catch(std::exception & e) { - (void)e; // unused - } - catch(ExceptionNoHuman & e) - { - (void)e; // unused - // You must position yourself prior to starting the game. - CInfoWindow::showInfoDialog(std::ref(CGI->generaltexth->allTexts[530]), CInfoWindow::TCompsInfo(), PlayerColor(1)); - } - catch(ExceptionNoTemplate & e) - { - (void)e; // unused - // Could not create a random map that fits current choices. - CInfoWindow::showInfoDialog(std::ref(CGI->generaltexth->allTexts[751]), CInfoWindow::TCompsInfo(), PlayerColor(1)); + logGlobal->error("Exception during startScenario: %s", e.what()); + + if(std::string(e.what()) == "ExceptionNoHuman") + CInfoWindow::showInfoDialog(std::ref(CGI->generaltexth->allTexts[530]), CInfoWindow::TCompsInfo(), PlayerColor(1)); + + if(std::string(e.what()) == "ExceptionNoTemplate") + CInfoWindow::showInfoDialog(std::ref(CGI->generaltexth->allTexts[751]), CInfoWindow::TCompsInfo(), PlayerColor(1)); } catch(...) { - + logGlobal->error("Unknown exception"); } } diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 000000000..f2b564057 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,197 @@ +from conan import ConanFile +from conan.tools.apple import is_apple_os +from conan.tools.cmake import CMakeDeps +from conans import tools + +import os + +class VCMI(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeToolchain" + requires = [ + "boost/1.79.0", + "ffmpeg/4.4", + "minizip/1.2.12", + "onetbb/2021.3.0", # Nullkiller AI + "qt/5.15.5", # launcher + "sdl/2.0.20", + "sdl_image/2.0.5", + "sdl_mixer/2.0.4", + "sdl_ttf/2.0.18", + ] + + def _disableQtOptions(disableFlag, options): + return " ".join([f"-{disableFlag}-{tool}" for tool in options]) + + _qtOptions = [ + _disableQtOptions("no", [ + "gif", + "ico", + ]), + _disableQtOptions("no-feature", [ + # xpm format is required for Drag'n'Drop support + "imageformat_bmp", + "imageformat_jpeg", + "imageformat_ppm", + "imageformat_xbm", + + # we need only macdeployqt + # TODO: disabling these doesn't disable generation of CMake targets + # TODO: in Qt 6.3 it's a part of qtbase + # "assistant", + # "designer", + # "distancefieldgenerator", + # "kmap2qmap", + # "linguist", + # "makeqpf", + # "pixeltool", + # "qdbus", + # "qev", + # "qtattributionsscanner", + # "qtdiag", + # "qtpaths", + # "qtplugininfo", + ]), + ] + + default_options = { + # shared libs + "boost/*:shared": True, + "libpng/*:shared": True, # SDL_image and Qt depend on it + "minizip/*:shared": True, + "onetbb/*:shared": True, + "qt/*:shared": True, + + # we need only the following Boost parts: + # date_time filesystem locale program_options system thread + # some other parts are also enabled because they're dependents + # see e.g. conan-center-index/recipes/boost/all/dependencies + "boost/*:without_context": True, + "boost/*:without_contract": True, + "boost/*:without_coroutine": True, + "boost/*:without_fiber": True, + "boost/*:without_graph": True, + "boost/*:without_graph_parallel": True, + "boost/*:without_iostreams": True, + "boost/*:without_json": True, + "boost/*:without_log": True, + "boost/*:without_math": True, + "boost/*:without_mpi": True, + "boost/*:without_nowide": True, + "boost/*:without_python": True, + "boost/*:without_random": True, + "boost/*:without_regex": True, + "boost/*:without_serialization": True, + "boost/*:without_stacktrace": True, + "boost/*:without_test": True, + "boost/*:without_timer": True, + "boost/*:without_type_erasure": True, + "boost/*:without_wave": True, + + "ffmpeg/*:avdevice": False, + "ffmpeg/*:avfilter": False, + "ffmpeg/*:postproc": False, + "ffmpeg/*:swresample": False, + "ffmpeg/*:with_freetype": False, + "ffmpeg/*:with_libfdk_aac": False, + "ffmpeg/*:with_libmp3lame": False, + "ffmpeg/*:with_libvpx": False, + "ffmpeg/*:with_libwebp": False, + "ffmpeg/*:with_libx264": False, + "ffmpeg/*:with_libx265": False, + "ffmpeg/*:with_openh264": False, + "ffmpeg/*:with_openjpeg": False, + "ffmpeg/*:with_opus": False, + "ffmpeg/*:with_programs": False, + "ffmpeg/*:with_ssl": False, + "ffmpeg/*:with_vorbis": False, + + "sdl/*:vulkan": False, + + "sdl_image/*:imageio": True, + "sdl_image/*:lbm": False, + "sdl_image/*:pnm": False, + "sdl_image/*:svg": False, + "sdl_image/*:tga": False, + "sdl_image/*:with_libjpeg": False, + "sdl_image/*:with_libtiff": False, + "sdl_image/*:with_libwebp": False, + "sdl_image/*:xcf": False, + "sdl_image/*:xpm": False, + "sdl_image/*:xv": False, + + "sdl_mixer/*:flac": False, + "sdl_mixer/*:mad": False, + "sdl_mixer/*:mikmod": False, + "sdl_mixer/*:modplug": False, + "sdl_mixer/*:nativemidi": False, + "sdl_mixer/*:opus": False, + "sdl_mixer/*:wav": False, + + "qt/*:config": " ".join(_qtOptions), + "qt/*:openssl": False, + "qt/*:qttools": True, + "qt/*:with_freetype": False, + "qt/*:with_libjpeg": False, + "qt/*:with_md4c": False, + "qt/*:with_mysql": False, + "qt/*:with_odbc": False, + "qt/*:with_openal": False, + "qt/*:with_pq": False, + + # transitive deps + "pcre2/*:build_pcre2grep": False, # doesn't link to overridden bzip2 & zlib, the tool isn't needed anyway + } + + def configure(self): + # workaround: macOS deployment target isn't passed to linker when building Boost + # TODO: remove when https://github.com/conan-io/conan-center-index/pull/12468 is merged + if is_apple_os(self): + osVersion = self.settings.get_safe("os.version") + if osVersion: + deploymentTargetFlag = tools.apple_deployment_target_flag( + self.settings.os, + osVersion, + self.settings.get_safe("os.sdk"), + self.settings.get_safe("os.subsystem"), + self.settings.get_safe("arch") + ) + self.options["boost"].extra_b2_flags = f"linkflags={deploymentTargetFlag}" + + def requirements(self): + # use Apple system libraries instead of external ones + if is_apple_os(self): + systemLibsOverrides = [ + "bzip2/1.0.8", + "libiconv/1.17", + "sqlite3/3.39.2", + "zlib/1.2.12", + ] + for lib in systemLibsOverrides: + self.requires(f"{lib}@kambala/apple", override=True) + + # TODO: the latest official release of LuaJIT (which is quite old) can't be built for arm Mac + if self.settings.os != "Macos" or self.settings.arch != "armv8": + self.requires("luajit/2.0.5") + + def generate(self): + deps = CMakeDeps(self) + if os.getenv("USE_CONAN_WITH_ALL_CONFIGS", "0") == "0": + deps.generate() + return + + # allow using prebuilt deps with all configs + # credits to https://github.com/conan-io/conan/issues/11607#issuecomment-1188500937 for the workaround + configs = [ + "Debug", + "MinSizeRel", + "Release", + "RelWithDebInfo", + ] + for config in configs: + print(f"generating CMakeDeps for {config}") + deps.configuration = config + deps.generate() + + def imports(self): + self.copy("*.dylib", "Frameworks", "lib") diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 6f8f0915a..66d39b630 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -363,12 +363,12 @@ "type" : "object", "default": {}, "additionalProperties" : false, - "required" : [ "repositoryURL", "enableInstalledMods", "autoCheckRepositories" ], + "required" : [ "repositoryURL", "enableInstalledMods", "autoCheckRepositories", "updateOnStartup", "updateConfigUrl" ], "properties" : { "repositoryURL" : { "type" : "array", "default" : [ - "http://download.vcmi.eu/mods/repository/repository.json" + "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/github.json" ], "items" : { "type" : "string" @@ -381,6 +381,14 @@ "autoCheckRepositories" : { "type" : "boolean", "default" : true + }, + "updateOnStartup" : { + "type" : "boolean", + "default" : true + }, + "updateConfigUrl" : { + "type" : "string", + "default" : "https://raw.githubusercontent.com/vcmi/vcmi-updates/master/vcmi-updates.json" } } } diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 1db1b3171..e9fd1ec00 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -32,6 +32,7 @@ set(launcher_SRCS mainwindow_moc.cpp launcherdirs.cpp jsonutils.cpp + updatedialog_moc.cpp ) set(launcher_HEADERS @@ -41,6 +42,7 @@ set(launcher_HEADERS mainwindow_moc.h launcherdirs.h jsonutils.h + updatedialog_moc.h ) set(launcher_FORMS @@ -48,6 +50,7 @@ set(launcher_FORMS modManager/imageviewer_moc.ui settingsView/csettingsview_moc.ui mainwindow_moc.ui + updatedialog_moc.ui ) assign_source_group(${launcher_SRCS} ${launcher_HEADERS} VCMI_launcher.rc) diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 08b4b8d63..8aa817664 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -19,6 +19,8 @@ #include "../lib/filesystem/Filesystem.h" #include "../lib/logging/CBasicLogConfigurator.h" +#include "updatedialog_moc.h" + void MainWindow::load() { // Set current working dir to executable folder. @@ -80,6 +82,9 @@ MainWindow::MainWindow(QWidget * parent) connect(ui->tabSelectList, SIGNAL(currentRowChanged(int)), ui->tabListWidget, SLOT(setCurrentIndex(int))); + + if(settings["launcher"]["updateOnStartup"].Bool()) + UpdateDialog::showUpdateDialog(false); } MainWindow::~MainWindow() diff --git a/launcher/modManager/cdownloadmanager_moc.cpp b/launcher/modManager/cdownloadmanager_moc.cpp index c3fade601..eb3a20b8e 100644 --- a/launcher/modManager/cdownloadmanager_moc.cpp +++ b/launcher/modManager/cdownloadmanager_moc.cpp @@ -20,6 +20,7 @@ CDownloadManager::CDownloadManager() void CDownloadManager::downloadFile(const QUrl & url, const QString & file) { + filename = file; QNetworkRequest request(url); FileEntry entry; entry.file.reset(new QFile(CLauncherDirs::get().downloadsPath() + '/' + file)); @@ -61,6 +62,23 @@ CDownloadManager::FileEntry & CDownloadManager::getEntry(QNetworkReply * reply) void CDownloadManager::downloadFinished(QNetworkReply * reply) { FileEntry & file = getEntry(reply); + + QVariant possibleRedirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + QUrl qurl = possibleRedirectUrl.toUrl(); + + if(possibleRedirectUrl.isValid()) + { + for(int i = 0; i< currentDownloads.size(); ++i) + { + if(currentDownloads[i].file == file.file) + { + currentDownloads.removeAt(i); + break; + } + } + downloadFile(qurl, filename); + return; + } if(file.reply->error()) { diff --git a/launcher/modManager/cdownloadmanager_moc.h b/launcher/modManager/cdownloadmanager_moc.h index 9bce4b359..94e808cac 100644 --- a/launcher/modManager/cdownloadmanager_moc.h +++ b/launcher/modManager/cdownloadmanager_moc.h @@ -35,6 +35,8 @@ class CDownloadManager : public QObject }; QStringList encounteredErrors; + + QString filename; QNetworkAccessManager manager; diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index e49d629d5..0d830899f 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -24,21 +24,21 @@ static QString detectModArchive(QString path, QString modName) QString modDirName; - for(auto file : files) + for(int folderLevel : {0, 1}) //search in subfolder if there is no mod.json in the root { - QString filename = QString::fromUtf8(file.c_str()); - if(filename.toLower().startsWith(modName)) + for(auto file : files) { - // archive must contain mod.json file - if(filename.toLower() == modName + "/mod.json") - modDirName = filename.section('/', 0, 0); - } - else // all files must be in directory - { - return ""; + QString filename = QString::fromUtf8(file.c_str()); + modDirName = filename.section('/', 0, folderLevel); + + if(filename == modDirName + "/mod.json") + { + return modDirName; + } } } - return modDirName; + + return ""; } CModManager::CModManager(CModList * modList) @@ -259,7 +259,18 @@ bool CModManager::doInstallMod(QString modname, QString archivePath) return addError(modname, "Failed to extract mod data"); } - QVariantMap json = JsonUtils::JsonFromFile(destDir + modDirName + "/mod.json").toMap(); + //rename folder and fix the path + QDir extractedDir(destDir + modDirName); + auto rc = QFile::rename(destDir + modDirName, destDir + modname); + if (rc) + extractedDir.setPath(destDir + modname); + + //there are possible excessive files - remove them + QString upperLevel = modDirName.section('/', 0, 0); + if(upperLevel != modDirName) + removeModDir(destDir + upperLevel); + + QVariantMap json = JsonUtils::JsonFromFile(destDir + modname + "/mod.json").toMap(); localMods.insert(modname, json); modList->setLocalModList(localMods); @@ -280,8 +291,9 @@ bool CModManager::doUninstallMod(QString modname) if(!localMods.contains(modname)) return addError(modname, "Data with this mod was not found"); + QDir modFullDir(modDir); if(!removeModDir(modDir)) - return addError(modname, "Failed to delete mod data"); + return addError(modname, "Mod is located in protected directory, plase remove it manually:\n" + modFullDir.absolutePath()); localMods.remove(modname); modList->setLocalModList(localMods); diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 58534d506..106666988 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -11,6 +11,8 @@ #include "csettingsview_moc.h" #include "ui_csettingsview_moc.h" +#include "../updatedialog_moc.h" + #include #include @@ -232,3 +234,9 @@ void CSettingsView::on_comboBoxAutoSave_currentIndexChanged(int index) Settings node = settings.write["general"]["saveFrequency"]; node->Integer() = index; } + +void CSettingsView::on_updatesButton_clicked() +{ + UpdateDialog::showUpdateDialog(true); +} + diff --git a/launcher/settingsView/csettingsview_moc.h b/launcher/settingsView/csettingsview_moc.h index 79079a314..b2084a78a 100644 --- a/launcher/settingsView/csettingsview_moc.h +++ b/launcher/settingsView/csettingsview_moc.h @@ -63,6 +63,8 @@ private slots: void on_comboBoxAutoSave_currentIndexChanged(int index); + void on_updatesButton_clicked(); + private: Ui::CSettingsView * ui; }; diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index 72b6cb512..5f9607a85 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -6,8 +6,8 @@ 0 0 - 738 - 471 + 779 + 619 @@ -26,40 +26,15 @@ 0 - - - - Log files directory - - - - - - - 1 - - - - Off - - - - - On - - - - - - + + - 75 true - Data Directories + AI on the map @@ -67,7 +42,6 @@ - 75 true @@ -76,68 +50,6 @@ - - - - Resolution - - - - - - - Open - - - - - - - - BattleAI - - - - - StupidAI - - - - - - - - User data directory - - - - - - - - 0 - 22 - - - - Enemy AI - - - - - - - Check repositories on startup - - - - - - - Display index - - - @@ -145,91 +57,6 @@ - - - - - 75 - true - - - - General - - - - - - - - 0 - - - - - - - - false - - - Change - - - - - - - - VCAI - - - - - Nullkiller - - - - - - - - false - - - - 150 - 0 - - - - /home/user/.vcmi - - - true - - - - - - - - 75 - true - - - - Repositories - - - - - - - Open - - - @@ -243,86 +70,6 @@ - - - - QPlainTextEdit::NoWrap - - - http://downloads.vcmi.eu/Mods/repository.json - - - - - - - Open - - - - - - - - 0 - 22 - - - - Friendly AI - - - - - - - Player AI - - - - - - - false - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - Fullscreen - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 56 - 8 - - - - @@ -336,10 +83,31 @@ - - + + - Extra data directory + Heroes III character set + + + + + + + Real fullscreen mode + + + + + + + Resolution + + + + + + + Fullscreen @@ -379,16 +147,72 @@ - - + + - 75 true - AI on the map + General + + + + + + + QPlainTextEdit::NoWrap + + + http://downloads.vcmi.eu/Mods/repository.json + + + + + + + Open + + + + + + + Player AI + + + + + + + + true + + + + Repositories + + + + + + + + VCAI + + + + + Nullkiller + + + + + + + + Display index @@ -409,11 +233,67 @@ + + + + + BattleAI + + + + + StupidAI + + + + + + + + false + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + Open + + + - 75 true @@ -422,10 +302,33 @@ - - + + - Network port + Extra data directory + + + + + + + + 0 + 22 + + + + Enemy AI + + + + + + + false + + + Change @@ -449,7 +352,7 @@ 1024x768 - + 1181x664 @@ -506,8 +409,24 @@ - - + + + + Neutral AI + + + + + + + + 0 + + + + + + Qt::Vertical @@ -516,14 +435,67 @@ - 20 + 56 8 - - + + + + false + + + + 150 + 0 + + + + /home/user/.vcmi + + + true + + + + + + + 1 + + + + Off + + + + + On + + + + + + + + 1 + + + + Off + + + + + On + + + + + + false @@ -542,6 +514,96 @@ + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 8 + + + + + + + + + true + true + false + + + + AI in the battlefield + + + + + + + Check repositories on startup + + + + + + + + 0 + 22 + + + + Friendly AI + + + + + + + Log files directory + + + + + + + + true + + + + Data Directories + + + + + + + User data directory + + + + + + + Open + + + + + + + Network port + + + @@ -576,58 +638,6 @@ - - - - - 75 - true - true - false - - - - AI in the battlefield - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 8 - 20 - - - - - - - - Heroes III character set - - - - - - - Neutral AI - - - - - - - Real fullscreen mode - - - @@ -635,21 +645,11 @@ - - - - 1 + + + + Updates - - - Off - - - - - On - - diff --git a/launcher/updatedialog_moc.cpp b/launcher/updatedialog_moc.cpp new file mode 100644 index 000000000..d5cd01ed1 --- /dev/null +++ b/launcher/updatedialog_moc.cpp @@ -0,0 +1,147 @@ +/* + * updatedialog_moc.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 "updatedialog_moc.h" +#include "ui_updatedialog_moc.h" + +#include "../lib/CConfigHandler.h" +#include "../lib/GameConstants.h" + +#include +#include + +UpdateDialog::UpdateDialog(bool calledManually, QWidget *parent): + QDialog(parent), + ui(new Ui::UpdateDialog), + calledManually(calledManually) +{ + ui->setupUi(this); + + if(calledManually) + { + setWindowModality(Qt::ApplicationModal); + show(); + } + + connect(ui->closeButton, SIGNAL(clicked()), this, SLOT(close())); + + if(settings["launcher"]["updateOnStartup"].Bool()) + ui->checkOnStartup->setCheckState(Qt::CheckState::Checked); + + currentVersion = GameConstants::VCMI_VERSION; + + setWindowTitle(QString::fromStdString(currentVersion)); + +#ifdef VCMI_WINDOWS + platformParameter = "windows"; +#elif defined(VCMI_MAC) + platformParameter = "macos"; +#elif defined(VCMI_IOS) + platformParameter = "ios"; +#elif defined(VCMI_ANDROID) + platformParameter = "android"; +#elif defined(VCMI_UNIX) + platformParameter = "linux"; +#endif + + QString url = QString::fromStdString(settings["launcher"]["updateConfigUrl"].String()); + + QNetworkReply *response = networkManager.get(QNetworkRequest(QUrl(url))); + + connect(response, &QNetworkReply::finished, [&, response]{ + response->deleteLater(); + + if(response->error() != QNetworkReply::NoError) + { + ui->versionLabel->setStyleSheet("QLabel { background-color : red; color : black; }"); + ui->versionLabel->setText("Network error"); + ui->plainTextEdit->setPlainText(response->errorString()); + return; + } + + auto byteArray = response->readAll(); + JsonNode node(byteArray.constData(), byteArray.size()); + loadFromJson(node); + }); +} + +UpdateDialog::~UpdateDialog() +{ + delete ui; +} + +void UpdateDialog::showUpdateDialog(bool isManually) +{ + UpdateDialog * dialog = new UpdateDialog(isManually); + + dialog->setAttribute(Qt::WA_DeleteOnClose); +} + +void UpdateDialog::on_checkOnStartup_stateChanged(int state) +{ + Settings node = settings.write["launcher"]["updateOnStartup"]; + node->Bool() = ui->checkOnStartup->isChecked(); +} + +void UpdateDialog::loadFromJson(const JsonNode & node) +{ + if(node.getType() != JsonNode::JsonType::DATA_STRUCT || + node["updateType"].getType() != JsonNode::JsonType::DATA_STRING || + node["version"].getType() != JsonNode::JsonType::DATA_STRING || + node["changeLog"].getType() != JsonNode::JsonType::DATA_STRING || + node.getType() != JsonNode::JsonType::DATA_STRUCT) //we need at least one link - other are optional + { + ui->plainTextEdit->setPlainText("Cannot read JSON from url or incorrect JSON data"); + return; + } + + //check whether update is needed + bool isFutureVersion = true; + std::string newVersion = node["version"].String(); + for(auto & prevVersion : node["history"].Vector()) + { + if(prevVersion.String() == currentVersion) + isFutureVersion = false; + } + + if(isFutureVersion || currentVersion == newVersion) + { + if(!calledManually) + close(); + + return; + } + + if(!calledManually) + { + setWindowModality(Qt::ApplicationModal); + show(); + } + + const auto updateType = node["updateType"].String(); + + QString bgColor; + if(updateType == "minor") + bgColor = "gray"; + else if(updateType == "major") + bgColor = "orange"; + else if(updateType == "critical") + bgColor = "red"; + + ui->versionLabel->setStyleSheet(QString("QLabel { background-color : %1; color : black; }").arg(bgColor)); + ui->versionLabel->setText(QString::fromStdString(newVersion)); + ui->plainTextEdit->setPlainText(QString::fromStdString(node["changeLog"].String())); + + QString downloadLink = QString::fromStdString(node["downloadLinks"]["other"].String()); + if(node["downloadLinks"][platformParameter].getType() == JsonNode::JsonType::DATA_STRING) + downloadLink = QString::fromStdString(node["downloadLinks"][platformParameter].String()); + + ui->downloadLink->setText(QString{"Download page"}.arg(downloadLink)); +} diff --git a/launcher/updatedialog_moc.h b/launcher/updatedialog_moc.h new file mode 100644 index 000000000..aab93ff89 --- /dev/null +++ b/launcher/updatedialog_moc.h @@ -0,0 +1,44 @@ +/* + * updatedialog_moc.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once +#include +#include + +class JsonNode; + +namespace Ui { +class UpdateDialog; +} + +class UpdateDialog : public QDialog +{ + Q_OBJECT + +public: + explicit UpdateDialog(bool calledManually, QWidget *parent = nullptr); + ~UpdateDialog(); + + static void showUpdateDialog(bool isManually); + +private slots: + void on_checkOnStartup_stateChanged(int state); + +private: + Ui::UpdateDialog *ui; + + std::string currentVersion; + std::string platformParameter = "other"; + + QNetworkAccessManager networkManager; + + bool calledManually; + + void loadFromJson(const JsonNode & node); +}; diff --git a/launcher/updatedialog_moc.ui b/launcher/updatedialog_moc.ui new file mode 100644 index 000000000..7693302f7 --- /dev/null +++ b/launcher/updatedialog_moc.ui @@ -0,0 +1,128 @@ + + + UpdateDialog + + + + 0 + 0 + 371 + 247 + + + + + 0 + 0 + + + + + 371 + 247 + + + + + + + + 12 + + + 12 + + + 12 + + + 12 + + + + + + 0 + 0 + + + + + 0 + 48 + + + + + 18 + true + + + + false + + + QLabel {background-color: green; color : black} + + + QFrame::Raised + + + You have latest version + + + + + + + true + + + + + + + + + + + 0 + 0 + + + + Close + + + + + + + Check updates on startup + + + + + + + + 0 + 0 + + + + + + + true + + + Qt::TextBrowserInteraction + + + + + + + + diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 04f496875..8a9f9873f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -440,7 +440,7 @@ assign_source_group(${lib_SRCS} ${lib_HEADERS}) add_library(vcmi SHARED ${lib_SRCS} ${lib_HEADERS}) set_target_properties(vcmi PROPERTIES COMPILE_DEFINITIONS "VCMI_DLL=1") target_link_libraries(vcmi PUBLIC - minizip::minizip SDL2::SDL2 ZLIB::ZLIB + minizip::minizip ZLIB::ZLIB ${SYSTEM_LIBS} Boost::boost Boost::thread Boost::filesystem Boost::program_options Boost::locale Boost::date_time ) @@ -448,6 +448,7 @@ target_include_directories(vcmi PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_HOME_DIRECTORY} PUBLIC ${CMAKE_HOME_DIRECTORY}/include + PRIVATE ${SDL2_INCLUDE_DIR} ) if(WIN32) diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index a4f0dc75d..50eaf021f 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -948,7 +948,7 @@ void CBonusSystemNode::getAllBonusesRec(BonusList &out) const getAllParents(lparents); for(auto parent : lparents) - parent->bonuses.getAllBonuses(beforeUpdate); + parent->getAllBonusesRec(beforeUpdate); bonuses.getAllBonuses(beforeUpdate); diff --git a/lib/StartInfo.cpp b/lib/StartInfo.cpp index 0b92a5e56..97b703616 100644 --- a/lib/StartInfo.cpp +++ b/lib/StartInfo.cpp @@ -64,7 +64,7 @@ std::string StartInfo::getCampaignName() const void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const { if(!mi) - throw ExceptionMapMissing(); + throw std::domain_error("ExceptionMapMissing"); //there must be at least one human player before game can be started std::map::const_iterator i; @@ -73,12 +73,12 @@ void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const break; if(i == si->playerInfos.cend() && !ignoreNoHuman) - throw ExceptionNoHuman(); + throw std::domain_error("ExceptionNoHuman"); if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME) { if(!si->mapGenOptions->checkOptions()) - throw ExceptionNoTemplate(); + throw std::domain_error("ExceptionNoTemplate"); } } diff --git a/lib/StartInfo.h b/lib/StartInfo.h index 1cd5bb77d..2f0fb3b71 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -175,6 +175,3 @@ struct DLL_LINKAGE LobbyInfo : public LobbyState TeamID getPlayerTeamId(PlayerColor color); }; -class ExceptionMapMissing : public std::exception {}; -class ExceptionNoHuman : public std::exception {}; -class ExceptionNoTemplate : public std::exception {}; diff --git a/lib/filesystem/CBinaryReader.cpp b/lib/filesystem/CBinaryReader.cpp index 643dcdd71..5a4808003 100644 --- a/lib/filesystem/CBinaryReader.cpp +++ b/lib/filesystem/CBinaryReader.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "CBinaryReader.h" +//FIXME:library file depends on SDL - make cause troubles #include #include "CInputStream.h" #include "../CGeneralTextHandler.h" diff --git a/lib/spells/TargetCondition.cpp b/lib/spells/TargetCondition.cpp index 47d1e113c..cb4ddc976 100644 --- a/lib/spells/TargetCondition.cpp +++ b/lib/spells/TargetCondition.cpp @@ -298,7 +298,7 @@ protected: //this stack is from other player else if(battleWideNegation) { - if(!m->ownerMatches(target, false)) + if(m->ownerMatches(target, false)) return true; } return false; diff --git a/osx/CMakeLists.txt b/osx/CMakeLists.txt index 84f0d2d0b..990a0ee15 100644 --- a/osx/CMakeLists.txt +++ b/osx/CMakeLists.txt @@ -1,38 +1,49 @@ # We need to keep this code into separate directory so CMake will execute it after all other subdirectories install code # Otherwise we can't fix Mac bundle dependencies since binaries wouldn't be there when this code executed if(APPLE) + set(bundleDir "\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_DIR}") + set(bundleContentsDir "${bundleDir}/Contents") + if(ENABLE_LAUNCHER) - find_program(TOOL_MACDEPLOYQT NAMES macdeployqt PATHS ${qt_base_dir}/bin) - if(NOT TOOL_MACDEPLOYQT) - message(FATAL_ERROR "Could not find macdeployqt") + # cross-compiled Qt 5 builds macdeployqt for target platform instead of host + if(CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL CMAKE_SYSTEM_PROCESSOR) + # deploy Qt dylibs with macdeployqt + find_program(TOOL_MACDEPLOYQT NAMES macdeployqt PATHS ${qt_base_dir}/bin) + if(NOT TOOL_MACDEPLOYQT) + message(FATAL_ERROR "Could not find macdeployqt") + endif() + install(CODE " + execute_process(COMMAND + \"${TOOL_MACDEPLOYQT}\" \"${bundleDir}\" -verbose=2 + ) + ") + else() + # simulate macdeployqt behavior, main Qt libs are copied by conan + get_target_property(qmakePath Qt5::qmake IMPORTED_LOCATION) + execute_process(COMMAND + "${qmakePath}" -query QT_INSTALL_PLUGINS + OUTPUT_VARIABLE qtPluginsDir + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + install(DIRECTORY + ${qtPluginsDir}/ + DESTINATION ${APP_BUNDLE_DIR}/Contents/PlugIns + ) + install(CODE " + file(WRITE ${bundleContentsDir}/Resources/qt.conf + \"[Paths]\nPlugins = PlugIns\" + ) + ") endif() - install(CODE " - execute_process(COMMAND ${TOOL_MACDEPLOYQT} \"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_DIR}\" -verbose=2) - ") endif() - # Manually fix VCMI library links in AI libraries with install_name_tool + # deploy other dylibs with conan install(CODE " - set(BU_CHMOD_BUNDLE_ITEMS ON) - include(BundleUtilities) - fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_DIR}\" \"\" \"\") - execute_process(COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change \"libvcmi.dylib\" \"@executable_path/../Frameworks/libvcmi.dylib\" \"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_BINARY_DIR}/AI/libBattleAI.dylib\") - execute_process(COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change \"libvcmi.dylib\" \"@executable_path/../Frameworks/libvcmi.dylib\" \"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_BINARY_DIR}/AI/libEmptyAI.dylib\") - execute_process(COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change \"libvcmi.dylib\" \"@executable_path/../Frameworks/libvcmi.dylib\" \"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_BINARY_DIR}/AI/libNullkiller.dylib\") - execute_process(COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change \"libvcmi.dylib\" \"@executable_path/../Frameworks/libvcmi.dylib\" \"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_BINARY_DIR}/AI/libStupidAI.dylib\") - execute_process(COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change \"libvcmi.dylib\" \"@executable_path/../Frameworks/libvcmi.dylib\" \"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_BINARY_DIR}/AI/libVCAI.dylib\") - - set(ENABLE_ERM ${ENABLE_ERM}) - if(ENABLE_ERM) - execute_process(COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change \"libvcmi.dylib\" \"@executable_path/../Frameworks/libvcmi.dylib\" \"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_BINARY_DIR}/scripting/libvcmiERM.dylib\") - endif() - set(ENABLE_LUA ${ENABLE_LUA}) - if(ENABLE_LUA) - execute_process(COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change \"libvcmi.dylib\" \"@executable_path/../Frameworks/libvcmi.dylib\" \"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_BINARY_DIR}/scripting/libvcmiLua.dylib\") - endif() - - execute_process(COMMAND rm \"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_BINARY_DIR}/libvcmi.dylib\") - " COMPONENT Runtime) + execute_process(COMMAND + conan imports \"${CMAKE_SOURCE_DIR}\" --install-folder \"${CMAKE_SOURCE_DIR}/conan-generated\" --import-folder \"${bundleContentsDir}\" + ) + file(REMOVE \"${bundleContentsDir}/conan_imports_manifest.txt\") + ") endif(APPLE) # This will likely only work for Vcpkg diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7c8a5aa05..4f4a562a2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,4 @@ -if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") - include(GoogleTest) -endif() +include(GoogleTest) set(googleTest_Dir ${CMAKE_CURRENT_SOURCE_DIR}/googletest) if(EXISTS ${googleTest_Dir})